@retray-dev/ui-kit 9.0.0 → 9.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 (57) hide show
  1. package/COMPONENTS.md +178 -7
  2. package/CONSUMER.md +247 -0
  3. package/DESIGN.md +668 -0
  4. package/EXAMPLES.md +19 -12
  5. package/FONTS.md +107 -0
  6. package/README.md +3 -3
  7. package/dist/AlertBanner.d.mts +3 -1
  8. package/dist/AlertBanner.d.ts +3 -1
  9. package/dist/AlertBanner.js +18 -2
  10. package/dist/AlertBanner.mjs +1 -1
  11. package/dist/ConfirmDialog.d.mts +3 -1
  12. package/dist/ConfirmDialog.d.ts +3 -1
  13. package/dist/ConfirmDialog.js +3 -0
  14. package/dist/ConfirmDialog.mjs +1 -1
  15. package/dist/CurrencyInput.d.mts +3 -1
  16. package/dist/CurrencyInput.d.ts +3 -1
  17. package/dist/CurrencyInput.js +31 -4
  18. package/dist/CurrencyInput.mjs +2 -2
  19. package/dist/ImageUpload.d.mts +27 -0
  20. package/dist/ImageUpload.d.ts +27 -0
  21. package/dist/ImageUpload.js +399 -0
  22. package/dist/ImageUpload.mjs +9 -0
  23. package/dist/Input.d.mts +3 -1
  24. package/dist/Input.d.ts +3 -1
  25. package/dist/Input.js +27 -2
  26. package/dist/Input.mjs +1 -1
  27. package/dist/ListItem.d.mts +3 -1
  28. package/dist/ListItem.d.ts +3 -1
  29. package/dist/ListItem.js +2 -1
  30. package/dist/ListItem.mjs +1 -1
  31. package/dist/SheetSelect.d.mts +25 -0
  32. package/dist/SheetSelect.d.ts +25 -0
  33. package/dist/SheetSelect.js +440 -0
  34. package/dist/SheetSelect.mjs +9 -0
  35. package/dist/{chunk-M6ZXVBTK.mjs → chunk-6MKGPAR2.mjs} +21 -5
  36. package/dist/{chunk-7QHVVCB3.mjs → chunk-FZZLPJ6B.mjs} +3 -0
  37. package/dist/{chunk-MAC465BB.mjs → chunk-KNSENOV4.mjs} +5 -3
  38. package/dist/{chunk-756RAKE4.mjs → chunk-LVYEU5ZK.mjs} +27 -2
  39. package/dist/{chunk-BNP626TY.mjs → chunk-T4I5WVHA.mjs} +2 -1
  40. package/dist/chunk-URI2WBIV.mjs +147 -0
  41. package/dist/chunk-Y4GL2MHX.mjs +112 -0
  42. package/dist/index.d.mts +26 -1
  43. package/dist/index.d.ts +26 -1
  44. package/dist/index.js +327 -8
  45. package/dist/index.mjs +51 -12
  46. package/package.json +18 -5
  47. package/src/components/AlertBanner/AlertBanner.tsx +21 -3
  48. package/src/components/ConfirmDialog/ConfirmDialog.tsx +5 -0
  49. package/src/components/CurrencyInput/CurrencyInput.tsx +4 -0
  50. package/src/components/ImageUpload/ImageUpload.tsx +158 -0
  51. package/src/components/ImageUpload/index.ts +1 -0
  52. package/src/components/Input/Input.tsx +51 -23
  53. package/src/components/ListItem/ListItem.tsx +4 -1
  54. package/src/components/SheetSelect/SheetSelect.tsx +192 -0
  55. package/src/components/SheetSelect/index.ts +1 -0
  56. package/src/hooks/useConfirmDialog.ts +67 -0
  57. package/src/index.ts +6 -0
package/COMPONENTS.md CHANGED
@@ -1,10 +1,12 @@
1
- # @retray-dev/ui-kit — Component Reference (v8.0.0)
1
+ # @retray-dev/ui-kit — Component Reference (v9.2.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
 
@@ -663,6 +666,8 @@ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
663
666
  | iconColor | `string` | — | Override icon color. Defaults to variant label color |
664
667
  | iconPosition | `'left' \| 'right'` | `'left'` | Side the icon appears on |
665
668
  | style | `ViewStyle` | — | Override container style |
669
+ | accessibilityLabel | `string` | — | Screen reader label. Defaults to `label` |
670
+ | accessibilityHint | `string` | — | Screen reader hint |
666
671
 
667
672
  **Variants:**
668
673
 
@@ -804,6 +809,8 @@ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
804
809
  | badge | `boolean \| number` | — | Notification badge overlay |
805
810
  | disabled | `boolean` | — | Reduces opacity to 0.5 |
