@retray-dev/ui-kit 2.5.1 → 2.6.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 (38) hide show
  1. package/COMPONENTS.md +153 -6
  2. package/dist/index.d.mts +98 -8
  3. package/dist/index.d.ts +98 -8
  4. package/dist/index.js +591 -505
  5. package/dist/index.mjs +533 -436
  6. package/package.json +23 -21
  7. package/src/components/Accordion/Accordion.tsx +61 -57
  8. package/src/components/Alert/Alert.tsx +11 -10
  9. package/src/components/AlertBanner/AlertBanner.tsx +23 -10
  10. package/src/components/Avatar/Avatar.tsx +9 -8
  11. package/src/components/Badge/Badge.tsx +27 -12
  12. package/src/components/Button/Button.tsx +30 -12
  13. package/src/components/Card/Card.tsx +12 -11
  14. package/src/components/Checkbox/Checkbox.tsx +16 -13
  15. package/src/components/Chip/Chip.tsx +8 -7
  16. package/src/components/ConfirmDialog/ConfirmDialog.tsx +12 -11
  17. package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +2 -1
  18. package/src/components/CurrencyInput/CurrencyInput.tsx +2 -1
  19. package/src/components/EmptyState/EmptyState.tsx +34 -21
  20. package/src/components/Input/Input.tsx +44 -22
  21. package/src/components/LabelValue/LabelValue.tsx +6 -5
  22. package/src/components/ListItem/ListItem.tsx +46 -22
  23. package/src/components/MonthPicker/MonthPicker.tsx +9 -8
  24. package/src/components/Progress/Progress.tsx +2 -1
  25. package/src/components/RadioGroup/RadioGroup.tsx +18 -15
  26. package/src/components/Select/Select.tsx +25 -24
  27. package/src/components/Sheet/Sheet.tsx +15 -14
  28. package/src/components/Slider/Slider.tsx +7 -6
  29. package/src/components/Switch/Switch.tsx +7 -6
  30. package/src/components/Tabs/Tabs.tsx +17 -14
  31. package/src/components/Text/Text.tsx +7 -6
  32. package/src/components/Textarea/Textarea.tsx +9 -8
  33. package/src/components/Toast/Toast.tsx +30 -19
  34. package/src/components/Toggle/Toggle.tsx +36 -10
  35. package/src/index.ts +4 -0
  36. package/src/utils/haptics.ts +32 -0
  37. package/src/utils/icons.ts +73 -0
  38. package/src/utils/scaling.ts +26 -0
@@ -1,7 +1,10 @@
1
1
  import React, { useRef } from 'react'
2
- import { TouchableOpacity, Animated, View, Text, StyleSheet, ViewStyle } from 'react-native'
3
- import * as Haptics from 'expo-haptics'
2
+ import { TouchableOpacity, Animated, View, Text, StyleSheet, ViewStyle, Platform } from 'react-native'
3
+ import { selectionAsync as hapticSelection } from '../../utils/haptics'
4
+
5
+ const nativeDriver = Platform.OS !== 'web'
4
6
  import { useTheme } from '../../theme'
7
+ import { s, vs, ms, mvs } from '../../utils/scaling'
5
8
 
6
9
  export interface CheckboxProps {
7
10
  checked?: boolean
@@ -23,11 +26,11 @@ export function Checkbox({
23
26
 
24
27
  const handlePressIn = () => {
25
28
  if (disabled) return
26
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: true, speed: 40, bounciness: 0 }).start()
29
+ Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
27
30
  }
28
31
 
29
32
  const handlePressOut = () => {
30
- Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 40, bounciness: 4 }).start()
33
+ Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
31
34
  }
32
35
 
