about summary refs log tree commit diff
path: root/src/view/com/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/util')
-rw-r--r--src/view/com/util/LoadingPlaceholder.tsx64
-rw-r--r--src/view/com/util/forms/PostDropdownBtn.tsx20
-rw-r--r--src/view/com/util/post-ctrls/PostCtrls.tsx147
-rw-r--r--src/view/com/util/post-ctrls/RepostButton.tsx174
-rw-r--r--src/view/com/util/post-ctrls/RepostButton.web.tsx194
5 files changed, 318 insertions, 281 deletions
diff --git a/src/view/com/util/LoadingPlaceholder.tsx b/src/view/com/util/LoadingPlaceholder.tsx
index 6dfe12598..882f7216b 100644
--- a/src/view/com/util/LoadingPlaceholder.tsx
+++ b/src/view/com/util/LoadingPlaceholder.tsx
@@ -1,20 +1,20 @@
 import React from 'react'
 import {
-  StyleSheet,
+  DimensionValue,
   StyleProp,
+  StyleSheet,
   View,
   ViewStyle,
-  DimensionValue,
 } from 'react-native'
-import {
-  HeartIcon,
-  HeartIconSolid,
-  CommentBottomArrow,
-  RepostIcon,
-} from 'lib/icons'
+
+import {usePalette} from 'lib/hooks/usePalette'
+import {HeartIconSolid} from 'lib/icons'
 import {s} from 'lib/styles'
 import {useTheme} from 'lib/ThemeContext'
