@retray-dev/ui-kit 9.1.0 → 9.3.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 (64) hide show
  1. package/COMPONENTS.md +166 -4
  2. package/CONSUMER.md +247 -0
  3. package/DESIGN.md +668 -0
  4. package/FONTS.md +107 -0
  5. package/README.md +3 -3
  6. package/dist/AlertBanner.d.mts +3 -1
  7. package/dist/AlertBanner.d.ts +3 -1
  8. package/dist/AlertBanner.js +18 -2
  9. package/dist/AlertBanner.mjs +1 -1
  10. package/dist/ConfirmDialog.d.mts +3 -1
  11. package/dist/ConfirmDialog.d.ts +3 -1
  12. package/dist/ConfirmDialog.js +3 -0
  13. package/dist/ConfirmDialog.mjs +1 -1
  14. package/dist/CurrencyInput.d.mts +3 -1
  15. package/dist/CurrencyInput.d.ts +3 -1
  16. package/dist/CurrencyInput.js +52 -39
  17. package/dist/CurrencyInput.mjs +2 -3
  18. package/dist/ImageUpload.d.mts +27 -0
  19. package/dist/ImageUpload.d.ts +27 -0
  20. package/dist/ImageUpload.js +399 -0
  21. package/dist/ImageUpload.mjs +9 -0
  22. package/dist/Input.d.mts +3 -1
  23. package/dist/Input.d.ts +3 -1
  24. package/dist/Input.js +48 -37
  25. package/dist/Input.mjs +1 -2
  26. package/dist/ListItem.d.mts +9 -2
  27. package/dist/ListItem.d.ts +9 -2
  28. package/dist/ListItem.js +9 -2
  29. package/dist/ListItem.mjs +1 -1
  30. package/dist/SheetSelect.d.mts +25 -0
  31. package/dist/SheetSelect.d.ts +25 -0
  32. package/dist/SheetSelect.js +440 -0
  33. package/dist/SheetSelect.mjs +9 -0
  34. package/dist/Textarea.mjs +1 -2
  35. package/dist/{chunk-M6ZXVBTK.mjs → chunk-6MKGPAR2.mjs} +21 -5
  36. package/dist/{chunk-EH745HE5.mjs → chunk-CZCQZHG6.mjs} +13 -4
  37. package/dist/{chunk-7QHVVCB3.mjs → chunk-FZZLPJ6B.mjs} +3 -0
  38. package/dist/{chunk-MAC465BB.mjs → chunk-JUXSWN54.mjs} +5 -3
  39. package/dist/{chunk-BNP626TY.mjs → chunk-OHBNABL5.mjs} +10 -3
  40. package/dist/chunk-URI2WBIV.mjs +147 -0
  41. package/dist/chunk-Y4GL2MHX.mjs +112 -0
  42. package/dist/{chunk-756RAKE4.mjs → chunk-ZUR7AU5R.mjs} +38 -20
  43. package/dist/fonts.d.mts +32 -0
  44. package/dist/fonts.d.ts +32 -0
  45. package/dist/fonts.js +44 -0
  46. package/dist/fonts.mjs +37 -0
  47. package/dist/index.d.mts +26 -1
  48. package/dist/index.d.ts +26 -1
  49. package/dist/index.js +425 -106
  50. package/dist/index.mjs +55 -17
  51. package/package.json +23 -6
  52. package/src/components/AlertBanner/AlertBanner.tsx +21 -3
  53. package/src/components/ConfirmDialog/ConfirmDialog.tsx +5 -0
  54. package/src/components/CurrencyInput/CurrencyInput.tsx +4 -0
  55. package/src/components/ImageUpload/ImageUpload.tsx +158 -0
  56. package/src/components/ImageUpload/index.ts +1 -0
  57. package/src/components/Input/Input.tsx +64 -53
  58. package/src/components/ListItem/ListItem.tsx +23 -4
  59. package/src/components/SheetSelect/SheetSelect.tsx +192 -0
  60. package/src/components/SheetSelect/index.ts +1 -0
  61. package/src/fonts.ts +30 -29
  62. package/src/hooks/useConfirmDialog.ts +67 -0
  63. package/src/index.ts +6 -0
  64. package/dist/chunk-26BCI223.mjs +0 -14
package/COMPONENTS.md CHANGED
@@ -1,10 +1,12 @@
1
- # @retray-dev/ui-kit — Component Reference (v9.1.0)
1
+ # @retray-dev/ui-kit — Component Reference (v9.3.0)
2
2
 
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:
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:
4
4
 
