@retray-dev/ui-kit 2.8.0 → 3.0.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 (55) hide show
  1. package/COMPONENTS.md +71 -15
  2. package/README.md +23 -3
  3. package/dist/index.d.mts +16 -5
  4. package/dist/index.d.ts +16 -5
  5. package/dist/index.js +441 -327
  6. package/dist/index.mjs +442 -328
  7. package/package.json +6 -3
  8. package/src/assets/fonts/Poppins-Black.ttf +0 -0
  9. package/src/assets/fonts/Poppins-BlackItalic.ttf +0 -0
  10. package/src/assets/fonts/Poppins-Bold.ttf +0 -0
  11. package/src/assets/fonts/Poppins-BoldItalic.ttf +0 -0
  12. package/src/assets/fonts/Poppins-ExtraBold.ttf +0 -0
  13. package/src/assets/fonts/Poppins-ExtraBoldItalic.ttf +0 -0
  14. package/src/assets/fonts/Poppins-ExtraLight.ttf +0 -0
  15. package/src/assets/fonts/Poppins-ExtraLightItalic.ttf +0 -0
  16. package/src/assets/fonts/Poppins-Italic.ttf +0 -0
  17. package/src/assets/fonts/Poppins-Light.ttf +0 -0
  18. package/src/assets/fonts/Poppins-LightItalic.ttf +0 -0
  19. package/src/assets/fonts/Poppins-Medium.ttf +0 -0
  20. package/src/assets/fonts/Poppins-MediumItalic.ttf +0 -0
  21. package/src/assets/fonts/Poppins-Regular.ttf +0 -0
  22. package/src/assets/fonts/Poppins-SemiBold.ttf +0 -0
  23. package/src/assets/fonts/Poppins-SemiBoldItalic.ttf +0 -0
  24. package/src/assets/fonts/Poppins-Thin.ttf +0 -0
  25. package/src/assets/fonts/Poppins-ThinItalic.ttf +0 -0
  26. package/src/components/Accordion/Accordion.tsx +16 -9
  27. package/src/components/AlertBanner/AlertBanner.tsx +35 -35
  28. package/src/components/Avatar/Avatar.tsx +1 -1
  29. package/src/components/Badge/Badge.tsx +12 -8
  30. package/src/components/Button/Button.tsx +8 -8
  31. package/src/components/Card/Card.tsx +12 -9
  32. package/src/components/Checkbox/Checkbox.tsx +8 -8
  33. package/src/components/Chip/Chip.tsx +22 -6
  34. package/src/components/ConfirmDialog/ConfirmDialog.tsx +86 -38
  35. package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +1 -1
  36. package/src/components/CurrencyInput/CurrencyInput.tsx +11 -4
  37. package/src/components/EmptyState/EmptyState.tsx +2 -1
  38. package/src/components/Input/Input.tsx +12 -8
  39. package/src/components/LabelValue/LabelValue.tsx +4 -3
  40. package/src/components/ListItem/ListItem.tsx +10 -9
  41. package/src/components/MonthPicker/MonthPicker.tsx +1 -1
  42. package/src/components/RadioGroup/RadioGroup.tsx +36 -36
  43. package/src/components/Select/Select.tsx +17 -19
  44. package/src/components/Sheet/Sheet.tsx +2 -1
  45. package/src/components/Slider/Slider.tsx +3 -3
  46. package/src/components/Spinner/Spinner.tsx +36 -2
  47. package/src/components/Switch/Switch.tsx +4 -4
  48. package/src/components/Tabs/Tabs.tsx +9 -16
  49. package/src/components/Text/Text.tsx +6 -6
  50. package/src/components/Textarea/Textarea.tsx +8 -6
  51. package/src/components/Toast/Toast.tsx +27 -21
  52. package/src/components/Toggle/Toggle.tsx +6 -4
  53. package/src/fonts.ts +30 -0
  54. package/src/theme/colors.ts +22 -14
  55. package/src/theme/types.ts +4 -0