-import {usePalette} from 'lib/hooks/usePalette'
+import {useTheme as useTheme_NEW} from '#/alf'
+import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble'
+import {Heart2_Stroke2_Corner0_Rounded as HeartIconOutline} from '#/components/icons/Heart2'
+import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
 
 export function LoadingPlaceholder({
   width,
@@ -46,7 +46,7 @@ export function PostLoadingPlaceholder({
 }: {
   style?: StyleProp<ViewStyle>
 }) {
-  const theme = useTheme()
+  const t = useTheme_NEW()
   const pal = usePalette('default')
   return (
     <View style={[styles.post, pal.view, style]}>
@@ -67,35 +67,47 @@ export function PostLoadingPlaceholder({
         <LoadingPlaceholder width="95%" height={6} style={{marginBottom: 8}} />
         <LoadingPlaceholder width="80%" height={6} style={{marginBottom: 11}} />
         <View style={styles.postCtrls}>
-          <View style={styles.postCtrl}>
-            <View style={[styles.postBtn, {paddingLeft: 0}]}>
-              <CommentBottomArrow
-                style={[{color: theme.palette.default.icon, marginTop: 1}]}
-                strokeWidth={3}
-                size={15}
+          <View style={[styles.postCtrl, {marginLeft: -5}]}>
+            <View style={styles.postBtn}>
+              <Bubble
+                style={[
+                  {
+                    color: t.palette.contrast_500,
+                  },
+                  {pointerEvents: 'none'},
+                ]}
+                width={18}
               />
             </View>
           </View>
           <View style={styles.postCtrl}>
             <View style={styles.postBtn}>
-              <RepostIcon
-                style={{color: theme.palette.default.icon}}
-                strokeWidth={3}
-                size={20}
+              <Repost
+                style={[
+                  {
+                    color: t.palette.contrast_500,
+                  },
+                  {pointerEvents: 'none'},
+                ]}
+                width={18}
               />
             </View>
           </View>
           <View style={styles.postCtrl}>
             <View style={styles.postBtn}>
-              <HeartIcon
-                style={{color: theme.palette.default.icon} as ViewStyle}
-                size={16}
-                strokeWidth={3}
+              <HeartIconOutline
+                style={[
+                  {
+                    color: t.palette.contrast_500,
+                  },
+                  {pointerEvents: 'none'},
+                ]}
+                width={18}
               />
             </View>
           </View>
           <View style={styles.postCtrl}>
-            <View style={styles.postBtn} />
+            <View style={[styles.postBtn, {minHeight: 30}]} />
           </View>
         </View>
       </View>
@@ -290,10 +302,10 @@ const styles = StyleSheet.create({
     flex: 1,
   },
   postBtn: {
-    padding: 5,
     flex: 1,
     flexDirection: 'row',
     alignItems: 'center',
+    padding: 5,
   },
   avatar: {
     borderRadius: 26,
diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx
index 50677ee8a..cd82ec98f 100644
--- a/src/view/com/util/forms/PostDropdownBtn.tsx
+++ b/src/view/com/util/forms/PostDropdownBtn.tsx
@@ -1,5 +1,10 @@
 import React, {memo} from 'react'
-import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native'
+import {
+  Pressable,
+  type PressableProps,
+  type StyleProp,
+  type ViewStyle,
+} from 'react-native'
 import * as Clipboard from 'expo-clipboard'
 import {
   AppBskyActorDefs,
@@ -7,7 +12,6 @@ import {
   AtUri,
   RichText as RichTextAPI,
 } from '@atproto/api'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useNavigation} from '@react-navigation/native'
@@ -37,6 +41,7 @@ import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons
 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 {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
 import {
   EmojiSad_Stroke2_Corner0_Rounded as EmojiSad,
   EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmile,
@@ -68,6 +73,7 @@ let PostDropdownBtn = ({
   richText,
   style,
   hitSlop,
+  size,
   timestamp,
 }: {
   testID: string
@@ -79,6 +85,7 @@ let PostDropdownBtn = ({
   richText: RichTextAPI
   style?: StyleProp<ViewStyle>
   hitSlop?: PressableProps['hitSlop']
+  size?: 'lg' | 'md' | 'sm'
   timestamp: string
 }): React.ReactNode => {
   const {hasSession, currentAccount} = useSession()
@@ -238,14 +245,13 @@ let PostDropdownBtn = ({
                   style,
                   a.rounded_full,
                   (state.hovered || state.pressed) && [
-                    alf.atoms.bg_contrast_50,
+                    alf.atoms.bg_contrast_25,
                   ],
                 ]}>
-                <FontAwesomeIcon
-                  icon="ellipsis"
-                  size={20}
-                  color={defaultCtrlColor}
+                <DotsHorizontal
+                  fill={defaultCtrlColor}
                   style={{pointerEvents: 'none'}}
+                  size={size}
                 />
               </Pressable>
             )
diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx
index b6c07d573..2b0220842 100644
--- a/src/view/com/util/post-ctrls/PostCtrls.tsx
+++ b/src/view/com/util/post-ctrls/PostCtrls.tsx
@@ -1,10 +1,10 @@
 import React, {memo, useCallback} from 'react'
 import {
-  StyleProp,
-  StyleSheet,
-  TouchableOpacity,
+  Pressable,
+  type PressableStateCallbackType,
+  type StyleProp,
   View,
-  ViewStyle,
+  type ViewStyle,
 } from 'react-native'
 import {
   AppBskyFeedDefs,
@@ -16,12 +16,11 @@ import {msg, plural} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {HITSLOP_10, HITSLOP_20} from '#/lib/constants'
-import {CommentBottomArrow, HeartIcon, HeartIconSolid} from '#/lib/icons'
+import {useHaptics} from '#/lib/haptics'
 import {makeProfileLink} from '#/lib/routes/links'
 import {shareUrl} from '#/lib/sharing'
 import {toShareUrl} from '#/lib/strings/url-helpers'
 import {s} from '#/lib/styles'
-import {useTheme} from '#/lib/ThemeContext'
 import {Shadow} from '#/state/cache/types'
 import {useFeedFeedbackContext} from '#/state/feed-feedback'
 import {useModalControls} from '#/state/modals'
@@ -31,9 +30,14 @@ import {
 } from '#/state/queries/post'
 import {useRequireAuth} from '#/state/session'
 import {useComposerControls} from '#/state/shell/composer'
-import {useHaptics} from 'lib/haptics'
+import {atoms as a, useTheme} from '#/alf'
 import {useDialogControl} from '#/components/Dialog'
 import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
+import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble'
+import {
+  Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled,
+  Heart2_Stroke2_Corner0_Rounded as HeartIconOutline,
+} from '#/components/icons/Heart2'
 import * as Prompt from '#/components/Prompt'
 import {PostDropdownBtn} from '../forms/PostDropdownBtn'
 import {Text} from '../text/Text'
@@ -58,7 +62,7 @@ let PostCtrls = ({
   onPressReply: () => void
   logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
 }): React.ReactNode => {
-  const theme = useTheme()
+  const t = useTheme()
   const {_} = useLingui()
   const {openComposer} = useComposerControls()
   const {closeModal} = useModalControls()
@@ -80,9 +84,9 @@ let PostCtrls = ({
 
   const defaultCtrlColor = React.useMemo(
     () => ({
-      color: theme.palette.default.postCtrl,
+      color: t.palette.contrast_500,
     }),
-    [theme],
+    [t],
   ) as StyleProp<ViewStyle>
 
   const onPressToggleLike = React.useCallback(async () => {
@@ -185,57 +189,70 @@ let PostCtrls = ({
     })
   }, [post.uri, post.author, sendInteraction, feedContext])
 
+  const btnStyle = React.useCallback(
+    ({pressed, hovered}: PressableStateCallbackType) => [
+      a.gap_xs,
+      a.rounded_full,
+      a.flex_row,
+      a.align_center,
+      a.justify_center,
+      {padding: 5},
+      (pressed || hovered) && t.atoms.bg_contrast_25,
+    ],
+    [t.atoms.bg_contrast_25],
+  )
+
   return (
-    <View style={[styles.ctrls, style]}>
+    <View style={[a.flex_row, a.justify_between, a.align_center, style]}>
       <View
         style={[
-          big ? styles.ctrlBig : styles.ctrl,
+          big ? a.align_center : [a.flex_1, a.align_start, {marginLeft: -5}],
           post.viewer?.replyDisabled ? {opacity: 0.5} : undefined,
         ]}>
-        <TouchableOpacity
+        <Pressable
           testID="replyBtn"
-          style={[styles.btn, !big && styles.btnPad, {paddingLeft: 0}]}
+          style={btnStyle}
           onPress={() => {
             if (!post.viewer?.replyDisabled) {
               requireAuth(() => onPressReply())
             }
           }}
-          accessibilityRole="button"
           accessibilityLabel={plural(post.replyCount || 0, {
             one: 'Reply (# reply)',
             other: 'Reply (# replies)',
           })}
           accessibilityHint=""
           hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
-          <CommentBottomArrow
-            style={[defaultCtrlColor, big ? s.mt2 : styles.mt1]}
-            strokeWidth={3}
-            size={big ? 20 : 15}
+          <Bubble
+            style={[defaultCtrlColor, {pointerEvents: 'none'}]}
+            width={big ? 22 : 18}
           />
           {typeof post.replyCount !== 'undefined' && post.replyCount > 0 ? (
-            <Text style={[defaultCtrlColor, s.ml5, s.f15]}>
+            <Text
+              style={[
+                defaultCtrlColor,
+                big ? a.text_md : {fontSize: 15},
+                a.user_select_none,
+              ]}>
               {post.replyCount}
             </Text>
           ) : undefined}
-        </TouchableOpacity>
+        </Pressable>
       </View>
-      <View style={big ? styles.ctrlBig : styles.ctrl}>
+      <View style={big ? a.align_center : [a.flex_1, a.align_start]}>
         <RepostButton
-          big={big}
           isReposted={!!post.viewer?.repost}
           repostCount={post.repostCount}
           onRepost={onRepost}
           onQuote={onQuote}
+          big={big}
         />
       </View>
-      <View style={big ? styles.ctrlBig : styles.ctrl}>
-        <TouchableOpacity
+      <View style={big ? a.align_center : [a.flex_1, a.align_start]}>
+        <Pressable
           testID="likeBtn"
-          style={[styles.btn, !big && styles.btnPad]}
-          onPress={() => {
-            requireAuth(() => onPressToggleLike())
-          }}
-          accessibilityRole="button"
+          style={btnStyle}
+          onPress={() => requireAuth(() => onPressToggleLike())}
           accessibilityLabel={
             post.viewer?.like
               ? plural(post.likeCount || 0, {
@@ -250,33 +267,36 @@ let PostCtrls = ({
           accessibilityHint=""
           hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
           {post.viewer?.like ? (
-            <HeartIconSolid style={s.likeColor} size={big ? 22 : 16} />
+            <HeartIconFilled style={s.likeColor} width={big ? 22 : 18} />
           ) : (
-            <HeartIcon
-              style={[defaultCtrlColor, big ? styles.mt1 : undefined]}
-              strokeWidth={3}
-              size={big ? 20 : 16}
+            <HeartIconOutline
+              style={[defaultCtrlColor, {pointerEvents: 'none'}]}
+              width={big ? 22 : 18}
             />
           )}
           {typeof post.likeCount !== 'undefined' && post.likeCount > 0 ? (
             <Text
               testID="likeCount"
-              style={
-                post.viewer?.like
-                  ? [s.bold, s.likeColor, s.f15, s.ml5]
-                  : [defaultCtrlColor, s.f15, s.ml5]
-              }>
+              style={[
+                [
+                  big ? a.text_md : {fontSize: 15},
+                  a.user_select_none,
+                  post.viewer?.like
+                    ? [a.font_bold, s.likeColor]
+                    : defaultCtrlColor,
+                ],
+              ]}>
               {post.likeCount}
             </Text>
           ) : undefined}
-        </TouchableOpacity>
+        </Pressable>
       </View>
       {big && (
         <>
-          <View style={styles.ctrlBig}>
-            <TouchableOpacity
+          <View style={a.align_center}>
+            <Pressable
               testID="shareBtn"
-              style={[styles.btn]}
+              style={btnStyle}
               onPress={() => {
                 if (shouldShowLoggedOutWarning) {
                   loggedOutWarningPromptControl.open()
@@ -284,15 +304,14 @@ let PostCtrls = ({
                   onShare()
                 }
               }}
-              accessibilityRole="button"
-              accessibilityLabel={`${_(msg`Share`)}`}
+              accessibilityLabel={_(msg`Share`)}
               accessibilityHint=""
               hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
               <ArrowOutOfBox
-                style={[defaultCtrlColor, styles.mt1]}
+                style={[defaultCtrlColor, {pointerEvents: 'none'}]}
                 width={22}
               />
-            </TouchableOpacity>
+            </Pressable>
           </View>
           <Prompt.Basic
             control={loggedOutWarningPromptControl}
@@ -305,7 +324,7 @@ let PostCtrls = ({
           />
         </>
       )}
-      <View style={big ? styles.ctrlBig : styles.ctrl}>
+      <View style={big ? a.align_center : [a.flex_1, a.align_start]}>
         <PostDropdownBtn
           testID="postDropdownBtn"
           postAuthor={post.author}
@@ -314,7 +333,7 @@ let PostCtrls = ({
           postFeedContext={feedContext}
           record={record}
           richText={richText}
-          style={styles.btnPad}
+          style={{padding: 5}}
           hitSlop={big ? HITSLOP_20 : HITSLOP_10}
           timestamp={post.indexedAt}
         />
@@ -324,31 +343,3 @@ let PostCtrls = ({
 }
 PostCtrls = memo(PostCtrls)
 export {PostCtrls}
-
-const styles = StyleSheet.create({
-  ctrls: {
-    flexDirection: 'row',
-    justifyContent: 'space-between',
-    alignItems: 'center',
-  },
-  ctrl: {
-    flex: 1,
-    alignItems: 'flex-start',
-  },
-  ctrlBig: {
-    alignItems: 'center',
-  },
-  btn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-  },
-  btnPad: {
-    paddingTop: 5,
-    paddingBottom: 5,
-    paddingLeft: 5,
-    paddingRight: 5,
-  },
-  mt1: {
-    marginTop: 1,
-  },
-})
diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx
index f58417887..1124cb405 100644
--- a/src/view/com/util/post-ctrls/RepostButton.tsx
+++ b/src/view/com/util/post-ctrls/RepostButton.tsx
@@ -1,108 +1,132 @@
 import React, {memo, useCallback} from 'react'
-import {StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native'
+import {View} from 'react-native'
 import {msg, plural} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
-import {useModalControls} from '#/state/modals'
 import {useRequireAuth} from '#/state/session'
-import {HITSLOP_10, HITSLOP_20} from 'lib/constants'
-import {RepostIcon} from 'lib/icons'
-import {colors, s} from 'lib/styles'
-import {useTheme} from 'lib/ThemeContext'
-import {Text} from '../text/Text'
+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'
 
 interface Props {
   isReposted: boolean
   repostCount?: number
-  big?: boolean
   onRepost: () => void
   onQuote: () => void
+  big?: boolean
 }
 
 let RepostButton = ({
   isReposted,
   repostCount,
-  big,
   onRepost,
   onQuote,
+  big,
 }: Props): React.ReactNode => {
-  const theme = useTheme()
+  const t = useTheme()
   const {_} = useLingui()
-  const {openModal} = useModalControls()
   const requireAuth = useRequireAuth()
+  const dialogControl = Dialog.useDialogControl()
 
-  const defaultControlColor = React.useMemo(
+  const color = React.useMemo(
     () => ({
-      color: theme.palette.default.postCtrl,
+      color: isReposted ? t.palette.positive_600 : t.palette.contrast_500,
     }),
-    [theme],
+    [t, isReposted],
   )
 
-  const onPressToggleRepostWrapper = useCallback(() => {
-    openModal({
-      name: 'repost',
-      onRepost: onRepost,
-      onQuote: onQuote,
-      isReposted,
-    })
-  }, [onRepost, onQuote, isReposted, openModal])
+  const close = useCallback(() => dialogControl.close(), [dialogControl])
 
   return (
-    <TouchableOpacity
-      testID="repostBtn"
-      onPress={() => {
-        requireAuth(() => onPressToggleRepostWrapper())
-      }}
-      style={[styles.btn, !big && styles.btnPad]}
-      accessibilityRole="button"
-      accessibilityLabel={`${
-        isReposted
-          ? _(msg`Undo repost`)
-          : _(msg({message: 'Repost', context: 'action'}))
-      } (${plural(repostCount || 0, {one: '# repost', other: '# reposts'})})`}
-      accessibilityHint=""
-      hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
-      <RepostIcon
-        style={
+    <>
+      <Button
+        testID="repostBtn"
+        onPress={() => {
+          requireAuth(() => dialogControl.open())
+        }}
+        style={[a.flex_row, a.align_center, a.gap_xs, {padding: 5}]}
+        hoverStyle={t.atoms.bg_contrast_25}
+        label={`${
           isReposted
-            ? (styles.reposted as StyleProp<ViewStyle>)
-            : defaultControlColor
-        }
-        strokeWidth={2.4}
-        size={big ? 24 : 20}
-      />
-      {typeof repostCount !== 'undefined' && repostCount > 0 ? (
-        <Text
-          testID="repostCount"
-          style={
-            isReposted
-              ? [s.bold, s.green3, s.f15, s.ml5]
-              : [defaultControlColor, s.f15, s.ml5]
-          }>
-          {repostCount}
-        </Text>
-      ) : undefined}
-    </TouchableOpacity>
+            ? _(msg`Undo repost`)
+            : _(msg({message: 'Repost', context: 'action'}))
+        } (${plural(repostCount || 0, {one: '# repost', other: '# reposts'})})`}
+        shape="round"
+        variant="ghost"
+        color="secondary">
+        <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,
+            ]}>
+            {repostCount}
+          </Text>
+        ) : undefined}
+      </Button>
+      <Dialog.Outer control={dialogControl}>
+        <Dialog.Handle />
+        <Dialog.Inner label={_(msg`Repost or quote post`)}>
+          <View style={a.gap_xl}>
+            <View style={a.gap_xs}>
+              <Button
+                style={[a.justify_start, a.px_md]}
+                label={
+                  isReposted
+                    ? _(msg`Remove repost`)
+                    : _(msg({message: `Repost`, context: 'action'}))
+                }
+                onPress={() => {
+                  dialogControl.close()
+                  onRepost()
+                }}
+                size="large"
+                variant="ghost"
+                color="primary">
+                <Repost size="lg" fill={t.palette.primary_500} />
+                <Text style={[a.font_bold, a.text_xl]}>
+                  {isReposted
+                    ? _(msg`Remove repost`)
+                    : _(msg({message: `Repost`, context: 'action'}))}
+                </Text>
+              </Button>
+              <Button
+                style={[a.justify_start, a.px_md]}
+                label={_(msg`Quote post`)}
+                onPress={() => {
+                  dialogControl.close(() => {
+                    onQuote()
+                  })
+                }}
+                size="large"
+                variant="ghost"
+                color="primary">
+                <Quote size="lg" fill={t.palette.primary_500} />
+                <Text style={[a.font_bold, a.text_xl]}>
+                  {_(msg`Quote post`)}
+                </Text>
+              </Button>
+            </View>
+            <Button
+              label={_(msg`Cancel quote post`)}
+              onAccessibilityEscape={close}
+              onPress={close}
+              size="medium"
+              variant="solid"
+              color="primary">
+              <ButtonText>{_(msg`Cancel`)}</ButtonText>
+            </Button>
+          </View>
+        </Dialog.Inner>
+      </Dialog.Outer>
+    </>
   )
 }
 RepostButton = memo(RepostButton)
 export {RepostButton}
-
-const styles = StyleSheet.create({
-  btn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-  },
-  btnPad: {
-    paddingTop: 5,
-    paddingBottom: 5,
-    paddingLeft: 5,
-    paddingRight: 5,
-  },
-  reposted: {
-    color: colors.green3,
-  },
-  repostCount: {
-    color: 'currentColor',
-  },
-})
diff --git a/src/view/com/util/post-ctrls/RepostButton.web.tsx b/src/view/com/util/post-ctrls/RepostButton.web.tsx
index bbe5869fe..089898141 100644
--- a/src/view/com/util/post-ctrls/RepostButton.web.tsx
+++ b/src/view/com/util/post-ctrls/RepostButton.web.tsx
@@ -1,130 +1,134 @@
 import React from 'react'
-import {StyleProp, StyleSheet, View, ViewStyle, Pressable} from 'react-native'
-import {RepostIcon} from 'lib/icons'
-import {colors} from 'lib/styles'
-import {useTheme} from 'lib/ThemeContext'
-import {Text} from '../text/Text'
-
-import {
-  NativeDropdown,
-  DropdownItem as NativeDropdownItem,
-} from '../forms/NativeDropdown'
-import {EventStopper} from '../EventStopper'
-import {useLingui} from '@lingui/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 {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'
 
 interface Props {
   isReposted: boolean
   repostCount?: number
-  big?: boolean
   onRepost: () => void
   onQuote: () => void
-  style?: StyleProp<ViewStyle>
+  big?: boolean
 }
 
 export const RepostButton = ({
   isReposted,
   repostCount,
-  big,
   onRepost,
   onQuote,
+  big,
 }: Props) => {
-  const theme = useTheme()
+  const t = useTheme()
   const {_} = useLingui()
   const {hasSession} = useSession()
   const requireAuth = useRequireAuth()
 
-  const defaultControlColor = React.useMemo(
+  const color = React.useMemo(
     () => ({
-      color: theme.palette.default.postCtrl,
+      color: isReposted ? t.palette.positive_600 : t.palette.contrast_500,
     }),
-    [theme],
-  )
-
-  const dropdownItems: NativeDropdownItem[] = [
-    {
-      label: isReposted ? _(msg`Undo repost`) : _(msg`Repost`),
-      testID: 'repostDropdownRepostBtn',
-      icon: {
-        ios: {name: 'repeat'},
-        android: '',
-        web: 'retweet',
-      },
-      onPress: onRepost,
-    },
-    {
-      label: _(msg`Quote post`),
-      testID: 'repostDropdownQuoteBtn',
-      icon: {
-        ios: {name: 'quote.bubble'},
-        android: '',
-        web: 'quote-left',
-      },
-      onPress: onQuote,
-    },
-  ]
-
-  const inner = (
-    <View
-      style={[
-        styles.btn,
-        !big && styles.btnPad,
-        (isReposted
-          ? styles.reposted
-          : defaultControlColor) as StyleProp<ViewStyle>,
-      ]}>
-      <RepostIcon strokeWidth={2.2} size={big ? 24 : 20} />
-      {typeof repostCount !== 'undefined' && repostCount > 0 ? (
-        <Text
-          testID="repostCount"
-          type={isReposted ? 'md-bold' : 'md'}
-          style={styles.repostCount}>
-          {repostCount}
-        </Text>
-      ) : undefined}
-    </View>
+    [t, isReposted],
   )
 
   return hasSession ? (
-    <EventStopper>
-      <NativeDropdown
-        items={dropdownItems}
-        accessibilityLabel={_(msg`Repost or quote post`)}
-        accessibilityHint="">
-        {inner}
-      </NativeDropdown>
+    <EventStopper onKeyDown={false}>
+      <Menu.Root>
+        <Menu.Trigger label={_(msg`Repost or quote post`)}>
+          {({props, state}) => {
+            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>
+            )
+          }}
+        </Menu.Trigger>
+        <Menu.Outer style={{minWidth: 170}}>
+          <Menu.Item
+            label={isReposted ? _(msg`Undo repost`) : _(msg`Repost`)}
+            testID="repostDropdownRepostBtn"
+            onPress={onRepost}>
+            <Menu.ItemText>
+              {isReposted ? _(msg`Undo repost`) : _(msg`Repost`)}
+            </Menu.ItemText>
+            <Menu.ItemIcon icon={Repost} position="right" />
+          </Menu.Item>
+          <Menu.Item
+            label={_(msg`Quote post`)}
+            testID="repostDropdownQuoteBtn"
+            onPress={onQuote}>
+            <Menu.ItemText>{_(msg`Quote post`)}</Menu.ItemText>
+            <Menu.ItemIcon icon={Quote} position="right" />
+          </Menu.Item>
+        </Menu.Outer>
+      </Menu.Root>
     </EventStopper>
   ) : (
-    <Pressable
-      accessibilityRole="button"
+    <Button
       onPress={() => {
         requireAuth(() => {})
       }}
-      accessibilityLabel={_(msg`Repost or quote post`)}
-      accessibilityHint="">
-      {inner}
-    </Pressable>
+      label={_(msg`Repost or quote post`)}
+      style={{padding: 0}}
+      hoverStyle={t.atoms.bg_contrast_25}
+      shape="round"
+      variant="ghost"
+      color="secondary">
+      <RepostInner
+        isReposted={isReposted}
+        color={color}
+        repostCount={repostCount}
+        big={big}
+      />
+    </Button>
   )
 }
 
-const styles = StyleSheet.create({
-  btn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    gap: 4,
-  },
-  btnPad: {
-    paddingTop: 5,
-    paddingBottom: 5,
-    paddingLeft: 5,
-    paddingRight: 5,
-  },
-  reposted: {
-    color: colors.green3,
-  },
-  repostCount: {
-    color: 'currentColor',
-  },
-})
+const RepostInner = ({
+  isReposted,
+  color,
+  repostCount,
+  big,
+}: {
+  isReposted: boolean
+  color: {color: string}
+  repostCount?: number
+  big?: boolean
+}) => (
+  <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,
+        ]}>
+        {repostCount}
+      </Text>
+    ) : undefined}
+  </View>
+)