about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Menu/index.tsx37
-rw-r--r--src/components/Menu/index.web.tsx4
-rw-r--r--src/components/PostControls/DiscoverDebug.tsx54
-rw-r--r--src/components/PostControls/PostControlButton.tsx126
-rw-r--r--src/components/PostControls/PostMenu/PostMenuItems.tsx (renamed from src/view/com/util/forms/PostDropdownBtnMenuItems.tsx)147
-rw-r--r--src/components/PostControls/PostMenu/index.tsx (renamed from src/view/com/util/forms/PostDropdownBtn.tsx)59
-rw-r--r--src/components/PostControls/RepostButton.tsx (renamed from src/view/com/util/post-ctrls/RepostButton.tsx)89
-rw-r--r--src/components/PostControls/RepostButton.web.tsx (renamed from src/view/com/util/post-ctrls/RepostButton.web.tsx)110
-rw-r--r--src/components/PostControls/ShareMenu/RecentChats.tsx200
-rw-r--r--src/components/PostControls/ShareMenu/ShareMenuItems.tsx197
-rw-r--r--src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx22
-rw-r--r--src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx192
-rw-r--r--src/components/PostControls/ShareMenu/index.tsx119
-rw-r--r--src/components/PostControls/index.tsx (renamed from src/view/com/util/post-ctrls/PostCtrls.tsx)254
-rw-r--r--src/components/icons/ArrowOutOfBox.tsx5
-rw-r--r--src/components/icons/ArrowShareRight.tsx5
-rw-r--r--src/components/icons/ChainLink.tsx5
-rw-r--r--src/lib/statsig/gates.ts1
-rw-r--r--src/logger/metrics.ts8
-rw-r--r--src/screens/Hashtag.tsx10
-rw-r--r--src/screens/Profile/components/ProfileFeedHeader.tsx2
-rw-r--r--src/screens/StarterPack/StarterPackScreen.tsx30
-rw-r--r--src/screens/Topic.tsx10
-rw-r--r--src/screens/VideoFeed/index.tsx4
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx6
-rw-r--r--src/view/com/post/Post.tsx22
-rw-r--r--src/view/com/posts/PostFeedItem.tsx9
-rw-r--r--src/view/com/profile/ProfileMenu.tsx29
28 files changed, 1224 insertions, 532 deletions
diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx
index 76fc74dc1..c5ccfa5ec 100644
--- a/src/components/Menu/index.tsx
+++ b/src/components/Menu/index.tsx
@@ -244,6 +244,38 @@ export function ItemRadio({selected}: {selected: boolean}) {
   )
 }
 