806
811
  | style | `ViewStyle` | — | — |
812
+ | accessibilityLabel | `string` | — | Screen reader label |
813
+ | accessibilityHint | `string` | — | Screen reader hint |
807
814
 
808
815
  **Variants:** Same token logic as Button.
809
816
 
@@ -819,7 +826,7 @@ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
819
826
 
820
827
  | Size | Dimensions | Icon size |
821
828
  |------|-----------|-----------|
822
- | `sm` | 32 × 32px | 16px |
829
+ | `sm` | 44 × 44px | 18px |
823
830
  | `md` | 44 × 44px | 20px |
824
831
  | `lg` | 52 × 52px | 24px |
825
832
 
@@ -880,6 +887,7 @@ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
880
887
  | label | `string` | — | Label above the input field |
881
888
  | error | `string` | — | Error text below — turns border `destructive` (2px) |
882
889
  | hint | `string` | — | Helper text below (hidden when `error` is set) |
890
+ | disabled | `boolean` | — | Reduces opacity to 0.5, disables interaction |
883
891
  | prefix | `string \| ReactNode` | — | Content before the input text (e.g. `"$"`, icon node) |
884
892
  | suffix | `string \| ReactNode` | — | Content after the input text (e.g. `"kg"`, icon node) |
885
893
  | prefixStyle | `TextStyle` | — | Style for prefix string (no effect on ReactNode) |
@@ -891,6 +899,7 @@ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
891
899
  | type | `'text' \| 'password'` | `'text'` | Password shows eye/eye-off toggle button in suffix slot |
892
900
  | containerStyle | `ViewStyle` | — | Outer container View style |
893
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 |
894
903
  | style | `TextStyle` | — | TextInput element style |
895
904
 
896
905
  **Dimensions:** 56px height (14px vertical padding), 8px border radius.
@@ -1005,6 +1014,7 @@ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
1005
1014
  | placeholder | `string` | `'$0'` | Defaults to `prefix + '0'` |
1006
1015
  | editable | `boolean` | — | Pass `false` to disable |
1007
1016
  | containerStyle | `ViewStyle` | — | Outer container style |
1017
+ | sheetMode | `boolean` | `false` | Use inside a Sheet — forwards `sheetMode` to underlying `Input` |
1008
1018
 
1009
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.
1010
1020
 
