From bd4cc86aa988b417dfc899b52fc3a2546a3a659b Mon Sep 17 00:00:00 2001 From: "xan.lol" Date: Mon, 28 Sep 2026 20:55:39 -0700 Subject: [PATCH] copy handle + outlink icon on profile also gives handle on profiles more vertical space --- src/screens/Profile/Header/Handle.tsx | 126 ++++++++++++++---- .../Profile/Header/ProfileHeaderLabeler.tsx | 2 +- .../Profile/Header/ProfileHeaderStandard.tsx | 2 +- 3 files changed, 105 insertions(+), 25 deletions(-) diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index e003f37a9..5747d66c4 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -1,17 +1,19 @@ -import {View} from 'react-native' +import {useEffect, useState} from 'react' +import {Pressable, View} from 'react-native' import {type GestureResponderEvent} from 'react-native' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import * as ExpoClipboard from 'expo-clipboard' +import {Trans, useLingui} from '@lingui/react/macro' import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles' import {type Shadow} from '#/state/cache/types' import {useHideDisplayNames} from '#/state/preferences/hide-display-names' import {useShowFollowsYouBadge} from '#/state/preferences/show-follows-you-badge' import {atoms as a, useTheme, web} from '#/alf' -import {InlineLinkText} from '#/components/Link.tsx' +import {SquareArrowTopRight_Stroke2_Corner0_Rounded as OutlinkIcon} from '#/components/icons/SquareArrowTopRight' +import {Link} from '#/components/Link' import {NewskieDialog} from '#/components/NewskieDialog' import {PronounPill} from '#/components/PronounPill' +import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_IOS, IS_NATIVE} from '#/env' import {type app} from '#/lexicons' @@ -22,14 +24,22 @@ export function ProfileHeaderHandle({ disableTaps, disableAuxiliaryTaps, onLinkPress, + verticalPadding = 0, }: { profile: Shadow disableTaps?: boolean disableAuxiliaryTaps?: boolean onLinkPress?: (e: GestureResponderEvent) => void | false + verticalPadding?: number }) { const t = useTheme() - const {_} = useLingui() + const {t: l} = useLingui() + const [copied, setCopied] = useState<{handle: string} | null>(null) + useEffect(() => { + if (!copied) return + const timeout = setTimeout(() => setCopied(null), 2000) + return () => clearTimeout(timeout) + }, [copied]) const hideDisplayNames = useHideDisplayNames() const invalidHandle = isInvalidHandle(profile.handle) const pronouns = profile.pronouns @@ -39,7 +49,7 @@ export function ProfileHeaderHandle({ const disableNewskieDialog = disableTaps || disableAuxiliaryTaps const sanitized = sanitizeHandle( profile.handle, - '@', + '', // forceLTR handled by CSS above on web IS_NATIVE, ) @@ -60,9 +70,32 @@ export function ProfileHeaderHandle({ unicodeBidi: 'isolate', }), ] + const HandleArea = IS_NATIVE ? Pressable : View + const AtSection = IS_NATIVE ? View : Pressable + const copyProps = { + testID: 'profileHeaderCopyHandleBtn', + accessibilityRole: 'button' as const, + accessibilityLabel: l`Copy handle`, + accessibilityHint: l`Copy this handle to the clipboard`, + accessibilityState: { + disabled: Boolean(disableTaps || disableAuxiliaryTaps), + }, + disabled: disableTaps || disableAuxiliaryTaps, + onPress: async () => { + const handle = `@${profile.handle}` + await ExpoClipboard.setStringAsync(handle) + setCopied({handle: profile.handle}) + Toast.show(l`Copied ${handle}`, {type: 'success'}) + }, + } return ( {showFollowsYouBadge && profile.viewer?.followedBy && !blockHide ? ( @@ -72,26 +105,73 @@ export function ProfileHeaderHandle({ ) : undefined} - + {!hideDisplayNames && (invalidHandle ? ( - {_(msg`⚠Invalid Handle`)} + {l`⚠Invalid Handle`} - ) : shouldShowProfileLink ? ( - - {sanitized} - ) : ( - - {sanitized} - + + + + + @ + + {copied?.handle === profile.handle && ( + + ✓ + + )} + + + {sanitized} + + + {shouldShowProfileLink && ( + { + if (disableTaps || disableAuxiliaryTaps) return false + return onLinkPress?.(e) + }}> + + + )} + ))} diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index 9b3a9909c..6db85639e 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -83,7 +83,7 @@ let ProfileHeaderLabeler = ({ - + {!isPlaceholderProfile && ( <> diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 4f5a06d3d..e88a12593 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -205,7 +205,7 @@ let ProfileHeaderStandard = ({ profile={profile} moderation={moderation} /> - + {!isPlaceholderProfile && !isBlockedUser && ( -- knotmirror 0.0.1