@retray-dev/ui-kit 13.0.0 → 13.2.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 (132) hide show
  1. package/CONSUMER.md +24 -9
  2. package/README.md +9 -10
  3. package/{COMPONENTS.md → SKILL.md} +296 -860
  4. package/dist/Accordion.d.mts +2 -0
  5. package/dist/Accordion.d.ts +2 -0
  6. package/dist/Accordion.js +1 -0
  7. package/dist/Accordion.mjs +1 -2
  8. package/dist/AlertBanner.mjs +0 -1
  9. package/dist/AppHeader.d.mts +5 -2
  10. package/dist/AppHeader.d.ts +5 -2
  11. package/dist/AppHeader.js +8 -4
  12. package/dist/AppHeader.mjs +1 -2
  13. package/dist/Avatar.mjs +0 -1
  14. package/dist/Badge.mjs +0 -1
  15. package/dist/Button.mjs +0 -1
  16. package/dist/ButtonGroup.mjs +0 -1
  17. package/dist/Card.mjs +0 -1
  18. package/dist/CategoryStrip.mjs +0 -1
  19. package/dist/Checkbox.d.mts +2 -1
  20. package/dist/Checkbox.d.ts +2 -1
  21. package/dist/Checkbox.js +3 -1
  22. package/dist/Checkbox.mjs +1 -2
  23. package/dist/Chip.mjs +0 -1
  24. package/dist/ConfirmDialog.d.mts +2 -1
  25. package/dist/ConfirmDialog.d.ts +2 -1
  26. package/dist/ConfirmDialog.js +26 -14
  27. package/dist/ConfirmDialog.mjs +1 -2
  28. package/dist/CurrencyDisplay.mjs +0 -1
  29. package/dist/CurrencyInput.mjs +0 -1
  30. package/dist/DetailRow.mjs +0 -1
  31. package/dist/EmptyState.mjs +0 -1
  32. package/dist/ErrorBoundary.mjs +0 -1
  33. package/dist/Form.mjs +0 -1
  34. package/dist/HolographicCard.mjs +0 -1
  35. package/dist/IconButton.mjs +0 -1
  36. package/dist/IconPicker.mjs +0 -1
  37. package/dist/ImageUpload.d.mts +1 -3
  38. package/dist/ImageUpload.d.ts +1 -3
  39. package/dist/ImageUpload.js +27 -26
  40. package/dist/ImageUpload.mjs +1 -2
  41. package/dist/ImageViewer.mjs +0 -1
  42. package/dist/Input.mjs +0 -1
  43. package/dist/LabelValue.mjs +0 -1
  44. package/dist/ListGroup.mjs +0 -1
  45. package/dist/ListItem.d.mts +2 -1
  46. package/dist/ListItem.d.ts +2 -1
  47. package/dist/ListItem.js +3 -1
  48. package/dist/ListItem.mjs +1 -2
  49. package/dist/MediaCard.mjs +0 -1
  50. package/dist/MenuGroup.mjs +0 -1
  51. package/dist/MenuItem.d.mts +2 -1
  52. package/dist/MenuItem.d.ts +2 -1
  53. package/dist/MenuItem.js +3 -1
  54. package/dist/MenuItem.mjs +1 -2
  55. package/dist/MonthPicker.mjs +0 -1
  56. package/dist/NumberStepper.d.mts +2 -1
  57. package/dist/NumberStepper.d.ts +2 -1
  58. package/dist/NumberStepper.js +4 -1
  59. package/dist/NumberStepper.mjs +1 -2
  60. package/dist/PagerDots.mjs +0 -1
  61. package/dist/Pressable.mjs +0 -1
  62. package/dist/PricingCard.mjs +0 -1
  63. package/dist/Progress.mjs +0 -1
  64. package/dist/RadioGroup.mjs +0 -1
  65. package/dist/RetrayProvider.mjs +0 -1
  66. package/dist/Select.d.mts +2 -1
  67. package/dist/Select.d.ts +2 -1
  68. package/dist/Select.js +3 -1
  69. package/dist/Select.mjs +1 -2
  70. package/dist/SelectableCard.mjs +0 -1
  71. package/dist/SelectableGrid.js +0 -1
  72. package/dist/SelectableGrid.mjs +1 -2
  73. package/dist/Separator.mjs +0 -1
  74. package/dist/Sheet.d.mts +1 -1
  75. package/dist/Sheet.d.ts +1 -1
  76. package/dist/Sheet.js +26 -13
  77. package/dist/Sheet.mjs +1 -2
  78. package/dist/SheetSelect.mjs +0 -1
  79. package/dist/Skeleton.mjs +0 -1
  80. package/dist/Slider.d.mts +2 -1
  81. package/dist/Slider.d.ts +2 -1
  82. package/dist/Slider.js +2 -0
  83. package/dist/Slider.mjs +1 -2
  84. package/dist/Spinner.mjs +0 -1
  85. package/dist/Stats.mjs +0 -1
  86. package/dist/Switch.d.mts +2 -1
  87. package/dist/Switch.d.ts +2 -1
  88. package/dist/Switch.js +2 -1
  89. package/dist/Switch.mjs +1 -2
  90. package/dist/TabBar.mjs +0 -1
  91. package/dist/Tabs.mjs +0 -1
  92. package/dist/Text.mjs +0 -1
  93. package/dist/Textarea.mjs +0 -1
  94. package/dist/Toast.d.mts +12 -10
  95. package/dist/Toast.d.ts +12 -10
  96. package/dist/Toast.mjs +0 -1
  97. package/dist/Toggle.mjs +0 -1
  98. package/dist/{chunk-TETMEKZE.mjs → chunk-2QXJDRVU.mjs} +4 -1
  99. package/dist/{chunk-CBIZLRYH.mjs → chunk-422IVD3H.mjs} +1 -0
  100. package/dist/{chunk-4ZO5PTKF.mjs → chunk-77UOVFIS.mjs} +3 -1
  101. package/dist/{chunk-2QOHHBJC.mjs → chunk-7BZJRB77.mjs} +25 -15
  102. package/dist/{chunk-UOKFSFNJ.mjs → chunk-C5ZRMR2E.mjs} +2 -0
  103. package/dist/{chunk-E4EQSCKR.mjs → chunk-COA2YZOX.mjs} +3 -1
  104. package/dist/{chunk-6QLBHUEG.mjs → chunk-CZN6L2QU.mjs} +3 -1
  105. package/dist/{chunk-BTUW5LSG.mjs → chunk-E2PONRJG.mjs} +2 -1
  106. package/dist/{chunk-6CR4S6W2.mjs → chunk-H6MQL7PS.mjs} +9 -4
  107. package/dist/{chunk-EROPDCB5.mjs → chunk-HHOOFDBA.mjs} +26 -21
  108. package/dist/{chunk-URIH43IJ.mjs → chunk-IDVUZIVY.mjs} +3 -1
  109. package/dist/{chunk-MP7GLMIR.mjs → chunk-NPCBNGNE.mjs} +0 -1
  110. package/dist/{chunk-V2ZB2XNS.mjs → chunk-UMZTPUB3.mjs} +27 -15
  111. package/dist/fonts.mjs +0 -2
  112. package/dist/index.d.mts +1 -1
  113. package/dist/index.d.ts +1 -1
  114. package/dist/index.js +108 -64
  115. package/dist/index.mjs +13 -14
  116. package/package.json +14 -12
  117. package/src/components/Accordion/Accordion.tsx +3 -0
  118. package/src/components/AppHeader/AppHeader.tsx +25 -10
  119. package/src/components/Checkbox/Checkbox.tsx +3 -0
  120. package/src/components/ConfirmDialog/ConfirmDialog.tsx +30 -14
  121. package/src/components/ImageUpload/ImageUpload.tsx +33 -25
  122. package/src/components/ListItem/ListItem.tsx +3 -0
  123. package/src/components/MenuItem/MenuItem.tsx +3 -0
  124. package/src/components/NumberStepper/NumberStepper.tsx +4 -0
  125. package/src/components/Select/Select.tsx +3 -0
  126. package/src/components/SelectableGrid/SelectableGrid.tsx +0 -1
  127. package/src/components/Sheet/Sheet.tsx +27 -14
  128. package/src/components/Sheet/index.ts +2 -2
  129. package/src/components/Slider/Slider.tsx +3 -0
  130. package/src/components/Switch/Switch.tsx +3 -1
  131. package/src/components/Text/Text.tsx +1 -0
  132. package/dist/chunk-Y6FXYEAI.mjs +0 -8
@@ -1,836 +1,431 @@
1
- # @retray-dev/ui-kit — Component Reference (v13.0.0)
1
+ ---
2
+ name: retray-ui-kit
3
+ description: >
4
+ Complete guide for @retray-dev/ui-kit — a React Native / Expo component
5
+ library (~55 components). Airbnb-inspired design, Sohne typography,
6
+ haptic-rich interactions, animated with pressto + react-native-ease.
7
+ Theme system, BottomSheet patterns, icon resolution, and conventions.
8
+ ---
2
9
 
3
- This file is the AI reference for this package. Add all three lines below to your project's `CLAUDE.md` to give Claude full context components, setup guide, and usage examples:
10
+ # @retray-dev/ui-kitAgent Skill
4
11
 
5
- ```markdown
6
- ## UI Components
7
- @./node_modules/@retray-dev/ui-kit/COMPONENTS.md
8
- @./node_modules/@retray-dev/ui-kit/CONSUMER.md
9
- @./node_modules/@retray-dev/ui-kit/EXAMPLES.md
10
- ```
12
+ > **Recommended — copy to repo root (no dependency on node_modules existing):**
13
+ > ```bash
14
+ > cp node_modules/@retray-dev/ui-kit/SKILL.md SKILL.md
15
+ > cp node_modules/@retray-dev/ui-kit/CONSUMER.md CONSUMER.md
16
+ > cp node_modules/@retray-dev/ui-kit/EXAMPLES.md EXAMPLES.md
17
+ > ```
18
+ >
19
+ > Then add to `CLAUDE.md`:
20
+ > ```markdown
21
+ > ## UI Kit
22
+ > @./SKILL.md
23
+ > @./CONSUMER.md
24
+ > @./EXAMPLES.md
25
+ > ```
11
26
 
12
27
  ---
13
28
 
14
- ## Setup (Required)
15
-
16
- ### Recommended: `RetrayProvider` (one wrapper)
29
+ ## Required Setup
17
30
 
18
- Since v8, a single `RetrayProvider` wires all five required providers in the correct order — use this and skip the manual nesting:
31
+ ### Providers
32
+ Use `RetrayProvider` (single wrapper) or manual tree. Order is mandatory:
19
33
 
20
- ```tsx
21
- import { RetrayProvider } from '@retray-dev/ui-kit'
22
-
23
- export default function App() {
24
- const [fontsLoaded] = useFonts(SohneFonts)
25
- if (!fontsLoaded) return null
26
- return (
27
- <RetrayProvider colorScheme="system">
28
- {/* your app */}
29
- </RetrayProvider>
30
- )
31
- }
32
34
  ```