+/**
+ * NATIVE ONLY - for adding non-pressable items to the menu
+ *
+ * @platform ios, android
+ */
+export function ContainerItem({
+  children,
+  style,
+}: {
+  children: React.ReactNode
+  style?: StyleProp<ViewStyle>
+}) {
+  const t = useTheme()
+  return (
+    <View
+      style={[
+        a.flex_row,
+        a.align_center,
+        a.gap_sm,
+        a.px_md,
+        a.rounded_md,
+        a.border,
+        t.atoms.bg_contrast_25,
+        t.atoms.border_contrast_low,
+        {paddingVertical: 10},
+        style,
+      ]}>
+      {children}
+    </View>
+  )
+}
+
 export function LabelText({children}: {children: React.ReactNode}) {
   const t = useTheme()
   return (
@@ -272,13 +304,14 @@ export function Group({children, style}: GroupProps) {
         style,
       ]}>
       {flattenReactChildren(children).map((child, i) => {
-        return React.isValidElement(child) && child.type === Item ? (
+        return React.isValidElement(child) &&
+          (child.type === Item || child.type === ContainerItem) ? (
           <React.Fragment key={i}>
             {i > 0 ? (
               <View style={[a.border_b, t.atoms.border_contrast_low]} />
             ) : null}
             {React.cloneElement(child, {
-              // @ts-ignore
+              // @ts-expect-error cloneElement is not aware of the types
               style: {
                 borderRadius: 0,
                 borderWidth: 0,
diff --git a/src/components/Menu/index.web.tsx b/src/components/Menu/index.web.tsx
index 27678bf2f..7d6e50556 100644
--- a/src/components/Menu/index.web.tsx
+++ b/src/components/Menu/index.web.tsx
@@ -390,3 +390,7 @@ export function Divider() {
     />
   )
 }
+
+export function ContainerItem() {
+  return null
+}
diff --git a/src/components/PostControls/DiscoverDebug.tsx b/src/components/PostControls/DiscoverDebug.tsx
new file mode 100644
index 000000000..796981f0c
--- /dev/null
+++ b/src/components/PostControls/DiscoverDebug.tsx
@@ -0,0 +1,54 @@
+import {Pressable} from 'react-native'
+import * as Clipboard from 'expo-clipboard'
+import {t} from '@lingui/macro'
+
+import {IS_INTERNAL} from '#/lib/app-info'
+import {DISCOVER_DEBUG_DIDS} from '#/lib/constants'
+import {useGate} from '#/lib/statsig/statsig'
+import {useSession} from '#/state/session'
+import * as Toast from '#/view/com/util/Toast'
+import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
+
+export function DiscoverDebug({
+  feedContext,
+}: {
+  feedContext: string | undefined
+}) {
+  const {currentAccount} = useSession()
+  const {gtMobile} = useBreakpoints()
+  const gate = useGate()
+  const isDiscoverDebugUser =
+    IS_INTERNAL ||
+    DISCOVER_DEBUG_DIDS[currentAccount?.did || ''] ||
+    gate('debug_show_feedcontext')
+  const theme = useTheme()
+
+  return (
+    isDiscoverDebugUser &&
+    feedContext && (
+      <Pressable
+        accessible={false}
+        hitSlop={10}
+        style={[
+          a.absolute,
+          a.bottom_0,
+          {zIndex: 1000},
+          gtMobile ? a.right_0 : a.left_0,
+        ]}
+        onPress={e => {
+          e.stopPropagation()
+          Clipboard.setStringAsync(feedContext)
+          Toast.show(t`Copied to clipboard`, 'clipboard-check')
+        }}>
+        <Text
+          style={{
+            color: theme.palette.contrast_400,
+            fontSize: 7,
+          }}>
+          {feedContext}
+        </Text>
+      </Pressable>
+    )
+  )
+}
diff --git a/src/components/PostControls/PostControlButton.tsx b/src/components/PostControls/PostControlButton.tsx
new file mode 100644
index 000000000..1585d429d
--- /dev/null
+++ b/src/components/PostControls/PostControlButton.tsx
@@ -0,0 +1,126 @@
+import {createContext, useContext, useMemo} from 'react'
+import {type GestureResponderEvent, type View} from 'react-native'
+
+import {POST_CTRL_HITSLOP} from '#/lib/constants'
+import {useHaptics} from '#/lib/haptics'
+import {atoms as a, useTheme} from '#/alf'
+import {Button, type ButtonProps} from '#/components/Button'
+import {type Props as SVGIconProps} from '#/components/icons/common'
+import {Text, type TextProps} from '#/components/Typography'
+
+const PostControlContext = createContext<{
+  big?: boolean
+  active?: boolean
+  color?: {color: string}
+}>({})
+
+// Base button style, which the the other ones extend
+export function PostControlButton({
+  ref,
+  onPress,
+  onLongPress,
+  children,
+  big,
+  active,
+  activeColor,
+  ...props
+}: ButtonProps & {
+  ref?: React.Ref<View>
+  active?: boolean
+  big?: boolean
+  color?: string
+  activeColor?: string
+}) {
+  const t = useTheme()
+  const playHaptic = useHaptics()
+
+  const ctx = useMemo(
+    () => ({
+      big,
+      active,
+      color: {
+        color: activeColor && active ? activeColor : t.palette.contrast_500,
+      },
+    }),
+    [big, active, activeColor, t.palette.contrast_500],
+  )
+
+  const style = useMemo(
+    () => [
+      a.flex_row,
+      a.align_center,
+      a.gap_xs,
+      a.bg_transparent,
+      {padding: 5},
+    ],
+    [],
+  )
+
+  const handlePress = useMemo(() => {
+    if (!onPress) return
+    return (evt: GestureResponderEvent) => {
+      playHaptic('Light')
+      onPress(evt)
+    }
+  }, [onPress, playHaptic])
+
+  const handleLongPress = useMemo(() => {
+    if (!onLongPress) return
+    return (evt: GestureResponderEvent) => {
+      playHaptic('Heavy')
+      onLongPress(evt)
+    }
+  }, [onLongPress, playHaptic])
+
+  return (
+    <Button
+      ref={ref}
+      onPress={handlePress}
+      onLongPress={handleLongPress}
+      style={style}
+      hoverStyle={t.atoms.bg_contrast_25}
+      shape="round"
+      variant="ghost"
+      color="secondary"
+      hitSlop={POST_CTRL_HITSLOP}
+      {...props}>
+      {typeof children === 'function' ? (
+        args => (
+          <PostControlContext.Provider value={ctx}>
+            {children(args)}
+          </PostControlContext.Provider>
+        )
+      ) : (
+        <PostControlContext.Provider value={ctx}>
+          {children}
+        </PostControlContext.Provider>
+      )}
+    </Button>
+  )
+}
+
+export function PostControlButtonIcon({
+  icon: Comp,
+}: {
+  icon: React.ComponentType<SVGIconProps>
+}) {
+  const {big, color} = useContext(PostControlContext)
+
+  return <Comp style={[color, a.pointer_events_none]} width={big ? 22 : 18} />
+}
+
+export function PostControlButtonText({style, ...props}: TextProps) {
+  const {big, active, color} = useContext(PostControlContext)
+
+  return (
+    <Text
+      style={[
+        color,
+        big ? a.text_md : {fontSize: 15},
+        active && a.font_bold,
+        style,
+      ]}
+      {...props}
+    />
+  )
+}
diff --git a/src/view/com/util/forms/PostDropdownBtnMenuItems.tsx b/src/components/PostControls/PostMenu/PostMenuItems.tsx
index a5f41ea7a..51991589f 100644
--- a/src/view/com/util/forms/PostDropdownBtnMenuItems.tsx
+++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx
@@ -1,4 +1,4 @@
-import React, {memo} from 'react'
+import {memo, useMemo} from 'react'
 import {
   Platform,
   type PressableProps,
@@ -13,7 +13,7 @@ import {
   AtUri,
   type RichText as RichTextAPI,
 } from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
+import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useNavigation} from '@react-navigation/native'
 
@@ -26,13 +26,11 @@ import {
   type CommonNavigatorParams,
   type NavigationProp,
 } from '#/lib/routes/types'
-import {shareText, shareUrl} from '#/lib/sharing'
 import {logEvent} from '#/lib/statsig/statsig'
 import {richTextToString} from '#/lib/strings/rich-text-helpers'
 import {toShareUrl} from '#/lib/strings/url-helpers'
 import {getTranslatorLink} from '#/locale/helpers'
 import {logger} from '#/logger'
-import {isWeb} from '#/platform/detection'
 import {type Shadow} from '#/state/cache/post-shadow'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
 import {useFeedFeedbackContext} from '#/state/feed-feedback'
@@ -52,20 +50,16 @@ import {
 import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate'
 import {useSession} from '#/state/session'
 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
-import {useBreakpoints} from '#/alf'
+import * as Toast from '#/view/com/util/Toast'
 import {useDialogControl} from '#/components/Dialog'
 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
-import {EmbedDialog} from '#/components/dialogs/Embed'
 import {
   PostInteractionSettingsDialog,
   usePrefetchPostInteractionSettings,
 } from '#/components/dialogs/PostInteractionSettingsDialog'
-import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
 import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom'
 import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
 import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
-import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBrackets} from '#/components/icons/CodeBrackets'
 import {
   EmojiSad_Stroke2_Corner0_Rounded as EmojiSad,
   EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmile,
@@ -75,7 +69,6 @@ import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/E
 import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter'
 import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
 import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
-import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane'
 import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/Person'
 import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
 import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2'
@@ -90,17 +83,14 @@ import {
   useReportDialogControl,
 } from '#/components/moderation/ReportDialog'
 import * as Prompt from '#/components/Prompt'
-import {useDevMode} from '#/storage/hooks/dev-mode'
 import * as bsky from '#/types/bsky'
-import * as Toast from '../Toast'
 
-let PostDropdownMenuItems = ({
+let PostMenuItems = ({
   post,
   postFeedContext,
   postReqId,
   record,
   richText,
-  timestamp,
   threadgateRecord,
   onShowLess,
 }: {
@@ -118,7 +108,6 @@ let PostDropdownMenuItems = ({
   onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
 }): React.ReactNode => {
   const {hasSession, currentAccount} = useSession()
-  const {gtMobile} = useBreakpoints()
   const {_} = useLingui()
   const langPrefs = useLanguagePrefs()
   const {mutateAsync: deletePostMutate} = usePostDeleteMutation()
@@ -134,20 +123,16 @@ let PostDropdownMenuItems = ({
   const reportDialogControl = useReportDialogControl()
   const deletePromptControl = useDialogControl()
   const hidePromptControl = useDialogControl()
-  const loggedOutWarningPromptControl = useDialogControl()
-  const embedPostControl = useDialogControl()
-  const sendViaChatControl = useDialogControl()
   const postInteractionSettingsDialogControl = useDialogControl()
   const quotePostDetachConfirmControl = useDialogControl()
   const hideReplyConfirmControl = useDialogControl()
   const {mutateAsync: toggleReplyVisibility} =
     useToggleReplyVisibilityMutation()
-  const [devModeEnabled] = useDevMode()
 
   const postUri = post.uri
   const postCid = post.cid
   const postAuthor = useProfileShadow(post.author)
-  const quoteEmbed = React.useMemo(() => {
+  const quoteEmbed = useMemo(() => {
     if (!currentAccount || !post.embed) return
     return getMaybeDetachedQuoteEmbed({
       viewerDid: currentAccount.did,
@@ -181,7 +166,7 @@ let PostDropdownMenuItems = ({
     rootPostUri: rootUri,
   })
 
-  const href = React.useMemo(() => {
+  const href = useMemo(() => {
     const urip = new AtUri(postUri)
     return makeProfileLink(postAuthor, 'post', urip.rkey)
   }, [postUri, postAuthor])
@@ -273,14 +258,6 @@ let PostDropdownMenuItems = ({
     label => label.val === '!no-unauthenticated',
   )
 
-  const showLoggedOutWarning =
-    postAuthor.did !== currentAccount?.did && hideInPWI
-
-  const onSharePost = () => {
-    const url = toShareUrl(href)
-    shareUrl(url)
-  }
-
   const onPressShowMore = () => {
     feedFeedback.sendInteraction({
       event: 'app.bsky.feed.defs#requestMore',
@@ -308,13 +285,6 @@ let PostDropdownMenuItems = ({
     }
   }
 
-  const onSelectChatToShareTo = (conversation: string) => {
-    navigation.navigate('MessagesConversation', {
-      conversation,
-      embed: postUri,
-    })
-  }
-
   const onToggleQuotePostAttachment = async () => {
     if (!quoteEmbed) return
 
@@ -341,7 +311,6 @@ let PostDropdownMenuItems = ({
   }
 
   const canHidePostForMe = !isAuthor && !isPostHidden
-  const canEmbed = isWeb && gtMobile && !hideInPWI
   const canHideReplyForEveryone =
     !isAuthor && isRootPostAuthor && !isPostHidden && isReply
   const canDetachQuote = quoteEmbed && quoteEmbed.isOwnedByViewer
@@ -417,14 +386,6 @@ let PostDropdownMenuItems = ({
     }
   }
 
-  const onShareATURI = () => {
-    shareText(postUri)
-  }
-
-  const onShareAuthorDID = () => {
-    shareText(postAuthor.did)
-  }
-
   const onReportMisclassification = () => {
     const url = `https://docs.google.com/forms/d/e/1FAIpQLSd0QPqhNFksDQf1YyOos7r1ofCLvmrKAH1lU042TaS3GAZaWQ/viewform?entry.1756031717=${toShareUrl(
       href,
@@ -482,44 +443,6 @@ let PostDropdownMenuItems = ({
               </Menu.Item>
             </>
           )}
-
-          {hasSession && (
-            <Menu.Item
-              testID="postDropdownSendViaDMBtn"
-              label={_(msg`Send via direct message`)}
-              onPress={() => sendViaChatControl.open()}>
-              <Menu.ItemText>
-                <Trans>Send via direct message</Trans>
-              </Menu.ItemText>
-              <Menu.ItemIcon icon={Send} position="right" />
-            </Menu.Item>
-          )}
-
-          <Menu.Item
-            testID="postDropdownShareBtn"
-            label={isWeb ? _(msg`Copy link to post`) : _(msg`Share`)}
-            onPress={() => {
-              if (showLoggedOutWarning) {
-                loggedOutWarningPromptControl.open()
-              } else {
-                onSharePost()
-              }
-            }}>
-            <Menu.ItemText>
-              {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)}
-            </Menu.ItemText>
-            <Menu.ItemIcon icon={Share} position="right" />
-          </Menu.Item>
-
-          {canEmbed && (
-            <Menu.Item
-              testID="postDropdownEmbedBtn"
-              label={_(msg`Embed post`)}
-              onPress={() => embedPostControl.open()}>
-              <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText>
-              <Menu.ItemIcon icon={CodeBrackets} position="right" />
-            </Menu.Item>
-          )}
         </Menu.Group>
 
         {hasSession && feedFeedback.enabled && (
@@ -550,11 +473,9 @@ let PostDropdownMenuItems = ({
           DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && (
             <Menu.Item
               testID="postDropdownReportMisclassificationBtn"
-              label={_(msg`Assign topic - help train Discover!`)}
+              label={_(msg`Assign topic for algo`)}
               onPress={onReportMisclassification}>
-              <Menu.ItemText>
-                {_(msg`Assign topic - help train Discover!`)}
-              </Menu.ItemText>
+              <Menu.ItemText>{_(msg`Assign topic for algo`)}</Menu.ItemText>
               <Menu.ItemIcon icon={AtomIcon} position="right" />
             </Menu.Item>
           )}
@@ -747,28 +668,6 @@ let PostDropdownMenuItems = ({
                 </>
               )}
             </Menu.Group>
-
-            {devModeEnabled ? (
-              <>
-                <Menu.Divider />
-                <Menu.Group>
-                  <Menu.Item
-                    testID="postAtUriShareBtn"
-                    label={_(msg`Copy post at:// URI`)}
-                    onPress={onShareATURI}>
-                    <Menu.ItemText>{_(msg`Copy post at:// URI`)}</Menu.ItemText>
-                    <Menu.ItemIcon icon={Share} position="right" />
-                  </Menu.Item>
-                  <Menu.Item
-                    testID="postAuthorDIDShareBtn"
-                    label={_(msg`Copy author DID`)}
-                    onPress={onShareAuthorDID}>
-                    <Menu.ItemText>{_(msg`Copy author DID`)}</Menu.ItemText>
-                    <Menu.ItemIcon icon={Share} position="right" />
-                  </Menu.Item>
-                </Menu.Group>
-              </>
-            ) : null}
           </>
         )}
       </Menu.Outer>
@@ -802,32 +701,6 @@ let PostDropdownMenuItems = ({
         }}
       />
 
-      <Prompt.Basic
-        control={loggedOutWarningPromptControl}
-        title={_(msg`Note about sharing`)}
-        description={_(
-          msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
-        )}
-        onConfirm={onSharePost}
-        confirmButtonCta={_(msg`Share anyway`)}
-      />
-
-      {canEmbed && (
-        <EmbedDialog
-          control={embedPostControl}
-          postCid={postCid}
-          postUri={postUri}
-          record={record}
-          postAuthor={postAuthor}
-          timestamp={timestamp}
-        />
-      )}
-
-      <SendViaChatDialog
-        control={sendViaChatControl}
-        onSelectChat={onSelectChatToShareTo}
-      />
-
       <PostInteractionSettingsDialog
         control={postInteractionSettingsDialogControl}
         postUri={post.uri}
@@ -868,5 +741,5 @@ let PostDropdownMenuItems = ({
     </>
   )
 }
-PostDropdownMenuItems = memo(PostDropdownMenuItems)
-export {PostDropdownMenuItems}
+PostMenuItems = memo(PostMenuItems)
+export {PostMenuItems}
diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/components/PostControls/PostMenu/index.tsx
index 57ee95e31..63aa460fb 100644
--- a/src/view/com/util/forms/PostDropdownBtn.tsx
+++ b/src/components/PostControls/PostMenu/index.tsx
@@ -1,11 +1,5 @@
 import {memo, useMemo, useState} from 'react'
 import {
-  Pressable,
-  type PressableProps,
-  type StyleProp,
-  type ViewStyle,
-} from 'react-native'
-import {
   type AppBskyFeedDefs,
   type AppBskyFeedPost,
   type AppBskyFeedThreadgate,
@@ -15,25 +9,22 @@ import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import type React from 'react'
 
-import {useTheme} from '#/lib/ThemeContext'
 import {type Shadow} from '#/state/cache/post-shadow'
-import {atoms as a, useTheme as useAlf} from '#/alf'
+import {EventStopper} from '#/view/com/util/EventStopper'
 import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
 import {useMenuControl} from '#/components/Menu'
 import * as Menu from '#/components/Menu'
-import {EventStopper} from '../EventStopper'
-import {PostDropdownMenuItems} from './PostDropdownBtnMenuItems'
+import {PostControlButton, PostControlButtonIcon} from '../PostControlButton'
+import {PostMenuItems} from './PostMenuItems'
 
-let PostDropdownBtn = ({
+let PostMenuButton = ({
   testID,
   post,
   postFeedContext,
   postReqId,
+  big,
   record,
   richText,
-  style,
-  hitSlop,
-  size,
   timestamp,
   threadgateRecord,
   onShowLess,
@@ -42,19 +33,15 @@ let PostDropdownBtn = ({
   post: Shadow<AppBskyFeedDefs.PostView>
   postFeedContext: string | undefined
   postReqId: string | undefined
+  big?: boolean
   record: AppBskyFeedPost.Record
   richText: RichTextAPI
-  style?: StyleProp<ViewStyle>
-  hitSlop?: PressableProps['hitSlop']
-  size?: 'lg' | 'md' | 'sm'
   timestamp: string
   threadgateRecord?: AppBskyFeedThreadgate.Record
   onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
 }): React.ReactNode => {
-  const theme = useTheme()
-  const alf = useAlf()
   const {_} = useLingui()
-  const defaultCtrlColor = theme.palette.default.postCtrl
+
   const menuControl = useMenuControl()
   const [hasBeenOpen, setHasBeenOpen] = useState(false)
   const lazyMenuControl = useMemo(
@@ -73,31 +60,21 @@ let PostDropdownBtn = ({
     <EventStopper onKeyDown={false}>
       <Menu.Root control={lazyMenuControl}>
         <Menu.Trigger label={_(msg`Open post options menu`)}>
-          {({props, state}) => {
+          {({props}) => {
             return (
-              <Pressable
-                {...props}
-                hitSlop={hitSlop}
-                testID={testID}
-                style={[
-                  style,
-                  a.rounded_full,
-                  (state.hovered || state.pressed) && [
-                    alf.atoms.bg_contrast_25,
-                  ],
-                ]}>
-                <DotsHorizontal
-                  fill={defaultCtrlColor}
-                  style={{pointerEvents: 'none'}}
-                  size={size}
-                />
-              </Pressable>
+              <PostControlButton
+                testID="postDropdownBtn"
+                big={big}
+                label={props.accessibilityLabel}
+                {...props}>
+                <PostControlButtonIcon icon={DotsHorizontal} />
+              </PostControlButton>
             )
           }}
         </Menu.Trigger>
         {hasBeenOpen && (
           // Lazily initialized. Once mounted, they stay mounted.
-          <PostDropdownMenuItems
+          <PostMenuItems
             testID={testID}
             post={post}
             postFeedContext={postFeedContext}
@@ -114,5 +91,5 @@ let PostDropdownBtn = ({
   )
 }
 
-PostDropdownBtn = memo(PostDropdownBtn)
-export {PostDropdownBtn}
+PostMenuButton = memo(PostMenuButton)
+export {PostMenuButton}
diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/components/PostControls/RepostButton.tsx
index ca1647a99..db63a7383 100644
--- a/src/view/com/util/post-ctrls/RepostButton.tsx
+++ b/src/components/PostControls/RepostButton.tsx
@@ -1,18 +1,22 @@
-import React, {memo, useCallback} from 'react'
+import {memo, useCallback} from 'react'
 import {View} from 'react-native'
 import {msg, plural, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
-import {POST_CTRL_HITSLOP} from '#/lib/constants'
 import {useHaptics} from '#/lib/haptics'
 import {useRequireAuth} from '#/state/session'
+import {formatCount} from '#/view/com/util/numeric/format'
 import {atoms as a, useTheme} from '#/alf'
 import {Button, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
 import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote'
 import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
 import {Text} from '#/components/Typography'
-import {formatCount} from '../numeric/format'
+import {
+  PostControlButton,
+  PostControlButtonIcon,
+  PostControlButtonText,
+} from './PostControlButton'
 
 interface Props {
   isReposted: boolean
@@ -35,65 +39,46 @@ let RepostButton = ({
   const {_, i18n} = useLingui()
   const requireAuth = useRequireAuth()
   const dialogControl = Dialog.useDialogControl()
-  const playHaptic = useHaptics()
-  const color = React.useMemo(
-    () => ({
-      color: isReposted ? t.palette.positive_600 : t.palette.contrast_500,
-    }),
-    [t, isReposted],
-  )
+
   return (
     <>
-      <Button
+      <PostControlButton
         testID="repostBtn"
-        onPress={() => {
-          playHaptic('Light')
-          requireAuth(() => dialogControl.open())
-        }}
-        onLongPress={() => {
-          playHaptic('Heavy')
-          requireAuth(() => onQuote())
-        }}
-        style={[
-          a.flex_row,
-          a.align_center,
-          a.gap_xs,
-          a.bg_transparent,
-          {padding: 5},
-        ]}
-        hoverStyle={t.atoms.bg_contrast_25}
+        active={isReposted}
+        activeColor={t.palette.positive_600}
+        big={big}
+        onPress={() => requireAuth(() => dialogControl.open())}
+        onLongPress={() => requireAuth(() => onQuote())}
         label={
           isReposted
             ? _(
-                msg`Undo repost (${plural(repostCount || 0, {
-                  one: '# repost',
-                  other: '# reposts',
-                })})`,
+                msg({
+                  message: `Undo repost (${plural(repostCount || 0, {
+                    one: '# repost',
+                    other: '# reposts',
+                  })})`,
+                  comment:
+                    'Accessibility label for the repost button when the post has been reposted, verb followed by number of reposts and noun',
+                }),
               )
             : _(
-                msg`Repost (${plural(repostCount || 0, {
-                  one: '# repost',
-                  other: '# reposts',
-                })})`,
+                msg({
+                  message: `Repost (${plural(repostCount || 0, {
+                    one: '# repost',
+                    other: '# reposts',
+                  })})`,
+                  comment:
+                    'Accessibility label for the repost button when the post has not been reposted, verb form followed by number of reposts and noun form',
+                }),
               )
-        }
-        shape="round"
-        variant="ghost"
-        color="secondary"
-        hitSlop={POST_CTRL_HITSLOP}>
-        <Repost style={color} width={big ? 22 : 18} />
-        {typeof repostCount !== 'undefined' && repostCount > 0 ? (
-          <Text
-            testID="repostCount"
-            style={[
-              color,
-              big ? a.text_md : {fontSize: 15},
-              isReposted && a.font_bold,
-            ]}>
+        }>
+        <PostControlButtonIcon icon={Repost} />
+        {typeof repostCount !== 'undefined' && repostCount > 0 && (
+          <PostControlButtonText testID="repostCount">
             {formatCount(i18n, repostCount)}
-          </Text>
-        ) : undefined}
-      </Button>
+          </PostControlButtonText>
+        )}
+      </PostControlButton>
       <Dialog.Outer
         control={dialogControl}
         nativeOptions={{preventExpansion: true}}>
diff --git a/src/view/com/util/post-ctrls/RepostButton.web.tsx b/src/components/PostControls/RepostButton.web.tsx
index 54119b532..48720b753 100644
--- a/src/view/com/util/post-ctrls/RepostButton.web.tsx
+++ b/src/components/PostControls/RepostButton.web.tsx
@@ -1,18 +1,19 @@
-import React from 'react'
-import {Pressable, View} from 'react-native'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {useRequireAuth} from '#/state/session'
 import {useSession} from '#/state/session'
-import {atoms as a, useTheme} from '#/alf'
-import {Button} from '#/components/Button'
+import {EventStopper} from '#/view/com/util/EventStopper'
+import {formatCount} from '#/view/com/util/numeric/format'
+import {useTheme} from '#/alf'
 import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote'
 import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
 import * as Menu from '#/components/Menu'
-import {Text} from '#/components/Typography'
-import {EventStopper} from '../EventStopper'
-import {formatCount} from '../numeric/format'
+import {
+  PostControlButton,
+  PostControlButtonIcon,
+  PostControlButtonText,
+} from './PostControlButton'
 
 interface Props {
   isReposted: boolean
@@ -32,38 +33,30 @@ export const RepostButton = ({
   embeddingDisabled,
 }: Props) => {
   const t = useTheme()
-  const {_} = useLingui()
+  const {_, i18n} = useLingui()
   const {hasSession} = useSession()
   const requireAuth = useRequireAuth()
 
-  const color = React.useMemo(
-    () => ({
-      color: isReposted ? t.palette.positive_600 : t.palette.contrast_500,
-    }),
-    [t, isReposted],
-  )
-
   return hasSession ? (
     <EventStopper onKeyDown={false}>
       <Menu.Root>
         <Menu.Trigger label={_(msg`Repost or quote post`)}>
-          {({props, state}) => {
+          {({props}) => {
             return (
-              <Pressable
-                {...props}
-                style={[
-                  a.rounded_full,
-                  (state.hovered || state.pressed) && {
-                    backgroundColor: t.palette.contrast_25,
-                  },
-                ]}>
-                <RepostInner
-                  isReposted={isReposted}
-                  color={color}
-                  repostCount={repostCount}
-                  big={big}
-                />
-              </Pressable>
+              <PostControlButton
+                testID="repostBtn"
+                active={isReposted}
+                activeColor={t.palette.positive_600}
+                label={props.accessibilityLabel}
+                big={big}
+                {...props}>
+                <PostControlButtonIcon icon={Repost} />
+                {typeof repostCount !== 'undefined' && repostCount > 0 && (
+                  <PostControlButtonText testID="repostCount">
+                    {formatCount(i18n, repostCount)}
+                  </PostControlButtonText>
+                )}
+              </PostControlButton>
             )
           }}
         </Menu.Trigger>
@@ -97,51 +90,18 @@ export const RepostButton = ({
       </Menu.Root>
     </EventStopper>
   ) : (
-    <Button
-      onPress={() => {
-        requireAuth(() => {})
-      }}
+    <PostControlButton
+      onPress={() => requireAuth(() => {})}
+      active={isReposted}
+      activeColor={t.palette.positive_600}
       label={_(msg`Repost or quote post`)}
-      style={{padding: 0}}
-      hoverStyle={t.atoms.bg_contrast_25}
-      shape="round">
-      <RepostInner
-        isReposted={isReposted}
-        color={color}
-        repostCount={repostCount}
-        big={big}
-      />
-    </Button>
-  )
-}
-
-const RepostInner = ({
-  isReposted,
-  color,
-  repostCount,
-  big,
-}: {
-  isReposted: boolean
-  color: {color: string}
-  repostCount?: number
-  big?: boolean
-}) => {
-  const {i18n} = useLingui()
-  return (
-    <View style={[a.flex_row, a.align_center, a.gap_xs, {padding: 5}]}>
-      <Repost style={color} width={big ? 22 : 18} />
-      {typeof repostCount !== 'undefined' && repostCount > 0 ? (
-        <Text
-          testID="repostCount"
-          style={[
-            color,
-            big ? a.text_md : {fontSize: 15},
-            isReposted && [a.font_bold],
-            a.user_select_none,
-          ]}>
+      big={big}>
+      <PostControlButtonIcon icon={Repost} />
+      {typeof repostCount !== 'undefined' && repostCount > 0 && (
+        <PostControlButtonText testID="repostCount">
           {formatCount(i18n, repostCount)}
-        </Text>
-      ) : undefined}
-    </View>
+        </PostControlButtonText>
+      )}
+    </PostControlButton>
   )
 }
diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx
new file mode 100644
index 000000000..ca5d0029e
--- /dev/null
+++ b/src/components/PostControls/ShareMenu/RecentChats.tsx
@@ -0,0 +1,200 @@
+import {ScrollView, View} from 'react-native'
+import {moderateProfile, type ModerationOpts} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+
+import {type NavigationProp} from '#/lib/routes/types'
+import {sanitizeDisplayName} from '#/lib/strings/display-names'
+import {sanitizeHandle} from '#/lib/strings/handles'
+import {logger} from '#/logger'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
+import {useListConvosQuery} from '#/state/queries/messages/list-conversations'
+import {useSession} from '#/state/session'
+import {UserAvatar} from '#/view/com/util/UserAvatar'
+import {atoms as a, tokens, useTheme} from '#/alf'
+import {Button} from '#/components/Button'
+import {useDialogContext} from '#/components/Dialog'
+import {Text} from '#/components/Typography'
+import {useSimpleVerificationState} from '#/components/verification'
+import {VerificationCheck} from '#/components/verification/VerificationCheck'
+import type * as bsky from '#/types/bsky'
+
+export function RecentChats({postUri}: {postUri: string}) {
+  const control = useDialogContext()
+  const {_} = useLingui()
+  const {currentAccount} = useSession()
+  const {data} = useListConvosQuery({status: 'accepted'})
+  const convos = data?.pages[0]?.convos?.slice(0, 10)
+  const moderationOpts = useModerationOpts()
+  const navigation = useNavigation<NavigationProp>()
+
+  const onSelectChat = (convoId: string) => {
+    control.close(() => {
+      logger.metric('share:press:recentDm', {}, {statsig: true})
+      navigation.navigate('MessagesConversation', {
+        conversation: convoId,
+        embed: postUri,
+      })
+    })
+  }
+
+  if (!moderationOpts) return null
+
+  return (
+    <View
+      style={[a.relative, a.flex_1, {marginHorizontal: tokens.space.md * -1}]}>
+      <ScrollView
+        horizontal
+        style={[a.flex_1, a.pt_2xs, {minHeight: 98}]}
+        contentContainerStyle={[a.gap_sm, a.px_md]}
+        showsHorizontalScrollIndicator={false}
+        fadingEdgeLength={64}
+        nestedScrollEnabled>
+        {convos && convos.length > 0 ? (
+          convos.map(convo => {
+            const otherMember = convo.members.find(
+              member => member.did !== currentAccount?.did,
+            )
+
+            if (!otherMember || otherMember.handle === 'missing.invalid')
+              return null
+
+            return (
+              <RecentChatItem
+                key={convo.id}
+                profile={otherMember}
+                onPress={() => onSelectChat(convo.id)}
+                moderationOpts={moderationOpts}
+              />
+            )
+          })
+        ) : (
+          <>
+            <ConvoSkeleton />
+            <ConvoSkeleton />
+            <ConvoSkeleton />
+            <ConvoSkeleton />
+            <ConvoSkeleton />
+          </>
+        )}
+      </ScrollView>
+      {convos && convos.length === 0 && <NoConvos />}
+    </View>
+  )
+}
+
+const WIDTH = 80
+
+function RecentChatItem({
+  profile,
+  onPress,
+  moderationOpts,
+}: {
+  profile: bsky.profile.AnyProfileView
+  onPress: () => void
+  moderationOpts: ModerationOpts
+}) {
+  const {_} = useLingui()
+  const t = useTheme()
+
+  const moderation = moderateProfile(profile, moderationOpts)
+  const name = sanitizeDisplayName(
+    profile.displayName || sanitizeHandle(profile.handle),
+    moderation.ui('displayName'),
+  )
+  const verification = useSimpleVerificationState({profile})
+
+  return (
+    <Button
+      onPress={onPress}
+      label={_(msg`Send post to ${name}`)}
+      style={[
+        a.flex_col,
+        {width: WIDTH},
+        a.gap_sm,
+        a.justify_start,
+        a.align_center,
+      ]}>
+      <UserAvatar
+        avatar={profile.avatar}
+        size={WIDTH - 8}
+        type={profile.associated?.labeler ? 'labeler' : 'user'}
+        moderation={moderation.ui('avatar')}
+      />
+      <View style={[a.flex_row, a.align_center, a.justify_center, a.w_full]}>
+        <Text
+          emoji
+          style={[a.text_xs, a.leading_snug, t.atoms.text_contrast_medium]}
+          numberOfLines={1}>
+          {name}
+        </Text>
+        {verification.showBadge && (
+          <View style={[a.pl_2xs]}>
+            <VerificationCheck
+              width={10}
+              verifier={verification.role === 'verifier'}
+            />
+          </View>
+        )}
+      </View>
+    </Button>
+  )
+}
+
+function ConvoSkeleton() {
+  const t = useTheme()
+  return (
+    <View
+      style={[
+        a.flex_col,
+        {width: WIDTH, height: WIDTH + 15},
+        a.gap_xs,
+        a.justify_start,
+        a.align_center,
+      ]}>
+      <View
+        style={[
+          t.atoms.bg_contrast_50,
+          {width: WIDTH - 8, height: WIDTH - 8},
+          a.rounded_full,
+        ]}
+      />
+      <View
+        style={[
+          t.atoms.bg_contrast_50,
+          {width: WIDTH - 8, height: 10},
+          a.rounded_xs,
+        ]}
+      />
+    </View>
+  )
+}
+
+function NoConvos() {
+  const t = useTheme()
+
+  return (
+    <View
+      style={[
+        a.absolute,
+        a.inset_0,
+        a.justify_center,
+        a.align_center,
+        a.px_2xl,
+      ]}>
+      <View
+        style={[a.absolute, a.inset_0, t.atoms.bg_contrast_25, {opacity: 0.5}]}
+      />
+      <Text
+        style={[
+          a.text_sm,
+          t.atoms.text_contrast_high,
+          a.text_center,
+          a.font_bold,
+        ]}>
+        <Trans>Start a conversation, and it will appear here.</Trans>
+      </Text>
+    </View>
+  )
+}
diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx
new file mode 100644
index 000000000..94369fcff
--- /dev/null
+++ b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx
@@ -0,0 +1,197 @@
+import {memo, useMemo} from 'react'
+import * as ExpoClipboard from 'expo-clipboard'
+import {AtUri} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+
+import {makeProfileLink} from '#/lib/routes/links'
+import {type NavigationProp} from '#/lib/routes/types'
+import {shareText, shareUrl} from '#/lib/sharing'
+import {toShareUrl} from '#/lib/strings/url-helpers'
+import {logger} from '#/logger'
+import {useProfileShadow} from '#/state/cache/profile-shadow'
+import {useSession} from '#/state/session'
+import * as Toast from '#/view/com/util/Toast'
+import {useDialogControl} from '#/components/Dialog'
+import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
+import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
+import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
+import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
+import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane'
+import * as Menu from '#/components/Menu'
+import * as Prompt from '#/components/Prompt'
+import {useDevMode} from '#/storage/hooks/dev-mode'
+import {RecentChats} from './RecentChats'
+import {type ShareMenuItemsProps} from './ShareMenuItems.types'
+
+let ShareMenuItems = ({
+  post,
+  onShare: onShareProp,
+}: ShareMenuItemsProps): React.ReactNode => {
+  const {hasSession, currentAccount} = useSession()
+  const {_} = useLingui()
+  const navigation = useNavigation<NavigationProp>()
+  const pwiWarningShareControl = useDialogControl()
+  const pwiWarningCopyControl = useDialogControl()
+  const sendViaChatControl = useDialogControl()
+  const [devModeEnabled] = useDevMode()
+
+  const postUri = post.uri
+  const postAuthor = useProfileShadow(post.author)
+
+  const href = useMemo(() => {
+    const urip = new AtUri(postUri)
+    return makeProfileLink(postAuthor, 'post', urip.rkey)
+  }, [postUri, postAuthor])
+
+  const hideInPWI = useMemo(() => {
+    return !!postAuthor.labels?.find(
+      label => label.val === '!no-unauthenticated',
+    )
+  }, [postAuthor])
+
+  const showLoggedOutWarning =
+    postAuthor.did !== currentAccount?.did && hideInPWI
+
+  const onSharePost = () => {
+    logger.metric('share:press:nativeShare', {}, {statsig: true})
+    const url = toShareUrl(href)
+    shareUrl(url)
+    onShareProp()
+  }
+
+  const onCopyLink = () => {
+    logger.metric('share:press:copyLink', {}, {statsig: true})
+    const url = toShareUrl(href)
+    ExpoClipboard.setUrlAsync(url).then(() =>
+      Toast.show(_(msg`Copied to clipboard`), 'clipboard-check'),
+    )
+    onShareProp()
+  }
+
+  const onSelectChatToShareTo = (conversation: string) => {
+    navigation.navigate('MessagesConversation', {
+      conversation,
+      embed: postUri,
+    })
+  }
+
+  const onShareATURI = () => {
+    shareText(postUri)
+  }
+
+  const onShareAuthorDID = () => {
+    shareText(postAuthor.did)
+  }
+
+  return (
+    <>
+      <Menu.Outer>
+        {hasSession && (
+          <Menu.Group>
+            <Menu.ContainerItem>
+              <RecentChats postUri={postUri} />
+            </Menu.ContainerItem>
+            <Menu.Item
+              testID="postDropdownSendViaDMBtn"
+              label={_(msg`Send via direct message`)}
+              onPress={() => {
+                logger.metric('share:press:openDmSearch', {}, {statsig: true})
+                sendViaChatControl.open()
+              }}>
+              <Menu.ItemText>
+                <Trans>Send via direct message</Trans>
+              </Menu.ItemText>
+              <Menu.ItemIcon icon={PaperPlaneIcon} position="right" />
+            </Menu.Item>
+          </Menu.Group>
+        )}
+
+        <Menu.Group>
+          <Menu.Item
+            testID="postDropdownShareBtn"
+            label={_(msg`Share via...`)}
+            onPress={() => {
+              if (showLoggedOutWarning) {
+                pwiWarningShareControl.open()
+              } else {
+                onSharePost()
+              }
+            }}>
+            <Menu.ItemText>
+              <Trans>Share via...</Trans>
+            </Menu.ItemText>
+            <Menu.ItemIcon icon={ArrowOutOfBoxIcon} position="right" />
+          </Menu.Item>
+
+          <Menu.Item
+            testID="postDropdownShareBtn"
+            label={_(msg`Copy link to post`)}
+            onPress={() => {
+              if (showLoggedOutWarning) {
+                pwiWarningCopyControl.open()
+              } else {
+                onCopyLink()
+              }
+            }}>
+            <Menu.ItemText>
+              <Trans>Copy link to post</Trans>
+            </Menu.ItemText>
+            <Menu.ItemIcon icon={ChainLinkIcon} position="right" />
+          </Menu.Item>
+        </Menu.Group>
+
+        {devModeEnabled && (
+          <Menu.Group>
+            <Menu.Item
+              testID="postAtUriShareBtn"
+              label={_(msg`Share post at:// URI`)}
+              onPress={onShareATURI}>
+              <Menu.ItemText>
+                <Trans>Share post at:// URI</Trans>
+              </Menu.ItemText>
+              <Menu.ItemIcon icon={ClipboardIcon} position="right" />
+            </Menu.Item>
+            <Menu.Item
+              testID="postAuthorDIDShareBtn"
+              label={_(msg`Share author DID`)}
+              onPress={onShareAuthorDID}>
+              <Menu.ItemText>
+                <Trans>Share author DID</Trans>
+              </Menu.ItemText>
+              <Menu.ItemIcon icon={ClipboardIcon} position="right" />
+            </Menu.Item>
+          </Menu.Group>
+        )}
+      </Menu.Outer>
+
+      <Prompt.Basic
+        control={pwiWarningShareControl}
+        title={_(msg`Note about sharing`)}
+        description={_(
+          msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
+        )}
+        onConfirm={onSharePost}
+        confirmButtonCta={_(msg`Share anyway`)}
+      />
+
+      <Prompt.Basic
+        control={pwiWarningCopyControl}
+        title={_(msg`Note about sharing`)}
+        description={_(
+          msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
+        )}
+        onConfirm={onCopyLink}
+        confirmButtonCta={_(msg`Copy anyway`)}
+      />
+
+      <SendViaChatDialog
+        control={sendViaChatControl}
+        onSelectChat={onSelectChatToShareTo}
+      />
+    </>
+  )
+}
+ShareMenuItems = memo(ShareMenuItems)
+export {ShareMenuItems}
diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx
new file mode 100644
index 000000000..5bc2a8fb6
--- /dev/null
+++ b/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx
@@ -0,0 +1,22 @@
+import {type PressableProps, type StyleProp, type ViewStyle} from 'react-native'
+import {
+  type AppBskyFeedDefs,
+  type AppBskyFeedPost,
+  type AppBskyFeedThreadgate,
+  type RichText as RichTextAPI,
+} from '@atproto/api'
+
+import {type Shadow} from '#/state/cache/post-shadow'
+
+export interface ShareMenuItemsProps {
+  testID: string
+  post: Shadow<AppBskyFeedDefs.PostView>
+  record: AppBskyFeedPost.Record
+  richText: RichTextAPI
+  style?: StyleProp<ViewStyle>
+  hitSlop?: PressableProps['hitSlop']
+  size?: 'lg' | 'md' | 'sm'
+  timestamp: string
+  threadgateRecord?: AppBskyFeedThreadgate.Record
+  onShare: () => void
+}
diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx
new file mode 100644
index 000000000..0da259678
--- /dev/null
+++ b/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx
@@ -0,0 +1,192 @@
+import {memo, useMemo} from 'react'
+import {AtUri} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+import type React from 'react'
+
+import {makeProfileLink} from '#/lib/routes/links'
+import {type NavigationProp} from '#/lib/routes/types'
+import {shareText, shareUrl} from '#/lib/sharing'
+import {toShareUrl} from '#/lib/strings/url-helpers'
+import {logger} from '#/logger'
+import {isWeb} from '#/platform/detection'
+import {useProfileShadow} from '#/state/cache/profile-shadow'
+import {useSession} from '#/state/session'
+import {useBreakpoints} from '#/alf'
+import {useDialogControl} from '#/components/Dialog'
+import {EmbedDialog} from '#/components/dialogs/Embed'
+import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
+import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
+import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
+import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets'
+import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane'
+import * as Menu from '#/components/Menu'
+import * as Prompt from '#/components/Prompt'
+import {useDevMode} from '#/storage/hooks/dev-mode'
+import {type ShareMenuItemsProps} from './ShareMenuItems.types'
+
+let ShareMenuItems = ({
+  post,
+  record,
+  timestamp,
+  onShare: onShareProp,
+}: ShareMenuItemsProps): React.ReactNode => {
+  const {hasSession, currentAccount} = useSession()
+  const {gtMobile} = useBreakpoints()
+  const {_} = useLingui()
+  const navigation = useNavigation<NavigationProp>()
+  const loggedOutWarningPromptControl = useDialogControl()
+  const embedPostControl = useDialogControl()
+  const sendViaChatControl = useDialogControl()
+  const [devModeEnabled] = useDevMode()
+
+  const postUri = post.uri
+  const postCid = post.cid
+  const postAuthor = useProfileShadow(post.author)
+
+  const href = useMemo(() => {
+    const urip = new AtUri(postUri)
+    return makeProfileLink(postAuthor, 'post', urip.rkey)
+  }, [postUri, postAuthor])
+
+  const hideInPWI = useMemo(() => {
+    return !!postAuthor.labels?.find(
+      label => label.val === '!no-unauthenticated',
+    )
+  }, [postAuthor])
+
+  const showLoggedOutWarning =
+    postAuthor.did !== currentAccount?.did && hideInPWI
+
+  const onCopyLink = () => {
+    logger.metric('share:press:copyLink', {}, {statsig: true})
+    const url = toShareUrl(href)
+    shareUrl(url)
+    onShareProp()
+  }
+
+  const onSelectChatToShareTo = (conversation: string) => {
+    logger.metric('share:press:dmSelected', {}, {statsig: true})
+    navigation.navigate('MessagesConversation', {
+      conversation,
+      embed: postUri,
+    })
+  }
+
+  const canEmbed = isWeb && gtMobile && !hideInPWI
+
+  const onShareATURI = () => {
+    shareText(postUri)
+  }
+
+  const onShareAuthorDID = () => {
+    shareText(postAuthor.did)
+  }
+
+  return (
+    <>
+      <Menu.Outer>
+        <Menu.Group>
+          <Menu.Item
+            testID="postDropdownShareBtn"
+            label={_(msg`Copy link to post`)}
+            onPress={() => {
+              if (showLoggedOutWarning) {
+                loggedOutWarningPromptControl.open()
+              } else {
+                onCopyLink()
+              }
+            }}>
+            <Menu.ItemText>
+              <Trans>Copy link to post</Trans>
+            </Menu.ItemText>
+            <Menu.ItemIcon icon={ChainLinkIcon} position="right" />
+          </Menu.Item>
+
+          {hasSession && (
+            <Menu.Item
+              testID="postDropdownSendViaDMBtn"
+              label={_(msg`Send via direct message`)}
+              onPress={() => {
+                logger.metric('share:press:openDmSearch', {}, {statsig: true})
+                sendViaChatControl.open()
+              }}>
+              <Menu.ItemText>
+                <Trans>Send via direct message</Trans>
+              </Menu.ItemText>
+              <Menu.ItemIcon icon={Send} position="right" />
+            </Menu.Item>
+          )}
+
+          {canEmbed && (
+            <Menu.Item
+              testID="postDropdownEmbedBtn"
+              label={_(msg`Embed post`)}
+              onPress={() => {
+                logger.metric('share:press:embed', {}, {statsig: true})
+                embedPostControl.open()
+              }}>
+              <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText>
+              <Menu.ItemIcon icon={CodeBracketsIcon} position="right" />
+            </Menu.Item>
+          )}
+        </Menu.Group>
+
+        {devModeEnabled && (
+          <>
+            <Menu.Divider />
+            <Menu.Group>
+              <Menu.Item
+                testID="postAtUriShareBtn"
+                label={_(msg`Copy post at:// URI`)}
+                onPress={onShareATURI}>
+                <Menu.ItemText>
+                  <Trans>Copy post at:// URI</Trans>
+                </Menu.ItemText>
+                <Menu.ItemIcon icon={ClipboardIcon} position="right" />
+              </Menu.Item>
+              <Menu.Item
+                testID="postAuthorDIDShareBtn"
+                label={_(msg`Copy author DID`)}
+                onPress={onShareAuthorDID}>
+                <Menu.ItemText>
+                  <Trans>Copy author DID</Trans>
+                </Menu.ItemText>
+                <Menu.ItemIcon icon={ClipboardIcon} position="right" />
+              </Menu.Item>
+            </Menu.Group>
+          </>
+        )}
+      </Menu.Outer>
+
+      <Prompt.Basic
+        control={loggedOutWarningPromptControl}
+        title={_(msg`Note about sharing`)}
+        description={_(
+          msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
+        )}
+        onConfirm={onCopyLink}
+        confirmButtonCta={_(msg`Share anyway`)}
+      />
+
+      {canEmbed && (
+        <EmbedDialog
+          control={embedPostControl}
+          postCid={postCid}
+          postUri={postUri}
+          record={record}
+          postAuthor={postAuthor}
+          timestamp={timestamp}
+        />
+      )}
+
+      <SendViaChatDialog
+        control={sendViaChatControl}
+        onSelectChat={onSelectChatToShareTo}
+      />
+    </>
+  )
+}
+ShareMenuItems = memo(ShareMenuItems)
+export {ShareMenuItems}
diff --git a/src/components/PostControls/ShareMenu/index.tsx b/src/components/PostControls/ShareMenu/index.tsx
new file mode 100644
index 000000000..d4ea18bb0
--- /dev/null
+++ b/src/components/PostControls/ShareMenu/index.tsx
@@ -0,0 +1,119 @@
+import {memo, useMemo, useState} from 'react'
+import {
+  type AppBskyFeedDefs,
+  type AppBskyFeedPost,
+  type AppBskyFeedThreadgate,
+  AtUri,
+  type RichText as RichTextAPI,
+} from '@atproto/api'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import type React from 'react'
+
+import {makeProfileLink} from '#/lib/routes/links'
+import {shareUrl} from '#/lib/sharing'
+import {useGate} from '#/lib/statsig/statsig'
+import {toShareUrl} from '#/lib/strings/url-helpers'
+import {logger} from '#/logger'
+import {type Shadow} from '#/state/cache/post-shadow'
+import {EventStopper} from '#/view/com/util/EventStopper'
+import {native} from '#/alf'
+import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
+import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/components/icons/ArrowShareRight'
+import {useMenuControl} from '#/components/Menu'
+import * as Menu from '#/components/Menu'
+import {PostControlButton, PostControlButtonIcon} from '../PostControlButton'
+import {ShareMenuItems} from './ShareMenuItems'
+
+let ShareMenuButton = ({
+  testID,
+  post,
+  big,
+  record,
+  richText,
+  timestamp,
+  threadgateRecord,
+  onShare,
+}: {
+  testID: string
+  post: Shadow<AppBskyFeedDefs.PostView>
+  big?: boolean
+  record: AppBskyFeedPost.Record
+  richText: RichTextAPI
+  timestamp: string
+  threadgateRecord?: AppBskyFeedThreadgate.Record
+  onShare: () => void
+}): React.ReactNode => {
+  const {_} = useLingui()
+  const gate = useGate()
+
+  const ShareIcon = gate('alt_share_icon')
+    ? ArrowShareRightIcon
+    : ArrowOutOfBoxIcon
+
+  const menuControl = useMenuControl()
+  const [hasBeenOpen, setHasBeenOpen] = useState(false)
+  const lazyMenuControl = useMemo(
+    () => ({
+      ...menuControl,
+      open() {
+        setHasBeenOpen(true)
+        // HACK. We need the state update to be flushed by the time
+        // menuControl.open() fires but RN doesn't expose flushSync.
+        setTimeout(menuControl.open)
+
+        logger.metric(
+          'share:open',
+          {context: big ? 'thread' : 'feed'},
+          {statsig: true},
+        )
+      },
+    }),
+    [menuControl, setHasBeenOpen, big],
+  )
+
+  const onNativeLongPress = () => {
+    logger.metric('share:press:nativeShare', {}, {statsig: true})
+    const urip = new AtUri(post.uri)
+    const href = makeProfileLink(post.author, 'post', urip.rkey)
+    const url = toShareUrl(href)
+    shareUrl(url)
+    onShare()
+  }
+
+  return (
+    <EventStopper onKeyDown={false}>
+      <Menu.Root control={lazyMenuControl}>
+        <Menu.Trigger label={_(msg`Open share menu`)}>
+          {({props}) => {
+            return (
+              <PostControlButton
+                testID="postShareBtn"
+                big={big}
+                label={props.accessibilityLabel}
+                {...props}
+                onLongPress={native(onNativeLongPress)}>
+                <PostControlButtonIcon icon={ShareIcon} />
+              </PostControlButton>
+            )
+          }}
+        </Menu.Trigger>
+        {hasBeenOpen && (
+          // Lazily initialized. Once mounted, they stay mounted.
+          <ShareMenuItems
+            testID={testID}
+            post={post}
+            record={record}
+            richText={richText}
+            timestamp={timestamp}
+            threadgateRecord={threadgateRecord}
+            onShare={onShare}
+          />
+        )}
+      </Menu.Root>
+    </EventStopper>
+  )
+}
+
+ShareMenuButton = memo(ShareMenuButton)
+export {ShareMenuButton}
diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/components/PostControls/index.tsx
index 3f82eb294..7739da56b 100644
--- a/src/view/com/util/post-ctrls/PostCtrls.tsx
+++ b/src/components/PostControls/index.tsx
@@ -1,55 +1,43 @@
-import React, {memo} from 'react'
-import {
-  Pressable,
-  type PressableStateCallbackType,
-  type StyleProp,
-  View,
-  type ViewStyle,
-} from 'react-native'
-import * as Clipboard from 'expo-clipboard'
+import {memo, useState} from 'react'
+import {type StyleProp, View, type ViewStyle} from 'react-native'
 import {
   type AppBskyFeedDefs,
   type AppBskyFeedPost,
   type AppBskyFeedThreadgate,
-  AtUri,
   type RichText as RichTextAPI,
 } from '@atproto/api'
 import {msg, plural} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
-import {IS_INTERNAL} from '#/lib/app-info'
-import {DISCOVER_DEBUG_DIDS, POST_CTRL_HITSLOP} from '#/lib/constants'
 import {CountWheel} from '#/lib/custom-animations/CountWheel'
 import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon'
 import {useHaptics} from '#/lib/haptics'
 import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
-import {makeProfileLink} from '#/lib/routes/links'
-import {shareUrl} from '#/lib/sharing'
-import {useGate} from '#/lib/statsig/statsig'
-import {toShareUrl} from '#/lib/strings/url-helpers'
 import {type Shadow} from '#/state/cache/types'
 import {useFeedFeedbackContext} from '#/state/feed-feedback'
 import {
   usePostLikeMutationQueue,
   usePostRepostMutationQueue,
 } from '#/state/queries/post'
-import {useRequireAuth, useSession} from '#/state/session'
+import {useRequireAuth} from '#/state/session'
 import {
   ProgressGuideAction,
   useProgressGuideControls,
 } from '#/state/shell/progress-guide'
-import {atoms as a, useTheme} from '#/alf'
-import {useDialogControl} from '#/components/Dialog'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
+import {formatCount} from '#/view/com/util/numeric/format'
+import * as Toast from '#/view/com/util/Toast'
+import {atoms as a, useBreakpoints} from '#/alf'
 import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble'
-import * as Prompt from '#/components/Prompt'
-import {PostDropdownBtn} from '../forms/PostDropdownBtn'
-import {formatCount} from '../numeric/format'
-import {Text} from '../text/Text'
-import * as Toast from '../Toast'
+import {
+  PostControlButton,
+  PostControlButtonIcon,
+  PostControlButtonText,
+} from './PostControlButton'
+import {PostMenuButton} from './PostMenu'
 import {RepostButton} from './RepostButton'
+import {ShareMenuButton} from './ShareMenu'
 
-let PostCtrls = ({
+let PostControls = ({
   big,
   post,
   record,
@@ -76,25 +64,18 @@ let PostCtrls = ({
   threadgateRecord?: AppBskyFeedThreadgate.Record
   onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
 }): React.ReactNode => {
-  const t = useTheme()
   const {_, i18n} = useLingui()
+  const {gtMobile} = useBreakpoints()
   const {openComposer} = useOpenComposer()
-  const {currentAccount} = useSession()
   const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext)
   const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(
     post,
     logContext,
   )
   const requireAuth = useRequireAuth()
-  const loggedOutWarningPromptControl = useDialogControl()
   const {sendInteraction} = useFeedFeedbackContext()
   const {captureAction} = useProgressGuideControls()
   const playHaptic = useHaptics()
-  const gate = useGate()
-  const isDiscoverDebugUser =
-    IS_INTERNAL ||
-    DISCOVER_DEBUG_DIDS[currentAccount?.did || ''] ||
-    gate('debug_show_feedcontext')
   const isBlocked = Boolean(
     post.author.viewer?.blocking ||
       post.author.viewer?.blockedBy ||
@@ -102,22 +83,7 @@ let PostCtrls = ({
   )
   const replyDisabled = post.viewer?.replyDisabled
 
-  const shouldShowLoggedOutWarning = React.useMemo(() => {
-    return (
-      post.author.did !== currentAccount?.did &&
-      !!post.author.labels?.find(label => label.val === '!no-unauthenticated')
-    )
-  }, [currentAccount, post])
-
-  const defaultCtrlColor = React.useMemo(
-    () => ({
-      color: t.palette.contrast_500,
-    }),
-    [t],
-  ) as StyleProp<ViewStyle>
-
-  const [hasLikeIconBeenToggled, setHasLikeIconBeenToggled] =
-    React.useState(false)
+  const [hasLikeIconBeenToggled, setHasLikeIconBeenToggled] = useState(false)
 
   const onPressToggleLike = async () => {
     if (isBlocked) {
@@ -200,10 +166,6 @@ let PostCtrls = ({
   }
 
   const onShare = () => {
-    const urip = new AtUri(post.uri)
-    const href = makeProfileLink(post.author, 'post', urip.rkey)
-    const url = toShareUrl(href)
-    shareUrl(url)
     sendInteraction({
       item: post.uri,
       event: 'app.bsky.feed.defs#interactionShare',
@@ -212,20 +174,6 @@ let PostCtrls = ({
     })
   }
 
-  const btnStyle = React.useCallback(
-    ({pressed, hovered}: PressableStateCallbackType) => [
-      a.gap_xs,
-      a.rounded_full,
-      a.flex_row,
-      a.justify_center,
-      a.align_center,
-      a.overflow_hidden,
-      {padding: 5},
-      (pressed || hovered) && t.atoms.bg_contrast_25,
-    ],
-    [t.atoms.bg_contrast_25],
-  )
-
   return (
     <View style={[a.flex_row, a.justify_between, a.align_center, style]}>
       <View
@@ -233,39 +181,29 @@ let PostCtrls = ({
           big ? a.align_center : [a.flex_1, a.align_start, {marginLeft: -6}],
           replyDisabled ? {opacity: 0.5} : undefined,
         ]}>
-        <Pressable
+        <PostControlButton
           testID="replyBtn"
-          style={btnStyle}
-          onPress={() => {
-            if (!replyDisabled) {
-              playHaptic('Light')
-              requireAuth(() => onPressReply())
-            }
-          }}
-          accessibilityRole="button"
-          accessibilityLabel={_(
-            msg`Reply (${plural(post.replyCount || 0, {
-              one: '# reply',
-              other: '# replies',
-            })})`,
+          onPress={
+            !replyDisabled ? () => requireAuth(() => onPressReply()) : undefined
+          }
+          label={_(
+            msg({
+              message: `Reply (${plural(post.replyCount || 0, {
+                one: '# reply',
+                other: '# replies',
+              })})`,
+              comment:
+                'Accessibility label for the reply button, verb form followed by number of replies and noun form',
+            }),
           )}
-          accessibilityHint=""
-          hitSlop={POST_CTRL_HITSLOP}>
-          <Bubble
-            style={[defaultCtrlColor, {pointerEvents: 'none'}]}
-            width={big ? 22 : 18}
-          />
-          {typeof post.replyCount !== 'undefined' && post.replyCount > 0 ? (
-            <Text
-              style={[
-                defaultCtrlColor,
-                big ? a.text_md : {fontSize: 15},
-                a.user_select_none,
-              ]}>
+          big={big}>
+          <PostControlButtonIcon icon={Bubble} />
+          {typeof post.replyCount !== 'undefined' && post.replyCount > 0 && (
+            <PostControlButtonText>
               {formatCount(i18n, post.replyCount)}
-            </Text>
-          ) : undefined}
-        </Pressable>
+            </PostControlButtonText>
+          )}
+        </PostControlButton>
       </View>
       <View style={big ? a.align_center : [a.flex_1, a.align_start]}>
         <RepostButton
@@ -278,28 +216,33 @@ let PostCtrls = ({
         />
       </View>
       <View style={big ? a.align_center : [a.flex_1, a.align_start]}>
-        <Pressable
+        <PostControlButton
           testID="likeBtn"
-          style={btnStyle}
+          big={big}
           onPress={() => requireAuth(() => onPressToggleLike())}
-          accessibilityRole="button"
-          accessibilityLabel={
+          label={
             post.viewer?.like
               ? _(
-                  msg`Unlike (${plural(post.likeCount || 0, {
-                    one: '# like',
-                    other: '# likes',
-                  })})`,
+                  msg({
+                    message: `Unlike (${plural(post.likeCount || 0, {
+                      one: '# like',
+                      other: '# likes',
+                    })})`,
+                    comment:
+                      'Accessibility label for the like button when the post has been liked, verb followed by number of likes and noun',
+                  }),
                 )
               : _(
-                  msg`Like (${plural(post.likeCount || 0, {
-                    one: '# like',
-                    other: '# likes',
-                  })})`,
+                  msg({
+                    message: `Like (${plural(post.likeCount || 0, {
+                      one: '# like',
+                      other: '# likes',
+                    })})`,
+                    comment:
+                      'Accessibility label for the like button when the post has not been liked, verb form followed by number of likes and noun form',
+                  }),
                 )
-          }
-          accessibilityHint=""
-          hitSlop={POST_CTRL_HITSLOP}>
+          }>
           <AnimatedLikeIcon
             isLiked={Boolean(post.viewer?.like)}
             big={big}
@@ -311,84 +254,39 @@ let PostCtrls = ({
             isLiked={Boolean(post.viewer?.like)}
             hasBeenToggled={hasLikeIconBeenToggled}
           />
-        </Pressable>
+        </PostControlButton>
       </View>
-      {big && (
-        <>
-          <View style={a.align_center}>
-            <Pressable
-              testID="shareBtn"
-              style={btnStyle}
-              onPress={() => {
-                if (shouldShowLoggedOutWarning) {
-                  loggedOutWarningPromptControl.open()
-                } else {
-                  onShare()
-                }
-              }}
-              accessibilityRole="button"
-              accessibilityLabel={_(msg`Share`)}
-              accessibilityHint=""
-              hitSlop={POST_CTRL_HITSLOP}>
-              <ArrowOutOfBox
-                style={[defaultCtrlColor, {pointerEvents: 'none'}]}
-                width={22}
-              />
-            </Pressable>
-          </View>
-          <Prompt.Basic
-            control={loggedOutWarningPromptControl}
-            title={_(msg`Note about sharing`)}
-            description={_(
-              msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
-            )}
-            onConfirm={onShare}
-            confirmButtonCta={_(msg`Share anyway`)}
-          />
-        </>
-      )}
       <View style={big ? a.align_center : [a.flex_1, a.align_start]}>
-        <PostDropdownBtn
+        <View style={[!big && a.ml_sm]}>
+          <ShareMenuButton
+            testID="postShareBtn"
+            post={post}
+            big={big}
+            record={record}
+            richText={richText}
+            timestamp={post.indexedAt}
+            threadgateRecord={threadgateRecord}
+            onShare={onShare}
+          />
+        </View>
+      </View>
+      <View
+        style={big ? a.align_center : [gtMobile && a.flex_1, a.align_start]}>
+        <PostMenuButton
           testID="postDropdownBtn"
           post={post}
           postFeedContext={feedContext}
           postReqId={reqId}
+          big={big}
           record={record}
           richText={richText}
-          style={{padding: 5}}
-          hitSlop={POST_CTRL_HITSLOP}
           timestamp={post.indexedAt}
           threadgateRecord={threadgateRecord}
           onShowLess={onShowLess}
         />
       </View>
-      {isDiscoverDebugUser && feedContext && (
-        <Pressable
-          accessible={false}
-          style={{
-            position: 'absolute',
-            top: 0,
-            bottom: 0,
-            right: 0,
-            display: 'flex',
-            justifyContent: 'center',
-          }}
-          onPress={e => {
-            e.stopPropagation()
-            Clipboard.setStringAsync(feedContext)
-            Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
-          }}>
-          <Text
-            style={{
-              color: t.palette.contrast_400,
-              fontSize: 7,
-            }}>
-            {feedContext}
-          </Text>
-        </Pressable>
-      )}
     </View>
   )
 }
-PostCtrls = memo(PostCtrls)
-export {PostCtrls}
+PostControls = memo(PostControls)
+export {PostControls}
diff --git a/src/components/icons/ArrowOutOfBox.tsx b/src/components/icons/ArrowOutOfBox.tsx
index 8b395016b..23fee7de0 100644
--- a/src/components/icons/ArrowOutOfBox.tsx
+++ b/src/components/icons/ArrowOutOfBox.tsx
@@ -3,3 +3,8 @@ import {createSinglePathSVG} from './TEMPLATE'
 export const ArrowOutOfBox_Stroke2_Corner0_Rounded = createSinglePathSVG({
   path: 'M12.707 3.293a1 1 0 0 0-1.414 0l-4.5 4.5a1 1 0 0 0 1.414 1.414L11 6.414v8.836a1 1 0 1 0 2 0V6.414l2.793 2.793a1 1 0 1 0 1.414-1.414l-4.5-4.5ZM5 12.75a1 1 0 1 0-2 0V20a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-7.25a1 1 0 1 0-2 0V19H5v-6.25Z',
 })
+
+export const ArrowOutOfBoxModified_Stroke2_Corner2_Rounded =
+  createSinglePathSVG({
+    path: 'M20 13.75a1 1 0 0 1 1 1V18a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3v-3.25a1 1 0 1 1 2 0V18a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-3.25a1 1 0 0 1 1-1ZM12 3a1 1 0 0 1 .707.293l4.5 4.5a1 1 0 1 1-1.414 1.414L13 6.414v8.836a1 1 0 1 1-2 0V6.414L8.207 9.207a1 1 0 1 1-1.414-1.414l4.5-4.5A1 1 0 0 1 12 3Z',
+  })
diff --git a/src/components/icons/ArrowShareRight.tsx b/src/components/icons/ArrowShareRight.tsx
new file mode 100644
index 000000000..499034da7
--- /dev/null
+++ b/src/components/icons/ArrowShareRight.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const ArrowShareRight_Stroke2_Corner2_Rounded = createSinglePathSVG({
+  path: 'M11.839 4.744c0-1.488 1.724-2.277 2.846-1.364l.107.094 7.66 7.256.128.134c.558.652.558 1.62 0 2.272l-.128.135-7.66 7.255c-1.115 1.057-2.953.267-2.953-1.27v-2.748c-3.503.055-5.417.41-6.592.97-.997.474-1.525 1.122-2.084 2.14l-.243.46c-.558 1.088-2.09.583-2.08-.515l.015-.748c.111-3.68.777-6.5 2.546-8.415 1.83-1.98 4.63-2.771 8.438-2.884V4.744Zm2 3.256c0 .79-.604 1.41-1.341 1.494l-.149.01c-3.9.057-6.147.813-7.48 2.254-.963 1.043-1.562 2.566-1.842 4.79.38-.327.826-.622 1.361-.877 1.656-.788 4.08-1.14 7.938-1.169l.153.007c.754.071 1.36.704 1.36 1.491v2.675L20.884 12l-7.045-6.676V8Z',
+})
diff --git a/src/components/icons/ChainLink.tsx b/src/components/icons/ChainLink.tsx
new file mode 100644
index 000000000..be19b556a
--- /dev/null
+++ b/src/components/icons/ChainLink.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const ChainLink_Stroke2_Corner0_Rounded = createSinglePathSVG({
+  path: 'M18.535 5.465a5.003 5.003 0 0 0-7.076 0l-.005.005-.752.742a1 1 0 1 1-1.404-1.424l.749-.74a7.003 7.003 0 0 1 9.904 9.905l-.002.003-.737.746a1 1 0 1 1-1.424-1.404l.747-.757a5.003 5.003 0 0 0 0-7.076ZM6.202 9.288a1 1 0 0 1 .01 1.414l-.747.757a5.003 5.003 0 1 0 7.076 7.076l.005-.005.752-.742a1 1 0 1 1 1.404 1.424l-.746.737-.003.002a7.003 7.003 0 0 1-9.904-9.904l.74-.75a1 1 0 0 1 1.413-.009Zm8.505.005a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414-1.414l4-4a1 1 0 0 1 1.414 0Z',
+})
diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts
index d3334d82f..c67bb60a3 100644
--- a/src/lib/statsig/gates.ts
+++ b/src/lib/statsig/gates.ts
@@ -1,5 +1,6 @@
 export type Gate =
   // Keep this alphabetic please.
+  | 'alt_share_icon'
   | 'debug_show_feedcontext'
   | 'debug_subscriptions'
   | 'explore_show_suggested_feeds'
diff --git a/src/logger/metrics.ts b/src/logger/metrics.ts
index d64e44b40..dfb8cd541 100644
--- a/src/logger/metrics.ts
+++ b/src/logger/metrics.ts
@@ -395,4 +395,12 @@ export type MetricEvents = {
   'live:card:openProfile': {subject: string}
   'live:view:profile': {subject: string}
   'live:view:post': {subject: string; feed?: string}
+
+  'share:open': {context: 'feed' | 'thread'}
+  'share:press:copyLink': {}
+  'share:press:nativeShare': {}
+  'share:press:openDmSearch': {}
+  'share:press:dmSelected': {}
+  'share:press:recentDm': {}
+  'share:press:embed': {}
 }
diff --git a/src/screens/Hashtag.tsx b/src/screens/Hashtag.tsx
index fd1bdffa7..d1b7ab0db 100644
--- a/src/screens/Hashtag.tsx
+++ b/src/screens/Hashtag.tsx
@@ -1,14 +1,14 @@
 import React from 'react'
-import {ListRenderItemInfo, View} from 'react-native'
-import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
+import {type ListRenderItemInfo, View} from 'react-native'
+import {type PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useFocusEffect} from '@react-navigation/native'
-import {NativeStackScreenProps} from '@react-navigation/native-stack'
+import {type NativeStackScreenProps} from '@react-navigation/native-stack'
 
 import {HITSLOP_10} from '#/lib/constants'
 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
-import {CommonNavigatorParams} from '#/lib/routes/types'
+import {type CommonNavigatorParams} from '#/lib/routes/types'
 import {shareUrl} from '#/lib/sharing'
 import {cleanError} from '#/lib/strings/errors'
 import {sanitizeHandle} from '#/lib/strings/handles'
@@ -21,7 +21,7 @@ import {Post} from '#/view/com/post/Post'
 import {List} from '#/view/com/util/List'
 import {atoms as a, web} from '#/alf'
 import {Button, ButtonIcon} from '#/components/Button'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
+import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
 import * as Layout from '#/components/Layout'
 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
 
diff --git a/src/screens/Profile/components/ProfileFeedHeader.tsx b/src/screens/Profile/components/ProfileFeedHeader.tsx
index e2ae3171c..26fa08fdb 100644
--- a/src/screens/Profile/components/ProfileFeedHeader.tsx
+++ b/src/screens/Profile/components/ProfileFeedHeader.tsx
@@ -29,7 +29,7 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
 import {Divider} from '#/components/Divider'
 import {useRichText} from '#/components/hooks/useRichText'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
+import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
 import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
 import {
diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx
index 9fae5d4d5..c0d0341a6 100644
--- a/src/screens/StarterPack/StarterPackScreen.tsx
+++ b/src/screens/StarterPack/StarterPackScreen.tsx
@@ -5,25 +5,29 @@ import {
   AppBskyGraphDefs,
   AppBskyGraphStarterpack,
   AtUri,
-  ModerationOpts,
+  type ModerationOpts,
   RichText as RichTextAPI,
 } from '@atproto/api'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {msg, Plural, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useNavigation} from '@react-navigation/native'
-import {NativeStackScreenProps} from '@react-navigation/native-stack'
+import {type NativeStackScreenProps} from '@react-navigation/native-stack'
 import {useQueryClient} from '@tanstack/react-query'
 
 import {batchedUpdates} from '#/lib/batchedUpdates'
 import {HITSLOP_20} from '#/lib/constants'
 import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
 import {makeProfileLink, makeStarterPackLink} from '#/lib/routes/links'
-import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
+import {
+  type CommonNavigatorParams,
+  type NavigationProp,
+} from '#/lib/routes/types'
 import {logEvent} from '#/lib/statsig/statsig'
 import {cleanError} from '#/lib/strings/errors'
 import {getStarterPackOgCard} from '#/lib/strings/starter-pack'
 import {logger} from '#/logger'
+import {isWeb} from '#/platform/detection'
 import {updateProfileShadow} from '#/state/cache/profile-shadow'
 import {useModerationOpts} from '#/state/preferences/moderation-opts'
 import {getAllListMembers} from '#/state/queries/list-members'
@@ -46,7 +50,8 @@ import {bulkWriteFollows} from '#/screens/Onboarding/util'
 import {atoms as a, useBreakpoints, useTheme} from '#/alf'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {useDialogControl} from '#/components/Dialog'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
+import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
+import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
 import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
 import {Pencil_Stroke2_Corner0_Rounded as Pencil} from '#/components/icons/Pencil'
@@ -600,13 +605,24 @@ function OverflowMenu({
             <>
               <Menu.Group>
                 <Menu.Item
-                  label={_(msg`Share`)}
+                  label={
+                    isWeb
+                      ? _(msg`Copy link to starter pack`)
+                      : _(msg`Share via...`)
+                  }
                   testID="shareStarterPackLinkBtn"
                   onPress={onOpenShareDialog}>
                   <Menu.ItemText>
-                    <Trans>Share link</Trans>
+                    {isWeb ? (
+                      <Trans>Copy link</Trans>
+                    ) : (
+                      <Trans>Share via...</Trans>
+                    )}
                   </Menu.ItemText>
-                  <Menu.ItemIcon icon={ArrowOutOfBox} position="right" />
+                  <Menu.ItemIcon
+                    icon={isWeb ? ChainLinkIcon : ArrowOutOfBoxIcon}
+                    position="right"
+                  />
                 </Menu.Item>
               </Menu.Group>
 
diff --git a/src/screens/Topic.tsx b/src/screens/Topic.tsx
index 62726bcc6..6cf7cf65b 100644
--- a/src/screens/Topic.tsx
+++ b/src/screens/Topic.tsx
@@ -1,14 +1,14 @@
 import React from 'react'
-import {ListRenderItemInfo, View} from 'react-native'
-import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
+import {type ListRenderItemInfo, View} from 'react-native'
+import {type PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useFocusEffect} from '@react-navigation/native'
-import {NativeStackScreenProps} from '@react-navigation/native-stack'
+import {type NativeStackScreenProps} from '@react-navigation/native-stack'
 
 import {HITSLOP_10} from '#/lib/constants'
 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
-import {CommonNavigatorParams} from '#/lib/routes/types'
+import {type CommonNavigatorParams} from '#/lib/routes/types'
 import {shareUrl} from '#/lib/sharing'
 import {cleanError} from '#/lib/strings/errors'
 import {enforceLen} from '#/lib/strings/helpers'
@@ -20,7 +20,7 @@ import {Post} from '#/view/com/post/Post'
 import {List} from '#/view/com/util/List'
 import {atoms as a, web} from '#/alf'
 import {Button, ButtonIcon} from '#/components/Button'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
+import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
 import * as Layout from '#/components/Layout'
 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
 
diff --git a/src/screens/VideoFeed/index.tsx b/src/screens/VideoFeed/index.tsx
index 047961766..2a61db715 100644
--- a/src/screens/VideoFeed/index.tsx
+++ b/src/screens/VideoFeed/index.tsx
@@ -82,7 +82,6 @@ import {useSetMinimalShellMode} from '#/state/shell'
 import {useSetLightStatusBar} from '#/state/shell/light-status-bar'
 import {PostThreadComposePrompt} from '#/view/com/post-thread/PostThreadComposePrompt'
 import {List} from '#/view/com/util/List'
-import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
 import {UserAvatar} from '#/view/com/util/UserAvatar'
 import {Header} from '#/screens/VideoFeed/components/Header'
 import {atoms as a, ios, platform, ThemeProvider, useTheme} from '#/alf'
@@ -97,6 +96,7 @@ import * as Layout from '#/components/Layout'
 import {Link} from '#/components/Link'
 import {ListFooter} from '#/components/Lists'
 import * as Hider from '#/components/moderation/Hider'
+import {PostControls} from '#/components/PostControls'
 import {RichText} from '#/components/RichText'
 import {Text} from '#/components/Typography'
 import * as bsky from '#/types/bsky'
@@ -861,7 +861,7 @@ function Overlay({
               )}
               {record && (
                 <View style={[{left: -5}]}>
-                  <PostCtrls
+                  <PostControls
                     richText={richText}
                     post={post}
                     record={record}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 3925ce9bd..82852aa62 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -43,7 +43,6 @@ import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
 import {Link, TextLink} from '#/view/com/util/Link'
 import {formatCount} from '#/view/com/util/numeric/format'
-import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
 import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
 import {PostMeta} from '#/view/com/util/PostMeta'
 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
@@ -60,6 +59,7 @@ import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
 import {PostAlerts} from '#/components/moderation/PostAlerts'
 import {PostHider} from '#/components/moderation/PostHider'
 import {type AppModerationCause} from '#/components/Pills'
+import {PostControls} from '#/components/PostControls'
 import * as Prompt from '#/components/Prompt'
 import {RichText} from '#/components/RichText'
 import {SubtleWebHover} from '#/components/SubtleWebHover'
@@ -494,7 +494,7 @@ let PostThreadItemLoaded = ({
                   marginLeft: -5,
                 },
               ]}>
-              <PostCtrls
+              <PostControls
                 big
                 post={post}
                 record={record}
@@ -642,7 +642,7 @@ let PostThreadItemLoaded = ({
                   />
                 </View>
               )}
-              <PostCtrls
+              <PostControls
                 post={post}
                 record={record}
                 richText={richText}
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 03463f977..1a48d64d8 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -27,21 +27,21 @@ import {
 import {useModerationOpts} from '#/state/preferences/moderation-opts'
 import {precacheProfile} from '#/state/queries/profile'
 import {useSession} from '#/state/session'
+import {Link, TextLink} from '#/view/com/util/Link'
+import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
+import {PostMeta} from '#/view/com/util/PostMeta'
+import {Text} from '#/view/com/util/text/Text'
+import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
+import {UserInfoText} from '#/view/com/util/UserInfoText'
 import {atoms as a} from '#/alf'
+import {ContentHider} from '#/components/moderation/ContentHider'
+import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
+import {PostAlerts} from '#/components/moderation/PostAlerts'
+import {PostControls} from '#/components/PostControls'
 import {ProfileHoverCard} from '#/components/ProfileHoverCard'
 import {RichText} from '#/components/RichText'
 import {SubtleWebHover} from '#/components/SubtleWebHover'
 import * as bsky from '#/types/bsky'
-import {ContentHider} from '../../../components/moderation/ContentHider'
-import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe'
-import {PostAlerts} from '../../../components/moderation/PostAlerts'
-import {Link, TextLink} from '../util/Link'
-import {PostCtrls} from '../util/post-ctrls/PostCtrls'
-import {PostEmbeds, PostEmbedViewContext} from '../util/post-embeds'
-import {PostMeta} from '../util/PostMeta'
-import {Text} from '../util/text/Text'
-import {PreviewableUserAvatar} from '../util/UserAvatar'
-import {UserInfoText} from '../util/UserInfoText'
 
 export function Post({
   post,
@@ -255,7 +255,7 @@ function PostInner({
               />
             ) : null}
           </ContentHider>
-          <PostCtrls
+          <PostControls
             post={post}
             record={record}
             richText={richText}
diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx
index 2cc749404..3735bbb5a 100644
--- a/src/view/com/posts/PostFeedItem.tsx
+++ b/src/view/com/posts/PostFeedItem.tsx
@@ -37,7 +37,7 @@ import {precacheProfile} from '#/state/queries/profile'
 import {useSession} from '#/state/session'
 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
 import {FeedNameText} from '#/view/com/util/FeedInfoText'
-import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
+import {Link, TextLink, TextLinkOnWebOnly} from '#/view/com/util/Link'
 import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
 import {PostMeta} from '#/view/com/util/PostMeta'
 import {Text} from '#/view/com/util/text/Text'
@@ -49,11 +49,12 @@ import {ContentHider} from '#/components/moderation/ContentHider'
 import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
 import {PostAlerts} from '#/components/moderation/PostAlerts'
 import {type AppModerationCause} from '#/components/Pills'
+import {PostControls} from '#/components/PostControls'
+import {DiscoverDebug} from '#/components/PostControls/DiscoverDebug'
 import {ProfileHoverCard} from '#/components/ProfileHoverCard'
 import {RichText} from '#/components/RichText'
 import {SubtleWebHover} from '#/components/SubtleWebHover'
 import * as bsky from '#/types/bsky'
-import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link'
 
 interface FeedItemProps {
   record: AppBskyFeedPost.Record
@@ -439,7 +440,7 @@ let FeedItemInner = ({
             post={post}
             threadgateRecord={threadgateRecord}
           />
-          <PostCtrls
+          <PostControls
             post={post}
             record={record}
             richText={richText}
@@ -451,6 +452,8 @@ let FeedItemInner = ({
             onShowLess={onShowLess}
           />
         </View>
+
+        <DiscoverDebug feedContext={feedContext} />
       </View>
     </Link>
   )
diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx
index f1fd237ec..d18ba12c1 100644
--- a/src/view/com/profile/ProfileMenu.tsx
+++ b/src/view/com/profile/ProfileMenu.tsx
@@ -12,6 +12,7 @@ import {type NavigationProp} from '#/lib/routes/types'
 import {shareText, shareUrl} from '#/lib/sharing'
 import {toShareUrl} from '#/lib/strings/url-helpers'
 import {logger} from '#/logger'
+import {isWeb} from '#/platform/detection'
 import {type Shadow} from '#/state/cache/types'
 import {useModalControls} from '#/state/modals'
 import {
@@ -26,9 +27,11 @@ import {EventStopper} from '#/view/com/util/EventStopper'
 import * as Toast from '#/view/com/util/Toast'
 import {Button, ButtonIcon} from '#/components/Button'
 import {useDialogControl} from '#/components/Dialog'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
-import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck'
-import {CircleX_Stroke2_Corner0_Rounded as CircleX} from '#/components/icons/CircleX'
+import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
+import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
+import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/components/icons/CircleCheck'
+import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX'
+import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
 import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
 import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
 import {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle'
@@ -236,7 +239,9 @@ let ProfileMenu = ({
           <Menu.Group>
             <Menu.Item
               testID="profileHeaderDropdownShareBtn"
-              label={_(msg`Share`)}
+              label={
+                isWeb ? _(msg`Copy link to profile`) : _(msg`Share via...`)
+              }
               onPress={() => {
                 if (showLoggedOutWarning) {
                   loggedOutWarningPromptControl.open()
@@ -245,9 +250,13 @@ let ProfileMenu = ({
                 }
               }}>
               <Menu.ItemText>
-                <Trans>Share</Trans>
+                {isWeb ? (
+                  <Trans>Copy link to profile</Trans>
+                ) : (
+                  <Trans>Share via...</Trans>
+                )}
               </Menu.ItemText>
-              <Menu.ItemIcon icon={Share} />
+              <Menu.ItemIcon icon={isWeb ? ChainLinkIcon : ArrowOutOfBoxIcon} />
             </Menu.Item>
             <Menu.Item
               testID="profileHeaderDropdownSearchBtn"
@@ -329,7 +338,7 @@ let ProfileMenu = ({
                       <Menu.ItemText>
                         <Trans>Remove verification</Trans>
                       </Menu.ItemText>
-                      <Menu.ItemIcon icon={CircleX} />
+                      <Menu.ItemIcon icon={CircleXIcon} />
                     </Menu.Item>
                   ) : (
                     <Menu.Item
@@ -339,7 +348,7 @@ let ProfileMenu = ({
                       <Menu.ItemText>
                         <Trans>Verify account</Trans>
                       </Menu.ItemText>
-                      <Menu.ItemIcon icon={CircleCheck} />
+                      <Menu.ItemIcon icon={CircleCheckIcon} />
                     </Menu.Item>
                   ))}
                 {!isSelf && (
@@ -414,7 +423,7 @@ let ProfileMenu = ({
                   <Menu.ItemText>
                     <Trans>Copy at:// URI</Trans>
                   </Menu.ItemText>
-                  <Menu.ItemIcon icon={Share} />
+                  <Menu.ItemIcon icon={ClipboardIcon} />
                 </Menu.Item>
                 <Menu.Item
                   testID="profileHeaderDropdownShareDIDBtn"
@@ -423,7 +432,7 @@ let ProfileMenu = ({
                   <Menu.ItemText>
                     <Trans>Copy DID</Trans>
                   </Menu.ItemText>
-                  <Menu.ItemIcon icon={Share} />
+                  <Menu.ItemIcon icon={ClipboardIcon} />
                 </Menu.Item>
               </Menu.Group>
             </>