@retray-dev/ui-kit 1.8.0 → 2.5.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 -43
- package/dist/index.d.mts +80 -44
- package/dist/index.d.ts +80 -44
- package/dist/index.js +627 -457
- package/dist/index.mjs +626 -457
- package/package.json +8 -2
- package/src/components/Accordion/Accordion.tsx +4 -6
- package/src/components/Alert/Alert.tsx +3 -3
- package/src/components/AlertBanner/AlertBanner.tsx +85 -0
- package/src/components/{Alert → AlertBanner}/index.ts +2 -2
- package/src/components/Avatar/Avatar.tsx +1 -0
- package/src/components/Badge/Badge.tsx +45 -9
- package/src/components/Button/Button.tsx +5 -5
- package/src/components/Card/Card.tsx +90 -18
- package/src/components/Checkbox/Checkbox.tsx +4 -4
- package/src/components/Chip/Chip.tsx +36 -5
- package/src/components/CurrencyInput/CurrencyInput.tsx +9 -1
- package/src/components/EmptyState/EmptyState.tsx +2 -1
- package/src/components/Input/Input.tsx +107 -26
- package/src/components/ListItem/ListItem.tsx +157 -21
- package/src/components/MonthPicker/MonthPicker.tsx +3 -6
- package/src/components/RadioGroup/RadioGroup.tsx +2 -2
- package/src/components/Select/Select.tsx +200 -132
- package/src/components/Slider/Slider.tsx +64 -100
- package/src/components/Switch/Switch.tsx +22 -20
- package/src/components/Textarea/Textarea.tsx +16 -7
- package/src/components/Toast/Toast.tsx +23 -18
- package/src/components/Toggle/Toggle.tsx +36 -49
- package/src/index.ts +3 -2
- package/src/theme/ThemeProvider.tsx +11 -8
- package/src/theme/colors.ts +19 -18
- package/src/theme/types.ts +2 -0
- package/src/components/CurrencyInputLarge/CurrencyInputLarge.tsx +0 -66
- package/src/components/CurrencyInputLarge/index.ts +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import React, { useRef,
|
|
2
|
-
import { View, Text, TouchableOpacity, Animated, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
BottomSheetView,
|
|
6
|
-
BottomSheetBackdrop,
|
|
7
|
-
type BottomSheetBackdropProps,
|
|
8
|
-
} from '@gorhom/bottom-sheet'
|
|
1
|
+
import React, { useRef, useState } from 'react'
|
|
2
|
+
import { View, Text, TouchableOpacity, Modal, Animated, StyleSheet, ViewStyle, Platform } from 'react-native'
|
|
3
|
+
import { Picker } from '@react-native-picker/picker'
|
|
4
|
+
import { Entypo } from '@expo/vector-icons'
|
|
9
5
|
import * as Haptics from 'expo-haptics'
|
|
10
6
|
import { useTheme } from '../../theme'
|
|
11
7
|
|
|
8
|
+
const isIOS = Platform.OS === 'ios'
|
|
9
|
+
const isAndroid = Platform.OS === 'android'
|
|
10
|
+
const isWeb = Platform.OS === 'web'
|
|
11
|
+
|
|
12
12
|
export interface SelectOption {
|
|
13
13
|
label: string
|
|
14
14
|
value: string
|
|
@@ -19,10 +19,8 @@ export interface SelectProps {
|
|
|
19
19
|
options: SelectOption[]
|
|
20
20
|
value?: string
|
|
21
21
|
onValueChange?: (value: string) => void
|
|
22
|
-
/** Text shown when no option is selected. Defaults to `'Select an option'`. */
|
|
23
22
|
placeholder?: string
|
|
24
23
|
label?: string
|
|
25
|
-
/** Red helper text; also changes trigger border to `destructive` color. */
|
|
26
24
|
error?: string
|
|
27
25
|
disabled?: boolean
|
|
28
26
|
style?: ViewStyle
|
|
@@ -39,8 +37,10 @@ export function Select({
|
|
|
39
37
|
style,
|
|
40
38
|
}: SelectProps) {
|
|
41
39
|
const { colors } = useTheme()
|
|
42
|
-
const bottomSheetRef = useRef<BottomSheetModal>(null)
|
|
43
40
|
const scale = useRef(new Animated.Value(1)).current
|
|
41
|
+
const [pickerVisible, setPickerVisible] = useState(false)
|
|
42
|
+
const [pendingValue, setPendingValue] = useState<string | undefined>(value)
|
|
43
|
+
const pickerRef = useRef<React.ElementRef<typeof Picker>>(null)
|
|
44
44
|
|
|
45
45
|
const selected = options.find((o) => o.value === value)
|
|
46
46
|
|
|
@@ -54,179 +54,247 @@ export function Select({
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const handleOpen = () => {
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
if (disabled) return
|
|
58
|
+
Haptics.selectionAsync()
|
|
59
|
+
if (isIOS) {
|
|
60
|
+
setPendingValue(value)
|
|
61
|
+
setPickerVisible(true)
|
|
62
|
+
} else if (isAndroid) {
|
|
63
|
+
;(pickerRef.current as any)?.focus()
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
const
|
|
64
|
-
(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
const handleDismiss = () => {
|
|
68
|
+
setPickerVisible(false)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleConfirm = () => {
|
|
72
|
+
if (pendingValue !== undefined && pendingValue !== '') {
|
|
73
|
+
Haptics.selectionAsync()
|
|
74
|
+
onValueChange?.(pendingValue)
|
|
75
|
+
}
|
|
76
|
+
setPickerVisible(false)
|
|
77
|
+
}
|
|
74
78
|
|
|
75
79
|
return (
|
|
76
80
|
<View style={[styles.container, style]}>
|
|
77
81
|
{label ? <Text style={[styles.label, { color: colors.foreground }]}>{label}</Text> : null}
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
style={[
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
83
|
+
{/* Trigger button — shown on iOS and Android only */}
|
|
84
|
+
{!isWeb ? (
|
|
85
|
+
<Animated.View style={{ transform: [{ scale }], opacity: disabled ? 0.45 : 1 }}>
|
|
86
|
+
<TouchableOpacity
|
|
87
|
+
style={[
|
|
88
|
+
styles.trigger,
|
|
89
|
+
{
|
|
90
|
+
borderColor: error ? colors.destructive : colors.border,
|
|
91
|
+
backgroundColor: colors.background,
|
|
92
|
+
},
|
|
93
|
+
]}
|
|
94
|
+
onPress={handleOpen}
|
|
95
|
+
onPressIn={handlePressIn}
|
|
96
|
+
onPressOut={handlePressOut}
|
|
97
|
+
activeOpacity={1}
|
|
98
|
+
touchSoundDisabled={true}
|
|
99
|
+
>
|
|
100
|
+
<Text
|
|
101
|
+
style={[
|
|
102
|
+
styles.triggerText,
|
|
103
|
+
{ color: selected ? colors.foreground : colors.mutedForeground },
|
|
104
|
+
]}
|
|
105
|
+
numberOfLines={1}
|
|
106
|
+
allowFontScaling={true}
|
|
107
|
+
>
|
|
108
|
+
{selected?.label ?? placeholder}
|
|
109
|
+
</Text>
|
|
110
|
+
<Entypo name="chevron-with-circle-down" size={20} color={colors.mutedForeground} />
|
|
111
|
+
</TouchableOpacity>
|
|
112
|
+
</Animated.View>
|
|
113
|
+
) : null}
|
|
114
|
+
|
|
115
|
+
{/* iOS: Modal with wheel Picker */}
|
|
116
|
+
{isIOS ? (
|
|
117
|
+
<Modal
|
|
118
|
+
visible={pickerVisible}
|
|
119
|
+
transparent
|
|
120
|
+
animationType="slide"
|
|
121
|
+
onRequestClose={handleDismiss}
|
|
122
|
+
>
|
|
123
|
+
<TouchableOpacity style={styles.iosBackdrop} activeOpacity={1} onPress={handleDismiss} />
|
|
124
|
+
<View style={[styles.iosSheet, { backgroundColor: colors.card }]}>
|
|
125
|
+
<View style={[styles.iosToolbar, { borderBottomColor: colors.border }]}>
|
|
126
|
+
{label ? (
|
|
127
|
+
<Text style={[styles.iosToolbarTitle, { color: colors.foreground }]} allowFontScaling={true}>
|
|
128
|
+
{label}
|
|
129
|
+
</Text>
|
|
130
|
+
) : (
|
|
131
|
+
<View />
|
|
132
|
+
)}
|
|
133
|
+
<TouchableOpacity onPress={handleConfirm} style={styles.iosDoneBtn} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
|
|
134
|
+
<Text style={[styles.iosDoneBtnText, { color: colors.primary }]} allowFontScaling={true}>
|
|
135
|
+
Done
|
|
136
|
+
</Text>
|
|
137
|
+
</TouchableOpacity>
|
|
138
|
+
</View>
|
|
139
|
+
<Picker
|
|
140
|
+
selectedValue={pendingValue ?? ''}
|
|
141
|
+
onValueChange={(val) => setPendingValue(val as string)}
|
|
142
|
+
itemStyle={{ color: colors.foreground }}
|
|
143
|
+
>
|
|
144
|
+
{!value ? (
|
|
145
|
+
<Picker.Item label={placeholder} value="" color={colors.mutedForeground} enabled={false} />
|
|
146
|
+
) : null}
|
|
147
|
+
{options.map((o) => (
|
|
148
|
+
<Picker.Item
|
|
149
|
+
key={o.value}
|
|
150
|
+
label={o.label}
|
|
151
|
+
value={o.value}
|
|
152
|
+
enabled={!o.disabled}
|
|
153
|
+
color={o.disabled ? colors.mutedForeground : colors.foreground}
|
|
154
|
+
/>
|
|
155
|
+
))}
|
|
156
|
+
</Picker>
|
|
157
|
+
</View>
|
|
158
|
+
</Modal>
|
|
159
|
+
) : null}
|
|
160
|
+
|
|
161
|
+
{/* Android: hidden Picker opened programmatically via focus() */}
|
|
162
|
+
{isAndroid ? (
|
|
163
|
+
<Picker
|
|
164
|
+
ref={pickerRef}
|
|
165
|
+
selectedValue={value ?? ''}
|
|
166
|
+
onValueChange={(val) => {
|
|
167
|
+
if (val !== '') {
|
|
168
|
+
Haptics.selectionAsync()
|
|
169
|
+
onValueChange?.(val as string)
|
|
170
|
+
}
|
|
171
|
+
}}
|
|
172
|
+
mode="dialog"
|
|
173
|
+
enabled={!disabled}
|
|
174
|
+
prompt={label}
|
|
175
|
+
style={styles.androidHiddenPicker}
|
|
176
|
+
>
|
|
177
|
+
{!value ? <Picker.Item label={placeholder} value="" enabled={false} /> : null}
|
|
178
|
+
{options.map((o) => (
|
|
179
|
+
<Picker.Item
|
|
180
|
+
key={o.value}
|
|
181
|
+
label={o.label}
|
|
182
|
+
value={o.value}
|
|
183
|
+
enabled={!o.disabled}
|
|
184
|
+
/>
|
|
185
|
+
))}
|
|
186
|
+
</Picker>
|
|
187
|
+
) : null}
|
|
188
|
+
|
|
189
|
+
{/* Web: Picker renders as native <select> */}
|
|
190
|
+
{isWeb ? (
|
|
191
|
+
<Picker
|
|
192
|
+
selectedValue={value ?? ''}
|
|
193
|
+
onValueChange={(val) => {
|
|
194
|
+
if (val !== '') {
|
|
195
|
+
onValueChange?.(val as string)
|
|
196
|
+
}
|
|
197
|
+
}}
|
|
198
|
+
enabled={!disabled}
|
|
95
199
|
style={[
|
|
96
|
-
styles.
|
|
97
|
-
{
|
|
200
|
+
styles.webPicker,
|
|
201
|
+
{
|
|
202
|
+
borderColor: error ? colors.destructive : colors.border,
|
|
203
|
+
color: selected ? colors.foreground : colors.mutedForeground,
|
|
204
|
+
backgroundColor: colors.background,
|
|
205
|
+
opacity: disabled ? 0.45 : 1,
|
|
206
|
+
},
|
|
98
207
|
]}
|
|
99
|
-
numberOfLines={1}
|
|
100
208
|
>
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
209
|
+
<Picker.Item label={placeholder} value="" enabled={false} />
|
|
210
|
+
{options.map((o) => (
|
|
211
|
+
<Picker.Item
|
|
212
|
+
key={o.value}
|
|
213
|
+
label={o.label}
|
|
214
|
+
value={o.value}
|
|
215
|
+
enabled={!o.disabled}
|
|
216
|
+
/>
|
|
217
|
+
))}
|
|
218
|
+
</Picker>
|
|
219
|
+
) : null}
|
|
106
220
|
|
|
107
221
|
{error ? (
|
|
108
222
|
<Text style={[styles.helperText, { color: colors.destructive }]}>{error}</Text>
|
|
109
223
|
) : null}
|
|
110
|
-
|
|
111
|
-
<BottomSheetModal
|
|
112
|
-
ref={bottomSheetRef}
|
|
113
|
-
enableDynamicSizing
|
|
114
|
-
enablePanDownToClose
|
|
115
|
-
backdropComponent={renderBackdrop}
|
|
116
|
-
backgroundStyle={[styles.sheetBackground, { backgroundColor: colors.card }]}
|
|
117
|
-
handleIndicatorStyle={[styles.sheetHandle, { backgroundColor: colors.border }]}
|
|
118
|
-
>
|
|
119
|
-
<BottomSheetView style={styles.sheetContent}>
|
|
120
|
-
{label ? (
|
|
121
|
-
<Text style={[styles.sheetTitle, { color: colors.foreground }]}>{label}</Text>
|
|
122
|
-
) : null}
|
|
123
|
-
{options.map((item) => {
|
|
124
|
-
const isSelected = item.value === value
|
|
125
|
-
return (
|
|
126
|
-
<TouchableOpacity
|
|
127
|
-
key={item.value}
|
|
128
|
-
style={[
|
|
129
|
-
styles.option,
|
|
130
|
-
isSelected && { backgroundColor: colors.accent },
|
|
131
|
-
item.disabled && styles.disabledOption,
|
|
132
|
-
]}
|
|
133
|
-
onPress={() => {
|
|
134
|
-
if (!item.disabled) {
|
|
135
|
-
Haptics.selectionAsync()
|
|
136
|
-
onValueChange?.(item.value)
|
|
137
|
-
bottomSheetRef.current?.dismiss()
|
|
138
|
-
}
|
|
139
|
-
}}
|
|
140
|
-
activeOpacity={0.7}
|
|
141
|
-
touchSoundDisabled={true}
|
|
142
|
-
>
|
|
143
|
-
<Text
|
|
144
|
-
style={[
|
|
145
|
-
styles.optionText,
|
|
146
|
-
{ color: item.disabled ? colors.mutedForeground : colors.foreground },
|
|
147
|
-
isSelected && { fontWeight: '500' },
|
|
148
|
-
]}
|
|
149
|
-
>
|
|
150
|
-
{item.label}
|
|
151
|
-
</Text>
|
|
152
|
-
{isSelected ? (
|
|
153
|
-
<Text style={[styles.checkmark, { color: colors.primary }]}>✓</Text>
|
|
154
|
-
) : null}
|
|
155
|
-
</TouchableOpacity>
|
|
156
|
-
)
|
|
157
|
-
})}
|
|
158
|
-
</BottomSheetView>
|
|
159
|
-
</BottomSheetModal>
|
|
160
224
|
</View>
|
|
161
225
|
)
|
|
162
226
|
}
|
|
163
227
|
|
|
164
228
|
const styles = StyleSheet.create({
|
|
165
229
|
container: {
|
|
166
|
-
gap:
|
|
230
|
+
gap: 8,
|
|
167
231
|
},
|
|
168
232
|
label: {
|
|
169
233
|
fontSize: 15,
|
|
170
234
|
fontWeight: '500',
|
|
171
|
-
marginBottom: 2,
|
|
172
235
|
},
|
|
173
236
|
trigger: {
|
|
174
237
|
flexDirection: 'row',
|
|
175
238
|
alignItems: 'center',
|
|
176
239
|
justifyContent: 'space-between',
|
|
177
240
|
borderWidth: 1.5,
|
|
178
|
-
borderRadius:
|
|
179
|
-
paddingHorizontal:
|
|
180
|
-
paddingVertical:
|
|
241
|
+
borderRadius: 8,
|
|
242
|
+
paddingHorizontal: 16,
|
|
243
|
+
paddingVertical: 14,
|
|
244
|
+
shadowColor: '#000',
|
|
245
|
+
shadowOffset: { width: 0, height: 1 },
|
|
246
|
+
shadowOpacity: 0.04,
|
|
247
|
+
shadowRadius: 2,
|
|
248
|
+
elevation: 1,
|
|
181
249
|
},
|
|
182
250
|
triggerText: {
|
|
183
251
|
fontSize: 17,
|
|
184
252
|
flex: 1,
|
|
185
253
|
},
|
|
186
254
|
chevron: {
|
|
187
|
-
fontSize: 16,
|
|
188
255
|
marginLeft: 8,
|
|
189
256
|
},
|
|
190
257
|
helperText: {
|
|
191
258
|
fontSize: 13,
|
|
192
259
|
},
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
},
|
|
197
|
-
sheetHandle: {
|
|
198
|
-
width: 36,
|
|
199
|
-
height: 4,
|
|
200
|
-
borderRadius: 2,
|
|
201
|
-
},
|
|
202
|
-
sheetContent: {
|
|
203
|
-
paddingHorizontal: 20,
|
|
204
|
-
paddingBottom: 36,
|
|
260
|
+
iosBackdrop: {
|
|
261
|
+
flex: 1,
|
|
262
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
205
263
|
},
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
paddingHorizontal: 4,
|
|
264
|
+
iosSheet: {
|
|
265
|
+
borderTopLeftRadius: 16,
|
|
266
|
+
borderTopRightRadius: 16,
|
|
267
|
+
paddingBottom: 32,
|
|
211
268
|
},
|
|
212
|
-
|
|
269
|
+
iosToolbar: {
|
|
213
270
|
flexDirection: 'row',
|
|
214
271
|
alignItems: 'center',
|
|
215
272
|
justifyContent: 'space-between',
|
|
216
273
|
paddingHorizontal: 16,
|
|
217
|
-
paddingVertical:
|
|
218
|
-
|
|
274
|
+
paddingVertical: 12,
|
|
275
|
+
borderBottomWidth: 1,
|
|
219
276
|
},
|
|
220
|
-
|
|
277
|
+
iosToolbarTitle: {
|
|
221
278
|
fontSize: 17,
|
|
222
|
-
|
|
279
|
+
fontWeight: '600',
|
|
223
280
|
},
|
|
224
|
-
|
|
225
|
-
|
|
281
|
+
iosDoneBtn: {
|
|
282
|
+
padding: 4,
|
|
226
283
|
},
|
|
227
|
-
|
|
228
|
-
fontSize:
|
|
284
|
+
iosDoneBtnText: {
|
|
285
|
+
fontSize: 17,
|
|
229
286
|
fontWeight: '600',
|
|
230
|
-
|
|
287
|
+
},
|
|
288
|
+
androidHiddenPicker: {
|
|
289
|
+
height: 0,
|
|
290
|
+
opacity: 0,
|
|
291
|
+
position: 'absolute',
|
|
292
|
+
},
|
|
293
|
+
webPicker: {
|
|
294
|
+
borderWidth: 1.5,
|
|
295
|
+
borderRadius: 8,
|
|
296
|
+
paddingHorizontal: 16,
|
|
297
|
+
paddingVertical: 14,
|
|
298
|
+
fontSize: 17,
|
|
231
299
|
},
|
|
232
300
|
})
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import React, { useRef
|
|
2
|
-
import { View,
|
|
1
|
+
import React, { useRef } from 'react'
|
|
2
|
+
import { View, Text, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
+
import RNSlider from '@react-native-community/slider'
|
|
3
4
|
import * as Haptics from 'expo-haptics'
|
|
4
5
|
import { useTheme } from '../../theme'
|
|
5
6
|
|
|
6
7
|
export interface SliderProps {
|
|
7
|
-
/** Current value. Controlled when provided; falls back to internal state otherwise. */
|
|
8
8
|
value?: number
|
|
9
9
|
minimumValue?: number
|
|
10
10
|
maximumValue?: number
|
|
11
|
-
/** Snap interval. `0` (default) means continuous (no snapping). */
|
|
12
11
|
step?: number
|
|
13
|
-
/** Called on every move while dragging. */
|
|
14
12
|
onValueChange?: (value: number) => void
|
|
15
|
-
/** Called once when the user releases the thumb. */
|
|
16
13
|
onSlidingComplete?: (value: number) => void
|
|
14
|
+
label?: string
|
|
15
|
+
showValue?: boolean
|
|
16
|
+
formatValue?: (value: number) => string
|
|
17
|
+
accessibilityLabel?: string
|
|
17
18
|
disabled?: boolean
|
|
18
19
|
style?: ViewStyle
|
|
19
20
|
}
|
|
@@ -25,119 +26,82 @@ export function Slider({
|
|
|
25
26
|
step = 0,
|
|
26
27
|
onValueChange,
|
|
27
28
|
onSlidingComplete,
|
|
29
|
+
label,
|
|
30
|
+
showValue = false,
|
|
31
|
+
formatValue = (v: number) => v.toFixed(2),
|
|
32
|
+
accessibilityLabel,
|
|
28
33
|
disabled,
|
|
29
34
|
style,
|
|
30
35
|
}: SliderProps) {
|
|
31
36
|
const { colors } = useTheme()
|
|
32
|
-
const trackWidth = useRef(0)
|
|
33
37
|
const lastSteppedValue = useRef(value)
|
|
34
|
-
const [internalValue, setInternalValue] = useState(value)
|
|
35
|
-
const currentValue = value ?? internalValue
|
|
36
38
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const xToValue = (x: number): number => {
|
|
45
|
-
const ratio = Math.min(Math.max(x / trackWidth.current, 0), 1)
|
|
46
|
-
const raw = ratio * (maximumValue - minimumValue) + minimumValue
|
|
47
|
-
return clamp(snapToStep(raw))
|
|
39
|
+
const handleValueChange = (v: number) => {
|
|
40
|
+
if (step && v !== lastSteppedValue.current) {
|
|
41
|
+
lastSteppedValue.current = v
|
|
42
|
+
Haptics.selectionAsync()
|
|
43
|
+
}
|
|
44
|
+
onValueChange?.(v)
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
const panResponder = useRef(
|
|
51
|
-
PanResponder.create({
|
|
52
|
-
onStartShouldSetPanResponder: () => !disabled,
|
|
53
|
-
onMoveShouldSetPanResponder: () => !disabled,
|
|
54
|
-
onPanResponderGrant: (e) => {
|
|
55
|
-
const x = e.nativeEvent.locationX
|
|
56
|
-
const newValue = xToValue(x)
|
|
57
|
-
setInternalValue(newValue)
|
|
58
|
-
onValueChange?.(newValue)
|
|
59
|
-
},
|
|
60
|
-
onPanResponderMove: (e) => {
|
|
61
|
-
const x = e.nativeEvent.locationX
|
|
62
|
-
const newValue = xToValue(x)
|
|
63
|
-
if (newValue !== lastSteppedValue.current) {
|
|
64
|
-
lastSteppedValue.current = newValue
|
|
65
|
-
Haptics.selectionAsync()
|
|
66
|
-
}
|
|
67
|
-
setInternalValue(newValue)
|
|
68
|
-
onValueChange?.(newValue)
|
|
69
|
-
},
|
|
70
|
-
onPanResponderRelease: (e) => {
|
|
71
|
-
const x = e.nativeEvent.locationX
|
|
72
|
-
const newValue = xToValue(x)
|
|
73
|
-
setInternalValue(newValue)
|
|
74
|
-
onSlidingComplete?.(newValue)
|
|
75
|
-
},
|
|
76
|
-
})
|
|
77
|
-
).current
|
|
78
|
-
|
|
79
|
-
const onLayout = (e: LayoutChangeEvent) => {
|
|
80
|
-
trackWidth.current = e.nativeEvent.layout.width
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const percent = ((currentValue - minimumValue) / (maximumValue - minimumValue)) * 100
|
|
84
|
-
|
|
85
47
|
return (
|
|
86
|
-
<View
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
48
|
+
<View style={[styles.wrapper, style]} accessibilityLabel={accessibilityLabel}>
|
|
49
|
+
{label || showValue ? (
|
|
50
|
+
<View style={styles.header}>
|
|
51
|
+
{label ? (
|
|
52
|
+
<Text style={[styles.label, { color: colors.foreground }]} allowFontScaling={true}>
|
|
53
|
+
{label}
|
|
54
|
+
</Text>
|
|
55
|
+
) : null}
|
|
56
|
+
{showValue ? (
|
|
57
|
+
<Text style={[styles.valueText, { color: colors.mutedForeground }]} allowFontScaling={true}>
|
|
58
|
+
{formatValue(value)}
|
|
59
|
+
</Text>
|
|
60
|
+
) : null}
|
|
61
|
+
</View>
|
|
62
|
+
) : null}
|
|
63
|
+
<View style={disabled ? styles.disabled : undefined}>
|
|
64
|
+
<RNSlider
|
|
65
|
+
value={value}
|
|
66
|
+
minimumValue={minimumValue}
|
|
67
|
+
maximumValue={maximumValue}
|
|
68
|
+
step={step || 0}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
onValueChange={handleValueChange}
|
|
71
|
+
onSlidingComplete={onSlidingComplete}
|
|
72
|
+
minimumTrackTintColor={colors.primary}
|
|
73
|
+
maximumTrackTintColor={colors.muted}
|
|
74
|
+
thumbTintColor={colors.primary}
|
|
75
|
+
style={styles.slider}
|
|
76
|
+
accessibilityLabel={accessibilityLabel}
|
|
94
77
|
/>
|
|
95
78
|
</View>
|
|
96
|
-
<View
|
|
97
|
-
style={[
|
|
98
|
-
styles.thumb,
|
|
99
|
-
{
|
|
100
|
-
left: `${percent}%` as any,
|
|
101
|
-
backgroundColor: colors.primary,
|
|
102
|
-
borderColor: colors.background,
|
|
103
|
-
transform: [{ translateX: -14 }],
|
|
104
|
-
},
|
|
105
|
-
]}
|
|
106
|
-
pointerEvents="none"
|
|
107
|
-
/>
|
|
108
79
|
</View>
|
|
109
80
|
)
|
|
110
81
|
}
|
|
111
82
|
|
|
112
83
|
const styles = StyleSheet.create({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
justifyContent: 'center',
|
|
116
|
-
position: 'relative',
|
|
84
|
+
wrapper: {
|
|
85
|
+
gap: 8,
|
|
117
86
|
},
|
|
118
|
-
|
|
119
|
-
|
|
87
|
+
header: {
|
|
88
|
+
flexDirection: 'row',
|
|
89
|
+
justifyContent: 'space-between',
|
|
90
|
+
alignItems: 'center',
|
|
120
91
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
overflow: 'hidden',
|
|
125
|
-
width: '100%',
|
|
92
|
+
label: {
|
|
93
|
+
fontSize: 15,
|
|
94
|
+
fontWeight: '500',
|
|
126
95
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
96
|
+
valueText: {
|
|
97
|
+
fontSize: 14,
|
|
98
|
+
fontWeight: '500',
|
|
130
99
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
shadowColor: '#000',
|
|
138
|
-
shadowOffset: { width: 0, height: 1 },
|
|
139
|
-
shadowOpacity: 0.2,
|
|
140
|
-
shadowRadius: 2,
|
|
141
|
-
elevation: 2,
|
|
100
|
+
slider: {
|
|
101
|
+
width: '100%',
|
|
102
|
+
height: 40,
|
|
103
|
+
},
|
|
104
|
+
disabled: {
|
|
105
|
+
opacity: 0.45,
|
|
142
106
|
},
|
|
143
107
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react'
|
|
2
|
-
import { TouchableOpacity, Animated, StyleSheet, ViewStyle, Platform } from 'react-native'
|
|
2
|
+
import { TouchableOpacity, Animated, StyleSheet, ViewStyle, Platform, View } from 'react-native'
|
|
3
3
|
|
|
4
4
|
const nativeDriver = Platform.OS !== 'web'
|
|
5
5
|
import * as Haptics from 'expo-haptics'
|
|
@@ -44,25 +44,27 @@ export function Switch({ checked = false, onCheckedChange, disabled, style }: Sw
|
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
return (
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<Animated.View
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
<View style={[{ opacity: disabled ? 0.45 : 1 }, style]}>
|
|
48
|
+
<TouchableOpacity
|
|
49
|
+
onPress={() => {
|
|
50
|
+
Haptics.selectionAsync()
|
|
51
|
+
onCheckedChange?.(!checked)
|
|
52
|
+
}}
|
|
53
|
+
disabled={disabled}
|
|
54
|
+
activeOpacity={0.8}
|
|
55
|
+
touchSoundDisabled={true}
|
|
56
|
+
style={styles.wrapper}
|
|
57
|
+
>
|
|
58
|
+
<Animated.View style={[styles.track, { backgroundColor: trackColor }]}>
|
|
59
|
+
<Animated.View
|
|
60
|
+
style={[
|
|
61
|
+
styles.thumb,
|
|
62
|
+
{ backgroundColor: colors.primaryForeground, transform: [{ translateX }] },
|
|
63
|
+
]}
|
|
64
|
+
/>
|
|
65
|
+
</Animated.View>
|
|
66
|
+
</TouchableOpacity>
|
|
67
|
+
</View>
|
|
66
68
|
)
|
|
67
69
|
}
|
|
68
70
|
|