33
-
34
- ### Manual (equivalent) — if you need a custom tree
35
-
36
- `RetrayProvider` is exactly this; the individual providers stay exported. Order is mandatory:
37
-
38
- ```tsx
39
- import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'
40
- import { GestureHandlerRootView } from 'react-native-gesture-handler'
41
- import { ThemeProvider, BottomSheetModalProvider, ToastProvider } from '@retray-dev/ui-kit'
42
-
43
- export default function App() {
44
- return (
45
- <SafeAreaProvider initialMetrics={initialWindowMetrics}>
46
- <GestureHandlerRootView style={{ flex: 1 }}>
47
- <ThemeProvider colorScheme="system">
48
- <BottomSheetModalProvider>
49
- <ToastProvider>
50
- {/* your app */}
51
- </ToastProvider>
52
- </BottomSheetModalProvider>
53
- </ThemeProvider>
54
- </GestureHandlerRootView>
55
- </SafeAreaProvider>
56
- )
57
- }
35
+ SafeAreaProvider > GestureHandlerRootView > ThemeProvider > BottomSheetModalProvider > ToastProvider
58
36
  ```
59
37
 
60
- **Provider order is mandatory:**
61
- - `SafeAreaProvider` must be outermost — required by `@gorhom/bottom-sheet`
62
- - `initialMetrics={initialWindowMetrics}` is required on Android to avoid a "No safe area value available" crash on first render
63
- - `GestureHandlerRootView` must wrap everything — required by `@gorhom/bottom-sheet`
64
- - `BottomSheetModalProvider` must be inside `GestureHandlerRootView`
65
- - `ToastProvider` wraps children and renders `Toaster` (from `sonner-native`) internally
66
-
67
- ### Peer dependencies
38
+ `initialMetrics={initialWindowMetrics}` required on Android.
68
39
 
69
- Install all required peers (Expo projects: swap `pnpm add` for `npx expo install` to get SDK-pinned versions):
70
-
71
- ```bash
72
- pnpm add expo-font expo-haptics expo-linear-gradient react-native-safe-area-context @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler react-native-worklets @react-native-picker/picker @react-native-community/slider @expo/vector-icons react-native-size-matters react-native-svg react-native-screens sonner-native pressto react-native-ease
73
- ```
74
-
75
- Then add the Worklets Babel plugin to `babel.config.js` (required by `@gorhom/bottom-sheet` and the pressable animations):
40
+ ### Peer Dependencies (all required unless noted)
41
+ - **Hard:** `expo-haptics`, `expo-linear-gradient`, `expo-font`, `expo-image`, `react-native-reanimated`, `react-native-gesture-handler`, `react-native-worklets`, `react-native-safe-area-context`, `react-native-screens`, `react-native-svg`, `@gorhom/bottom-sheet`, `@react-native-picker/picker`, `@react-native-community/slider`, `@expo/vector-icons`, `react-native-size-matters`, `sonner-native`, `react-native-ease`, `pressto`
42
+ - **Optional:** `react-native-image-picker` (ImageUpload), `@shopify/react-native-skia` + `expo-sensors` (HolographicCard deep-import only)
76
43
 
44
+ ### Babel Config
77
45
  ```js
78
- module.exports = function (api) {
79
- api.cache(true)
80
- return {
81
- presets: ['babel-preset-expo'],
82
- plugins: ['react-native-worklets/plugin'], // NOT react-native-reanimated/plugin
83
- }
84
- }
85
- ```
86
-
87
- | Peer | Required? | Used by | Notes |
88
- |------|-----------|---------|-------|
89
- | `pressto` | **Required** | Every interactive component | Press-scale animations (`src/utils/pressable.ts`). Static import via the barrel — omitting it crashes the module load, not just one component. |
90
- | `sonner-native` | **Required** | `Toast` | Also pulls `react-native-svg` + `react-native-screens`. |
91
- | `react-native-reanimated` (≥4.0) | **Required** | Animations, `Sheet`, pressables | Needs the `react-native-worklets/plugin` Babel plugin. |
92
- | `react-native-gesture-handler` | **Required** | `Sheet`, pressables | Wrap app in `GestureHandlerRootView`. |
93
- | `@gorhom/bottom-sheet` (≥5.2.0) | **Required** | `Sheet`, `ConfirmDialog` | 5.1.x crashes on Reanimated v4. |
94
- | `expo-haptics` | **Required** | Haptic feedback (all interactions) | Web-safe wrapper, no-op on web. |
95
- | `expo-font` | **Required** | All `Text` | Load `SohneFonts` at app root before rendering. |
96
- | `expo-linear-gradient` | **Required** | `Skeleton` shimmer | |
97
- | `@react-native-picker/picker` | **Required** | `Select` | |
98
- | `@react-native-community/slider` | **Required** | `Slider` | |
99
- | `@expo/vector-icons` | **Required** | Icons everywhere | |
100
- | `react-native-size-matters` | **Required** | Responsive scaling | |
101
- | `react-native-ease` | **Required** | `Checkbox`, `Chip`, `CategoryStrip`, `Switch`, `RadioGroup`, `Toggle` | Declarative `EaseView` animation layer. Static import in source — omitting it crashes the module load. `pnpm add react-native-ease` |
102
-
103
- | `@shopify/react-native-skia` + `expo-sensors` | **Optional** | Deep-import `HolographicCard` only | `pnpm add @shopify/react-native-skia expo-sensors` |
104
-
105
-
106
-
107
- ### Troubleshooting: `react-native-screens` codegen on RN 0.83
108
-
109
- Toast pulls `sonner-native`, which imports `react-native-screens` via its `src/*` path. On **React Native 0.83** that source trips a `@react-native/codegen` parse error (`CT.WithDefault` not parseable). It is a `react-native-screens` issue, not the kit — but since Toast surfaces it, add this resolver to your `metro.config.js` to force the compiled `lib/commonjs` build:
110
-
111
- ```js
112
- // metro.config.js
113
- const { getDefaultConfig } = require('expo/metro-config')
114
- const config = getDefaultConfig(__dirname)
115
-
116
- const defaultResolveRequest = config.resolver.resolveRequest
117
- config.resolver.resolveRequest = (context, moduleName, platform) => {
118
- // Force react-native-screens to its compiled output, bypassing the src codegen bug.
119
- if (moduleName.startsWith('react-native-screens/src')) {
120
- moduleName = moduleName.replace('react-native-screens/src', 'react-native-screens/lib/commonjs')
121
- }
122
- return (defaultResolveRequest ?? context.resolveRequest)(context, moduleName, platform)
123
- }
124
-
125
- module.exports = config
126
- ```
127
-
128
- ## Typography — Sohne (Required)
129
-
130
- All components use **Sohne** as the font family. You **must** load it before rendering any UI kit component.
131
-
132
- ### How it works
133
-
134
- 1. When you install `@retray-dev/ui-kit`, the **postinstall script** automatically copies 28 `.otf` font files to `assets/fonts/sohne/` in your project root
135
- 2. You must define `SohneFonts` with static `require()` calls in your `App.tsx` (see boilerplate below)
136
- 3. Pass it to `expo-font`'s `useFonts()` hook at your app root
137
- 4. All library components reference fonts by family name (e.g., `fontFamily: 'Sohne-SemiBold'`)
138
-
139
- ### SohneFonts boilerplate — copy this into your App.tsx
140
-
141
- ```tsx
142
- import { useFonts } from 'expo-font'
143
-
144
- // Fonts copied to assets/fonts/sohne/ by @retray-dev/ui-kit postinstall
145
- const SohneFonts = {
146
- 'Sohne-ExtraLight': require('./assets/fonts/sohne/Sohne-ExtraLight.otf'),
147
- 'Sohne-ExtraLightItalic': require('./assets/fonts/sohne/Sohne-ExtraLightItalic.otf'),
148
- 'Sohne-Light': require('./assets/fonts/sohne/Sohne-Light.otf'),
149
- 'Sohne-LightItalic': require('./assets/fonts/sohne/Sohne-LightItalic.otf'),
150
- 'Sohne-Regular': require('./assets/fonts/sohne/Sohne-Regular.otf'),
151
- 'Sohne-Italic': require('./assets/fonts/sohne/Sohne-Italic.otf'),
152
- 'Sohne-Medium': require('./assets/fonts/sohne/Sohne-Medium.otf'),
153
- 'Sohne-MediumItalic': require('./assets/fonts/sohne/Sohne-MediumItalic.otf'),
154
- 'Sohne-SemiBold': require('./assets/fonts/sohne/Sohne-SemiBold.otf'),
155
- 'Sohne-SemiBoldItalic': require('./assets/fonts/sohne/Sohne-SemiBoldItalic.otf'),
156
- 'Sohne-Bold': require('./assets/fonts/sohne/Sohne-Bold.otf'),
157
- 'Sohne-BoldItalic': require('./assets/fonts/sohne/Sohne-BoldItalic.otf'),
158
- 'Sohne-ExtraBold': require('./assets/fonts/sohne/Sohne-ExtraBold.otf'),
159
- 'Sohne-ExtraBoldItalic': require('./assets/fonts/sohne/Sohne-ExtraBoldItalic.otf'),
160
- 'SohneMono-ExtraLight': require('./assets/fonts/sohne/SohneMono-ExtraLight.otf'),
161
- 'SohneMono-ExtraLightItalic': require('./assets/fonts/sohne/SohneMono-ExtraLightItalic.otf'),
162
- 'SohneMono-Light': require('./assets/fonts/sohne/SohneMono-Light.otf'),
163
- 'SohneMono-LightItalic': require('./assets/fonts/sohne/SohneMono-LightItalic.otf'),
164
- 'SohneMono-Regular': require('./assets/fonts/sohne/SohneMono-Regular.otf'),
165
- 'SohneMono-Italic': require('./assets/fonts/sohne/SohneMono-Italic.otf'),
166
- 'SohneMono-Medium': require('./assets/fonts/sohne/SohneMono-Medium.otf'),
167
- 'SohneMono-MediumItalic': require('./assets/fonts/sohne/SohneMono-MediumItalic.otf'),
168
- 'SohneMono-SemiBold': require('./assets/fonts/sohne/SohneMono-SemiBold.otf'),
169
- 'SohneMono-SemiBoldItalic': require('./assets/fonts/sohne/SohneMono-SemiBoldItalic.otf'),
170
- 'SohneMono-Bold': require('./assets/fonts/sohne/SohneMono-Bold.otf'),
171
- 'SohneMono-BoldItalic': require('./assets/fonts/sohne/SohneMono-BoldItalic.otf'),
172
- 'SohneMono-ExtraBold': require('./assets/fonts/sohne/SohneMono-ExtraBold.otf'),
173
- 'SohneMono-ExtraBoldItalic': require('./assets/fonts/sohne/SohneMono-ExtraBoldItalic.otf'),
174
- }
175
-
176
- export default function App() {
177
- const [fontsLoaded] = useFonts(SohneFonts)
178
- if (!fontsLoaded) return null
179
- return (
180
- // ... your providers and app
181
- )
182
- }
183
- ```
184
-
185
- ### .gitignore recommendation
186
-
187
- Fonts are copied to `assets/fonts/sohne/` on install. You can either:
188
- - **Commit them** (no network needed during CI builds)
189
- - **Ignore them** (re-copied on every `pnpm install`)
190
-
191
- ```gitignore
192
- # Sohne fonts — copied by @retray-dev/ui-kit postinstall
193
- # Either commit these or ignore them (re-copied on install)
194
- assets/fonts/sohne/
195
- ```
196
-
197
- **Included weights (28 files):**
198
- - Sohne: `Sohne-ExtraLight`, `Sohne-Light`, `Sohne-Regular`, `Sohne-Medium`, `Sohne-SemiBold`, `Sohne-Bold`, `Sohne-ExtraBold` + italic variants
199
- - SohneMono: `SohneMono-ExtraLight`, `SohneMono-Light`, `SohneMono-Regular`, `SohneMono-Medium`, `SohneMono-SemiBold`, `SohneMono-Bold`, `SohneMono-ExtraBold` + italic variants
200
-
201
- Pair with `expo-splash-screen` in production:
202
- ```tsx
203
- import * as SplashScreen from 'expo-splash-screen'
204
- SplashScreen.preventAutoHideAsync()
205
-
206
- useEffect(() => {
207
- if (fontsLoaded) SplashScreen.hideAsync()
208
- }, [fontsLoaded])
46
+ plugins: ['react-native-worklets/plugin']
47
+ // NOT react-native-reanimated/plugin
209
48
  ```
