Bluesky app fork with some witchin' additions 💫

Use ALF for post controls (#3400)

* alf the repost dropdown on web + import icons

* alf like icon

* convert other post controls

* add missing padding to share button

* refine buttons and use better icons

* revert buttonicon changes

* remove ButtonIcon and ButtonText from repost dialog

* use 15px font size when not big

* reduce size and use contrast_25

* add hover state to logged out view

* add `userSelect: 'none'` to buttons

* use width rather than height

* fix quote close behaviour

* prettier

* Fix Esc on repost

* Use new icons for placeholder

* Fix placeholder

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

authored by samuel.fm Dan Abramov and committed by GitHub 165feedb 4d39ef2e

+1
assets/icons/closeQuote_filled_stroke2_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M3.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7ZM14.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7Z"/></svg>
+1
assets/icons/closeQuote_stroke2_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2.004 5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936A1 1 0 0 1 5.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234c.104-.085.21-.177.316-.276.846-.793 1.684-2.043 1.684-3.958V6h-5v6h2a1 1 0 0 1 1 1v4.234Zm6-12.234a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234V13a1 1 0 0 0-1-1h-2V6h5v7c0 1.915-.838 3.165-1.684 3.958-.106.1-.212.191-.316.276Z" clip-rule="evenodd"/></svg>
+1
assets/icons/closeQuote_stroke2_corner1_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2.003 5.999a2 2 0 0 1 2-1.999h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936a1 1 0 0 1-1.351-.935L5 14H4a2 2 0 0 1-2-2.001l.002-6Zm5 11.236L7 12.999A1 1 0 0 0 6 12H4l.003-6h5v7c0 1.915-.837 3.165-1.683 3.958-.106.1-.213.192-.317.277Zm6-11.235a2 2 0 0 1 2-2h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-1a2 2 0 0 1-2-2V6Zm7 0h-5v6h2a1 1 0 0 1 1 1v4.234c.105-.085.211-.177.317-.276.846-.793 1.684-2.043 1.684-3.958V6Z" clip-rule="evenodd"/></svg>
+1
assets/icons/openQuote_filled_stroke2_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M8.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15C3.166 6.665 2.004 8.415 2.004 11v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5ZM19.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15-1.154 1.082-2.316 2.832-2.316 5.417v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5Z"/></svg>
+1
assets/icons/openQuote_stroke2_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7.574 4.178a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.163 8.163 0 0 1 1.569-1.15 7.029 7.029 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588a5.99 5.99 0 0 0-.316.276C4.842 7.835 4.004 9.085 4.004 11v7h5v-6h-2a1 1 0 0 1-1-1V6.766Zm12.57-2.588a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.166 8.166 0 0 1 1.569-1.15 7.038 7.038 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588c-.105.085-.21.177-.316.276-.846.793-1.684 2.043-1.684 3.958v7h5v-6h-2a1 1 0 0 1-1-1V6.766Z" clip-rule="evenodd"/></svg>
+1
assets/icons/repost_stroke2_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M16.293 2.293a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 0 1-1.414-1.414L17.586 7H5v4a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1h13.586l-1.293-1.293a1 1 0 0 1 0-1.414ZM21 13v5a1 1 0 0 1-1 1H6.414l1.293 1.293a1 1 0 1 1-1.414 1.414l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 0 1 1.414 1.414L6.414 17H19v-4a1 1 0 1 1 2 0Z" clip-rule="evenodd"/></svg>
+1
assets/icons/repost_stroke2_corner3_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M16.793 2.293a1 1 0 0 1 1.414 0L20.5 4.586a2 2 0 0 1 0 2.828l-2.293 2.293a1 1 0 0 1-1.414-1.414L18.086 7H7a2 2 0 0 0-2 2v2a1 1 0 1 1-2 0V9a4 4 0 0 1 4-4h11.086l-1.293-1.293a1 1 0 0 1 0-1.414ZM20 12a1 1 0 0 1 1 1v2a4 4 0 0 1-4 4H5.914l1.293 1.293a1 1 0 1 1-1.414 1.414L3.5 19.414a2 2 0 0 1 0-2.828l2.293-2.293a1 1 0 0 1 1.414 1.414L5.914 17H17a2 2 0 0 0 2-2v-2a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>
+10 -1
src/alf/atoms.ts
··· 841 841 marginRight: 'auto', 842 842 }, 843 843 /* 844 - * Pointer events 844 + * Pointer events & user select 845 845 */ 846 846 pointer_events_none: { 847 847 pointerEvents: 'none', 848 848 }, 849 849 pointer_events_auto: { 850 850 pointerEvents: 'auto', 851 + }, 852 + user_select_none: { 853 + userSelect: 'none', 854 + }, 855 + user_select_text: { 856 + userSelect: 'text', 857 + }, 858 + user_select_all: { 859 + userSelect: 'all', 851 860 }, 852 861 /* 853 862 * Text decoration
+5 -1
src/components/Button.tsx
··· 71 71 testID?: string 72 72 label: string 73 73 style?: StyleProp<ViewStyle> 74 + hoverStyle?: StyleProp<ViewStyle> 74 75 children: NonTextElements | ((context: ButtonContext) => NonTextElements) 75 76 } 76 77 ··· 96 97 label, 97 98 disabled = false, 98 99 style, 100 + hoverStyle: hoverStyleProp, 99 101 ...rest 100 102 }: ButtonProps) { 101 103 const t = useTheme() ··· 374 376 a.align_center, 375 377 a.justify_center, 376 378 flattenedBaseStyles, 377 - ...(state.hovered || state.pressed ? hoverStyles : []), 379 + ...(state.hovered || state.pressed 380 + ? [hoverStyles, flatten(hoverStyleProp)] 381 + : []), 378 382 flatten(style), 379 383 ]} 380 384 onPressIn={onPressIn}
+21
src/components/icons/Quote.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const OpenQuote_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 + path: 'M7.574 4.178a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.163 8.163 0 0 1 1.569-1.15 7.029 7.029 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588a5.99 5.99 0 0 0-.316.276C4.842 7.835 4.004 9.085 4.004 11v7h5v-6h-2a1 1 0 0 1-1-1V6.766Zm12.57-2.588a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.166 8.166 0 0 1 1.569-1.15 7.038 7.038 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588c-.105.085-.21.177-.316.276-.846.793-1.684 2.043-1.684 3.958v7h5v-6h-2a1 1 0 0 1-1-1V6.766Z', 5 + }) 6 + 7 + export const OpenQuote_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({ 8 + path: 'M8.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15C3.166 6.665 2.004 8.415 2.004 11v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5ZM19.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15-1.154 1.082-2.316 2.832-2.316 5.417v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5Z', 9 + }) 10 + 11 + export const CloseQuote_Stroke2_Corner0_Rounded = createSinglePathSVG({ 12 + path: 'M2.004 5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936A1 1 0 0 1 5.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234c.104-.085.21-.177.316-.276.846-.793 1.684-2.043 1.684-3.958V6h-5v6h2a1 1 0 0 1 1 1v4.234Zm6-12.234a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234V13a1 1 0 0 0-1-1h-2V6h5v7c0 1.915-.838 3.165-1.684 3.958-.106.1-.212.191-.316.276Z', 13 + }) 14 + 15 + export const CloseQuote_Stroke2_Corner1_Rounded = createSinglePathSVG({ 16 + path: 'M2.003 5.999a2 2 0 0 1 2-1.999h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936a1 1 0 0 1-1.351-.935L5 14H4a2 2 0 0 1-2-2.001l.002-6Zm5 11.236L7 12.999A1 1 0 0 0 6 12H4l.003-6h5v7c0 1.915-.837 3.165-1.683 3.958-.106.1-.213.192-.317.277Zm6-11.235a2 2 0 0 1 2-2h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-1a2 2 0 0 1-2-2V6Zm7 0h-5v6h2a1 1 0 0 1 1 1v4.234c.105-.085.211-.177.317-.276.846-.793 1.684-2.043 1.684-3.958V6Z', 17 + }) 18 + 19 + export const CloseQuote_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({ 20 + path: 'M3.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7ZM14.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7Z', 21 + })
+13
src/components/icons/Repost.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Repost_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 + path: 'M16.293 2.293a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 0 1-1.414-1.414L17.586 7H5v4a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1h13.586l-1.293-1.293a1 1 0 0 1 0-1.414ZM21 13v5a1 1 0 0 1-1 1H6.414l1.293 1.293a1 1 0 1 1-1.414 1.414l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 0 1 1.414 1.414L6.414 17H19v-4a1 1 0 1 1 2 0Z', 5 + }) 6 + 7 + export const Repost_Stroke2_Corner2_Rounded = createSinglePathSVG({ 8 + path: 'M17.957 2.293a1 1 0 1 0-1.414 1.414L17.836 5H6a3 3 0 0 0-3 3v3a1 1 0 1 0 2 0V8a1 1 0 0 1 1-1h11.836l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.47-2.47a1.75 1.75 0 0 0 0-2.474l-2.47-2.47ZM20 12a1 1 0 0 1 1 1v3a3 3 0 0 1-3 3H6.164l1.293 1.293a1 1 0 1 1-1.414 1.414l-2.47-2.47a1.75 1.75 0 0 1 0-2.474l2.47-2.47a1 1 0 0 1 1.414 1.414L6.164 17H18a1 1 0 0 0 1-1v-3a1 1 0 0 1 1-1Z', 9 + }) 10 + 11 + export const Repost_Stroke2_Corner3_Rounded = createSinglePathSVG({ 12 + path: 'M16.793 2.293a1 1 0 0 1 1.414 0L20.5 4.586a2 2 0 0 1 0 2.828l-2.293 2.293a1 1 0 0 1-1.414-1.414L18.086 7H7a2 2 0 0 0-2 2v2a1 1 0 1 1-2 0V9a4 4 0 0 1 4-4h11.086l-1.293-1.293a1 1 0 0 1 0-1.414ZM20 12a1 1 0 0 1 1 1v2a4 4 0 0 1-4 4H5.914l1.293 1.293a1 1 0 1 1-1.414 1.414L3.5 19.414a2 2 0 0 1 0-2.828l2.293-2.293a1 1 0 0 1 1.414 1.414L5.914 17H17a2 2 0 0 0 2-2v-2a1 1 0 0 1 1-1Z', 13 + })
+2 -2
src/view/com/post-thread/PostThreadItem.tsx
··· 367 367 ) : null} 368 368 </View> 369 369 ) : null} 370 - <View style={[s.pl10, s.pr10, s.pb5]}> 370 + <View style={[s.pl10, s.pr10]}> 371 371 <PostCtrls 372 372 big 373 373 post={post} ··· 733 733 borderTopWidth: 1, 734 734 borderBottomWidth: 1, 735 735 marginTop: 5, 736 - marginBottom: 15, 736 + marginBottom: 10, 737 737 }, 738 738 expandedInfoItem: { 739 739 marginRight: 10,
+38 -26
src/view/com/util/LoadingPlaceholder.tsx
··· 1 1 import React from 'react' 2 2 import { 3 - StyleSheet, 3 + DimensionValue, 4 4 StyleProp, 5 + StyleSheet, 5 6 View, 6 7 ViewStyle, 7 - DimensionValue, 8 8 } from 'react-native' 9 - import { 10 - HeartIcon, 11 - HeartIconSolid, 12 - CommentBottomArrow, 13 - RepostIcon, 14 - } from 'lib/icons' 9 + 10 + import {usePalette} from 'lib/hooks/usePalette' 11 + import {HeartIconSolid} from 'lib/icons' 15 12 import {s} from 'lib/styles' 16 13 import {useTheme} from 'lib/ThemeContext' 17 - import {usePalette} from 'lib/hooks/usePalette' 14 + import {useTheme as useTheme_NEW} from '#/alf' 15 + import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble' 16 + import {Heart2_Stroke2_Corner0_Rounded as HeartIconOutline} from '#/components/icons/Heart2' 17 + import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost' 18 18 19 19 export function LoadingPlaceholder({ 20 20 width, ··· 46 46 }: { 47 47 style?: StyleProp<ViewStyle> 48 48 }) { 49 - const theme = useTheme() 49 + const t = useTheme_NEW() 50 50 const pal = usePalette('default') 51 51 return ( 52 52 <View style={[styles.post, pal.view, style]}> ··· 67 67 <LoadingPlaceholder width="95%" height={6} style={{marginBottom: 8}} /> 68 68 <LoadingPlaceholder width="80%" height={6} style={{marginBottom: 11}} /> 69 69 <View style={styles.postCtrls}> 70 - <View style={styles.postCtrl}> 71 - <View style={[styles.postBtn, {paddingLeft: 0}]}> 72 - <CommentBottomArrow 73 - style={[{color: theme.palette.default.icon, marginTop: 1}]} 74 - strokeWidth={3} 75 - size={15} 70 + <View style={[styles.postCtrl, {marginLeft: -5}]}> 71 + <View style={styles.postBtn}> 72 + <Bubble 73 + style={[ 74 + { 75 + color: t.palette.contrast_500, 76 + }, 77 + {pointerEvents: 'none'}, 78 + ]} 79 + width={18} 76 80 /> 77 81 </View> 78 82 </View> 79 83 <View style={styles.postCtrl}> 80 84 <View style={styles.postBtn}> 81 - <RepostIcon 82 - style={{color: theme.palette.default.icon}} 83 - strokeWidth={3} 84 - size={20} 85 + <Repost 86 + style={[ 87 + { 88 + color: t.palette.contrast_500, 89 + }, 90 + {pointerEvents: 'none'}, 91 + ]} 92 + width={18} 85 93 /> 86 94 </View> 87 95 </View> 88 96 <View style={styles.postCtrl}> 89 97 <View style={styles.postBtn}> 90 - <HeartIcon 91 - style={{color: theme.palette.default.icon} as ViewStyle} 92 - size={16} 93 - strokeWidth={3} 98 + <HeartIconOutline 99 + style={[ 100 + { 101 + color: t.palette.contrast_500, 102 + }, 103 + {pointerEvents: 'none'}, 104 + ]} 105 + width={18} 94 106 /> 95 107 </View> 96 108 </View> 97 109 <View style={styles.postCtrl}> 98 - <View style={styles.postBtn} /> 110 + <View style={[styles.postBtn, {minHeight: 30}]} /> 99 111 </View> 100 112 </View> 101 113 </View> ··· 290 302 flex: 1, 291 303 }, 292 304 postBtn: { 293 - padding: 5, 294 305 flex: 1, 295 306 flexDirection: 'row', 296 307 alignItems: 'center', 308 + padding: 5, 297 309 }, 298 310 avatar: { 299 311 borderRadius: 26,
+13 -7
src/view/com/util/forms/PostDropdownBtn.tsx
··· 1 1 import React, {memo} from 'react' 2 - import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native' 2 + import { 3 + Pressable, 4 + type PressableProps, 5 + type StyleProp, 6 + type ViewStyle, 7 + } from 'react-native' 3 8 import * as Clipboard from 'expo-clipboard' 4 9 import { 5 10 AppBskyActorDefs, ··· 7 12 AtUri, 8 13 RichText as RichTextAPI, 9 14 } from '@atproto/api' 10 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 11 15 import {msg} from '@lingui/macro' 12 16 import {useLingui} from '@lingui/react' 13 17 import {useNavigation} from '@react-navigation/native' ··· 37 41 import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' 38 42 import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' 39 43 import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBrackets} from '#/components/icons/CodeBrackets' 44 + import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' 40 45 import { 41 46 EmojiSad_Stroke2_Corner0_Rounded as EmojiSad, 42 47 EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmile, ··· 68 73 richText, 69 74 style, 70 75 hitSlop, 76 + size, 71 77 timestamp, 72 78 }: { 73 79 testID: string ··· 79 85 richText: RichTextAPI 80 86 style?: StyleProp<ViewStyle> 81 87 hitSlop?: PressableProps['hitSlop'] 88 + size?: 'lg' | 'md' | 'sm' 82 89 timestamp: string 83 90 }): React.ReactNode => { 84 91 const {hasSession, currentAccount} = useSession() ··· 238 245 style, 239 246 a.rounded_full, 240 247 (state.hovered || state.pressed) && [ 241 - alf.atoms.bg_contrast_50, 248 + alf.atoms.bg_contrast_25, 242 249 ], 243 250 ]}> 244 - <FontAwesomeIcon 245 - icon="ellipsis" 246 - size={20} 247 - color={defaultCtrlColor} 251 + <DotsHorizontal 252 + fill={defaultCtrlColor} 248 253 style={{pointerEvents: 'none'}} 254 + size={size} 249 255 /> 250 256 </Pressable> 251 257 )
+69 -78
src/view/com/util/post-ctrls/PostCtrls.tsx
··· 1 1 import React, {memo, useCallback} from 'react' 2 2 import { 3 - StyleProp, 4 - StyleSheet, 5 - TouchableOpacity, 3 + Pressable, 4 + type PressableStateCallbackType, 5 + type StyleProp, 6 6 View, 7 - ViewStyle, 7 + type ViewStyle, 8 8 } from 'react-native' 9 9 import { 10 10 AppBskyFeedDefs, ··· 16 16 import {useLingui} from '@lingui/react' 17 17 18 18 import {HITSLOP_10, HITSLOP_20} from '#/lib/constants' 19 - import {CommentBottomArrow, HeartIcon, HeartIconSolid} from '#/lib/icons' 19 + import {useHaptics} from '#/lib/haptics' 20 20 import {makeProfileLink} from '#/lib/routes/links' 21 21 import {shareUrl} from '#/lib/sharing' 22 22 import {toShareUrl} from '#/lib/strings/url-helpers' 23 23 import {s} from '#/lib/styles' 24 - import {useTheme} from '#/lib/ThemeContext' 25 24 import {Shadow} from '#/state/cache/types' 26 25 import {useFeedFeedbackContext} from '#/state/feed-feedback' 27 26 import {useModalControls} from '#/state/modals' ··· 31 30 } from '#/state/queries/post' 32 31 import {useRequireAuth} from '#/state/session' 33 32 import {useComposerControls} from '#/state/shell/composer' 34 - import {useHaptics} from 'lib/haptics' 33 + import {atoms as a, useTheme} from '#/alf' 35 34 import {useDialogControl} from '#/components/Dialog' 36 35 import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' 36 + import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble' 37 + import { 38 + Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled, 39 + Heart2_Stroke2_Corner0_Rounded as HeartIconOutline, 40 + } from '#/components/icons/Heart2' 37 41 import * as Prompt from '#/components/Prompt' 38 42 import {PostDropdownBtn} from '../forms/PostDropdownBtn' 39 43 import {Text} from '../text/Text' ··· 58 62 onPressReply: () => void 59 63 logContext: 'FeedItem' | 'PostThreadItem' | 'Post' 60 64 }): React.ReactNode => { 61 - const theme = useTheme() 65 + const t = useTheme() 62 66 const {_} = useLingui() 63 67 const {openComposer} = useComposerControls() 64 68 const {closeModal} = useModalControls() ··· 80 84 81 85 const defaultCtrlColor = React.useMemo( 82 86 () => ({ 83 - color: theme.palette.default.postCtrl, 87 + color: t.palette.contrast_500, 84 88 }), 85 - [theme], 89 + [t], 86 90 ) as StyleProp<ViewStyle> 87 91 88 92 const onPressToggleLike = React.useCallback(async () => { ··· 184 188 feedContext, 185 189 }) 186 190 }, [post.uri, post.author, sendInteraction, feedContext]) 191 + 192 + const btnStyle = React.useCallback( 193 + ({pressed, hovered}: PressableStateCallbackType) => [ 194 + a.gap_xs, 195 + a.rounded_full, 196 + a.flex_row, 197 + a.align_center, 198 + a.justify_center, 199 + {padding: 5}, 200 + (pressed || hovered) && t.atoms.bg_contrast_25, 201 + ], 202 + [t.atoms.bg_contrast_25], 203 + ) 187 204 188 205 return ( 189 - <View style={[styles.ctrls, style]}> 206 + <View style={[a.flex_row, a.justify_between, a.align_center, style]}> 190 207 <View 191 208 style={[ 192 - big ? styles.ctrlBig : styles.ctrl, 209 + big ? a.align_center : [a.flex_1, a.align_start, {marginLeft: -5}], 193 210 post.viewer?.replyDisabled ? {opacity: 0.5} : undefined, 194 211 ]}> 195 - <TouchableOpacity 212 + <Pressable 196 213 testID="replyBtn" 197 - style={[styles.btn, !big && styles.btnPad, {paddingLeft: 0}]} 214 + style={btnStyle} 198 215 onPress={() => { 199 216 if (!post.viewer?.replyDisabled) { 200 217 requireAuth(() => onPressReply()) 201 218 } 202 219 }} 203 - accessibilityRole="button" 204 220 accessibilityLabel={plural(post.replyCount || 0, { 205 221 one: 'Reply (# reply)', 206 222 other: 'Reply (# replies)', 207 223 })} 208 224 accessibilityHint="" 209 225 hitSlop={big ? HITSLOP_20 : HITSLOP_10}> 210 - <CommentBottomArrow 211 - style={[defaultCtrlColor, big ? s.mt2 : styles.mt1]} 212 - strokeWidth={3} 213 - size={big ? 20 : 15} 226 + <Bubble 227 + style={[defaultCtrlColor, {pointerEvents: 'none'}]} 228 + width={big ? 22 : 18} 214 229 /> 215 230 {typeof post.replyCount !== 'undefined' && post.replyCount > 0 ? ( 216 - <Text style={[defaultCtrlColor, s.ml5, s.f15]}> 231 + <Text 232 + style={[ 233 + defaultCtrlColor, 234 + big ? a.text_md : {fontSize: 15}, 235 + a.user_select_none, 236 + ]}> 217 237 {post.replyCount} 218 238 </Text> 219 239 ) : undefined} 220 - </TouchableOpacity> 240 + </Pressable> 221 241 </View> 222 - <View style={big ? styles.ctrlBig : styles.ctrl}> 242 + <View style={big ? a.align_center : [a.flex_1, a.align_start]}> 223 243 <RepostButton 224 - big={big} 225 244 isReposted={!!post.viewer?.repost} 226 245 repostCount={post.repostCount} 227 246 onRepost={onRepost} 228 247 onQuote={onQuote} 248 + big={big} 229 249 /> 230 250 </View> 231 - <View style={big ? styles.ctrlBig : styles.ctrl}> 232 - <TouchableOpacity 251 + <View style={big ? a.align_center : [a.flex_1, a.align_start]}> 252 + <Pressable 233 253 testID="likeBtn" 234 - style={[styles.btn, !big && styles.btnPad]} 235 - onPress={() => { 236 - requireAuth(() => onPressToggleLike()) 237 - }} 238 - accessibilityRole="button" 254 + style={btnStyle} 255 + onPress={() => requireAuth(() => onPressToggleLike())} 239 256 accessibilityLabel={ 240 257 post.viewer?.like 241 258 ? plural(post.likeCount || 0, { ··· 250 267 accessibilityHint="" 251 268 hitSlop={big ? HITSLOP_20 : HITSLOP_10}> 252 269 {post.viewer?.like ? ( 253 - <HeartIconSolid style={s.likeColor} size={big ? 22 : 16} /> 270 + <HeartIconFilled style={s.likeColor} width={big ? 22 : 18} /> 254 271 ) : ( 255 - <HeartIcon 256 - style={[defaultCtrlColor, big ? styles.mt1 : undefined]} 257 - strokeWidth={3} 258 - size={big ? 20 : 16} 272 + <HeartIconOutline 273 + style={[defaultCtrlColor, {pointerEvents: 'none'}]} 274 + width={big ? 22 : 18} 259 275 /> 260 276 )} 261 277 {typeof post.likeCount !== 'undefined' && post.likeCount > 0 ? ( 262 278 <Text 263 279 testID="likeCount" 264 - style={ 265 - post.viewer?.like 266 - ? [s.bold, s.likeColor, s.f15, s.ml5] 267 - : [defaultCtrlColor, s.f15, s.ml5] 268 - }> 280 + style={[ 281 + [ 282 + big ? a.text_md : {fontSize: 15}, 283 + a.user_select_none, 284 + post.viewer?.like 285 + ? [a.font_bold, s.likeColor] 286 + : defaultCtrlColor, 287 + ], 288 + ]}> 269 289 {post.likeCount} 270 290 </Text> 271 291 ) : undefined} 272 - </TouchableOpacity> 292 + </Pressable> 273 293 </View> 274 294 {big && ( 275 295 <> 276 - <View style={styles.ctrlBig}> 277 - <TouchableOpacity 296 + <View style={a.align_center}> 297 + <Pressable 278 298 testID="shareBtn" 279 - style={[styles.btn]} 299 + style={btnStyle} 280 300 onPress={() => { 281 301 if (shouldShowLoggedOutWarning) { 282 302 loggedOutWarningPromptControl.open() ··· 284 304 onShare() 285 305 } 286 306 }} 287 - accessibilityRole="button" 288 - accessibilityLabel={`${_(msg`Share`)}`} 307 + accessibilityLabel={_(msg`Share`)} 289 308 accessibilityHint="" 290 309 hitSlop={big ? HITSLOP_20 : HITSLOP_10}> 291 310 <ArrowOutOfBox 292 - style={[defaultCtrlColor, styles.mt1]} 311 + style={[defaultCtrlColor, {pointerEvents: 'none'}]} 293 312 width={22} 294 313 /> 295 - </TouchableOpacity> 314 + </Pressable> 296 315 </View> 297 316 <Prompt.Basic 298 317 control={loggedOutWarningPromptControl} ··· 305 324 /> 306 325 </> 307 326 )} 308 - <View style={big ? styles.ctrlBig : styles.ctrl}> 327 + <View style={big ? a.align_center : [a.flex_1, a.align_start]}> 309 328 <PostDropdownBtn 310 329 testID="postDropdownBtn" 311 330 postAuthor={post.author} ··· 314 333 postFeedContext={feedContext} 315 334 record={record} 316 335 richText={richText} 317 - style={styles.btnPad} 336 + style={{padding: 5}} 318 337 hitSlop={big ? HITSLOP_20 : HITSLOP_10} 319 338 timestamp={post.indexedAt} 320 339 /> ··· 324 343 } 325 344 PostCtrls = memo(PostCtrls) 326 345 export {PostCtrls} 327 - 328 - const styles = StyleSheet.create({ 329 - ctrls: { 330 - flexDirection: 'row', 331 - justifyContent: 'space-between', 332 - alignItems: 'center', 333 - }, 334 - ctrl: { 335 - flex: 1, 336 - alignItems: 'flex-start', 337 - }, 338 - ctrlBig: { 339 - alignItems: 'center', 340 - }, 341 - btn: { 342 - flexDirection: 'row', 343 - alignItems: 'center', 344 - }, 345 - btnPad: { 346 - paddingTop: 5, 347 - paddingBottom: 5, 348 - paddingLeft: 5, 349 - paddingRight: 5, 350 - }, 351 - mt1: { 352 - marginTop: 1, 353 - }, 354 - })
+99 -75
src/view/com/util/post-ctrls/RepostButton.tsx
··· 1 1 import React, {memo, useCallback} from 'react' 2 - import {StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native' 2 + import {View} from 'react-native' 3 3 import {msg, plural} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {useModalControls} from '#/state/modals' 7 6 import {useRequireAuth} from '#/state/session' 8 - import {HITSLOP_10, HITSLOP_20} from 'lib/constants' 9 - import {RepostIcon} from 'lib/icons' 10 - import {colors, s} from 'lib/styles' 11 - import {useTheme} from 'lib/ThemeContext' 12 - import {Text} from '../text/Text' 7 + import {atoms as a, useTheme} from '#/alf' 8 + import {Button, ButtonText} from '#/components/Button' 9 + import * as Dialog from '#/components/Dialog' 10 + import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote' 11 + import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost' 12 + import {Text} from '#/components/Typography' 13 13 14 14 interface Props { 15 15 isReposted: boolean 16 16 repostCount?: number 17 - big?: boolean 18 17 onRepost: () => void 19 18 onQuote: () => void 19 + big?: boolean 20 20 } 21 21 22 22 let RepostButton = ({ 23 23 isReposted, 24 24 repostCount, 25 - big, 26 25 onRepost, 27 26 onQuote, 27 + big, 28 28 }: Props): React.ReactNode => { 29 - const theme = useTheme() 29 + const t = useTheme() 30 30 const {_} = useLingui() 31 - const {openModal} = useModalControls() 32 31 const requireAuth = useRequireAuth() 32 + const dialogControl = Dialog.useDialogControl() 33 33 34 - const defaultControlColor = React.useMemo( 34 + const color = React.useMemo( 35 35 () => ({ 36 - color: theme.palette.default.postCtrl, 36 + color: isReposted ? t.palette.positive_600 : t.palette.contrast_500, 37 37 }), 38 - [theme], 38 + [t, isReposted], 39 39 ) 40 40 41 - const onPressToggleRepostWrapper = useCallback(() => { 42 - openModal({ 43 - name: 'repost', 44 - onRepost: onRepost, 45 - onQuote: onQuote, 46 - isReposted, 47 - }) 48 - }, [onRepost, onQuote, isReposted, openModal]) 41 + const close = useCallback(() => dialogControl.close(), [dialogControl]) 49 42 50 43 return ( 51 - <TouchableOpacity 52 - testID="repostBtn" 53 - onPress={() => { 54 - requireAuth(() => onPressToggleRepostWrapper()) 55 - }} 56 - style={[styles.btn, !big && styles.btnPad]} 57 - accessibilityRole="button" 58 - accessibilityLabel={`${ 59 - isReposted 60 - ? _(msg`Undo repost`) 61 - : _(msg({message: 'Repost', context: 'action'})) 62 - } (${plural(repostCount || 0, {one: '# repost', other: '# reposts'})})`} 63 - accessibilityHint="" 64 - hitSlop={big ? HITSLOP_20 : HITSLOP_10}> 65 - <RepostIcon 66 - style={ 44 + <> 45 + <Button 46 + testID="repostBtn" 47 + onPress={() => { 48 + requireAuth(() => dialogControl.open()) 49 + }} 50 + style={[a.flex_row, a.align_center, a.gap_xs, {padding: 5}]} 51 + hoverStyle={t.atoms.bg_contrast_25} 52 + label={`${ 67 53 isReposted 68 - ? (styles.reposted as StyleProp<ViewStyle>) 69 - : defaultControlColor 70 - } 71 - strokeWidth={2.4} 72 - size={big ? 24 : 20} 73 - /> 74 - {typeof repostCount !== 'undefined' && repostCount > 0 ? ( 75 - <Text 76 - testID="repostCount" 77 - style={ 78 - isReposted 79 - ? [s.bold, s.green3, s.f15, s.ml5] 80 - : [defaultControlColor, s.f15, s.ml5] 81 - }> 82 - {repostCount} 83 - </Text> 84 - ) : undefined} 85 - </TouchableOpacity> 54 + ? _(msg`Undo repost`) 55 + : _(msg({message: 'Repost', context: 'action'})) 56 + } (${plural(repostCount || 0, {one: '# repost', other: '# reposts'})})`} 57 + shape="round" 58 + variant="ghost" 59 + color="secondary"> 60 + <Repost style={color} width={big ? 22 : 18} /> 61 + {typeof repostCount !== 'undefined' && repostCount > 0 ? ( 62 + <Text 63 + testID="repostCount" 64 + style={[ 65 + color, 66 + big ? a.text_md : {fontSize: 15}, 67 + isReposted && a.font_bold, 68 + ]}> 69 + {repostCount} 70 + </Text> 71 + ) : undefined} 72 + </Button> 73 + <Dialog.Outer control={dialogControl}> 74 + <Dialog.Handle /> 75 + <Dialog.Inner label={_(msg`Repost or quote post`)}> 76 + <View style={a.gap_xl}> 77 + <View style={a.gap_xs}> 78 + <Button 79 + style={[a.justify_start, a.px_md]} 80 + label={ 81 + isReposted 82 + ? _(msg`Remove repost`) 83 + : _(msg({message: `Repost`, context: 'action'})) 84 + } 85 + onPress={() => { 86 + dialogControl.close() 87 + onRepost() 88 + }} 89 + size="large" 90 + variant="ghost" 91 + color="primary"> 92 + <Repost size="lg" fill={t.palette.primary_500} /> 93 + <Text style={[a.font_bold, a.text_xl]}> 94 + {isReposted 95 + ? _(msg`Remove repost`) 96 + : _(msg({message: `Repost`, context: 'action'}))} 97 + </Text> 98 + </Button> 99 + <Button 100 + style={[a.justify_start, a.px_md]} 101 + label={_(msg`Quote post`)} 102 + onPress={() => { 103 + dialogControl.close(() => { 104 + onQuote() 105 + }) 106 + }} 107 + size="large" 108 + variant="ghost" 109 + color="primary"> 110 + <Quote size="lg" fill={t.palette.primary_500} /> 111 + <Text style={[a.font_bold, a.text_xl]}> 112 + {_(msg`Quote post`)} 113 + </Text> 114 + </Button> 115 + </View> 116 + <Button 117 + label={_(msg`Cancel quote post`)} 118 + onAccessibilityEscape={close} 119 + onPress={close} 120 + size="medium" 121 + variant="solid" 122 + color="primary"> 123 + <ButtonText>{_(msg`Cancel`)}</ButtonText> 124 + </Button> 125 + </View> 126 + </Dialog.Inner> 127 + </Dialog.Outer> 128 + </> 86 129 ) 87 130 } 88 131 RepostButton = memo(RepostButton) 89 132 export {RepostButton} 90 - 91 - const styles = StyleSheet.create({ 92 - btn: { 93 - flexDirection: 'row', 94 - alignItems: 'center', 95 - }, 96 - btnPad: { 97 - paddingTop: 5, 98 - paddingBottom: 5, 99 - paddingLeft: 5, 100 - paddingRight: 5, 101 - }, 102 - reposted: { 103 - color: colors.green3, 104 - }, 105 - repostCount: { 106 - color: 'currentColor', 107 - }, 108 - })
+99 -95
src/view/com/util/post-ctrls/RepostButton.web.tsx
··· 1 1 import React from 'react' 2 - import {StyleProp, StyleSheet, View, ViewStyle, Pressable} from 'react-native' 3 - import {RepostIcon} from 'lib/icons' 4 - import {colors} from 'lib/styles' 5 - import {useTheme} from 'lib/ThemeContext' 6 - import {Text} from '../text/Text' 2 + import {Pressable, View} from 'react-native' 3 + import {msg} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 7 5 8 - import { 9 - NativeDropdown, 10 - DropdownItem as NativeDropdownItem, 11 - } from '../forms/NativeDropdown' 12 - import {EventStopper} from '../EventStopper' 13 - import {useLingui} from '@lingui/react' 14 - import {msg} from '@lingui/macro' 15 6 import {useRequireAuth} from '#/state/session' 16 7 import {useSession} from '#/state/session' 8 + import {atoms as a, useTheme} from '#/alf' 9 + import {Button} from '#/components/Button' 10 + import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote' 11 + import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost' 12 + import * as Menu from '#/components/Menu' 13 + import {Text} from '#/components/Typography' 14 + import {EventStopper} from '../EventStopper' 17 15 18 16 interface Props { 19 17 isReposted: boolean 20 18 repostCount?: number 21 - big?: boolean 22 19 onRepost: () => void 23 20 onQuote: () => void 24 - style?: StyleProp<ViewStyle> 21 + big?: boolean 25 22 } 26 23 27 24 export const RepostButton = ({ 28 25 isReposted, 29 26 repostCount, 30 - big, 31 27 onRepost, 32 28 onQuote, 29 + big, 33 30 }: Props) => { 34 - const theme = useTheme() 31 + const t = useTheme() 35 32 const {_} = useLingui() 36 33 const {hasSession} = useSession() 37 34 const requireAuth = useRequireAuth() 38 35 39 - const defaultControlColor = React.useMemo( 36 + const color = React.useMemo( 40 37 () => ({ 41 - color: theme.palette.default.postCtrl, 38 + color: isReposted ? t.palette.positive_600 : t.palette.contrast_500, 42 39 }), 43 - [theme], 44 - ) 45 - 46 - const dropdownItems: NativeDropdownItem[] = [ 47 - { 48 - label: isReposted ? _(msg`Undo repost`) : _(msg`Repost`), 49 - testID: 'repostDropdownRepostBtn', 50 - icon: { 51 - ios: {name: 'repeat'}, 52 - android: '', 53 - web: 'retweet', 54 - }, 55 - onPress: onRepost, 56 - }, 57 - { 58 - label: _(msg`Quote post`), 59 - testID: 'repostDropdownQuoteBtn', 60 - icon: { 61 - ios: {name: 'quote.bubble'}, 62 - android: '', 63 - web: 'quote-left', 64 - }, 65 - onPress: onQuote, 66 - }, 67 - ] 68 - 69 - const inner = ( 70 - <View 71 - style={[ 72 - styles.btn, 73 - !big && styles.btnPad, 74 - (isReposted 75 - ? styles.reposted 76 - : defaultControlColor) as StyleProp<ViewStyle>, 77 - ]}> 78 - <RepostIcon strokeWidth={2.2} size={big ? 24 : 20} /> 79 - {typeof repostCount !== 'undefined' && repostCount > 0 ? ( 80 - <Text 81 - testID="repostCount" 82 - type={isReposted ? 'md-bold' : 'md'} 83 - style={styles.repostCount}> 84 - {repostCount} 85 - </Text> 86 - ) : undefined} 87 - </View> 40 + [t, isReposted], 88 41 ) 89 42 90 43 return hasSession ? ( 91 - <EventStopper> 92 - <NativeDropdown 93 - items={dropdownItems} 94 - accessibilityLabel={_(msg`Repost or quote post`)} 95 - accessibilityHint=""> 96 - {inner} 97 - </NativeDropdown> 44 + <EventStopper onKeyDown={false}> 45 + <Menu.Root> 46 + <Menu.Trigger label={_(msg`Repost or quote post`)}> 47 + {({props, state}) => { 48 + return ( 49 + <Pressable 50 + {...props} 51 + style={[ 52 + a.rounded_full, 53 + (state.hovered || state.pressed) && { 54 + backgroundColor: t.palette.contrast_25, 55 + }, 56 + ]}> 57 + <RepostInner 58 + isReposted={isReposted} 59 + color={color} 60 + repostCount={repostCount} 61 + big={big} 62 + /> 63 + </Pressable> 64 + ) 65 + }} 66 + </Menu.Trigger> 67 + <Menu.Outer style={{minWidth: 170}}> 68 + <Menu.Item 69 + label={isReposted ? _(msg`Undo repost`) : _(msg`Repost`)} 70 + testID="repostDropdownRepostBtn" 71 + onPress={onRepost}> 72 + <Menu.ItemText> 73 + {isReposted ? _(msg`Undo repost`) : _(msg`Repost`)} 74 + </Menu.ItemText> 75 + <Menu.ItemIcon icon={Repost} position="right" /> 76 + </Menu.Item> 77 + <Menu.Item 78 + label={_(msg`Quote post`)} 79 + testID="repostDropdownQuoteBtn" 80 + onPress={onQuote}> 81 + <Menu.ItemText>{_(msg`Quote post`)}</Menu.ItemText> 82 + <Menu.ItemIcon icon={Quote} position="right" /> 83 + </Menu.Item> 84 + </Menu.Outer> 85 + </Menu.Root> 98 86 </EventStopper> 99 87 ) : ( 100 - <Pressable 101 - accessibilityRole="button" 88 + <Button 102 89 onPress={() => { 103 90 requireAuth(() => {}) 104 91 }} 105 - accessibilityLabel={_(msg`Repost or quote post`)} 106 - accessibilityHint=""> 107 - {inner} 108 - </Pressable> 92 + label={_(msg`Repost or quote post`)} 93 + style={{padding: 0}} 94 + hoverStyle={t.atoms.bg_contrast_25} 95 + shape="round" 96 + variant="ghost" 97 + color="secondary"> 98 + <RepostInner 99 + isReposted={isReposted} 100 + color={color} 101 + repostCount={repostCount} 102 + big={big} 103 + /> 104 + </Button> 109 105 ) 110 106 } 111 107 112 - const styles = StyleSheet.create({ 113 - btn: { 114 - flexDirection: 'row', 115 - alignItems: 'center', 116 - gap: 4, 117 - }, 118 - btnPad: { 119 - paddingTop: 5, 120 - paddingBottom: 5, 121 - paddingLeft: 5, 122 - paddingRight: 5, 123 - }, 124 - reposted: { 125 - color: colors.green3, 126 - }, 127 - repostCount: { 128 - color: 'currentColor', 129 - }, 130 - }) 108 + const RepostInner = ({ 109 + isReposted, 110 + color, 111 + repostCount, 112 + big, 113 + }: { 114 + isReposted: boolean 115 + color: {color: string} 116 + repostCount?: number 117 + big?: boolean 118 + }) => ( 119 + <View style={[a.flex_row, a.align_center, a.gap_xs, {padding: 5}]}> 120 + <Repost style={color} width={big ? 22 : 18} /> 121 + {typeof repostCount !== 'undefined' && repostCount > 0 ? ( 122 + <Text 123 + testID="repostCount" 124 + style={[ 125 + color, 126 + big ? a.text_md : {fontSize: 15}, 127 + isReposted && [a.font_bold], 128 + a.user_select_none, 129 + ]}> 130 + {repostCount} 131 + </Text> 132 + ) : undefined} 133 + </View> 134 + )