@@ -1,5 +1,13 @@
1
- import React from 'react'
2
- import { Modal, View, Text, StyleSheet, TouchableOpacity } from 'react-native'
1
+ import React, { useEffect, useRef } from 'react'
2
+ import { View, Text, StyleSheet } from 'react-native'
3
+ import {
4
+ BottomSheetModal,
5
+ BottomSheetView,
6
+ BottomSheetBackdrop,
7
+ type BottomSheetBackdropProps,
8
+ } from '@gorhom/bottom-sheet'
9
+ import { Feather } from '@expo/vector-icons'
10
+ import { impactLight } from '../../utils/haptics'
3
11
  import { useTheme } from '../../theme'
4
12
  import { Button } from '../Button'
5
13
  import { s, vs, ms, mvs } from '../../utils/scaling'
@@ -26,58 +34,98 @@ export function ConfirmDialog({
26
34
  onCancel,
27
35
  }: ConfirmDialogProps) {
28
36
  const { colors } = useTheme()
37
+ const ref = useRef<BottomSheetModal>(null)
38
+
39
+ useEffect(() => {
40
+ if (visible) {
41
+ impactLight()
42
+ ref.current?.present()
43
+ } else {
44
+ ref.current?.dismiss()
45
+ }
46
+ }, [visible])
47
+
48
+ const renderBackdrop = (props: BottomSheetBackdropProps) => (
49
+ <BottomSheetBackdrop
50
+ {...props}
51
+ disappearsOnIndex={-1}
52
+ appearsOnIndex={0}
53
+ pressBehavior="close"
54
+ />
55
+ )
29
56
 
30
57
  return (
31
- <Modal visible={visible} transparent animationType="fade" onRequestClose={onCancel}>
32
- <TouchableOpacity style={styles.overlay} activeOpacity={1} onPress={onCancel}>
33
- <View
34
- style={[styles.dialog, { backgroundColor: colors.card }]}
35
- onStartShouldSetResponder={() => true}
36
- >
37
- <Text style={[styles.title, { color: colors.cardForeground }]} allowFontScaling={true}>
38
- {title}
58
+ <BottomSheetModal
59
+ ref={ref}
60
+ snapPoints={['35%']}
61
+ onDismiss={onCancel}
62
+ backdropComponent={renderBackdrop}
63
+ backgroundStyle={[styles.background, { backgroundColor: colors.card }]}
64
+ handleIndicatorStyle={[styles.handle, { backgroundColor: colors.border }]}
65
+ enablePanDownToClose
66
+ >
67
+ <BottomSheetView style={styles.content}>
68
+ <Text style={[styles.title, { color: colors.cardForeground }]} allowFontScaling={true}>
69
+ {title}
70
+ </Text>
71
+ {description ? (
72
+ <Text style={[styles.description, { color: colors.mutedForeground }]} allowFontScaling={true}>
73
+ {description}
39
74
  </Text>
40
- {description ? (
41
- <Text style={[styles.description, { color: colors.mutedForeground }]} allowFontScaling={true}>
42
- {description}
43
- </Text>
44
- ) : null}
45
- <View style={styles.actions}>
46
- <Button label={cancelLabel} variant="outline" fullWidth onPress={onCancel} />
47
- <Button label={confirmLabel} variant={confirmVariant} fullWidth onPress={onConfirm} />
48
- </View>
75
+ ) : null}
76
+ <View style={styles.actions}>
77
+ <Button
78
+ label={cancelLabel}
79
+ variant="outline"
80
+ fullWidth
81
+ onPress={onCancel}
82
+ icon={<Feather name="x" size={15} color={colors.foreground} />}
83
+ />
84
+ <Button
85
+ label={confirmLabel}
86
+ variant={confirmVariant}
87
+ fullWidth
88
+ onPress={onConfirm}
89
+ icon={
90
+ <Feather
91
+ name={confirmVariant === 'destructive' ? 'trash-2' : 'check'}
92
+ size={15}
93
+ color={
94
+ confirmVariant === 'destructive'
95
+ ? colors.destructiveForeground
96
+ : colors.primaryForeground
97
+ }
98
+ />
99
+ }
100
+ />
49
101
  </View>
50
- </TouchableOpacity>
51
- </Modal>
102
+ </BottomSheetView>
103
+ </BottomSheetModal>
52
104
  )
53
105
  }