210
49
 
211
- ---
212
-
213
- ## Theme System
214
-
215
- ### ThemeProvider Props
216
-
217
- | Prop | Type | Default | Notes |
218
- |------|------|---------|-------|
219
- | colorScheme | `'light' \| 'dark' \| 'system'` | `'system'` | `'system'` auto-detects device setting and updates when it changes |
220
- | theme | `{ light?: Partial<ThemeColors>, dark?: Partial<ThemeColors> }` | — | Override any subset of the 12 public tokens per scheme |
221
-
222
- **Custom theme example:**
223
- ```tsx
224
- const myTheme = {
225
- light: { primary: '#ff385c', primaryForeground: '#ffffff' },
226
- dark: { primary: '#ff385c', primaryForeground: '#ffffff' },
227
- }
228
- <ThemeProvider theme={myTheme} colorScheme="system">
229
- ```
230
-
231
- ### useTheme Hook
232
-
233
- ```tsx
234
- import { useTheme } from '@retray-dev/ui-kit'
235
-
236
- function MyComponent() {
237
- const { colors, colorScheme } = useTheme()
238
- return <View style={{ backgroundColor: colors.background }} />
239
- }
240
- ```
241
-
242
- Returns `colors` (full `ResolvedColors` palette) and `colorScheme` (`'light' | 'dark'`).
50
+ ### Fonts (Sohne)
51
+ Postinstall copies 28 `.otf` to `assets/fonts/sohne/`. Consumer defines static `require()` calls in App.tsx with `expo-font`'s `useFonts()`.
243
52
 
244
53
  ---
245
54
 
246
- ## Theme Tokens
55
+ ## Design System (Airbnb-inspired)
247
56
 
248
- ### Public Tokens (ThemeColors) — 12 tokens consumer can override
57
+ | Token | Rule |
58
+ |---|---|
59
+ | **Touch targets** | ≥44pt height on all interactive elements |
60
+ | **Spacing** | 8pt grid, 4pt micro-steps |
61
+ | **Radius** | sm=6, md=8, lg=12, xl=16 |
62
+ | **Colors** | 12 public tokens → `deriveColors()` computes 26 resolved tokens |
63
+ | **Typography** | Sohne, modest weights (500-600 display), `allowFontScaling={true}` everywhere |
64
+ | **Shadows** | Max 2 tiers. Flat baseline + single float tier |
65
+ | **Elevation** | Depth from rounded corners + surface separation, not shadows |
249
66
 
250
- These are the only values you need to supply when customizing the theme. The library derives all other colors internally.
67
+ ### Theme Tokens 12 Public (consumer can override)
251
68
 
252
- | Token | Light Default | Dark Default | Semantic Role |
253
- |-------|--------------|--------------|---------------|
69
+ | Token | Light Default | Dark Default | Role |
70
+ |-------|--------------|--------------|------|
254
71
  | `background` | `#ffffff` | `#0f0f0f` | Screen / page background |
255
- | `foreground` | `#1a1a1a` | `#fafafa` | Primary text — deep near-black, not pure black |
256
- | `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface background |
257
- | `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states, active indicators) |
258
- | `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon placed on primary-colored backgrounds |
72
+ | `foreground` | `#1a1a1a` | `#fafafa` | Primary text |
73
+ | `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface |
74
+ | `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states) |
75
+ | `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon on primary |
259
76
  | `border` | `#dddddd` | `#303030` | Borders, dividers, input outlines |
260
- | `destructive` | `#c72828` | `#ef5350` | Error / danger / delete actions |
261
- | `destructiveForeground` | `#ffffff` | `#ffffff` | Text/icon on destructive backgrounds |
262
- | `success` | `#1a7a45` | `#2e7d52` | Success / confirmation states |
263
- | `successForeground` | `#ffffff` | `#ffffff` | Text/icon on success backgrounds |
264
- | `warning` | `#9a5200` | `#f5a623` | Warning / caution states |
265
- | `warningForeground` | `#ffffff` | `#0f0f0f` | Text/icon on warning backgrounds |
266
- | `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop/overlay color behind sheets and dialogs |
267
- | `accent` *(optional)* | `#d4561d` | `#e87645` | Secondary brand accent (e.g. Airbnb coral). Falls back to `primary` |
268
- | `accentForeground` *(optional)* | same as `primaryForeground` | same as `primaryForeground` | Text/icon on accent backgrounds. Falls back to `primaryForeground` |
269
-
270
- ### Derived Tokens (ResolvedColors) read-only via useTheme().colors
271
-
272
- The full palette components consume. Never supply these directly — they are computed by `deriveColors()` from the 12 public tokens above.
273
-
274
- | Token | Derived From | Purpose |
275
- |-------|-------------|---------|
276
- | `foregroundSubtle` | `foreground` @ ~70% | Body text, subtitles, secondary content |
277
- | `foregroundMuted` | `foreground` @ ~62% | Captions, timestamps, placeholders |
278
- | `surface` | `background` slightly off-canvas | Chip backgrounds, input fills, tag backgrounds |
279
- | `surfaceStrong` | `background` stronger offset | Pressed/hover fill states |
280
- | `skeleton` | `background` @ ±10% | Skeleton placeholder higher contrast than surface for visibility |
281
- | `destructiveTint` | `destructive` blended to bg | Alert banner background, toast background |
282
- | `destructiveBorder` | `destructive` @ 30% | Alert banner border, badge outline |
283
- | `successTint` | `success` blended to bg | Success banner background |
77
+ | `destructive` | `#c72828` | `#ef5350` | Error / danger / delete |
78
+ | `destructiveForeground` | `#ffffff` | `#ffffff` | Text on destructive |
79
+ | `success` | `#1a7a45` | `#2e7d52` | Success / confirmation |
80
+ | `successForeground` | `#ffffff` | `#ffffff` | Text on success |
81
+ | `warning` | `#9a5200` | `#f5a623` | Warning / caution |
82
+ | `warningForeground` | `#ffffff` | `#0f0f0f` | Text on warning |
83
+ | `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop behind sheets |
84
+ | `accent` *(optional)* | `#d4561d` | `#e87645` | Brand accent (falls back to `primary`) |
85
+ | `accentForeground` *(optional)* | `primaryForeground` | `primaryForeground` | Text on accent |
86
+
87
+ ### Derived Tokens — 26 ResolvedColors (read-only via `useTheme().colors`)
88
+
89
+ | Token | Source | Purpose |
90
+ |-------|--------|---------|
91
+ | `foregroundSubtle` | `foreground` @ ~70% | Body text, subtitles |
92
+ | `foregroundMuted` | `foreground` @ ~62% | Captions, placeholders (WCAG AA 4.5:1) |
93
+ | `surface` | `background` off-canvas | Chip backgrounds, input fills |
94
+ | `surfaceStrong` | `background` stronger offset | Pressed/hover states |
95
+ | `skeleton` | `background` @ ±10% | Skeleton placeholder |
96
+ | `destructiveTint` | `destructive` to bg | Alert banner bg |
97
+ | `destructiveBorder` | `destructive` @ 30% | Alert banner border |
98
+ | `successTint` | `success` to bg | Success banner bg |
284
99
  | `successBorder` | `success` @ 30% | Success banner border |
285
- | `warningTint` | `warning` blended to bg | Warning banner background |
100
+ | `warningTint` | `warning` to bg | Warning banner bg |
286
101
  | `warningBorder` | `warning` @ 30% | Warning banner border |
287
- | `ring` | `= primary` | Focus ring color (always matches primary) |
288
- | `input` | `= border` | Input field border (always matches border) |
289
- | `separator` | `border` @ ±16-22% | Divider/separator line — deliberately darker than border |
290
- | `overlay` | `overlay` token or `rgba(0,0,0,0.45)` | Backdrop behind sheets and dialogs |
291
- | `accentResolved` | `accent` token or `= primary` | Resolved accent color — always present |
292
- | `accentForegroundResolved` | `accentForeground` token or `= primaryForeground` | Resolved text on accent — always present |
293
-
294
- **Usage example — building a custom component using derived tokens:**
295
- ```tsx
296
- const { colors } = useTheme()
297
-
298
- // Text hierarchy
299
- <Text style={{ color: colors.foreground }}>Primary text</Text>
300
- <Text style={{ color: colors.foregroundSubtle }}>Secondary text</Text>
301
- <Text style={{ color: colors.foregroundMuted }}>Caption / timestamp</Text>
302
-
303
- // Surface fills (unselected chips, inactive backgrounds)
304
- <View style={{ backgroundColor: colors.surface }}>
305
-
306
- // Warning alert banner
307
- <View style={{ backgroundColor: colors.warningTint, borderColor: colors.warningBorder }}>
308
- <Text style={{ color: colors.warning }}>Warning message</Text>
309
- ```
310
-
311
- ### deriveColors export
102
+ | `ring` | `= primary` | Focus ring |
103
+ | `input` | `= border` | Input border |
104
+ | `separator` | `border` @ ±16-22% | Divider line |
105
+ | `overlay` | `overlay` token or `rgba(0,0,0,0.45)` | Sheet backdrop |
106
+ | `accentResolved` | `accent` token or `= primary` | Resolved accent |
107
+ | `accentForegroundResolved` | `accentForeground` or `= primaryForeground` | Resolved text on accent |
312
108
 
109
+ ### Color Utility
313
110
  ```tsx
314
- import { deriveColors } from '@retray-dev/ui-kit'
315
-
316
- const resolved = deriveColors(myThemeColors, 'light')
317
- // resolved contains all 26 ResolvedColors tokens
111
+ import { withAlpha } from '@retray-dev/ui-kit'
112
+ // hex → rgba: withAlpha('#1a1a1a', 0.15) → 'rgba(26,26,26,0.15)'
318
113
  ```
319
114
 
