mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {
3 ActivityIndicator,
4 type GestureResponderEvent,
5 type NativeSyntheticEvent,
6 type NativeTouchEvent,
7 Pressable,
8 type PressableStateCallbackType,
9 type StyleProp,
10 StyleSheet,
11 type TextStyle,
12 View,
13 type ViewStyle,
14} from 'react-native'
15
16import {choose} from '#/lib/functions'
17import {useTheme} from '#/lib/ThemeContext'
18import {Text} from '../text/Text'
19
20export type ButtonType =
21 | 'primary'
22 | 'secondary'
23 | 'default'
24 | 'inverted'
25 | 'primary-outline'
26 | 'secondary-outline'
27 | 'primary-light'
28 | 'secondary-light'
29 | 'default-light'
30
31// Augment type for react-native-web (see https://github.com/necolas/react-native-web/issues/1684#issuecomment-766451866)
32declare module 'react-native' {
33 interface PressableStateCallbackType {
34 // @ts-ignore web only
35 hovered?: boolean
36 focused?: boolean
37 }
38}
39
40/**
41 * @deprecated use Button from `#/components/Button.tsx` instead
42 */
43export function Button({
44 type = 'primary',
45 label,
46 style,
47 labelContainerStyle,
48 labelStyle,
49 onPress,
50 children,
51 testID,
52 accessibilityLabel,
53 accessibilityHint,
54 accessibilityLabelledBy,
55 onAccessibilityEscape,
56 withLoading = false,
57 disabled = false,
58}: React.PropsWithChildren<{
59 type?: ButtonType
60 label?: string
61 style?: StyleProp<ViewStyle>
62 labelContainerStyle?: StyleProp<ViewStyle>
63 labelStyle?: StyleProp<TextStyle>
64 onPress?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void | Promise<void>
65 testID?: string
66 accessibilityLabel?: string
67 accessibilityHint?: string
68 accessibilityLabelledBy?: string
69 onAccessibilityEscape?: () => void
70 withLoading?: boolean
71 disabled?: boolean
72}>) {
73 const theme = useTheme()
74 const typeOuterStyle = choose<ViewStyle, Record<ButtonType, ViewStyle>>(
75 type,
76 {
77 primary: {
78 backgroundColor: theme.palette.primary.background,
79 },
80 secondary: {
81 backgroundColor: theme.palette.secondary.background,
82 },
83 default: {
84 backgroundColor: theme.palette.default.backgroundLight,
85 },
86 inverted: {
87 backgroundColor: theme.palette.inverted.background,
88 },
89 'primary-outline': {
90 backgroundColor: theme.palette.default.background,
91 borderWidth: 1,
92 borderColor: theme.palette.primary.border,
93 },
94 'secondary-outline': {
95 backgroundColor: theme.palette.default.background,
96 borderWidth: 1,
97 borderColor: theme.palette.secondary.border,
98 },
99 'primary-light': {
100 backgroundColor: theme.palette.default.background,
101 },
102 'secondary-light': {
103 backgroundColor: theme.palette.default.background,
104 },
105 'default-light': {
106 backgroundColor: theme.palette.default.background,
107 },
108 },
109 )
110 const typeLabelStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(
111 type,
112 {
113 primary: {
114 color: theme.palette.primary.text,
115 fontWeight: '600',
116 },
117 secondary: {
118 color: theme.palette.secondary.text,
119 fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined,
120 },
121 default: {
122 color: theme.palette.default.text,
123 },
124 inverted: {
125 color: theme.palette.inverted.text,
126 fontWeight: '600',
127 },
128 'primary-outline': {
129 color: theme.palette.primary.textInverted,
130 fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined,
131 },
132 'secondary-outline': {
133 color: theme.palette.secondary.textInverted,
134 fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined,
135 },
136 'primary-light': {
137 color: theme.palette.primary.textInverted,
138 fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined,
139 },
140 'secondary-light': {
141 color: theme.palette.secondary.textInverted,
142 fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined,
143 },
144 'default-light': {
145 color: theme.palette.default.text,
146 fontWeight: theme.palette.default.isLowContrast ? '600' : undefined,
147 },
148 },
149 )
150
151 const [isLoading, setIsLoading] = React.useState(false)
152 const onPressWrapped = React.useCallback(
153 async (event: GestureResponderEvent) => {
154 event.stopPropagation()
155 event.preventDefault()
156 withLoading && setIsLoading(true)
157 await onPress?.(event)
158 withLoading && setIsLoading(false)
159 },
160 [onPress, withLoading],
161 )
162
163 const getStyle = React.useCallback(
164 (state: PressableStateCallbackType) => {
165 const arr = [typeOuterStyle, styles.outer, style]
166 if (state.pressed) {
167 arr.push({opacity: 0.6})
168 } else if (state.hovered) {
169 arr.push({opacity: 0.8})
170 }
171 return arr
172 },
173 [typeOuterStyle, style],
174 )
175
176 const renderChildern = React.useCallback(() => {
177 if (!label) {
178 return children
179 }
180
181 return (
182 <View style={[styles.labelContainer, labelContainerStyle]}>
183 {label && withLoading && isLoading ? (
184 <ActivityIndicator size={12} color={typeLabelStyle.color} />
185 ) : null}
186 <Text type="button" style={[typeLabelStyle, labelStyle]}>
187 {label}
188 </Text>
189 </View>
190 )
191 }, [
192 children,
193 label,
194 withLoading,
195 isLoading,
196 labelContainerStyle,
197 typeLabelStyle,
198 labelStyle,
199 ])
200
201 return (
202 <Pressable
203 style={getStyle}
204 onPress={onPressWrapped}
205 disabled={disabled || isLoading}
206 testID={testID}
207 accessibilityRole="button"
208 accessibilityLabel={accessibilityLabel}
209 accessibilityHint={accessibilityHint}
210 accessibilityLabelledBy={accessibilityLabelledBy}
211 onAccessibilityEscape={onAccessibilityEscape}>
212 {renderChildern}
213 </Pressable>
214 )
215}
216
217const styles = StyleSheet.create({
218 outer: {
219 paddingHorizontal: 14,
220 paddingVertical: 8,
221 borderRadius: 24,
222 },
223 labelContainer: {
224 flexDirection: 'row',
225 gap: 8,
226 },
227})