@retray-dev/ui-kit 7.0.1 → 9.1.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 (234) hide show
  1. package/COMPONENTS.md +567 -14
  2. package/EXAMPLES.md +21 -14
  3. package/README.md +14 -8
  4. package/dist/Accordion.js +57 -5
  5. package/dist/Accordion.mjs +4 -3
  6. package/dist/AlertBanner.js +4 -1
  7. package/dist/AlertBanner.mjs +3 -2
  8. package/dist/AppHeader.d.mts +40 -0
  9. package/dist/AppHeader.d.ts +40 -0
  10. package/dist/AppHeader.js +515 -0
  11. package/dist/AppHeader.mjs +10 -0
  12. package/dist/Avatar.js +39 -29
  13. package/dist/Avatar.mjs +2 -1
  14. package/dist/Badge.js +11 -1
  15. package/dist/Badge.mjs +2 -1
  16. package/dist/Button.d.mts +8 -3
  17. package/dist/Button.d.ts +8 -3
  18. package/dist/Button.js +126 -108
  19. package/dist/Button.mjs +6 -5
  20. package/dist/ButtonGroup.mjs +1 -0
  21. package/dist/Card.js +90 -70
  22. package/dist/Card.mjs +5 -4
  23. package/dist/CategoryStrip.js +79 -22
  24. package/dist/CategoryStrip.mjs +6 -6
  25. package/dist/Checkbox.js +118 -86
  26. package/dist/Checkbox.mjs +5 -5
  27. package/dist/Chip.js +113 -80
  28. package/dist/Chip.mjs +5 -5
  29. package/dist/ConfirmDialog.js +140 -110
  30. package/dist/ConfirmDialog.mjs +7 -6
  31. package/dist/CurrencyDisplay.mjs +1 -0
  32. package/dist/CurrencyInput.d.mts +1 -1
  33. package/dist/CurrencyInput.d.ts +1 -1
  34. package/dist/CurrencyInput.js +9 -5
  35. package/dist/CurrencyInput.mjs +5 -4
  36. package/dist/DetailRow.mjs +1 -0
  37. package/dist/EmptyState.js +131 -111
  38. package/dist/EmptyState.mjs +7 -6
  39. package/dist/ErrorBoundary.d.mts +42 -0
  40. package/dist/ErrorBoundary.d.ts +42 -0
  41. package/dist/ErrorBoundary.js +351 -0
  42. package/dist/ErrorBoundary.mjs +7 -0
  43. package/dist/Form.mjs +1 -0
  44. package/dist/HolographicCard.d.mts +55 -0
  45. package/dist/HolographicCard.d.ts +55 -0
  46. package/dist/HolographicCard.js +316 -0
  47. package/dist/HolographicCard.mjs +191 -0
  48. package/dist/IconButton.d.mts +8 -3
  49. package/dist/IconButton.d.ts +8 -3
  50. package/dist/IconButton.js +115 -98
  51. package/dist/IconButton.mjs +5 -4
  52. package/dist/ImageViewer.d.mts +23 -0
  53. package/dist/ImageViewer.d.ts +23 -0
  54. package/dist/ImageViewer.js +582 -0
  55. package/dist/ImageViewer.mjs +8 -0
  56. package/dist/Input.mjs +4 -3
  57. package/dist/LabelValue.mjs +1 -0
  58. package/dist/ListGroup.mjs +1 -0
  59. package/dist/ListItem.js +131 -117
  60. package/dist/ListItem.mjs +6 -5
  61. package/dist/MediaCard.js +54 -6
  62. package/dist/MediaCard.mjs +6 -5
  63. package/dist/MenuGroup.mjs +1 -0
  64. package/dist/MenuItem.js +91 -79
  65. package/dist/MenuItem.mjs +6 -5
  66. package/dist/MonthPicker.d.mts +10 -2
  67. package/dist/MonthPicker.d.ts +10 -2
  68. package/dist/MonthPicker.js +80 -17
  69. package/dist/MonthPicker.mjs +3 -2
  70. package/dist/PagerDots.d.mts +35 -0
  71. package/dist/PagerDots.d.ts +35 -0
  72. package/dist/PagerDots.js +392 -0
  73. package/dist/PagerDots.mjs +7 -0
  74. package/dist/Pressable.d.mts +5 -5
  75. package/dist/Pressable.d.ts +5 -5
  76. package/dist/Pressable.js +97 -86
  77. package/dist/Pressable.mjs +5 -4
  78. package/dist/PricingCard.d.mts +50 -0
  79. package/dist/PricingCard.d.ts +50 -0
  80. package/dist/PricingCard.js +636 -0
  81. package/dist/PricingCard.mjs +11 -0
  82. package/dist/Progress.mjs +3 -2
  83. package/dist/RadioGroup.js +81 -30
  84. package/dist/RadioGroup.mjs +5 -5
  85. package/dist/RetrayProvider.d.mts +2 -0
  86. package/dist/RetrayProvider.d.ts +2 -0
  87. package/dist/RetrayProvider.js +214 -0
  88. package/dist/RetrayProvider.mjs +5 -0
  89. package/dist/Select.js +51 -4
  90. package/dist/Select.mjs +5 -4
  91. package/dist/SelectableGrid.d.mts +44 -0
  92. package/dist/SelectableGrid.d.ts +44 -0
  93. package/dist/SelectableGrid.js +448 -0
  94. package/dist/SelectableGrid.mjs +9 -0
  95. package/dist/Separator.mjs +1 -0
  96. package/dist/Sheet.d.mts +13 -1
  97. package/dist/Sheet.d.ts +13 -1
  98. package/dist/Sheet.js +115 -5
  99. package/dist/Sheet.mjs +4 -2
  100. package/dist/Skeleton.d.mts +50 -0
  101. package/dist/Skeleton.d.ts +50 -0
  102. package/dist/Skeleton.js +61 -0
  103. package/dist/Skeleton.mjs +4 -2
  104. package/dist/Slider.js +51 -4
  105. package/dist/Slider.mjs +3 -2
  106. package/dist/Spinner.js +28 -7
  107. package/dist/Spinner.mjs +2 -1
  108. package/dist/Switch.js +98 -48
  109. package/dist/Switch.mjs +4 -3
  110. package/dist/TabBar.d.mts +42 -0
  111. package/dist/TabBar.d.ts +42 -0
  112. package/dist/TabBar.js +361 -0
  113. package/dist/TabBar.mjs +6 -0
  114. package/dist/Tabs.js +92 -62
  115. package/dist/Tabs.mjs +5 -4
  116. package/dist/Text.js +16 -0
  117. package/dist/Text.mjs +2 -1
  118. package/dist/Textarea.mjs +4 -3
  119. package/dist/Toast.d.mts +7 -7
  120. package/dist/Toast.d.ts +7 -7
  121. package/dist/Toast.mjs +1 -0
  122. package/dist/Toggle.d.mts +6 -3
  123. package/dist/Toggle.d.ts +6 -3
  124. package/dist/Toggle.js +135 -120
  125. package/dist/Toggle.mjs +5 -5
  126. package/dist/VirtualList.mjs +1 -0
  127. package/dist/{chunk-7H2OR44A.mjs → chunk-26BCI223.mjs} +1 -1
  128. package/dist/{chunk-CRYBX2CM.mjs → chunk-2TFTAWVJ.mjs} +44 -59
  129. package/dist/chunk-3DKJ2GIC.mjs +30 -0
  130. package/dist/{chunk-KWCPOM6W.mjs → chunk-3U4SSNWP.mjs} +32 -48
  131. package/dist/chunk-4I7D47FH.mjs +139 -0
  132. package/dist/chunk-4K625MVM.mjs +142 -0
  133. package/dist/{chunk-MN7OG7IY.mjs → chunk-6OAZJ577.mjs} +6 -4
  134. package/dist/{chunk-L7E7TVEZ.mjs → chunk-756RAKE4.mjs} +2 -2
  135. package/dist/{chunk-HSPSMN6U.mjs → chunk-7QHVVCB3.mjs} +2 -2
  136. package/dist/{chunk-URLL5JBR.mjs → chunk-A3A6KNQN.mjs} +3 -3
  137. package/dist/chunk-AJ7ZDNBT.mjs +120 -0
  138. package/dist/{chunk-FTLJOUOQ.mjs → chunk-AV4EMIRH.mjs} +25 -28
  139. package/dist/chunk-AZJF2BLK.mjs +115 -0
  140. package/dist/chunk-BNP626TY.mjs +159 -0
  141. package/dist/{chunk-5IKW3VNC.mjs → chunk-DVK4G2GT.mjs} +17 -1
  142. package/dist/{chunk-6LQYY7HC.mjs → chunk-EH745HE5.mjs} +2 -2
  143. package/dist/chunk-EJ7ZPXOH.mjs +163 -0
  144. package/dist/{chunk-RKLHUDZS.mjs → chunk-GD6KXMG5.mjs} +29 -15
  145. package/dist/{chunk-RR2VQLKE.mjs → chunk-GQYFLP3D.mjs} +14 -17
  146. package/dist/{chunk-Y6MXOREN.mjs → chunk-ID72TK46.mjs} +8 -17
  147. package/dist/{chunk-NQGVLMWG.mjs → chunk-JMOZEC77.mjs} +1 -1
  148. package/dist/{chunk-GCWOGZYL.mjs → chunk-JT7HKXRB.mjs} +39 -29
  149. package/dist/{chunk-LWG526VX.mjs → chunk-KIHCWCWL.mjs} +47 -62
  150. package/dist/chunk-LXJIIOYQ.mjs +104 -0
  151. package/dist/{chunk-SBZYEV4S.mjs → chunk-M6ZXVBTK.mjs} +5 -2
  152. package/dist/{chunk-XDMN67KV.mjs → chunk-MAC465BB.mjs} +10 -8
  153. package/dist/chunk-MBMXYJJV.mjs +36 -0
  154. package/dist/chunk-MLF3EZFW.mjs +119 -0
  155. package/dist/chunk-NA7PARID.mjs +147 -0
  156. package/dist/{chunk-QXGYKWI7.mjs → chunk-O3HA6TYM.mjs} +9 -4
  157. package/dist/{chunk-63357L2X.mjs → chunk-OB4JUQ3O.mjs} +1 -1
  158. package/dist/{chunk-AU2VDY4P.mjs → chunk-PFZTM6D5.mjs} +52 -4
  159. package/dist/chunk-QKH5ZOD5.mjs +97 -0
  160. package/dist/{chunk-KZJRQOIU.mjs → chunk-TERDKCLE.mjs} +11 -1
  161. package/dist/{chunk-U4N7WF4Z.mjs → chunk-UREA2GYY.mjs} +28 -23
  162. package/dist/{chunk-TAJ2PQ2O.mjs → chunk-VGTDN7SW.mjs} +7 -6
  163. package/dist/{chunk-URDE3EUU.mjs → chunk-VQ57HWPL.mjs} +27 -15
  164. package/dist/chunk-WBOOUHSS.mjs +62 -0
  165. package/dist/{chunk-GNGLDL6Z.mjs → chunk-WJLKJMKR.mjs} +18 -0
  166. package/dist/{chunk-YZJAFS4P.mjs → chunk-X4G6APW6.mjs} +22 -19
  167. package/dist/chunk-Y6FXYEAI.mjs +8 -0
  168. package/dist/chunk-YFZ3ELX5.mjs +16 -0
  169. package/dist/{chunk-QCNARS3X.mjs → chunk-YNROWHQJ.mjs} +1 -1
  170. package/dist/chunk-Z4BVUWW6.mjs +196 -0
  171. package/dist/{chunk-GPOUINK5.mjs → chunk-ZJKGQMYH.mjs} +10 -27
  172. package/dist/index-wt-orHUi.d.mts +85 -0
  173. package/dist/index-wt-orHUi.d.ts +85 -0
  174. package/dist/index.d.mts +59 -51
  175. package/dist/index.d.ts +59 -51
  176. package/dist/index.js +1940 -744
  177. package/dist/index.mjs +49 -39
  178. package/package.json +35 -5
  179. package/src/components/Accordion/Accordion.tsx +12 -1
  180. package/src/components/AlertBanner/AlertBanner.tsx +5 -0
  181. package/src/components/AppHeader/AppHeader.tsx +172 -0
  182. package/src/components/AppHeader/index.ts +1 -0
  183. package/src/components/Avatar/Avatar.tsx +10 -2
  184. package/src/components/Badge/Badge.tsx +8 -1
  185. package/src/components/Button/Button.tsx +20 -27
  186. package/src/components/Card/Card.tsx +12 -23
  187. package/src/components/CategoryStrip/CategoryStrip.tsx +17 -21
  188. package/src/components/Checkbox/Checkbox.tsx +26 -40
  189. package/src/components/Chip/Chip.tsx +24 -33
  190. package/src/components/CurrencyInput/CurrencyInput.tsx +10 -8
  191. package/src/components/EmptyState/EmptyState.tsx +2 -1
  192. package/src/components/ErrorBoundary/ErrorBoundary.tsx +153 -0
  193. package/src/components/ErrorBoundary/index.ts +1 -0
  194. package/src/components/HolographicCard/HolographicCard.tsx +315 -0
  195. package/src/components/HolographicCard/index.ts +1 -0
  196. package/src/components/IconButton/IconButton.tsx +19 -27
  197. package/src/components/ImageViewer/ImageViewer.tsx +290 -0
  198. package/src/components/ImageViewer/index.ts +1 -0
  199. package/src/components/ListItem/ListItem.tsx +70 -67
  200. package/src/components/MediaCard/MediaCard.tsx +8 -2
  201. package/src/components/MenuItem/MenuItem.tsx +10 -25
  202. package/src/components/MonthPicker/MonthPicker.tsx +39 -13
  203. package/src/components/MonthPicker/index.ts +1 -1
  204. package/src/components/PagerDots/PagerDots.tsx +200 -0
  205. package/src/components/PagerDots/index.ts +1 -0
  206. package/src/components/Pressable/Pressable.tsx +19 -35
  207. package/src/components/PricingCard/PricingCard.tsx +220 -0
  208. package/src/components/PricingCard/index.ts +1 -0
  209. package/src/components/RadioGroup/RadioGroup.tsx +14 -27
  210. package/src/components/RetrayProvider/RetrayProvider.tsx +59 -0
  211. package/src/components/RetrayProvider/index.ts +1 -0
  212. package/src/components/SelectableGrid/SelectableGrid.tsx +205 -0
  213. package/src/components/SelectableGrid/index.ts +1 -0
  214. package/src/components/Sheet/Sheet.tsx +65 -1
  215. package/src/components/Skeleton/Skeleton.tsx +142 -1
  216. package/src/components/Spinner/Spinner.tsx +17 -2
  217. package/src/components/Switch/Switch.tsx +30 -58
  218. package/src/components/TabBar/TabBar.tsx +169 -0
  219. package/src/components/TabBar/index.ts +1 -0
  220. package/src/components/Tabs/Tabs.tsx +23 -26
  221. package/src/components/Text/Text.tsx +2 -0
  222. package/src/components/Toggle/Toggle.tsx +35 -51
  223. package/src/fonts.ts +4 -1
  224. package/src/index.ts +23 -2
  225. package/src/utils/animations.ts +29 -1
  226. package/src/utils/fontGuard.ts +34 -0
  227. package/src/utils/haptics.ts +211 -9
  228. package/src/utils/pressable.ts +66 -0
  229. package/dist/chunk-76PFOSM2.mjs +0 -41
  230. package/dist/chunk-DITNP6PL.mjs +0 -106
  231. package/dist/chunk-JBLL7U3U.mjs +0 -64
  232. package/dist/chunk-LG4DO3DK.mjs +0 -174
  233. package/dist/chunk-RMMK64W5.mjs +0 -54
  234. package/dist/chunk-RTC3CFXF.mjs +0 -29