5
5
  ```markdown
6
6
  ## UI Components
7
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
8
10
  ```
9
11
 
10
12
  ---
@@ -67,7 +69,7 @@ export default function App() {
67
69
  Install all required peers (Expo projects: swap `pnpm add` for `npx expo install` to get SDK-pinned versions):
68
70
 
69
71
  ```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
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
71
73
  ```
72
74
 
73
75
  Then add the Worklets Babel plugin to `babel.config.js` (required by `@gorhom/bottom-sheet` and the pressable animations):
@@ -96,6 +98,7 @@ module.exports = function (api) {
96
98
  | `@react-native-community/slider` | **Required** | `Slider` | |
97
99
  | `@expo/vector-icons` | **Required** | Icons everywhere | |
98
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` |
99
102
  | `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
103
  | `@shopify/react-native-skia` + `expo-sensors` | **Optional** | Deep-import `HolographicCard` only | `pnpm add @shopify/react-native-skia expo-sensors` |
101
104
 
@@ -896,6 +899,7 @@ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
896
899
  | type | `'text' \| 'password'` | `'text'` | Password shows eye/eye-off toggle button in suffix slot |
897
900
  | containerStyle | `ViewStyle` | — | Outer container View style |
898
901
  | inputWrapperStyle | `ViewStyle` | — | The bordered wrapper around the input row |
902
+ | sheetMode | `boolean` | `false` | Use inside a Sheet — swaps `TextInput` for `BottomSheetTextInput` to fix keyboard handling |
899
903
  | style | `TextStyle` | — | TextInput element style |
900
904
 
901
905
  **Dimensions:** 56px height (14px vertical padding), 8px border radius.
@@ -1010,6 +1014,7 @@ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
1010
1014
  | placeholder | `string` | `'$0'` | Defaults to `prefix + '0'` |
1011
1015
  | editable | `boolean` | — | Pass `false` to disable |
1012
1016
  | containerStyle | `ViewStyle` | — | Outer container style |
1017
+ | sheetMode | `boolean` | `false` | Use inside a Sheet — forwards `sheetMode` to underlying `Input` |
1013
1018
 
1014
1019
  **Formatting behavior:** Strips all non-digit characters from input, then re-applies thousands separator every 3 digits from the right, then prepends `prefix`. Decimal point input is not supported — this is for whole-number monetary amounts.
1015
1020
 
@@ -1675,6 +1680,7 @@ const renderItem = useCallback(({ item }) => (
1675
1680
  | icon | `ReactNode` | — | Override default icon with custom ReactNode |
1676
1681
  | iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon`, but still falls back to variant default when neither is set |
1677
1682
  | iconColor | `string` | — | Override icon color. Defaults to variant title color |
1683
+ | onDismiss | `() => void` | — | When provided, shows an × button on the right. Call to hide the banner from state. |
1678
1684
  | style | `ViewStyle` | — | — |
1679
1685
 
1680
1686
  **Variants:**
@@ -2579,6 +2585,7 @@ dismiss(id)
2579
2585
  | confirmLabel | `string` | `'Confirm'` | Confirm button text |
2580
2586
  | cancelLabel | `string` | `'Cancel'` | Cancel button text |
2581
2587
  | confirmVariant | `'primary' \| 'destructive'` | `'primary'` | Use `'destructive'` for delete/remove actions |
2588
+ | loading | `boolean` | `false` | Show spinner in confirm button and disable it (for async `onConfirm`) |
2582
2589
  | onConfirm | `() => void` | required | Called when confirm is tapped |
2583
2590
  | onCancel | `() => void` | required | Called when cancel is tapped or backdrop pressed |
2584
2591
 
@@ -2640,6 +2647,7 @@ dismiss(id)
2640
2647
  | title | `string` | required | Primary text |
2641
2648
  | subtitle | `string` | — | Secondary line below title |
2642
2649
  | caption | `string` | — | Tertiary line below subtitle |
2650
+ | imageSource | `ImageSourcePropType` | — | Image for left slot (40×40, radius 8). Takes precedence over `leftRender`/`leftIcon` |
2643
2651
  | leftRender | `ReactNode` | — | Content in the fixed 44×44pt left slot |
2644
2652
  | rightRender | `string \| ReactNode` | — | Content on the right edge. Strings auto-styled as muted text |
2645
2653
  | leftIcon | `string` | — | Icon name for left slot. Takes precedence over `leftRender` |
@@ -2654,6 +2662,7 @@ dismiss(id)
2654
2662
  | style | `ViewStyle` | — | Outer container style |
2655
2663
  | titleStyle | `TextStyle` | — | Override title text style |
2656
2664
  | subtitleStyle | `TextStyle` | — | Override subtitle text style |
2665
+ | subtitleNumberOfLines | `number` | `2` | Max lines for subtitle before truncation |
2657
2666
  | captionStyle | `TextStyle` | — | Override caption text style |
2658
2667
  | icon | `ReactNode` | — | **Deprecated** — use `leftRender` |
2659
2668
  | trailing | `string \| ReactNode` | — | **Deprecated** — use `rightRender` |
@@ -3738,6 +3747,159 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
3738
3747
 
3739
3748
  ---
3740
3749
 
3750
+ ### SheetSelect
3751
+
3752
+ **Import:** `import { SheetSelect } from '@retray-dev/ui-kit'`
3753
+
3754
+ **When to use:** Selecting one or multiple options from a list *inside a Sheet or BottomSheet*. Replaces `Select` (which uses `@react-native-picker/picker` — broken inside BottomSheet) with a chip-based UI that works correctly in any surface.
3755
+
3756
+ | Prop | Type | Default | Notes |
3757
+ |------|------|---------|-------|
3758
+ | options | `SheetSelectOption[]` | required | `{ label, value, iconName?, disabled? }` |
3759
+ | value | `string \| number \| (string \| number)[]` | — | Controlled selected value(s) |
3760
+ | onValueChange | `(value) => void` | — | Called with new value on selection |
3761
+ | multiSelect | `boolean` | `false` | Allow multiple simultaneous selections |
3762
+ | label | `string` | — | Label above the chips |
3763
+ | error | `string` | — | Error text below |
3764
+ | wrap | `boolean` | `false` | Wrap chips to multiple rows. Default: single horizontal scroll |
3765
+ | style | `ViewStyle` | — | — |
3766
+ | accessibilityLabel | `string` | — | — |
3767
+
3768
+ **Examples:**
3769
+ ```tsx
3770
+ // Single-select category picker (inside Sheet)
3771
+ <SheetSelect
3772
+ label="Category"
3773
+ options={[
3774
+ { label: 'Food', value: 'food', iconName: 'coffee' },
3775
+ { label: 'Transport', value: 'transport', iconName: 'truck' },
3776
+ { label: 'Entertainment', value: 'entertainment' },
3777
+ ]}
3778
+ value={category}
3779
+ onValueChange={(v) => setCategory(v as string)}
3780
+ />
3781
+
3782
+ // Multi-select roles (wrap layout)
3783
+ <SheetSelect
3784
+ label="Roles"
3785
+ options={roles.map((r) => ({ label: r.name, value: r.id }))}
3786
+ value={selectedRoles}
3787
+ onValueChange={(v) => setSelectedRoles(v as string[])}
3788
+ multiSelect
3789
+ wrap
3790
+ />
3791
+ ```
3792
+
3793
+ ---
3794
+
3795
+ ### ImageUpload
3796
+
3797
+ **Import:** `import { ImageUpload } from '@retray-dev/ui-kit'`
3798
+
3799
+ **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.
3800
+
3801
+ **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.
3802
+
3803
+ | Prop | Type | Default | Notes |
3804
+ |------|------|---------|-------|
3805
+ | value | `string \| null` | — | Current image URI. `null`/`undefined` shows placeholder |
3806
+ | onChange | `(uri: string \| null) => void` | — | Called with selected URI after picker completes |
3807
+ | loading | `boolean` | `false` | Show spinner overlay (e.g. while uploading to server) |
3808
+ | placeholder | `string` | `'Tap to add image'` | Text shown when no image selected |
3809
+ | width | `number` | — | Width of the upload area. Defaults to full width |
3810
+ | height | `number` | `200` | Height of the upload area |
3811
+ | borderRadius | `number` | `RADIUS.lg` | — |
3812
+ | resizeMode | `'cover' \| 'contain' \| 'stretch'` | `'cover'` | Image resize mode |
3813
+ | disabled | `boolean` | `false` | Prevents pressing |
3814
+ | style | `ViewStyle` | — | — |
3815
+ | accessibilityLabel | `string` | — | — |
3816
+
3817
+ **Examples:**
3818
+ ```tsx
3819
+ const [imageUri, setImageUri] = useState<string | null>(null)
3820
+ const [uploading, setUploading] = useState(false)
3821
+
3822
+ const handleChange = async (uri: string | null) => {
3823
+ if (!uri) return
3824
+ setImageUri(uri)
3825
+ setUploading(true)
3826
+ await uploadToServer(uri)
3827
+ setUploading(false)
3828
+ }
3829
+
3830
+ <ImageUpload
3831
+ value={imageUri}
3832
+ onChange={handleChange}
3833
+ loading={uploading}
3834
+ placeholder="Tap to add product image"
3835
+ height={200}
3836
+ />
3837
+
3838
+ // Avatar upload (square)
3839
+ <ImageUpload
3840
+ value={avatarUri}
3841
+ onChange={setAvatarUri}
3842
+ width={100}
3843
+ height={100}
3844
+ borderRadius={50}
3845
+ />
3846
+ ```
3847
+
3848
+ ---
3849
+
3850
+ ### useConfirmDialog
3851
+
3852
+ **Import:** `import { useConfirmDialog } from '@retray-dev/ui-kit'`
3853
+
3854
+ **When to use:** Manage `ConfirmDialog` state without boilerplate. Replaces the `[target, setTarget]` + `visible` + `loading` state pattern that each screen would otherwise duplicate.
3855
+
3856
+ ```ts
3857
+ function useConfirmDialog<T>(options: UseConfirmDialogOptions): UseConfirmDialogResult<T>
3858
+
3859
+ interface UseConfirmDialogOptions {
3860
+ onConfirm: () => void | Promise<void>
3861
+ onCancel?: () => void
3862
+ }
3863
+ ```
3864
+
3865
+ **Returns:**
3866
+
3867
+ | Field | Type | Notes |
3868
+ |-------|------|-------|
3869
+ | visible | `boolean` | Pass to `ConfirmDialog.visible` |
3870
+ | target | `T \| null` | The value passed to `open()` — e.g. the item being deleted |
3871
+ | loading | `boolean` | Pass to `ConfirmDialog.loading` |
3872
+ | open | `(target?: T) => void` | Call to show the dialog |
3873
+ | dialogProps | `object` | Spread directly onto `<ConfirmDialog>` (contains `visible`, `loading`, `onConfirm`, `onCancel`) |
3874
+
3875
+ **Example:**
3876
+ ```tsx
3877
+ const { open, target, dialogProps } = useConfirmDialog<Product>({
3878
+ onConfirm: async () => {
3879
+ await deleteProduct(target!.id)
3880
+ toast.success('Product deleted')
3881
+ },
3882
+ })
3883
+
3884
+ // Somewhere in your list:
3885
+ <ListItem
3886
+ title={product.name}
3887
+ rightIcon="trash-2"
3888
+ onPress={() => open(product)}
3889
+ />
3890
+
3891
+ // Once in your screen's JSX:
3892
+ <ConfirmDialog
3893
+ title={`Delete "${target?.name}"?`}
3894
+ description="This action cannot be undone."
3895
+ confirmLabel="Delete"
3896
+ confirmVariant="destructive"
3897
+ {...dialogProps}
3898
+ />
3899
+ ```
3900
+
3901
+ ---
3902
+
3741
3903
  ## Icon System
3742
3904
 
3743
3905
  The library ships a built-in icon resolver — pass any icon name string to components without manual imports.
@@ -3939,4 +4101,4 @@ Each example includes:
3939
4101
  - Common patterns (spacing, colors, navigation, toast feedback)
3940
4102
  - StyleSheet definitions
3941
4103
 
3942
- **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. Access via "🖥 App Screen Compositions" accordion.
4104
+ **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.
package/CONSUMER.md ADDED
@@ -0,0 +1,247 @@
1
+ # @retray-dev/ui-kit — Consumer Setup Guide (v9.2.0)
2
+
3
+ > **For AI assistants (Claude Code):** Add all three lines below to your project's `CLAUDE.md` so Claude has full context about this UI kit:
4
+ > ```markdown
5
+ > ## UI Components
6
+ > @./node_modules/@retray-dev/ui-kit/COMPONENTS.md
7
+ > @./node_modules/@retray-dev/ui-kit/CONSUMER.md
8
+ > @./node_modules/@retray-dev/ui-kit/EXAMPLES.md
9
+ > ```
10
+
11
+ ---
12
+
13
+ ## Prerequisites
14
+
15
+ | Requirement | Minimum | Notes |
16
+ |---|---|---|
17
+ | Node | 18.18 | |
18
+ | Expo SDK | 54 | SDK 53 not supported |
19
+ | React Native | 0.79 | Tested up to 0.81.5 |
20
+ | React | 19 | |
21
+ | Expo Go | NOT supported | Missing native TurboModules for Sheet, ConfirmDialog, haptics |
22
+ | Dev build | Required | Use `npx expo run:ios` / `npx expo run:android` or EAS Build |
23
+
24
+ ---
25
+
26
+ ## Install: Required Peer Dependencies
27
+
28
+ **All of these are required.** Missing any one will crash the app at load time.
29
+
30
+ ```bash
31
+ npx expo install \
32
+ expo-haptics \
33
+ expo-linear-gradient \
34
+ expo-font \
35
+ react-native-reanimated \
36
+ react-native-gesture-handler \
37
+ react-native-worklets \
38
+ react-native-safe-area-context \
39
+ react-native-screens \
40
+ react-native-svg \
41
+ @gorhom/bottom-sheet \
42
+ @react-native-picker/picker \
43
+ @react-native-community/slider \
44
+ @expo/vector-icons \
45
+ react-native-size-matters \
46
+ sonner-native \
47
+ react-native-ease \
48
+ pressto
49
+ ```
50
+
51
+ > `react-native-ease` and `pressto` are hard required. Omitting either crashes every component at module load — you will see a blank screen with no error message on screen.
52
+
53
+ ---
54
+
55
+ ## Install: Optional Peer Dependencies
56
+
57
+ ```bash
58
+ npx expo install expo-image-picker # Required only for ImageUpload component
59
+ npx expo install react-native-pulsar # Richer haptic feedback (graceful fallback to expo-haptics when absent)
60
+
61
+ # HolographicCard only — deep-import, NOT in main barrel:
62
+ npx expo install @shopify/react-native-skia expo-sensors
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Bundler Configuration
68
+
69
+ ### `babel.config.js` — CRITICAL
70
+
71
+ ```js
72
+ module.exports = function (api) {
73
+ api.cache(true)
74
+ return {
75
+ presets: ['babel-preset-expo'],
76
+ plugins: ['react-native-worklets/plugin'], // ← NOT 'react-native-reanimated/plugin'
77
+ }
78
+ }
79
+ ```
80
+
81
+ > Using `react-native-reanimated/plugin` instead of `react-native-worklets/plugin` is the **#1 cause of broken animations and missing input borders**. The worklets plugin covers both.
82
+
83
+ After changing `babel.config.js` always clear cache:
84
+ ```bash
85
+ npx expo start --clear
86
+ ```
87
+
88
+ ### `metro.config.js` — Monorepo / Workspace only
89
+
90
+ If your app lives inside a monorepo (pnpm/yarn workspaces), you must prevent duplicate React/React Native instances:
91
+
92
+ ```js
93
+ const { getDefaultConfig } = require('expo/metro-config')
94
+ const path = require('path')
95
+
96
+ const projectRoot = __dirname
97
+ const workspaceRoot = path.resolve(projectRoot, '../..') // adjust to your repo root
98
+
99
+ const config = getDefaultConfig(projectRoot)
100
+
101
+ config.watchFolders = [workspaceRoot]
102
+ config.resolver.nodeModulesPaths = [
103
+ path.resolve(projectRoot, 'node_modules'),
104
+ path.resolve(workspaceRoot, 'node_modules'),
105
+ ]
106
+ config.resolver.extraNodeModules = {
107
+ react: path.resolve(projectRoot, 'node_modules/react'),
108
+ 'react-native': path.resolve(projectRoot, 'node_modules/react-native'),
109
+ }
110
+
111
+ module.exports = config
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Provider Setup
117
+
118
+ ### Recommended — `RetrayProvider` (one wrapper, correct order)
119
+
120
+ ```tsx
121
+ import { useFonts } from 'expo-font'
122
+ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
123
+ import { RetrayProvider } from '@retray-dev/ui-kit'
124
+
125
+ export default function App() {
126
+ const [fontsLoaded] = useFonts(SohneFonts)
127
+ if (!fontsLoaded) return null
128
+ return (
129
+ <RetrayProvider colorScheme="system">
130
+ {/* your app */}
131
+ </RetrayProvider>
132
+ )
133
+ }
134
+ ```
135
+
136
+ ### Manual — only if custom provider tree required
137
+
138
+ ```tsx
139
+ import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'
140
+ import { GestureHandlerRootView } from 'react-native-gesture-handler'
141
+ import { ThemeProvider, BottomSheetModalProvider, ToastProvider } from '@retray-dev/ui-kit'
142
+
143
+ export default function App() {
144
+ return (
145
+ <SafeAreaProvider initialMetrics={initialWindowMetrics}>
146
+ <GestureHandlerRootView style={{ flex: 1 }}>
147
+ <ThemeProvider>
148
+ <BottomSheetModalProvider>
149
+ <ToastProvider>
150
+ {/* your app */}
151
+ </ToastProvider>
152
+ </BottomSheetModalProvider>
153
+ </ThemeProvider>
154
+ </GestureHandlerRootView>
155
+ </SafeAreaProvider>
156
+ )
157
+ }
158
+ ```
159
+
160
+ Provider order is strict: `SafeAreaProvider` outermost, `GestureHandlerRootView` second.
161
+
162
+ ---
163
+
164
+ ## Version Compatibility Table
165
+
166
+ | Package | Min | Max tested | Notes |
167
+ |---|---|---|---|
168
+ | Expo SDK | 54 | 54 | SDK 53 not supported |
169
+ | React Native | 0.79 | 0.81.5 | |
170
+ | react-native-reanimated | 4.0.0 | 4.1.1 | **v3 NOT supported** — animations will not work |
171
+ | react-native-worklets | 0.5.1 | 0.5.1 | Exact version — other 0.5.x may crash |
172
+ | @gorhom/bottom-sheet | 5.2.0 | 5.2.8 | <5.2.0 crashes with Reanimated v4 |
173
+ | sonner-native | 0.22.0 | 0.23.1 | <0.22.0 has incompatible toast API |
174
+ | react-native-ease | 0.7.0 | 0.7.2 | **Required** — not optional |
175
+ | pressto | 0.6.0 | 0.6.1 | **Required** — not optional |
176
+ | expo-haptics | 15.0.0 | 15.0.8 | |
177
+ | expo-linear-gradient | 15.0.0 | 15.0.8 | |
178
+ | expo-font | 14.0.0 | 14.0.11 | |
179
+
180
+ ---
181
+
182
+ ## Troubleshooting
183
+
184
+ ### 1. Blank screen / "Cannot find module 'react-native-ease'" or `'pressto'`
185
+
186
+ These are required peers — not optional. The barrel import crashes at module load, rendering nothing.
187
+
188
+ **Fix:**
189
+ ```bash
190
+ npx expo install react-native-ease pressto
191
+ npx expo run:ios # or run:android — full native rebuild required
192
+ ```
193
+
194
+ ### 2. Input has no visible border / all animations are frozen
195
+
196
+ Wrong or missing Babel plugin. Reanimated worklets never initialize.
197
+
198
+ **Fix:**
199
+ 1. Open `babel.config.js` — confirm it has `plugins: ['react-native-worklets/plugin']`
200
+ 2. If it has `react-native-reanimated/plugin` instead, replace it with `react-native-worklets/plugin`
201
+ 3. Clear cache and restart:
202
+ ```bash
203
+ npx expo start --clear
204
+ ```
205
+ 4. If in monorepo: verify `metro.config.js` `extraNodeModules` points `react-native` to a single copy (duplicate instances break Reanimated context)
206
+
207
+ ### 3. "useWorkletCallback is not a function" crash on Sheet or ConfirmDialog
208
+
209
+ `@gorhom/bottom-sheet` version older than 5.2.0 installed.
210
+
211
+ **Fix:**
212
+ ```bash
213
+ npx expo install @gorhom/bottom-sheet@5.2.8
214
+ npx expo run:ios # or run:android
215
+ ```
216
+
217
+ ---
218
+
219
+ ## HolographicCard (Optional Deep Import)
220
+
221
+ `HolographicCard` depends on `@shopify/react-native-skia` and `expo-sensors` which are heavy optional peers. It is **not included in the main barrel** to avoid pulling Skia for consumers who don't need it.
222
+
223
+ ```tsx
224
+ // Deep import — do NOT import from '@retray-dev/ui-kit'
225
+ import { HolographicCard } from '@retray-dev/ui-kit/HolographicCard'
226
+ ```
227
+
228
+ Install before using:
229
+ ```bash
230
+ npx expo install @shopify/react-native-skia expo-sensors
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Fonts
236
+
237
+ All components use the **Sohne** font family. Load fonts before rendering any component:
238
+
239
+ ```tsx
240
+ import { useFonts } from 'expo-font'
241
+ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
242
+
243
+ const [fontsLoaded] = useFonts(SohneFonts)
244
+ if (!fontsLoaded) return null
245
+ ```
246
+
247
+ See `FONTS.md` for the full font inventory and weight reference.