rnnovaui 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import React, { memo, useCallback, useMemo } from "react";
|
|
1
|
+
import React, { memo, useCallback, useMemo, useRef } from "react";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
ActivityIndicator,
|
|
5
|
+
Animated,
|
|
6
|
+
Easing,
|
|
5
7
|
Pressable,
|
|
6
8
|
StyleSheet,
|
|
7
9
|
Text,
|
|
@@ -10,7 +12,7 @@ import {
|
|
|
10
12
|
|
|
11
13
|
import { useUITheme } from "../../theme";
|
|
12
14
|
|
|
13
|
-
const
|
|
15
|
+
const BUTTON_VARIANTS = {
|
|
14
16
|
solid: "solid",
|
|
15
17
|
outline: "outline",
|
|
16
18
|
ghost: "ghost",
|
|
@@ -19,14 +21,14 @@ const VARIANTS = {
|
|
|
19
21
|
success: "success",
|
|
20
22
|
};
|
|
21
23
|
|
|
22
|
-
const
|
|
24
|
+
const BUTTON_SIZES = {
|
|
23
25
|
sm: "sm",
|
|
24
26
|
md: "md",
|
|
25
27
|
lg: "lg",
|
|
26
28
|
xl: "xl",
|
|
27
29
|
};
|
|
28
30
|
|
|
29
|
-
const
|
|
31
|
+
const FALLBACK_COLORS = {
|
|
30
32
|
primary: "#FF5A1F",
|
|
31
33
|
primaryPressed: "#E94D17",
|
|
32
34
|
primarySoft: "#FFF0EA",
|
|
@@ -41,9 +43,10 @@ const FALLBACK = {
|
|
|
41
43
|
|
|
42
44
|
disabledBackground: "#E5E5E5",
|
|
43
45
|
disabledText: "#A3A3A3",
|
|
46
|
+
disabledBorder: "#E5E5E5",
|
|
44
47
|
};
|
|
45
48
|
|
|
46
|
-
function
|
|
49
|
+
function UIButtonComponent({
|
|
47
50
|
title,
|
|
48
51
|
children,
|
|
49
52
|
|
|
@@ -55,136 +58,146 @@ function UIButton({
|
|
|
55
58
|
|
|
56
59
|
fullWidth = false,
|
|
57
60
|
|
|
58
|
-
leftIcon,
|
|
59
|
-
rightIcon,
|
|
61
|
+
leftIcon = null,
|
|
62
|
+
rightIcon = null,
|
|
60
63
|
|
|
61
64
|
onPress,
|
|
62
65
|
onLongPress,
|
|
66
|
+
onPressIn,
|
|
67
|
+
onPressOut,
|
|
63
68
|
|
|
64
69
|
style,
|
|
65
70
|
contentStyle,
|
|
66
71
|
textStyle,
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
...props
|
|
71
|
-
}) {
|
|
72
|
-
const { theme } = useUITheme();
|
|
73
|
+
activeOpacity = 0.82,
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const primary = colors.primary || FALLBACK.primary;
|
|
77
|
-
|
|
78
|
-
const primaryPressed = colors.primaryPressed || FALLBACK.primaryPressed;
|
|
75
|
+
pressAnimation = true,
|
|
79
76
|
|
|
80
|
-
|
|
77
|
+
pressScale = 0.97,
|
|
81
78
|
|
|
82
|
-
|
|
79
|
+
pressAnimationDuration = 100,
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
testID,
|
|
85
82
|
|
|
86
|
-
|
|
83
|
+
accessibilityLabel,
|
|
84
|
+
accessibilityHint,
|
|
87
85
|
|
|
88
|
-
|
|
86
|
+
...props
|
|
87
|
+
}) {
|
|
88
|
+
const { theme } = useUITheme();
|
|
89
89
|
|
|
90
|
-
const
|
|
90
|
+
const colors = theme?.colors || {};
|
|
91
91
|
|
|
92
92
|
const isDisabled = disabled || loading;
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
text: colors.buttonDisabledText || FALLBACK.disabledText,
|
|
103
|
-
|
|
104
|
-
borderWidth: 1,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
94
|
+
/*
|
|
95
|
+
* Native Animated value.
|
|
96
|
+
*
|
|
97
|
+
* Created once and reused.
|
|
98
|
+
* This avoids creating animation
|
|
99
|
+
* objects on every render.
|
|
100
|
+
*/
|
|
101
|
+
const scale = useRef(new Animated.Value(1)).current;
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
case "outline":
|
|
110
|
-
return {
|
|
111
|
-
background: "transparent",
|
|
103
|
+
const animationRef = useRef(null);
|
|
112
104
|
|
|
113
|
-
|
|
105
|
+
const safeVariant = BUTTON_VARIANTS[variant]
|
|
106
|
+
? variant
|
|
107
|
+
: BUTTON_VARIANTS.solid;
|
|
114
108
|
|
|
115
|
-
|
|
109
|
+
const safeSize = BUTTON_SIZES[size] ? size : BUTTON_SIZES.md;
|
|
116
110
|
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
const buttonColors = useMemo(
|
|
112
|
+
() => getVariantColors(safeVariant, colors, isDisabled),
|
|
113
|
+
[safeVariant, colors, isDisabled],
|
|
114
|
+
);
|
|
119
115
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
const dimensions = useMemo(
|
|
117
|
+
() => getDimensions(safeSize, theme),
|
|
118
|
+
[safeSize, theme],
|
|
119
|
+
);
|
|
123
120
|
|
|
124
|
-
|
|
121
|
+
const stopAnimation = useCallback(() => {
|
|
122
|
+
if (animationRef.current) {
|
|
123
|
+
animationRef.current.stop();
|
|
125
124
|
|
|
126
|
-
|
|
125
|
+
animationRef.current = null;
|
|
126
|
+
}
|
|
127
|
+
}, []);
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
const animateScale = useCallback(
|
|
130
|
+
(targetScale) => {
|
|
131
|
+
if (!pressAnimation || isDisabled) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
130
134
|
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
background: colors.buttonSoftBackground || primarySoft,
|
|
135
|
+
stopAnimation();
|
|
134
136
|
|
|
135
|
-
|
|
137
|
+
animationRef.current = Animated.timing(scale, {
|
|
138
|
+
toValue: targetScale,
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
duration: pressAnimationDuration,
|
|
138
141
|
|
|
139
|
-
|
|
140
|
-
};
|
|
142
|
+
easing: Easing.out(Easing.quad),
|
|
141
143
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
background: colors.buttonDangerBackground || danger,
|
|
144
|
+
useNativeDriver: true,
|
|
145
|
+
});
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
animationRef.current.start(() => {
|
|
148
|
+
animationRef.current = null;
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
[pressAnimation, isDisabled, stopAnimation, scale, pressAnimationDuration],
|
|
152
|
+
);
|
|
147
153
|
|
|
148
|
-
|
|
154
|
+
const handlePressIn = useCallback(
|
|
155
|
+
(event) => {
|
|
156
|
+
if (isDisabled) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
149
159
|
|
|
150
|
-
|
|
151
|
-
};
|
|
160
|
+
animateScale(pressScale);
|
|
152
161
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
onPressIn?.(event);
|
|
163
|
+
},
|
|
164
|
+
[isDisabled, animateScale, pressScale, onPressIn],
|
|
165
|
+
);
|
|
156
166
|
|
|
157
|
-
|
|
167
|
+
const handlePressOut = useCallback(
|
|
168
|
+
(event) => {
|
|
169
|
+
if (isDisabled) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
158
172
|
|
|
159
|
-
|
|
173
|
+
animateScale(1);
|
|
160
174
|
|
|
161
|
-
|
|
162
|
-
|
|
175
|
+
onPressOut?.(event);
|
|
176
|
+
},
|
|
177
|
+
[isDisabled, animateScale, onPressOut],
|
|
178
|
+
);
|
|
163
179
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
const handlePress = useCallback(
|
|
181
|
+
(event) => {
|
|
182
|
+
if (isDisabled) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
168
185
|
|
|
169
|
-
|
|
186
|
+
onPress?.(event);
|
|
187
|
+
},
|
|
188
|
+
[isDisabled, onPress],
|
|
189
|
+
);
|
|
170
190
|
|
|
171
|
-
|
|
191
|
+
const handleLongPress = useCallback(
|
|
192
|
+
(event) => {
|
|
193
|
+
if (isDisabled) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
172
196
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
variant,
|
|
178
|
-
isDisabled,
|
|
179
|
-
colors,
|
|
180
|
-
primary,
|
|
181
|
-
primarySoft,
|
|
182
|
-
success,
|
|
183
|
-
danger,
|
|
184
|
-
white,
|
|
185
|
-
]);
|
|
186
|
-
|
|
187
|
-
const dimensions = useMemo(() => getDimensions(size, theme), [size, theme]);
|
|
197
|
+
onLongPress?.(event);
|
|
198
|
+
},
|
|
199
|
+
[isDisabled, onLongPress],
|
|
200
|
+
);
|
|
188
201
|
|
|
189
202
|
const buttonStyle = useMemo(
|
|
190
203
|
() => [
|
|
@@ -208,88 +221,187 @@ function UIButton({
|
|
|
208
221
|
width: fullWidth ? "100%" : undefined,
|
|
209
222
|
|
|
210
223
|
opacity: isDisabled ? 0.55 : 1,
|
|
224
|
+
|
|
225
|
+
transform: [
|
|
226
|
+
{
|
|
227
|
+
scale,
|
|
228
|
+
},
|
|
229
|
+
],
|
|
211
230
|
},
|
|
212
231
|
|
|
213
232
|
style,
|
|
214
233
|
],
|
|
215
|
-
[dimensions, buttonColors, fullWidth, isDisabled, style],
|
|
234
|
+
[dimensions, buttonColors, fullWidth, isDisabled, scale, style],
|
|
216
235
|
);
|
|
217
236
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
237
|
+
return (
|
|
238
|
+
<Animated.View style={buttonStyle}>
|
|
239
|
+
<Pressable
|
|
240
|
+
{...props}
|
|
241
|
+
testID={testID}
|
|
242
|
+
disabled={isDisabled}
|
|
243
|
+
onPress={handlePress}
|
|
244
|
+
onLongPress={handleLongPress}
|
|
245
|
+
onPressIn={handlePressIn}
|
|
246
|
+
onPressOut={handlePressOut}
|
|
247
|
+
accessibilityRole="button"
|
|
248
|
+
accessibilityLabel={
|
|
249
|
+
accessibilityLabel || (typeof title === "string" ? title : undefined)
|
|
250
|
+
}
|
|
251
|
+
accessibilityHint={accessibilityHint}
|
|
252
|
+
accessibilityState={{
|
|
253
|
+
disabled: isDisabled,
|
|
254
|
+
|
|
255
|
+
busy: loading,
|
|
256
|
+
}}
|
|
257
|
+
style={styles.pressable}
|
|
258
|
+
>
|
|
259
|
+
<View
|
|
260
|
+
style={[
|
|
261
|
+
styles.content,
|
|
262
|
+
|
|
263
|
+
{
|
|
264
|
+
gap: dimensions.gap,
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
contentStyle,
|
|
268
|
+
]}
|
|
269
|
+
>
|
|
270
|
+
{loading ? (
|
|
271
|
+
<ActivityIndicator size="small" color={buttonColors.text} />
|
|
272
|
+
) : (
|
|
273
|
+
leftIcon && <View style={styles.icon}>{leftIcon}</View>
|
|
274
|
+
)}
|
|
275
|
+
|
|
276
|
+
{children !== undefined && children !== null ? (
|
|
277
|
+
<View style={styles.children}>{children}</View>
|
|
278
|
+
) : title !== undefined && title !== null ? (
|
|
279
|
+
<Text
|
|
280
|
+
numberOfLines={1}
|
|
281
|
+
style={[
|
|
282
|
+
styles.text,
|
|
283
|
+
|
|
284
|
+
{
|
|
285
|
+
color: buttonColors.text,
|
|
286
|
+
|
|
287
|
+
fontSize: dimensions.fontSize,
|
|
288
|
+
|
|
289
|
+
lineHeight: dimensions.lineHeight,
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
textStyle,
|
|
293
|
+
]}
|
|
294
|
+
>
|
|
295
|
+
{title}
|
|
296
|
+
</Text>
|
|
297
|
+
) : null}
|
|
298
|
+
|
|
299
|
+
{!loading && rightIcon && (
|
|
300
|
+
<View style={styles.icon}>{rightIcon}</View>
|
|
301
|
+
)}
|
|
302
|
+
</View>
|
|
303
|
+
</Pressable>
|
|
304
|
+
</Animated.View>
|
|
227
305
|
);
|
|
306
|
+
}
|
|
228
307
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (isDisabled) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
308
|
+
function getVariantColors(variant, colors, disabled) {
|
|
309
|
+
const primary = colors.primary || FALLBACK_COLORS.primary;
|
|
234
310
|
|
|
235
|
-
|
|
236
|
-
},
|
|
237
|
-
[isDisabled, onLongPress],
|
|
238
|
-
);
|
|
311
|
+
const primarySoft = colors.primarySoft || FALLBACK_COLORS.primarySoft;
|
|
239
312
|
|
|
240
|
-
|
|
241
|
-
<Pressable
|
|
242
|
-
{...props}
|
|
243
|
-
testID={testID}
|
|
244
|
-
disabled={isDisabled}
|
|
245
|
-
onPress={handlePress}
|
|
246
|
-
onLongPress={handleLongPress}
|
|
247
|
-
style={buttonStyle}
|
|
248
|
-
>
|
|
249
|
-
<View
|
|
250
|
-
style={[
|
|
251
|
-
styles.content,
|
|
313
|
+
const success = colors.success || FALLBACK_COLORS.success;
|
|
252
314
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
},
|
|
315
|
+
const successPressed =
|
|
316
|
+
colors.successPressed || FALLBACK_COLORS.successPressed;
|
|
256
317
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
318
|
+
const danger = colors.danger || FALLBACK_COLORS.danger;
|
|
319
|
+
|
|
320
|
+
const dangerPressed = colors.dangerPressed || FALLBACK_COLORS.dangerPressed;
|
|
321
|
+
|
|
322
|
+
const white = colors.onPrimary || FALLBACK_COLORS.white;
|
|
323
|
+
|
|
324
|
+
if (disabled) {
|
|
325
|
+
return {
|
|
326
|
+
background:
|
|
327
|
+
colors.buttonDisabledBackground || FALLBACK_COLORS.disabledBackground,
|
|
328
|
+
|
|
329
|
+
border: colors.buttonDisabledBorder || FALLBACK_COLORS.disabledBorder,
|
|
330
|
+
|
|
331
|
+
borderWidth: 1,
|
|
332
|
+
|
|
333
|
+
text: colors.buttonDisabledText || FALLBACK_COLORS.disabledText,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
switch (variant) {
|
|
338
|
+
case BUTTON_VARIANTS.outline:
|
|
339
|
+
return {
|
|
340
|
+
background: colors.buttonOutlineBackground || "transparent",
|
|
341
|
+
|
|
342
|
+
border: colors.buttonOutlineBorder || primary,
|
|
343
|
+
|
|
344
|
+
borderWidth: 1,
|
|
345
|
+
|
|
346
|
+
text: colors.buttonOutlineText || primary,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
case BUTTON_VARIANTS.ghost:
|
|
350
|
+
return {
|
|
351
|
+
background: colors.buttonGhostBackground || "transparent",
|
|
352
|
+
|
|
353
|
+
border: "transparent",
|
|
354
|
+
|
|
355
|
+
borderWidth: 0,
|
|
356
|
+
|
|
357
|
+
text: colors.buttonGhostText || primary,
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
case BUTTON_VARIANTS.soft:
|
|
361
|
+
return {
|
|
362
|
+
background: colors.buttonSoftBackground || primarySoft,
|
|
363
|
+
|
|
364
|
+
border: colors.buttonSoftBackground || primarySoft,
|
|
365
|
+
|
|
366
|
+
borderWidth: 1,
|
|
367
|
+
|
|
368
|
+
text: colors.buttonSoftText || primary,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
case BUTTON_VARIANTS.danger:
|
|
372
|
+
return {
|
|
373
|
+
background: colors.buttonDangerBackground || danger,
|
|
374
|
+
|
|
375
|
+
border: colors.buttonDangerBackground || danger,
|
|
376
|
+
|
|
377
|
+
borderWidth: 1,
|
|
378
|
+
|
|
379
|
+
text: colors.buttonDangerText || white,
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
case BUTTON_VARIANTS.success:
|
|
383
|
+
return {
|
|
384
|
+
background: colors.buttonSuccessBackground || success,
|
|
385
|
+
|
|
386
|
+
border: colors.buttonSuccessBackground || success,
|
|
387
|
+
|
|
388
|
+
borderWidth: 1,
|
|
389
|
+
|
|
390
|
+
text: colors.buttonSuccessText || white,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
case BUTTON_VARIANTS.solid:
|
|
394
|
+
default:
|
|
395
|
+
return {
|
|
396
|
+
background: colors.buttonPrimaryBackground || primary,
|
|
397
|
+
|
|
398
|
+
border: colors.buttonPrimaryBackground || primary,
|
|
399
|
+
|
|
400
|
+
borderWidth: 1,
|
|
401
|
+
|
|
402
|
+
text: colors.buttonPrimaryText || white,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
293
405
|
}
|
|
294
406
|
|
|
295
407
|
function getDimensions(size, theme) {
|
|
@@ -301,7 +413,7 @@ function getDimensions(size, theme) {
|
|
|
301
413
|
const spacing = theme?.spacing || {};
|
|
302
414
|
|
|
303
415
|
switch (size) {
|
|
304
|
-
case
|
|
416
|
+
case BUTTON_SIZES.sm:
|
|
305
417
|
return {
|
|
306
418
|
height: buttonSizes.sm || 36,
|
|
307
419
|
|
|
@@ -316,7 +428,7 @@ function getDimensions(size, theme) {
|
|
|
316
428
|
lineHeight: 18,
|
|
317
429
|
};
|
|
318
430
|
|
|
319
|
-
case
|
|
431
|
+
case BUTTON_SIZES.lg:
|
|
320
432
|
return {
|
|
321
433
|
height: buttonSizes.lg || 52,
|
|
322
434
|
|
|
@@ -331,7 +443,7 @@ function getDimensions(size, theme) {
|
|
|
331
443
|
lineHeight: 22,
|
|
332
444
|
};
|
|
333
445
|
|
|
334
|
-
case
|
|
446
|
+
case BUTTON_SIZES.xl:
|
|
335
447
|
return {
|
|
336
448
|
height: buttonSizes.xl || 60,
|
|
337
449
|
|
|
@@ -346,7 +458,7 @@ function getDimensions(size, theme) {
|
|
|
346
458
|
lineHeight: 24,
|
|
347
459
|
};
|
|
348
460
|
|
|
349
|
-
case
|
|
461
|
+
case BUTTON_SIZES.md:
|
|
350
462
|
default:
|
|
351
463
|
return {
|
|
352
464
|
height: buttonSizes.md || 44,
|
|
@@ -366,18 +478,28 @@ function getDimensions(size, theme) {
|
|
|
366
478
|
|
|
367
479
|
const styles = StyleSheet.create({
|
|
368
480
|
button: {
|
|
369
|
-
|
|
481
|
+
flexShrink: 0,
|
|
370
482
|
|
|
371
483
|
alignItems: "center",
|
|
372
484
|
|
|
373
485
|
justifyContent: "center",
|
|
486
|
+
},
|
|
374
487
|
|
|
375
|
-
|
|
488
|
+
pressable: {
|
|
489
|
+
width: "100%",
|
|
376
490
|
|
|
377
|
-
|
|
491
|
+
height: "100%",
|
|
492
|
+
|
|
493
|
+
alignItems: "center",
|
|
494
|
+
|
|
495
|
+
justifyContent: "center",
|
|
378
496
|
},
|
|
379
497
|
|
|
380
498
|
content: {
|
|
499
|
+
width: "100%",
|
|
500
|
+
|
|
501
|
+
height: "100%",
|
|
502
|
+
|
|
381
503
|
flexDirection: "row",
|
|
382
504
|
|
|
383
505
|
alignItems: "center",
|
|
@@ -404,16 +526,20 @@ const styles = StyleSheet.create({
|
|
|
404
526
|
},
|
|
405
527
|
|
|
406
528
|
text: {
|
|
529
|
+
flexShrink: 1,
|
|
530
|
+
|
|
407
531
|
fontWeight: "600",
|
|
408
532
|
|
|
409
533
|
textAlign: "center",
|
|
410
534
|
|
|
411
535
|
includeFontPadding: false,
|
|
412
|
-
|
|
413
|
-
flexShrink: 1,
|
|
414
536
|
},
|
|
415
537
|
});
|
|
416
538
|
|
|
417
|
-
export
|
|
539
|
+
export const UIButton = memo(UIButtonComponent);
|
|
540
|
+
|
|
541
|
+
UIButton.displayName = "UIButton";
|
|
542
|
+
|
|
543
|
+
export { BUTTON_VARIANTS as UIButtonVariants, BUTTON_SIZES as UIButtonSizes };
|
|
418
544
|
|
|
419
|
-
export
|
|
545
|
+
export default UIButton;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import React, { memo, useCallback, useMemo } from "react";
|
|
1
|
+
import React, { memo, useCallback, useMemo, useRef } from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ActivityIndicator,
|
|
5
|
+
Animated,
|
|
6
|
+
Easing,
|
|
7
|
+
Pressable,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
View,
|
|
10
|
+
} from "react-native";
|
|
4
11
|
|
|
5
12
|
import { useUITheme } from "../../theme";
|
|
6
13
|
|
|
@@ -41,9 +48,8 @@ const FALLBACK_COLORS = {
|
|
|
41
48
|
|
|
42
49
|
white: "#FFFFFF",
|
|
43
50
|
|
|
44
|
-
border: "#E5E5E5",
|
|
45
|
-
|
|
46
51
|
disabledBackground: "#E5E5E5",
|
|
52
|
+
disabledText: "#A3A3A3",
|
|
47
53
|
disabledBorder: "#E5E5E5",
|
|
48
54
|
};
|
|
49
55
|
|
|
@@ -74,6 +80,12 @@ function UIIconButtonComponent({
|
|
|
74
80
|
|
|
75
81
|
hitSlop,
|
|
76
82
|
|
|
83
|
+
pressAnimation = true,
|
|
84
|
+
|
|
85
|
+
pressScale = 0.94,
|
|
86
|
+
|
|
87
|
+
pressAnimationDuration = 100,
|
|
88
|
+
|
|
77
89
|
testID,
|
|
78
90
|
|
|
79
91
|
accessibilityLabel,
|
|
@@ -88,58 +100,113 @@ function UIIconButtonComponent({
|
|
|
88
100
|
|
|
89
101
|
const isDisabled = disabled || loading;
|
|
90
102
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
/*
|
|
104
|
+
* Reusable native animation value.
|
|
105
|
+
*
|
|
106
|
+
* Created once for the lifetime
|
|
107
|
+
* of this component instance.
|
|
108
|
+
*/
|
|
109
|
+
const scale = useRef(new Animated.Value(1)).current;
|
|
110
|
+
|
|
111
|
+
const animationRef = useRef(null);
|
|
112
|
+
|
|
113
|
+
const safeVariant = VARIANTS[variant] ? variant : VARIANTS.ghost;
|
|
114
|
+
|
|
115
|
+
const safeSize = SIZES[size] ? size : SIZES.md;
|
|
116
|
+
|
|
117
|
+
const safeShape = SHAPES[shape] ? shape : SHAPES.circle;
|
|
118
|
+
|
|
119
|
+
const variantColors = useMemo(
|
|
120
|
+
() => getVariantColors(safeVariant, colors, isDisabled),
|
|
121
|
+
[safeVariant, colors, isDisabled],
|
|
94
122
|
);
|
|
95
123
|
|
|
96
124
|
const dimensions = useMemo(
|
|
97
|
-
() => getDimensions(
|
|
98
|
-
[
|
|
125
|
+
() => getDimensions(safeSize, safeShape, theme),
|
|
126
|
+
[safeSize, safeShape, theme],
|
|
99
127
|
);
|
|
100
128
|
|
|
101
|
-
const
|
|
129
|
+
const stopAnimation = useCallback(() => {
|
|
130
|
+
if (animationRef.current) {
|
|
131
|
+
animationRef.current.stop();
|
|
132
|
+
|
|
133
|
+
animationRef.current = null;
|
|
134
|
+
}
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
const animateScale = useCallback(
|
|
138
|
+
(targetScale) => {
|
|
139
|
+
if (!pressAnimation || isDisabled) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
stopAnimation();
|
|
144
|
+
|
|
145
|
+
const animation = Animated.timing(scale, {
|
|
146
|
+
toValue: targetScale,
|
|
147
|
+
|
|
148
|
+
duration: pressAnimationDuration,
|
|
149
|
+
|
|
150
|
+
easing: Easing.out(Easing.quad),
|
|
151
|
+
|
|
152
|
+
useNativeDriver: true,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
animationRef.current = animation;
|
|
156
|
+
|
|
157
|
+
animation.start(() => {
|
|
158
|
+
animationRef.current = null;
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
[pressAnimation, isDisabled, stopAnimation, scale, pressAnimationDuration],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const handlePressIn = useCallback(
|
|
102
165
|
(event) => {
|
|
103
166
|
if (isDisabled) {
|
|
104
167
|
return;
|
|
105
168
|
}
|
|
106
169
|
|
|
107
|
-
|
|
170
|
+
animateScale(pressScale);
|
|
171
|
+
|
|
172
|
+
onPressIn?.(event);
|
|
108
173
|
},
|
|
109
|
-
[isDisabled,
|
|
174
|
+
[isDisabled, animateScale, pressScale, onPressIn],
|
|
110
175
|
);
|
|
111
176
|
|
|
112
|
-
const
|
|
177
|
+
const handlePressOut = useCallback(
|
|
113
178
|
(event) => {
|
|
114
179
|
if (isDisabled) {
|
|
115
180
|
return;
|
|
116
181
|
}
|
|
117
182
|
|
|
118
|
-
|
|
183
|
+
animateScale(1);
|
|
184
|
+
|
|
185
|
+
onPressOut?.(event);
|
|
119
186
|
},
|
|
120
|
-
[isDisabled,
|
|
187
|
+
[isDisabled, animateScale, onPressOut],
|
|
121
188
|
);
|
|
122
189
|
|
|
123
|
-
const
|
|
190
|
+
const handlePress = useCallback(
|
|
124
191
|
(event) => {
|
|
125
192
|
if (isDisabled) {
|
|
126
193
|
return;
|
|
127
194
|
}
|
|
128
195
|
|
|
129
|
-
|
|
196
|
+
onPress?.(event);
|
|
130
197
|
},
|
|
131
|
-
[isDisabled,
|
|
198
|
+
[isDisabled, onPress],
|
|
132
199
|
);
|
|
133
200
|
|
|
134
|
-
const
|
|
201
|
+
const handleLongPress = useCallback(
|
|
135
202
|
(event) => {
|
|
136
203
|
if (isDisabled) {
|
|
137
204
|
return;
|
|
138
205
|
}
|
|
139
206
|
|
|
140
|
-
|
|
207
|
+
onLongPress?.(event);
|
|
141
208
|
},
|
|
142
|
-
[isDisabled,
|
|
209
|
+
[isDisabled, onLongPress],
|
|
143
210
|
);
|
|
144
211
|
|
|
145
212
|
const buttonStyle = useMemo(
|
|
@@ -157,48 +224,56 @@ function UIIconButtonComponent({
|
|
|
157
224
|
|
|
158
225
|
borderRadius: dimensions.borderRadius,
|
|
159
226
|
|
|
160
|
-
backgroundColor:
|
|
227
|
+
backgroundColor: variantColors.background,
|
|
161
228
|
|
|
162
|
-
borderColor:
|
|
229
|
+
borderColor: variantColors.border,
|
|
163
230
|
|
|
164
|
-
borderWidth:
|
|
231
|
+
borderWidth: variantColors.borderWidth,
|
|
165
232
|
|
|
166
233
|
opacity: isDisabled ? 0.55 : 1,
|
|
234
|
+
|
|
235
|
+
transform: [
|
|
236
|
+
{
|
|
237
|
+
scale,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
167
240
|
},
|
|
168
241
|
|
|
169
242
|
style,
|
|
170
243
|
],
|
|
171
|
-
[dimensions,
|
|
244
|
+
[dimensions, variantColors, isDisabled, scale, style],
|
|
172
245
|
);
|
|
173
246
|
|
|
174
247
|
return (
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
{
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
248
|
+
<Animated.View style={buttonStyle}>
|
|
249
|
+
<Pressable
|
|
250
|
+
{...props}
|
|
251
|
+
testID={testID}
|
|
252
|
+
disabled={isDisabled}
|
|
253
|
+
onPress={handlePress}
|
|
254
|
+
onLongPress={handleLongPress}
|
|
255
|
+
onPressIn={handlePressIn}
|
|
256
|
+
onPressOut={handlePressOut}
|
|
257
|
+
hitSlop={hitSlop}
|
|
258
|
+
accessibilityRole="button"
|
|
259
|
+
accessibilityLabel={accessibilityLabel}
|
|
260
|
+
accessibilityHint={accessibilityHint}
|
|
261
|
+
accessibilityState={{
|
|
262
|
+
disabled: isDisabled,
|
|
263
|
+
|
|
264
|
+
busy: loading,
|
|
265
|
+
}}
|
|
266
|
+
style={styles.pressable}
|
|
267
|
+
>
|
|
268
|
+
<View style={[styles.iconContainer, iconContainerStyle]}>
|
|
269
|
+
{loading ? (
|
|
270
|
+
<ActivityIndicator size="small" color={variantColors.icon} />
|
|
271
|
+
) : (
|
|
272
|
+
icon
|
|
273
|
+
)}
|
|
274
|
+
</View>
|
|
275
|
+
</Pressable>
|
|
276
|
+
</Animated.View>
|
|
202
277
|
);
|
|
203
278
|
}
|
|
204
279
|
|
|
@@ -230,10 +305,7 @@ function getVariantColors(variant, colors, disabled) {
|
|
|
230
305
|
|
|
231
306
|
borderWidth: 1,
|
|
232
307
|
|
|
233
|
-
icon: colors.buttonDisabledText ||
|
|
234
|
-
|
|
235
|
-
pressed:
|
|
236
|
-
colors.buttonDisabledBackground || FALLBACK_COLORS.disabledBackground,
|
|
308
|
+
icon: colors.buttonDisabledText || FALLBACK_COLORS.disabledText,
|
|
237
309
|
};
|
|
238
310
|
}
|
|
239
311
|
|
|
@@ -247,21 +319,17 @@ function getVariantColors(variant, colors, disabled) {
|
|
|
247
319
|
borderWidth: 1,
|
|
248
320
|
|
|
249
321
|
icon: colors.buttonPrimaryText || white,
|
|
250
|
-
|
|
251
|
-
pressed: colors.buttonPrimaryPressed || primaryPressed,
|
|
252
322
|
};
|
|
253
323
|
|
|
254
324
|
case VARIANTS.outline:
|
|
255
325
|
return {
|
|
256
|
-
background: "transparent",
|
|
326
|
+
background: colors.buttonOutlineBackground || "transparent",
|
|
257
327
|
|
|
258
328
|
border: colors.buttonOutlineBorder || primary,
|
|
259
329
|
|
|
260
330
|
borderWidth: 1,
|
|
261
331
|
|
|
262
332
|
icon: colors.buttonOutlineText || primary,
|
|
263
|
-
|
|
264
|
-
pressed: colors.buttonSoftBackground || primarySoft,
|
|
265
333
|
};
|
|
266
334
|
|
|
267
335
|
case VARIANTS.soft:
|
|
@@ -273,8 +341,6 @@ function getVariantColors(variant, colors, disabled) {
|
|
|
273
341
|
borderWidth: 1,
|
|
274
342
|
|
|
275
343
|
icon: colors.buttonSoftText || primary,
|
|
276
|
-
|
|
277
|
-
pressed: colors.primaryMuted || primarySoft,
|
|
278
344
|
};
|
|
279
345
|
|
|
280
346
|
case VARIANTS.danger:
|
|
@@ -286,8 +352,6 @@ function getVariantColors(variant, colors, disabled) {
|
|
|
286
352
|
borderWidth: 1,
|
|
287
353
|
|
|
288
354
|
icon: colors.buttonDangerText || white,
|
|
289
|
-
|
|
290
|
-
pressed: colors.buttonDangerPressed || dangerPressed,
|
|
291
355
|
};
|
|
292
356
|
|
|
293
357
|
case VARIANTS.success:
|
|
@@ -299,22 +363,18 @@ function getVariantColors(variant, colors, disabled) {
|
|
|
299
363
|
borderWidth: 1,
|
|
300
364
|
|
|
301
365
|
icon: colors.buttonSuccessText || white,
|
|
302
|
-
|
|
303
|
-
pressed: colors.buttonSuccessPressed || successPressed,
|
|
304
366
|
};
|
|
305
367
|
|
|
306
368
|
case VARIANTS.ghost:
|
|
307
369
|
default:
|
|
308
370
|
return {
|
|
309
|
-
background: "transparent",
|
|
371
|
+
background: colors.buttonGhostBackground || "transparent",
|
|
310
372
|
|
|
311
373
|
border: "transparent",
|
|
312
374
|
|
|
313
375
|
borderWidth: 0,
|
|
314
376
|
|
|
315
377
|
icon: colors.buttonGhostText || primary,
|
|
316
|
-
|
|
317
|
-
pressed: colors.buttonSoftBackground || primarySoft,
|
|
318
378
|
};
|
|
319
379
|
}
|
|
320
380
|
}
|
|
@@ -383,11 +443,19 @@ const styles = StyleSheet.create({
|
|
|
383
443
|
|
|
384
444
|
justifyContent: "center",
|
|
385
445
|
|
|
386
|
-
overflow: "hidden",
|
|
387
|
-
|
|
388
446
|
flexShrink: 0,
|
|
389
447
|
},
|
|
390
448
|
|
|
449
|
+
pressable: {
|
|
450
|
+
width: "100%",
|
|
451
|
+
|
|
452
|
+
height: "100%",
|
|
453
|
+
|
|
454
|
+
alignItems: "center",
|
|
455
|
+
|
|
456
|
+
justifyContent: "center",
|
|
457
|
+
},
|
|
458
|
+
|
|
391
459
|
iconContainer: {
|
|
392
460
|
width: "100%",
|
|
393
461
|
|