@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,8 +1,10 @@
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'
7
+ import { renderIcon } from '../../utils/icons'
6
8
 
7
9
  export type ToggleVariant = 'default' | 'outline'
8
10
  export type ToggleSize = 'sm' | 'md' | 'lg'
@@ -17,14 +19,30 @@ export interface ToggleProps extends TouchableOpacityProps {
17
19
  icon?: React.ReactNode | ((pressed: boolean) => React.ReactNode)
18
20
  /** Icon to show when pressed/active. If omitted, a default check mark is used. */
19
21
  activeIcon?: React.ReactNode | ((pressed: boolean) => React.ReactNode)
22
+ /**
23
+ * Icon name from `@expo/vector-icons` shown when not pressed.
24
+ * See https://icons.expo.fyi. Takes precedence over `icon`.
25
+ */
26
+ iconName?: string
27
+ /**
28
+ * Icon name from `@expo/vector-icons` shown when pressed/active.
29
+ * See https://icons.expo.fyi. Takes precedence over `activeIcon`.
30
+ */
31
+ activeIconName?: string
32
+ /** Override the resolved inactive icon color. Defaults to `mutedForeground`. */
33
+ iconColor?: string
34
+ /** Override the resolved active icon color. Defaults to `primary`. */
35
+ activeIconColor?: string
20
36
  }
21
37
 
22
38
  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 },
39
+ sm: { paddingHorizontal: s(12), paddingVertical: vs(8), minWidth: s(40), minHeight: vs(40) },
40
+ md: { paddingHorizontal: s(16), paddingVertical: vs(12), minWidth: s(44), minHeight: vs(44) },
41
+ lg: { paddingHorizontal: s(20), paddingVertical: vs(14), minWidth: s(48), minHeight: vs(48) },
26
42
  }
27
43
 
