@retray-dev/ui-kit 2.5.1 → 2.5.2

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 (34) hide show
  1. package/COMPONENTS.md +2 -2
  2. package/dist/index.js +374 -362
  3. package/dist/index.mjs +362 -331
  4. package/package.json +23 -21
  5. package/src/components/Accordion/Accordion.tsx +61 -57
  6. package/src/components/Alert/Alert.tsx +11 -10
  7. package/src/components/AlertBanner/AlertBanner.tsx +9 -8
  8. package/src/components/Avatar/Avatar.tsx +9 -8
  9. package/src/components/Badge/Badge.tsx +11 -10
  10. package/src/components/Button/Button.tsx +10 -9
  11. package/src/components/Card/Card.tsx +12 -11
  12. package/src/components/Checkbox/Checkbox.tsx +16 -13
  13. package/src/components/Chip/Chip.tsx +8 -7
  14. package/src/components/ConfirmDialog/ConfirmDialog.tsx +12 -11
  15. package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +2 -1
  16. package/src/components/CurrencyInput/CurrencyInput.tsx +2 -1
  17. package/src/components/EmptyState/EmptyState.tsx +19 -18
  18. package/src/components/Input/Input.tsx +15 -14
  19. package/src/components/LabelValue/LabelValue.tsx +6 -5
  20. package/src/components/ListItem/ListItem.tsx +20 -19
  21. package/src/components/MonthPicker/MonthPicker.tsx +9 -8
  22. package/src/components/Progress/Progress.tsx +2 -1
  23. package/src/components/RadioGroup/RadioGroup.tsx +18 -15
  24. package/src/components/Select/Select.tsx +25 -24
  25. package/src/components/Sheet/Sheet.tsx +15 -14
  26. package/src/components/Slider/Slider.tsx +7 -6
  27. package/src/components/Switch/Switch.tsx +7 -6
  28. package/src/components/Tabs/Tabs.tsx +17 -14
  29. package/src/components/Text/Text.tsx +7 -6
  30. package/src/components/Textarea/Textarea.tsx +9 -8
  31. package/src/components/Toast/Toast.tsx +19 -18
  32. package/src/components/Toggle/Toggle.tsx +9 -8
  33. package/src/utils/haptics.ts +32 -0
  34. package/src/utils/scaling.ts +26 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retray-dev/ui-kit",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
4
4
  "description": "Personal UI Kit for React Native / Expo",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -37,18 +37,19 @@
37
37
  ],
38
38
  "license": "MIT",
39
39
  "peerDependencies": {
40
- "react": ">=17",
41
- "react-native": ">=0.70",
40
+ "@expo/vector-icons": ">=14.0.0",
41
+ "@gorhom/bottom-sheet": ">=5.0.0",
42
+ "@react-native-community/slider": ">=4.0.0",
43
+ "@react-native-picker/picker": ">=2.0.0",
42
44
  "expo-haptics": ">=14.0.0",
43
45
  "expo-linear-gradient": ">=13.0.0",
44
- "@gorhom/bottom-sheet": ">=5.0.0",
45
- "react-native-reanimated": ">=4.0.0",
46
+ "react": ">=17",
47
+ "react-native": ">=0.70",
46
48
  "react-native-gesture-handler": ">=2.0.0",
47
- "react-native-worklets": ">=0.8.0",
49
+ "react-native-reanimated": ">=4.0.0",
48
50
  "react-native-safe-area-context": ">=4.0.0",
49
- "@react-native-picker/picker": ">=2.0.0",
50
- "@react-native-community/slider": ">=4.0.0",
51
- "@expo/vector-icons": ">=14.0.0"
51
+ "react-native-size-matters": ">=0.4.0",
52
+ "react-native-worklets": ">=0.8.0"
52
53
  },
53
54
  "pnpm": {
54
55
  "overrides": {
@@ -62,27 +63,28 @@
62
63
  ]
63
64
  },
