An ATproto social media client -- with an independent Appview.
1import {createContext, useContext, useMemo} from 'react'
2import {type GestureResponderEvent, type Insets, type View} from 'react-native'
3
4import {useHaptics} from '#/lib/haptics'
5import {atoms as a, useTheme} from '#/alf'
6import {Button, type ButtonProps} from '#/components/Button'
7import {type Props as SVGIconProps} from '#/components/icons/common'
8import {Text, type TextProps} from '#/components/Typography'
9
10export const DEFAULT_HITSLOP = {top: 5, bottom: 10, left: 10, right: 10}
11
12const PostControlContext = createContext<{
13 big?: boolean
14 active?: boolean
15 color?: {color: string}
16}>({})
17PostControlContext.displayName = 'PostControlContext'
18
19// Base button style, which the the other ones extend
20export function PostControlButton({
21 ref,
22 onPress,
23 onLongPress,
24 children,
25 big,
26 active,
27 activeColor,
28 ...props
29}: Omit<ButtonProps, 'hitSlop'> & {
30 ref?: React.Ref<View>
31 active?: boolean
32 big?: boolean
33 color?: string
34 activeColor?: string
35 hitSlop?: Insets
36}) {
37 const t = useTheme()
38 const playHaptic = useHaptics()
39
40 const ctx = useMemo(
41 () => ({
42 big,
43 active,
44 color: {
45 color: activeColor && active ? activeColor : t.palette.contrast_500,
46 },
47 }),
48 [big, active, activeColor, t.palette.contrast_500],
49 )
50
51 const style = useMemo(
52 () => [
53 a.flex_row,
54 a.align_center,
55 a.gap_xs,
56 a.bg_transparent,
57 {padding: 5},
58 ],
59 [],
60 )
61
62 const handlePress = useMemo(() => {
63 if (!onPress) return
64 return (evt: GestureResponderEvent) => {
65 playHaptic('Light')
66 onPress(evt)
67 }
68 }, [onPress, playHaptic])
69
70 const handleLongPress = useMemo(() => {
71 if (!onLongPress) return
72 return (evt: GestureResponderEvent) => {
73 playHaptic('Heavy')
74 onLongPress(evt)
75 }
76 }, [onLongPress, playHaptic])
77
78 return (
79 <Button
80 ref={ref}
81 onPress={handlePress}
82 onLongPress={handleLongPress}
83 style={style}
84 hoverStyle={t.atoms.bg_contrast_25}
85 shape="round"
86 variant="ghost"
87 color="secondary"
88 {...props}
89 hitSlop={{
90 ...DEFAULT_HITSLOP,
91 ...(props.hitSlop || {}),
92 }}>
93 {typeof children === 'function' ? (
94 args => (
95 <PostControlContext.Provider value={ctx}>
96 {children(args)}
97 </PostControlContext.Provider>
98 )
99 ) : (
100 <PostControlContext.Provider value={ctx}>
101 {children}
102 </PostControlContext.Provider>
103 )}
104 </Button>
105 )
106}
107
108export function PostControlButtonIcon({
109 icon: Comp,
110 style,
111 ...rest
112}: SVGIconProps & {
113 icon: React.ComponentType<SVGIconProps>
114}) {
115 const {big, color} = useContext(PostControlContext)
116
117 return (
118 <Comp
119 style={[color, a.pointer_events_none, style]}
120 {...rest}
121 width={big ? 22 : 18}
122 />
123 )
124}
125
126export function PostControlButtonText({style, ...props}: TextProps) {
127 const {big, active, color} = useContext(PostControlContext)
128
129 return (
130 <Text
131 style={[color, big ? a.text_md : a.text_sm, active && a.font_bold, style]}
132 {...props}
133 />
134 )
135}