package/COMPONENTS.md CHANGED
@@ -1,4 +1,4 @@
1
- # @retray-dev/ui-kit — Component Reference (v7.0.1)
1
+ # @retray-dev/ui-kit — Component Reference (v9.1.0)
2
2
 
3
3
  This file is the AI reference for this package. It is shipped inside the npm package so consuming projects can import it into their `CLAUDE.md` with:
4
4
 
@@ -11,7 +11,27 @@ This file is the AI reference for this package. It is shipped inside the npm pac
11
11
 
12
12
  ## Setup (Required)
13
13
 
14
- Wrap your app root with all required providers in this exact order:
14
+ ### Recommended: `RetrayProvider` (one wrapper)
15
+
16
+ Since v8, a single `RetrayProvider` wires all five required providers in the correct order — use this and skip the manual nesting:
17
+
18
+ ```tsx
19
+ import { RetrayProvider } from '@retray-dev/ui-kit'
20
+
21
+ export default function App() {
22
+ const [fontsLoaded] = useFonts(SohneFonts)
23
+ if (!fontsLoaded) return null
24
+ return (
25
+ <RetrayProvider colorScheme="system">
26
+ {/* your app */}
27
+ </RetrayProvider>
28
+ )
29
+ }
30
+ ```
31
+
32
+ ### Manual (equivalent) — if you need a custom tree
33
+
34
+ `RetrayProvider` is exactly this; the individual providers stay exported. Order is mandatory:
15
35
 
