rnnovaui 0.1.7 → 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,6 +1,6 @@
1
1
  {
2
2
  "name": "rnnovaui",
3
- "version": "0.1.7",
3
+ "version": "0.2.1",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -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 VARIANTS = {
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 SIZES = {
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 FALLBACK = {
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 UIButton({
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
- testID,
69
-
70
- ...props
71
- }) {
72
- const { theme } = useUITheme();
73
+ activeOpacity = 0.82,
73
74
 
74
- const colors = theme?.colors || {};
75
-
76
- const primary = colors.primary || FALLBACK.primary;
77
-
78
- const primaryPressed = colors.primaryPressed || FALLBACK.primaryPressed;
75
+ pressAnimation = true,
79
76
 
80
- const primarySoft = colors.primarySoft || FALLBACK.primarySoft;
77
+ pressScale = 0.97,
81
78
 
82
- const success = colors.success || FALLBACK.success;
79
+ pressAnimationDuration = 100,
83
80
 
84
- const successPressed = colors.successPressed || FALLBACK.successPressed;
81
+ testID,
85
82
 
86
- const danger = colors.danger || FALLBACK.danger;
83
+ accessibilityLabel,
84
+ accessibilityHint,
87
85
 
88
- const dangerPressed = colors.dangerPressed || FALLBACK.dangerPressed;
86
+ ...props
87
+ }) {
88
+ const { theme } = useUITheme();
89
89
 
90
- const white = colors.onPrimary || FALLBACK.white;
90
+ const colors = theme?.colors || {};
91
91
 
92
92
  const isDisabled = disabled || loading;
93
93
 
94
- const buttonColors = useMemo(() => {
95
- if (isDisabled) {
96
- return {
97
- background:
98
- colors.buttonDisabledBackground || FALLBACK.disabledBackground,
99
-
100
- border: colors.buttonDisabledBorder || FALLBACK.disabledBackground,
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
- switch (variant) {
109
- case "outline":
110
- return {
111
- background: "transparent",
103
+ const animationRef = useRef(null);
112
104
 
113
- border: colors.buttonOutlineBorder || primary,
105
+ const safeVariant = BUTTON_VARIANTS[variant]
106
+ ? variant
107
+ : BUTTON_VARIANTS.solid;
114
108
 
115
- text: colors.buttonOutlineText || primary,
109
+ const safeSize = BUTTON_SIZES[size] ? size : BUTTON_SIZES.md;
116
110
 
117
- borderWidth: 1,
118
- };
111
+ const buttonColors = useMemo(
112
+ () => getVariantColors(safeVariant, colors, isDisabled),
113
+ [safeVariant, colors, isDisabled],
114
+ );
119
115
 
120
- case "ghost":
121
- return {
122
- background: "transparent",
116
+ const dimensions = useMemo(
117
+ () => getDimensions(safeSize, theme),
118
+ [safeSize, theme],
119
+ );
123
120
 
124
- border: "transparent",
121
+ const stopAnimation = useCallback(() => {
122
+ if (animationRef.current) {
123
+ animationRef.current.stop();
125
124
 
126
- text: colors.buttonGhostText || primary,
125
+ animationRef.current = null;
126
+ }
127
+ }, []);
127
128
 
128
- borderWidth: 0,
129
- };
129
+ const animateScale = useCallback(
130
+ (targetScale) => {
131
+ if (!pressAnimation || isDisabled) {
132
+ return;
133
+ }
130
134
 
131
- case "soft":
132
- return {
133
- background: colors.buttonSoftBackground || primarySoft,
135
+ stopAnimation();
134
136
 
135
- border: colors.buttonSoftBackground || primarySoft,
137
+ animationRef.current = Animated.timing(scale, {
138
+ toValue: targetScale,
136
139
 
137
- text: colors.buttonSoftText || primary,
140
+ duration: pressAnimationDuration,
138
141
 
139
- borderWidth: 1,
140
- };
142
+ easing: Easing.out(Easing.quad),
141
143
 
142
- case "danger":
143
- return {
144
- background: colors.buttonDangerBackground || danger,
144
+ useNativeDriver: true,
145
+ });
145
146
 
146
- border: colors.buttonDangerBackground || danger,
147
+ animationRef.current.start(() => {
148
+ animationRef.current = null;
149
+ });
150
+ },
151
+ [pressAnimation, isDisabled, stopAnimation, scale, pressAnimationDuration],
152
+ );
147
153
 
148
- text: colors.buttonDangerText || white,
154
+ const handlePressIn = useCallback(
155
+ (event) => {
156
+ if (isDisabled) {
157
+ return;
158
+ }
149
159
 
150
- borderWidth: 1,
151
- };
160
+ animateScale(pressScale);
152
161
 
153
- case "success":
154
- return {
155
- background: colors.buttonSuccessBackground || success,
162
+ onPressIn?.(event);
163
+ },
164
+ [isDisabled, animateScale, pressScale, onPressIn],
165
+ );
156
166
 
157
- border: colors.buttonSuccessBackground || success,
167
+ const handlePressOut = useCallback(
168
+ (event) => {
169
+ if (isDisabled) {
170
+ return;
171
+ }
158
172
 
159
- text: colors.buttonSuccessText || white,
173
+ animateScale(1);
160
174
 
161
- borderWidth: 1,
162
- };
175
+ onPressOut?.(event);
176
+ },
177
+ [isDisabled, animateScale, onPressOut],
178
+ );
163
179
 
164
- case "solid":
165
- default:
166
- return {
167
- background: colors.buttonPrimaryBackground || primary,
180
+ const handlePress = useCallback(
181
+ (event) => {
182
+ if (isDisabled) {
183
+ return;
184
+ }
168
185
 
169
- border: colors.buttonPrimaryBackground || primary,
186
+ onPress?.(event);
187
+ },
188
+ [isDisabled, onPress],
189
+ );
170
190
 
171
- text: colors.buttonPrimaryText || white,
191
+ const handleLongPress = useCallback(
192
+ (event) => {
193
+ if (isDisabled) {
194
+ return;
195
+ }
172
196
 
173
- borderWidth: 1,
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
- const handlePress = useCallback(
219
- (event) => {
220
- if (isDisabled) {
221
- return;
222
- }
223
-
224
- onPress?.(event);
225
- },
226
- [isDisabled, onPress],
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
- const handleLongPress = useCallback(
230
- (event) => {
231
- if (isDisabled) {
232
- return;
233
- }
308
+ function getVariantColors(variant, colors, disabled) {
309
+ const primary = colors.primary || FALLBACK_COLORS.primary;
234
310
 
235
- onLongPress?.(event);
236
- },
237
- [isDisabled, onLongPress],
238
- );
311
+ const primarySoft = colors.primarySoft || FALLBACK_COLORS.primarySoft;
239
312
 
240
- return (
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
- gap: dimensions.gap,
255
- },
315
+ const successPressed =
316
+ colors.successPressed || FALLBACK_COLORS.successPressed;
256
317
 
257
- contentStyle,
258
- ]}
259
- >
260
- {loading ? (
261
- <ActivityIndicator size="small" color={buttonColors.text} />
262
- ) : (
263
- leftIcon && <View style={styles.icon}>{leftIcon}</View>
264
- )}
265
-
266
- {children !== undefined && children !== null ? (
267
- <View style={styles.children}>{children}</View>
268
- ) : title !== undefined && title !== null ? (
269
- <Text
270
- numberOfLines={1}
271
- style={[
272
- styles.text,
273
-
274
- {
275
- color: buttonColors.text,
276
-
277
- fontSize: dimensions.fontSize,
278
-
279
- lineHeight: dimensions.lineHeight,
280
- },
281
-
282
- textStyle,
283
- ]}
284
- >
285
- {title}
286
- </Text>
287
- ) : null}
288
-
289
- {!loading && rightIcon && <View style={styles.icon}>{rightIcon}</View>}
290
- </View>
291
- </Pressable>
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 "sm":
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 "lg":
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 "xl":
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 "md":
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
- flexDirection: "row",
481
+ flexShrink: 0,
370
482
 
371
483
  alignItems: "center",
372
484
 
373
485
  justifyContent: "center",
486
+ },
374
487
 
375
- overflow: "hidden",
488
+ pressable: {
489
+ width: "100%",
376
490
 
377
- flexShrink: 0,
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 default memo(UIButton);
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 { UIButton, VARIANTS as UIButtonVariants, SIZES as UIButtonSizes };
545
+ export default UIButton;
@@ -1,17 +1,16 @@
1
- import React, { memo, useCallback } from "react";
1
+ import React, { memo, useCallback, useMemo, useRef } from "react";
2
2
 
3
- import { ActivityIndicator, Pressable, StyleSheet, View } from "react-native";
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
 
7
- const SIZES = {
8
- xs: "xs",
9
- sm: "sm",
10
- md: "md",
11
- lg: "lg",
12
- xl: "xl",
13
- };
14
-
15
14
  const VARIANTS = {
16
15
  solid: "solid",
17
16
  outline: "outline",
@@ -21,19 +20,46 @@ const VARIANTS = {
21
20
  success: "success",
22
21
  };
23
22
 
23
+ const SIZES = {
24
+ xs: "xs",
25
+ sm: "sm",
26
+ md: "md",
27
+ lg: "lg",
28
+ xl: "xl",
29
+ xxl: "xxl",
30
+ };
31
+
24
32
  const SHAPES = {
25
33
  circle: "circle",
26
34
  rounded: "rounded",
27
35
  square: "square",
28
36
  };
29
37
 
30
- const UIIconButtonComponent = ({
31
- icon,
38
+ const FALLBACK_COLORS = {
39
+ primary: "#FF5A1F",
40
+ primarySoft: "#FFF0EA",
41
+ primaryPressed: "#E94D17",
32
42
 
33
- size = "md",
43
+ success: "#16A34A",
44
+ successPressed: "#128238",
45
+
46
+ danger: "#DC2626",
47
+ dangerPressed: "#B91C1C",
48
+
49
+ white: "#FFFFFF",
50
+
51
+ disabledBackground: "#E5E5E5",
52
+ disabledText: "#A3A3A3",
53
+ disabledBorder: "#E5E5E5",
54
+ };
55
+
56
+ function UIIconButtonComponent({
57
+ icon,
34
58
 
35
59
  variant = "ghost",
36
60
 
61
+ size = "md",
62
+
37
63
  shape = "circle",
38
64
 
39
65
  disabled = false,
@@ -52,264 +78,392 @@ const UIIconButtonComponent = ({
52
78
 
53
79
  iconContainerStyle,
54
80
 
55
- activeOpacity = 0.82,
81
+ hitSlop,
56
82
 
57
- android_ripple = true,
83
+ pressAnimation = true,
58
84
 
59
- hitSlop = 8,
85
+ pressScale = 0.94,
86
+
87
+ pressAnimationDuration = 100,
88
+
89
+ testID,
60
90
 
61
91
  accessibilityLabel,
62
92
 
63
93
  accessibilityHint,
64
94
 
65
- testID,
66
-
67
95
  ...props
68
- }) => {
96
+ }) {
69
97
  const { theme } = useUITheme();
70
98
 
71
- const safeSize = SIZES[size] ? size : SIZES.md;
99
+ const colors = theme?.colors || {};
100
+
101
+ const isDisabled = disabled || loading;
102
+
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);
72
112
 
73
113
  const safeVariant = VARIANTS[variant] ? variant : VARIANTS.ghost;
74
114
 
115
+ const safeSize = SIZES[size] ? size : SIZES.md;
116
+
75
117
  const safeShape = SHAPES[shape] ? shape : SHAPES.circle;
76
118
 
77
- const isDisabled = disabled || loading;
119
+ const variantColors = useMemo(
120
+ () => getVariantColors(safeVariant, colors, isDisabled),
121
+ [safeVariant, colors, isDisabled],
122
+ );
123
+
124
+ const dimensions = useMemo(
125
+ () => getDimensions(safeSize, safeShape, theme),
126
+ [safeSize, safeShape, theme],
127
+ );
128
+
129
+ const stopAnimation = useCallback(() => {
130
+ if (animationRef.current) {
131
+ animationRef.current.stop();
132
+
133
+ animationRef.current = null;
134
+ }
135
+ }, []);
78
136
 
79
- const dimensions = getDimensions(safeSize, theme);
137
+ const animateScale = useCallback(
138
+ (targetScale) => {
139
+ if (!pressAnimation || isDisabled) {
140
+ return;
141
+ }
80
142
 
81
- const colors = getColors(safeVariant, theme.colors);
143
+ stopAnimation();
82
144
 
83
- const borderRadius = getBorderRadius(safeShape, theme);
145
+ const animation = Animated.timing(scale, {
146
+ toValue: targetScale,
84
147
 
85
- const handlePress = useCallback(
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(
86
165
  (event) => {
87
166
  if (isDisabled) {
88
167
  return;
89
168
  }
90
169
 
91
- onPress?.(event);
170
+ animateScale(pressScale);
171
+
172
+ onPressIn?.(event);
92
173
  },
93
- [isDisabled, onPress],
174
+ [isDisabled, animateScale, pressScale, onPressIn],
94
175
  );
95
176
 
96
- const handleLongPress = useCallback(
177
+ const handlePressOut = useCallback(
97
178
  (event) => {
98
179
  if (isDisabled) {
99
180
  return;
100
181
  }
101
182
 
102
- onLongPress?.(event);
183
+ animateScale(1);
184
+
185
+ onPressOut?.(event);
103
186
  },
104
- [isDisabled, onLongPress],
187
+ [isDisabled, animateScale, onPressOut],
105
188
  );
106
189
 
107
- const handlePressIn = useCallback(
190
+ const handlePress = useCallback(
108
191
  (event) => {
109
192
  if (isDisabled) {
110
193
  return;
111
194
  }
112
195
 
113
- onPressIn?.(event);
196
+ onPress?.(event);
114
197
  },
115
- [isDisabled, onPressIn],
198
+ [isDisabled, onPress],
116
199
  );
117
200
 
118
- const handlePressOut = useCallback(
201
+ const handleLongPress = useCallback(
119
202
  (event) => {
120
203
  if (isDisabled) {
121
204
  return;
122
205
  }
123
206
 
124
- onPressOut?.(event);
207
+ onLongPress?.(event);
125
208
  },
126
- [isDisabled, onPressOut],
209
+ [isDisabled, onLongPress],
127
210
  );
128
211
 
129
- const ripple = android_ripple
130
- ? {
131
- color: colors.ripple,
132
- borderless: false,
133
- }
134
- : undefined;
212
+ const buttonStyle = useMemo(
213
+ () => [
214
+ styles.button,
215
+
216
+ {
217
+ width: dimensions.size,
218
+
219
+ height: dimensions.size,
220
+
221
+ minWidth: dimensions.size,
222
+
223
+ minHeight: dimensions.size,
224
+
225
+ borderRadius: dimensions.borderRadius,
226
+
227
+ backgroundColor: variantColors.background,
228
+
229
+ borderColor: variantColors.border,
230
+
231
+ borderWidth: variantColors.borderWidth,
232
+
233
+ opacity: isDisabled ? 0.55 : 1,
234
+
235
+ transform: [
236
+ {
237
+ scale,
238
+ },
239
+ ],
240
+ },
241
+
242
+ style,
243
+ ],
244
+ [dimensions, variantColors, isDisabled, scale, style],
245
+ );
135
246
 
136
247
  return (
137
- <Pressable
138
- {...props}
139
- testID={testID}
140
- disabled={isDisabled}
141
- onPress={handlePress}
142
- onLongPress={handleLongPress}
143
- onPressIn={handlePressIn}
144
- onPressOut={handlePressOut}
145
- hitSlop={hitSlop}
146
- android_ripple={ripple}
147
- accessibilityRole="button"
148
- accessibilityLabel={accessibilityLabel}
149
- accessibilityHint={accessibilityHint}
150
- accessibilityState={{
151
- disabled: isDisabled,
152
- busy: loading,
153
- }}
154
- style={({ pressed }) => [
155
- styles.button,
156
-
157
- {
158
- width: dimensions.size,
159
-
160
- height: dimensions.size,
161
-
162
- borderRadius,
163
-
164
- backgroundColor: colors.background,
165
-
166
- borderColor: colors.border,
167
-
168
- borderWidth: colors.border ? 1 : 0,
169
-
170
- opacity: isDisabled ? 0.5 : pressed ? activeOpacity : 1,
171
- },
172
-
173
- style,
174
- ]}
175
- >
176
- <View style={[styles.iconContainer, iconContainerStyle]}>
177
- {loading ? (
178
- <ActivityIndicator size="small" color={colors.icon} />
179
- ) : (
180
- icon
181
- )}
182
- </View>
183
- </Pressable>
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>
184
277
  );
185
- };
278
+ }
186
279
 
187
- function getDimensions(size, theme) {
188
- switch (size) {
189
- case SIZES.xs:
190
- return {
191
- size: theme.sizes.icon.lg + theme.spacing.sm,
192
- };
280
+ function getVariantColors(variant, colors, disabled) {
281
+ const primary = colors.primary || FALLBACK_COLORS.primary;
193
282
 
194
- case SIZES.sm:
195
- return {
196
- size: theme.sizes.button.sm,
197
- };
283
+ const primarySoft = colors.primarySoft || FALLBACK_COLORS.primarySoft;
198
284
 
199
- case SIZES.lg:
200
- return {
201
- size: theme.sizes.button.lg,
202
- };
285
+ const primaryPressed =
286
+ colors.primaryPressed || FALLBACK_COLORS.primaryPressed;
203
287
 
204
- case SIZES.xl:
205
- return {
206
- size: theme.sizes.button.xl,
207
- };
288
+ const success = colors.success || FALLBACK_COLORS.success;
208
289
 
209
- case SIZES.md:
210
- default:
211
- return {
212
- size: theme.sizes.button.md,
213
- };
214
- }
215
- }
290
+ const successPressed =
291
+ colors.successPressed || FALLBACK_COLORS.successPressed;
216
292
 
217
- function getBorderRadius(shape, theme) {
218
- switch (shape) {
219
- case SHAPES.rounded:
220
- return theme.radius.lg;
293
+ const danger = colors.danger || FALLBACK_COLORS.danger;
221
294
 
222
- case SHAPES.square:
223
- return theme.radius.sm;
295
+ const dangerPressed = colors.dangerPressed || FALLBACK_COLORS.dangerPressed;
224
296
 
225
- case SHAPES.circle:
226
- default:
227
- return theme.radius.circle;
297
+ const white = colors.onPrimary || FALLBACK_COLORS.white;
298
+
299
+ if (disabled) {
300
+ return {
301
+ background:
302
+ colors.buttonDisabledBackground || FALLBACK_COLORS.disabledBackground,
303
+
304
+ border: colors.buttonDisabledBorder || FALLBACK_COLORS.disabledBorder,
305
+
306
+ borderWidth: 1,
307
+
308
+ icon: colors.buttonDisabledText || FALLBACK_COLORS.disabledText,
309
+ };
228
310
  }
229
- }
230
311
 
231
- function getColors(variant, colors) {
232
312
  switch (variant) {
233
313
  case VARIANTS.solid:
234
314
  return {
235
- background: colors.primary,
315
+ background: colors.buttonPrimaryBackground || primary,
236
316
 
237
- border: colors.primary,
317
+ border: colors.buttonPrimaryBackground || primary,
238
318
 
239
- icon: colors.onPrimary,
319
+ borderWidth: 1,
240
320
 
241
- ripple: colors.primaryPressed,
321
+ icon: colors.buttonPrimaryText || white,
242
322
  };
243
323
 
244
324
  case VARIANTS.outline:
245
325
  return {
246
- background: colors.transparent,
326
+ background: colors.buttonOutlineBackground || "transparent",
247
327
 
248
- border: colors.borderStrong,
328
+ border: colors.buttonOutlineBorder || primary,
249
329
 
250
- icon: colors.text,
330
+ borderWidth: 1,
251
331
 
252
- ripple: colors.surfaceSecondary,
332
+ icon: colors.buttonOutlineText || primary,
253
333
  };
254
334
 
255
335
  case VARIANTS.soft:
256
336
  return {
257
- background: colors.primarySoft,
337
+ background: colors.buttonSoftBackground || primarySoft,
258
338
 
259
- border: null,
339
+ border: colors.buttonSoftBackground || primarySoft,
260
340
 
261
- icon: colors.primary,
341
+ borderWidth: 1,
262
342
 
263
- ripple: colors.primary,
343
+ icon: colors.buttonSoftText || primary,
264
344
  };
265
345
 
266
346
  case VARIANTS.danger:
267
347
  return {
268
- background: colors.danger,
348
+ background: colors.buttonDangerBackground || danger,
269
349
 
270
- border: colors.danger,
350
+ border: colors.buttonDangerBackground || danger,
271
351
 
272
- icon: colors.onPrimary,
352
+ borderWidth: 1,
273
353
 
274
- ripple: colors.dangerSoft,
354
+ icon: colors.buttonDangerText || white,
275
355
  };
276
356
 
277
357
  case VARIANTS.success:
278
358
  return {
279
- background: colors.success,
359
+ background: colors.buttonSuccessBackground || success,
280
360
 
281
- border: colors.success,
361
+ border: colors.buttonSuccessBackground || success,
282
362
 
283
- icon: colors.onPrimary,
363
+ borderWidth: 1,
284
364
 
285
- ripple: colors.successSoft,
365
+ icon: colors.buttonSuccessText || white,
286
366
  };
287
367
 
288
368
  case VARIANTS.ghost:
289
369
  default:
290
370
  return {
291
- background: colors.transparent,
371
+ background: colors.buttonGhostBackground || "transparent",
292
372
 
293
- border: null,
373
+ border: "transparent",
294
374
 
295
- icon: colors.text,
375
+ borderWidth: 0,
296
376
 
297
- ripple: colors.surfaceSecondary,
377
+ icon: colors.buttonGhostText || primary,
298
378
  };
299
379
  }
300
380
  }
301
381
 
382
+ function getDimensions(size, shape, theme) {
383
+ const iconSizes = theme?.sizes?.icon || {};
384
+
385
+ const radius = theme?.radius || {};
386
+
387
+ let resolvedSize;
388
+
389
+ switch (size) {
390
+ case SIZES.xs:
391
+ resolvedSize = iconSizes.xs ? iconSizes.xs + 16 : 32;
392
+ break;
393
+
394
+ case SIZES.sm:
395
+ resolvedSize = iconSizes.sm ? iconSizes.sm + 16 : 40;
396
+ break;
397
+
398
+ case SIZES.lg:
399
+ resolvedSize = iconSizes.lg ? iconSizes.lg + 20 : 52;
400
+ break;
401
+
402
+ case SIZES.xl:
403
+ resolvedSize = iconSizes.xl ? iconSizes.xl + 24 : 60;
404
+ break;
405
+
406
+ case SIZES.xxl:
407
+ resolvedSize = iconSizes.xxl ? iconSizes.xxl + 28 : 72;
408
+ break;
409
+
410
+ case SIZES.md:
411
+ default:
412
+ resolvedSize = iconSizes.md ? iconSizes.md + 20 : 44;
413
+ break;
414
+ }
415
+
416
+ let borderRadius;
417
+
418
+ switch (shape) {
419
+ case SHAPES.square:
420
+ borderRadius = radius.sm ?? 6;
421
+ break;
422
+
423
+ case SHAPES.rounded:
424
+ borderRadius = radius.button ?? 12;
425
+ break;
426
+
427
+ case SHAPES.circle:
428
+ default:
429
+ borderRadius = resolvedSize / 2;
430
+ break;
431
+ }
432
+
433
+ return {
434
+ size: resolvedSize,
435
+
436
+ borderRadius,
437
+ };
438
+ }
439
+
302
440
  const styles = StyleSheet.create({
303
441
  button: {
304
442
  alignItems: "center",
443
+
444
+ justifyContent: "center",
445
+
446
+ flexShrink: 0,
447
+ },
448
+
449
+ pressable: {
450
+ width: "100%",
451
+
452
+ height: "100%",
453
+
454
+ alignItems: "center",
455
+
305
456
  justifyContent: "center",
306
- overflow: "hidden",
307
457
  },
308
458
 
309
459
  iconContainer: {
460
+ width: "100%",
461
+
462
+ height: "100%",
463
+
310
464
  alignItems: "center",
465
+
311
466
  justifyContent: "center",
312
- flex: 1,
313
467
  },
314
468
  });
315
469
 
@@ -318,7 +472,9 @@ export const UIIconButton = memo(UIIconButtonComponent);
318
472
  UIIconButton.displayName = "UIIconButton";
319
473
 
320
474
  export {
321
- SIZES as UIIconButtonSizes,
322
475
  VARIANTS as UIIconButtonVariants,
476
+ SIZES as UIIconButtonSizes,
323
477
  SHAPES as UIIconButtonShapes,
324
478
  };
479
+
480
+ export default UIIconButton;
@@ -1,6 +1,6 @@
1
1
  export {
2
2
  UIIconButton,
3
- UIIconButtonSizes,
4
3
  UIIconButtonVariants,
4
+ UIIconButtonSizes,
5
5
  UIIconButtonShapes,
6
6
  } from "./UIIconButton";
@@ -2,7 +2,7 @@ export { UIText } from "./Text";
2
2
  export { UIButton, UIButtonVariants, UIButtonSizes } from "./Button";
3
3
  export {
4
4
  UIIconButton,
5
- UIIconButtonSizes,
6
5
  UIIconButtonVariants,
6
+ UIIconButtonSizes,
7
7
  UIIconButtonShapes,
8
8
  } from "./IconButton";
package/src/index.js CHANGED
@@ -21,7 +21,7 @@ export {
21
21
  UIButtonVariants,
22
22
  UIButtonSizes,
23
23
  UIIconButton,
24
- UIIconButtonSizes,
25
24
  UIIconButtonVariants,
25
+ UIIconButtonSizes,
26
26
  UIIconButtonShapes,
27
27
  } from "./components";