@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
@@ -1,8 +1,9 @@
1
1
  import React, { useRef } from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import RNSlider from '@react-native-community/slider'
4
- import * as Haptics from 'expo-haptics'
4
+ import { selectionAsync as hapticSelection } from '../../utils/haptics'
5
5
  import { useTheme } from '../../theme'
6
+ import { vs, ms } from '../../utils/scaling'
6
7
 
7
8
  export interface SliderProps {
8
9
  value?: number
@@ -39,7 +40,7 @@ export function Slider({
39
40
  const handleValueChange = (v: number) => {
40
41
  if (step && v !== lastSteppedValue.current) {
41
42
  lastSteppedValue.current = v
42
- Haptics.selectionAsync()
43
+ hapticSelection()
43
44
  }
44
45
  onValueChange?.(v)
45
46
  }
@@ -82,7 +83,7 @@ export function Slider({
82
83
 
83
84
  const styles = StyleSheet.create({
84
85
  wrapper: {
85
- gap: 8,
86
+ gap: vs(8),
86
87
  },
87
88
  header: {
88
89
  flexDirection: 'row',
@@ -90,16 +91,16 @@ const styles = StyleSheet.create({
90
91
  alignItems: 'center',
91
92
  },
92
93
  label: {
93
- fontSize: 15,
94
+ fontSize: ms(15),
94
95
  fontWeight: '500',
95
96
  },
96
97
  valueText: {
97
- fontSize: 14,
98
+ fontSize: ms(14),
98
99
  fontWeight: '500',
99
100
  },
100
101
  slider: {
101
102
  width: '100%',
102
- height: 40,
103
+ height: vs(40),
103
104
  },
104
105
  disabled: {
105
106
  opacity: 0.45,
@@ -2,13 +2,14 @@ import React, { useEffect, useRef } from 'react'
2
2
  import { TouchableOpacity, Animated, StyleSheet, ViewStyle, Platform, View } from 'react-native'
3
3
 
4
4
  const nativeDriver = Platform.OS !== 'web'
5
- import * as Haptics from 'expo-haptics'
5
+ import { selectionAsync as hapticSelection } from '../../utils/haptics'
6
6
  import { useTheme } from '../../theme'
7
+ import { s, vs } from '../../utils/scaling'
7
8
 
8
- const TRACK_WIDTH = 60
9
- const TRACK_HEIGHT = 36
10
- const THUMB_SIZE = 28
11
- const THUMB_OFFSET = 4
9
+ const TRACK_WIDTH = s(60)
10
+ const TRACK_HEIGHT = vs(36)
11
+ const THUMB_SIZE = s(28)
12
+ const THUMB_OFFSET = s(4)
12
13
  const THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2
13
14
 
14
15
  export interface SwitchProps {
@@ -47,7 +48,7 @@ export function Switch({ checked = false, onCheckedChange, disabled, style }: Sw
47
48
  <View style={[{ opacity: disabled ? 0.45 : 1 }, style]}>
48
49
  <TouchableOpacity
49
50
  onPress={() => {
50
- Haptics.selectionAsync()
51
+ hapticSelection()
51
52
  onCheckedChange?.(!checked)
52
53
  }}
53
54
  disabled={disabled}
@@ -1,7 +1,10 @@
1
1
  import React, { useState, useRef, useEffect } from 'react'
2
- import { View, TouchableOpacity, Text, Animated, StyleSheet, ViewStyle } from 'react-native'
3
- import * as Haptics from 'expo-haptics'
2
+ import { View, TouchableOpacity, Text, Animated, 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 } from '../../utils/scaling'
5
8
 
6
9
  export interface TabItem {
7
10
  label: string
@@ -43,11 +46,11 @@ function TabTrigger({
43
46
  const scale = useRef(new Animated.Value(1)).current
44
47
 
45
48
  const handlePressIn = () => {
46
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: true, speed: 40, bounciness: 0 }).start()
49
+ Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
47
50
  }
48
51
 
49
52
  const handlePressOut = () => {
50
- Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 40, bounciness: 4 }).start()
53
+ Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
51
54
  }
52
55
 
53
56
  return (
@@ -123,7 +126,7 @@ export function Tabs({ tabs, value, onValueChange, children, style }: TabsProps)
123
126
  }, [active])
124
127
 
125
128
  const handlePress = (v: string) => {
126
- Haptics.selectionAsync()
129
+ hapticSelection()
127
130
  if (!value) setInternal(v)
128
131
  onValueChange?.(v)
129
132
  }
@@ -180,16 +183,16 @@ export function TabsContent({ value, activeValue, children, style }: TabsContent
180
183
  const styles = StyleSheet.create({
181
184
  list: {
182
185
  flexDirection: 'row',
183
- borderRadius: 12,
184
- padding: 4,
185
- gap: 4,
186
+ borderRadius: ms(12),
187
+ padding: s(4),
188
+ gap: s(4),
186
189
  },
187
190
  pill: {},
188
191
  trigger: {
189
192
  flex: 1,
190
- paddingVertical: 12,
191
- paddingHorizontal: 16,
192
- borderRadius: 8,
193
+ paddingVertical: vs(12),
194
+ paddingHorizontal: s(16),
195
+ borderRadius: ms(8),
193
196
  alignItems: 'center',
194
197
  justifyContent: 'center',
195
198
  zIndex: 1,
@@ -198,15 +201,15 @@ const styles = StyleSheet.create({
198
201
  flexDirection: 'row',
199
202
  alignItems: 'center',
200
203
  justifyContent: 'center',
201
- gap: 8,
204
+ gap: s(8),
202
205
  },
203
206
  triggerIcon: {
204
- marginRight: 6,
207
+ marginRight: s(6),
205
208
  alignItems: 'center',
206
209
  justifyContent: 'center',
207
210
  },
208
211
  triggerLabel: {
209
- fontSize: 15,
212
+ fontSize: ms(15),
210
213
  fontWeight: '400',
211
214
  },
212
215
  activeTriggerLabel: {
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import { Text as RNText, TextProps as RNTextProps, TextStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
+ import { ms, mvs } from '../../utils/scaling'
4
5
 
5
6
  export type TextVariant = 'h1' | 'h2' | 'h3' | 'body' | 'caption' | 'label'
6
7
 
@@ -10,12 +11,12 @@ export interface TextProps extends RNTextProps {
10
11
  }
11
12
 
12
13
  const variantStyles: Record<TextVariant, TextStyle> = {
13
- h1: { fontSize: 40, fontWeight: '700', lineHeight: 52 },
14
- h2: { fontSize: 28, fontWeight: '700', lineHeight: 36 },
15
- h3: { fontSize: 22, fontWeight: '600', lineHeight: 30 },
16
- body: { fontSize: 17, fontWeight: '400', lineHeight: 26 },
17
- caption: { fontSize: 13, fontWeight: '400', lineHeight: 20 },
18
- label: { fontSize: 15, fontWeight: '500', lineHeight: 22 },
14
+ h1: { fontSize: ms(40), fontWeight: '700', lineHeight: mvs(52) },
15
+ h2: { fontSize: ms(28), fontWeight: '700', lineHeight: mvs(36) },
16
+ h3: { fontSize: ms(22), fontWeight: '600', lineHeight: mvs(30) },
17
+ body: { fontSize: ms(17), fontWeight: '400', lineHeight: mvs(26) },
18
+ caption: { fontSize: ms(13), fontWeight: '400', lineHeight: mvs(20) },
19
+ label: { fontSize: ms(15), fontWeight: '500', lineHeight: mvs(22) },
19
20
  }
20
21
 
21
22
  export function Text({ variant = 'body', color, style, children, ...props }: TextProps) {
@@ -1,6 +1,7 @@
1
1
  import React, { useState } from 'react'
2
2
  import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, Platform } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
+ import { s, vs, ms } from '../../utils/scaling'
4
5
 
5
6
  const webInputResetStyle: any =
6
7
  Platform.OS === 'web'
@@ -50,7 +51,7 @@ export function Textarea({
50
51
  : colors.border,
51
52
  color: colors.foreground,
52
53
  backgroundColor: colors.background,
53
- minHeight: rows * 30,
54
+ minHeight: rows * vs(30),
54
55
  },
55
56
  webInputResetStyle,
56
57
  style,
@@ -79,20 +80,20 @@ export function Textarea({
79
80
 
80
81
  const styles = StyleSheet.create({
81
82
  container: {
82
- gap: 8,
83
+ gap: vs(8),
83
84
  },
84
85
  label: {
85
- fontSize: 15,
86
+ fontSize: ms(15),
86
87
  fontWeight: '500',
87
88
  },
88
89
  input: {
89
90
  borderWidth: 1.5,
90
- borderRadius: 8,
91
- paddingHorizontal: 16,
92
- paddingVertical: 14,
93
- fontSize: 17,
91
+ borderRadius: ms(8),
92
+ paddingHorizontal: s(16),
93
+ paddingVertical: vs(14),
94
+ fontSize: ms(17),
94
95
  },
95
96
  helperText: {
96
- fontSize: 13,
97
+ fontSize: ms(13),
97
98
  },
98
99
  })
@@ -11,8 +11,9 @@ import Animated, {
11
11
  import { scheduleOnRN } from 'react-native-worklets'
12
12
  import { Gesture, GestureDetector } from 'react-native-gesture-handler'
13
13
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
14
- import * as Haptics from 'expo-haptics'
14
+ import { notificationSuccess, notificationError, impactLight } from '../../utils/haptics'
15
15
  import { useTheme } from '../../theme'
16
+ import { s, vs, ms } from '../../utils/scaling'
16
17
 
17
18
  export type ToastVariant = 'default' | 'destructive' | 'success'
18
19
 
@@ -148,11 +149,11 @@ export function ToastProvider({ children }: ToastProviderProps) {
148
149
  const toast = useCallback((item: Omit<ToastItem, 'id'>) => {
149
150
  const id = Math.random().toString(36).slice(2)
150
151
  if (item.variant === 'success') {
151
- Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
152
+ notificationSuccess()
152
153
  } else if (item.variant === 'destructive') {
153
- Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
154
+ notificationError()
154
155
  } else {
155
- Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
156
+ impactLight()
156
157
  }
157
158
  setToasts((prev) => [{ ...item, id }, ...prev].slice(0, 3))
158
159
  }, [])
@@ -176,23 +177,23 @@ export function ToastProvider({ children }: ToastProviderProps) {
176
177
  const styles = StyleSheet.create({
177
178
  container: {
178
179
  position: 'absolute',
179
- left: 16,
180
- right: 16,
181
- gap: 8,
180
+ left: s(16),
181
+ right: s(16),
182
+ gap: vs(8),
182
183
  zIndex: 9999,
183
184
  },
184
185
  containerWeb: {
185
186
  left: undefined,
186
187
  right: undefined,
187
188
  alignSelf: 'center',
188
- width: 400,
189
+ width: s(400),
189
190
  },
190
191
  toast: {
191
192
  flexDirection: 'row',
192
193
  alignItems: 'center',
193
- borderRadius: 16,
194
- paddingHorizontal: 20,
195
- paddingVertical: 14,
194
+ borderRadius: ms(16),
195
+ paddingHorizontal: s(20),
196
+ paddingVertical: vs(14),
196
197
  shadowColor: '#000',
197
198
  shadowOffset: { width: 0, height: 4 },
198
199
  shadowOpacity: 0.15,
@@ -201,23 +202,23 @@ const styles = StyleSheet.create({
201
202
  },
202
203
  toastContent: {
203
204
  flex: 1,
204
- gap: 4,
205
+ gap: vs(4),
205
206
  },
206
207
  leftIconContainer: {
207
- width: 40,
208
+ width: s(40),
208
209
  alignItems: 'center',
209
210
  justifyContent: 'center',
210
- marginRight: 8,
211
+ marginRight: s(8),
211
212
  },
212
213
  toastTitle: {
213
- fontSize: 15,
214
+ fontSize: ms(15),
214
215
  fontWeight: '600',
215
216
  },
216
217
  toastDescription: {
217
- fontSize: 14,
218
+ fontSize: ms(14),
218
219
  },
219
220
  dismissButton: {
220
- padding: 8,
221
- marginLeft: 4,
221
+ padding: s(8),
222
+ marginLeft: s(4),
222
223
  },
223
224
  })
@@ -1,8 +1,9 @@
1
1
  import React, { useRef, useEffect } from 'react'
2
2
  import { TouchableOpacity, Animated, StyleSheet, TouchableOpacityProps, ViewStyle, View, Easing } from 'react-native'
3
3
  import { FontAwesome5 } 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 } from '../../utils/scaling'
6
7
 
7
8
  export type ToggleVariant = 'default' | 'outline'
8
9
  export type ToggleSize = 'sm' | 'md' | 'lg'
@@ -20,9 +21,9 @@ export interface ToggleProps extends TouchableOpacityProps {
20
21
  }
21
22
 
22
23
  const sizeStyles: Record<ToggleSize, ViewStyle> = {
23
- sm: { paddingHorizontal: 12, paddingVertical: 8, minWidth: 40, minHeight: 40 },
24
- md: { paddingHorizontal: 16, paddingVertical: 12, minWidth: 44, minHeight: 44 },
25
- lg: { paddingHorizontal: 20, paddingVertical: 14, minWidth: 48, minHeight: 48 },
24
+ sm: { paddingHorizontal: s(12), paddingVertical: vs(8), minWidth: s(40), minHeight: vs(40) },
25
+ md: { paddingHorizontal: s(16), paddingVertical: vs(12), minWidth: s(44), minHeight: vs(44) },
26
+ lg: { paddingHorizontal: s(20), paddingVertical: vs(14), minWidth: s(48), minHeight: vs(48) },
26
27
  }
27
28
 
28
29
  export function Toggle({
@@ -101,7 +102,7 @@ export function Toggle({
101
102
  <Animated.View style={[{ transform: [{ scale }] }, disabled && styles.disabled, style]}>
102
103
  <TouchableOpacity
103
104
  onPress={() => {
104
- Haptics.selectionAsync()
105
+ hapticSelection()
105
106
  onPressedChange?.(!pressed)
106
107
  }}
107
108
  onPressIn={handlePressIn}
@@ -130,19 +131,19 @@ export function Toggle({
130
131
 
131
132
  const styles = StyleSheet.create({
132
133
  base: {
133
- borderRadius: 8,
134
+ borderRadius: ms(8),
134
135
  },
135
136
  inner: {
136
137
  flexDirection: 'row',
137
138
  alignItems: 'center',
138
139
  justifyContent: 'center',
139
- gap: 8,
140
+ gap: s(8),
140
141
  },
141
142
  disabled: {
142
143
  opacity: 0.45,
143
144
  },
144
145
  label: {
145
- fontSize: 14,
146
+ fontSize: ms(14),
146
147
  fontWeight: '500',
147
148
  },
148
149
  })
@@ -0,0 +1,32 @@
1
+ import { Platform } from 'react-native'
2
+
3
+ /**
4
+ * Web-safe haptics helpers. All calls are no-ops on web since expo-haptics
5
+ * is a native-only module and throws on web.
6
+ */
7
+
8
+ let Haptics: typeof import('expo-haptics') | null = null
9
+
10
+ if (Platform.OS !== 'web') {
11
+ Haptics = require('expo-haptics')
12
+ }
13
+
14
+ export function selectionAsync(): void {
15
+ Haptics?.selectionAsync()
16
+ }
17
+
18
+ export function impactLight(): void {
19
+ Haptics?.impactAsync(Haptics.ImpactFeedbackStyle.Light)
20
+ }
21
+
22
+ export function impactMedium(): void {
23
+ Haptics?.impactAsync(Haptics.ImpactFeedbackStyle.Medium)
24
+ }
25
+
26
+ export function notificationSuccess(): void {
27
+ Haptics?.notificationAsync(Haptics.NotificationFeedbackType.Success)
28
+ }
29
+
30
+ export function notificationError(): void {
31
+ Haptics?.notificationAsync(Haptics.NotificationFeedbackType.Error)
32
+ }
@@ -0,0 +1,26 @@
1
+ import { Platform } from 'react-native'
2
+ import {
3
+ scale,
4
+ verticalScale,
5
+ moderateScale,
6
+ moderateVerticalScale,
7
+ } from 'react-native-size-matters'
8
+
9
+ /**
10
+ * Scaling utilities wrapping react-native-size-matters.
11
+ *
12
+ * On native: scales relative to guideline base 350×680 (~5" mobile).
13
+ * On web: identity functions — no scaling, values used as-is (px in web = pt on a standard display).
14
+ *
15
+ * Usage:
16
+ * s(n) — scale horizontal values (paddingHorizontal, width, gap)
17
+ * vs(n) — scale vertical values (paddingVertical, height, minHeight)
18
+ * ms(n) — moderate scale (fontSize, borderRadius)
19
+ * mvs(n) — moderate vertical scale (lineHeight)
20
+ */
21
+ const isWeb = Platform.OS === 'web'
22
+
23
+ export const s = isWeb ? (n: number) => n : scale
24
+ export const vs = isWeb ? (n: number) => n : verticalScale
25
+ export const ms = isWeb ? (n: number, _factor?: number) => n : moderateScale
26
+ export const mvs = isWeb ? (n: number, _factor?: number) => n : moderateVerticalScale