16
36
  ```tsx
17
37
  import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'
@@ -42,6 +62,66 @@ export default function App() {
42
62
  - `BottomSheetModalProvider` must be inside `GestureHandlerRootView`
43
63
  - `ToastProvider` wraps children and renders `Toaster` (from `sonner-native`) internally
44
64
 
65
+ ### Peer dependencies
66
+
67
+ Install all required peers (Expo projects: swap `pnpm add` for `npx expo install` to get SDK-pinned versions):
68
+
69
+ ```bash
70
+ 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
71
+ ```
72
+
73
+ Then add the Worklets Babel plugin to `babel.config.js` (required by `@gorhom/bottom-sheet` and the pressable animations):
74
+
75
+ ```js
76
+ module.exports = function (api) {
77
+ api.cache(true)
78
+ return {
79
+ presets: ['babel-preset-expo'],
80
+ plugins: ['react-native-worklets/plugin'], // NOT react-native-reanimated/plugin
81
+ }
82
+ }
83
+ ```
84
+
85
+ | Peer | Required? | Used by | Notes |
86
+ |------|-----------|---------|-------|
87
+ | `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. |
88
+ | `sonner-native` | **Required** | `Toast` | Also pulls `react-native-svg` + `react-native-screens`. |
89
+ | `react-native-reanimated` (≥4.0) | **Required** | Animations, `Sheet`, pressables | Needs the `react-native-worklets/plugin` Babel plugin. |
90
+ | `react-native-gesture-handler` | **Required** | `Sheet`, pressables | Wrap app in `GestureHandlerRootView`. |
91
+ | `@gorhom/bottom-sheet` (≥5.2.0) | **Required** | `Sheet`, `ConfirmDialog` | 5.1.x crashes on Reanimated v4. |
92
+ | `expo-haptics` | **Required** | Haptic feedback (all interactions) | Web-safe wrapper, no-op on web. |
93
+ | `expo-font` | **Required** | All `Text` | Load `SohneFonts` at app root before rendering. |
94
+ | `expo-linear-gradient` | **Required** | `Skeleton` shimmer | |
95
+ | `@react-native-picker/picker` | **Required** | `Select` | |
96
+ | `@react-native-community/slider` | **Required** | `Slider` | |
97
+ | `@expo/vector-icons` | **Required** | Icons everywhere | |
98
+ | `react-native-size-matters` | **Required** | Responsive scaling | |
99
+ | `react-native-pulsar` | **Optional** | Rich haptics on dev builds | Loaded via dynamic `import()` + try/catch; falls back to `expo-haptics`. Skip in Expo Go. `pnpm add react-native-pulsar` |
100
+ | `@shopify/react-native-skia` + `expo-sensors` | **Optional** | Deep-import `HolographicCard` only | `pnpm add @shopify/react-native-skia expo-sensors` |
101
+
102
+ > **Dev build vs Expo Go:** Everything in the kit runs in **Expo Go** — `react-native-pulsar` is the only native module that needs a custom dev build, and it degrades gracefully (basic haptics) when absent. No component is broken by running in Expo Go.
103
+
104
+ ### Troubleshooting: `react-native-screens` codegen on RN 0.83
105
+
106
+ 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:
107
+
108
+ ```js
109
+ // metro.config.js
110
+ const { getDefaultConfig } = require('expo/metro-config')
111
+ const config = getDefaultConfig(__dirname)
112
+
113
+ const defaultResolveRequest = config.resolver.resolveRequest
114
+ config.resolver.resolveRequest = (context, moduleName, platform) => {
115
+ // Force react-native-screens to its compiled output, bypassing the src codegen bug.
116
+ if (moduleName.startsWith('react-native-screens/src')) {
117
+ moduleName = moduleName.replace('react-native-screens/src', 'react-native-screens/lib/commonjs')
118
+ }
119
+ return (defaultResolveRequest ?? context.resolveRequest)(context, moduleName, platform)
120
+ }
121
+
122
+ module.exports = config
123
+ ```
124
+
45
125
  ## Typography — Sohne (Required)