@@ -1670,6 +1680,7 @@ const renderItem = useCallback(({ item }) => (
1670
1680
  | icon | `ReactNode` | — | Override default icon with custom ReactNode |
1671
1681
  | iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon`, but still falls back to variant default when neither is set |
1672
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. |
1673
1684
  | style | `ViewStyle` | — | — |
1674
1685
 
1675
1686
  **Variants:**
@@ -1710,6 +1721,8 @@ const renderItem = useCallback(({ item }) => (
1710
1721
  | iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon` |
1711
1722
  | iconColor | `string` | — | Override icon color. Defaults to `foregroundMuted` |
1712
1723
  | action | `ReactNode` | — | CTA below text — typically a `Button`. Hidden in `compact` size |
1724
+ | actionLabel | `string` | — | Shorthand for a primary `Button` CTA — renders a default `Button` with this label. Requires `onAction`. Hidden in `compact` size |
1725
+ | onAction | `() => void` | — | Handler for the shorthand `actionLabel` button. Required when `actionLabel` is set |
1713
1726
  | size | `'default' \| 'compact'` | `'default'` | `compact` is smaller, hides description and action |
1714
1727
  | style | `ViewStyle` | — | — |
1715
1728
 
@@ -2211,7 +2224,8 @@ Add `react-native-worklets/plugin` (not `react-native-reanimated/plugin`) to `ba
2211
2224
  | open | `boolean` | required | `true` presents the sheet, `false` dismisses it |
2212
2225
  | onClose | `() => void` | required | Called on swipe-dismiss or backdrop press |
2213
2226
  | title | `string` | — | Sheet heading |
2214
- | subtitle | `string` | — | Supporting text below title (replaces deprecated `description`) |
2227
+ | subtitle | `string` | — | Supporting text below title |
2228
+ | description | `string` | — | **Deprecated alias** for `subtitle` — prefer `subtitle` |
2215
2229
  | showCloseButton | `boolean` | `false` | Show X close button in the header |
2216
2230
  | children | `ReactNode` | — | Sheet content |
2217
2231
  | style | `ViewStyle` | — | Inner scroll/content container style |
@@ -2571,6 +2585,7 @@ dismiss(id)
2571
2585
  | confirmLabel | `string` | `'Confirm'` | Confirm button text |
2572
2586
  | cancelLabel | `string` | `'Cancel'` | Cancel button text |
2573
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`) |
2574
2589
  | onConfirm | `() => void` | required | Called when confirm is tapped |
2575
2590
  | onCancel | `() => void` | required | Called when cancel is tapped or backdrop pressed |
2576
2591
 
@@ -2646,6 +2661,7 @@ dismiss(id)
2646
2661
  | style | `ViewStyle` | — | Outer container style |
2647
2662
  | titleStyle | `TextStyle` | — | Override title text style |
2648
2663
  | subtitleStyle | `TextStyle` | — | Override subtitle text style |
2664
+ | subtitleNumberOfLines | `number` | `2` | Max lines for subtitle before truncation |
2649
2665
  | captionStyle | `TextStyle` | — | Override caption text style |
2650
2666
  | icon | `ReactNode` | — | **Deprecated** — use `leftRender` |
2651
2667
  | trailing | `string \| ReactNode` | — | **Deprecated** — use `rightRender` |
@@ -3028,6 +3044,7 @@ const [categories, setCategories] = useState<number[]>([1, 3])
3028
3044
  | multiSelect | `boolean` | `false` | Allow multiple simultaneous selections |
3029
3045
  | style | `ViewStyle` | — | ScrollView content container style |
3030
3046
  | itemStyle | `ViewStyle` | — | Style applied to each chip's wrapper |
3047
+ | accessibilityLabel | `string` | — | Screen reader label for the scroll container |
3031
3048
 
3032
3049
  **CategoryItem type:**
3033
3050
  ```ts
@@ -3729,6 +3746,159 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
3729
3746
 
3730
3747
  ---
3731
3748
 
3749
+ ### SheetSelect
3750
+
3751
+ **Import:** `import { SheetSelect } from '@retray-dev/ui-kit'`
3752
+
3753
+ **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.
3754
+
3755
+ | Prop | Type | Default | Notes |
3756
+ |------|------|---------|-------|
3757
+ | options | `SheetSelectOption[]` | required | `{ label, value, iconName?, disabled? }` |
3758
+ | value | `string \| number \| (string \| number)[]` | — | Controlled selected value(s) |
3759
+ | onValueChange | `(value) => void` | — | Called with new value on selection |
3760
+ | multiSelect | `boolean` | `false` | Allow multiple simultaneous selections |
3761
+ | label | `string` | — | Label above the chips |
3762
+ | error | `string` | — | Error text below |
3763
+ | wrap | `boolean` | `false` | Wrap chips to multiple rows. Default: single horizontal scroll |
3764
+ | style | `ViewStyle` | — | — |
3765
+ | accessibilityLabel | `string` | — | — |
3766
+
3767
+ **Examples:**
3768
+ ```tsx
3769
+ // Single-select category picker (inside Sheet)
3770
+ <SheetSelect
3771
+ label="Category"
3772
+ options={[
3773
+ { label: 'Food', value: 'food', iconName: 'coffee' },
3774
+ { label: 'Transport', value: 'transport', iconName: 'truck' },
3775
+ { label: 'Entertainment', value: 'entertainment' },
3776
+ ]}
3777
+ value={category}
3778
+ onValueChange={(v) => setCategory(v as string)}
3779
+ />
3780
+
3781
+ // Multi-select roles (wrap layout)
3782
+ <SheetSelect
3783
+ label="Roles"
3784
+ options={roles.map((r) => ({ label: r.name, value: r.id }))}
3785
+ value={selectedRoles}
3786
+ onValueChange={(v) => setSelectedRoles(v as string[])}
3787
+ multiSelect
3788
+ wrap
3789
+ />
3790
+ ```
3791
+
3792
+ ---
3793
+
3794
+ ### ImageUpload
3795
+
3796
+ **Import:** `import { ImageUpload } from '@retray-dev/ui-kit'`
3797
+
3798
+ **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.
3799
+
3800
+ **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.
3801
+
3802
+ | Prop | Type | Default | Notes |
3803
+ |------|------|---------|-------|
3804
+ | value | `string \| null` | — | Current image URI. `null`/`undefined` shows placeholder |
3805
+ | onChange | `(uri: string \| null) => void` | — | Called with selected URI after picker completes |
3806
+ | loading | `boolean` | `false` | Show spinner overlay (e.g. while uploading to server) |
3807
+ | placeholder | `string` | `'Tap to add image'` | Text shown when no image selected |
3808
+ | width | `number` | — | Width of the upload area. Defaults to full width |
3809
+ | height | `number` | `200` | Height of the upload area |
3810
+ | borderRadius | `number` | `RADIUS.lg` | — |
3811
+ | resizeMode | `'cover' \| 'contain' \| 'stretch'` | `'cover'` | Image resize mode |
3812
+ | disabled | `boolean` | `false` | Prevents pressing |
3813
+ | style | `ViewStyle` | — | — |
3814
+ | accessibilityLabel | `string` | — | — |
3815
+
3816
+ **Examples:**
3817
+ ```tsx
3818
+ const [imageUri, setImageUri] = useState<string | null>(null)
3819
+ const [uploading, setUploading] = useState(false)
3820
+
3821
+ const handleChange = async (uri: string | null) => {
3822
+ if (!uri) return
3823
+ setImageUri(uri)
3824
+ setUploading(true)
3825
+ await uploadToServer(uri)
3826
+ setUploading(false)
3827
+ }
3828
+
3829
+ <ImageUpload
3830
+ value={imageUri}
3831
+ onChange={handleChange}
3832
+ loading={uploading}
3833
+ placeholder="Tap to add product image"
3834
+ height={200}
3835
+ />
3836
+
3837
+ // Avatar upload (square)
3838
+ <ImageUpload
3839
+ value={avatarUri}
3840
+ onChange={setAvatarUri}
3841
+ width={100}
3842
+ height={100}
3843
+ borderRadius={50}
3844
+ />
3845
+ ```
3846
+
3847
+ ---
3848
+
3849
+ ### useConfirmDialog
3850
+
3851
+ **Import:** `import { useConfirmDialog } from '@retray-dev/ui-kit'`
3852
+
3853
+ **When to use:** Manage `ConfirmDialog` state without boilerplate. Replaces the `[target, setTarget]` + `visible` + `loading` state pattern that each screen would otherwise duplicate.
3854
+
3855
+ ```ts
3856
+ function useConfirmDialog<T>(options: UseConfirmDialogOptions): UseConfirmDialogResult<T>
3857
+
3858
+ interface UseConfirmDialogOptions {
3859
+ onConfirm: () => void | Promise<void>
3860
+ onCancel?: () => void
3861
+ }
3862
+ ```
3863
+
3864
+ **Returns:**
3865
+
3866
+ | Field | Type | Notes |
3867
+ |-------|------|-------|
3868
+ | visible | `boolean` | Pass to `ConfirmDialog.visible` |
3869
+ | target | `T \| null` | The value passed to `open()` — e.g. the item being deleted |
3870
+ | loading | `boolean` | Pass to `ConfirmDialog.loading` |
3871
+ | open | `(target?: T) => void` | Call to show the dialog |
3872
+ | dialogProps | `object` | Spread directly onto `<ConfirmDialog>` (contains `visible`, `loading`, `onConfirm`, `onCancel`) |
3873
+
3874
+ **Example:**
3875
+ ```tsx
3876
+ const { open, target, dialogProps } = useConfirmDialog<Product>({
3877
+ onConfirm: async () => {
3878
+ await deleteProduct(target!.id)
3879
+ toast.success('Product deleted')
3880
+ },
3881
+ })
3882
+
3883
+ // Somewhere in your list:
3884
+ <ListItem
3885
+ title={product.name}
3886
+ rightIcon="trash-2"
3887
+ onPress={() => open(product)}
3888
+ />
3889
+
3890
+ // Once in your screen's JSX:
3891
+ <ConfirmDialog
3892
+ title={`Delete "${target?.name}"?`}
3893
+ description="This action cannot be undone."
3894
+ confirmLabel="Delete"
3895
+ confirmVariant="destructive"
3896
+ {...dialogProps}
3897
+ />
3898
+ ```
3899
+
3900
+ ---
3901
+
3732
3902
  ## Icon System
3733
3903
 
3734
3904
  The library ships a built-in icon resolver — pass any icon name string to components without manual imports.
@@ -3909,11 +4079,12 @@ Used internally — not exported. `s()` horizontal scale, `vs()` vertical scale,
3909
4079
 
3910
4080
  ## Full Composition Examples
3911
4081
 
3912
- The package includes **EXAMPLES.md** with complete, working code for 3 real-world app screens:
4082
+ The package includes **EXAMPLES.md** with complete, working code for 4 real-world app screens:
3913
4083
 
3914
4084
  1. **Finance Dashboard** — MonthPicker, CurrencyDisplay, Progress, CategoryStrip, ListItem, DetailRow
3915
4085
  2. **Edit Profile** — Avatar, Badge, Input, Select, Switch, Card, AlertBanner
3916
4086
  3. **Onboarding Flow** — Badge, Progress, Input, RadioGroup, EmptyState (multi-step wizard)
4087
+ 4. **Settings Screen** — MenuGroup, ListGroup, Form, MenuItem, ListItem, compound component patterns
3917
4088
 
3918
4089
  **For AI agents:** Import EXAMPLES.md from the package for full source code and patterns:
3919
4090
 
@@ -3929,4 +4100,4 @@ Each example includes:
3929
4100
  - Common patterns (spacing, colors, navigation, toast feedback)
3930
4101
  - StyleSheet definitions
3931
4102
 
3932
- **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.
4103
+ **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.