64
65
  "devDependencies": {
66
+ "@eslint/js": "^9.0.0",
67
+ "react-native-size-matters": "^0.4.2",
68
+ "@expo/vector-icons": "^15.1.1",
65
69
  "@gorhom/bottom-sheet": "^5.0.0",
66
- "@react-native-picker/picker": "2.11.4",
67
- "@react-native-community/slider": "^4.5.5",
70
+ "@react-native-community/slider": "5.0.1",
71
+ "@react-native-picker/picker": "2.11.1",
68
72
  "@types/react": "^19.1.0",
69
- "@expo/vector-icons": "^15.1.1",
73
+ "eslint": "^9.0.0",
74
+ "eslint-config-prettier": "^10.0.0",
75
+ "eslint-plugin-react": "^7.37.0",
76
+ "eslint-plugin-react-hooks": "^5.0.0",
70
77
  "expo-haptics": "~15.0.8",
71
78
  "expo-linear-gradient": "~15.0.8",
79
+ "prettier": "^3.0.0",
72
80
  "react": "19.1.0",
73
81
  "react-native": "0.81.5",
74
82
  "react-native-gesture-handler": "~2.28.0",
75
83
  "react-native-reanimated": "~4.1.1",
76
- "react-native-worklets": "~0.5.1",
77
84
  "react-native-safe-area-context": "~5.6.2",
78
- "eslint": "^9.0.0",
79
- "@eslint/js": "^9.0.0",
80
- "typescript-eslint": "^8.0.0",
81
- "eslint-plugin-react": "^7.37.0",
82
- "eslint-plugin-react-hooks": "^5.0.0",
83
- "eslint-config-prettier": "^10.0.0",
84
- "prettier": "^3.0.0",
85
+ "react-native-worklets": "~0.5.1",
85
86
  "tsup": "^8.0.0",
86
- "typescript": "^5.4.0"
87
+ "typescript": "^5.4.0",
88
+ "typescript-eslint": "^8.0.0"
87
89
  }
88
90
  }
@@ -1,22 +1,22 @@
1
- import React, { useState, useRef } from 'react'
1
+ import React, { useState } from 'react'
2
2
  import {
3
3
  View,
4
4
  Text,
5
- TouchableOpacity,
6
- Animated,
5
+ Pressable,
7
6
  StyleSheet,
8
- LayoutChangeEvent,
9
7
  ViewStyle,
10
8
  } from 'react-native'