33
36
  return (
@@ -35,7 +38,7 @@ export function Checkbox({
35
38
  <TouchableOpacity
36
39
  style={[styles.row, style]}
37
40
  onPress={() => {
38
- Haptics.selectionAsync()
41
+ hapticSelection()
39
42
  onCheckedChange?.(!checked)
40
43
  }}
41
44
  onPressIn={handlePressIn}
@@ -74,25 +77,25 @@ const styles = StyleSheet.create({
74
77
  row: {
75
78
  flexDirection: 'row',
76
79
  alignItems: 'center',
77
- gap: 12,
80
+ gap: s(12),
78
81
  },
79
82
  box: {
80
- width: 24,
81
- height: 24,
82
- borderRadius: 8,
83
+ width: s(24),
84
+ height: s(24),
85
+ borderRadius: ms(8),
83
86
  borderWidth: 1.5,
84
87
  alignItems: 'center',
85
88
  justifyContent: 'center',
86
89
  },
87
90
  checkmark: {
88
- width: 12,
89
- height: 7,
91
+ width: s(12),
92
+ height: vs(7),
90
93
  borderLeftWidth: 2,
91
94
  borderBottomWidth: 2,
92
95
  transform: [{ rotate: '-45deg' }, { translateY: -1 }],
93
96
  },
94
97
  label: {
95
- fontSize: 15,
96
- lineHeight: 22,
98
+ fontSize: ms(15),
99
+ lineHeight: mvs(22),
97
100
  },
98
101
  })
@@ -9,8 +9,9 @@ import {
9
9
  Platform,
10
10
  Easing,
11
11
  } from 'react-native'
12
- import * as Haptics from 'expo-haptics'
12
+ import { selectionAsync as hapticSelection } from '../../utils/haptics'
13
13
  import { useTheme } from '../../theme'
14
+ import { s, vs, ms, mvs } from '../../utils/scaling'
14
15
 
15
16
  const nativeDriver = Platform.OS !== 'web'
16
17
 
@@ -68,7 +69,7 @@ export function Chip({ label, selected = false, onPress, style }: ChipProps) {
68
69
  }
69
70
 
70
71
  const handlePress = () => {
71
- Haptics.selectionAsync()
72
+ hapticSelection()
72
73
  onPress?.()
73
74
  }
74
75
 
@@ -154,20 +155,20 @@ const styles = StyleSheet.create({
154
155
  wrapper: {},
155
156
  chip: {
156
157
  borderRadius: 999,
157
- paddingHorizontal: 14,
158
- paddingVertical: 8,
158
+ paddingHorizontal: s(14),
159
+ paddingVertical: vs(8),
159
160
  borderWidth: 1.5,
160
161
  alignItems: 'center',
161
162
  justifyContent: 'center',
162
163
  },
163
164
  label: {
164
- fontSize: 14,
165
+ fontSize: ms(14),
165
166
  fontWeight: '500',
166
- lineHeight: 20,
167
+ lineHeight: mvs(20),
167
168
  },
168
169
  group: {
169
170
  flexDirection: 'row',
170
171
  flexWrap: 'wrap',
171
- gap: 8,
172
+ gap: s(8),
172
173
  },
173
174
  })
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import { Modal, View, Text, StyleSheet, TouchableOpacity } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
4
  import { Button } from '../Button'
5
+ import { s, vs, ms, mvs } from '../../utils/scaling'
5
6
 
6
7
  export interface ConfirmDialogProps {
7
8
  visible: boolean
@@ -57,14 +58,14 @@ const styles = StyleSheet.create({
57
58
  backgroundColor: 'rgba(0,0,0,0.5)',
58
59
  justifyContent: 'center',
59
60
  alignItems: 'center',
60
- padding: 24,
61
+ padding: s(24),
61
62
  },
62
63
  dialog: {
63
64
  width: '100%',
64
- maxWidth: 400,
65
- borderRadius: 16,
66
- padding: 24,
67
- gap: 12,
65
+ maxWidth: s(400),
66
+ borderRadius: ms(16),
67
+ padding: s(24),
68
+ gap: vs(12),
68
69
  shadowColor: '#000',
69
70
  shadowOffset: { width: 0, height: 8 },
70
71
  shadowOpacity: 0.15,
@@ -72,16 +73,16 @@ const styles = StyleSheet.create({
72
73
  elevation: 8,
73
74
  },
74
75
  title: {
75
- fontSize: 18,
76
+ fontSize: ms(18),
76
77
  fontWeight: '600',
77
- lineHeight: 26,
78
+ lineHeight: mvs(26),
78
79
  },
79
80
  description: {
80
- fontSize: 15,
81
- lineHeight: 22,
81
+ fontSize: ms(15),
82
+ lineHeight: mvs(22),
82
83
  },
83
84
  actions: {
84
- gap: 10,
85
- marginTop: 8,
85
+ gap: vs(10),
86
+ marginTop: vs(8),
86
87
  },
87
88
  })
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
+ import { ms } from '../../utils/scaling'
4
5
 
5
6
  export interface CurrencyDisplayProps {
6
7
  value: number | string
@@ -42,7 +43,7 @@ export function CurrencyDisplay({ value, prefix = '$', showDecimals = false, tex
42
43
  const styles = StyleSheet.create({
43
44
  container: {},
44
45
  amount: {
45
- fontSize: 56,
46
+ fontSize: ms(56),
46
47
  fontWeight: '700',
47
48
  },
48
49
  })
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import { ViewStyle, TextStyle } from 'react-native'
3
3
  import { Input } from '../Input'
4
+ import { ms } from '../../utils/scaling'
4
5
 
5
6
  export interface CurrencyInputProps {
6
7
  value?: string
@@ -54,7 +55,7 @@ export function CurrencyInput({
54
55
  onChangeValue?.(isNaN(raw) ? 0 : raw)
55
56
  }
56
57
 
57
- const inputStyle: TextStyle = size === 'large' ? { fontSize: 36 } : {}
58
+ const inputStyle: TextStyle = size === 'large' ? { fontSize: ms(36) } : {}
58
59
 
59
60
  return (
60
61
  <Input
@@ -1,9 +1,18 @@
1
1
  import React from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
+ import { s, vs, ms, mvs } from '../../utils/scaling'
5
+ import { renderIcon } from '../../utils/icons'
4
6
 
5
7
  export interface EmptyStateProps {
6
8
  icon?: React.ReactNode
9
+ /**
10
+ * Icon name from `@expo/vector-icons`. See https://icons.expo.fyi.
11
+ * Takes precedence over `icon`. Sized automatically to fit the slot (48 default, 32 compact).
12
+ */
13
+ iconName?: string
14
+ /** Override the resolved icon color. Defaults to `mutedForeground`. */
15
+ iconColor?: string
7
16
  title: string
8
17
  description?: string
9
18
  action?: React.ReactNode
@@ -12,10 +21,14 @@ export interface EmptyStateProps {
12
21
  style?: ViewStyle
13
22
  }
14
23
 
15
- export function EmptyState({ icon, title, description, action, size = 'default', style }: EmptyStateProps) {
24
+ export function EmptyState({ icon, iconName, iconColor, title, description, action, size = 'default', style }: EmptyStateProps) {
16
25
  const { colors } = useTheme()
17
26
  const isCompact = size === 'compact'
18
27
 
28
+ const effectiveIcon: React.ReactNode = iconName
29
+ ? renderIcon(iconName, isCompact ? 32 : 48, iconColor ?? colors.mutedForeground)
30
+ : icon
31
+
19
32
  return (
20
33
  <View
21
34
  style={[
@@ -25,7 +38,7 @@ export function EmptyState({ icon, title, description, action, size = 'default',
25
38
  style,
26
39
  ]}
27
40
  >
28
- {icon ? (
41
+ {effectiveIcon ? (
29
42
  <View
30
43
  style={[
31
44
  styles.iconWrapper,
@@ -33,7 +46,7 @@ export function EmptyState({ icon, title, description, action, size = 'default',
33
46
  { backgroundColor: colors.muted },
34
47
  ]}
35
48
  >
36
- {icon}
49
+ {effectiveIcon}
37
50
  </View>
38
51
  ) : null}
39
52
  <View style={styles.textWrapper}>
@@ -58,45 +71,45 @@ const styles = StyleSheet.create({
58
71
  justifyContent: 'center',
59
72
  borderWidth: 1,
60
73
  borderStyle: 'dashed',
61
- borderRadius: 12,
62
- padding: 32,
63
- gap: 16,
74
+ borderRadius: ms(12),
75
+ padding: s(32),
76
+ gap: vs(16),
64
77
  },
65
78
  containerCompact: {
66
- padding: 20,
67
- gap: 10,
79
+ padding: s(20),
80
+ gap: vs(10),
68
81
  },
69
82
  iconWrapper: {
70
- width: 48,
71
- height: 48,
72
- borderRadius: 12,
83
+ width: s(48),
84
+ height: s(48),
85
+ borderRadius: ms(12),
73
86
  alignItems: 'center',
74
87
  justifyContent: 'center',
75
88
  },
76
89
  iconWrapperCompact: {
77
- width: 36,
78
- height: 36,
79
- borderRadius: 8,
90
+ width: s(36),
91
+ height: s(36),
92
+ borderRadius: ms(8),
80
93
  },
81
94
  textWrapper: {
82
95
  alignItems: 'center',
83
- gap: 8,
84
- maxWidth: 320,
96
+ gap: vs(8),
97
+ maxWidth: s(320),
85
98
  },
86
99
  title: {
87
- fontSize: 18,
100
+ fontSize: ms(18),
88
101
  fontWeight: '500',
89
102
  textAlign: 'center',
90
103
  },
91
104
  titleCompact: {
92
- fontSize: 15,
105
+ fontSize: ms(15),
93
106
  },
94
107
  description: {
95
- fontSize: 14,
96
- lineHeight: 20,
108
+ fontSize: ms(14),
109
+ lineHeight: mvs(20),
97
110
  textAlign: 'center',
98
111
  },
99
112
  action: {
100
- marginTop: 8,
113
+ marginTop: vs(8),
101
114
  },
102
115
  })
@@ -2,6 +2,8 @@ import React, { useState } from 'react'
2
2
  import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, TextStyle, TouchableOpacity, Platform } from 'react-native'
3
3
  import { AntDesign } from '@expo/vector-icons'
4
4
  import { useTheme } from '../../theme'
5
+ import { s, vs, ms } from '../../utils/scaling'
6
+ import { renderIcon } from '../../utils/icons'
5
7
 
6
8
  const webInputResetStyle: any =
7
9
  Platform.OS === 'web'
@@ -22,13 +24,27 @@ export interface InputProps extends TextInputProps {
22
24
  prefixStyle?: TextStyle
23
25
  /** Style applied to suffix text if suffix is a string. */
24
26
  suffixStyle?: TextStyle
27
+ /**
28
+ * Icon name from `@expo/vector-icons` rendered before the input text.
29
+ * See https://icons.expo.fyi. Takes precedence over `prefix`.
30
+ */
31
+ prefixIcon?: string
32
+ /**
33
+ * Icon name from `@expo/vector-icons` rendered after the input text.
34
+ * See https://icons.expo.fyi. Takes precedence over `suffix` (unless `type="password"`).
35
+ */
36
+ suffixIcon?: string
37
+ /** Override the resolved prefix icon color. Defaults to `mutedForeground`. */
38
+ prefixIconColor?: string
39
+ /** Override the resolved suffix icon color. Defaults to `mutedForeground`. */
40
+ suffixIconColor?: string
25
41
  /** Input type. When set to \`'password'\`, shows a toggle button to reveal/hide text. */
26
42
  type?: 'text' | 'password'
27
43
  /** Style for the outer container \`View\`. Use \`style\` (from \`TextInputProps\`) to style the \`TextInput\` itself. */
28
44
  containerStyle?: ViewStyle
29
45
  }
30
46
 
31
- export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixStyle, type = 'text', containerStyle, style, onFocus, onBlur, secureTextEntry, ...props }: InputProps) {
47
+ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixStyle, prefixIcon, suffixIcon, prefixIconColor, suffixIconColor, type = 'text', containerStyle, style, onFocus, onBlur, secureTextEntry, ...props }: InputProps) {
32
48
  const { colors } = useTheme()
33
49
  const [focused, setFocused] = useState(false)
34
50
  const [showPassword, setShowPassword] = useState(false)
@@ -36,12 +52,18 @@ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixS
36
52
  const isPassword = type === 'password'
37
53
  const effectiveSecure = isPassword ? !showPassword : secureTextEntry
38
54
 
39
- // If type is password and no suffix is provided, add the toggle button
40
- const effectiveSuffix = isPassword && !suffix ? (
55
+ const effectivePrefix: React.ReactNode = prefixIcon
56
+ ? renderIcon(prefixIcon, 20, prefixIconColor ?? colors.mutedForeground)
57
+ : prefix
58
+
59
+ // If type is password and no suffix override is provided, add the toggle button
60
+ const effectiveSuffix: React.ReactNode = isPassword && !suffix && !suffixIcon ? (
41
61
  <TouchableOpacity onPress={() => setShowPassword(!showPassword)} style={styles.passwordToggle} activeOpacity={0.6}>
42
62
  <AntDesign name={showPassword ? 'eye' : 'eye-invisible'} size={20} color={colors.mutedForeground} />
43
63
  </TouchableOpacity>
44
- ) : suffix
64
+ ) : suffixIcon
65
+ ? renderIcon(suffixIcon, 20, suffixIconColor ?? colors.mutedForeground)
66
+ : suffix
45
67
 
46
68
  return (
47
69
  <View style={[styles.container, containerStyle]}>
@@ -59,13 +81,13 @@ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixS
59
81
  },
60
82
  ]}
61
83
  >
62
- {prefix ? (
63
- typeof prefix === 'string' ? (
84
+ {effectivePrefix ? (
85
+ typeof effectivePrefix === 'string' ? (
64
86
  <Text style={[styles.prefixText, { color: colors.mutedForeground }, prefixStyle]} allowFontScaling={true}>
65
- {prefix}
87
+ {effectivePrefix}
66
88
  </Text>
67
89
  ) : (
68
- <View style={styles.prefixContainer}>{prefix}</View>
90
+ <View style={styles.prefixContainer}>{effectivePrefix}</View>
69
91
  )
70
92
  ) : null}
71
93
  <TextInput
@@ -112,43 +134,43 @@ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixS
112
134
 
113
135
  const styles = StyleSheet.create({
114
136
  container: {
115
- gap: 8,
137
+ gap: vs(8),
116
138
  },
117
139
  label: {
118
- fontSize: 15,
140
+ fontSize: ms(15),
119
141
  fontWeight: '500',
120
142
  },
121
143
  inputWrapper: {
122
144
  flexDirection: 'row',
123
145
  alignItems: 'center',
124
146
  borderWidth: 1.5,
125
- borderRadius: 8,
126
- paddingHorizontal: 16,
127
- paddingVertical: 14,
147
+ borderRadius: ms(8),
148
+ paddingHorizontal: s(16),
149
+ paddingVertical: vs(14),
128
150
  },
129
151
  input: {
130
152
  flex: 1,
131
- fontSize: 17,
153
+ fontSize: ms(17),
132
154
  paddingVertical: 0,
133
155
  },
134
156
  prefixContainer: {
135
- marginRight: 8,
157
+ marginRight: s(8),
136
158
  },
137
159
  prefixText: {
138
- fontSize: 17,
139
- marginRight: 8,
160
+ fontSize: ms(17),
161
+ marginRight: s(8),
140
162
  },
141
163
  suffixContainer: {
142
- marginLeft: 8,
164
+ marginLeft: s(8),
143
165
  },
144
166
  suffixText: {
145
- fontSize: 17,
146
- marginLeft: 8,
167
+ fontSize: ms(17),
168
+ marginLeft: s(8),
147
169
  },
148
170
  passwordToggle: {
149
- padding: 4,
171
+ padding: s(4),
150
172
  },
151
173
  helperText: {
152
- fontSize: 13,
174
+ fontSize: ms(13),
153
175
  },
154
176
  })
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
+ import { s, vs, ms, mvs } from '../../utils/scaling'
4
5
 
5
6
  export interface LabelValueProps {
6
7
  label: string
@@ -32,16 +33,16 @@ const styles = StyleSheet.create({
32
33
  flexDirection: 'row',
33
34
  justifyContent: 'space-between',
34
35
  alignItems: 'center',
35
- gap: 12,
36
+ gap: s(12),
36
37
  },
37
38
  label: {
38
- fontSize: 13,
39
- lineHeight: 18,
39
+ fontSize: ms(13),
40
+ lineHeight: mvs(18),
40
41
  },
41
42
  value: {
42
- fontSize: 15,
43
+ fontSize: ms(15),
43
44
  fontWeight: '500',
44
- lineHeight: 22,
45
+ lineHeight: mvs(22),
45
46
  textAlign: 'right',
46
47
  },
47
48
  })
@@ -10,8 +10,10 @@ import {
10
10
  Platform,
11
11
  } from 'react-native'
12
12
  import { Entypo } from '@expo/vector-icons'
13
- import * as Haptics from 'expo-haptics'
13
+ import { selectionAsync as hapticSelection } from '../../utils/haptics'
14
14
  import { useTheme } from '../../theme'
15
+ import { s, vs, ms, mvs } from '../../utils/scaling'
16
+ import { renderIcon } from '../../utils/icons'
15
17
 
16
18
  const nativeDriver = Platform.OS !== 'web'
17
19
 
@@ -32,6 +34,20 @@ export interface ListItemProps {
32
34
  trailing?: React.ReactNode | string
33
35
  /** @deprecated Use `leftRender` instead. */
34
36
  icon?: React.ReactNode
37
+ /**
38
+ * Icon name from `@expo/vector-icons` rendered in the left slot.
39
+ * See https://icons.expo.fyi. Takes precedence over `leftRender`.
40
+ */
41
+ leftIcon?: string
42
+ /**
43
+ * Icon name from `@expo/vector-icons` rendered in the right slot.
44
+ * See https://icons.expo.fyi. Takes precedence over `rightRender`.
45
+ */
46
+ rightIcon?: string
47
+ /** Override the resolved left icon color. Defaults to `foreground`. */
48
+ leftIconColor?: string
49
+ /** Override the resolved right icon color. Defaults to `mutedForeground`. */
50
+ rightIconColor?: string
35
51
 
36
52
  title: string
37
53
  /** Secondary line below the title. */
@@ -68,6 +84,10 @@ export function ListItem({
68
84
  rightRender,
69
85
  trailing,
70
86
  icon,
87
+ leftIcon,
88
+ rightIcon,
89
+ leftIconColor,
90
+ rightIconColor,
71
91
  title,
72
92
  subtitle,
73
93
  caption,
@@ -104,13 +124,17 @@ export function ListItem({
104
124
  }
105
125
 
106
126
  const handlePress = () => {
107
- Haptics.selectionAsync()
127
+ hapticSelection()
108
128
  onPress?.()
109
129
  }
110
130
 
111
- // Support legacy props
112
- const effectiveLeft = leftRender ?? icon
113
- const effectiveRight = rightRender ?? trailing
131
+ const effectiveLeft: React.ReactNode = leftIcon
132
+ ? renderIcon(leftIcon, 24, leftIconColor ?? colors.foreground)
133
+ : leftRender ?? icon
134
+
135
+ const effectiveRight: React.ReactNode | string | undefined = rightIcon
136
+ ? renderIcon(rightIcon, 24, rightIconColor ?? colors.mutedForeground)
137
+ : rightRender ?? trailing
114
138
 
115
139
  const cardStyle: ViewStyle =
116
140
  variant === 'card'
@@ -192,7 +216,7 @@ export function ListItem({
192
216
  <View
193
217
  style={[
194
218
  styles.separator,
195
- { backgroundColor: colors.border, marginLeft: effectiveLeft ? 16 + 44 + 12 : 16 },
219
+ { backgroundColor: colors.border, marginLeft: effectiveLeft ? s(16) + s(44) + s(12) : s(16) },
196
220
  ]}
197
221
  />
198
222
  ) : null}
@@ -204,52 +228,52 @@ const styles = StyleSheet.create({
204
228
  container: {
205
229
  flexDirection: 'row',
206
230
  alignItems: 'center',
207
- paddingHorizontal: 16,
208
- paddingVertical: 14,
209
- gap: 12,
231
+ paddingHorizontal: s(16),
232
+ paddingVertical: vs(14),
233
+ gap: s(12),
210
234
  },
211
235
  leftContainer: {
212
- width: 44,
213
- height: 44,
236
+ width: s(44),
237
+ height: s(44),
214
238
  alignItems: 'center',
215
239
  justifyContent: 'center',
216
240
  flexShrink: 0,
217
241
  },
218
242
  content: {
219
243
  flex: 1,
220
- gap: 4,
244
+ gap: vs(4),
221
245
  },
222
246
  title: {
223
- fontSize: 17,
247
+ fontSize: ms(17),
224
248
  fontWeight: '500',
225
- lineHeight: 24,
249
+ lineHeight: mvs(24),
226
250
  },
227
251
  subtitle: {
228
- fontSize: 14,
252
+ fontSize: ms(14),
229
253
  fontWeight: '400',
230
- lineHeight: 20,
254
+ lineHeight: mvs(20),
231
255
  },
232
256
  caption: {
233
- fontSize: 12,
257
+ fontSize: ms(12),
234
258
  fontWeight: '400',
235
- lineHeight: 16,
259
+ lineHeight: mvs(16),
236
260
  opacity: 0.7,
237
261
  },
238
262
  rightContainer: {
239
263
  alignItems: 'flex-end',
240
264
  justifyContent: 'center',
241
265
  flexShrink: 0,
242
- maxWidth: 160,
266
+ maxWidth: s(160),
243
267
  },
244
268
  rightText: {
245
- fontSize: 15,
269
+ fontSize: ms(15),
246
270
  },
247
271
  chevron: {
248
- marginLeft: 4,
272
+ marginLeft: s(4),
249
273
  },
250
274
  separator: {
251
275
  height: StyleSheet.hairlineWidth,
252
- marginRight: 16,
276
+ marginRight: s(16),
253
277
  },
254
278
  disabled: {
255
279
  opacity: 0.45,
@@ -1,8 +1,9 @@
1
1
  import React from 'react'
2
2
  import { View, Text, TouchableOpacity, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { Entypo } from '@expo/vector-icons'
4
- import * as Haptics from 'expo-haptics'
4
+ import { selectionAsync as hapticSelection } from '../../utils/haptics'
5
5
  import { useTheme } from '../../theme'
6
+ import { s, vs, ms, mvs } from '../../utils/scaling'
6
7
 
7
8
  const MONTH_NAMES = [
8
9
  'January', 'February', 'March', 'April', 'May', 'June',
@@ -25,7 +26,7 @@ export function MonthPicker({ value, onChange, style }: MonthPickerProps) {
25
26
  const { colors } = useTheme()
26
27
 
27
28
  const handlePrev = () => {
28
- Haptics.selectionAsync()
29
+ hapticSelection()
29
30
  if (value.month === 1) {
30
31
  onChange({ month: 12, year: value.year - 1 })
31
32
  } else {
@@ -34,7 +35,7 @@ export function MonthPicker({ value, onChange, style }: MonthPickerProps) {
34
35
  }
35
36
 
36
37
  const handleNext = () => {
37
- Haptics.selectionAsync()
38
+ hapticSelection()
38
39
  if (value.month === 12) {
39
40
  onChange({ month: 1, year: value.year + 1 })
40
41
  } else {
@@ -74,16 +75,16 @@ const styles = StyleSheet.create({
74
75
  justifyContent: 'space-between',
75
76
  },
76
77
  arrow: {
77
- width: 44,
78
- height: 44,
78
+ width: s(44),
79
+ height: s(44),
79
80
  alignItems: 'center',
80
81
  justifyContent: 'center',
81
82
  },
82
83
  label: {
83
- fontSize: 17,
84
+ fontSize: ms(17),
84
85
  fontWeight: '500',
85
- lineHeight: 24,
86
+ lineHeight: mvs(24),
86
87
  textAlign: 'center',
87
- minWidth: 160,
88
+ minWidth: s(160),
88
89
  },
89
90
  })