@retray-dev/ui-kit 9.1.0 → 9.3.0

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.
Files changed (64) hide show
  1. package/COMPONENTS.md +166 -4
  2. package/CONSUMER.md +247 -0
  3. package/DESIGN.md +668 -0
  4. package/FONTS.md +107 -0
  5. package/README.md +3 -3
  6. package/dist/AlertBanner.d.mts +3 -1
  7. package/dist/AlertBanner.d.ts +3 -1
  8. package/dist/AlertBanner.js +18 -2
  9. package/dist/AlertBanner.mjs +1 -1
  10. package/dist/ConfirmDialog.d.mts +3 -1
  11. package/dist/ConfirmDialog.d.ts +3 -1
  12. package/dist/ConfirmDialog.js +3 -0
  13. package/dist/ConfirmDialog.mjs +1 -1
  14. package/dist/CurrencyInput.d.mts +3 -1
  15. package/dist/CurrencyInput.d.ts +3 -1
  16. package/dist/CurrencyInput.js +52 -39
  17. package/dist/CurrencyInput.mjs +2 -3
  18. package/dist/ImageUpload.d.mts +27 -0
  19. package/dist/ImageUpload.d.ts +27 -0
  20. package/dist/ImageUpload.js +399 -0
  21. package/dist/ImageUpload.mjs +9 -0
  22. package/dist/Input.d.mts +3 -1
  23. package/dist/Input.d.ts +3 -1
  24. package/dist/Input.js +48 -37
  25. package/dist/Input.mjs +1 -2
  26. package/dist/ListItem.d.mts +9 -2
  27. package/dist/ListItem.d.ts +9 -2
  28. package/dist/ListItem.js +9 -2
  29. package/dist/ListItem.mjs +1 -1
  30. package/dist/SheetSelect.d.mts +25 -0
  31. package/dist/SheetSelect.d.ts +25 -0
  32. package/dist/SheetSelect.js +440 -0
  33. package/dist/SheetSelect.mjs +9 -0
  34. package/dist/Textarea.mjs +1 -2
  35. package/dist/{chunk-M6ZXVBTK.mjs → chunk-6MKGPAR2.mjs} +21 -5
  36. package/dist/{chunk-EH745HE5.mjs → chunk-CZCQZHG6.mjs} +13 -4
  37. package/dist/{chunk-7QHVVCB3.mjs → chunk-FZZLPJ6B.mjs} +3 -0
  38. package/dist/{chunk-MAC465BB.mjs → chunk-JUXSWN54.mjs} +5 -3
  39. package/dist/{chunk-BNP626TY.mjs → chunk-OHBNABL5.mjs} +10 -3
  40. package/dist/chunk-URI2WBIV.mjs +147 -0
  41. package/dist/chunk-Y4GL2MHX.mjs +112 -0
  42. package/dist/{chunk-756RAKE4.mjs → chunk-ZUR7AU5R.mjs} +38 -20
  43. package/dist/fonts.d.mts +32 -0
  44. package/dist/fonts.d.ts +32 -0
  45. package/dist/fonts.js +44 -0
  46. package/dist/fonts.mjs +37 -0
  47. package/dist/index.d.mts +26 -1
  48. package/dist/index.d.ts +26 -1
  49. package/dist/index.js +425 -106
  50. package/dist/index.mjs +55 -17
  51. package/package.json +23 -6
  52. package/src/components/AlertBanner/AlertBanner.tsx +21 -3
  53. package/src/components/ConfirmDialog/ConfirmDialog.tsx +5 -0
  54. package/src/components/CurrencyInput/CurrencyInput.tsx +4 -0
  55. package/src/components/ImageUpload/ImageUpload.tsx +158 -0
  56. package/src/components/ImageUpload/index.ts +1 -0
  57. package/src/components/Input/Input.tsx +64 -53
  58. package/src/components/ListItem/ListItem.tsx +23 -4
  59. package/src/components/SheetSelect/SheetSelect.tsx +192 -0
  60. package/src/components/SheetSelect/index.ts +1 -0
  61. package/src/fonts.ts +30 -29
  62. package/src/hooks/useConfirmDialog.ts +67 -0
  63. package/src/index.ts +6 -0
  64. package/dist/chunk-26BCI223.mjs +0 -14