46
126
 
47
127
  All components use **Sohne** as the font family. You **must** load it before rendering any UI kit component.
@@ -454,6 +534,41 @@ The example app now has a dedicated **Compound Components** section showcasing `
454
534
 
455
535
  ---
456
536
 
537
+ ## Migration Guide: v7 → v8
538
+
539
+ ### Breaking Changes
540
+
541
+ **1. New required peer dependency: `sonner-native`**
542
+ 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:
543
+ ```bash
544
+ pnpm add sonner-native
545
+ ```
546
+ (You already need its companions `react-native-svg` and `react-native-screens`.)
547
+
548
+ **2. `@gorhom/bottom-sheet` peer range tightened to `>=5.2.0`**
549
+ 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`.
550
+
551
+ **3. `./fonts` stays at `src/fonts.ts` (unchanged)**
552
+ 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:
553
+ ```ts
554
+ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
555
+ ```
556
+
557
+ ### New — packaging & DX
558
+
559
+ - **`RetrayProvider`** — one wrapper replacing the five-provider boilerplate (see Setup below).
560
+ - **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.
561
+
562
+ ### New components
563
+
564
+ `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.
565
+
566
+ ### Optional peers (only if you use the matching component)
567
+
568
+ - `@shopify/react-native-skia` + `expo-sensors` → `HolographicCard` (deep-import only, not in the main barrel).
569
+
570
+ ---
571
+
457
572
  ## Components
458
573
 
459
574
  ---
@@ -548,6 +663,8 @@ The example app now has a dedicated **Compound Components** section showcasing `
548
663
  | iconColor | `string` | — | Override icon color. Defaults to variant label color |
549
664
  | iconPosition | `'left' \| 'right'` | `'left'` | Side the icon appears on |
550
665
  | style | `ViewStyle` | — | Override container style |
666
+ | accessibilityLabel | `string` | — | Screen reader label. Defaults to `label` |
667
+ | accessibilityHint | `string` | — | Screen reader hint |
551
668
 
552
669
  **Variants:**
553
670
 
@@ -689,6 +806,8 @@ The example app now has a dedicated **Compound Components** section showcasing `
689
806
  | badge | `boolean \| number` | — | Notification badge overlay |
690
807
  | disabled | `boolean` | — | Reduces opacity to 0.5 |
691
808
  | style | `ViewStyle` | — | — |
809
+ | accessibilityLabel | `string` | — | Screen reader label |
810
+ | accessibilityHint | `string` | — | Screen reader hint |
692
811
 
693
812
  **Variants:** Same token logic as Button.
694
813
 
@@ -704,7 +823,7 @@ The example app now has a dedicated **Compound Components** section showcasing `
704
823
 
705
824
  | Size | Dimensions | Icon size |
706
825
  |------|-----------|-----------|
707
- | `sm` | 32 × 32px | 16px |
826
+ | `sm` | 44 × 44px | 18px |
708
827
  | `md` | 44 × 44px | 20px |
709
828
  | `lg` | 52 × 52px | 24px |
710
829
 
@@ -765,6 +884,7 @@ The example app now has a dedicated **Compound Components** section showcasing `
765
884
  | label | `string` | — | Label above the input field |
766
885
  | error | `string` | — | Error text below — turns border `destructive` (2px) |
767
886
  | hint | `string` | — | Helper text below (hidden when `error` is set) |
887
+ | disabled | `boolean` | — | Reduces opacity to 0.5, disables interaction |
768
888
  | prefix | `string \| ReactNode` | — | Content before the input text (e.g. `"$"`, icon node) |
769
889
  | suffix | `string \| ReactNode` | — | Content after the input text (e.g. `"kg"`, icon node) |
770
890
  | prefixStyle | `TextStyle` | — | Style for prefix string (no effect on ReactNode) |
@@ -1469,15 +1589,29 @@ const renderItem = useCallback(({ item }) => (
1469
1589
  </View>
1470
1590
  ```
1471
1591
 
1472
- **Composition skeleton for a ListItem:**
1592
+ **Sub-skeletons (v8):** ready-made placeholders that mirror a component's footprint, so grids/lists don't reflow when real data arrives.
1593
+
1473
1594
  ```tsx
1474
- <View style={{ flexDirection: 'row', gap: SPACING.md, padding: SPACING.base }}>
1475
- <Skeleton preset="circle" diameter={40} />
1476
- <View style={{ flex: 1, gap: SPACING.xs }}>
1477
- <Skeleton height={16} width="60%" />
1478
- <Skeleton preset="text" />
1479
- </View>
1480
- </View>
1595
+ // Matches <MediaCard> image block + title/subtitle lines
1596
+ <Skeleton.MediaCard aspectRatio="4:3" /> // props: aspectRatio ('1:1'|'4:3'|'16:9'|'4:5'|'3:2'), showSubtitle, style
1597
+
1598
+ // Matches <ListItem> leading circle + title/subtitle lines
1599
+ <Skeleton.ListItem /> // props: showAvatar, showSubtitle, style
1600
+
1601
+ // Repeated list/grid placeholder — for VirtualList / FlatList while loading.
1602
+ // columns=1 → stacked ListItemSkeleton; columns>1 → grid of MediaCardSkeleton.
1603
+ <Skeleton.List count={6} /> // stacked list
1604
+ <Skeleton.List count={6} columns={2} /> // 2-col card grid
1605
+ // props: count, columns, gap, aspectRatio (grid), showAvatar (list), style
1606
+
1607
+ // Also exported as named components: MediaCardSkeleton, ListItemSkeleton, ListSkeleton
1608
+
1609
+ // As a VirtualList loading state
1610
+ <VirtualList
1611
+ data={loading ? [] : rows}
1612
+ renderItem={renderItem}
1613
+ ListEmptyComponent={loading ? <Skeleton.List count={8} /> : <EmptyState .../>}
1614
+ />
1481
1615
  ```
1482
1616
 
1483
1617
  ---
