@retray-dev/ui-kit 10.0.0 → 10.2.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.
- package/COMPONENTS.md +150 -17
- package/CONSUMER.md +1 -1
- package/README.md +4 -4
- package/dist/Accordion.d.mts +1 -1
- package/dist/Accordion.d.ts +1 -1
- package/dist/Accordion.js +3 -3
- package/dist/Accordion.mjs +2 -2
- package/dist/AlertBanner.js +1 -1
- package/dist/AlertBanner.mjs +2 -2
- package/dist/AppHeader.js +1 -1
- package/dist/AppHeader.mjs +3 -3
- package/dist/Badge.js +1 -1
- package/dist/Badge.mjs +2 -2
- package/dist/Button.js +1 -1
- package/dist/Button.mjs +2 -2
- package/dist/CategoryStrip.js +1 -1
- package/dist/CategoryStrip.mjs +2 -2
- package/dist/Chip.js +1 -1
- package/dist/Chip.mjs +2 -2
- package/dist/ConfirmDialog.d.mts +6 -1
- package/dist/ConfirmDialog.d.ts +6 -1
- package/dist/ConfirmDialog.js +45 -15
- package/dist/ConfirmDialog.mjs +3 -3
- package/dist/CurrencyInput.js +1 -1
- package/dist/CurrencyInput.mjs +3 -3
- package/dist/DetailRow.d.mts +1 -1
- package/dist/DetailRow.d.ts +1 -1
- package/dist/DetailRow.js +1 -1
- package/dist/DetailRow.mjs +2 -2
- package/dist/EmptyState.js +1 -1
- package/dist/EmptyState.mjs +3 -3
- package/dist/ErrorBoundary.js +1 -1
- package/dist/ErrorBoundary.mjs +2 -2
- package/dist/IconButton.js +1 -1
- package/dist/IconButton.mjs +2 -2
- package/dist/IconPicker.d.mts +17 -0
- package/dist/IconPicker.d.ts +17 -0
- package/dist/IconPicker.js +997 -0
- package/dist/IconPicker.mjs +7 -0
- package/dist/ImageUpload.d.mts +3 -1
- package/dist/ImageUpload.d.ts +3 -1
- package/dist/ImageUpload.js +28 -10
- package/dist/ImageUpload.mjs +1 -1
- package/dist/ImageViewer.js +282 -141
- package/dist/ImageViewer.mjs +5 -3
- package/dist/Input.js +1 -1
- package/dist/Input.mjs +2 -2
- package/dist/LabelValue.js +1 -1
- package/dist/LabelValue.mjs +2 -2
- package/dist/ListItem.js +1 -1
- package/dist/ListItem.mjs +2 -2
- package/dist/MediaCard.js +1 -1
- package/dist/MediaCard.mjs +2 -2
- package/dist/MenuItem.js +1 -1
- package/dist/MenuItem.mjs +2 -2
- package/dist/NumberStepper.d.mts +19 -0
- package/dist/NumberStepper.d.ts +19 -0
- package/dist/NumberStepper.js +410 -0
- package/dist/NumberStepper.mjs +9 -0
- package/dist/PagerDots.js +1 -1
- package/dist/PagerDots.mjs +2 -2
- package/dist/PricingCard.js +1 -1
- package/dist/PricingCard.mjs +4 -4
- package/dist/SelectableGrid.js +1 -1
- package/dist/SelectableGrid.mjs +2 -2
- package/dist/Sheet.js +16 -13
- package/dist/Sheet.mjs +1 -1
- package/dist/SheetSelect.js +1 -1
- package/dist/SheetSelect.mjs +2 -2
- package/dist/Switch.js +40 -17
- package/dist/Switch.mjs +1 -1
- package/dist/TabBar.js +1 -1
- package/dist/TabBar.mjs +2 -2
- package/dist/Textarea.js +1 -1
- package/dist/Textarea.mjs +2 -2
- package/dist/Toggle.js +1 -1
- package/dist/Toggle.mjs +2 -2
- package/dist/chunk-53Z3NYGE.mjs +742 -0
- package/dist/{chunk-VQ57HWPL.mjs → chunk-6L4G6PBT.mjs} +1 -1
- package/dist/{chunk-6OAZJ577.mjs → chunk-6SECQ2ZF.mjs} +2 -2
- package/dist/{chunk-KIHCWCWL.mjs → chunk-7LWRKMF5.mjs} +1 -1
- package/dist/{chunk-4I7D47FH.mjs → chunk-AJRVDP2H.mjs} +3 -3
- package/dist/{chunk-6MKGPAR2.mjs → chunk-BEMIQXXU.mjs} +1 -1
- package/dist/chunk-BUMAMSTZ.mjs +126 -0
- package/dist/{chunk-UREA2GYY.mjs → chunk-DYT7BG5I.mjs} +1 -1
- package/dist/{chunk-Z4BVUWW6.mjs → chunk-ELXBDILQ.mjs} +20 -32
- package/dist/{chunk-A4MDAP7G.mjs → chunk-FCSSQK3L.mjs} +1 -1
- package/dist/{chunk-2TFTAWVJ.mjs → chunk-HTHGSXFG.mjs} +1 -1
- package/dist/{chunk-VGTDN7SW.mjs → chunk-IX3NYLYQ.mjs} +1 -1
- package/dist/{chunk-T7XZ7H7Y.mjs → chunk-KA7LTET3.mjs} +17 -3
- package/dist/{chunk-URI2WBIV.mjs → chunk-KOO4WITD.mjs} +1 -1
- package/dist/{chunk-JUXSWN54.mjs → chunk-NMU5FMQJ.mjs} +1 -1
- package/dist/{chunk-LXJIIOYQ.mjs → chunk-RYZC432S.mjs} +1 -1
- package/dist/{chunk-JB67UOB5.mjs → chunk-S2R7UVOE.mjs} +1 -1
- package/dist/{chunk-ZUR7AU5R.mjs → chunk-SXLKNTA4.mjs} +1 -1
- package/dist/{chunk-3U4SSNWP.mjs → chunk-T2KCAHOS.mjs} +1 -1
- package/dist/{chunk-ZJKGQMYH.mjs → chunk-TB6SD2FT.mjs} +1 -1
- package/dist/{chunk-AZJF2BLK.mjs → chunk-TBNZHU6C.mjs} +1 -1
- package/dist/{chunk-Y4GL2MHX.mjs → chunk-TZDGAP5N.mjs} +28 -10
- package/dist/{chunk-CZCQZHG6.mjs → chunk-U2XJFYED.mjs} +1 -1
- package/dist/{chunk-TERDKCLE.mjs → chunk-VF2ATYN3.mjs} +1 -1
- package/dist/{chunk-OHBNABL5.mjs → chunk-VKID2D2I.mjs} +1 -1
- package/dist/{chunk-QKH5ZOD5.mjs → chunk-WF2XDFRK.mjs} +40 -17
- package/dist/{chunk-FZZLPJ6B.mjs → chunk-WYEUNUTP.mjs} +44 -15
- package/dist/{chunk-PFZTM6D5.mjs → chunk-Y2NS74WS.mjs} +9 -7
- package/dist/{chunk-O3HA6TYM.mjs → chunk-YJ7I257J.mjs} +3 -3
- package/dist/{chunk-NA7PARID.mjs → chunk-Z4VHZ7B5.mjs} +1 -1
- package/dist/{chunk-MLF3EZFW.mjs → chunk-Z6SFHN6T.mjs} +1 -1
- package/dist/{chunk-4K625MVM.mjs → chunk-ZZ2R6KZ3.mjs} +1 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1011 -88
- package/dist/index.mjs +34 -32
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.tsx +7 -3
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +61 -23
- package/src/components/DetailRow/DetailRow.tsx +1 -1
- package/src/components/IconPicker/IconPicker.tsx +383 -0
- package/src/components/IconPicker/index.ts +1 -0
- package/src/components/ImageUpload/ImageUpload.tsx +34 -12
- package/src/components/ImageViewer/ImageViewer.tsx +25 -30
- package/src/components/NumberStepper/NumberStepper.tsx +147 -0
- package/src/components/NumberStepper/index.ts +1 -0
- package/src/components/Sheet/Sheet.tsx +10 -9
- package/src/components/Switch/Switch.tsx +30 -17
- package/src/index.ts +3 -1
- package/src/utils/curatedIcons.ts +286 -0
- package/src/utils/icons.ts +20 -2
|
@@ -17,6 +17,8 @@ export interface ImageUploadProps {
|
|
|
17
17
|
loading?: boolean
|
|
18
18
|
/** Text shown when no image is selected. */
|
|
19
19
|
placeholder?: string
|
|
20
|
+
/** Whether to show the placeholder text. Use false for compact/avatar variants. */
|
|
21
|
+
showPlaceholderText?: boolean
|
|
20
22
|
/** Width of the upload area. Defaults to full width (undefined). */
|
|
21
23
|
width?: number
|
|
22
24
|
/** Height of the upload area. Defaults to 200. */
|
|
@@ -35,6 +37,7 @@ export function ImageUpload({
|
|
|
35
37
|
onChange,
|
|
36
38
|
loading = false,
|
|
37
39
|
placeholder = 'Tap to add image',
|
|
40
|
+
showPlaceholderText = true,
|
|
38
41
|
width,
|
|
39
42
|
height = 200,
|
|
40
43
|
borderRadius = RADIUS.lg,
|
|
@@ -50,27 +53,39 @@ export function ImageUpload({
|
|
|
50
53
|
impactLight()
|
|
51
54
|
|
|
52
55
|
// Dynamic import so expo-image-picker is optional at module load time.
|
|
53
|
-
//
|
|
54
|
-
|
|
56
|
+
// Import ExponentImagePicker (the raw native module proxy) directly to
|
|
57
|
+
// avoid expo-image-picker's top-level createPermissionHook dependency.
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
let picker: any
|
|
55
60
|
try {
|
|
56
|
-
|
|
61
|
+
const mod = await import('expo-image-picker/build/ExponentImagePicker')
|
|
62
|
+
picker = (mod as { default: unknown }).default
|
|
57
63
|
} catch {
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
// Fallback: try the main module
|
|
65
|
+
try {
|
|
66
|
+
picker = await import('expo-image-picker')
|
|
67
|
+
} catch {
|
|
68
|
+
if (__DEV__) console.warn('[ImageUpload] expo-image-picker not installed.')
|
|
69
|
+
return
|
|
70
|
+
}
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
if (Platform.OS !== 'web') {
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
try {
|
|
75
|
+
const { status } = await picker.requestMediaLibraryPermissionsAsync()
|
|
76
|
+
if (status !== 'granted') return
|
|
77
|
+
} catch {
|
|
78
|
+
// Permission check failed — try picker anyway
|
|
79
|
+
}
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
const result = await
|
|
82
|
+
const result = await picker.launchImageLibraryAsync({
|
|
68
83
|
mediaTypes: ['images'],
|
|
69
84
|
allowsEditing: true,
|
|
70
85
|
quality: 0.8,
|
|
71
86
|
})
|
|
72
87
|
|
|
73
|
-
if (!result.canceled && result.assets[0]) {
|
|
88
|
+
if (!result.canceled && result.assets?.[0]) {
|
|
74
89
|
onChange?.(result.assets[0].uri)
|
|
75
90
|
}
|
|
76
91
|
}
|
|
@@ -106,9 +121,15 @@ export function ImageUpload({
|
|
|
106
121
|
) : (
|
|
107
122
|
<View style={styles.placeholder}>
|
|
108
123
|
<Feather name="image" size={ms(28)} color={colors.foregroundMuted} />
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
124
|
+
{showPlaceholderText ? (
|
|
125
|
+
<Text
|
|
126
|
+
style={[styles.placeholderText, { color: colors.foregroundMuted }]}
|
|
127
|
+
numberOfLines={1}
|
|
128
|
+
allowFontScaling={true}
|
|
129
|
+
>
|
|
130
|
+
{placeholder}
|
|
131
|
+
</Text>
|
|
132
|
+
) : null}
|
|
112
133
|
</View>
|
|
113
134
|
)}
|
|
114
135
|
{loading ? (
|
|
@@ -137,6 +158,7 @@ const styles = StyleSheet.create({
|
|
|
137
158
|
placeholderText: {
|
|
138
159
|
fontFamily: 'Sohne-Regular',
|
|
139
160
|
fontSize: ms(13),
|
|
161
|
+
textAlign: 'center',
|
|
140
162
|
},
|
|
141
163
|
loadingOverlay: {
|
|
142
164
|
...StyleSheet.absoluteFillObject,
|
|
@@ -2,7 +2,8 @@ import React, { useState, useCallback } from 'react'
|
|
|
2
2
|
import {
|
|
3
3
|
Modal,
|
|
4
4
|
View,
|
|
5
|
-
|
|
5
|
+
Image,
|
|
6
|
+
Dimensions,
|
|
6
7
|
StyleSheet,
|
|
7
8
|
useWindowDimensions,
|
|
8
9
|
ImageSourcePropType,
|
|
@@ -18,7 +19,7 @@ import Animated, {
|
|
|
18
19
|
runOnJS,
|
|
19
20
|
} from 'react-native-reanimated'
|
|
20
21
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
21
|
-
import {
|
|
22
|
+
import { IconButton } from '../IconButton'
|
|
22
23
|
import { PagerDots } from '../PagerDots'
|
|
23
24
|
import { s, vs } from '../../utils/scaling'
|
|
24
25
|
|
|
@@ -102,13 +103,11 @@ function ZoomableImage({ source, width, height, onZoomChange }: ZoomableImagePro
|
|
|
102
103
|
|
|
103
104
|
return (
|
|
104
105
|
<GestureDetector gesture={composed}>
|
|
105
|
-
<
|
|
106
|
-
<Animated.
|
|
107
|
-
source={source}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/>
|
|
111
|
-
</Animated.View>
|
|
106
|
+
<View style={[{ width, height }, styles.imageWrap]} collapsable={false}>
|
|
107
|
+
<Animated.View style={[{ width, height }, animatedStyle]}>
|
|
108
|
+
<Image source={source} style={{ width, height }} resizeMode="contain" />
|
|
109
|
+
</Animated.View>
|
|
110
|
+
</View>
|
|
112
111
|
</GestureDetector>
|
|
113
112
|
)
|
|
114
113
|
}
|
|
@@ -132,7 +131,9 @@ export interface ImageViewerProps {
|
|
|
132
131
|
* <ImageViewer images={pages} visible={open} initialIndex={page} onClose={() => setOpen(false)} />
|
|
133
132
|
*/
|
|
134
133
|
export function ImageViewer({ images, visible, onClose, initialIndex = 0 }: ImageViewerProps) {
|
|
135
|
-
const
|
|
134
|
+
const window = useWindowDimensions()
|
|
135
|
+
const width = window.width > 0 ? window.width : Dimensions.get('window').width
|
|
136
|
+
const height = window.height > 0 ? window.height : Dimensions.get('window').height
|
|
136
137
|
const insets = useSafeAreaInsets()
|
|
137
138
|
const [index, setIndex] = useState(initialIndex)
|
|
138
139
|
const [pagingEnabled, setPagingEnabled] = useState(true)
|
|
@@ -203,7 +204,7 @@ export function ImageViewer({ images, visible, onClose, initialIndex = 0 }: Imag
|
|
|
203
204
|
<Animated.View style={[styles.backdrop, backdropStyle]} pointerEvents="none" />
|
|
204
205
|
<Animated.View style={[styles.container, dismissStyle]}>
|
|
205
206
|
<GestureDetector gesture={swipeDown}>
|
|
206
|
-
<
|
|
207
|
+
<View style={styles.root} collapsable={false}>
|
|
207
208
|
<ScrollView
|
|
208
209
|
ref={scrollRef}
|
|
209
210
|
horizontal
|
|
@@ -223,20 +224,20 @@ export function ImageViewer({ images, visible, onClose, initialIndex = 0 }: Imag
|
|
|
223
224
|
/>
|
|
224
225
|
))}
|
|
225
226
|
</ScrollView>
|
|
226
|
-
</
|
|
227
|
+
</View>
|
|
227
228
|
</GestureDetector>
|
|
228
229
|
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
</
|
|
230
|
+
<View style={[styles.closeButtonWrapper, { top: insets.top + vs(8) }]}>
|
|
231
|
+
<IconButton
|
|
232
|
+
iconName="x"
|
|
233
|
+
size="md"
|
|
234
|
+
variant="text"
|
|
235
|
+
style={{ backgroundColor: 'rgba(255,255,255,0.18)' }}
|
|
236
|
+
iconColor="#fff"
|
|
237
|
+
onPress={onClose}
|
|
238
|
+
accessibilityLabel="Close"
|
|
239
|
+
/>
|
|
240
|
+
</View>
|
|
240
241
|
|
|
241
242
|
{images.length > 1 ? (
|
|
242
243
|
<View style={[styles.dots, { bottom: insets.bottom + vs(16) }]} pointerEvents="box-none">
|
|
@@ -271,15 +272,9 @@ const styles = StyleSheet.create({
|
|
|
271
272
|
justifyContent: 'center',
|
|
272
273
|
overflow: 'hidden',
|
|
273
274
|
},
|
|
274
|
-
|
|
275
|
+
closeButtonWrapper: {
|
|
275
276
|
position: 'absolute',
|
|
276
277
|
right: s(12),
|
|
277
|
-
width: s(40),
|
|
278
|
-
height: s(40),
|
|
279
|
-
borderRadius: s(20),
|
|
280
|
-
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
281
|
-
alignItems: 'center',
|
|
282
|
-
justifyContent: 'center',
|
|
283
278
|
},
|
|
284
279
|
dots: {
|
|
285
280
|
position: 'absolute',
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
+
import { impactLight } from '../../utils/haptics'
|
|
4
|
+
import { useTheme } from '../../theme'
|
|
5
|
+
import { s, ms, mvs } from '../../utils/scaling'
|
|
6
|
+
import { renderIcon } from '../../utils/icons'
|
|
7
|
+
import { RADIUS } from '../../tokens'
|
|
8
|
+
import { PressableButton } from '../../utils/pressable'
|
|
9
|
+
|
|
10
|
+
export type NumberStepperSize = 'sm' | 'md' | 'lg'
|
|
11
|
+
|
|
12
|
+
export interface NumberStepperProps {
|
|
13
|
+
value: number
|
|
14
|
+
onValueChange: (value: number) => void
|
|
15
|
+
min?: number
|
|
16
|
+
max?: number
|
|
17
|
+
step?: number
|
|
18
|
+
size?: NumberStepperSize
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
style?: ViewStyle
|
|
21
|
+
accessibilityLabel?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sizeConfig: Record<NumberStepperSize, { button: number; icon: number; valueFontSize: number; valueLineHeight: number; valueMinWidth: number }> = {
|
|
25
|
+
sm: { button: s(40), icon: 16, valueFontSize: ms(18), valueLineHeight: mvs(24), valueMinWidth: s(32) },
|
|
26
|
+
md: { button: s(44), icon: 18, valueFontSize: ms(22), valueLineHeight: mvs(28), valueMinWidth: s(36) },
|
|
27
|
+
lg: { button: s(52), icon: 22, valueFontSize: ms(26), valueLineHeight: mvs(32), valueMinWidth: s(40) },
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function NumberStepperBase({
|
|
31
|
+
value,
|
|
32
|
+
onValueChange,
|
|
33
|
+
min = 1,
|
|
34
|
+
max = 99,
|
|
35
|
+
step = 1,
|
|
36
|
+
size = 'md',
|
|
37
|
+
disabled = false,
|
|
38
|
+
style,
|
|
39
|
+
accessibilityLabel,
|
|
40
|
+
}: NumberStepperProps) {
|
|
41
|
+
const { colors } = useTheme()
|
|
42
|
+
|
|
43
|
+
const canDecrement = value > min && !disabled
|
|
44
|
+
const canIncrement = value < max && !disabled
|
|
45
|
+
|
|
46
|
+
const handleDecrement = () => {
|
|
47
|
+
if (!canDecrement) return
|
|
48
|
+
impactLight()
|
|
49
|
+
onValueChange(Math.max(min, value - step))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const handleIncrement = () => {
|
|
53
|
+
if (!canIncrement) return
|
|
54
|
+
impactLight()
|
|
55
|
+
onValueChange(Math.min(max, value + step))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { button: buttonSize, icon: iconSize, valueFontSize, valueLineHeight, valueMinWidth } = sizeConfig[size]
|
|
59
|
+
|
|
60
|
+
const displayValue = String(value)
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<View style={[styles.container, style]}>
|
|
64
|
+
<PressableButton
|
|
65
|
+
style={[
|
|
66
|
+
styles.button,
|
|
67
|
+
{
|
|
68
|
+
width: buttonSize,
|
|
69
|
+
height: buttonSize,
|
|
70
|
+
backgroundColor: colors.surface,
|
|
71
|
+
borderColor: colors.border,
|
|
72
|
+
},
|
|
73
|
+
!canDecrement && styles.buttonDisabled,
|
|
74
|
+
]}
|
|
75
|
+
enabled={canDecrement}
|
|
76
|
+
onPress={handleDecrement}
|
|
77
|
+
rippleColor="transparent"
|
|
78
|
+
touchSoundDisabled
|
|
79
|
+
accessibilityRole="button"
|
|
80
|
+
accessibilityLabel={`Decrease, current value ${displayValue}`}
|
|
81
|
+
accessibilityState={{ disabled: !canDecrement }}
|
|
82
|
+
>
|
|
83
|
+
{renderIcon('minus', iconSize, canDecrement ? colors.foreground : colors.foregroundMuted)}
|
|
84
|
+
</PressableButton>
|
|
85
|
+
<Text
|
|
86
|
+
style={[
|
|
87
|
+
styles.value,
|
|
88
|
+
{
|
|
89
|
+
color: colors.foreground,
|
|
90
|
+
fontSize: valueFontSize,
|
|
91
|
+
lineHeight: valueLineHeight,
|
|
92
|
+
minWidth: valueMinWidth,
|
|
93
|
+
},
|
|
94
|
+
]}
|
|
95
|
+
allowFontScaling={true}
|
|
96
|
+
accessibilityLabel={accessibilityLabel ?? `Quantity: ${displayValue}`}
|
|
97
|
+
accessibilityRole="text"
|
|
98
|
+
>
|
|
99
|
+
{displayValue}
|
|
100
|
+
</Text>
|
|
101
|
+
<PressableButton
|
|
102
|
+
style={[
|
|
103
|
+
styles.button,
|
|
104
|
+
{
|
|
105
|
+
width: buttonSize,
|
|
106
|
+
height: buttonSize,
|
|
107
|
+
backgroundColor: colors.surface,
|
|
108
|
+
borderColor: colors.border,
|
|
109
|
+
},
|
|
110
|
+
!canIncrement && styles.buttonDisabled,
|
|
111
|
+
]}
|
|
112
|
+
enabled={canIncrement}
|
|
113
|
+
onPress={handleIncrement}
|
|
114
|
+
rippleColor="transparent"
|
|
115
|
+
touchSoundDisabled
|
|
116
|
+
accessibilityRole="button"
|
|
117
|
+
accessibilityLabel={`Increase, current value ${displayValue}`}
|
|
118
|
+
accessibilityState={{ disabled: !canIncrement }}
|
|
119
|
+
>
|
|
120
|
+
{renderIcon('plus', iconSize, canIncrement ? colors.foreground : colors.foregroundMuted)}
|
|
121
|
+
</PressableButton>
|
|
122
|
+
</View>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const NumberStepper = React.memo(NumberStepperBase)
|
|
127
|
+
|
|
128
|
+
const styles = StyleSheet.create({
|
|
129
|
+
container: {
|
|
130
|
+
flexDirection: 'row',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
gap: s(12),
|
|
133
|
+
},
|
|
134
|
+
button: {
|
|
135
|
+
borderRadius: RADIUS.md,
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
justifyContent: 'center',
|
|
138
|
+
borderWidth: 1.5,
|
|
139
|
+
},
|
|
140
|
+
buttonDisabled: {
|
|
141
|
+
opacity: 0.35,
|
|
142
|
+
},
|
|
143
|
+
value: {
|
|
144
|
+
fontFamily: 'Sohne-Medium',
|
|
145
|
+
textAlign: 'center',
|
|
146
|
+
},
|
|
147
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './NumberStepper'
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef } from 'react'
|
|
2
2
|
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle, Dimensions, Platform, Modal, ScrollView, useWindowDimensions, Pressable } from 'react-native'
|
|
3
|
-
import {
|
|
4
|
-
BottomSheetModal,
|
|
3
|
+
import BottomSheet, {
|
|
5
4
|
BottomSheetView,
|
|
6
5
|
BottomSheetScrollView,
|
|
7
6
|
BottomSheetBackdrop,
|
|
@@ -149,7 +148,7 @@ export function Sheet({
|
|
|
149
148
|
const { colors } = useTheme()
|
|
150
149
|
const insets = useSafeAreaInsets()
|
|
151
150
|
const { width: windowWidth } = useWindowDimensions()
|
|
152
|
-
const ref = useRef<
|
|
151
|
+
const ref = useRef<BottomSheet>(null)
|
|
153
152
|
const asDialog = responsive && windowWidth >= BREAKPOINTS.wide
|
|
154
153
|
|
|
155
154
|
// 'interactive' + 'adjustPan' works properly with enableDynamicSizing on both platforms
|
|
@@ -159,9 +158,9 @@ export function Sheet({
|
|
|
159
158
|
useEffect(() => {
|
|
160
159
|
if (open) {
|
|
161
160
|
impactMedium()
|
|
162
|
-
ref.current?.
|
|
161
|
+
ref.current?.snapToIndex(0)
|
|
163
162
|
} else {
|
|
164
|
-
ref.current?.
|
|
163
|
+
ref.current?.close()
|
|
165
164
|
}
|
|
166
165
|
}, [open])
|
|
167
166
|
|
|
@@ -193,7 +192,7 @@ export function Sheet({
|
|
|
193
192
|
const showHeader = !!(title || effectiveSubtitle || showCloseButton) && !customHeader
|
|
194
193
|
|
|
195
194
|
const headerNode = customHeader ? customHeader : (showHeader ? (
|
|
196
|
-
<View style={styles.header} accessibilityRole="header">
|
|
195
|
+
<View style={[styles.header, { backgroundColor: colors.card }]} accessibilityRole="header">
|
|
197
196
|
<View style={styles.headerRow}>
|
|
198
197
|
{title ? (
|
|
199
198
|
<Text style={[styles.title, { color: colors.foreground }]} allowFontScaling={true}>
|
|
@@ -270,12 +269,13 @@ export function Sheet({
|
|
|
270
269
|
const useDynamicSizing = !snapPoints
|
|
271
270
|
|
|
272
271
|
return (
|
|
273
|
-
<
|
|
272
|
+
<BottomSheet
|
|
274
273
|
ref={ref}
|
|
274
|
+
index={-1}
|
|
275
|
+
onClose={onClose}
|
|
275
276
|
enableDynamicSizing={useDynamicSizing}
|
|
276
277
|
snapPoints={snapPoints}
|
|
277
278
|
maxDynamicContentSize={useDynamicSizing ? effectiveMaxHeight : undefined}
|
|
278
|
-
onDismiss={onClose}
|
|
279
279
|
backdropComponent={renderBackdrop}
|
|
280
280
|
footerComponent={effectiveFooter ? renderFooter : undefined}
|
|
281
281
|
backgroundStyle={[styles.background, { backgroundColor: colors.card }]}
|
|
@@ -297,6 +297,7 @@ export function Sheet({
|
|
|
297
297
|
showsVerticalScrollIndicator={true}
|
|
298
298
|
indicatorStyle="black"
|
|
299
299
|
persistentScrollbar={isAndroid}
|
|
300
|
+
stickyHeaderIndices={headerNode ? [0] : undefined}
|
|
300
301
|
>
|
|
301
302
|
{headerNode}
|
|
302
303
|
{contentNode}
|
|
@@ -307,7 +308,7 @@ export function Sheet({
|
|
|
307
308
|
{contentNode}
|
|
308
309
|
</BottomSheetView>
|
|
309
310
|
)}
|
|
310
|
-
</
|
|
311
|
+
</BottomSheet>
|
|
311
312
|
)
|
|
312
313
|
}
|
|
313
314
|
|
|
@@ -14,6 +14,8 @@ const THUMB_OFFSET = s(3)
|
|
|
14
14
|
const THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2
|
|
15
15
|
const ICON_SIZE = s(13)
|
|
16
16
|
|
|
17
|
+
const DISABLED_OPACITY = 0.45
|
|
18
|
+
|
|
17
19
|
export interface SwitchProps {
|
|
18
20
|
checked?: boolean
|
|
19
21
|
onCheckedChange?: (checked: boolean) => void
|
|
@@ -24,9 +26,10 @@ export interface SwitchProps {
|
|
|
24
26
|
|
|
25
27
|
export function Switch({ checked = false, onCheckedChange, disabled, style, accessibilityLabel }: SwitchProps) {
|
|
26
28
|
const { colors } = useTheme()
|
|
29
|
+
const isDisabled = !!disabled
|
|
27
30
|
|
|
28
31
|
return (
|
|
29
|
-
<View style={[{
|
|
32
|
+
<View style={[{ alignSelf: 'flex-start' }, style]}>
|
|
30
33
|
<TouchableOpacity
|
|
31
34
|
onPress={() => {
|
|
32
35
|
hapticSelection()
|
|
@@ -37,20 +40,15 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
|
|
|
37
40
|
touchSoundDisabled={true}
|
|
38
41
|
accessibilityRole="switch"
|
|
39
42
|
accessibilityLabel={accessibilityLabel}
|
|
40
|
-
accessibilityState={{ checked, disabled:
|
|
43
|
+
accessibilityState={{ checked, disabled: isDisabled }}
|
|
41
44
|
style={styles.touchable}
|
|
42
45
|
>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
AUDIT FIX: the off-state track used surfaceStrong (~#ebebeb in light mode)
|
|
50
|
-
with no border — nearly invisible on white page/card surfaces. A 1.5px border
|
|
51
|
-
that fades out as the track fills gives the off state clear visual definition
|
|
52
|
-
without adding visual weight to the on state.
|
|
53
|
-
*/}
|
|
46
|
+
<View style={styles.trackContainer}>
|
|
47
|
+
<EaseView
|
|
48
|
+
style={[styles.track, isDisabled && styles.disabledTrack]}
|
|
49
|
+
animate={{ backgroundColor: checked ? colors.primary : colors.surfaceStrong }}
|
|
50
|
+
transition={COLOR_TRANSITION}
|
|
51
|
+
/>
|
|
54
52
|
<EaseView
|
|
55
53
|
style={[styles.trackBorder, { borderWidth: 1.5 }]}
|
|
56
54
|
pointerEvents="none"
|
|
@@ -62,14 +60,22 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
|
|
|
62
60
|
animate={{ translateX: checked ? THUMB_TRAVEL : 0 }}
|
|
63
61
|
transition={SPRING_ELASTIC}
|
|
64
62
|
>
|
|
65
|
-
<EaseView
|
|
63
|
+
<EaseView
|
|
64
|
+
style={styles.iconWrapper}
|
|
65
|
+
animate={{ opacity: checked ? (isDisabled ? DISABLED_OPACITY : 1) : 0 }}
|
|
66
|
+
transition={OPACITY_TRANSITION}
|
|
67
|
+
>
|
|
66
68
|
<Feather name="check" size={ICON_SIZE} color={colors.primary} />
|
|
67
69
|
</EaseView>
|
|
68
|
-
<EaseView
|
|
70
|
+
<EaseView
|
|
71
|
+
style={styles.iconWrapper}
|
|
72
|
+
animate={{ opacity: checked ? 0 : (isDisabled ? DISABLED_OPACITY : 1) }}
|
|
73
|
+
transition={OPACITY_TRANSITION}
|
|
74
|
+
>
|
|
69
75
|
<Feather name="x" size={ICON_SIZE} color={colors.foregroundMuted} />
|
|
70
76
|
</EaseView>
|
|
71
77
|
</EaseView>
|
|
72
|
-
</
|
|
78
|
+
</View>
|
|
73
79
|
</TouchableOpacity>
|
|
74
80
|
</View>
|
|
75
81
|
)
|
|
@@ -79,11 +85,18 @@ const styles = StyleSheet.create({
|
|
|
79
85
|
touchable: {
|
|
80
86
|
alignSelf: 'flex-start',
|
|
81
87
|
},
|
|
82
|
-
|
|
88
|
+
trackContainer: {
|
|
89
|
+
position: 'relative',
|
|
83
90
|
width: TRACK_WIDTH,
|
|
84
91
|
height: TRACK_HEIGHT,
|
|
92
|
+
},
|
|
93
|
+
track: {
|
|
94
|
+
...StyleSheet.absoluteFillObject,
|
|
85
95
|
borderRadius: TRACK_HEIGHT / 2,
|
|
86
96
|
},
|
|
97
|
+
disabledTrack: {
|
|
98
|
+
opacity: DISABLED_OPACITY,
|
|
99
|
+
},
|
|
87
100
|
trackBorder: {
|
|
88
101
|
...StyleSheet.absoluteFillObject,
|
|
89
102
|
borderRadius: TRACK_HEIGHT / 2,
|
package/src/index.ts
CHANGED
|
@@ -55,12 +55,14 @@ export * from './components/TabBar'
|
|
|
55
55
|
export * from './components/ImageViewer'
|
|
56
56
|
export * from './components/SheetSelect'
|
|
57
57
|
export * from './components/ImageUpload'
|
|
58
|
+
export * from './components/IconPicker'
|
|
59
|
+
export * from './components/NumberStepper'
|
|
58
60
|
// HolographicCard is intentionally NOT re-exported here — it depends on the
|
|
59
61
|
// optional peer @shopify/react-native-skia, so it must stay out of the main
|
|
60
62
|
// barrel's module graph. Deep-import it: '@retray-dev/ui-kit/HolographicCard'.
|
|
61
63
|
|
|
62
64
|
// Icon utility
|
|
63
|
-
export { Icon, renderIcon, configureIconFamilies } from './utils/icons'
|
|
65
|
+
export { Icon, renderIcon, configureIconFamilies, getValidIconNames } from './utils/icons'
|
|
64
66
|
|
|
65
67
|
// Typography utilities
|
|
66
68
|
export { getResponsiveFontSize } from './utils/typography'
|