44
+ const iconSizeMap: Record<ToggleSize, number> = { sm: 16, md: 18, lg: 20 }
45
+
28
46
  export function Toggle({
29
47
  pressed = false,
30
48
  onPressedChange,
@@ -33,6 +51,10 @@ export function Toggle({
33
51
  label,
34
52
  icon,
35
53
  activeIcon,
54
+ iconName,
55
+ activeIconName,
56
+ iconColor,
57
+ activeIconColor,
36
58
  disabled,
37
59
  style,
38
60
  ...props
@@ -77,6 +99,8 @@ export function Toggle({
77
99
  outputRange: [colors.foreground, colors.primary],
78
100
  })
79
101
 
102
+ const iconSize = iconSizeMap[size]
103
+
80
104
  const LeftIcon = () => {
81
105
  const renderProp = (prop?: any) => {
82
106
  if (!prop) return null
@@ -85,23 +109,25 @@ export function Toggle({
85
109
  }
86
110
 
87
111
  if (pressed) {
112
+ if (activeIconName) return <>{renderIcon(activeIconName, iconSize, activeIconColor ?? colors.primary)}</>
88
113
  const active = renderProp(activeIcon)
89
114
  if (active) return <>{active}</>
90
- return <FontAwesome5 name="check-circle" size={20} color={colors.primary} />
115
+ return <FontAwesome5 name="check-circle" size={iconSize} color={colors.primary} />
91
116
  }
92
117
 
118
+ if (iconName) return <>{renderIcon(iconName, iconSize, iconColor ?? colors.mutedForeground)}</>
93
119
  const custom = renderProp(icon)
94
120
  if (custom) return <>{custom}</>
95
121
 
96
122
  // Default: empty circle to signal an action is available
97
- return <FontAwesome5 name="circle" size={20} color={colors.mutedForeground} />
123
+ return <FontAwesome5 name="circle" size={iconSize} color={colors.mutedForeground} />
98
124
  }
99
125
 
100
126
  return (
101
127
  <Animated.View style={[{ transform: [{ scale }] }, disabled && styles.disabled, style]}>
102
128
  <TouchableOpacity
103
129
  onPress={() => {
104
- Haptics.selectionAsync()
130
+ hapticSelection()
105
131
  onPressedChange?.(!pressed)
106
132
  }}
107
133
  onPressIn={handlePressIn}
@@ -130,19 +156,19 @@ export function Toggle({
130
156
 
131
157
  const styles = StyleSheet.create({
132
158
  base: {
133
- borderRadius: 8,
159
+ borderRadius: ms(8),
134
160
  },
135
161
  inner: {
136
162
  flexDirection: 'row',
137
163
  alignItems: 'center',
138
164
  justifyContent: 'center',
139
- gap: 8,
165
+ gap: s(8),
140
166
  },
141
167
  disabled: {
142
168
  opacity: 0.45,
143
169
  },
144
170
  label: {
145
- fontSize: 14,
171
+ fontSize: ms(14),
146
172
  fontWeight: '500',
147
173
  },
148
174
  })
package/src/index.ts CHANGED
@@ -36,3 +36,7 @@ export * from './components/Chip'
36
36
  export * from './components/ConfirmDialog'
37
37
  export * from './components/LabelValue'
38
38
  export * from './components/MonthPicker'
39
+
40
+ // Icon utility
41
+ export { Icon, renderIcon } from './utils/icons'
42
+ export type { IconProps, IconFamily } from './utils/icons'
@@ -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,73 @@
1
+ import React from 'react'
2
+ import AntDesign from '@expo/vector-icons/AntDesign'
3
+ import Entypo from '@expo/vector-icons/Entypo'
4
+ import Feather from '@expo/vector-icons/Feather'
5
+ import FontAwesome5 from '@expo/vector-icons/FontAwesome5'
6
+ import MaterialIcons from '@expo/vector-icons/MaterialIcons'
7
+ import Ionicons from '@expo/vector-icons/Ionicons'
8
+
9
+ export type IconFamily = 'Feather' | 'AntDesign' | 'Entypo' | 'FontAwesome5' | 'MaterialIcons' | 'Ionicons'
10
+
11
+ export interface IconProps {
12
+ /** Icon name from any supported @expo/vector-icons family. See https://icons.expo.fyi */
13
+ name: string
14
+ size: number
15
+ color: string
16
+ /** Override the resolved family when the same name exists in multiple families. */
17
+ family?: IconFamily
18
+ }
19
+
20
+ type ResolvedFamily = {
21
+ name: IconFamily
22
+ component: React.ComponentType<{ name: any; size: number; color: string }>
23
+ glyphMap: Record<string, number>
24
+ }
25
+
26
+ // Priority order: highest-priority family listed last so it overwrites lower-priority entries in the cache
27
+ const ICON_FAMILIES: ResolvedFamily[] = [
28
+ { name: 'Ionicons', component: Ionicons as any, glyphMap: (Ionicons as any).glyphMap },
29
+ { name: 'MaterialIcons', component: MaterialIcons as any, glyphMap: (MaterialIcons as any).glyphMap },
30
+ { name: 'FontAwesome5', component: FontAwesome5 as any, glyphMap: (FontAwesome5 as any).glyphMap },
31
+ { name: 'Entypo', component: Entypo as any, glyphMap: (Entypo as any).glyphMap },
32
+ { name: 'AntDesign', component: AntDesign as any, glyphMap: (AntDesign as any).glyphMap },
33
+ { name: 'Feather', component: Feather as any, glyphMap: (Feather as any).glyphMap },
34
+ ]
35
+
36
+ let resolvedCache: Map<string, ResolvedFamily> | null = null
37
+
38
+ function buildCache(): Map<string, ResolvedFamily> {
39
+ const cache = new Map<string, ResolvedFamily>()
40
+ for (const family of ICON_FAMILIES) {
41
+ if (!family.glyphMap) continue
42
+ for (const iconName of Object.keys(family.glyphMap)) {
43
+ cache.set(iconName, family)
44
+ }
45
+ }
46
+ return cache
47
+ }
48
+
49
+ function resolveFamily(name: string): ResolvedFamily | null {
50
+ if (!resolvedCache) {
51
+ resolvedCache = buildCache()
52
+ }
53
+ return resolvedCache.get(name) ?? null
54
+ }
55
+
56
+ export function Icon({ name, size, color, family }: IconProps): React.ReactElement | null {
57
+ let resolved: ResolvedFamily | null = null
58
+
59
+ if (family) {
60
+ resolved = ICON_FAMILIES.find((f) => f.name === family) ?? null
61
+ } else {
62
+ resolved = resolveFamily(name)
63
+ }
64
+
65
+ if (!resolved) return null
66
+
67
+ const Component = resolved.component
68
+ return React.createElement(Component, { name, size, color })
69
+ }
70
+
71
+ export function renderIcon(name: string, size: number, color: string): React.ReactElement | null {
72
+ return React.createElement(Icon, { name, size, color })
73
+ }
@@ -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