@@ -1581,6 +1715,8 @@ const renderItem = useCallback(({ item }) => (
1581
1715
  | iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon` |
1582
1716
  | iconColor | `string` | — | Override icon color. Defaults to `foregroundMuted` |
1583
1717
  | action | `ReactNode` | — | CTA below text — typically a `Button`. Hidden in `compact` size |
1718
+ | actionLabel | `string` | — | Shorthand for a primary `Button` CTA — renders a default `Button` with this label. Requires `onAction`. Hidden in `compact` size |
1719
+ | onAction | `() => void` | — | Handler for the shorthand `actionLabel` button. Required when `actionLabel` is set |
1584
1720
  | size | `'default' \| 'compact'` | `'default'` | `compact` is smaller, hides description and action |
1585
1721
  | style | `ViewStyle` | — | — |
1586
1722
 
@@ -2082,7 +2218,8 @@ Add `react-native-worklets/plugin` (not `react-native-reanimated/plugin`) to `ba
2082
2218
  | open | `boolean` | required | `true` presents the sheet, `false` dismisses it |
2083
2219
  | onClose | `() => void` | required | Called on swipe-dismiss or backdrop press |
2084
2220
  | title | `string` | — | Sheet heading |
2085
- | subtitle | `string` | — | Supporting text below title (replaces deprecated `description`) |
2221
+ | subtitle | `string` | — | Supporting text below title |
2222
+ | description | `string` | — | **Deprecated alias** for `subtitle` — prefer `subtitle` |
2086
2223
  | showCloseButton | `boolean` | `false` | Show X close button in the header |
2087
2224
  | children | `ReactNode` | — | Sheet content |
2088
2225
  | style | `ViewStyle` | — | Inner scroll/content container style |
@@ -2095,10 +2232,13 @@ Add `react-native-worklets/plugin` (not `react-native-reanimated/plugin`) to `ba
2095
2232
  | android_keyboardInputMode | `'adjustPan' \| 'adjustResize'` | `'adjustPan'` | Android-only: `'adjustPan'` moves the window (default — fixes restore issues with dynamic sizing). `'adjustResize'` resizes the container (can cause transparent gap when keyboard dismisses) |
2096
2233
  | footer | `ReactNode` | — | Sticky footer below scroll area (sticky above keyboard) |
2097
2234
  | snapPoints | `(string \| number)[]` | — | Optional snap points (e.g., `['50%', '85%']`). When omitted, uses dynamic sizing (auto-fits content) |
2235
+ | responsive | `boolean` | `false` | **(v8)** On wide screens (width ≥ `BREAKPOINTS.wide`) render a centered modal dialog instead of a bottom sheet. Stays a bottom sheet on phones |
2236
+ | dialogMaxWidth | `number` | `480` | Max width of the centered dialog. Only applies when `responsive` |
2098
2237
 
2099
2238
  **Features:**
2100
2239
  - `enableDynamicSizing` — height auto-fits content, no `snapPoints` needed (default behavior when `snapPoints` is omitted)
2101
2240
  - `snapPoints` — optionally provide custom snap points (e.g., `['50%', '85%']`). Disables dynamic sizing when provided
2241
+ - `responsive` — on tablets/web, becomes a centered dialog (max width `dialogMaxWidth`). Note: the dialog path uses a plain RN `Modal`, so `SheetTextInput` is **not** required there — use a regular `TextInput`
2102
2242
  - `enablePanDownToClose` — swipe down to dismiss
2103
2243
  - Backdrop press dismisses
2104
2244
  - **Scrollable content:** use `scrollable` prop or `maxHeight`. Both use `BottomSheetScrollView` — do NOT use plain `ScrollView` inside Sheet
@@ -2896,6 +3036,7 @@ const [categories, setCategories] = useState<number[]>([1, 3])
2896
3036
  | multiSelect | `boolean` | `false` | Allow multiple simultaneous selections |
2897
3037
  | style | `ViewStyle` | — | ScrollView content container style |
2898
3038
  | itemStyle | `ViewStyle` | — | Style applied to each chip's wrapper |
3039
+ | accessibilityLabel | `string` | — | Screen reader label for the scroll container |
2899
3040
 
2900
3041
  **CategoryItem type:**
2901
3042
  ```ts
@@ -3078,6 +3219,8 @@ Total ·············· $50.000
3078
3219
  |------|------|---------|-------|
3079
3220
  | value | `MonthPickerValue` | required | `{ month: 1–12, year: number }` |
3080
3221
  | onChange | `(value: MonthPickerValue) => void` | required | Called on navigation |
3222
+ | minValue | `MonthPickerValue` | — | Earliest selectable month (inclusive). Prev arrow disables + dims at this bound |
3223
+ | maxValue | `MonthPickerValue` | — | Latest selectable month (inclusive). Next arrow disables + dims at this bound — e.g. cap at the current month |
3081
3224
  | locale | `string` | `'en'` | BCP 47 locale. Built-in: `'en'`, `'es'`, `'pt'`, `'fr'`. Other locales: use `formatLabel` |
3082
3225
  | formatLabel | `(value: MonthPickerValue) => string` | — | Custom label formatter. Takes precedence over `locale` |
3083
3226
  | style | `ViewStyle` | — | — |
@@ -3087,7 +3230,15 @@ Total ·············· $50.000
3087
3230
  { month: number; year: number } // month is 1-indexed (January = 1)
3088
3231
  ```
3089
3232
 
3090
- **Navigation:** Year wraps correctly at boundaries (December January increments year, January December decrements year). Fully controlled no internal state.
3233
+ **Date bridge helpers** (exported): for domains that store `Date` (e.g. finance apps in a fixed TZ), convert at the boundary instead of changing your model.
3234
+ ```ts
3235
+ import { dateToMonthPickerValue, monthPickerValueToDate } from '@retray-dev/ui-kit'
3236
+
3237
+ dateToMonthPickerValue(new Date()) // → { month, year } (local time)
3238
+ monthPickerValueToDate({ month: 6, year: 2026 }) // → Date at first day of the month
3239
+ ```
3240
+
3241
+ **Navigation:** Year wraps correctly at boundaries (December → January increments year, January → December decrements year). When `minValue`/`maxValue` are set, the boundary arrow is disabled (opacity 0.3) and presses are ignored. Fully controlled — no internal state.
3091
3242
 
3092
3243
  **Styling:** Centered row with `←` / `→` buttons (44×44px each). Label: 17pt / Medium / 160px min-width, centered.
3093
3244
 
@@ -3186,6 +3337,407 @@ const [period, setPeriod] = useState({
3186
3337
 
3187
3338
  ---
3188
3339
 
3340
+ ### RetrayProvider
3341
+
3342
+ **Import:** `import { RetrayProvider } from '@retray-dev/ui-kit'`
3343
+
3344
+ **When to use:** At your app root, instead of hand-nesting the five required providers. Removes an entire class of provider-order bugs. The individual providers stay exported for custom trees.
3345
+
3346
+ | Prop | Type | Default | Notes |
3347
+ |------|------|---------|-------|
3348
+ | children | `ReactNode` | required | Your app |
3349
+ | theme | `Theme` | — | Per-scheme token overrides, forwarded to `ThemeProvider` |
3350
+ | colorScheme | `'system' \| 'light' \| 'dark'` | `'system'` | Forwarded to `ThemeProvider` |
3351
+
3352
+ **Wraps (in order):** `SafeAreaProvider` (with `initialWindowMetrics`) → `GestureHandlerRootView` → `ThemeProvider` → `BottomSheetModalProvider` → `ToastProvider`.
3353
+
3354
+ **Example:**
3355
+ ```tsx
3356
+ import { RetrayProvider } from '@retray-dev/ui-kit'
3357
+
3358
+ export default function App() {
3359
+ const [fontsLoaded] = useFonts(SohneFonts)
3360
+ if (!fontsLoaded) return null
3361
+ return (
3362
+ <RetrayProvider colorScheme="system" theme={{ light: { primary: '#ff385c' } }}>
3363
+ <RootNavigator />
3364
+ </RetrayProvider>
3365
+ )
3366
+ }
3367
+ ```
3368
+
3369
+ ---
3370
+
3371
+ ### AppHeader
3372
+
3373
+ **Import:** `import { AppHeader } from '@retray-dev/ui-kit'`
3374
+
3375
+ **When to use:** Top app bar / screen chrome — back button, title/subtitle, and a right action slot. Responsive: title left-aligns on phones and centers when width ≥ `BREAKPOINTS.wide`.
3376
+
3377
+ **Expo Router compatibility:** ✅ Fully compatible. Use with `useRouter()` for navigation:
3378
+
3379
+ ```tsx
3380
+ import { useRouter } from 'expo-router'
3381
+ import { AppHeader } from '@retray-dev/ui-kit'
3382
+
3383
+ export default function Screen() {
3384
+ const router = useRouter()
3385
+ return (
3386
+ <AppHeader
3387
+ title="Settings"
3388
+ onBack={() => router.back()}
3389
+ right={<IconButton iconName="more-horizontal" variant="text" />}
3390
+ />
3391
+ )
3392
+ }
3393
+ ```
3394
+
3395
+ | Prop | Type | Default | Notes |
3396
+ |------|------|---------|-------|
3397
+ | title | `string` | — | Primary title (truncates to 1 line) |
3398
+ | subtitle | `string` | — | Secondary line under the title |
3399
+ | onBack | `() => void` | — | Shows a back button on the left when provided |
3400
+ | backIconName | `string` | `'chevron-left'` | Icon for the back button |
3401
+ | left | `ReactNode` | — | Custom left content — overrides the back button |
3402
+ | right | `ReactNode` | — | Custom right content (actions) |
3403
+ | titleAlign | `'auto' \| 'left' \| 'center'` | `'auto'` | `auto` = left on narrow, centered on wide |
3404
+ | bordered | `boolean` | `true` | Hairline border underneath |
3405
+ | withSafeArea | `boolean` | `true` | Apply top safe-area inset as padding |
3406
+ | backgroundColor | `string` | `colors.background` | — |
3407
+ | style | `ViewStyle` | — | — |
3408
+
3409
+ **Example:**
3410
+ ```tsx
3411
+ <AppHeader
3412
+ title="Settings"
3413
+ onBack={navigation.goBack}
3414
+ right={<IconButton iconName="more-horizontal" variant="text" onPress={openMenu} />}
3415
+ />
3416
+ ```
3417
+
3418
+ ---
3419
+
3420
+ ### TabBar
3421
+
3422
+ **Import:** `import { TabBar } from '@retray-dev/ui-kit'`
3423
+
3424
+ **When to use:** Bottom tab navigation — icon + label tabs with active tint and badges. Pair with your navigator, or drive with local state for a single-screen app.
3425
+
3426
+ **Expo Router compatibility:** ✅ Fully compatible. Use in `_layout.tsx` to build custom tab navigation:
3427
+
3428
+ ```tsx
3429
+ // app/(tabs)/_layout.tsx
3430
+ import { Tabs, useRouter, useSegments } from 'expo-router'
3431
+ import { TabBar } from '@retray-dev/ui-kit'
3432
+
3433
+ export default function TabLayout() {
3434
+ const router = useRouter()
3435
+ const segments = useSegments()
3436
+ const activeKey = segments[1] || 'home'
3437
+
3438
+ return (
3439
+ <Tabs
3440
+ tabBar={() => (
3441
+ <TabBar
3442
+ items={[
3443
+ { key: 'home', label: 'Home', iconName: 'home' },
3444
+ { key: 'search', label: 'Search', iconName: 'search' },
3445
+ { key: 'profile', label: 'Profile', iconName: 'user', badge: 3 },
3446
+ ]}
3447
+ activeKey={activeKey}
3448
+ onTabPress={(key) => router.push(`/(tabs)/${key}`)}
3449
+ />
3450
+ )}
3451
+ >
3452
+ {/* ... screens */}
3453
+ </Tabs>
3454
+ )
3455
+ }
3456
+ ```
3457
+
3458
+ | Prop | Type | Default | Notes |
3459
+ |------|------|---------|-------|
3460
+ | items | `TabBarItem[]` | required | `{ key, label?, iconName?, icon?, badge? }` |
3461
+ | activeKey | `string` | required | Key of the active tab |
3462
+ | onTabPress | `(key: string) => void` | required | — |
3463
+ | activeColor | `string` | `colors.primary` | — |
3464
+ | inactiveColor | `string` | `colors.foregroundMuted` | — |
3465
+ | withSafeArea | `boolean` | `true` | Apply bottom safe-area inset |
3466
+ | style | `ViewStyle` | — | — |
3467
+
3468
+ **TabBarItem.badge:** `true` shows a dot; a number shows a count (capped at `99+`).
3469
+
3470
+ **Haptics:** `selectionAsync` when switching to a new tab.
3471
+
3472
+ **Example:**
3473
+ ```tsx
3474
+ <TabBar
3475
+ activeKey={tab}
3476
+ onTabPress={setTab}
3477
+ items={[
3478
+ { key: 'home', label: 'Home', iconName: 'home' },
3479
+ { key: 'wallet', label: 'Wallet', iconName: 'credit-card' },
3480
+ { key: 'profile', label: 'Profile', iconName: 'user', badge: 3 },
3481
+ ]}
3482
+ />
3483
+ ```
3484
+
3485
+ ---
3486
+
3487
+ ### PagerDots
3488
+
3489
+ **Import:** `import { PagerDots } from '@retray-dev/ui-kit'`
3490
+
3491
+ **When to use:** Page indicator for carousels / document pagers. The active dot stretches into a pill and color-crossfades — all on the UI thread. Now supports optional previous/next control buttons.
3492
+
3493
+ | Prop | Type | Default | Notes |
3494
+ |------|------|---------|-------|
3495
+ | count | `number` | required | Total pages |
3496
+ | activeIndex | `number` | required | 0-based active page |
3497
+ | onDotPress | `(index: number) => void` | — | Omit to make dots non-interactive |
3498
+ | showControls | `boolean \| { onPrevious?, onNext? }` | `false` | Show prev/next buttons. If `true`, uses `onDotPress(activeIndex ± 1)`. If object, uses custom handlers |
3499
+ | dotSize | `number` | `8` | Inactive dot diameter (dp) |
3500
+ | spacing | `number` | `8` | Gap between dots (dp) |
3501
+ | activeColor | `string` | `colors.primary` | — |
3502
+ | inactiveColor | `string` | `colors.border` | — |
3503
+ | style | `ViewStyle` | — | — |
3504
+
3505
+ **Example:**
3506
+ ```tsx
3507
+ <PagerDots count={pages.length} activeIndex={page} onDotPress={setPage} showControls />
3508
+ ```
3509
+
3510
+ ---
3511
+
3512
+ ### SelectableGrid
3513
+
3514
+ **Import:** `import { SelectableGrid } from '@retray-dev/ui-kit'`
3515
+
3516
+ **When to use:** Grid of selectable cells (icon + label) — store / category / emoji pickers where a list would be the wrong shape. Single or multi select. Generic over the value type (`string | number`). Now supports horizontal scrolling mode for single-row layouts.
3517
+
3518
+ | Prop | Type | Default | Notes |
3519
+ |------|------|---------|-------|
3520
+ | items | `SelectableGridItem<T>[]` | required | `{ value, label?, iconName?, icon?, disabled? }` |
3521
+ | value | `T \| T[] \| null` | required | Selected value(s) — array when `multiple` |
3522
+ | onChange | `(value: T) => void` | required | Fires with the toggled item's value |
3523
+ | multiple | `boolean` | `false` | Allow multiple selection (`value` should be an array) |
3524
+ | numColumns | `number` | `4` | Cells per row (exact-fit width is measured, not percentage). Ignored when `orientation='horizontal'` |
3525
+ | gap | `number` | `12` | Gap between cells (dp) |
3526
+ | orientation | `'grid' \| 'horizontal'` | `'grid'` | `'grid'` wraps into rows, `'horizontal'` creates a single scrollable row |
3527
+ | style | `ViewStyle` | — | — |
3528
+
3529
+ **Selected cell:** primary border + tinted background; icon/label switch to `primary`. **Press scale:** chip (0.94). **Haptics:** `selectionAsync` on press.
3530
+
3531
+ **Horizontal mode:** Fixed 96dp cell width, horizontal ScrollView, no vertical wrapping.
3532
+
3533
+ **Example:**
3534
+ ```tsx
3535
+ {/* Grid layout */}
3536
+ <SelectableGrid
3537
+ items={categories}
3538
+ value={category}
3539
+ onChange={setCategory}
3540
+ numColumns={4}
3541
+ />
3542
+
3543
+ {/* Horizontal scrolling */}
3544
+ <SelectableGrid
3545
+ items={categories}
3546
+ value={category}
3547
+ onChange={setCategory}
3548
+ orientation="horizontal"
3549
+ />
3550
+ ```
3551
+
3552
+ **Example:**
3553
+ ```tsx
3554
+ <SelectableGrid
3555
+ items={[
3556
+ { value: 'food', label: 'Food', iconName: 'coffee' },
3557
+ { value: 'transport', label: 'Transport', iconName: 'truck' },
3558
+ { value: 'home', label: 'Home', iconName: 'home' },
3559
+ ]}
3560
+ value={category}
3561
+ onChange={setCategory}
3562
+ numColumns={4}
3563
+ />
3564
+ ```
3565
+
3566
+ ---
3567
+
3568
+ ### PricingCard
3569
+
3570
+ **Import:** `import { PricingCard } from '@retray-dev/ui-kit'`
3571
+
3572
+ **When to use:** Paywalls and freemium tiers — price header, feature list with check marks, optional promo badge, and CTA. Set `highlighted` on the recommended plan.
3573
+
3574
+ | Prop | Type | Default | Notes |
3575
+ |------|------|---------|-------|
3576
+ | name | `string` | required | Plan name |
3577
+ | price | `string` | required | Formatted price, e.g. `"$9"` or `"Free"` |
3578
+ | period | `string` | — | Billing suffix, e.g. `"/mo"` |
3579
+ | description | `string` | — | Short text under the price |
3580
+ | features | `(string \| PricingFeature)[]` | `[]` | Strings = included; `{ label, included: false }` for excluded (dashed, struck) |
3581
+ | ctaLabel | `string` | — | CTA button label (renders a `Button`) |
3582
+ | onCtaPress | `() => void` | — | — |
3583
+ | badge | `string` | — | Promo badge, e.g. `"Most popular"` |
3584
+ | highlighted | `boolean` | `false` | Primary border + elevation + primary CTA |
3585
+ | footnote | `string` | — | Small print under the CTA |
3586
+ | style | `ViewStyle` | — | — |
3587
+
3588
+ **Example:**
3589
+ ```tsx
3590
+ <PricingCard
3591
+ name="Pro"
3592
+ price="$9"
3593
+ period="/mo"
3594
+ badge="Most popular"
3595
+ highlighted
3596
+ features={['Unlimited docs', 'Priority support', { label: 'SSO', included: false }]}
3597
+ ctaLabel="Start trial"
3598
+ onCtaPress={subscribe}
3599
+ footnote="Cancel anytime"
3600
+ />
3601
+ ```
3602
+
3603
+ ---
3604
+
3605
+ ### ErrorBoundary
3606
+
3607
+ **Import:** `import { ErrorBoundary } from '@retray-dev/ui-kit'`
3608
+
3609
+ **When to use:** Wrap screens or risky subtrees so a render error shows a themed fallback instead of unmounting the whole app.
3610
+
3611
+ | Prop | Type | Default | Notes |
3612
+ |------|------|---------|-------|
3613
+ | children | `ReactNode` | required | Protected subtree |
3614
+ | fallback | `ReactNode \| (props: { error, reset }) => ReactNode` | themed card | Custom fallback |
3615
+ | title | `string` | `'Something went wrong'` | Default fallback title |
3616
+ | message | `string` | `error.message` | Default fallback body |
3617
+ | onError | `(error, info) => void` | — | Wire your crash reporter here |
3618
+
3619
+ **Default fallback:** centered themed card with a destructive icon, title, message, and a "Try again" button that calls `reset()`.
3620
+
3621
+ **Example:**
3622
+ ```tsx
3623
+ <ErrorBoundary onError={reportCrash}>
3624
+ <DocumentViewer />
3625
+ </ErrorBoundary>
3626
+
3627
+ // Custom fallback
3628
+ <ErrorBoundary fallback={({ error, reset }) => <MyFallback error={error} onRetry={reset} />}>
3629
+ <RiskyScreen />
3630
+ </ErrorBoundary>
3631
+ ```
3632
+
3633
+ ---
3634
+
3635
+ ### ImageViewer
3636
+
3637
+ **Import:** `import { ImageViewer } from '@retray-dev/ui-kit'`
3638
+
3639
+ **When to use:** Full-screen zoomable image gallery — multi-page documents, photo viewers. Horizontal paging + pinch / double-tap zoom + pan, with page dots and a close button. Requires `react-native-gesture-handler` (already a peer).
3640
+
3641
+ | Prop | Type | Default | Notes |
3642
+ |------|------|---------|-------|
3643
+ | images | `ImageSourcePropType[]` | required | `{ uri }` or `require()` sources |
3644
+ | visible | `boolean` | required | Controls the modal |
3645
+ | onClose | `() => void` | required | — |
3646
+ | initialIndex | `number` | `0` | Page to open on |
3647
+
3648
+ **Behavior:** pinch to zoom (max 3×), double-tap to toggle 2.5× zoom, pan while zoomed. Paging locks automatically while a page is zoomed in. `PagerDots` shown when there is more than one image.
3649
+
3650
+ **Example:**
3651
+ ```tsx
3652
+ <ImageViewer
3653
+ images={doc.pages.map((uri) => ({ uri }))}
3654
+ visible={viewerOpen}
3655
+ initialIndex={page}
3656
+ onClose={() => setViewerOpen(false)}
3657
+ />
3658
+ ```
3659
+
3660
+ ---
3661
+
3662
+ ### HolographicCard
3663
+
3664
+ **Import (deep import only):** `import { HolographicCard, FOIL_PRESETS, FoilPreset } from '@retray-dev/ui-kit/HolographicCard'`
3665
+
3666
+ > Not re-exported from the main barrel — it depends on the **optional** peers `@shopify/react-native-skia` and `expo-sensors`, which are deliberately kept out of the main module graph. Deep-import it so consumers who don't use it never pull Skia.
3667
+
3668
+ **When to use:** Holographic / foil trading-card surface (Pokémon-TCG style). Renders art on a Skia canvas with an animated rainbow sheen and tilts in 3D toward device motion; the sheen tracks the tilt so the foil "catches the light".
3669
+
3670
+ **Install:** `pnpm add @shopify/react-native-skia expo-sensors`
3671
+
3672
+ | Prop | Type | Default | Notes |
3673
+ |------|------|---------|-------|
3674
+ | source | `require()` / `{ uri }` | — | Card art. Omitted = gradient-only foil surface |
3675
+ | width | `number` | `300` | Card width (dp) |
3676
+ | height | `number` | `width * 1.4` | Trading-card ratio by default |
3677
+ | borderRadius | `number` | `RADIUS.md` | — |
3678
+ | enableTilt | `boolean` | `true` | React to gyroscope. No-op on web; static sheen if `expo-sensors` is absent |
3679
+ | intensity | `number` | `1` | Foil sheen strength, 0–1 |
3680
+ | onPress | `() => void` | — | Adds card press-scale + `impactLight` |
3681
+ | style | `ViewStyle` | — | — |
3682
+ | foilPreset | `FoilPreset` | `'rainbow'` | Foil color preset. Ignored if `foilColors` is provided |
3683
+ | foilColors | `string[]` | — | Custom foil gradient colors (array of RGBA strings). Overrides `foilPreset` |
3684
+ | maxTiltDegrees | `number` | `10` | Maximum tilt angle in degrees (0–45) |
3685
+ | tiltSensitivity | `number` | `1` | Sensitivity of tilt to device motion (0.1–2). Higher = more responsive |
3686
+ | sheenSpread | `number` | `0.6` | How far the sheen moves relative to tilt (0–1) |
3687
+ | tiltAnimationDuration | `number` | `80` | Animation duration for tilt smoothing in ms |
3688
+ | perspective | `number` | `800` | Perspective depth for 3D effect (200–2000) |
3689
+ | blendMode | `string` | `'plus'` | Blend mode: `'plus'` \| `'screen'` \| `'overlay'` \| `'softLight'` \| `'hardLight'` |
3690
+
3691
+ **Available foil presets (`FoilPreset`):**
3692
+
3693
+ | Preset | Description |
3694
+ |--------|-------------|
3695
+ | `rainbow` | Classic holographic rainbow (default) |
3696
+ | `gold` | Premium gold foil |
3697
+ | `silver` | Chrome silver foil |
3698
+ | `cosmic` | Deep space cosmic |
3699
+ | `emerald` | Emerald green luxury |
3700
+ | `rose` | Rose gold / pink |
3701
+ | `ocean` | Deep ocean blue |
3702
+ | `fire` | Hot fire gradient |
3703
+ | `aurora` | Aurora borealis |
3704
+ | `neon` | Neon cyberpunk |
3705
+
3706
+ **Example:**
3707
+ ```tsx
3708
+ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCard'
3709
+
3710
+ // Basic usage
3711
+ <HolographicCard source={require('./assets/card.png')} width={320} onPress={openCard} />
3712
+
3713
+ // Gold foil preset with higher tilt
3714
+ <HolographicCard
3715
+ source={{ uri: 'https://example.com/card.png' }}
3716
+ width={280}
3717
+ foilPreset="gold"
3718
+ maxTiltDegrees={25}
3719
+ tiltSensitivity={1.5}
3720
+ intensity={0.9}
3721
+ />
3722
+
3723
+ // Custom foil colors
3724
+ <HolographicCard
3725
+ width={280}
3726
+ foilColors={[
3727
+ 'rgba(255, 0, 0, 0.5)',
3728
+ 'rgba(255, 255, 0, 0.4)',
3729
+ 'rgba(0, 255, 0, 0.5)',
3730
+ ]}
3731
+ blendMode="screen"
3732
+ perspective={1200}
3733
+ />
3734
+
3735
+ // Foil-only surface (no image)
3736
+ <HolographicCard foilPreset="cosmic" width={200} maxTiltDegrees={30} />
3737
+ ```
3738
+
3739
+ ---
3740
+
3189
3741
  ## Icon System
3190
3742
 
3191
3743
  The library ships a built-in icon resolver — pass any icon name string to components without manual imports.
@@ -3366,11 +3918,12 @@ Used internally — not exported. `s()` horizontal scale, `vs()` vertical scale,
3366
3918
 
3367
3919
  ## Full Composition Examples
3368
3920
 
3369
- The package includes **EXAMPLES.md** with complete, working code for 3 real-world app screens:
3921
+ The package includes **EXAMPLES.md** with complete, working code for 4 real-world app screens:
3370
3922
 
3371
3923
  1. **Finance Dashboard** — MonthPicker, CurrencyDisplay, Progress, CategoryStrip, ListItem, DetailRow
3372
3924
  2. **Edit Profile** — Avatar, Badge, Input, Select, Switch, Card, AlertBanner
3373
3925
  3. **Onboarding Flow** — Badge, Progress, Input, RadioGroup, EmptyState (multi-step wizard)
3926
+ 4. **Settings Screen** — MenuGroup, ListGroup, Form, MenuItem, ListItem, compound component patterns
3374
3927
 
3375
3928
  **For AI agents:** Import EXAMPLES.md from the package for full source code and patterns:
3376
3929