@retray-dev/ui-kit 1.8.0 → 2.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.
- package/COMPONENTS.md +80 -23
- package/dist/index.d.mts +46 -42
- package/dist/index.d.ts +46 -42
- package/dist/index.js +442 -333
- package/dist/index.mjs +441 -333
- package/package.json +6 -2
- package/src/components/Alert/Alert.tsx +3 -3
- package/src/components/AlertBanner/AlertBanner.tsx +83 -0
- package/src/components/{Alert → AlertBanner}/index.ts +2 -2
- package/src/components/Avatar/Avatar.tsx +1 -0
- package/src/components/Badge/Badge.tsx +44 -8
- package/src/components/Button/Button.tsx +1 -1
- package/src/components/Card/Card.tsx +86 -9
- 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 +102 -21
- package/src/components/MonthPicker/MonthPicker.tsx +2 -2
- package/src/components/Select/Select.tsx +189 -125
- package/src/components/Slider/Slider.tsx +64 -100
- package/src/components/Switch/Switch.tsx +22 -20
- package/src/components/Textarea/Textarea.tsx +12 -2
- package/src/components/Toggle/Toggle.tsx +13 -6
- 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,13 @@
|
|
|
1
|
-
import React, { useRef,
|
|
2
|
-
import { View, Text, TouchableOpacity, Animated, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
-
import {
|
|
4
|
-
BottomSheetModal,
|
|
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'
|
|
9
4
|
import * as Haptics from 'expo-haptics'
|
|
10
5
|
import { useTheme } from '../../theme'
|
|
11
6
|
|
|
7
|
+
const isIOS = Platform.OS === 'ios'
|
|
8
|
+
const isAndroid = Platform.OS === 'android'
|
|
9
|
+
const isWeb = Platform.OS === 'web'
|
|
10
|
+
|
|
12
11
|
export interface SelectOption {
|
|
13
12
|
label: string
|
|
14
13
|
value: string
|
|
@@ -19,10 +18,8 @@ export interface SelectProps {
|
|
|
19
18
|
options: SelectOption[]
|
|
20
19
|
value?: string
|
|
21
20
|
onValueChange?: (value: string) => void
|
|
22
|
-
/** Text shown when no option is selected. Defaults to `'Select an option'`. */
|
|
23
21
|
placeholder?: string
|
|
24
22
|
label?: string
|
|
25
|
-
/** Red helper text; also changes trigger border to `destructive` color. */
|
|
26
23
|
error?: string
|
|
27
24
|
disabled?: boolean
|
|
28
25
|
style?: ViewStyle
|
|
@@ -39,8 +36,10 @@ export function Select({
|
|
|
39
36
|
style,
|
|
40
37
|
}: SelectProps) {
|
|
41
38
|
const { colors } = useTheme()
|
|
42
|
-
const bottomSheetRef = useRef<BottomSheetModal>(null)
|
|
43
39
|
const scale = useRef(new Animated.Value(1)).current
|
|
40
|
+
const [pickerVisible, setPickerVisible] = useState(false)
|
|
41
|
+
const [pendingValue, setPendingValue] = useState<string | undefined>(value)
|
|
42
|
+
const pickerRef = useRef<React.ElementRef<typeof Picker>>(null)
|
|
44
43
|
|
|
45
44
|
const selected = options.find((o) => o.value === value)
|
|
46
45
|
|
|
@@ -54,109 +53,173 @@ export function Select({
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
const handleOpen = () => {
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
if (disabled) return
|
|
57
|
+
Haptics.selectionAsync()
|
|
58
|
+
if (isIOS) {
|
|
59
|
+
setPendingValue(value)
|
|
60
|
+
setPickerVisible(true)
|
|
61
|
+
} else if (isAndroid) {
|
|
62
|
+
;(pickerRef.current as any)?.focus()
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
const
|
|
64
|
-
(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
const handleDismiss = () => {
|
|
67
|
+
setPickerVisible(false)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const handleConfirm = () => {
|
|
71
|
+
if (pendingValue !== undefined && pendingValue !== '') {
|
|
72
|
+
Haptics.selectionAsync()
|
|
73
|
+
onValueChange?.(pendingValue)
|
|
74
|
+
}
|
|
75
|
+
setPickerVisible(false)
|
|
76
|
+
}
|
|
74
77
|
|
|
75
78
|
return (
|
|
76
79
|
<View style={[styles.container, style]}>
|
|
77
80
|
{label ? <Text style={[styles.label, { color: colors.foreground }]}>{label}</Text> : null}
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
style={[
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
82
|
+
{/* Trigger button — shown on iOS and Android only */}
|
|
83
|
+
{!isWeb ? (
|
|
84
|
+
<Animated.View style={{ transform: [{ scale }], opacity: disabled ? 0.45 : 1 }}>
|
|
85
|
+
<TouchableOpacity
|
|
86
|
+
style={[
|
|
87
|
+
styles.trigger,
|
|
88
|
+
{
|
|
89
|
+
borderColor: error ? colors.destructive : colors.border,
|
|
90
|
+
backgroundColor: colors.background,
|
|
91
|
+
},
|
|
92
|
+
]}
|
|
93
|
+
onPress={handleOpen}
|
|
94
|
+
onPressIn={handlePressIn}
|
|
95
|
+
onPressOut={handlePressOut}
|
|
96
|
+
activeOpacity={1}
|
|
97
|
+
touchSoundDisabled={true}
|
|
98
|
+
>
|
|
99
|
+
<Text
|
|
100
|
+
style={[
|
|
101
|
+
styles.triggerText,
|
|
102
|
+
{ color: selected ? colors.foreground : colors.mutedForeground },
|
|
103
|
+
]}
|
|
104
|
+
numberOfLines={1}
|
|
105
|
+
allowFontScaling={true}
|
|
106
|
+
>
|
|
107
|
+
{selected?.label ?? placeholder}
|
|
108
|
+
</Text>
|
|
109
|
+
<Text style={[styles.chevron, { color: colors.mutedForeground }]}>▾</Text>
|
|
110
|
+
</TouchableOpacity>
|
|
111
|
+
</Animated.View>
|
|
112
|
+
) : null}
|
|
113
|
+
|
|
114
|
+
{/* iOS: Modal with wheel Picker */}
|
|
115
|
+
{isIOS ? (
|
|
116
|
+
<Modal
|
|
117
|
+
visible={pickerVisible}
|
|
118
|
+
transparent
|
|
119
|
+
animationType="slide"
|
|
120
|
+
onRequestClose={handleDismiss}
|
|
121
|
+
>
|
|
122
|
+
<TouchableOpacity style={styles.iosBackdrop} activeOpacity={1} onPress={handleDismiss} />
|
|
123
|
+
<View style={[styles.iosSheet, { backgroundColor: colors.card }]}>
|
|
124
|
+
<View style={[styles.iosToolbar, { borderBottomColor: colors.border }]}>
|
|
125
|
+
{label ? (
|
|
126
|
+
<Text style={[styles.iosToolbarTitle, { color: colors.foreground }]} allowFontScaling={true}>
|
|
127
|
+
{label}
|
|
128
|
+
</Text>
|
|
129
|
+
) : (
|
|
130
|
+
<View />
|
|
131
|
+
)}
|
|
132
|
+
<TouchableOpacity onPress={handleConfirm} style={styles.iosDoneBtn} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
|
|
133
|
+
<Text style={[styles.iosDoneBtnText, { color: colors.primary }]} allowFontScaling={true}>
|
|
134
|
+
Done
|
|
135
|
+
</Text>
|
|
136
|
+
</TouchableOpacity>
|
|
137
|
+
</View>
|
|
138
|
+
<Picker
|
|
139
|
+
selectedValue={pendingValue ?? ''}
|
|
140
|
+
onValueChange={(val) => setPendingValue(val as string)}
|
|
141
|
+
itemStyle={{ color: colors.foreground }}
|
|
142
|
+
>
|
|
143
|
+
{!value ? (
|
|
144
|
+
<Picker.Item label={placeholder} value="" color={colors.mutedForeground} enabled={false} />
|
|
145
|
+
) : null}
|
|
146
|
+
{options.map((o) => (
|
|
147
|
+
<Picker.Item
|
|
148
|
+
key={o.value}
|
|
149
|
+
label={o.label}
|
|
150
|
+
value={o.value}
|
|
151
|
+
enabled={!o.disabled}
|
|
152
|
+
color={o.disabled ? colors.mutedForeground : colors.foreground}
|
|
153
|
+
/>
|
|
154
|
+
))}
|
|
155
|
+
</Picker>
|
|
156
|
+
</View>
|
|
157
|
+
</Modal>
|
|
158
|
+
) : null}
|
|
159
|
+
|
|
160
|
+
{/* Android: hidden Picker opened programmatically via focus() */}
|
|
161
|
+
{isAndroid ? (
|
|
162
|
+
<Picker
|
|
163
|
+
ref={pickerRef}
|
|
164
|
+
selectedValue={value ?? ''}
|
|
165
|
+
onValueChange={(val) => {
|
|
166
|
+
if (val !== '') {
|
|
167
|
+
Haptics.selectionAsync()
|
|
168
|
+
onValueChange?.(val as string)
|
|
169
|
+
}
|
|
170
|
+
}}
|
|
171
|
+
mode="dialog"
|
|
172
|
+
enabled={!disabled}
|
|
173
|
+
prompt={label}
|
|
174
|
+
style={styles.androidHiddenPicker}
|
|
175
|
+
>
|
|
176
|
+
{!value ? <Picker.Item label={placeholder} value="" enabled={false} /> : null}
|
|
177
|
+
{options.map((o) => (
|
|
178
|
+
<Picker.Item
|
|
179
|
+
key={o.value}
|
|
180
|
+
label={o.label}
|
|
181
|
+
value={o.value}
|
|
182
|
+
enabled={!o.disabled}
|
|
183
|
+
/>
|
|
184
|
+
))}
|
|
185
|
+
</Picker>
|
|
186
|
+
) : null}
|
|
187
|
+
|
|
188
|
+
{/* Web: Picker renders as native <select> */}
|
|
189
|
+
{isWeb ? (
|
|
190
|
+
<Picker
|
|
191
|
+
selectedValue={value ?? ''}
|
|
192
|
+
onValueChange={(val) => {
|
|
193
|
+
if (val !== '') {
|
|
194
|
+
onValueChange?.(val as string)
|
|
195
|
+
}
|
|
196
|
+
}}
|
|
197
|
+
enabled={!disabled}
|
|
95
198
|
style={[
|
|
96
|
-
styles.
|
|
97
|
-
{
|
|
199
|
+
styles.webPicker,
|
|
200
|
+
{
|
|
201
|
+
borderColor: error ? colors.destructive : colors.border,
|
|
202
|
+
color: selected ? colors.foreground : colors.mutedForeground,
|
|
203
|
+
backgroundColor: colors.background,
|
|
204
|
+
opacity: disabled ? 0.45 : 1,
|
|
205
|
+
},
|
|
98
206
|
]}
|
|
99
|
-
numberOfLines={1}
|
|
100
207
|
>
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
208
|
+
<Picker.Item label={placeholder} value="" enabled={false} />
|
|
209
|
+
{options.map((o) => (
|
|
210
|
+
<Picker.Item
|
|
211
|
+
key={o.value}
|
|
212
|
+
label={o.label}
|
|
213
|
+
value={o.value}
|
|
214
|
+
enabled={!o.disabled}
|
|
215
|
+
/>
|
|
216
|
+
))}
|
|
217
|
+
</Picker>
|
|
218
|
+
) : null}
|
|
106
219
|
|
|
107
220
|
{error ? (
|
|
108
221
|
<Text style={[styles.helperText, { color: colors.destructive }]}>{error}</Text>
|
|
109
222
|
) : 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
223
|
</View>
|
|
161
224
|
)
|
|
162
225
|
}
|
|
@@ -190,43 +253,44 @@ const styles = StyleSheet.create({
|
|
|
190
253
|
helperText: {
|
|
191
254
|
fontSize: 13,
|
|
192
255
|
},
|
|
193
|
-
|
|
256
|
+
iosBackdrop: {
|
|
257
|
+
flex: 1,
|
|
258
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
259
|
+
},
|
|
260
|
+
iosSheet: {
|
|
194
261
|
borderTopLeftRadius: 24,
|
|
195
262
|
borderTopRightRadius: 24,
|
|
263
|
+
paddingBottom: 32,
|
|
196
264
|
},
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
},
|
|
202
|
-
sheetContent: {
|
|
265
|
+
iosToolbar: {
|
|
266
|
+
flexDirection: 'row',
|
|
267
|
+
alignItems: 'center',
|
|
268
|
+
justifyContent: 'space-between',
|
|
203
269
|
paddingHorizontal: 20,
|
|
204
|
-
|
|
270
|
+
paddingVertical: 12,
|
|
271
|
+
borderBottomWidth: 1,
|
|
205
272
|
},
|
|
206
|
-
|
|
273
|
+
iosToolbarTitle: {
|
|
207
274
|
fontSize: 17,
|
|
208
275
|
fontWeight: '600',
|
|
209
|
-
paddingVertical: 16,
|
|
210
|
-
paddingHorizontal: 4,
|
|
211
276
|
},
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
alignItems: 'center',
|
|
215
|
-
justifyContent: 'space-between',
|
|
216
|
-
paddingHorizontal: 16,
|
|
217
|
-
paddingVertical: 16,
|
|
218
|
-
borderRadius: 12,
|
|
277
|
+
iosDoneBtn: {
|
|
278
|
+
padding: 4,
|
|
219
279
|
},
|
|
220
|
-
|
|
280
|
+
iosDoneBtnText: {
|
|
221
281
|
fontSize: 17,
|
|
222
|
-
|
|
282
|
+
fontWeight: '600',
|
|
223
283
|
},
|
|
224
|
-
|
|
225
|
-
|
|
284
|
+
androidHiddenPicker: {
|
|
285
|
+
height: 0,
|
|
286
|
+
opacity: 0,
|
|
287
|
+
position: 'absolute',
|
|
226
288
|
},
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
289
|
+
webPicker: {
|
|
290
|
+
borderWidth: 1.5,
|
|
291
|
+
borderRadius: 14,
|
|
292
|
+
paddingHorizontal: 20,
|
|
293
|
+
paddingVertical: 16,
|
|
294
|
+
fontSize: 17,
|
|
231
295
|
},
|
|
232
296
|
})
|
|
@@ -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
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
|
-
import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle } from 'react-native'
|
|
2
|
+
import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, Platform } from 'react-native'
|
|
3
3
|
import { useTheme } from '../../theme'
|
|
4
4
|
|
|
5
|
+
const webInputResetStyle: any =
|
|
6
|
+
Platform.OS === 'web'
|
|
7
|
+
? { outlineStyle: 'none', outlineWidth: 0, outlineColor: 'transparent', boxShadow: 'none' }
|
|
8
|
+
: {}
|
|
9
|
+
|
|
5
10
|
export interface TextareaProps extends TextInputProps {
|
|
6
11
|
label?: string
|
|
7
12
|
/** Red helper text below the textarea; also changes border to `destructive` color. Takes priority over `hint`. */
|
|
@@ -38,11 +43,16 @@ export function Textarea({
|
|
|
38
43
|
style={[
|
|
39
44
|
styles.input,
|
|
40
45
|
{
|
|
41
|
-
borderColor: error
|
|
46
|
+
borderColor: error
|
|
47
|
+
? colors.destructive
|
|
48
|
+
: focused
|
|
49
|
+
? (colors.ring ?? colors.primary)
|
|
50
|
+
: colors.border,
|
|
42
51
|
color: colors.foreground,
|
|
43
52
|
backgroundColor: colors.background,
|
|
44
53
|
minHeight: rows * 30,
|
|
45
54
|
},
|
|
55
|
+
webInputResetStyle,
|
|
46
56
|
style,
|
|
47
57
|
]}
|
|
48
58
|
onFocus={(e) => {
|