11
- import ReanimatedAnimated, {
9
+ import Animated, {
12
10
  useSharedValue,
11
+ useDerivedValue,
13
12
  useAnimatedStyle,
14
13
  withTiming,
15
14
  Easing,
16
15
  } from 'react-native-reanimated'
17
16
  import { Entypo } from '@expo/vector-icons'
18
- import * as Haptics from 'expo-haptics'
17
+ import { selectionAsync as hapticSelection } from '../../utils/haptics'
19
18
  import { useTheme } from '../../theme'
19
+ import { s, vs, ms } from '../../utils/scaling'
20
20
 
21
21
  export interface AccordionItem {
22
22
  value: string
@@ -46,71 +46,73 @@ function AccordionItemComponent({
46
46
  onToggle: () => void
47
47
  }) {
48
48
  const { colors } = useTheme()
49
- const animatedHeight = useSharedValue(0)
50
- const animatedRotation = useSharedValue(0)
51
- const contentHeight = useRef(0)
52
- const scale = useRef(new Animated.Value(1)).current
53
49
 
54
- const toggle = (open: boolean) => {
55
- const easing = open ? Easing.out(Easing.ease) : Easing.in(Easing.ease)
56
- animatedHeight.value = withTiming(open ? contentHeight.current : 0, { duration: 220, easing })
57
- animatedRotation.value = withTiming(open ? 1 : 0, { duration: 220, easing })
58
- }
50
+ // Shared values all animation lives on the UI thread
51
+ const isExpanded = useSharedValue(isOpen)
52
+ const height = useSharedValue(0)
59
53
 
54
+ // Keep isExpanded in sync with the parent-driven isOpen prop
60
55
  React.useEffect(() => {
61
- toggle(isOpen)
56
+ isExpanded.value = isOpen
62
57
  }, [isOpen])
63
58
 
64
- const onLayout = (e: LayoutChangeEvent) => {
65
- if (contentHeight.current === 0) {
66
- contentHeight.current = e.nativeEvent.layout.height
67
- if (isOpen) animatedHeight.value = contentHeight.current
68
- }
69
- }
59
+ // Derived animated height pattern from Reanimated docs:
60
+ // height * Number(isExpanded) gives 0 when closed and the measured height when open.
61
+ // withTiming wraps it so every change animates automatically.
62
+ const derivedHeight = useDerivedValue(() =>
63
+ withTiming(height.value * Number(isExpanded.value), {
64
+ duration: 220,
65
+ easing: isExpanded.value ? Easing.out(Easing.ease) : Easing.in(Easing.ease),
66
+ })
67
+ )
70
68
 
71
- const heightStyle = useAnimatedStyle(() => ({
72
- height: animatedHeight.value,
69
+ const derivedRotation = useDerivedValue(() =>
70
+ withTiming(isExpanded.value ? 1 : 0, {
71
+ duration: 220,
72
+ easing: isExpanded.value ? Easing.out(Easing.ease) : Easing.in(Easing.ease),
73
+ })
74
+ )
75
+
76
+ const bodyStyle = useAnimatedStyle(() => ({
77
+ height: derivedHeight.value,
73
78
  overflow: 'hidden',
74
79
  }))
75
80
 
76
81
  const rotationStyle = useAnimatedStyle(() => ({
77
- transform: [{ rotate: `${animatedRotation.value * 180}deg` }],
82
+ transform: [{ rotate: `${derivedRotation.value * 180}deg` }],
78
83
  }))
79
84
 
80
- const handlePressIn = () => {
81
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: true, speed: 40, bounciness: 0 }).start()
82
- }
83
-
84
- const handlePressOut = () => {
85
- Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 40, bounciness: 4 }).start()
86
- }
87
-
88
85
  return (
89
86
  <View style={[styles.item, { borderBottomColor: colors.border }]}>
90
- <Animated.View style={{ transform: [{ scale }] }}>
91
- <TouchableOpacity
92
- style={styles.trigger}
93
- onPress={() => {
94
- Haptics.selectionAsync()
95
- onToggle()
87
+ <Pressable
88
+ style={({ pressed }) => [styles.trigger, { opacity: pressed ? 0.6 : 1 }]}
89
+ onPress={() => {
90
+ hapticSelection()
91
+ onToggle()
92
+ }}
93
+ >
94
+ <Text style={[styles.triggerText, { color: colors.foreground }]}>{item.trigger}</Text>
95
+ <Animated.View style={[styles.chevron, rotationStyle]}>
96
+ <Entypo name="chevron-down" size={20} color={colors.foreground} />
97
+ </Animated.View>
98
+ </Pressable>
99
+
100
+ {/*
101
+ The Animated.View height is driven by derivedHeight (0 when closed, full height when open).
102
+ The inner View uses position:'absolute' so onLayout always measures the natural content
103
+ height regardless of the animated wrapper's current height — this is the key pattern
104
+ from the Reanimated docs that prevents the jump.
105
+ */}
106
+ <Animated.View style={bodyStyle}>
107
+ <View
108
+ style={styles.content}
109
+ onLayout={(e) => {
110
+ height.value = e.nativeEvent.layout.height
96
111
  }}
97
- onPressIn={handlePressIn}
98
- onPressOut={handlePressOut}
99
- activeOpacity={1}
100
- touchSoundDisabled={true}
101
112
  >
102
- <Text style={[styles.triggerText, { color: colors.foreground }]}>{item.trigger}</Text>
103
- <ReanimatedAnimated.View style={[styles.chevron, rotationStyle]}>
104
- <Entypo name="chevron-down" size={20} color={colors.foreground} />
105
- </ReanimatedAnimated.View>
106
- </TouchableOpacity>
107
- </Animated.View>
108
-
109
- <ReanimatedAnimated.View style={heightStyle}>
110
- <View style={styles.content} onLayout={onLayout}>
111
113
  {item.content}
112
114
  </View>
113
- </ReanimatedAnimated.View>
115
+ </Animated.View>
114
116
  </View>
115
117
  )
116
118
  }
@@ -153,18 +155,20 @@ const styles = StyleSheet.create({
153
155
  flexDirection: 'row',
154
156
  justifyContent: 'space-between',
155
157
  alignItems: 'center',
156
- paddingVertical: 20,
158
+ paddingVertical: vs(20),
157
159
  },
158
160
  triggerText: {
159
- fontSize: 17,
161
+ fontSize: ms(17),
160
162
  fontWeight: '500',
161
163
  flex: 1,
162
164
  },
163
165
  chevron: {
164
- marginLeft: 8,
166
+ marginLeft: s(8),
165
167
  },
168
+ // position:'absolute' is the key — the inner View escapes the animated wrapper's
169
+ // clipped height so onLayout always reports the true content height.
166
170
  content: {
167
- paddingBottom: 20,
171
+ paddingBottom: vs(20),
168
172
  position: 'absolute',
169
173
  width: '100%',
170
174
  },
@@ -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 type AlertBannerVariant = 'default' | 'destructive' | 'success'
6
7
 
@@ -56,28 +57,28 @@ const styles = StyleSheet.create({
56
57
  container: {
57
58
  flexDirection: 'row',
58
59
  borderWidth: 1,
59
- borderRadius: 8,
60
- padding: 16,
61
- gap: 12,
60
+ borderRadius: ms(8),
61
+ padding: s(16),
62
+ gap: s(12),
62
63
  },
63
64
  icon: {
64
- marginTop: 2,
65
+ marginTop: vs(2),
65
66
  },
66
67
  content: {
67
68
  flex: 1,
68
- gap: 4,
69
+ gap: vs(4),
69
70
  },
70
71
  title: {
71
- fontSize: 14,
72
+ fontSize: ms(14),
72
73
  fontWeight: '500',
73
- lineHeight: 20,
74
+ lineHeight: mvs(20),
74
75
  },
75
76
  description: {
76
- fontSize: 14,
77
- lineHeight: 20,
77
+ fontSize: ms(14),
78
+ lineHeight: mvs(20),
78
79
  },
79
80
  defaultIcon: {
80
- fontSize: 18,
81
+ fontSize: ms(18),
81
82
  fontWeight: '700',
82
83
  },
83
84
  })
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { FontAwesome5, MaterialIcons, Entypo } from '@expo/vector-icons'
4
4
  import { useTheme } from '../../theme'
5
+ import { s, vs, ms, mvs } from '../../utils/scaling'
5
6
 
6
7
  export type AlertBannerVariant = 'default' | 'destructive' | 'success'
7
8
 
@@ -57,9 +58,9 @@ const styles = StyleSheet.create({
57
58
  container: {
58
59
  flexDirection: 'row',
59
60
  borderWidth: 1,
60
- borderRadius: 12,
61
- padding: 16,
62
- gap: 12,
61
+ borderRadius: ms(12),
62
+ padding: s(16),
63
+ gap: s(12),
63
64
  shadowColor: '#000',
64
65
  shadowOffset: { width: 0, height: 4 },
65
66
  shadowOpacity: 0.06,
@@ -71,15 +72,15 @@ const styles = StyleSheet.create({
71
72
  },
72
73
  content: {
73
74
  flex: 1,
74
- gap: 4,
75
+ gap: vs(4),
75
76
  },
76
77
  title: {
77
- fontSize: 14,
78
+ fontSize: ms(14),
78
79
  fontWeight: '500',
79
- lineHeight: 20,
80
+ lineHeight: mvs(20),
80
81
  },
81
82
  description: {
82
- fontSize: 14,
83
- lineHeight: 20,
83
+ fontSize: ms(14),
84
+ lineHeight: mvs(20),
84
85
  },
85
86
  })
@@ -1,6 +1,7 @@
1
1
  import React, { useState } from 'react'
2
2
  import { View, Text, Image, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
+ import { s, ms } from '../../utils/scaling'
4
5
 
5
6
  export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl'
6
7
 
@@ -14,17 +15,17 @@ export interface AvatarProps {
14
15
  }
15
16
 
16
17
  const sizeMap: Record<AvatarSize, number> = {
17
- sm: 28,
18
- md: 40,
19
- lg: 56,
20
- xl: 72,
18
+ sm: s(28),
19
+ md: s(40),
20
+ lg: s(56),
21
+ xl: s(72),
21
22
  }
22
23
 
23
24
  const fontSizeMap: Record<AvatarSize, number> = {
24
- sm: 12,
25
- md: 16,
26
- lg: 22,
27
- xl: 28,
25
+ sm: ms(12),
26
+ md: ms(16),
27
+ lg: ms(22),
28
+ xl: ms(28),
28
29
  }
29
30
 
30
31
  export function Avatar({ src, fallback, size = 'md', style }: AvatarProps) {
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle, TextStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
+ import { s, vs, ms } from '../../utils/scaling'
4
5
 
5
6
  export type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline'
6
7
  export type BadgeSize = 'sm' | 'md' | 'lg'
@@ -17,21 +18,21 @@ export interface BadgeProps {
17
18
  }
18
19
 
19
20
  const sizePadding: Record<BadgeSize, ViewStyle> = {
20
- sm: { paddingHorizontal: 8, paddingVertical: 2 },
21
- md: { paddingHorizontal: 10, paddingVertical: 4 },
22
- lg: { paddingHorizontal: 12, paddingVertical: 6 },
21
+ sm: { paddingHorizontal: s(8), paddingVertical: vs(2) },
22
+ md: { paddingHorizontal: s(10), paddingVertical: vs(4) },
23
+ lg: { paddingHorizontal: s(12), paddingVertical: vs(6) },
23
24
  }
24
25
 
25
26
  const sizeFontSize: Record<BadgeSize, TextStyle> = {
26
- sm: { fontSize: 11 },
27
- md: { fontSize: 13 },
28
- lg: { fontSize: 15 },
27
+ sm: { fontSize: ms(11) },
28
+ md: { fontSize: ms(13) },
29
+ lg: { fontSize: ms(15) },
29
30
  }
30
31
 
31
32
  const sizeIconGap: Record<BadgeSize, number> = {
32
- sm: 4,
33
- md: 6,
34
- lg: 6,
33
+ sm: s(4),
34
+ md: s(6),
35
+ lg: s(6),
35
36
  }
36
37
 
37
38
  export function Badge({ label, children, variant = 'default', size = 'md', icon, style }: BadgeProps) {
@@ -69,7 +70,7 @@ export function Badge({ label, children, variant = 'default', size = 'md', icon,
69
70
 
70
71
  const styles = StyleSheet.create({
71
72
  container: {
72
- borderRadius: 6,
73
+ borderRadius: ms(6),
73
74
  alignSelf: 'flex-start',
74
75
  flexDirection: 'row',
75
76
  alignItems: 'center',
@@ -12,8 +12,9 @@ import {
12
12
  } from 'react-native'
13
13
 
14
14
  const nativeDriver = Platform.OS !== 'web'
15
- import * as Haptics from 'expo-haptics'
15
+ import { impactLight } from '../../utils/haptics'
16
16
  import { useTheme } from '../../theme'
17
+ import { s, vs, ms } from '../../utils/scaling'
17
18
 
18
19
  export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive'
19
20
  export type ButtonSize = 'sm' | 'md' | 'lg'
@@ -38,15 +39,15 @@ export interface ButtonProps extends TouchableOpacityProps {
38
39
  }
39
40
 
40
41
  const containerSizeStyles: Record<ButtonSize, ViewStyle> = {
41
- sm: { paddingHorizontal: 20, paddingVertical: 10 },
42
- md: { paddingHorizontal: 24, paddingVertical: 14 },
43
- lg: { paddingHorizontal: 32, paddingVertical: 18 },
42
+ sm: { paddingHorizontal: s(20), paddingVertical: vs(10) },
43
+ md: { paddingHorizontal: s(24), paddingVertical: vs(14) },
44
+ lg: { paddingHorizontal: s(32), paddingVertical: vs(18) },
44
45
  }
45
46
 
46
47
  const labelSizeStyles: Record<ButtonSize, TextStyle> = {
47
- sm: { fontSize: 15 },
48
- md: { fontSize: 17 },
49
- lg: { fontSize: 18 },
48
+ sm: { fontSize: ms(15) },
49
+ md: { fontSize: ms(17) },
50
+ lg: { fontSize: ms(18) },
50
51
  }
51
52
 
52
53
  export function Button({
@@ -81,7 +82,7 @@ export function Button({
81
82
  }
82
83
 
83
84
  const handlePress: TouchableOpacityProps['onPress'] = (e) => {
84
- Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
85
+ impactLight()
85
86
  onPress?.(e)
86
87
  }
87
88
 
@@ -156,6 +157,6 @@ const styles = StyleSheet.create({
156
157
  fontWeight: '600',
157
158
  },
158
159
  labelWithIcon: {
159
- marginHorizontal: 8,
160
+ marginHorizontal: s(8),
160
161
  },
161
162
  })
@@ -1,7 +1,8 @@
1
1
  import React, { useRef } from 'react'
2
2
  import { View, Text, TouchableOpacity, Animated, StyleSheet, ViewStyle, TextStyle, Platform } from 'react-native'
3
- import * as Haptics from 'expo-haptics'
3
+ import { impactLight } from '../../utils/haptics'
4
4
  import { useTheme } from '../../theme'
5
+ import { s, vs, ms, mvs } from '../../utils/scaling'
5
6
 
6
7
  const nativeDriver = Platform.OS !== 'web'
7
8
 
@@ -67,7 +68,7 @@ export function Card({ children, variant = 'elevated', onPress, style }: CardPro
67
68
 
68
69
  const handlePress = () => {
69
70
  if (!onPress) return
70
- Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
71
+ impactLight()
71
72
  onPress()
72
73
  }
73
74
 
@@ -146,28 +147,28 @@ export function CardFooter({ children, style }: CardFooterProps) {
146
147
 
147
148
  const styles = StyleSheet.create({
148
149
  card: {
149
- borderRadius: 12,
150
+ borderRadius: ms(12),
150
151
  borderWidth: 1,
151
152
  },
152
153
  header: {
153
- padding: 24,
154
+ padding: s(24),
154
155
  paddingBottom: 0,
155
- gap: 8,
156
+ gap: vs(8),
156
157
  },
157
158
  title: {
158
- fontSize: 20,
159
+ fontSize: ms(20),
159
160
  fontWeight: '600',
160
- lineHeight: 28,
161
+ lineHeight: mvs(28),
161
162
  },
162
163
  description: {
163
- fontSize: 15,
164
- lineHeight: 22,
164
+ fontSize: ms(15),
165
+ lineHeight: mvs(22),
165
166
  },
166
167
  content: {
167
- padding: 24,
168
+ padding: s(24),
168
169
  },
169
170
  footer: {
170
- padding: 24,
171
+ padding: s(24),
171
172
  paddingTop: 0,
172
173
  flexDirection: 'row',
173
174
  alignItems: 'center',
@@ -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
  })