@@ -5,6 +5,8 @@ import {
5
5
  StyleSheet,
6
6
  ViewStyle,
7
7
  TextStyle,
8
+ Image,
9
+ ImageSourcePropType,
8
10
  } from 'react-native'
9
11
  import { Entypo } from '@expo/vector-icons'
10
12
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
@@ -17,6 +19,11 @@ import { PressableRow } from '../../utils/pressable'
17
19
  export type ListItemVariant = 'plain' | 'card'
18
20
 
19
21
  export interface ListItemProps {
22
+ /**
23
+ * Image source for the left slot. If provided, renders an Image (40×40, borderRadius 8).
24
+ * Takes precedence over `leftRender` and `leftIcon`.
25
+ */
26
+ imageSource?: ImageSourcePropType
20
27
  /**
21
28
  * Arbitrary content rendered on the left (avatar, icon, image, etc.).
22
29
  * Rendered inside a 44×44 aligned container.
@@ -72,6 +79,8 @@ export interface ListItemProps {
72
79
  titleStyle?: TextStyle
73
80
  /** Style applied to the subtitle Text. */
74
81
  subtitleStyle?: TextStyle
82
+ /** Max lines for the subtitle. Defaults to 2. */
83
+ subtitleNumberOfLines?: number
75
84
  /** Style applied to the caption Text. */
76
85
  captionStyle?: TextStyle
77
86
  /** Accessibility label override. Defaults to the title. */
@@ -79,6 +88,7 @@ export interface ListItemProps {
79
88
  }
80
89
 
81
90
  function ListItemBase({
91
+ imageSource,
82
92
  leftRender,
83
93
  rightRender,
84
94
  trailing,
@@ -98,6 +108,7 @@ function ListItemBase({
98
108
  style,
99
109
  titleStyle,
100
110
  subtitleStyle,
111
+ subtitleNumberOfLines = 2,
101
112
  captionStyle,
102
113
  accessibilityLabel,
103
114
  }: ListItemProps) {
@@ -108,9 +119,12 @@ function ListItemBase({
108
119
  onPress?.()
109
120
  }
110
121
 
111
- const effectiveLeft: React.ReactNode = leftIcon
112
- ? renderIcon(leftIcon, 24, leftIconColor ?? colors.foreground)
113
- : leftRender ?? icon
122
+ // imageSource takes precedence, then leftIcon, then leftRender/icon
123
+ const effectiveLeft: React.ReactNode = imageSource
124
+ ? <Image source={imageSource} style={styles.image} />
125
+ : leftIcon
126
+ ? renderIcon(leftIcon, 24, leftIconColor ?? colors.foreground)
127
+ : leftRender ?? icon
114
128
 
115
129
  const effectiveRight: React.ReactNode | string | undefined = rightIcon
116
130
  ? renderIcon(rightIcon, 24, rightIconColor ?? colors.foregroundMuted)
@@ -150,7 +164,7 @@ function ListItemBase({
150
164
  {subtitle ? (
151
165
  <Text
152
166
  style={[styles.subtitle, { color: colors.foregroundMuted }, subtitleStyle]}
153
- numberOfLines={2}
167
+ numberOfLines={subtitleNumberOfLines}
154
168
  allowFontScaling={true}
155
169
  >
156
170
  {subtitle}
@@ -238,6 +252,11 @@ const styles = StyleSheet.create({
238
252
  justifyContent: 'center',
239
253
  flexShrink: 0,
240
254
  },
255
+ image: {
256
+ width: s(40),
257
+ height: s(40),
258
+ borderRadius: 8,
259
+ },
241
260
  content: {
242
261
  flex: 1,
243
262
  gap: vs(4),
@@ -0,0 +1,192 @@
1
+ import React from 'react'
2
+ import { View, Text, ScrollView, StyleSheet, ViewStyle } from 'react-native'
3
+ import { EaseView } from 'react-native-ease'
4
+ import { selectionAsync as hapticSelection } from '../../utils/haptics'
5
+ import { useTheme } from '../../theme'
6
+ import { s, vs, ms, mvs } from '../../utils/scaling'
7
+ import { renderIcon } from '../../utils/icons'
8
+ import { COLOR_TRANSITION } from '../../utils/animations'
9
+ import { PressableChip } from '../../utils/pressable'
10
+ import { RADIUS } from '../../tokens'
11
+
12
+ export interface SheetSelectOption {
13
+ label: string
14
+ value: string | number
15
+ iconName?: string
16
+ disabled?: boolean
17
+ }
18
+
19
+ export interface SheetSelectProps {
20
+ options: SheetSelectOption[]
21
+ value?: string | number | (string | number)[]
22
+ onValueChange?: (value: string | number | (string | number)[]) => void
23
+ /** Allow multiple simultaneous selections. Defaults to false (radio). */
24
+ multiSelect?: boolean
25
+ label?: string
26
+ error?: string
27
+ /** Wrap chips into multiple rows instead of a single horizontal scroll. Defaults to false. */
28
+ wrap?: boolean
29
+ style?: ViewStyle
30
+ accessibilityLabel?: string
31
+ }
32
+
33
+ function SheetSelectChip({
34
+ option,
35
+ selected,
36
+ onPress,
37
+ }: {
38
+ option: SheetSelectOption
39
+ selected: boolean
40
+ onPress: () => void
41
+ }) {
42
+ const { colors } = useTheme()
43
+
44
+ const handlePress = () => {
45
+ hapticSelection()
46
+ onPress()
47
+ }
48
+
49
+ const iconColor = selected ? colors.primaryForeground : colors.foreground
50
+ const resolvedIcon = option.iconName ? renderIcon(option.iconName, ms(13), iconColor) : null
51
+
52
+ return (
53
+ <PressableChip
54
+ onPress={option.disabled ? undefined : handlePress}
55
+ rippleColor="transparent"
56
+ touchSoundDisabled
57
+ accessibilityRole="button"
58
+ accessibilityLabel={option.disabled ? `${option.label}, unavailable` : option.label}
59
+ accessibilityState={{ selected, disabled: option.disabled }}
60
+ >
61
+ <EaseView
62
+ style={[styles.chip, option.disabled && styles.chipDisabled]}
63
+ animate={{
64
+ backgroundColor: selected ? colors.primary : colors.surface,
65
+ borderColor: selected ? colors.primary : colors.border,
66
+ }}
67
+ transition={COLOR_TRANSITION}
68
+ >
69
+ {resolvedIcon ? <View style={styles.chipIcon}>{resolvedIcon}</View> : null}
70
+ <Text
71
+ style={[styles.chipLabel, { color: selected ? colors.primaryForeground : colors.foreground }]}
72
+ allowFontScaling={true}
73
+ >
74
+ {option.label}
75
+ </Text>
76
+ </EaseView>
77
+ </PressableChip>
78
+ )
79
+ }
80
+
81
+ export function SheetSelect({
82
+ options,
83
+ value,
84
+ onValueChange,
85
+ multiSelect = false,
86
+ label,
87
+ error,
88
+ wrap = false,
89
+ style,
90
+ accessibilityLabel,
91
+ }: SheetSelectProps) {
92
+ const { colors } = useTheme()
93
+
94
+ const isSelected = (optionValue: string | number): boolean => {
95
+ if (Array.isArray(value)) return value.includes(optionValue)
96
+ return optionValue === value
97
+ }
98
+
99
+ const handlePress = (optionValue: string | number) => {
100
+ if (!multiSelect) {
101
+ onValueChange?.(optionValue)
102
+ return
103
+ }
104
+ const currentArray = Array.isArray(value) ? value : value != null ? [value] : []
105
+ const alreadySelected = currentArray.includes(optionValue)
106
+ const newArray: (string | number)[] = alreadySelected
107
+ ? currentArray.filter((v) => v !== optionValue)
108
+ : [...currentArray, optionValue]
109
+ onValueChange?.(newArray)
110
+ }
111
+
112
+ const chips = options.map((opt) => (
113
+ <SheetSelectChip
114
+ key={opt.value}
115
+ option={opt}
116
+ selected={isSelected(opt.value)}
117
+ onPress={() => handlePress(opt.value)}
118
+ />
119
+ ))
120
+
121
+ return (
122
+ <View style={[styles.container, style]} accessibilityLabel={accessibilityLabel}>
123
+ {label ? (
124
+ <Text style={[styles.label, { color: colors.foreground }]} allowFontScaling={true}>
125
+ {label}
126
+ </Text>
127
+ ) : null}
128
+ {wrap ? (
129
+ <View style={styles.wrapContainer}>{chips}</View>
130
+ ) : (
131
+ <ScrollView
132
+ horizontal
133
+ showsHorizontalScrollIndicator={false}
134
+ contentContainerStyle={styles.scrollContent}
135
+ >
136
+ {chips}
137
+ </ScrollView>
138
+ )}
139
+ {error ? (
140
+ <Text style={[styles.error, { color: colors.destructive }]} allowFontScaling={true} accessibilityLiveRegion="polite">
141
+ {error}
142
+ </Text>
143
+ ) : null}
144
+ </View>
145
+ )
146
+ }
147
+
148
+ const styles = StyleSheet.create({
149
+ container: {
150
+ gap: vs(8),
151
+ },
152
+ label: {
153
+ fontFamily: 'Sohne-Medium',
154
+ fontSize: ms(14),
155
+ },
156
+ scrollContent: {
157
+ flexDirection: 'row',
158
+ gap: s(8),
159
+ },
160
+ wrapContainer: {
161
+ flexDirection: 'row',
162
+ flexWrap: 'wrap',
163
+ gap: s(8),
164
+ },
165
+ chip: {
166
+ borderRadius: RADIUS.full,
167
+ paddingHorizontal: s(14),
168
+ paddingVertical: vs(10),
169
+ minHeight: 44,
170
+ borderWidth: 1,
171
+ alignItems: 'center',
172
+ justifyContent: 'center',
173
+ flexDirection: 'row',
174
+ gap: s(5),
175
+ },
176
+ chipDisabled: {
177
+ opacity: 0.4,
178
+ },
179
+ chipIcon: {
180
+ alignItems: 'center',
181
+ justifyContent: 'center',
182
+ },
183
+ chipLabel: {
184
+ fontFamily: 'Sohne-Medium',
185
+ fontSize: ms(13),
186
+ lineHeight: mvs(18),
187
+ },
188
+ error: {
189
+ fontFamily: 'Sohne-Regular',
190
+ fontSize: ms(13),
191
+ },
192
+ })
@@ -0,0 +1 @@
1
+ export * from './SheetSelect'
package/src/fonts.ts CHANGED
@@ -14,38 +14,39 @@
14
14
  * }
15
15
  */
16
16
  // `.otf` assets resolve to a Metro asset module id (number) via require() at the
17
- // consumer's build time. Declared locally so the dts build does not depend on @types/node.
17
+ // consumer's build time. Paths are relative to dist/fonts.js (the compiled output).
18
+ // Both dist/ and src/ are published in the package, so ../src/assets/fonts/ resolves correctly.
18
19
  declare const require: (path: string) => number
19
20
 
20
21
  export const SohneFonts = {
21
22
  // Sohne base
22
- 'Sohne-ExtraLight': require('./assets/fonts/Sohne-ExtraLight.otf'),
23
- 'Sohne-ExtraLightItalic': require('./assets/fonts/Sohne-ExtraLightItalic.otf'),
24
- 'Sohne-Light': require('./assets/fonts/Sohne-Light.otf'),
25
- 'Sohne-LightItalic': require('./assets/fonts/Sohne-LightItalic.otf'),
26
- 'Sohne-Regular': require('./assets/fonts/Sohne-Regular.otf'),
27
- 'Sohne-Italic': require('./assets/fonts/Sohne-Italic.otf'),
28
- 'Sohne-Medium': require('./assets/fonts/Sohne-Medium.otf'),
29
- 'Sohne-MediumItalic': require('./assets/fonts/Sohne-MediumItalic.otf'),
30
- 'Sohne-SemiBold': require('./assets/fonts/Sohne-SemiBold.otf'),
31
- 'Sohne-SemiBoldItalic': require('./assets/fonts/Sohne-SemiBoldItalic.otf'),
32
- 'Sohne-Bold': require('./assets/fonts/Sohne-Bold.otf'),
33
- 'Sohne-BoldItalic': require('./assets/fonts/Sohne-BoldItalic.otf'),
34
- 'Sohne-ExtraBold': require('./assets/fonts/Sohne-ExtraBold.otf'),
35
- 'Sohne-ExtraBoldItalic': require('./assets/fonts/Sohne-ExtraBoldItalic.otf'),
23
+ 'Sohne-ExtraLight': require('../src/assets/fonts/Sohne-ExtraLight.otf'),
24
+ 'Sohne-ExtraLightItalic': require('../src/assets/fonts/Sohne-ExtraLightItalic.otf'),
25
+ 'Sohne-Light': require('../src/assets/fonts/Sohne-Light.otf'),
26
+ 'Sohne-LightItalic': require('../src/assets/fonts/Sohne-LightItalic.otf'),
27
+ 'Sohne-Regular': require('../src/assets/fonts/Sohne-Regular.otf'),
28
+ 'Sohne-Italic': require('../src/assets/fonts/Sohne-Italic.otf'),
29
+ 'Sohne-Medium': require('../src/assets/fonts/Sohne-Medium.otf'),
30
+ 'Sohne-MediumItalic': require('../src/assets/fonts/Sohne-MediumItalic.otf'),
31
+ 'Sohne-SemiBold': require('../src/assets/fonts/Sohne-SemiBold.otf'),
32
+ 'Sohne-SemiBoldItalic': require('../src/assets/fonts/Sohne-SemiBoldItalic.otf'),
33
+ 'Sohne-Bold': require('../src/assets/fonts/Sohne-Bold.otf'),
34
+ 'Sohne-BoldItalic': require('../src/assets/fonts/Sohne-BoldItalic.otf'),
35
+ 'Sohne-ExtraBold': require('../src/assets/fonts/Sohne-ExtraBold.otf'),
36
+ 'Sohne-ExtraBoldItalic': require('../src/assets/fonts/Sohne-ExtraBoldItalic.otf'),
36
37
  // SohneMono
37
- 'SohneMono-ExtraLight': require('./assets/fonts/SohneMono-ExtraLight.otf'),
38
- 'SohneMono-ExtraLightItalic': require('./assets/fonts/SohneMono-ExtraLightItalic.otf'),
39
- 'SohneMono-Light': require('./assets/fonts/SohneMono-Light.otf'),
40
- 'SohneMono-LightItalic': require('./assets/fonts/SohneMono-LightItalic.otf'),
41
- 'SohneMono-Regular': require('./assets/fonts/SohneMono-Regular.otf'),
42
- 'SohneMono-Italic': require('./assets/fonts/SohneMono-Italic.otf'),
43
- 'SohneMono-Medium': require('./assets/fonts/SohneMono-Medium.otf'),
44
- 'SohneMono-MediumItalic': require('./assets/fonts/SohneMono-MediumItalic.otf'),
45
- 'SohneMono-SemiBold': require('./assets/fonts/SohneMono-SemiBold.otf'),
46
- 'SohneMono-SemiBoldItalic': require('./assets/fonts/SohneMono-SemiBoldItalic.otf'),
47
- 'SohneMono-Bold': require('./assets/fonts/SohneMono-Bold.otf'),
48
- 'SohneMono-BoldItalic': require('./assets/fonts/SohneMono-BoldItalic.otf'),
49
- 'SohneMono-ExtraBold': require('./assets/fonts/SohneMono-ExtraBold.otf'),
50
- 'SohneMono-ExtraBoldItalic': require('./assets/fonts/SohneMono-ExtraBoldItalic.otf'),
38
+ 'SohneMono-ExtraLight': require('../src/assets/fonts/SohneMono-ExtraLight.otf'),
39
+ 'SohneMono-ExtraLightItalic': require('../src/assets/fonts/SohneMono-ExtraLightItalic.otf'),
40
+ 'SohneMono-Light': require('../src/assets/fonts/SohneMono-Light.otf'),
41
+ 'SohneMono-LightItalic': require('../src/assets/fonts/SohneMono-LightItalic.otf'),
42
+ 'SohneMono-Regular': require('../src/assets/fonts/SohneMono-Regular.otf'),
43
+ 'SohneMono-Italic': require('../src/assets/fonts/SohneMono-Italic.otf'),
44
+ 'SohneMono-Medium': require('../src/assets/fonts/SohneMono-Medium.otf'),
45
+ 'SohneMono-MediumItalic': require('../src/assets/fonts/SohneMono-MediumItalic.otf'),
46
+ 'SohneMono-SemiBold': require('../src/assets/fonts/SohneMono-SemiBold.otf'),
47
+ 'SohneMono-SemiBoldItalic': require('../src/assets/fonts/SohneMono-SemiBoldItalic.otf'),
48
+ 'SohneMono-Bold': require('../src/assets/fonts/SohneMono-Bold.otf'),
49
+ 'SohneMono-BoldItalic': require('../src/assets/fonts/SohneMono-BoldItalic.otf'),
50
+ 'SohneMono-ExtraBold': require('../src/assets/fonts/SohneMono-ExtraBold.otf'),
51
+ 'SohneMono-ExtraBoldItalic': require('../src/assets/fonts/SohneMono-ExtraBoldItalic.otf'),
51
52
  } as const
@@ -0,0 +1,67 @@
1
+ import { useState, useCallback } from 'react'
2
+
3
+ export interface UseConfirmDialogOptions {
4
+ onConfirm: () => void | Promise<void>
5
+ onCancel?: () => void
6
+ }
7
+
8
+ export interface UseConfirmDialogResult<T> {
9
+ /** Pass to ConfirmDialog `visible` prop. */
10
+ visible: boolean
11
+ /** The value passed to `open()` — available during the confirmation flow. */
12
+ target: T | null
13
+ /** Whether `onConfirm` is currently executing. Pass to ConfirmDialog `loading` prop. */
14
+ loading: boolean
15
+ /** Open the dialog, optionally with an associated value (e.g. the item to delete). */
16
+ open: (target?: T) => void
17
+ /** Handlers to pass directly to ConfirmDialog. */
18
+ dialogProps: {
19
+ visible: boolean
20
+ loading: boolean
21
+ onConfirm: () => void
22
+ onCancel: () => void
23
+ }
24
+ }
25
+
26
+ export function useConfirmDialog<T = undefined>(
27
+ options: UseConfirmDialogOptions,
28
+ ): UseConfirmDialogResult<T> {
29
+ const [visible, setVisible] = useState(false)
30
+ const [target, setTarget] = useState<T | null>(null)
31
+ const [loading, setLoading] = useState(false)
32
+
33
+ const open = useCallback((t?: T) => {
34
+ setTarget(t ?? null)
35
+ setVisible(true)
36
+ }, [])
37
+
38
+ const handleConfirm = useCallback(async () => {
39
+ setLoading(true)
40
+ try {
41
+ await options.onConfirm()
42
+ } finally {
43
+ setLoading(false)
44
+ setVisible(false)
45
+ setTarget(null)
46
+ }
47
+ }, [options])
48
+
49
+ const handleCancel = useCallback(() => {
50
+ setVisible(false)
51
+ setTarget(null)
52
+ options.onCancel?.()
53
+ }, [options])
54
+
55
+ return {
56
+ visible,
57
+ target,
58
+ loading,
59
+ open,
60
+ dialogProps: {
61
+ visible,
62
+ loading,
63
+ onConfirm: handleConfirm,
64
+ onCancel: handleCancel,
65
+ },
66
+ }
67
+ }
package/src/index.ts CHANGED
@@ -53,6 +53,8 @@ export * from './components/SelectableGrid'
53
53
  export * from './components/PricingCard'
54
54
  export * from './components/TabBar'
55
55
  export * from './components/ImageViewer'
56
+ export * from './components/SheetSelect'
57
+ export * from './components/ImageUpload'
56
58
  // HolographicCard is intentionally NOT re-exported here — it depends on the
57
59
  // optional peer @shopify/react-native-skia, so it must stay out of the main
58
60
  // barrel's module graph. Deep-import it: '@retray-dev/ui-kit/HolographicCard'.
@@ -76,6 +78,10 @@ export {
76
78
  richHaptics,
77
79
  } from './utils/haptics'
78
80
 
81
+ // Hooks
82
+ export { useConfirmDialog } from './hooks/useConfirmDialog'
83
+ export type { UseConfirmDialogOptions, UseConfirmDialogResult } from './hooks/useConfirmDialog'
84
+
79
85
  // Design tokens
80
86
  export {
81
87
  SPACING,
@@ -1,14 +0,0 @@
1
- import { TIMINGS, EASINGS } from './chunk-DVK4G2GT.mjs';
2
- import { useEffect } from 'react';
3
- import { useSharedValue, withTiming } from 'react-native-reanimated';
4
-
5
- function useColorTransition(active, options = {}) {
6
- const { duration = TIMINGS.state.duration } = options;
7
- const progress = useSharedValue(active ? 1 : 0);
8
- useEffect(() => {
9
- progress.value = withTiming(active ? 1 : 0, { duration, easing: EASINGS.standard });
10
- }, [active, duration, progress]);
11
- return progress;
12
- }
13
-
14
- export { useColorTransition };