320
- ---
321
-
322
- ### Color Utilities
323
-
324
- **Import:** `import { withAlpha } from '@retray-dev/ui-kit'`
325
-
326
- Convert a hex color to rgba with the given alpha. Useful for semi-transparent backgrounds, borders, and overlays derived from theme colors.
327
-
328
- ```tsx
329
- import { useTheme, withAlpha } from '@retray-dev/ui-kit'
330
-
331
- const { colors } = useTheme()
332
-
333
- <View style={{ backgroundColor: withAlpha(colors.primary, 0.15) }}>
334
- <Text style={{ color: colors.primary }}>Tinted background</Text>
335
- </View>
336
- ```
115
+ ### Shape Language
116
+ Soft rounded rects everywhere. Buttons: `RADIUS.md=14px` (NOT pill). IconButton: `RADIUS.full` (circle). Inputs: 8px. Cards: 14px. Modals: 16px top corners only.
337
117
 
338
118
  ---
339
119
 
340
- ## Design Tokens
341
-
342
- Static structural constants — no context or provider needed.
120
+ ### Design Tokens — Static Constants, No Provider Needed
343
121
 
344
122
  ```ts
345
123
  import { SPACING, ICON_SIZES, RADIUS, SHADOWS, BREAKPOINTS, TYPOGRAPHY } from '@retray-dev/ui-kit'
346
124
  ```
347
125
 
348
- ### SPACING — 8pt grid with 2pt micro-step
126
+ **SPACING — 8pt grid**
349
127
 
350
128
  | Key | Value | Use |
351
129
  |-----|-------|-----|
352
- | `xxs` | 2 | Micro gaps (icon to text in badges) |
130
+ | `xxs` | 2 | Micro gaps |
353
131
  | `xs` | 4 | Tight internal gaps |
354
132
  | `sm` | 8 | Component internal padding |
355
133
  | `md` | 12 | Medium gaps |
356
134
  | `base` | 16 | Default content padding |
357
135
  | `lg` | 24 | Section internal padding |
358
- | `xl` | 32 | Between major content blocks |
136
+ | `xl` | 32 | Between major blocks |
359
137
  | `xxl` | 48 | Section padding |
360
- | `section` | 64 | Major band separators (Airbnb hero → grid rhythm) |
361
-
362
- **Types:** `Spacing`, `SpacingKey`
363
-
364
- ```tsx
365
- <View style={{ gap: SPACING.md, padding: SPACING.base }} />
366
- ```
138
+ | `section` | 64 | Major band separators |
367
139
 
368
- ### ICON_SIZES
140
+ **ICON_SIZES**
369
141
 
370
142
  | Key | Value | Use |
371
143
  |-----|-------|-----|
372
- | `sm` | 14 | Badge icons, inline micro icons |
144
+ | `sm` | 14 | Badge icons |
373
145
  | `md` | 18 | Standard component icons |
374
146
  | `lg` | 22 | Larger inline icons |
375
147
  | `xl` | 28 | Feature icons |
376
148
  | `2xl` | 32 | Display icons |
377
149
 
378
- **Types:** `IconSize`, `IconSizeKey`
379
-
380
- ### RADIUS — Airbnb shape language
150
+ **RADIUS Airbnb shape language**
381
151
 
382
152
  | Key | Value | Used in |
383
153
  |-----|-------|---------|
384
- | `none` | 0 | No rounding |
154
+ | `none` | 0 | |
385
155
  | `xs` | 4 | Micro chips, tags |
386
156
  | `sm` | 8 | Inputs, Textarea, Select, Checkbox |
387
- | `md` | 14 | Cards, Buttons (all variants), MediaCard, AlertBanner, Toast, EmptyState |
157
+ | `md` | 14 | Cards, Buttons, MediaCard, AlertBanner, Toast |
388
158
  | `lg` | 20 | Sheet top corners |
389
159
  | `xl` | 32 | Large decorative elements |
390
160
  | `full` | 9999 | IconButton (circle), CategoryStrip chips |
391
161
 
392
- **Types:** `Radius`, `RadiusKey`
393
-
394
- ```tsx
395
- <View style={{ borderRadius: RADIUS.md }} />
396
- ```
397
-
398
- ### SHADOWS — Cross-platform shadow presets
162
+ **SHADOWS Cross-platform**
399
163
 
400
- | Key | shadowOffset.height | shadowOpacity | shadowRadius | elevation | Use |
401
- |-----|---------------------|---------------|--------------|-----------|-----|
402
- | `sm` | 1 | 0.06 | 4 | 2 | Default card shadow |
403
- | `md` | 2 | 0.10 | 8 | 5 | Hover float / elevated elements |
404
- | `lg` | 6 | 0.16 | 16 | 10 | Modals, overlays |
405
- | `xl` | 12 | 0.24 | 24 | 18 | High-elevation dialogs |
406
-
407
- ```tsx
408
- <View style={[styles.card, SHADOWS.sm]} />
409
- // Hover state
410
- <View style={[styles.card, hovered ? SHADOWS.md : SHADOWS.sm]} />
411
- ```
164
+ | Key | opacity | radius | elevation | Use |
165
+ |-----|---------|--------|-----------|-----|
166
+ | `sm` | 0.06 | 4 | 2 | Default card |
167
+ | `md` | 0.10 | 8 | 5 | Hover float |
168
+ | `lg` | 0.16 | 16 | 10 | Modals |
169
+ | `xl` | 0.24 | 24 | 18 | High-elevation |
412
170
 
413
- ### BREAKPOINTS
171
+ **BREAKPOINTS**
414
172
 
415
173
  | Key | Value | Use |
416
174
  |-----|-------|-----|
417
175
  | `wide` | 700 | Tablet / wide layout threshold |
418
176
 
419
- ```tsx
420
- const isWide = useWindowDimensions().width >= BREAKPOINTS.wide
421
- ```
422
-
423
- ### TYPOGRAPHY — 16 Airbnb-aligned variants
177
+ **TYPOGRAPHY — 17 variants**
424
178
 
425
- All components use these tokens for text styling. Import and use in custom components for consistency.
426
-
427
- | Key | Size | Weight | lineHeight | letterSpacing | Use |
428
- |-----|------|--------|-----------|---------------|-----|
429
- | `display-hero` | 64 | 700 | 70 | -1 | Large number display, balance totals |
179
+ | Key | Size | Weight | lh | ls | Use |
180
+ |-----|------|--------|----|----|-----|
181
+ | `display-hero` | 64 | 700 | 70 | -1 | Large number display |
430
182
  | `display-xl` | 28 | 700 | 40 | 0 | Screen titles, hero headings |
431
- | `display-lg` | 22 | 500 | 26 | -0.44 | Section headings |
432
- | `display-md` | 21 | 700 | 30 | 0 | Card titles, dialog titles |
433
- | `display-sm` | 20 | 600 | 24 | -0.18 | Sub-section headings |
434
- | `title-md` | 16 | 600 | 20 | 0 | Row titles, list item titles |
435
- | `title-sm` | 16 | 500 | 20 | 0 | Secondary titles |
183
+ | `display-lg` | 24 | 600 | 32 | -0.3 | Section headings |
184
+ | `display-md` | 20 | 600 | 28 | 0 | Card titles |
185
+ | `display-sm` | 18 | 600 | 24 | -0.18 | Sub-section headings |
186
+ | `title-md` | 17 | 600 | 22 | 0 | Row titles |
187
+ | `title-sm` | 15 | 500 | 20 | 0 | Secondary titles |
436
188
  | `body-md` | 16 | 400 | 24 | 0 | Primary body copy |
437
- | `body-sm` | 14 | 400 | 20 | 0 | Secondary body, descriptions |
438
- | `caption` | 14 | 500 | 18 | 0 | Labels above inputs, item captions |
439
- | `caption-sm` | 13 | 400 | 16 | 0 | Timestamps, metadata |
440
- | `badge-text` | 11 | 600 | 13 | 0 | Badge labels, small tags |
441
- | `micro-label` | 12 | 700 | 16 | 0 | Micro labels, overlines |
442
- | `uppercase-tag` | 10 | 700 | 13 | 0.8 | Uppercase decorative tags (auto-uppercase) |
443
- | `button-lg` | 16 | 500 | 20 | 0 | Button labels (md/lg size) |
444
- | `button-sm` | 14 | 500 | 18 | 0 | Button labels (sm size) |
445
-
446
- **Types:** `Typography`, `TypographyKey`
447
-
448
- ```tsx
449
- import { TYPOGRAPHY } from '@retray-dev/ui-kit'
450
-
451
- // Use in StyleSheet
452
- const styles = StyleSheet.create({
453
- heading: {
454
- ...TYPOGRAPHY['display-xl'],
455
- color: colors.foreground,
456
- },
457
- })
458
- ```
189
+ | `body-sm` | 14 | 400 | 20 | 0 | Secondary body |
190
+ | `caption` | 14 | 500 | 18 | 0 | Input labels |
191
+ | `caption-sm` | 13 | 400 | 16 | 0 | Timestamps |
192
+ | `badge-text` | 11 | 600 | 14 | 0 | Badge labels |
193
+ | `badge-text-md` | 13 | 600 | 16 | 0 | Badge labels (md size) |
194
+ | `micro-label` | 12 | 700 | 16 | 0 | Overlines |
195
+ | `uppercase-tag` | 11 | 700 | 14 | 0.6 | Decorative tags (auto-uppercase) |
196
+ | `button-lg` | 16 | 500 | 22 | 0 | Button (md/lg) |
197
+ | `button-sm` | 14 | 500 | 18 | 0 | Button (sm) |
459
198
 
460
199
  ---
461
200
 
462
- ## Migration Guide: v4 → v5
463
-
464
- ### Breaking Changes
465
-
466
- **Button variants renamed:**
467
- | v4 | v5 |
468
- |----|-----|
469
- | `outline` | `secondary` |
470
- | `ghost` | `text` |
471
- | `secondary` (filled gray) | removed — use Card/surface instead |
472
-
473
- **Text variants replaced:**
474
- | v4 | v5 equivalent |
475
- |----|---------------|
476
- | `h1` | `display-xl` |
477
- | `h2` | `display-lg` or `display-md` |
478
- | `h3` | `display-sm` |
479
- | `body` | `body-md` |
480
- | `label` | `title-sm` or `caption` |
481
- | `caption` | `caption-sm` |
482
-
483
- **Theme tokens changed:**
484
- | v4 | v5 |
485
- |----|-----|
486
- | `secondary`, `secondaryForeground` | removed (use `surface` derived token) |
487
- | `accent`, `accentForeground` | removed (use `surfaceStrong`) |
488
- | `muted` | removed → `surface` (derived) |
489
- | `mutedForeground` | removed → `foregroundSubtle` / `foregroundMuted` (derived) |
490
- | Added | `warning`, `warningForeground` |
491
- | Added (derived) | `warningTint`, `warningBorder` |
492
-
493
- **IconButton variant:**
494
- | v4 | v5 |
495
- |----|-----|
496
- | `ghost` | `text` |
497
-
498
- ---
499
-
500
- ## Migration Guide: v5 → v6
501
-
502
- ### New Components
503
-
504
- **`MenuItem`** — Navigation row with icon, label, and optional right slot (`rightRender`). Replaces ad-hoc `ListItem` usage for settings/nav menus. Zero horizontal padding by design — consumer controls spacing.
201
+ ## Icons
505
202
 
203
+ ### Icon Component
506
204
  ```tsx
507
- import { MenuItem } from '@retray-dev/ui-kit'
508
- // variants: 'plain' (default) | 'card'
509
- <MenuItem label="Profile" subtitle="Edit your details" iconName="user" onPress={() => {}} />
510
- <MenuItem label="Notifications" iconName="bell" rightRender={<Switch value={on} onValueChange={setOn} />} showChevron={false} onPress={() => {}} />
205
+ import { Icon } from '@retray-dev/ui-kit'
206
+ <Icon name="home" size={24} color="#000" />
207
+ // Returns null if name not found no crash
511
208
  ```