54
106
 
55
107
  const styles = StyleSheet.create({
56
- overlay: {
57
- flex: 1,
58
- backgroundColor: 'rgba(0,0,0,0.5)',
59
- justifyContent: 'center',
60
- alignItems: 'center',
61
- padding: s(24),
108
+ background: {
109
+ borderTopLeftRadius: ms(16),
110
+ borderTopRightRadius: ms(16),
111
+ },
112
+ handle: {
113
+ width: s(36),
114
+ height: vs(4),
115
+ borderRadius: ms(2),
62
116
  },
63
- dialog: {
64
- width: '100%',
65
- maxWidth: s(400),
66
- borderRadius: ms(16),
67
- padding: s(24),
117
+ content: {
118
+ paddingHorizontal: s(24),
119
+ paddingBottom: vs(32),
68
120
  gap: vs(12),
69
- shadowColor: '#000',
70
- shadowOffset: { width: 0, height: 8 },
71
- shadowOpacity: 0.15,
72
- shadowRadius: 16,
73
- elevation: 8,
74
121
  },
75
122
  title: {
123
+ fontFamily: 'Poppins-SemiBold',
76
124
  fontSize: ms(18),
77
- fontWeight: '600',
78
125
  lineHeight: mvs(26),
79
126
  },
80
127
  description: {
128
+ fontFamily: 'Poppins-Regular',
81
129
  fontSize: ms(15),
82
130
  lineHeight: mvs(22),
83
131
  },
@@ -43,7 +43,7 @@ export function CurrencyDisplay({ value, prefix = '$', showDecimals = false, tex
43
43
  const styles = StyleSheet.create({
44
44
  container: {},
45
45
  amount: {
46
+ fontFamily: 'Poppins-Bold',
46
47
  fontSize: ms(56),
47
- fontWeight: '700',
48
48
  },
49
49
  })
@@ -20,6 +20,8 @@ export interface CurrencyInputProps {
20
20
  hint?: string
21
21
  placeholder?: string
22
22
  editable?: boolean
23
+ /** Icon or element rendered at the left edge inside the input field. */
24
+ prefixIcon?: React.ReactNode
23
25
  containerStyle?: ViewStyle
24
26
  style?: TextStyle
25
27
  }
@@ -42,31 +44,36 @@ export function CurrencyInput({
42
44
  hint,
43
45
  placeholder,
44
46
  editable,
47
+ prefixIcon,
45
48
  containerStyle,
46
49
  style,
47
50
  }: CurrencyInputProps) {
48
51
  const handleChange = (text: string) => {
49
52
  const withoutPrefix = prefix && text.startsWith(prefix) ? text.slice(prefix.length) : text
50
53
  const formatted = formatCurrency(withoutPrefix, thousandsSeparator)
51
- const display = formatted ? `${prefix}${formatted}` : ''
54
+ const display = formatted
52
55
  onChangeText?.(display)
53
56
  const separatorRegex = new RegExp(`\\${thousandsSeparator}`, 'g')
54
57
  const raw = parseFloat(formatted.replace(separatorRegex, '') || '0')
55
58
  onChangeValue?.(isNaN(raw) ? 0 : raw)
56
59
  }
57
60
 
58
- const inputStyle: TextStyle = size === 'large' ? { fontSize: ms(36) } : {}
61
+ const inputStyle: TextStyle = size === 'large' ? { fontFamily: 'Poppins-Regular', fontSize: ms(36) } : { fontFamily: 'Poppins-Regular' }
62
+
63
+ // Remove prefix from display value if present
64
+ const displayValue = value && prefix && value.startsWith(prefix) ? value.slice(prefix.length) : value
59
65
 
60
66
  return (
61
67
  <Input
62
- value={value}
68
+ value={displayValue}
63
69
  onChangeText={handleChange}
64
70
  keyboardType="numeric"
65
71
  label={label}
66
72
  error={error}
67
73
  hint={hint}
68
- placeholder={placeholder ?? `${prefix}0`}
74
+ placeholder={placeholder ?? '0'}
69
75
  editable={editable}
76
+ prefix={prefixIcon}
70
77
  containerStyle={containerStyle}
71
78
  style={[inputStyle, style]}
72
79
  />
@@ -97,14 +97,15 @@ const styles = StyleSheet.create({
97
97
  maxWidth: s(320),
98
98
  },
99
99
  title: {
100
+ fontFamily: 'Poppins-Medium',
100
101
  fontSize: ms(18),
101
- fontWeight: '500',
102
102
  textAlign: 'center',
103
103
  },
104
104
  titleCompact: {
105
105
  fontSize: ms(15),
106
106
  },
107
107
  description: {
108
+ fontFamily: 'Poppins-Regular',
108
109
  fontSize: ms(14),
109
110
  lineHeight: mvs(20),
110
111
  textAlign: 'center',
@@ -137,40 +137,44 @@ const styles = StyleSheet.create({
137
137
  gap: vs(8),
138
138
  },
139
139
  label: {
140
- fontSize: ms(15),
141
- fontWeight: '500',
140
+ fontFamily: 'Poppins-Medium',
141
+ fontSize: ms(13),
142
142
  },
143
143
  inputWrapper: {
144
144
  flexDirection: 'row',
145
145
  alignItems: 'center',
146
- borderWidth: 1.5,
146
+ borderWidth: 1,
147
147
  borderRadius: ms(8),
148
- paddingHorizontal: s(16),
149
- paddingVertical: vs(14),
148
+ paddingHorizontal: s(14),
149
+ paddingVertical: vs(11),
150
150
  },
151
151
  input: {
152
+ fontFamily: 'Poppins-Regular',
152
153
  flex: 1,
153
- fontSize: ms(17),
154
+ fontSize: ms(15),
154
155
  paddingVertical: 0,
155
156
  },
156
157
  prefixContainer: {
157
158
  marginRight: s(8),
158
159
  },
159
160
  prefixText: {
160
- fontSize: ms(17),
161
+ fontFamily: 'Poppins-Regular',
162
+ fontSize: ms(15),
161
163
  marginRight: s(8),
162
164
  },
163
165
  suffixContainer: {
164
166
  marginLeft: s(8),
165
167
  },
166
168
  suffixText: {
167
- fontSize: ms(17),
169
+ fontFamily: 'Poppins-Regular',
170
+ fontSize: ms(15),
168
171
  marginLeft: s(8),
169
172
  },
170
173
  passwordToggle: {
171
174
  padding: s(4),
172
175
  },
173
176
  helperText: {
177
+ fontFamily: 'Poppins-Regular',
174
178
  fontSize: ms(13),
175
179
  },
176
180
  })
@@ -36,13 +36,14 @@ const styles = StyleSheet.create({
36
36
  gap: s(12),
37
37
  },
38
38
  label: {
39
+ fontFamily: 'Poppins-Regular',
39
40
  fontSize: ms(13),
40
41
  lineHeight: mvs(18),
41
42
  },
42
43
  value: {
43
- fontSize: ms(15),
44
- fontWeight: '500',
45
- lineHeight: mvs(22),
44
+ fontFamily: 'Poppins-Medium',
45
+ fontSize: ms(14),
46
+ lineHeight: mvs(20),
46
47
  textAlign: 'right',
47
48
  },
48
49
  })
@@ -229,7 +229,7 @@ const styles = StyleSheet.create({
229
229
  flexDirection: 'row',
230
230
  alignItems: 'center',
231
231
  paddingHorizontal: s(16),
232
- paddingVertical: vs(14),
232
+ paddingVertical: vs(10),
233
233
  gap: s(12),
234
234
  },
235
235
  leftContainer: {
@@ -244,18 +244,18 @@ const styles = StyleSheet.create({
244
244
  gap: vs(4),
245
245
  },
246
246
  title: {
247
- fontSize: ms(17),
248
- fontWeight: '500',
249
- lineHeight: mvs(24),
247
+ fontFamily: 'Poppins-Medium',
248
+ fontSize: ms(15),
249
+ lineHeight: mvs(22),
250
250
  },
251
251
  subtitle: {
252
- fontSize: ms(14),
253
- fontWeight: '400',
254
- lineHeight: mvs(20),
252
+ fontFamily: 'Poppins-Regular',
253
+ fontSize: ms(13),
254
+ lineHeight: mvs(18),
255
255
  },
256
256
  caption: {
257
+ fontFamily: 'Poppins-Regular',
257
258
  fontSize: ms(12),
258
- fontWeight: '400',
259
259
  lineHeight: mvs(16),
260
260
  opacity: 0.7,
261
261
  },
@@ -266,7 +266,8 @@ const styles = StyleSheet.create({
266
266
  maxWidth: s(160),
267
267
  },
268
268
  rightText: {
269
- fontSize: ms(15),
269
+ fontFamily: 'Poppins-Regular',
270
+ fontSize: ms(14),
270
271
  },
271
272
  chevron: {
272
273
  marginLeft: s(4),
@@ -81,8 +81,8 @@ const styles = StyleSheet.create({
81
81
  justifyContent: 'center',
82
82
  },
83
83
  label: {
84
+ fontFamily: 'Poppins-Medium',
84
85
  fontSize: ms(17),
85
- fontWeight: '500',
86
86
  lineHeight: mvs(24),
87
87
  textAlign: 'center',
88
88
  minWidth: s(160),
@@ -42,43 +42,42 @@ function RadioItem({
42
42
  }
43
43
 
44
44
  return (
45
- <Animated.View style={{ transform: [{ scale }] }}>
46
- <TouchableOpacity
47
- style={styles.row}
48
- onPress={() => {
49
- if (!option.disabled) {
50
- hapticSelection()
51
- onSelect()
52
- }
53
- }}
54
- onPressIn={handlePressIn}
55
- onPressOut={handlePressOut}
56
- activeOpacity={1}
57
- touchSoundDisabled={true}
58
- disabled={option.disabled}
45
+ <TouchableOpacity
46
+ style={styles.row}
47
+ onPress={() => {
48
+ if (!option.disabled) {
49
+ hapticSelection()
50
+ onSelect()
51
+ }
52
+ }}
53
+ onPressIn={handlePressIn}
54
+ onPressOut={handlePressOut}
55
+ activeOpacity={1}
56
+ touchSoundDisabled={true}
57
+ disabled={option.disabled}
58
+ >
59
+ <Animated.View
60
+ style={[
61
+ styles.radio,
62
+ {
63
+ borderColor: selected ? colors.primary : colors.border,
64
+ opacity: option.disabled ? 0.45 : 1,
65
+ transform: [{ scale }],
66
+ },
67
+ ]}
59
68
  >
60
- <View
61
- style={[
62
- styles.radio,
63
- {
64
- borderColor: selected ? colors.primary : colors.border,
65
- opacity: option.disabled ? 0.45 : 1,
66
- },
67
- ]}
68
- >
69
- {selected ? <View style={[styles.dot, { backgroundColor: colors.primary }]} /> : null}
70
- </View>
71
- <Text
72
- style={[
73
- styles.label,
74
- { color: option.disabled ? colors.mutedForeground : colors.foreground },
75
- ]}
76
- allowFontScaling={true}
77
- >
78
- {option.label}
79
- </Text>
80
- </TouchableOpacity>
81
- </Animated.View>
69
+ {selected ? <View style={[styles.dot, { backgroundColor: colors.primary }]} /> : null}
70
+ </Animated.View>
71
+ <Text
72
+ style={[
73
+ styles.label,
74
+ { color: option.disabled ? colors.mutedForeground : colors.foreground },
75
+ ]}
76
+ allowFontScaling={true}
77
+ >
78
+ {option.label}
79
+ </Text>
80
+ </TouchableOpacity>
82
81
  )
83
82
  }
84
83
 
@@ -130,6 +129,7 @@ const styles = StyleSheet.create({
130
129
  borderRadius: s(5),
131
130
  },
132
131
  label: {
132
+ fontFamily: 'Poppins-Regular',
133
133
  fontSize: ms(14),
134
134
  lineHeight: mvs(20),
135
135
  },
@@ -9,6 +9,7 @@ import { s, vs, ms } from '../../utils/scaling'
9
9
  const isIOS = Platform.OS === 'ios'
10
10
  const isAndroid = Platform.OS === 'android'
11
11
  const isWeb = Platform.OS === 'web'
12
+ const nativeDriver = Platform.OS !== 'web'
12
13
 
13
14
  export interface SelectOption {
14
15
  label: string
@@ -47,11 +48,11 @@ export function Select({
47
48
 
48
49
  const handlePressIn = () => {
49
50
  if (disabled) return
50
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: true, speed: 40, bounciness: 0 }).start()
51
+ Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
51
52
  }
52
53
 
53
54
  const handlePressOut = () => {
54
- Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 40, bounciness: 4 }).start()
55
+ Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
55
56
  }
56
57
 
57
58
  const handleOpen = () => {
@@ -231,31 +232,28 @@ const styles = StyleSheet.create({
231
232
  gap: vs(8),
232
233
  },
233
234
  label: {
234
- fontSize: ms(15),
235
- fontWeight: '500',
235
+ fontFamily: 'Poppins-Medium',
236
+ fontSize: ms(13),
236
237
  },
237
238
  trigger: {
238
239
  flexDirection: 'row',
239
240
  alignItems: 'center',
240
241
  justifyContent: 'space-between',
241
- borderWidth: 1.5,
242
+ borderWidth: 1,
242
243
  borderRadius: ms(8),
243
- paddingHorizontal: s(16),
244
- paddingVertical: vs(14),
245
- shadowColor: '#000',
246
- shadowOffset: { width: 0, height: 1 },
247
- shadowOpacity: 0.04,
248
- shadowRadius: 2,
249
- elevation: 1,
244
+ paddingHorizontal: s(14),
245
+ paddingVertical: vs(11),
250
246
  },
251
247
  triggerText: {
252
- fontSize: ms(17),
248
+ fontFamily: 'Poppins-Regular',
249
+ fontSize: ms(15),
253
250
  flex: 1,
254
251
  },
255
252
  chevron: {
256
253
  marginLeft: s(8),
257
254
  },
258
255
  helperText: {
256
+ fontFamily: 'Poppins-Regular',
259
257
  fontSize: ms(13),
260
258
  },
261
259
  iosBackdrop: {
@@ -276,15 +274,15 @@ const styles = StyleSheet.create({
276
274
  borderBottomWidth: 1,
277
275
  },
278
276
  iosToolbarTitle: {
277
+ fontFamily: 'Poppins-SemiBold',
279
278
  fontSize: ms(17),
280
- fontWeight: '600',
281
279
  },
282
280
  iosDoneBtn: {
283
281
  padding: s(4),
284
282
  },
285
283
  iosDoneBtnText: {
284
+ fontFamily: 'Poppins-SemiBold',
286
285
  fontSize: ms(17),
287
- fontWeight: '600',
288
286
  },
289
287
  androidHiddenPicker: {
290
288
  height: 0,
@@ -292,10 +290,10 @@ const styles = StyleSheet.create({
292
290
  position: 'absolute',
293
291
  },
294
292
  webPicker: {
295
- borderWidth: 1.5,
293
+ borderWidth: 1,
296
294
  borderRadius: ms(8),
297
- paddingHorizontal: s(16),
298
- paddingVertical: vs(14),
299
- fontSize: ms(17),
295
+ paddingHorizontal: s(14),
296
+ paddingVertical: vs(11),
297
+ fontSize: ms(15),
300
298
  },
301
299
  })
@@ -106,10 +106,11 @@ const styles = StyleSheet.create({
106
106
  marginBottom: vs(16),
107
107
  },
108
108
  title: {
109
+ fontFamily: 'Poppins-SemiBold',
109
110
  fontSize: ms(18),
110
- fontWeight: '600',
111
111
  },
112
112
  description: {
113
+ fontFamily: 'Poppins-Regular',
113
114
  fontSize: ms(14),
114
115
  lineHeight: mvs(20),
115
116
  },
@@ -91,16 +91,16 @@ const styles = StyleSheet.create({
91
91
  alignItems: 'center',
92
92
  },
93
93
  label: {
94
+ fontFamily: 'Poppins-Medium',
94
95
  fontSize: ms(15),
95
- fontWeight: '500',
96
96
  },
97
97
  valueText: {
98
+ fontFamily: 'Poppins-Medium',
98
99
  fontSize: ms(14),
99
- fontWeight: '500',
100
100
  },
101
101
  slider: {
102
102
  width: '100%',
103
- height: vs(40),
103
+ height: vs(60),
104
104
  },
105
105
  disabled: {
106
106
  opacity: 0.45,
@@ -1,12 +1,14 @@
1
1
  import React from 'react'
2
- import { ActivityIndicator, ActivityIndicatorProps } from 'react-native'
2
+ import { ActivityIndicator, ActivityIndicatorProps, View, Text, StyleSheet } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
+ import { vs, ms, mvs } from '../../utils/scaling'
4
5
 
5
6
  export type SpinnerSize = 'sm' | 'md' | 'lg'
6
7
 
7
8
  export interface SpinnerProps extends Omit<ActivityIndicatorProps, 'size'> {
8
9
  size?: SpinnerSize
9
10
  color?: string
11
+ label?: string
10
12
  }
11
13
 
12
14
  const sizeMap: Record<SpinnerSize, 'small' | 'large'> = {
@@ -15,7 +17,39 @@ const sizeMap: Record<SpinnerSize, 'small' | 'large'> = {
15
17
  lg: 'large',
16
18
  }
17
19
 
18
- export function Spinner({ size = 'md', color, ...props }: SpinnerProps) {
20
+ const labelFontSize: Record<SpinnerSize, number> = {
21
+ sm: ms(11),
22
+ md: ms(13),
23
+ lg: ms(14),
24
+ }
25
+
26
+ export function Spinner({ size = 'md', color, label, ...props }: SpinnerProps) {
19
27
  const { colors } = useTheme()
28
+
29
+ if (label) {
30
+ return (
31
+ <View style={styles.wrapper}>
32
+ <ActivityIndicator size={sizeMap[size]} color={color ?? colors.primary} {...props} />
33
+ <Text
34
+ style={[styles.label, { color: colors.mutedForeground, fontSize: labelFontSize[size] }]}
35
+ allowFontScaling={true}
36
+ >
37
+ {label}
38
+ </Text>
39
+ </View>
40
+ )
41
+ }
42
+
20
43
  return <ActivityIndicator size={sizeMap[size]} color={color ?? colors.primary} {...props} />
21
44
  }
45
+
46
+ const styles = StyleSheet.create({
47
+ wrapper: {
48
+ alignItems: 'center',
49
+ gap: vs(6),
50
+ },
51
+ label: {
52
+ fontFamily: 'Poppins-Regular',
53
+ lineHeight: mvs(18),
54
+ },
55
+ })
@@ -6,10 +6,10 @@ import { selectionAsync as hapticSelection } from '../../utils/haptics'
6
6
  import { useTheme } from '../../theme'
7
7
  import { s, vs } from '../../utils/scaling'
8
8
 
9
- const TRACK_WIDTH = s(60)
10
- const TRACK_HEIGHT = vs(36)
11
- const THUMB_SIZE = s(28)
12
- const THUMB_OFFSET = s(4)
9
+ const TRACK_WIDTH = s(52)
10
+ const TRACK_HEIGHT = s(30)
11
+ const THUMB_SIZE = s(24)
12
+ const THUMB_OFFSET = s(3)
13
13
  const THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2
14
14
 
15
15
  export interface SwitchProps {