512
209
 
513
- ### Breaking Changes
210
+ ### Resolution Order (first-match wins)
211
+ Feather > AntDesign > Entypo > FontAwesome5 > MaterialIcons > Ionicons
514
212
 
515
- **Sheet keyboard defaults changed:**
516
- | Prop | v5 default | v6 default |
517
- |------|-----------|-----------|
518
- | `keyboardBehavior` | `'fillParent'` (Android) / `'interactive'` (iOS) | `'interactive'` (both) |
519
- | `android_keyboardInputMode` | `'adjustResize'` | `'adjustPan'` |
213
+ **Feather is first** — most names resolve there (outlined style). Names with `-outline` suffix resolve to Ionicons (no Feather collision).
520
214
 
521
- If you relied on `adjustResize`, set it explicitly: `android_keyboardInputMode="adjustResize"`.
522
-
523
- **Toast API simplified:**
215
+ ### `iconName` props on components
216
+ | Component | Prop(s) | Color |
217
+ |-----------|---------|-------|
218
+ | `Button` | `iconName`, `iconColor` | Variant label |
219
+ | `IconButton` | `iconName`, `iconColor` | Variant foreground |
220
+ | `Input` | `prefixIcon`, `prefixIconColor`, `suffixIcon`, `suffixIconColor` | `foregroundMuted` |
221
+ | `ListItem` | `leftIcon`, `leftIconColor`, `rightIcon`, `rightIconColor` | `foreground` / `foregroundMuted` |
222
+ | `Badge` | `iconName`, `iconColor` | Variant foreground |
223
+ | `Toggle` | `iconName`, `iconColor`, `activeIconName`, `activeIconColor` | `foregroundMuted` / `primary` |
224
+ | `AlertBanner` | `iconName`, `iconColor` | Title color |
225
+ | `EmptyState` | `iconName`, `iconColor` | `foregroundMuted` |
226
+ | `MediaCard` | `actionIconName` | White |
227
+ | `AvatarGroup` | — | — (avatars use own colors) |
228
+ | `Chip` | `iconName` | Variant foreground |
229
+ | `DetailRow` | `leftIconName`, `leftIconColor`, `rightIconName`, `rightIconColor` | `foregroundMuted` |
230
+ | `MenuItem` | `iconName`, `iconColor` | `foreground` |
231
+
232
+ ### curatedIcons.ts
233
+ 8 categories, ~20 icons each, all Feather (outlined). Actions, comunicación, navegación, comida, negocios, perfil, multimedia, texto.
234
+
235
+ ### Rules
236
+ - Prefer Feather (outlined) over FA5 solid (filled). FA5 `defaultStyle='regular'` but some names only exist in `solid` — avoid those.
237
+ - Icon lookup uses lazy singleton cache from `glyphMap` introspection.
524
238
 
239
+ ### `getResponsiveFontSize` utility
525
240
  ```tsx
526
- // v5 hook-only API
527
- const { toast } = useToast()
528
- toast.success('Done')
529
-
530
- // v6 — direct import preferred (hook still works for compat)
531
- import { toast } from '@retray-dev/ui-kit'
532
- toast.success('Done')
241
+ import { getResponsiveFontSize } from '@retray-dev/ui-kit'
242
+ const fontSize = getResponsiveFontSize(text, 48)
243
+ // Steps: ≤10→max, ≤12→max-4, ≤14→max-6, >14→max-8
533
244
  ```
534
245
 
535
- ### New Theme Tokens
536
-
537
- Three optional `ThemeColors` tokens — fall back gracefully if omitted:
538
-
539
- | Token | Default | Purpose |
540
- |-------|---------|---------|
541
- | `overlay` | `rgba(0,0,0,0.45)` | Backdrop color behind sheets and dialogs |
542
- | `accent` | `= primary` | Secondary brand accent color |
543
- | `accentForeground` | `= primaryForeground` | Text on accent backgrounds |
544
-
545
- Access resolved values via `useTheme().colors.overlay`, `.accentResolved`, `.accentForegroundResolved`.
546
-
547
- ### Token Corrections
548
-
549
- - `TYPOGRAPHY['uppercase-tag']` — size corrected to `10` (was documented as `8`, actual value was always `10`)
550
-
551
246
  ---
552
247
 
553
- ## Migration Guide: v6 → v7
554
-
555
- ### Breaking Changes
248
+ ## Animations & Interactions
556
249
 
557
- **No prop API changes** — all component imports and props are identical. This is a breaking version bump due to visual behavior changes that affect rendered output.
558
-
559
- **Typography scale corrected:**
560
-
561
- | Token | v6 | v7 | Reason |
562
- |-------|----|----|--------|
563
- | `display-lg` | 22px / 500 | 24px / 600 | Removed weight inversion vs display-md |
564
- | `display-md` | 21px / 700 | 20px / 600 | 4px gap preserved; weight normalised |
565
- | `title-md` | 16px / 600 | 17px / 600 | Now visibly distinct from title-sm |
566
- | `title-sm` | 16px / 500 | 15px / 500 | Was same px as title-md |
567
- | `uppercase-tag` | 10px / 0.8 letterSpacing | 11px / 0.6 | Below Apple HIG 11pt minimum |
568
- | `badge-text-md` | (missing) | 13px / 600 | New canonical token for Badge md |
569
-
570
- **Default color values changed (WCAG AA fixes):**
571
-
572
- | Token | v6 | v7 | WCAG impact |
573
- |-------|----|----|-------------|
574
- | `foregroundMuted` opacity | 0.38 (~#ababab) | 0.62 (~#767676) | 2.2:1 ❌ → 4.5:1 ✓ |
575
- | `foregroundSubtle` opacity | 0.55 (~#858585) | 0.70 (~#646464) | 3.5:1 ❌ → 5.9:1 ✓ |
576
- | `warning` (light) | `#e67e00` | `#9a5200` | 2.86:1 ❌ → 5.86:1 ✓ |
577
- | `warningForeground` (dark) | `#ffffff` | `#0f0f0f` | Dark text on amber, 8.6:1 ✓ |
578
- | `destructive` (light) | `#e53935` | `#c72828` | 4.22:1 ❌ → 5.59:1 ✓ |
579
- | `accent` (light) | `= primary` | `#d4561d` | Explicit brand accent |
580
- | `accent` (dark) | `= primary` | `#e87645` | Warm accent for dark surfaces |
250
+ ### Pressables (pressto)
251
+ | Pressable | Scale | Used by |
252
+ |---|---|---|
253
+ | `PressableButton` | 0.95 | Button, IconButton, Toggle, Checkbox |
254
+ | `PressableCard` | 0.98 | Card, MediaCard, Stats, Pressable |
255
+ | `PressableRow` | 0.97 | ListItem, MenuItem |
256
+ | `PressableChip` | 0.94 | Chip |
257
+ | `PressableTab` | 0.95 | Tabs triggers |
581
258
 
582
- **Component visual behavior changes:**
259
+ All use `enabled={!disabled}`, `rippleColor="transparent"`, `touchSoundDisabled`.
583
260
 
584
- | Component | Change |
585
- |-----------|--------|
586
- | `AlertBanner` | Background now uses semantic tint per variant (was white card); 1px semantic border added |
587
- | `Button` (text variant) | Label color: `foreground` → `accentResolved` — clearer CTA signal |
588
- | `Card` (elevated variant) | `borderWidth: 0` — shadow is sole depth signal; border was redundant |
589
- | `Chip` | `paddingVertical` doubled to hit 44pt WCAG 2.5.5 tap target |
590
- | `Input` / `Textarea` | Border: 1px at rest → animates to 2px on focus (was always 2px) |
591
- | `Switch` | Off-state now shows animated 1.5px border (invisible on white surfaces before) |
592
- | `Tabs` | Labels always `Sohne-SemiBold`; active = color only — eliminates layout reflow on selection |
593
- | `Checkbox` / `RadioGroup` | Disabled `opacity: 0.45` now on full row (was box only) |
594
- | `Toast` | `richColors={true}` — semantic variants now visually distinct by color |
261
+ ### Spring Presets
262
+ - `glide`: `{ stiffness: 380, damping: 38, mass: 1.0 }` — sliding indicators
263
+ - `elastic`: `{ stiffness: 320, damping: 22, mass: 0.7 }` Switch thumb, RadioGroup dot
595
264
 
596
- **If you customised any of these values** via `ThemeProvider` overrides, your overrides continue to take precedence — default palette changes only affect apps using the out-of-the-box defaults.
265
+ ### Timing Presets
266
+ - `state`: 160ms (checkbox/toggle color)
267
+ - `expand` / `collapse`: 240ms / 200ms (Accordion)
268
+ - `shimmer`: 1400ms (Skeleton)
597
269
 
598
- ### New Compound Component Section
270
+ ### EaseView (react-native-ease)
271
+ Declarative color/opacity transitions: Switch track, Checkbox fill, RadioGroup dot. UI thread.
599
272
 
600
- The example app now has a dedicated **Compound Components** section showcasing `Card`, `ButtonGroup`, `Form`, `ListGroup`, and `MenuGroup` with all sub-components and variants.
273
+ ### Reanimated v4
274
+ Complex animations only (gestures, layout interpolations). Simple opacity: use RN `Animated` + `useNativeDriver`.
601
275
 
602
276
  ---
603
277
 
604
- ## Migration Guide: v7 → v8
605
-
606
- ### Breaking Changes
607
-
608
- **1. New required peer dependency: `sonner-native`**
609
- Toast (`ToastProvider` / `toast` / `useToast`) imports `sonner-native`, but it was never declared as a peer — installs silently lacked it and Metro failed with `Unable to resolve "sonner-native"`. It is now a declared peer. Install it:
610
- ```bash
611
- pnpm add sonner-native
612
- ```
613
- (You already need its companions `react-native-svg` and `react-native-screens`.)
614
-
615
- **2. `@gorhom/bottom-sheet` peer range tightened to `>=5.2.0`**
616
- 5.1.x crashes with Reanimated v4 (`useWorkletCallback is not a function`), which broke `Sheet`, `ConfirmDialog`, and `Select` on open. The range no longer admits broken versions. Pin `5.2.8` if you also use Worklets `0.5.x`.
617
-
618
- **3. `./fonts` stays at `src/fonts.ts` (unchanged)**
619
- The `./fonts` export deliberately points at `src/fonts.ts`, not `dist`. The `.otf` files ship as raw assets in `src/assets/fonts/` and are resolved by Metro at your build time via `require()`. Compiling this entry to `dist` and repointing the `require()` paths breaks Metro asset resolution under pnpm symlinked workspaces (`requiring unknown module ../src/assets/fonts/...`), so it is intentionally left as source. No API change — import as before:
620
- ```ts
621
- import { SohneFonts } from '@retray-dev/ui-kit/fonts'
622
- ```
278
+ ## Sheets (BottomSheetModal)
623
279
 
624
- ### New packaging & DX
280
+ ### Strict rules (from @gorhom/bottom-sheet)
281
+ 1. **Always `BottomSheetModal`** — never `BottomSheet` base. `present()` in handler directly (not `useEffect`).
282
+ 2. **`enableDynamicSizing`** — no `snapPoints` (mutually exclusive)
283
+ 3. **`topInset={insets.top}`** — prevents notch overlap
284
+ 4. **`keyboardBehavior="interactive"`** + **`android_keyboardInputMode="adjustPan"`** — keyboard
285
+ 5. **`SheetTextInput`** inside sheets — never `<TextInput />`
286
+ 6. **`onDismiss`** for cleanup, not `onClose`
287
+ 7. **`renderBackdrop`** wrapped in `useCallback`
288
+ 8. **`enableBlurKeyboardOnGesture={true}`** — dismiss keyboard on drag
289
+ 9. **`BottomSheetModalProvider`** re-exported, included in `RetrayProvider`
625
290
 
626
- - **`RetrayProvider`** one wrapper replacing the five-provider boilerplate (see Setup below).
627
- - **Dev font guard**if you render a UI kit component without loading `SohneFonts`, `Text` now logs a one-time `console.warn` in dev (silent in production) instead of failing invisibly.
291
+ ### Input inside Sheet
292
+ Use `<Input sheetMode />`transparently swaps to `BottomSheetTextInput`. For low-level: `SheetTextInput`.
628
293
 
629
- ### New components
294
+ ---
630
295
 
631
- `RetrayProvider`, `AppHeader`, `TabBar`, `PagerDots`, `SelectableGrid`, `PricingCard`, `ErrorBoundary`, `ImageViewer`, plus `Skeleton.MediaCard` / `Skeleton.ListItem` sub-skeletons and the optional deep-import-only `HolographicCard` (Skia foil card). `MonthPicker` gained `minValue`/`maxValue` and `Date` bridge helpers.
296
+ ## Haptics (expo-haptics)
632
297
 
633
- ### Optional peers (only if you use the matching component)
298
+ Web-safe via `Platform.OS !== 'web'` guard + dynamic `import()`.
634
299
 
635
- - `@shopify/react-native-skia` + `expo-sensors` → `HolographicCard` (deep-import only, not in the main barrel).
300
+ | Function | Usage |
301
+ |---|---|
302
+ | `selectionAsync()` | Checkbox, Switch, Toggle, RadioGroup, Select, Slider (step), Accordion, ListItem, MenuItem, ConfirmDialog cancel |
303
+ | `impactLight()` / `impactMedium()` / `impactHeavy()` | Button, Sheet open, ConfirmDialog open, Stats |
304
+ | `notificationSuccess()` / `notificationError()` / `notificationWarning()` | ConfirmDialog confirm, form validation |
636
305
 
637
306
  ---
638
307
 
639
- ## Migration Guide: v9 → v10
640
-
641
- ### Breaking Changes
642
-
643
- **1. `SohneFonts` export removed from `@retray-dev/ui-kit/fonts`**
644
-
645
- The `SohneFonts` object with `require()` calls is no longer exported. Metro cannot reliably resolve `require()` from inside `node_modules`, especially in monorepos. The export now returns `undefined` and logs a deprecation warning.
646
-
647
- **New approach — postinstall script:**
648
- 1. When you install `@retray-dev/ui-kit`, a **postinstall script** automatically copies 28 `.otf` font files to `assets/fonts/sohne/` in your project root
649
- 2. Define `SohneFonts` locally in your `App.tsx` with static `require()` calls (see below)
650
- 3. Pass it to `expo-font`'s `useFonts()` hook
651
-
652
- **Migration:**
653
-
654
- ```diff
655
- // App.tsx
656
- import { useFonts } from 'expo-font'
657
- -import { SohneFonts } from '@retray-dev/ui-kit/fonts'
658
-
659
- +// Fonts copied to assets/fonts/sohne/ by @retray-dev/ui-kit postinstall
660
- +const SohneFonts = {
661
- + 'Sohne-ExtraLight': require('./assets/fonts/sohne/Sohne-ExtraLight.otf'),
662
- + 'Sohne-ExtraLightItalic': require('./assets/fonts/sohne/Sohne-ExtraLightItalic.otf'),
663
- + 'Sohne-Light': require('./assets/fonts/sohne/Sohne-Light.otf'),
664
- + 'Sohne-LightItalic': require('./assets/fonts/sohne/Sohne-LightItalic.otf'),
665
- + 'Sohne-Regular': require('./assets/fonts/sohne/Sohne-Regular.otf'),
666
- + 'Sohne-Italic': require('./assets/fonts/sohne/Sohne-Italic.otf'),
667
- + 'Sohne-Medium': require('./assets/fonts/sohne/Sohne-Medium.otf'),
668
- + 'Sohne-MediumItalic': require('./assets/fonts/sohne/Sohne-MediumItalic.otf'),
669
- + 'Sohne-SemiBold': require('./assets/fonts/sohne/Sohne-SemiBold.otf'),
670
- + 'Sohne-SemiBoldItalic': require('./assets/fonts/sohne/Sohne-SemiBoldItalic.otf'),
671
- + 'Sohne-Bold': require('./assets/fonts/sohne/Sohne-Bold.otf'),
672
- + 'Sohne-BoldItalic': require('./assets/fonts/sohne/Sohne-BoldItalic.otf'),
673
- + 'Sohne-ExtraBold': require('./assets/fonts/sohne/Sohne-ExtraBold.otf'),
674
- + 'Sohne-ExtraBoldItalic': require('./assets/fonts/sohne/Sohne-ExtraBoldItalic.otf'),
675
- + 'SohneMono-ExtraLight': require('./assets/fonts/sohne/SohneMono-ExtraLight.otf'),
676
- + 'SohneMono-ExtraLightItalic': require('./assets/fonts/sohne/SohneMono-ExtraLightItalic.otf'),
677
- + 'SohneMono-Light': require('./assets/fonts/sohne/SohneMono-Light.otf'),
678
- + 'SohneMono-LightItalic': require('./assets/fonts/sohne/SohneMono-LightItalic.otf'),
679
- + 'SohneMono-Regular': require('./assets/fonts/sohne/SohneMono-Regular.otf'),
680
- + 'SohneMono-Italic': require('./assets/fonts/sohne/SohneMono-Italic.otf'),
681
- + 'SohneMono-Medium': require('./assets/fonts/sohne/SohneMono-Medium.otf'),
682
- + 'SohneMono-MediumItalic': require('./assets/fonts/sohne/SohneMono-MediumItalic.otf'),
683
- + 'SohneMono-SemiBold': require('./assets/fonts/sohne/SohneMono-SemiBold.otf'),
684
- + 'SohneMono-SemiBoldItalic': require('./assets/fonts/sohne/SohneMono-SemiBoldItalic.otf'),
685
- + 'SohneMono-Bold': require('./assets/fonts/sohne/SohneMono-Bold.otf'),
686
- + 'SohneMono-BoldItalic': require('./assets/fonts/sohne/SohneMono-BoldItalic.otf'),
687
- + 'SohneMono-ExtraBold': require('./assets/fonts/sohne/SohneMono-ExtraBold.otf'),
688
- + 'SohneMono-ExtraBoldItalic': require('./assets/fonts/sohne/SohneMono-ExtraBoldItalic.otf'),
689
- +}
308
+ ## Theming
690
309
 
691
- export default function App() {
692
- const [fontsLoaded] = useFonts(SohneFonts)
693
- // ...
310
+ ### Consumer supplies 12 ThemeColors
311
+ ```tsx
312
+ import { ThemeProvider, ThemeColors } from '@retray-dev/ui-kit'
313
+
314
+ const customLight: ThemeColors = {
315
+ background: '#ffffff',
316
+ foreground: '#1a1a1a',
317
+ primary: '#d4561d',
694
318
  }
695
319
  ```
696
320
 
697
- **.gitignore recommendation:**
698
- ```gitignore
699
- # Sohne fonts — copied by @retray-dev/ui-kit postinstall
700
- assets/fonts/sohne/
701
- ```
321
+ ### deriveColors()
322
+ Computes 26 `ResolvedColors` from 12 `ThemeColors`. Components consume `useTheme().colors` — never raw `ThemeColors`.
702
323
 
703
- **Why this change:** Metro bundler requires `require()` calls to originate from the consumer's source tree, not from `node_modules`. The previous approach worked in simple setups but failed in monorepos and symlinked workspaces.
324
+ ### Structural tokens (not in theme)
325
+ Spacing, radii, shadows, typography = static `as const` exports from `tokens.ts`. No provider needed.
704
326
 
705
327
  ---
706
328
 
707
- ## Migration Guide: v10 → v11
708
-
709
- ### New — public utility
710
-
711
- **`withAlpha(hex, alpha)`** — hex-to-rgba color helper, now exported from the package root. Useful for semi-transparent overlays derived from theme colors without adding a new token.
329
+ ## Toast (sonner-native)
712
330
 
713
331
  ```tsx
714
- import { useTheme, withAlpha } from '@retray-dev/ui-kit'
332
+ import { toast } from 'sonner-native'
333
+ // or
334
+ import { useToast } from '@retray-dev/ui-kit'
335
+ const { toast } = useToast()
715
336
 
716
- const { colors } = useTheme()
717
- <View style={{ backgroundColor: withAlpha(colors.primary, 0.15) }} />
337
+ toast('Hello', { description: 'World' })
338
+ toast.success('Saved')
339
+ toast.error('Failed', { description: 'Check network' })
340
+ toast.promise(save(), { loading: 'Saving...', success: 'Done', error: 'Failed' })
718
341
  ```
719
342
 
720
- ### Updated
721
-
722
- - `Stats` component promoted from internal/experimental to public (`Stats` + `Stats.Group`).
723
- - Documentation refreshed for `IconPicker` and `NumberStepper`.
724
-
725
- No breaking changes in v11. Safe minor upgrade from v10.
343
+ `Toaster` rendered inside `ToastProvider`. Config: `richColors: false`, `swipeToDismissDirection="up"`, `duration: 4000`.
726
344
 
727
345
  ---
728
346
 
729
- ## Migration Guide: v11 → v12
730
-
731
- ### Breaking Changes
732
-
733
- `Sheet` and `ConfirmDialog` were rewritten on top of `@gorhom/bottom-sheet`'s **`BottomSheetModal`** (lazy-mounted, `present()` / `dismiss()` driven) — replacing the old `BottomSheet` + `index={-1}` + `snapToIndex(0)` pattern. This fixes timing issues with `enableDynamicSizing` and unifies the API with the rest of the gorhom ecosystem.
734
-
735
- **1. Removed `responsive` / `dialogMaxWidth` props from `Sheet` and `ConfirmDialog`**
736
-
737
- The previous wide-screen fallback that bypassed gorhom with a native `Modal` + `ScrollView` was a partial workaround (see REGLA 1). It has been deleted. `@gorhom/bottom-sheet` handles responsive behavior natively — the modal simply renders inside its modal layer at the device width.
738
-
739
- ```diff
740
- <Sheet
741
- open={open}
742
- onClose={() => setOpen(false)}
743
- title="Options"
744
- - responsive
745
- - dialogMaxWidth={600}
746
- />
747
- ```
748
-
749
- **2. `onClose` is the only close handler**
347
+ ## Data Display Patterns
750
348
 
751
- `onClose` is called from `BottomSheetModal.onDismiss` — the native gorhom callback. The previous `onClose` prop (passed directly to `BottomSheet`) was redundant. No public-API change for consumers; this is an internal alignment.
349
+ ### ListItem
350
+ - `rightActions` prop for swipe-to-reveal (iOS Mail style)
351
+ - Uses `PressableRow` (scale 0.97)
352
+ - Haptic: `selectionAsync()` on press
353
+ - Zero horizontal padding by design
752
354
 
753
- **3. `Sheet` requires `BottomSheetModalProvider` at app root**
355
+ ### ListGroup
356
+ - Optional `Header` / `Footer` sub-components
357
+ - Auto-separators between items except last
754
358
 
755
- `RetrayProvider` already wires it. If you assemble providers manually, ensure `BottomSheetModalProvider` sits inside `GestureHandlerRootView`:
359
+ ### MenuItem / MenuGroup
360
+ - Settings/nav rows. Auto-separators.
361
+ - `rightRender` for Switch, Badge, etc.
362
+ - `variant="card"` for standalone surface
756
363
 
757
- ```tsx
758
- <SafeAreaProvider initialMetrics={initialWindowMetrics}>
759
- <GestureHandlerRootView style={{ flex: 1 }}>
760
- <ThemeProvider>
761
- <BottomSheetModalProvider>
762
- <ToastProvider>{/* app */}</ToastProvider>
763
- </BottomSheetModalProvider>
764
- </ThemeProvider>
765
- </GestureHandlerRootView>
766
- </SafeAreaProvider>
767
- ```
364
+ ### Chip / ChipGroup
365
+ - Multi-select toggle chips. `PressableChip` (scale 0.94)
366
+ - `ChipGroup` wraps to rows; `CategoryStrip` scrolls horizontally
768
367
 
769
- **4. Keyboard prop renames (no consumer action — defaults match v11)**
368
+ ### LabelValue
369
+ - Key-value rows (label left, value right)
370
+ - Icon support
770
371
 
771
- | Prop | v11 | v12 default |
772
- |------|-----|-------------|
773
- | `keyboardBehavior` | `'interactive'` | `'interactive'` (unchanged) |
774
- | `android_keyboardInputMode` | `'adjustPan'` | `'adjustPan'` (unchanged) |
372
+ ### DetailRow
373
+ - Ticket/receipt rows with `···` dotted separator
374
+ - Icon + label + value
775
375
 
776
- **5. Removed top-level imports of `Modal`, `ScrollView`, `useWindowDimensions`, `BREAKPOINTS` from `Sheet.tsx`**
376
+ ### Skeleton
377
+ - `Skeleton.MediaCard`, `Skeleton.ListItem`, `Skeleton.List` (6 items, 2 columns)
378
+ - Shimmer animation loops every 1200ms
777
379
 
778
- Internal only — no public API.
380
+ ---
779
381
 
780
- ### Behavioral changes (no code change required)
382
+ ## Accessibility (WCAG AA)
781
383
 
782
- - **Backdrop / swipe close is now driven by gorhom's modal lifecycle** — `onClose` fires once on full dismiss instead of on every interaction. State-setter closures (e.g. `setOpen(false)`) are safe to call from `onClose` without causing "dismiss on unmounted" loops.
783
- - **`topInset={insets.top}`** is now applied automatically from safe-area context — sheet never crosses the notch / status bar on iOS or Android.
784
- - **`snapPoints` + `enableDynamicSizing` are mutually exclusive.** If you pass `snapPoints`, dynamic sizing is disabled and the sheet snaps to those points. Omit `snapPoints` to use dynamic sizing (default).
384
+ - `allowFontScaling={true}` on all `<Text>` and `<TextInput>`
385
+ - Touch targets ≥44pt
386
+ - `foregroundMuted` = `#9a9a9a` (dark) / `#a2a2a2` (light) ≥4.5:1 contrast
387
+ - Accessibility props passed through via `...props`
785
388
 
786
- ### New — recommended pattern
389
+ ---
787
390
 
788
- **Use `Input` with `sheetMode` inside a Sheet** instead of `SheetTextInput` directly. The new `sheetMode` prop on `Input` swaps in `BottomSheetTextInput` for keyboard-aware focus/blur handling while preserving the full `Input` API (label, error, hint, prefix/suffix, icons, type="password").
391
+ ## Deep Import Pattern
789
392
 
393
+ Prefer deep imports in production for smaller bundle:
790
394
  ```tsx
791
- <Sheet open={open} onClose={() => setOpen(false)} title="Add note">
792
- <Input
793
- label="Note"
794
- placeholder="Type your note..."
795
- value={note}
796
- onChangeText={setNote}
797
- sheetMode
798
- />
799
- <Button label="Save" fullWidth onPress={handleSave} />
800
- </Sheet>
395
+ import { Button } from '@retray-dev/ui-kit/Button' // deep
396
+ // vs
397
+ import { Button } from '@retray-dev/ui-kit' // barrel
801
398
  ```
802
399
 
803
- `SheetTextInput` is still re-exported for low-level use.
400
+ Deep-import only (NOT in barrel):
401
+ ```tsx
402
+ import { HolographicCard } from '@retray-dev/ui-kit/HolographicCard'
403
+ ```
804
404
 
805
405
  ---
806
406
 
807
- ## Migration Guide: v12.0 → v12.1
808
-
809
- ### New — IconPicker feedback pattern (REGLA 4)
810
-
811
- `IconPicker` now follows the "no frozen screen" rule. When the user taps the trigger, the sheet presents **immediately** (no `useEffect` delay). While the inner grid measures its container, a centered `<Spinner />` is shown inside the sheet so the user sees visible feedback. The grid fades in as soon as `onLayout` fires.
812
-
813
- This is now the canonical pattern for any sheet whose content needs to measure or load before rendering — apply it to new overlay components.
814
-
815
- ### New — race-condition fix in `Sheet` and `ConfirmDialog`
407
+ ## Anti-Patterns
816
408
 
817
- Both components now track a `wasOpened` ref so `dismiss()` is only called after the sheet has been presented at least once. This eliminates a class of crashes that occurred when the parent component unmounted (or the `open`/`visible` prop flipped) before the gorham modal had a chance to mount.
818
-
819
- No consumer action required the fix is internal.
409
+ | Anti-pattern | Why | Fix |
410
+ |---|---|---|
411
+ | `<TextInput />` inside Sheet | Keyboard broken | Use `<Input sheetMode />` or `<SheetTextInput />` |
412
+ | `BottomSheet` base with `snapToIndex` | Fragile timing | Use `BottomSheetModal` with `present()` |
413
+ | `snapPoints` + `enableDynamicSizing` | Runtime error | Use one or the other |
414
+ | Static import of optional peer | Crash at module init | Dynamic `import()` in handler |
415
+ | Bundling native modules | Duplicate Context | Externalize in tsup |
416
+ | `--legacy-peer-deps` | Hides conflicts | Use `overrides` in package.json |
820
417
 
821
- ### New — expanded curated icon library
418
+ ---
822
419
 
823
- `src/utils/curatedIcons.ts` has been expanded: every category now carries 42+ icons (some up to 66), totaling ~600 themed icons across the 12 categories. Selection rules are now codified in REGLA 5 — only Feather, Ionicons `-outline`, and themed FA5/Entypo/AntDesign icons are eligible (no filled variants).
420
+ ## Version
824
421
 
825
- ### Updated
422
+ **Current: 13.1.0.** Requires Expo SDK 54+, React Native 0.81+, React 19.
826
423
 
827
- - `Sheet` and `ConfirmDialog` pass a stable `name` prop to `BottomSheetModal` (from `useId()`) for correct gorhom modal registry behavior when multiple modals are mounted.
828
- - `Input` `sheetMode` prop documented in the Setup section (replaces the `SheetTextInput` boilerplate inside sheets).
829
- - `CompositionScreen` example now uses `<Input sheetMode />` inside a `Sheet` to demonstrate the keyboard-friendly pattern.
424
+ For full setup guide, see `CONSUMER.md`. For usage examples, see `EXAMPLES.md`.
830
425
 
831
426
  ---
832
427
 
833
- ## Components
428
+ ## Component Reference
834
429
 
835
430
  ---
836
431
 
@@ -857,18 +452,19 @@ No consumer action required — the fix is internal.
857
452
  |---------|------|--------|--------------|-------------|
858
453
  | `display-hero` | 64 | 700 | `foreground` | Large numeric displays — balance totals, stats, hero numbers |
859
454
  | `display-xl` | 28 | 700 | `foreground` | Screen-level titles, onboarding hero headings |
860
- | `display-lg` | 22 | 500 | `foreground` | Section headings, card group labels |
861
- | `display-md` | 21 | 700 | `foreground` | Card titles, dialog headings |
862
- | `display-sm` | 20 | 600 | `foreground` | Sub-section titles |
863
- | `title-md` | 16 | 600 | `foreground` | List row titles, navigation labels |
864
- | `title-sm` | 16 | 500 | `foreground` | Secondary row titles |
455
+ | `display-lg` | 24 | 600 | `foreground` | Section headings, card group labels |
456
+ | `display-md` | 20 | 600 | `foreground` | Card titles, dialog headings |
457
+ | `display-sm` | 18 | 600 | `foreground` | Sub-section titles |
458
+ | `title-md` | 17 | 600 | `foreground` | List row titles, navigation labels |
459
+ | `title-sm` | 15 | 500 | `foreground` | Secondary row titles |
865
460
  | `body-md` | 16 | 400 | `foregroundSubtle` | Main body copy, descriptions |
866
461
  | `body-sm` | 14 | 400 | `foregroundSubtle` | Secondary descriptions, detail text |
867
462
  | `caption` | 14 | 500 | `foreground` | Labels above inputs, item captions |
868
463
  | `caption-sm` | 13 | 400 | `foregroundMuted` | Timestamps, metadata, helper text |
869
464
  | `badge-text` | 11 | 600 | `foreground` | Badge labels, small status tags |
465
+ | `badge-text-md` | 13 | 600 | `foreground` | Badge labels (md size) |
870
466
  | `micro-label` | 12 | 700 | `foreground` | Micro labels, overlines |
871
- | `uppercase-tag` | 10 | 700 | `foreground` | Decorative uppercase category labels (auto-transforms text) |
467
+ | `uppercase-tag` | 11 | 700 | `foreground` | Decorative uppercase category labels (auto-transforms text) |
872
468
  | `button-lg` | 16 | 500 | `foreground` | Internal use in Button component (md/lg) |
873
469
  | `button-sm` | 14 | 500 | `foreground` | Internal use in Button component (sm) |
874
470
 
@@ -3847,7 +3443,8 @@ export default function Screen() {
3847
3443
  | subtitle | `string` | — | Secondary line under the title |
3848
3444
  | onBack | `() => void` | — | Shows a back button on the left when provided |
3849
3445
  | backIconName | `string` | `'chevron-left'` | Icon for the back button |
3850
- | left | `ReactNode` | — | Custom left content overrides the back button |
3446
+ | iconName | `string` | — | Decorative icon left of title, after back button. Ignored when `left` is provided |
3447
+ | left | `ReactNode` | — | Custom left content — overrides the back button and `iconName` |
3851
3448
  | right | `ReactNode` | — | Custom right content (actions) |
3852
3449
  | titleAlign | `'auto' \| 'left' \| 'center'` | `'auto'` | `auto` = left on narrow, centered on wide |
3853
3450
  | bordered | `boolean` | `true` | Hairline border underneath |
@@ -3862,6 +3459,13 @@ export default function Screen() {
3862
3459
  onBack={navigation.goBack}
3863
3460
  right={<IconButton iconName="more-horizontal" variant="text" onPress={openMenu} />}
3864
3461
  />
3462
+
3463
+ {/* With decorative icon and back button */}
3464
+ <AppHeader
3465
+ title="Perfil"
3466
+ iconName="user"
3467
+ onBack={() => router.back()}
3468
+ />
3865
3469
  ```
3866
3470
 
3867
3471
  ---
@@ -4309,7 +3913,7 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
4309
3913
 
4310
3914
  **When to use:** Image selection from the device library — product photos, profile avatars, tenant logos. Shows a dashed placeholder when empty, the selected image when filled, and a loading overlay while uploading.
4311
3915
 
4312
- **Requires:** `expo-image-picker` installed in the consuming app (`pnpm add expo-image-picker`). It is an optional peer dep — consumers who don't use `ImageUpload` never pull it.
3916
+ **Requires:** `react-native-image-picker` installed in the consuming app (`pnpm add react-native-image-picker`). It is an optional peer dep — consumers who don't use `ImageUpload` never pull it. It is dynamically imported at tap time.
4313
3917
 
4314
3918
  | Prop | Type | Default | Notes |
4315
3919
  |------|------|---------|-------|
@@ -4323,9 +3927,9 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
4323
3927
  | borderRadius | `number` | `RADIUS.lg` | — |
4324
3928
  | resizeMode | `'cover' \| 'contain' \| 'stretch'` | `'cover'` | Image resize mode |
4325
3929
  | disabled | `boolean` | `false` | Prevents pressing |
4326
- | allowsEditing | `boolean` | `true` | When `true`, iOS opens the crop/editing screen after selecting an image. Set `false` to accept the image directly without cropping |
4327
3930
  | style | `ViewStyle` | — | — |
4328
3931
  | accessibilityLabel | `string` | — | — |
3932
+ | onPickerStarting | `() => void` | — | Called synchronously when user taps the upload area, before dynamic import and permission request |
4329
3933
 
4330
3934
  **Examples:**
4331
3935
  ```tsx
@@ -4465,171 +4069,3 @@ const { visible, loading, open, onConfirm, onCancel } = useConfirmDialog({
4465
4069
 
4466
4070
  ---
4467
4071
 
4468
- ## Icon System
4469
-
4470
- The library ships a built-in icon resolver — pass any icon name string to components without manual imports.
4471
-
4472
- ### Supported families (priority order — first match wins)
4473
-
4474
- | Priority | Family | Best for |
4475
- |---|---|---|
4476
- | 1 | `Feather` | Clean line icons, UI essentials (first match wins) |
4477
- | 2 | `AntDesign` | Semantic UI icons |
4478
- | 3 | `Entypo` | Social, media, navigation icons |
4479
- | 4 | `FontAwesome5` | Wide coverage |
4480
- | 5 | `MaterialIcons` | Material-style icons |
4481
- | 6 | `Ionicons` | Fallback |
4482
-
4483
- Browse all available icons: **https://icons.expo.fyi**
4484
-
4485
- ### Standalone `Icon` component
4486
-
4487
- ```tsx
4488
- import { Icon } from '@retray-dev/ui-kit'
4489
-
4490
- <Icon name="home" size={24} color={colors.foreground} />
4491
- <Icon name="star" size={20} color={colors.primary} />
4492
-
4493
- // Force a specific family when same name exists in multiple families:
4494
- <Icon name="heart" size={24} color="red" family="FontAwesome5" />
4495
- ```
4496
-
4497
- **Props:**
4498
-
4499
- | Prop | Type | Required | Notes |
4500
- |------|------|----------|-------|
4501
- | name | `string` | yes | Icon name (e.g. `"home"`, `"arrow-right"`) |
4502
- | size | `number` | yes | Icon size in points |
4503
- | color | `string` | yes | Icon color |
4504
- | family | `IconFamily` | no | Force a specific family |
4505
-
4506
- Returns `null` (no crash) if name not found in any family.
4507
-
4508
- > **v13:** `renderIcon`, `getValidIconNames`, and `configureIconFamilies` were removed. Use `Icon` component directly for all icon rendering.
4509
-
4510
- ### `iconName` props on components
4511
-
4512
- All components with icon slots accept `iconName` — auto-resolved size and color:
4513
-
4514
- | Component | Prop(s) | Slot | Default color |
4515
- |-----------|---------|------|---------------|
4516
- | `Button` | `iconName`, `iconColor` | Left or right of label | Variant label color |
4517
- | `IconButton` | `iconName`, `iconColor` | Center | Variant foreground |
4518
- | `Input` | `prefixIcon`, `prefixIconColor` | Before input | `foregroundMuted` |
4519
- | `Input` | `suffixIcon`, `suffixIconColor` | After input | `foregroundMuted` |
4520
- | `ListItem` | `leftIcon`, `leftIconColor` | Left 44×44 slot | `foreground` |
4521
- | `ListItem` | `rightIcon`, `rightIconColor` | Right slot | `foregroundMuted` |
4522
- | `Badge` | `iconName`, `iconColor` | Before label | Variant foreground |
4523
- | `Toggle` | `iconName`, `iconColor` | When not pressed | `foregroundMuted` |
4524
- | `Toggle` | `activeIconName`, `activeIconColor` | When pressed | `primary` |
4525
- | `AlertBanner` | `iconName`, `iconColor` | Left of content | Variant title color |
4526
- | `EmptyState` | `iconName`, `iconColor` | Center icon slot | `foregroundMuted` |
4527
- | `Toast` | `iconName`, `iconColor` | Left of message | Variant text color |
4528
- | `MediaCard` | `actionIconName` | Top-right of image | `#ffffff` |
4529
- | `Chip` | `iconName` | Before label | Variant foreground |
4530
-
4531
- **Precedence:** `iconName` always takes precedence over the corresponding `ReactNode` prop when both are supplied.
4532
-
4533
- ---
4534
-
4535
- ### `getResponsiveFontSize` utility
4536
-
4537
- Calculates a stepped-down font size for long numeric strings (currency amounts, large numbers) that would overflow at full size.
4538
-
4539
- ```tsx
4540
- import { getResponsiveFontSize } from '@retray-dev/ui-kit'
4541
-
4542
- // Default steps: ≤10 chars → max, ≤12 → max-4, ≤14 → max-6, >14 → max-8
4543
- const fontSize = getResponsiveFontSize(formatCOP(amount), 48)
4544
-
4545
- <Text style={{ fontSize }} numberOfLines={1} adjustsFontSizeToFit>
4546
- {formatCOP(amount)}
4547
- </Text>
4548
- ```
4549
-
4550
- **Signature:**
4551
- ```ts
4552
- getResponsiveFontSize(
4553
- text: string,
4554
- maxSize: number,
4555
- steps?: { maxLen: number; subtract: number }[]
4556
- ): number
4557
- ```
4558
-
4559
- Custom steps example:
4560
- ```tsx
4561
- getResponsiveFontSize(text, 48, [
4562
- { maxLen: 8, subtract: 0 },
4563
- { maxLen: 11, subtract: 6 },
4564
- { maxLen: 14, subtract: 10 },
4565
- ])
4566
- ```
4567
-
4568
- **Alternative:** Use `<CurrencyDisplay autoScale maxFontSize={48} />` for currency values — no manual sizing needed.
4569
-
4570
- ---
4571
-
4572
- ## Design System Conventions
4573
-
4574
- ### Touch targets
4575
- All interactive elements maintain ≥44pt touch height per Apple HIG:
4576
- - Button md: 48px, sm: 40px, lg: 56px
4577
- - Input / Textarea: 56px (14px vertical padding)
4578
- - IconButton md: 44px, lg: 52px
4579
- - Checkbox: 24×24px box
4580
- - Switch: 30px track height
4581
- - MonthPicker arrows: 44×44px
4582
-
4583
- ### Haptic patterns
4584
- - `impactLight` — Button, Card (press), Sheet open, Toast, MediaCard action
4585
- - `selectionAsync` — Checkbox, Switch, Toggle, RadioGroup, Select, Slider steps, Accordion, ListItem, Chip, CategoryStrip, Tabs, MonthPicker
4586
- - `notificationSuccess` — Toast `success`
4587
- - `notificationError` — Toast `destructive`, Toast `warning`
4588
-
4589
- ### Animation press scales
4590
- - `Button` → 0.95 (strong spring feedback)
4591
- - `Card` → 0.98 (subtle, appropriate for large surfaces)
4592
- - `ListItem` → 0.97 (between — medium row targets)
4593
- - `MediaCard` → 0.98 (large surface)
4594
- - `IconButton` → 0.95
4595
- - `Chip`, `CategoryStrip chip` → 0.95
4596
-
4597
- ### Platform differences
4598
- - `useNativeDriver` — `Platform.OS !== 'web'` everywhere (native driver disabled on web)
4599
- - `Select` — wheel modal on iOS, dialog on Android, `<select>` on web
4600
- - `Toast` — full width on mobile, 400px centered on web
4601
- - Hover states — web only
4602
- - `Skeleton` shimmer highlight — adapts opacity for light/dark mode
4603
-
4604
- ### Dynamic Type
4605
- All `Text` and `TextInput` components have `allowFontScaling={true}` — respects user font size accessibility settings.
4606
-
4607
- ### Scaling utilities (internal)
4608
- Used internally — not exported. `s()` horizontal scale, `vs()` vertical scale, `ms()` moderate scale relative to 350×680 base.
4609
-
4610
- ---
4611
-
4612
- ## Full Composition Examples
4613
-
4614
- The package includes **EXAMPLES.md** with complete, working code for 4 real-world app screens:
4615
-
4616
- 1. **Finance Dashboard** — MonthPicker, CurrencyDisplay, Progress, CategoryStrip, ListItem, DetailRow
4617
- 2. **Edit Profile** — Avatar, Badge, Input, Select, Switch, Card, AlertBanner
4618
- 3. **Onboarding Flow** — Badge, Progress, Input, RadioGroup, EmptyState (multi-step wizard)
4619
- 4. **Settings Screen** — MenuGroup, ListGroup, Form, MenuItem, ListItem, compound component patterns
4620
-
4621
- **For AI agents:** Import EXAMPLES.md from the package for full source code and patterns:
4622
-
4623
- ```markdown
4624
- ## UI Kit Composition Examples
4625
- @./node_modules/@retray-dev/ui-kit/EXAMPLES.md
4626
- ```
4627
-
4628
- Each example includes:
4629
- - Complete component code (copy-paste ready)
4630
- - State management setup
4631
- - Proper imports and theme usage
4632
- - Common patterns (spacing, colors, navigation, toast feedback)
4633
- - StyleSheet definitions
4634
-
4635
- **For human developers:** Examples are also live in the `example/` app. Clone the repo, run `pnpm install && pnpm build`, then `cd example && pnpm start` to see them in action.