@retray-dev/ui-kit 12.2.0 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONSUMER.md +26 -11
- package/DESIGN.md +2 -2
- package/README.md +15 -11
- package/{COMPONENTS.md → SKILL.md} +374 -996
- package/dist/Accordion.d.mts +2 -0
- package/dist/Accordion.d.ts +2 -0
- package/dist/Accordion.js +49 -210
- package/dist/Accordion.mjs +6 -6
- package/dist/AlertBanner.js +29 -153
- package/dist/AlertBanner.mjs +3 -4
- package/dist/AppHeader.d.mts +5 -2
- package/dist/AppHeader.d.ts +5 -2
- package/dist/AppHeader.js +45 -239
- package/dist/AppHeader.mjs +6 -8
- package/dist/Avatar.d.mts +17 -1
- package/dist/Avatar.d.ts +17 -1
- package/dist/Avatar.js +80 -115
- package/dist/Avatar.mjs +2 -3
- package/dist/Badge.js +24 -149
- package/dist/Badge.mjs +3 -4
- package/dist/Button.js +79 -267
- package/dist/Button.mjs +6 -7
- package/dist/ButtonGroup.mjs +0 -1
- package/dist/Card.js +15 -200
- package/dist/Card.mjs +4 -6
- package/dist/CategoryStrip.d.mts +0 -5
- package/dist/CategoryStrip.d.ts +0 -5
- package/dist/CategoryStrip.js +47 -265
- package/dist/CategoryStrip.mjs +6 -7
- package/dist/Checkbox.d.mts +2 -1
- package/dist/Checkbox.d.ts +2 -1
- package/dist/Checkbox.js +18 -201
- package/dist/Checkbox.mjs +5 -6
- package/dist/Chip.js +44 -236
- package/dist/Chip.mjs +7 -7
- package/dist/ConfirmDialog.d.mts +2 -1
- package/dist/ConfirmDialog.d.ts +2 -1
- package/dist/ConfirmDialog.js +110 -300
- package/dist/ConfirmDialog.mjs +7 -8
- package/dist/CurrencyDisplay.js +1 -114
- package/dist/CurrencyDisplay.mjs +2 -3
- package/dist/CurrencyInput.js +35 -162
- package/dist/CurrencyInput.mjs +5 -6
- package/dist/DetailRow.js +25 -150
- package/dist/DetailRow.mjs +3 -4
- package/dist/EmptyState.js +80 -268
- package/dist/EmptyState.mjs +7 -8
- package/dist/ErrorBoundary.js +32 -199
- package/dist/ErrorBoundary.mjs +4 -5
- package/dist/Form.js +1 -114
- package/dist/Form.mjs +2 -3
- package/dist/HolographicCard.d.mts +0 -28
- package/dist/HolographicCard.d.ts +0 -28
- package/dist/HolographicCard.js +20 -130
- package/dist/HolographicCard.mjs +9 -33
- package/dist/IconButton.js +36 -234
- package/dist/IconButton.mjs +5 -7
- package/dist/IconPicker.js +222 -929
- package/dist/IconPicker.mjs +5 -6
- package/dist/ImageUpload.d.mts +3 -3
- package/dist/ImageUpload.d.ts +3 -3
- package/dist/ImageUpload.js +49 -238
- package/dist/ImageUpload.mjs +5 -7
- package/dist/ImageViewer.js +75 -266
- package/dist/ImageViewer.mjs +8 -9
- package/dist/Input.d.mts +1 -1
- package/dist/Input.d.ts +1 -1
- package/dist/Input.js +35 -162
- package/dist/Input.mjs +4 -5
- package/dist/LabelValue.js +24 -149
- package/dist/LabelValue.mjs +3 -4
- package/dist/ListGroup.js +1 -114
- package/dist/ListGroup.mjs +2 -3
- package/dist/ListItem.d.mts +2 -1
- package/dist/ListItem.d.ts +2 -1
- package/dist/ListItem.js +41 -236
- package/dist/ListItem.mjs +5 -7
- package/dist/MediaCard.d.mts +0 -14
- package/dist/MediaCard.d.ts +0 -14
- package/dist/MediaCard.js +69 -315
- package/dist/MediaCard.mjs +5 -7
- package/dist/MenuGroup.js +1 -114
- package/dist/MenuGroup.mjs +2 -3
- package/dist/MenuItem.d.mts +2 -1
- package/dist/MenuItem.d.ts +2 -1
- package/dist/MenuItem.js +39 -235
- package/dist/MenuItem.mjs +5 -7
- package/dist/MonthPicker.js +8 -163
- package/dist/MonthPicker.mjs +3 -4
- package/dist/NumberStepper.d.mts +2 -1
- package/dist/NumberStepper.d.ts +2 -1
- package/dist/NumberStepper.js +44 -239
- package/dist/NumberStepper.mjs +5 -7
- package/dist/PagerDots.d.mts +1 -1
- package/dist/PagerDots.d.ts +1 -1
- package/dist/PagerDots.js +69 -224
- package/dist/PagerDots.mjs +6 -6
- package/dist/Pressable.js +14 -85
- package/dist/Pressable.mjs +4 -5
- package/dist/PricingCard.js +87 -272
- package/dist/PricingCard.mjs +8 -9
- package/dist/Progress.js +3 -123
- package/dist/Progress.mjs +3 -4
- package/dist/RadioGroup.js +52 -265
- package/dist/RadioGroup.mjs +5 -6
- package/dist/RetrayProvider.js +3 -6
- package/dist/RetrayProvider.mjs +3 -4
- package/dist/Select.d.mts +3 -1
- package/dist/Select.d.ts +3 -1
- package/dist/Select.js +27 -233
- package/dist/Select.mjs +4 -6
- package/dist/SelectableCard.js +33 -209
- package/dist/SelectableCard.mjs +5 -6
- package/dist/SelectableGrid.d.mts +0 -21
- package/dist/SelectableGrid.d.ts +0 -21
- package/dist/SelectableGrid.js +49 -272
- package/dist/SelectableGrid.mjs +5 -7
- package/dist/Separator.js +1 -114
- package/dist/Separator.mjs +2 -3
- package/dist/Sheet.d.mts +1 -1
- package/dist/Sheet.d.ts +1 -1
- package/dist/Sheet.js +33 -175
- package/dist/Sheet.mjs +3 -4
- package/dist/SheetSelect.js +39 -236
- package/dist/SheetSelect.mjs +6 -7
- package/dist/Skeleton.js +4 -124
- package/dist/Skeleton.mjs +3 -4
- package/dist/Slider.d.mts +2 -1
- package/dist/Slider.d.ts +2 -1
- package/dist/Slider.js +8 -161
- package/dist/Slider.mjs +3 -4
- package/dist/Spinner.js +3 -116
- package/dist/Spinner.mjs +2 -3
- package/dist/Stats.js +36 -234
- package/dist/Stats.mjs +5 -7
- package/dist/Switch.d.mts +2 -1
- package/dist/Switch.d.ts +2 -1
- package/dist/Switch.js +26 -176
- package/dist/Switch.mjs +5 -5
- package/dist/TabBar.js +43 -200
- package/dist/TabBar.mjs +5 -5
- package/dist/Tabs.js +15 -199
- package/dist/Tabs.mjs +5 -6
- package/dist/Text.js +9 -130
- package/dist/Text.mjs +2 -3
- package/dist/Textarea.d.mts +2 -1
- package/dist/Textarea.d.ts +2 -1
- package/dist/Textarea.js +71 -219
- package/dist/Textarea.mjs +4 -5
- package/dist/Toast.d.mts +12 -10
- package/dist/Toast.d.ts +12 -10
- package/dist/Toast.js +1 -114
- package/dist/Toast.mjs +2 -3
- package/dist/Toggle.js +39 -236
- package/dist/Toggle.mjs +6 -7
- package/dist/{chunk-ELGEOM7I.mjs → chunk-2QXJDRVU.mjs} +13 -10
- package/dist/{chunk-LIS6I5UP.mjs → chunk-2VIDP72N.mjs} +3 -3
- package/dist/{chunk-NHDI3VQB.mjs → chunk-422IVD3H.mjs} +16 -12
- package/dist/{chunk-DF7JA72E.mjs → chunk-4NQFTHN3.mjs} +13 -7
- package/dist/{chunk-3XCFYSX4.mjs → chunk-5MYNAAFE.mjs} +13 -17
- package/dist/{chunk-E7NEHHXV.mjs → chunk-62BBSSUF.mjs} +3 -3
- package/dist/{chunk-UBUXUMER.mjs → chunk-77UOVFIS.mjs} +7 -5
- package/dist/{chunk-M3C7XM2M.mjs → chunk-7BZJRB77.mjs} +28 -18
- package/dist/chunk-ARONDO7M.mjs +40 -0
- package/dist/{chunk-GH67YXG6.mjs → chunk-AZV7KNJI.mjs} +3 -3
- package/dist/{chunk-2P2CB235.mjs → chunk-BULKGOIZ.mjs} +7 -8
- package/dist/{chunk-RJNLAH76.mjs → chunk-C5ZRMR2E.mjs} +4 -2
- package/dist/chunk-CM2DG4MR.mjs +142 -0
- package/dist/{chunk-UQ4742ET.mjs → chunk-COA2YZOX.mjs} +8 -6
- package/dist/{chunk-EDLCGYIO.mjs → chunk-CZN6L2QU.mjs} +11 -8
- package/dist/{chunk-TS7DGUIR.mjs → chunk-DBHSUUKU.mjs} +2 -2
- package/dist/{chunk-57V2LXCK.mjs → chunk-DE25XTVQ.mjs} +3 -3
- package/dist/{chunk-RMRS44MQ.mjs → chunk-E2PONRJG.mjs} +13 -9
- package/dist/{chunk-GUTDFUNF.mjs → chunk-EHGBHFMH.mjs} +9 -17
- package/dist/{chunk-ZIMY2QUM.mjs → chunk-ERWJPVX7.mjs} +2 -2
- package/dist/{chunk-NLZY4TXU.mjs → chunk-ESQDPO5E.mjs} +7 -7
- package/dist/{chunk-VJBUCITV.mjs → chunk-EW2FIDSM.mjs} +1 -1
- package/dist/{chunk-HC4VVCWY.mjs → chunk-FTTI6T5Q.mjs} +4 -4
- package/dist/{chunk-MVMGPZN6.mjs → chunk-H6MQL7PS.mjs} +12 -7
- package/dist/{chunk-CF27NBXO.mjs → chunk-HHOOFDBA.mjs} +38 -41
- package/dist/{chunk-2HFD4IHU.mjs → chunk-HUSSF6TF.mjs} +1 -1
- package/dist/{chunk-HEDQPK4I.mjs → chunk-IDVUZIVY.mjs} +16 -22
- package/dist/chunk-IFYMBOEN.mjs +14 -0
- package/dist/{chunk-QOLWA2PW.mjs → chunk-IGU223UM.mjs} +80 -4
- package/dist/chunk-IJCMPVW5.mjs +121 -0
- package/dist/{chunk-AENAVIKT.mjs → chunk-ITG4JQM3.mjs} +4 -4
- package/dist/{chunk-E5UKLSJZ.mjs → chunk-K3QX2M26.mjs} +11 -8
- package/dist/{chunk-4OORJ2DY.mjs → chunk-K7TKID3V.mjs} +8 -7
- package/dist/{chunk-2LG326TT.mjs → chunk-KAGADD2O.mjs} +4 -4
- package/dist/{chunk-IVSRW4HS.mjs → chunk-KC5QDYGZ.mjs} +4 -4
- package/dist/{chunk-7AFZWSCI.mjs → chunk-KPTY7UYQ.mjs} +1 -1
- package/dist/{chunk-YTXRIXNZ.mjs → chunk-KSSVIFYR.mjs} +9 -12
- package/dist/chunk-L3YKPTJQ.mjs +119 -0
- package/dist/chunk-M53LC4Q7.mjs +35 -0
- package/dist/chunk-MZ6WRTD2.mjs +40 -0
- package/dist/chunk-NGEN2EES.mjs +581 -0
- package/dist/{chunk-ZR6HSEAB.mjs → chunk-NPCBNGNE.mjs} +17 -26
- package/dist/{chunk-C43HRKXH.mjs → chunk-OBV72JD4.mjs} +1 -1
- package/dist/{chunk-LPV4NJJK.mjs → chunk-PGQ6FMXS.mjs} +6 -5
- package/dist/{chunk-MEPSKGBO.mjs → chunk-PI6RULJX.mjs} +1 -1
- package/dist/{chunk-F3YTWO3T.mjs → chunk-RA6SAAFE.mjs} +9 -8
- package/dist/{chunk-UNNRUJTM.mjs → chunk-RRKM4MKB.mjs} +7 -7
- package/dist/{chunk-ULGNQPNE.mjs → chunk-S2VGME7X.mjs} +1 -1
- package/dist/{chunk-OLVJFKXS.mjs → chunk-S44XWTTC.mjs} +35 -25
- package/dist/{chunk-YMYIEVZP.mjs → chunk-SZEKQAOY.mjs} +1 -1
- package/dist/{chunk-BXF4AMHY.mjs → chunk-TMH263OK.mjs} +5 -4
- package/dist/{chunk-NJG7DHVF.mjs → chunk-U6DEBYU5.mjs} +10 -9
- package/dist/{chunk-QXDGGOLC.mjs → chunk-UMZTPUB3.mjs} +33 -21
- package/dist/{chunk-KSUWPU2F.mjs → chunk-WIPEDNSD.mjs} +7 -7
- package/dist/{chunk-QDAZGZUF.mjs → chunk-XCIG6HT2.mjs} +3 -3
- package/dist/{chunk-4J2PXL36.mjs → chunk-Y6YS33GM.mjs} +40 -38
- package/dist/{chunk-4XOB5TTD.mjs → chunk-ZKDKKQCE.mjs} +5 -5
- package/dist/{chunk-LOBLCFMN.mjs → chunk-ZTPYUU5C.mjs} +5 -5
- package/dist/fonts.mjs +0 -2
- package/dist/index.d.mts +13 -73
- package/dist/index.d.ts +13 -73
- package/dist/index.js +1149 -1892
- package/dist/index.mjs +81 -86
- package/package.json +20 -20
- package/src/components/Accordion/Accordion.tsx +15 -9
- package/src/components/AlertBanner/AlertBanner.tsx +7 -6
- package/src/components/AppHeader/AppHeader.tsx +25 -10
- package/src/components/Avatar/Avatar.tsx +92 -1
- package/src/components/Avatar/index.ts +2 -2
- package/src/components/Badge/Badge.tsx +2 -2
- package/src/components/Button/Button.tsx +50 -46
- package/src/components/Card/Card.tsx +1 -0
- package/src/components/CategoryStrip/CategoryStrip.tsx +36 -49
- package/src/components/Checkbox/Checkbox.tsx +3 -0
- package/src/components/Chip/Chip.tsx +5 -4
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +33 -17
- package/src/components/DetailRow/DetailRow.tsx +3 -3
- package/src/components/EmptyState/EmptyState.tsx +2 -2
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +6 -6
- package/src/components/HolographicCard/HolographicCard.tsx +14 -95
- package/src/components/IconButton/IconButton.tsx +2 -2
- package/src/components/IconPicker/IconPicker.tsx +13 -12
- package/src/components/ImageUpload/ImageUpload.tsx +43 -46
- package/src/components/ImageViewer/ImageViewer.tsx +3 -3
- package/src/components/Input/Input.tsx +11 -5
- package/src/components/LabelValue/LabelValue.tsx +2 -2
- package/src/components/ListItem/ListItem.tsx +7 -4
- package/src/components/MediaCard/MediaCard.tsx +21 -59
- package/src/components/MenuItem/MenuItem.tsx +5 -2
- package/src/components/MonthPicker/MonthPicker.tsx +2 -2
- package/src/components/NumberStepper/NumberStepper.tsx +10 -6
- package/src/components/PagerDots/PagerDots.tsx +38 -28
- package/src/components/PricingCard/PricingCard.tsx +6 -6
- package/src/components/RadioGroup/RadioGroup.tsx +18 -31
- package/src/components/Select/Select.tsx +35 -39
- package/src/components/SelectableCard/SelectableCard.tsx +4 -6
- package/src/components/SelectableGrid/SelectableGrid.tsx +37 -72
- package/src/components/Sheet/Sheet.tsx +28 -15
- package/src/components/Sheet/index.ts +2 -2
- package/src/components/SheetSelect/SheetSelect.tsx +3 -3
- package/src/components/Skeleton/Skeleton.tsx +1 -1
- package/src/components/Slider/Slider.tsx +3 -0
- package/src/components/Spinner/Spinner.tsx +2 -2
- package/src/components/Stats/Stats.tsx +2 -2
- package/src/components/Switch/Switch.tsx +12 -7
- package/src/components/TabBar/TabBar.tsx +9 -8
- package/src/components/Text/Text.tsx +13 -1
- package/src/components/Textarea/Textarea.tsx +18 -32
- package/src/components/Toggle/Toggle.tsx +3 -3
- package/src/hooks/useConfirmDialog.ts +31 -42
- package/src/index.ts +3 -4
- package/src/theme/ThemeProvider.tsx +1 -4
- package/src/theme/colorUtils.ts +1 -72
- package/src/theme/colors.ts +40 -1
- package/src/theme/types.ts +2 -2
- package/src/utils/animations.ts +0 -47
- package/src/utils/curatedIcons.ts +93 -801
- package/src/utils/haptics.ts +13 -208
- package/src/utils/icons.ts +27 -91
- package/src/utils/pressable.ts +10 -61
- package/dist/VirtualList.d.mts +0 -19
- package/dist/VirtualList.d.ts +0 -19
- package/dist/VirtualList.js +0 -38
- package/dist/VirtualList.mjs +0 -2
- package/dist/chunk-2BA3JMKK.mjs +0 -136
- package/dist/chunk-3DKJ2GIC.mjs +0 -30
- package/dist/chunk-7ELGZ66G.mjs +0 -164
- package/dist/chunk-DVK4G2GT.mjs +0 -59
- package/dist/chunk-EJ7ZPXOH.mjs +0 -163
- package/dist/chunk-KA7LTET3.mjs +0 -71
- package/dist/chunk-LNPKGWBG.mjs +0 -134
- package/dist/chunk-NC5ZTR2Y.mjs +0 -32
- package/dist/chunk-SAWUXP3A.mjs +0 -1114
- package/dist/chunk-Y6FXYEAI.mjs +0 -8
- package/dist/chunk-YNROWHQJ.mjs +0 -46
- package/src/components/VirtualList/VirtualList.tsx +0 -60
- package/src/components/VirtualList/index.ts +0 -1
- package/src/utils/fontGuard.ts +0 -35
- package/src/utils/hover.ts +0 -25
- package/src/utils/useColorTransition.ts +0 -40
- package/src/utils/usePressScale.ts +0 -75
|
@@ -1,836 +1,431 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
name: retray-ui-kit
|
|
3
|
+
description: >
|
|
4
|
+
Complete guide for @retray-dev/ui-kit — a React Native / Expo component
|
|
5
|
+
library (~55 components). Airbnb-inspired design, Sohne typography,
|
|
6
|
+
haptic-rich interactions, animated with pressto + react-native-ease.
|
|
7
|
+
Theme system, BottomSheet patterns, icon resolution, and conventions.
|
|
8
|
+
---
|
|
2
9
|
|
|
3
|
-
|
|
10
|
+
# @retray-dev/ui-kit — Agent Skill
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
```
|
|
12
|
+
> **Recommended — copy to repo root (no dependency on node_modules existing):**
|
|
13
|
+
> ```bash
|
|
14
|
+
> cp node_modules/@retray-dev/ui-kit/SKILL.md SKILL.md
|
|
15
|
+
> cp node_modules/@retray-dev/ui-kit/CONSUMER.md CONSUMER.md
|
|
16
|
+
> cp node_modules/@retray-dev/ui-kit/EXAMPLES.md EXAMPLES.md
|
|
17
|
+
> ```
|
|
18
|
+
>
|
|
19
|
+
> Then add to `CLAUDE.md`:
|
|
20
|
+
> ```markdown
|
|
21
|
+
> ## UI Kit
|
|
22
|
+
> @./SKILL.md
|
|
23
|
+
> @./CONSUMER.md
|
|
24
|
+
> @./EXAMPLES.md
|
|
25
|
+
> ```
|
|
11
26
|
|
|
12
27
|
---
|
|
13
28
|
|
|
14
|
-
## Setup
|
|
15
|
-
|
|
16
|
-
### Recommended: `RetrayProvider` (one wrapper)
|
|
29
|
+
## Required Setup
|
|
17
30
|
|
|
18
|
-
|
|
31
|
+
### Providers
|
|
32
|
+
Use `RetrayProvider` (single wrapper) or manual tree. Order is mandatory:
|
|
19
33
|
|
|
20
|
-
```tsx
|
|
21
|
-
import { RetrayProvider } from '@retray-dev/ui-kit'
|
|
22
|
-
|
|
23
|
-
export default function App() {
|
|
24
|
-
const [fontsLoaded] = useFonts(SohneFonts)
|
|
25
|
-
if (!fontsLoaded) return null
|
|
26
|
-
return (
|
|
27
|
-
<RetrayProvider colorScheme="system">
|
|
28
|
-
{/* your app */}
|
|
29
|
-
</RetrayProvider>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
34
|
```
|
|
33
|
-
|
|
34
|
-
### Manual (equivalent) — if you need a custom tree
|
|
35
|
-
|
|
36
|
-
`RetrayProvider` is exactly this; the individual providers stay exported. Order is mandatory:
|
|
37
|
-
|
|
38
|
-
```tsx
|
|
39
|
-
import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'
|
|
40
|
-
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
41
|
-
import { ThemeProvider, BottomSheetModalProvider, ToastProvider } from '@retray-dev/ui-kit'
|
|
42
|
-
|
|
43
|
-
export default function App() {
|
|
44
|
-
return (
|
|
45
|
-
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
|
46
|
-
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
47
|
-
<ThemeProvider colorScheme="system">
|
|
48
|
-
<BottomSheetModalProvider>
|
|
49
|
-
<ToastProvider>
|
|
50
|
-
{/* your app */}
|
|
51
|
-
</ToastProvider>
|
|
52
|
-
</BottomSheetModalProvider>
|
|
53
|
-
</ThemeProvider>
|
|
54
|
-
</GestureHandlerRootView>
|
|
55
|
-
</SafeAreaProvider>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
35
|
+
SafeAreaProvider > GestureHandlerRootView > ThemeProvider > BottomSheetModalProvider > ToastProvider
|
|
58
36
|
```
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
- `SafeAreaProvider` must be outermost — required by `@gorhom/bottom-sheet`
|
|
62
|
-
- `initialMetrics={initialWindowMetrics}` is required on Android to avoid a "No safe area value available" crash on first render
|
|
63
|
-
- `GestureHandlerRootView` must wrap everything — required by `@gorhom/bottom-sheet`
|
|
64
|
-
- `BottomSheetModalProvider` must be inside `GestureHandlerRootView`
|
|
65
|
-
- `ToastProvider` wraps children and renders `Toaster` (from `sonner-native`) internally
|
|
66
|
-
|
|
67
|
-
### Peer dependencies
|
|
38
|
+
`initialMetrics={initialWindowMetrics}` required on Android.
|
|
68
39
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
pnpm add expo-font expo-haptics expo-linear-gradient react-native-safe-area-context @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler react-native-worklets @react-native-picker/picker @react-native-community/slider @expo/vector-icons react-native-size-matters react-native-svg react-native-screens sonner-native pressto react-native-ease
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Then add the Worklets Babel plugin to `babel.config.js` (required by `@gorhom/bottom-sheet` and the pressable animations):
|
|
40
|
+
### Peer Dependencies (all required unless noted)
|
|
41
|
+
- **Hard:** `expo-haptics`, `expo-linear-gradient`, `expo-font`, `expo-image`, `react-native-reanimated`, `react-native-gesture-handler`, `react-native-worklets`, `react-native-safe-area-context`, `react-native-screens`, `react-native-svg`, `@gorhom/bottom-sheet`, `@react-native-picker/picker`, `@react-native-community/slider`, `@expo/vector-icons`, `react-native-size-matters`, `sonner-native`, `react-native-ease`, `pressto`
|
|
42
|
+
- **Optional:** `react-native-image-picker` (ImageUpload), `@shopify/react-native-skia` + `expo-sensors` (HolographicCard deep-import only)
|
|
76
43
|
|
|
44
|
+
### Babel Config
|
|
77
45
|
```js
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
presets: ['babel-preset-expo'],
|
|
82
|
-
plugins: ['react-native-worklets/plugin'], // NOT react-native-reanimated/plugin
|
|
83
|
-
}
|
|
84
|
-
}
|
|
46
|
+
plugins: ['react-native-worklets/plugin']
|
|
47
|
+
// NOT react-native-reanimated/plugin
|
|
85
48
|
```
|
|
86
49
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
| `pressto` | **Required** | Every interactive component | Press-scale animations (`src/utils/pressable.ts`). Static import via the barrel — omitting it crashes the module load, not just one component. |
|
|
90
|
-
| `sonner-native` | **Required** | `Toast` | Also pulls `react-native-svg` + `react-native-screens`. |
|
|
91
|
-
| `react-native-reanimated` (≥4.0) | **Required** | Animations, `Sheet`, pressables | Needs the `react-native-worklets/plugin` Babel plugin. |
|
|
92
|
-
| `react-native-gesture-handler` | **Required** | `Sheet`, pressables | Wrap app in `GestureHandlerRootView`. |
|
|
93
|
-
| `@gorhom/bottom-sheet` (≥5.2.0) | **Required** | `Sheet`, `ConfirmDialog` | 5.1.x crashes on Reanimated v4. |
|
|
94
|
-
| `expo-haptics` | **Required** | Haptic feedback (all interactions) | Web-safe wrapper, no-op on web. |
|
|
95
|
-
| `expo-font` | **Required** | All `Text` | Load `SohneFonts` at app root before rendering. |
|
|
96
|
-
| `expo-linear-gradient` | **Required** | `Skeleton` shimmer | |
|
|
97
|
-
| `@react-native-picker/picker` | **Required** | `Select` | |
|
|
98
|
-
| `@react-native-community/slider` | **Required** | `Slider` | |
|
|
99
|
-
| `@expo/vector-icons` | **Required** | Icons everywhere | |
|
|
100
|
-
| `react-native-size-matters` | **Required** | Responsive scaling | |
|
|
101
|
-
| `react-native-ease` | **Required** | `Checkbox`, `Chip`, `CategoryStrip`, `Switch`, `RadioGroup`, `Toggle` | Declarative `EaseView` animation layer. Static import in source — omitting it crashes the module load. `pnpm add react-native-ease` |
|
|
102
|
-
| `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` |
|
|
103
|
-
| `@shopify/react-native-skia` + `expo-sensors` | **Optional** | Deep-import `HolographicCard` only | `pnpm add @shopify/react-native-skia expo-sensors` |
|
|
104
|
-
|
|
105
|
-
> **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.
|
|
106
|
-
|
|
107
|
-
### Troubleshooting: `react-native-screens` codegen on RN 0.83
|
|
108
|
-
|
|
109
|
-
Toast pulls `sonner-native`, which imports `react-native-screens` via its `src/*` path. On **React Native 0.83** that source trips a `@react-native/codegen` parse error (`CT.WithDefault` not parseable). It is a `react-native-screens` issue, not the kit — but since Toast surfaces it, add this resolver to your `metro.config.js` to force the compiled `lib/commonjs` build:
|
|
110
|
-
|
|
111
|
-
```js
|
|
112
|
-
// metro.config.js
|
|
113
|
-
const { getDefaultConfig } = require('expo/metro-config')
|
|
114
|
-
const config = getDefaultConfig(__dirname)
|
|
115
|
-
|
|
116
|
-
const defaultResolveRequest = config.resolver.resolveRequest
|
|
117
|
-
config.resolver.resolveRequest = (context, moduleName, platform) => {
|
|
118
|
-
// Force react-native-screens to its compiled output, bypassing the src codegen bug.
|
|
119
|
-
if (moduleName.startsWith('react-native-screens/src')) {
|
|
120
|
-
moduleName = moduleName.replace('react-native-screens/src', 'react-native-screens/lib/commonjs')
|
|
121
|
-
}
|
|
122
|
-
return (defaultResolveRequest ?? context.resolveRequest)(context, moduleName, platform)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
module.exports = config
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Typography — Sohne (Required)
|
|
129
|
-
|
|
130
|
-
All components use **Sohne** as the font family. You **must** load it before rendering any UI kit component.
|
|
131
|
-
|
|
132
|
-
### How it works
|
|
133
|
-
|
|
134
|
-
1. When you install `@retray-dev/ui-kit`, the **postinstall script** automatically copies 28 `.otf` font files to `assets/fonts/sohne/` in your project root
|
|
135
|
-
2. You must define `SohneFonts` with static `require()` calls in your `App.tsx` (see boilerplate below)
|
|
136
|
-
3. Pass it to `expo-font`'s `useFonts()` hook at your app root
|
|
137
|
-
4. All library components reference fonts by family name (e.g., `fontFamily: 'Sohne-SemiBold'`)
|
|
138
|
-
|
|
139
|
-
### SohneFonts boilerplate — copy this into your App.tsx
|
|
140
|
-
|
|
141
|
-
```tsx
|
|
142
|
-
import { useFonts } from 'expo-font'
|
|
143
|
-
|
|
144
|
-
// Fonts copied to assets/fonts/sohne/ by @retray-dev/ui-kit postinstall
|
|
145
|
-
const SohneFonts = {
|
|
146
|
-
'Sohne-ExtraLight': require('./assets/fonts/sohne/Sohne-ExtraLight.otf'),
|
|
147
|
-
'Sohne-ExtraLightItalic': require('./assets/fonts/sohne/Sohne-ExtraLightItalic.otf'),
|
|
148
|
-
'Sohne-Light': require('./assets/fonts/sohne/Sohne-Light.otf'),
|
|
149
|
-
'Sohne-LightItalic': require('./assets/fonts/sohne/Sohne-LightItalic.otf'),
|
|
150
|
-
'Sohne-Regular': require('./assets/fonts/sohne/Sohne-Regular.otf'),
|
|
151
|
-
'Sohne-Italic': require('./assets/fonts/sohne/Sohne-Italic.otf'),
|
|
152
|
-
'Sohne-Medium': require('./assets/fonts/sohne/Sohne-Medium.otf'),
|
|
153
|
-
'Sohne-MediumItalic': require('./assets/fonts/sohne/Sohne-MediumItalic.otf'),
|
|
154
|
-
'Sohne-SemiBold': require('./assets/fonts/sohne/Sohne-SemiBold.otf'),
|
|
155
|
-
'Sohne-SemiBoldItalic': require('./assets/fonts/sohne/Sohne-SemiBoldItalic.otf'),
|
|
156
|
-
'Sohne-Bold': require('./assets/fonts/sohne/Sohne-Bold.otf'),
|
|
157
|
-
'Sohne-BoldItalic': require('./assets/fonts/sohne/Sohne-BoldItalic.otf'),
|
|
158
|
-
'Sohne-ExtraBold': require('./assets/fonts/sohne/Sohne-ExtraBold.otf'),
|
|
159
|
-
'Sohne-ExtraBoldItalic': require('./assets/fonts/sohne/Sohne-ExtraBoldItalic.otf'),
|
|
160
|
-
'SohneMono-ExtraLight': require('./assets/fonts/sohne/SohneMono-ExtraLight.otf'),
|
|
161
|
-
'SohneMono-ExtraLightItalic': require('./assets/fonts/sohne/SohneMono-ExtraLightItalic.otf'),
|
|
162
|
-
'SohneMono-Light': require('./assets/fonts/sohne/SohneMono-Light.otf'),
|
|
163
|
-
'SohneMono-LightItalic': require('./assets/fonts/sohne/SohneMono-LightItalic.otf'),
|
|
164
|
-
'SohneMono-Regular': require('./assets/fonts/sohne/SohneMono-Regular.otf'),
|
|
165
|
-
'SohneMono-Italic': require('./assets/fonts/sohne/SohneMono-Italic.otf'),
|
|
166
|
-
'SohneMono-Medium': require('./assets/fonts/sohne/SohneMono-Medium.otf'),
|
|
167
|
-
'SohneMono-MediumItalic': require('./assets/fonts/sohne/SohneMono-MediumItalic.otf'),
|
|
168
|
-
'SohneMono-SemiBold': require('./assets/fonts/sohne/SohneMono-SemiBold.otf'),
|
|
169
|
-
'SohneMono-SemiBoldItalic': require('./assets/fonts/sohne/SohneMono-SemiBoldItalic.otf'),
|
|
170
|
-
'SohneMono-Bold': require('./assets/fonts/sohne/SohneMono-Bold.otf'),
|
|
171
|
-
'SohneMono-BoldItalic': require('./assets/fonts/sohne/SohneMono-BoldItalic.otf'),
|
|
172
|
-
'SohneMono-ExtraBold': require('./assets/fonts/sohne/SohneMono-ExtraBold.otf'),
|
|
173
|
-
'SohneMono-ExtraBoldItalic': require('./assets/fonts/sohne/SohneMono-ExtraBoldItalic.otf'),
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export default function App() {
|
|
177
|
-
const [fontsLoaded] = useFonts(SohneFonts)
|
|
178
|
-
if (!fontsLoaded) return null
|
|
179
|
-
return (
|
|
180
|
-
// ... your providers and app
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### .gitignore recommendation
|
|
186
|
-
|
|
187
|
-
Fonts are copied to `assets/fonts/sohne/` on install. You can either:
|
|
188
|
-
- **Commit them** (no network needed during CI builds)
|
|
189
|
-
- **Ignore them** (re-copied on every `pnpm install`)
|
|
190
|
-
|
|
191
|
-
```gitignore
|
|
192
|
-
# Sohne fonts — copied by @retray-dev/ui-kit postinstall
|
|
193
|
-
# Either commit these or ignore them (re-copied on install)
|
|
194
|
-
assets/fonts/sohne/
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
**Included weights (28 files):**
|
|
198
|
-
- Sohne: `Sohne-ExtraLight`, `Sohne-Light`, `Sohne-Regular`, `Sohne-Medium`, `Sohne-SemiBold`, `Sohne-Bold`, `Sohne-ExtraBold` + italic variants
|
|
199
|
-
- SohneMono: `SohneMono-ExtraLight`, `SohneMono-Light`, `SohneMono-Regular`, `SohneMono-Medium`, `SohneMono-SemiBold`, `SohneMono-Bold`, `SohneMono-ExtraBold` + italic variants
|
|
200
|
-
|
|
201
|
-
Pair with `expo-splash-screen` in production:
|
|
202
|
-
```tsx
|
|
203
|
-
import * as SplashScreen from 'expo-splash-screen'
|
|
204
|
-
SplashScreen.preventAutoHideAsync()
|
|
205
|
-
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
if (fontsLoaded) SplashScreen.hideAsync()
|
|
208
|
-
}, [fontsLoaded])
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## Theme System
|
|
214
|
-
|
|
215
|
-
### ThemeProvider Props
|
|
216
|
-
|
|
217
|
-
| Prop | Type | Default | Notes |
|
|
218
|
-
|------|------|---------|-------|
|
|
219
|
-
| colorScheme | `'light' \| 'dark' \| 'system'` | `'system'` | `'system'` auto-detects device setting and updates when it changes |
|
|
220
|
-
| theme | `{ light?: Partial<ThemeColors>, dark?: Partial<ThemeColors> }` | — | Override any subset of the 12 public tokens per scheme |
|
|
221
|
-
|
|
222
|
-
**Custom theme example:**
|
|
223
|
-
```tsx
|
|
224
|
-
const myTheme = {
|
|
225
|
-
light: { primary: '#ff385c', primaryForeground: '#ffffff' },
|
|
226
|
-
dark: { primary: '#ff385c', primaryForeground: '#ffffff' },
|
|
227
|
-
}
|
|
228
|
-
<ThemeProvider theme={myTheme} colorScheme="system">
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### useTheme Hook
|
|
232
|
-
|
|
233
|
-
```tsx
|
|
234
|
-
import { useTheme } from '@retray-dev/ui-kit'
|
|
235
|
-
|
|
236
|
-
function MyComponent() {
|
|
237
|
-
const { colors, colorScheme } = useTheme()
|
|
238
|
-
return <View style={{ backgroundColor: colors.background }} />
|
|
239
|
-
}
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
Returns `colors` (full `ResolvedColors` palette) and `colorScheme` (`'light' | 'dark'`).
|
|
50
|
+
### Fonts (Sohne)
|
|
51
|
+
Postinstall copies 28 `.otf` to `assets/fonts/sohne/`. Consumer defines static `require()` calls in App.tsx with `expo-font`'s `useFonts()`.
|
|
243
52
|
|
|
244
53
|
---
|
|
245
54
|
|
|
246
|
-
##
|
|
55
|
+
## Design System (Airbnb-inspired)
|
|
247
56
|
|
|
248
|
-
|
|
57
|
+
| Token | Rule |
|
|
58
|
+
|---|---|
|
|
59
|
+
| **Touch targets** | ≥44pt height on all interactive elements |
|
|
60
|
+
| **Spacing** | 8pt grid, 4pt micro-steps |
|
|
61
|
+
| **Radius** | sm=6, md=8, lg=12, xl=16 |
|
|
62
|
+
| **Colors** | 12 public tokens → `deriveColors()` computes 26 resolved tokens |
|
|
63
|
+
| **Typography** | Sohne, modest weights (500-600 display), `allowFontScaling={true}` everywhere |
|
|
64
|
+
| **Shadows** | Max 2 tiers. Flat baseline + single float tier |
|
|
65
|
+
| **Elevation** | Depth from rounded corners + surface separation, not shadows |
|
|
249
66
|
|
|
250
|
-
|
|
67
|
+
### Theme Tokens — 12 Public (consumer can override)
|
|
251
68
|
|
|
252
|
-
| Token | Light Default | Dark Default |
|
|
253
|
-
|
|
69
|
+
| Token | Light Default | Dark Default | Role |
|
|
70
|
+
|-------|--------------|--------------|------|
|
|
254
71
|
| `background` | `#ffffff` | `#0f0f0f` | Screen / page background |
|
|
255
|
-
| `foreground` | `#1a1a1a` | `#fafafa` | Primary text
|
|
256
|
-
| `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface
|
|
257
|
-
| `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states
|
|
258
|
-
| `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon
|
|
72
|
+
| `foreground` | `#1a1a1a` | `#fafafa` | Primary text |
|
|
73
|
+
| `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface |
|
|
74
|
+
| `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states) |
|
|
75
|
+
| `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon on primary |
|
|
259
76
|
| `border` | `#dddddd` | `#303030` | Borders, dividers, input outlines |
|
|
260
|
-
| `destructive` | `#c72828` | `#ef5350` | Error / danger / delete
|
|
261
|
-
| `destructiveForeground` | `#ffffff` | `#ffffff` | Text
|
|
262
|
-
| `success` | `#1a7a45` | `#2e7d52` | Success / confirmation
|
|
263
|
-
| `successForeground` | `#ffffff` | `#ffffff` | Text
|
|
264
|
-
| `warning` | `#9a5200` | `#f5a623` | Warning / caution
|
|
265
|
-
| `warningForeground` | `#ffffff` | `#0f0f0f` | Text
|
|
266
|
-
| `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop
|
|
267
|
-
| `accent` *(optional)* | `#d4561d` | `#e87645` |
|
|
268
|
-
| `accentForeground` *(optional)* |
|
|
269
|
-
|
|
270
|
-
### Derived Tokens
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
|
275
|
-
|
|
276
|
-
| `
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
279
|
-
| `
|
|
280
|
-
| `
|
|
281
|
-
| `
|
|
282
|
-
| `destructiveBorder` | `destructive` @ 30% | Alert banner border, badge outline |
|
|
283
|
-
| `successTint` | `success` blended to bg | Success banner background |
|
|
77
|
+
| `destructive` | `#c72828` | `#ef5350` | Error / danger / delete |
|
|
78
|
+
| `destructiveForeground` | `#ffffff` | `#ffffff` | Text on destructive |
|
|
79
|
+
| `success` | `#1a7a45` | `#2e7d52` | Success / confirmation |
|
|
80
|
+
| `successForeground` | `#ffffff` | `#ffffff` | Text on success |
|
|
81
|
+
| `warning` | `#9a5200` | `#f5a623` | Warning / caution |
|
|
82
|
+
| `warningForeground` | `#ffffff` | `#0f0f0f` | Text on warning |
|
|
83
|
+
| `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop behind sheets |
|
|
84
|
+
| `accent` *(optional)* | `#d4561d` | `#e87645` | Brand accent (falls back to `primary`) |
|
|
85
|
+
| `accentForeground` *(optional)* | `primaryForeground` | `primaryForeground` | Text on accent |
|
|
86
|
+
|
|
87
|
+
### Derived Tokens — 26 ResolvedColors (read-only via `useTheme().colors`)
|
|
88
|
+
|
|
89
|
+
| Token | Source | Purpose |
|
|
90
|
+
|-------|--------|---------|
|
|
91
|
+
| `foregroundSubtle` | `foreground` @ ~70% | Body text, subtitles |
|
|
92
|
+
| `foregroundMuted` | `foreground` @ ~62% | Captions, placeholders (WCAG AA 4.5:1) |
|
|
93
|
+
| `surface` | `background` off-canvas | Chip backgrounds, input fills |
|
|
94
|
+
| `surfaceStrong` | `background` stronger offset | Pressed/hover states |
|
|
95
|
+
| `skeleton` | `background` @ ±10% | Skeleton placeholder |
|
|
96
|
+
| `destructiveTint` | `destructive` to bg | Alert banner bg |
|
|
97
|
+
| `destructiveBorder` | `destructive` @ 30% | Alert banner border |
|
|
98
|
+
| `successTint` | `success` to bg | Success banner bg |
|
|
284
99
|
| `successBorder` | `success` @ 30% | Success banner border |
|
|
285
|
-
| `warningTint` | `warning`
|
|
100
|
+
| `warningTint` | `warning` to bg | Warning banner bg |
|
|
286
101
|
| `warningBorder` | `warning` @ 30% | Warning banner border |
|
|
287
|
-
| `ring` | `= primary` | Focus ring
|
|
288
|
-
| `input` | `= border` | Input
|
|
289
|
-
| `separator` | `border` @ ±16-22% | Divider
|
|
290
|
-
| `overlay` | `overlay` token or `rgba(0,0,0,0.45)` |
|
|
291
|
-
| `accentResolved` | `accent` token or `= primary` | Resolved accent
|
|
292
|
-
| `accentForegroundResolved` | `accentForeground`
|
|
293
|
-
|
|
294
|
-
**Usage example — building a custom component using derived tokens:**
|
|
295
|
-
```tsx
|
|
296
|
-
const { colors } = useTheme()
|
|
297
|
-
|
|
298
|
-
// Text hierarchy
|
|
299
|
-
<Text style={{ color: colors.foreground }}>Primary text</Text>
|
|
300
|
-
<Text style={{ color: colors.foregroundSubtle }}>Secondary text</Text>
|
|
301
|
-
<Text style={{ color: colors.foregroundMuted }}>Caption / timestamp</Text>
|
|
302
|
-
|
|
303
|
-
// Surface fills (unselected chips, inactive backgrounds)
|
|
304
|
-
<View style={{ backgroundColor: colors.surface }}>
|
|
305
|
-
|
|
306
|
-
// Warning alert banner
|
|
307
|
-
<View style={{ backgroundColor: colors.warningTint, borderColor: colors.warningBorder }}>
|
|
308
|
-
<Text style={{ color: colors.warning }}>Warning message</Text>
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
### deriveColors export
|
|
102
|
+
| `ring` | `= primary` | Focus ring |
|
|
103
|
+
| `input` | `= border` | Input border |
|
|
104
|
+
| `separator` | `border` @ ±16-22% | Divider line |
|
|
105
|
+
| `overlay` | `overlay` token or `rgba(0,0,0,0.45)` | Sheet backdrop |
|
|
106
|
+
| `accentResolved` | `accent` token or `= primary` | Resolved accent |
|
|
107
|
+
| `accentForegroundResolved` | `accentForeground` or `= primaryForeground` | Resolved text on accent |
|
|
312
108
|
|
|
109
|
+
### Color Utility
|
|
313
110
|
```tsx
|
|
314
|
-
import {
|
|
315
|
-
|
|
316
|
-
const resolved = deriveColors(myThemeColors, 'light')
|
|
317
|
-
// resolved contains all 26 ResolvedColors tokens
|
|
111
|
+
import { withAlpha } from '@retray-dev/ui-kit'
|
|
112
|
+
// hex → rgba: withAlpha('#1a1a1a', 0.15) → 'rgba(26,26,26,0.15)'
|
|
318
113
|
```
|
|
319
114
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
### Color Utilities
|
|
323
|
-
|
|
324
|
-
**Import:** `import { withAlpha } from '@retray-dev/ui-kit'`
|
|
325
|
-
|
|
326
|
-
Convert a hex color to rgba with the given alpha. Useful for semi-transparent backgrounds, borders, and overlays derived from theme colors.
|
|
327
|
-
|
|
328
|
-
```tsx
|
|
329
|
-
import { useTheme, withAlpha } from '@retray-dev/ui-kit'
|
|
330
|
-
|
|
331
|
-
const { colors } = useTheme()
|
|
332
|
-
|
|
333
|
-
<View style={{ backgroundColor: withAlpha(colors.primary, 0.15) }}>
|
|
334
|
-
<Text style={{ color: colors.primary }}>Tinted background</Text>
|
|
335
|
-
</View>
|
|
336
|
-
```
|
|
115
|
+
### Shape Language
|
|
116
|
+
Soft rounded rects everywhere. Buttons: `RADIUS.md=14px` (NOT pill). IconButton: `RADIUS.full` (circle). Inputs: 8px. Cards: 14px. Modals: 16px top corners only.
|
|
337
117
|
|
|
338
118
|
---
|
|
339
119
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
Static structural constants — no context or provider needed.
|
|
120
|
+
### Design Tokens — Static Constants, No Provider Needed
|
|
343
121
|
|
|
344
122
|
```ts
|
|
345
123
|
import { SPACING, ICON_SIZES, RADIUS, SHADOWS, BREAKPOINTS, TYPOGRAPHY } from '@retray-dev/ui-kit'
|
|
346
124
|
```
|
|
347
125
|
|
|
348
|
-
|
|
126
|
+
**SPACING — 8pt grid**
|
|
349
127
|
|
|
350
128
|
| Key | Value | Use |
|
|
351
129
|
|-----|-------|-----|
|
|
352
|
-
| `xxs` | 2 | Micro gaps
|
|
130
|
+
| `xxs` | 2 | Micro gaps |
|
|
353
131
|
| `xs` | 4 | Tight internal gaps |
|
|
354
132
|
| `sm` | 8 | Component internal padding |
|
|
355
133
|
| `md` | 12 | Medium gaps |
|
|
356
134
|
| `base` | 16 | Default content padding |
|
|
357
135
|
| `lg` | 24 | Section internal padding |
|
|
358
|
-
| `xl` | 32 | Between major
|
|
136
|
+
| `xl` | 32 | Between major blocks |
|
|
359
137
|
| `xxl` | 48 | Section padding |
|
|
360
|
-
| `section` | 64 | Major band separators
|
|
361
|
-
|
|
362
|
-
**Types:** `Spacing`, `SpacingKey`
|
|
363
|
-
|
|
364
|
-
```tsx
|
|
365
|
-
<View style={{ gap: SPACING.md, padding: SPACING.base }} />
|
|
366
|
-
```
|
|
138
|
+
| `section` | 64 | Major band separators |
|
|
367
139
|
|
|
368
|
-
|
|
140
|
+
**ICON_SIZES**
|
|
369
141
|
|
|
370
142
|
| Key | Value | Use |
|
|
371
143
|
|-----|-------|-----|
|
|
372
|
-
| `sm` | 14 | Badge icons
|
|
144
|
+
| `sm` | 14 | Badge icons |
|
|
373
145
|
| `md` | 18 | Standard component icons |
|
|
374
146
|
| `lg` | 22 | Larger inline icons |
|
|
375
147
|
| `xl` | 28 | Feature icons |
|
|
376
148
|
| `2xl` | 32 | Display icons |
|
|
377
149
|
|
|
378
|
-
**
|
|
379
|
-
|
|
380
|
-
### RADIUS — Airbnb shape language
|
|
150
|
+
**RADIUS — Airbnb shape language**
|
|
381
151
|
|
|
382
152
|
| Key | Value | Used in |
|
|
383
153
|
|-----|-------|---------|
|
|
384
|
-
| `none` | 0 |
|
|
154
|
+
| `none` | 0 | — |
|
|
385
155
|
| `xs` | 4 | Micro chips, tags |
|
|
386
156
|
| `sm` | 8 | Inputs, Textarea, Select, Checkbox |
|
|
387
|
-
| `md` | 14 | Cards, Buttons
|
|
157
|
+
| `md` | 14 | Cards, Buttons, MediaCard, AlertBanner, Toast |
|
|
388
158
|
| `lg` | 20 | Sheet top corners |
|
|
389
159
|
| `xl` | 32 | Large decorative elements |
|
|
390
160
|
| `full` | 9999 | IconButton (circle), CategoryStrip chips |
|
|
391
161
|
|
|
392
|
-
**
|
|
393
|
-
|
|
394
|
-
```tsx
|
|
395
|
-
<View style={{ borderRadius: RADIUS.md }} />
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### SHADOWS — Cross-platform shadow presets
|
|
399
|
-
|
|
400
|
-
| Key | shadowOffset.height | shadowOpacity | shadowRadius | elevation | Use |
|
|
401
|
-
|-----|---------------------|---------------|--------------|-----------|-----|
|
|
402
|
-
| `sm` | 1 | 0.06 | 4 | 2 | Default card shadow |
|
|
403
|
-
| `md` | 2 | 0.10 | 8 | 5 | Hover float / elevated elements |
|
|
404
|
-
| `lg` | 6 | 0.16 | 16 | 10 | Modals, overlays |
|
|
405
|
-
| `xl` | 12 | 0.24 | 24 | 18 | High-elevation dialogs |
|
|
162
|
+
**SHADOWS — Cross-platform**
|
|
406
163
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
164
|
+
| Key | opacity | radius | elevation | Use |
|
|
165
|
+
|-----|---------|--------|-----------|-----|
|
|
166
|
+
| `sm` | 0.06 | 4 | 2 | Default card |
|
|
167
|
+
| `md` | 0.10 | 8 | 5 | Hover float |
|
|
168
|
+
| `lg` | 0.16 | 16 | 10 | Modals |
|
|
169
|
+
| `xl` | 0.24 | 24 | 18 | High-elevation |
|
|
412
170
|
|
|
413
|
-
|
|
171
|
+
**BREAKPOINTS**
|
|
414
172
|
|
|
415
173
|
| Key | Value | Use |
|
|
416
174
|
|-----|-------|-----|
|
|
417
175
|
| `wide` | 700 | Tablet / wide layout threshold |
|
|
418
176
|
|
|
419
|
-
|
|
420
|
-
const isWide = useWindowDimensions().width >= BREAKPOINTS.wide
|
|
421
|
-
```
|
|
177
|
+
**TYPOGRAPHY — 17 variants**
|
|
422
178
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
| Key | Size | Weight | lineHeight | letterSpacing | Use |
|
|
428
|
-
|-----|------|--------|-----------|---------------|-----|
|
|
429
|
-
| `display-hero` | 64 | 700 | 70 | -1 | Large number display, balance totals |
|
|
179
|
+
| Key | Size | Weight | lh | ls | Use |
|
|
180
|
+
|-----|------|--------|----|----|-----|
|
|
181
|
+
| `display-hero` | 64 | 700 | 70 | -1 | Large number display |
|
|
430
182
|
| `display-xl` | 28 | 700 | 40 | 0 | Screen titles, hero headings |
|
|
431
|
-
| `display-lg` |
|
|
432
|
-
| `display-md` |
|
|
433
|
-
| `display-sm` |
|
|
434
|
-
| `title-md` |
|
|
435
|
-
| `title-sm` |
|
|
183
|
+
| `display-lg` | 24 | 600 | 32 | -0.3 | Section headings |
|
|
184
|
+
| `display-md` | 20 | 600 | 28 | 0 | Card titles |
|
|
185
|
+
| `display-sm` | 18 | 600 | 24 | -0.18 | Sub-section headings |
|
|
186
|
+
| `title-md` | 17 | 600 | 22 | 0 | Row titles |
|
|
187
|
+
| `title-sm` | 15 | 500 | 20 | 0 | Secondary titles |
|
|
436
188
|
| `body-md` | 16 | 400 | 24 | 0 | Primary body copy |
|
|
437
|
-
| `body-sm` | 14 | 400 | 20 | 0 | Secondary body
|
|
438
|
-
| `caption` | 14 | 500 | 18 | 0 |
|
|
439
|
-
| `caption-sm` | 13 | 400 | 16 | 0 | Timestamps
|
|
440
|
-
| `badge-text` | 11 | 600 |
|
|
441
|
-
| `
|
|
442
|
-
| `
|
|
443
|
-
| `
|
|
444
|
-
| `button-
|
|
445
|
-
|
|
446
|
-
**Types:** `Typography`, `TypographyKey`
|
|
447
|
-
|
|
448
|
-
```tsx
|
|
449
|
-
import { TYPOGRAPHY } from '@retray-dev/ui-kit'
|
|
450
|
-
|
|
451
|
-
// Use in StyleSheet
|
|
452
|
-
const styles = StyleSheet.create({
|
|
453
|
-
heading: {
|
|
454
|
-
...TYPOGRAPHY['display-xl'],
|
|
455
|
-
color: colors.foreground,
|
|
456
|
-
},
|
|
457
|
-
})
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
---
|
|
461
|
-
|
|
462
|
-
## Migration Guide: v4 → v5
|
|
463
|
-
|
|
464
|
-
### Breaking Changes
|
|
465
|
-
|
|
466
|
-
**Button variants renamed:**
|
|
467
|
-
| v4 | v5 |
|
|
468
|
-
|----|-----|
|
|
469
|
-
| `outline` | `secondary` |
|
|
470
|
-
| `ghost` | `text` |
|
|
471
|
-
| `secondary` (filled gray) | removed — use Card/surface instead |
|
|
472
|
-
|
|
473
|
-
**Text variants replaced:**
|
|
474
|
-
| v4 | v5 equivalent |
|
|
475
|
-
|----|---------------|
|
|
476
|
-
| `h1` | `display-xl` |
|
|
477
|
-
| `h2` | `display-lg` or `display-md` |
|
|
478
|
-
| `h3` | `display-sm` |
|
|
479
|
-
| `body` | `body-md` |
|
|
480
|
-
| `label` | `title-sm` or `caption` |
|
|
481
|
-
| `caption` | `caption-sm` |
|
|
482
|
-
|
|
483
|
-
**Theme tokens changed:**
|
|
484
|
-
| v4 | v5 |
|
|
485
|
-
|----|-----|
|
|
486
|
-
| `secondary`, `secondaryForeground` | removed (use `surface` derived token) |
|
|
487
|
-
| `accent`, `accentForeground` | removed (use `surfaceStrong`) |
|
|
488
|
-
| `muted` | removed → `surface` (derived) |
|
|
489
|
-
| `mutedForeground` | removed → `foregroundSubtle` / `foregroundMuted` (derived) |
|
|
490
|
-
| Added | `warning`, `warningForeground` |
|
|
491
|
-
| Added (derived) | `warningTint`, `warningBorder` |
|
|
492
|
-
|
|
493
|
-
**IconButton variant:**
|
|
494
|
-
| v4 | v5 |
|
|
495
|
-
|----|-----|
|
|
496
|
-
| `ghost` | `text` |
|
|
189
|
+
| `body-sm` | 14 | 400 | 20 | 0 | Secondary body |
|
|
190
|
+
| `caption` | 14 | 500 | 18 | 0 | Input labels |
|
|
191
|
+
| `caption-sm` | 13 | 400 | 16 | 0 | Timestamps |
|
|
192
|
+
| `badge-text` | 11 | 600 | 14 | 0 | Badge labels |
|
|
193
|
+
| `badge-text-md` | 13 | 600 | 16 | 0 | Badge labels (md size) |
|
|
194
|
+
| `micro-label` | 12 | 700 | 16 | 0 | Overlines |
|
|
195
|
+
| `uppercase-tag` | 11 | 700 | 14 | 0.6 | Decorative tags (auto-uppercase) |
|
|
196
|
+
| `button-lg` | 16 | 500 | 22 | 0 | Button (md/lg) |
|
|
197
|
+
| `button-sm` | 14 | 500 | 18 | 0 | Button (sm) |
|
|
497
198
|
|
|
498
199
|
---
|
|
499
200
|
|
|
500
|
-
##
|
|
501
|
-
|
|
502
|
-
### New Components
|
|
503
|
-
|
|
504
|
-
**`MenuItem`** — Navigation row with icon, label, and optional right slot (`rightRender`). Replaces ad-hoc `ListItem` usage for settings/nav menus. Zero horizontal padding by design — consumer controls spacing.
|
|
201
|
+
## Icons
|
|
505
202
|
|
|
203
|
+
### Icon Component
|
|
506
204
|
```tsx
|
|
507
|
-
import {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
<MenuItem label="Notifications" iconName="bell" rightRender={<Switch value={on} onValueChange={setOn} />} showChevron={false} onPress={() => {}} />
|
|
205
|
+
import { Icon } from '@retray-dev/ui-kit'
|
|
206
|
+
<Icon name="home" size={24} color="#000" />
|
|
207
|
+
// Returns null if name not found — no crash
|
|
511
208
|
```
|
|
512
209
|
|
|
513
|
-
###
|
|
210
|
+
### Resolution Order (first-match wins)
|
|
211
|
+
Feather > AntDesign > Entypo > FontAwesome5 > MaterialIcons > Ionicons
|
|
514
212
|
|
|
515
|
-
**
|
|
516
|
-
| Prop | v5 default | v6 default |
|
|
517
|
-
|------|-----------|-----------|
|
|
518
|
-
| `keyboardBehavior` | `'fillParent'` (Android) / `'interactive'` (iOS) | `'interactive'` (both) |
|
|
519
|
-
| `android_keyboardInputMode` | `'adjustResize'` | `'adjustPan'` |
|
|
520
|
-
|
|
521
|
-
If you relied on `adjustResize`, set it explicitly: `android_keyboardInputMode="adjustResize"`.
|
|
213
|
+
**Feather is first** — most names resolve there (outlined style). Names with `-outline` suffix resolve to Ionicons (no Feather collision).
|
|
522
214
|
|
|
523
|
-
|
|
215
|
+
### `iconName` props on components
|
|
216
|
+
| Component | Prop(s) | Color |
|
|
217
|
+
|-----------|---------|-------|
|
|
218
|
+
| `Button` | `iconName`, `iconColor` | Variant label |
|
|
219
|
+
| `IconButton` | `iconName`, `iconColor` | Variant foreground |
|
|
220
|
+
| `Input` | `prefixIcon`, `prefixIconColor`, `suffixIcon`, `suffixIconColor` | `foregroundMuted` |
|
|
221
|
+
| `ListItem` | `leftIcon`, `leftIconColor`, `rightIcon`, `rightIconColor` | `foreground` / `foregroundMuted` |
|
|
222
|
+
| `Badge` | `iconName`, `iconColor` | Variant foreground |
|
|
223
|
+
| `Toggle` | `iconName`, `iconColor`, `activeIconName`, `activeIconColor` | `foregroundMuted` / `primary` |
|
|
224
|
+
| `AlertBanner` | `iconName`, `iconColor` | Title color |
|
|
225
|
+
| `EmptyState` | `iconName`, `iconColor` | `foregroundMuted` |
|
|
226
|
+
| `MediaCard` | `actionIconName` | White |
|
|
227
|
+
| `AvatarGroup` | — | — (avatars use own colors) |
|
|
228
|
+
| `Chip` | `iconName` | Variant foreground |
|
|
229
|
+
| `DetailRow` | `leftIconName`, `leftIconColor`, `rightIconName`, `rightIconColor` | `foregroundMuted` |
|
|
230
|
+
| `MenuItem` | `iconName`, `iconColor` | `foreground` |
|
|
231
|
+
|
|
232
|
+
### curatedIcons.ts
|
|
233
|
+
8 categories, ~20 icons each, all Feather (outlined). Actions, comunicación, navegación, comida, negocios, perfil, multimedia, texto.
|
|
234
|
+
|
|
235
|
+
### Rules
|
|
236
|
+
- Prefer Feather (outlined) over FA5 solid (filled). FA5 `defaultStyle='regular'` but some names only exist in `solid` — avoid those.
|
|
237
|
+
- Icon lookup uses lazy singleton cache from `glyphMap` introspection.
|
|
524
238
|
|
|
239
|
+
### `getResponsiveFontSize` utility
|
|
525
240
|
```tsx
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
// v6 — direct import preferred (hook still works for compat)
|
|
531
|
-
import { toast } from '@retray-dev/ui-kit'
|
|
532
|
-
toast.success('Done')
|
|
241
|
+
import { getResponsiveFontSize } from '@retray-dev/ui-kit'
|
|
242
|
+
const fontSize = getResponsiveFontSize(text, 48)
|
|
243
|
+
// Steps: ≤10→max, ≤12→max-4, ≤14→max-6, >14→max-8
|
|
533
244
|
```
|
|
534
245
|
|
|
535
|
-
### New Theme Tokens
|
|
536
|
-
|
|
537
|
-
Three optional `ThemeColors` tokens — fall back gracefully if omitted:
|
|
538
|
-
|
|
539
|
-
| Token | Default | Purpose |
|
|
540
|
-
|-------|---------|---------|
|
|
541
|
-
| `overlay` | `rgba(0,0,0,0.45)` | Backdrop color behind sheets and dialogs |
|
|
542
|
-
| `accent` | `= primary` | Secondary brand accent color |
|
|
543
|
-
| `accentForeground` | `= primaryForeground` | Text on accent backgrounds |
|
|
544
|
-
|
|
545
|
-
Access resolved values via `useTheme().colors.overlay`, `.accentResolved`, `.accentForegroundResolved`.
|
|
546
|
-
|
|
547
|
-
### Token Corrections
|
|
548
|
-
|
|
549
|
-
- `TYPOGRAPHY['uppercase-tag']` — size corrected to `10` (was documented as `8`, actual value was always `10`)
|
|
550
|
-
|
|
551
246
|
---
|
|
552
247
|
|
|
553
|
-
##
|
|
554
|
-
|
|
555
|
-
### Breaking Changes
|
|
556
|
-
|
|
557
|
-
**No prop API changes** — all component imports and props are identical. This is a breaking version bump due to visual behavior changes that affect rendered output.
|
|
248
|
+
## Animations & Interactions
|
|
558
249
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
| `
|
|
564
|
-
| `
|
|
565
|
-
| `
|
|
566
|
-
| `
|
|
567
|
-
| `uppercase-tag` | 10px / 0.8 letterSpacing | 11px / 0.6 | Below Apple HIG 11pt minimum |
|
|
568
|
-
| `badge-text-md` | (missing) | 13px / 600 | New canonical token for Badge md |
|
|
569
|
-
|
|
570
|
-
**Default color values changed (WCAG AA fixes):**
|
|
571
|
-
|
|
572
|
-
| Token | v6 | v7 | WCAG impact |
|
|
573
|
-
|-------|----|----|-------------|
|
|
574
|
-
| `foregroundMuted` opacity | 0.38 (~#ababab) | 0.62 (~#767676) | 2.2:1 ❌ → 4.5:1 ✓ |
|
|
575
|
-
| `foregroundSubtle` opacity | 0.55 (~#858585) | 0.70 (~#646464) | 3.5:1 ❌ → 5.9:1 ✓ |
|
|
576
|
-
| `warning` (light) | `#e67e00` | `#9a5200` | 2.86:1 ❌ → 5.86:1 ✓ |
|
|
577
|
-
| `warningForeground` (dark) | `#ffffff` | `#0f0f0f` | Dark text on amber, 8.6:1 ✓ |
|
|
578
|
-
| `destructive` (light) | `#e53935` | `#c72828` | 4.22:1 ❌ → 5.59:1 ✓ |
|
|
579
|
-
| `accent` (light) | `= primary` | `#d4561d` | Explicit brand accent |
|
|
580
|
-
| `accent` (dark) | `= primary` | `#e87645` | Warm accent for dark surfaces |
|
|
250
|
+
### Pressables (pressto)
|
|
251
|
+
| Pressable | Scale | Used by |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| `PressableButton` | 0.95 | Button, IconButton, Toggle, Checkbox |
|
|
254
|
+
| `PressableCard` | 0.98 | Card, MediaCard, Stats, Pressable |
|
|
255
|
+
| `PressableRow` | 0.97 | ListItem, MenuItem |
|
|
256
|
+
| `PressableChip` | 0.94 | Chip |
|
|
257
|
+
| `PressableTab` | 0.95 | Tabs triggers |
|
|
581
258
|
|
|
582
|
-
|
|
259
|
+
All use `enabled={!disabled}`, `rippleColor="transparent"`, `touchSoundDisabled`.
|
|
583
260
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
| `Button` (text variant) | Label color: `foreground` → `accentResolved` — clearer CTA signal |
|
|
588
|
-
| `Card` (elevated variant) | `borderWidth: 0` — shadow is sole depth signal; border was redundant |
|
|
589
|
-
| `Chip` | `paddingVertical` doubled to hit 44pt WCAG 2.5.5 tap target |
|
|
590
|
-
| `Input` / `Textarea` | Border: 1px at rest → animates to 2px on focus (was always 2px) |
|
|
591
|
-
| `Switch` | Off-state now shows animated 1.5px border (invisible on white surfaces before) |
|
|
592
|
-
| `Tabs` | Labels always `Sohne-SemiBold`; active = color only — eliminates layout reflow on selection |
|
|
593
|
-
| `Checkbox` / `RadioGroup` | Disabled `opacity: 0.45` now on full row (was box only) |
|
|
594
|
-
| `Toast` | `richColors={true}` — semantic variants now visually distinct by color |
|
|
261
|
+
### Spring Presets
|
|
262
|
+
- `glide`: `{ stiffness: 380, damping: 38, mass: 1.0 }` — sliding indicators
|
|
263
|
+
- `elastic`: `{ stiffness: 320, damping: 22, mass: 0.7 }` — Switch thumb, RadioGroup dot
|
|
595
264
|
|
|
596
|
-
|
|
265
|
+
### Timing Presets
|
|
266
|
+
- `state`: 160ms (checkbox/toggle color)
|
|
267
|
+
- `expand` / `collapse`: 240ms / 200ms (Accordion)
|
|
268
|
+
- `shimmer`: 1400ms (Skeleton)
|
|
597
269
|
|
|
598
|
-
###
|
|
270
|
+
### EaseView (react-native-ease)
|
|
271
|
+
Declarative color/opacity transitions: Switch track, Checkbox fill, RadioGroup dot. UI thread.
|
|
599
272
|
|
|
600
|
-
|
|
273
|
+
### Reanimated v4
|
|
274
|
+
Complex animations only (gestures, layout interpolations). Simple opacity: use RN `Animated` + `useNativeDriver`.
|
|
601
275
|
|
|
602
276
|
---
|
|
603
277
|
|
|
604
|
-
##
|
|
605
|
-
|
|
606
|
-
### Breaking Changes
|
|
278
|
+
## Sheets (BottomSheetModal)
|
|
607
279
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
**3. `./fonts` stays at `src/fonts.ts` (unchanged)**
|
|
619
|
-
The `./fonts` export deliberately points at `src/fonts.ts`, not `dist`. The `.otf` files ship as raw assets in `src/assets/fonts/` and are resolved by Metro at your build time via `require()`. Compiling this entry to `dist` and repointing the `require()` paths breaks Metro asset resolution under pnpm symlinked workspaces (`requiring unknown module ../src/assets/fonts/...`), so it is intentionally left as source. No API change — import as before:
|
|
620
|
-
```ts
|
|
621
|
-
import { SohneFonts } from '@retray-dev/ui-kit/fonts'
|
|
622
|
-
```
|
|
280
|
+
### Strict rules (from @gorhom/bottom-sheet)
|
|
281
|
+
1. **Always `BottomSheetModal`** — never `BottomSheet` base. `present()` in handler directly (not `useEffect`).
|
|
282
|
+
2. **`enableDynamicSizing`** — no `snapPoints` (mutually exclusive)
|
|
283
|
+
3. **`topInset={insets.top}`** — prevents notch overlap
|
|
284
|
+
4. **`keyboardBehavior="interactive"`** + **`android_keyboardInputMode="adjustPan"`** — keyboard
|
|
285
|
+
5. **`SheetTextInput`** inside sheets — never `<TextInput />`
|
|
286
|
+
6. **`onDismiss`** for cleanup, not `onClose`
|
|
287
|
+
7. **`renderBackdrop`** wrapped in `useCallback`
|
|
288
|
+
8. **`enableBlurKeyboardOnGesture={true}`** — dismiss keyboard on drag
|
|
289
|
+
9. **`BottomSheetModalProvider`** re-exported, included in `RetrayProvider`
|
|
623
290
|
|
|
624
|
-
###
|
|
291
|
+
### Input inside Sheet
|
|
292
|
+
Use `<Input sheetMode />` — transparently swaps to `BottomSheetTextInput`. For low-level: `SheetTextInput`.
|
|
625
293
|
|
|
626
|
-
|
|
627
|
-
- **Dev font guard** — if you render a UI kit component without loading `SohneFonts`, `Text` now logs a one-time `console.warn` in dev (silent in production) instead of failing invisibly.
|
|
628
|
-
|
|
629
|
-
### New components
|
|
294
|
+
---
|
|
630
295
|
|
|
631
|
-
|
|
296
|
+
## Haptics (expo-haptics)
|
|
632
297
|
|
|
633
|
-
|
|
298
|
+
Web-safe via `Platform.OS !== 'web'` guard + dynamic `import()`.
|
|
634
299
|
|
|
635
|
-
|
|
300
|
+
| Function | Usage |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `selectionAsync()` | Checkbox, Switch, Toggle, RadioGroup, Select, Slider (step), Accordion, ListItem, MenuItem, ConfirmDialog cancel |
|
|
303
|
+
| `impactLight()` / `impactMedium()` / `impactHeavy()` | Button, Sheet open, ConfirmDialog open, Stats |
|
|
304
|
+
| `notificationSuccess()` / `notificationError()` / `notificationWarning()` | ConfirmDialog confirm, form validation |
|
|
636
305
|
|
|
637
306
|
---
|
|
638
307
|
|
|
639
|
-
##
|
|
640
|
-
|
|
641
|
-
### Breaking Changes
|
|
642
|
-
|
|
643
|
-
**1. `SohneFonts` export removed from `@retray-dev/ui-kit/fonts`**
|
|
644
|
-
|
|
645
|
-
The `SohneFonts` object with `require()` calls is no longer exported. Metro cannot reliably resolve `require()` from inside `node_modules`, especially in monorepos. The export now returns `undefined` and logs a deprecation warning.
|
|
646
|
-
|
|
647
|
-
**New approach — postinstall script:**
|
|
648
|
-
1. When you install `@retray-dev/ui-kit`, a **postinstall script** automatically copies 28 `.otf` font files to `assets/fonts/sohne/` in your project root
|
|
649
|
-
2. Define `SohneFonts` locally in your `App.tsx` with static `require()` calls (see below)
|
|
650
|
-
3. Pass it to `expo-font`'s `useFonts()` hook
|
|
651
|
-
|
|
652
|
-
**Migration:**
|
|
653
|
-
|
|
654
|
-
```diff
|
|
655
|
-
// App.tsx
|
|
656
|
-
import { useFonts } from 'expo-font'
|
|
657
|
-
-import { SohneFonts } from '@retray-dev/ui-kit/fonts'
|
|
658
|
-
|
|
659
|
-
+// Fonts copied to assets/fonts/sohne/ by @retray-dev/ui-kit postinstall
|
|
660
|
-
+const SohneFonts = {
|
|
661
|
-
+ 'Sohne-ExtraLight': require('./assets/fonts/sohne/Sohne-ExtraLight.otf'),
|
|
662
|
-
+ 'Sohne-ExtraLightItalic': require('./assets/fonts/sohne/Sohne-ExtraLightItalic.otf'),
|
|
663
|
-
+ 'Sohne-Light': require('./assets/fonts/sohne/Sohne-Light.otf'),
|
|
664
|
-
+ 'Sohne-LightItalic': require('./assets/fonts/sohne/Sohne-LightItalic.otf'),
|
|
665
|
-
+ 'Sohne-Regular': require('./assets/fonts/sohne/Sohne-Regular.otf'),
|
|
666
|
-
+ 'Sohne-Italic': require('./assets/fonts/sohne/Sohne-Italic.otf'),
|
|
667
|
-
+ 'Sohne-Medium': require('./assets/fonts/sohne/Sohne-Medium.otf'),
|
|
668
|
-
+ 'Sohne-MediumItalic': require('./assets/fonts/sohne/Sohne-MediumItalic.otf'),
|
|
669
|
-
+ 'Sohne-SemiBold': require('./assets/fonts/sohne/Sohne-SemiBold.otf'),
|
|
670
|
-
+ 'Sohne-SemiBoldItalic': require('./assets/fonts/sohne/Sohne-SemiBoldItalic.otf'),
|
|
671
|
-
+ 'Sohne-Bold': require('./assets/fonts/sohne/Sohne-Bold.otf'),
|
|
672
|
-
+ 'Sohne-BoldItalic': require('./assets/fonts/sohne/Sohne-BoldItalic.otf'),
|
|
673
|
-
+ 'Sohne-ExtraBold': require('./assets/fonts/sohne/Sohne-ExtraBold.otf'),
|
|
674
|
-
+ 'Sohne-ExtraBoldItalic': require('./assets/fonts/sohne/Sohne-ExtraBoldItalic.otf'),
|
|
675
|
-
+ 'SohneMono-ExtraLight': require('./assets/fonts/sohne/SohneMono-ExtraLight.otf'),
|
|
676
|
-
+ 'SohneMono-ExtraLightItalic': require('./assets/fonts/sohne/SohneMono-ExtraLightItalic.otf'),
|
|
677
|
-
+ 'SohneMono-Light': require('./assets/fonts/sohne/SohneMono-Light.otf'),
|
|
678
|
-
+ 'SohneMono-LightItalic': require('./assets/fonts/sohne/SohneMono-LightItalic.otf'),
|
|
679
|
-
+ 'SohneMono-Regular': require('./assets/fonts/sohne/SohneMono-Regular.otf'),
|
|
680
|
-
+ 'SohneMono-Italic': require('./assets/fonts/sohne/SohneMono-Italic.otf'),
|
|
681
|
-
+ 'SohneMono-Medium': require('./assets/fonts/sohne/SohneMono-Medium.otf'),
|
|
682
|
-
+ 'SohneMono-MediumItalic': require('./assets/fonts/sohne/SohneMono-MediumItalic.otf'),
|
|
683
|
-
+ 'SohneMono-SemiBold': require('./assets/fonts/sohne/SohneMono-SemiBold.otf'),
|
|
684
|
-
+ 'SohneMono-SemiBoldItalic': require('./assets/fonts/sohne/SohneMono-SemiBoldItalic.otf'),
|
|
685
|
-
+ 'SohneMono-Bold': require('./assets/fonts/sohne/SohneMono-Bold.otf'),
|
|
686
|
-
+ 'SohneMono-BoldItalic': require('./assets/fonts/sohne/SohneMono-BoldItalic.otf'),
|
|
687
|
-
+ 'SohneMono-ExtraBold': require('./assets/fonts/sohne/SohneMono-ExtraBold.otf'),
|
|
688
|
-
+ 'SohneMono-ExtraBoldItalic': require('./assets/fonts/sohne/SohneMono-ExtraBoldItalic.otf'),
|
|
689
|
-
+}
|
|
308
|
+
## Theming
|
|
690
309
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
310
|
+
### Consumer supplies 12 ThemeColors
|
|
311
|
+
```tsx
|
|
312
|
+
import { ThemeProvider, ThemeColors } from '@retray-dev/ui-kit'
|
|
313
|
+
|
|
314
|
+
const customLight: ThemeColors = {
|
|
315
|
+
background: '#ffffff',
|
|
316
|
+
foreground: '#1a1a1a',
|
|
317
|
+
primary: '#d4561d',
|
|
694
318
|
}
|
|
695
319
|
```
|
|
696
320
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
# Sohne fonts — copied by @retray-dev/ui-kit postinstall
|
|
700
|
-
assets/fonts/sohne/
|
|
701
|
-
```
|
|
321
|
+
### deriveColors()
|
|
322
|
+
Computes 26 `ResolvedColors` from 12 `ThemeColors`. Components consume `useTheme().colors` — never raw `ThemeColors`.
|
|
702
323
|
|
|
703
|
-
|
|
324
|
+
### Structural tokens (not in theme)
|
|
325
|
+
Spacing, radii, shadows, typography = static `as const` exports from `tokens.ts`. No provider needed.
|
|
704
326
|
|
|
705
327
|
---
|
|
706
328
|
|
|
707
|
-
##
|
|
708
|
-
|
|
709
|
-
### New — public utility
|
|
710
|
-
|
|
711
|
-
**`withAlpha(hex, alpha)`** — hex-to-rgba color helper, now exported from the package root. Useful for semi-transparent overlays derived from theme colors without adding a new token.
|
|
329
|
+
## Toast (sonner-native)
|
|
712
330
|
|
|
713
331
|
```tsx
|
|
714
|
-
import {
|
|
332
|
+
import { toast } from 'sonner-native'
|
|
333
|
+
// or
|
|
334
|
+
import { useToast } from '@retray-dev/ui-kit'
|
|
335
|
+
const { toast } = useToast()
|
|
715
336
|
|
|
716
|
-
|
|
717
|
-
|
|
337
|
+
toast('Hello', { description: 'World' })
|
|
338
|
+
toast.success('Saved')
|
|
339
|
+
toast.error('Failed', { description: 'Check network' })
|
|
340
|
+
toast.promise(save(), { loading: 'Saving...', success: 'Done', error: 'Failed' })
|
|
718
341
|
```
|
|
719
342
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
- `Stats` component promoted from internal/experimental to public (`Stats` + `Stats.Group`).
|
|
723
|
-
- Documentation refreshed for `IconPicker` and `NumberStepper`.
|
|
724
|
-
|
|
725
|
-
No breaking changes in v11. Safe minor upgrade from v10.
|
|
343
|
+
`Toaster` rendered inside `ToastProvider`. Config: `richColors: false`, `swipeToDismissDirection="up"`, `duration: 4000`.
|
|
726
344
|
|
|
727
345
|
---
|
|
728
346
|
|
|
729
|
-
##
|
|
730
|
-
|
|
731
|
-
### Breaking Changes
|
|
732
|
-
|
|
733
|
-
`Sheet` and `ConfirmDialog` were rewritten on top of `@gorhom/bottom-sheet`'s **`BottomSheetModal`** (lazy-mounted, `present()` / `dismiss()` driven) — replacing the old `BottomSheet` + `index={-1}` + `snapToIndex(0)` pattern. This fixes timing issues with `enableDynamicSizing` and unifies the API with the rest of the gorhom ecosystem.
|
|
347
|
+
## Data Display Patterns
|
|
734
348
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
<Sheet
|
|
741
|
-
open={open}
|
|
742
|
-
onClose={() => setOpen(false)}
|
|
743
|
-
title="Options"
|
|
744
|
-
- responsive
|
|
745
|
-
- dialogMaxWidth={600}
|
|
746
|
-
/>
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
**2. `onClose` is the only close handler**
|
|
750
|
-
|
|
751
|
-
`onClose` is called from `BottomSheetModal.onDismiss` — the native gorhom callback. The previous `onClose` prop (passed directly to `BottomSheet`) was redundant. No public-API change for consumers; this is an internal alignment.
|
|
349
|
+
### ListItem
|
|
350
|
+
- `rightActions` prop for swipe-to-reveal (iOS Mail style)
|
|
351
|
+
- Uses `PressableRow` (scale 0.97)
|
|
352
|
+
- Haptic: `selectionAsync()` on press
|
|
353
|
+
- Zero horizontal padding by design
|
|
752
354
|
|
|
753
|
-
|
|
355
|
+
### ListGroup
|
|
356
|
+
- Optional `Header` / `Footer` sub-components
|
|
357
|
+
- Auto-separators between items except last
|
|
754
358
|
|
|
755
|
-
|
|
359
|
+
### MenuItem / MenuGroup
|
|
360
|
+
- Settings/nav rows. Auto-separators.
|
|
361
|
+
- `rightRender` for Switch, Badge, etc.
|
|
362
|
+
- `variant="card"` for standalone surface
|
|
756
363
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
<ThemeProvider>
|
|
761
|
-
<BottomSheetModalProvider>
|
|
762
|
-
<ToastProvider>{/* app */}</ToastProvider>
|
|
763
|
-
</BottomSheetModalProvider>
|
|
764
|
-
</ThemeProvider>
|
|
765
|
-
</GestureHandlerRootView>
|
|
766
|
-
</SafeAreaProvider>
|
|
767
|
-
```
|
|
364
|
+
### Chip / ChipGroup
|
|
365
|
+
- Multi-select toggle chips. `PressableChip` (scale 0.94)
|
|
366
|
+
- `ChipGroup` wraps to rows; `CategoryStrip` scrolls horizontally
|
|
768
367
|
|
|
769
|
-
|
|
368
|
+
### LabelValue
|
|
369
|
+
- Key-value rows (label left, value right)
|
|
370
|
+
- Icon support
|
|
770
371
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
| `android_keyboardInputMode` | `'adjustPan'` | `'adjustPan'` (unchanged) |
|
|
372
|
+
### DetailRow
|
|
373
|
+
- Ticket/receipt rows with `···` dotted separator
|
|
374
|
+
- Icon + label + value
|
|
775
375
|
|
|
776
|
-
|
|
376
|
+
### Skeleton
|
|
377
|
+
- `Skeleton.MediaCard`, `Skeleton.ListItem`, `Skeleton.List` (6 items, 2 columns)
|
|
378
|
+
- Shimmer animation loops every 1200ms
|
|
777
379
|
|
|
778
|
-
|
|
380
|
+
---
|
|
779
381
|
|
|
780
|
-
|
|
382
|
+
## Accessibility (WCAG AA)
|
|
781
383
|
|
|
782
|
-
-
|
|
783
|
-
-
|
|
784
|
-
-
|
|
384
|
+
- `allowFontScaling={true}` on all `<Text>` and `<TextInput>`
|
|
385
|
+
- Touch targets ≥44pt
|
|
386
|
+
- `foregroundMuted` = `#9a9a9a` (dark) / `#a2a2a2` (light) — ≥4.5:1 contrast
|
|
387
|
+
- Accessibility props passed through via `...props`
|
|
785
388
|
|
|
786
|
-
|
|
389
|
+
---
|
|
787
390
|
|
|
788
|
-
|
|
391
|
+
## Deep Import Pattern
|
|
789
392
|
|
|
393
|
+
Prefer deep imports in production for smaller bundle:
|
|
790
394
|
```tsx
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
placeholder="Type your note..."
|
|
795
|
-
value={note}
|
|
796
|
-
onChangeText={setNote}
|
|
797
|
-
sheetMode
|
|
798
|
-
/>
|
|
799
|
-
<Button label="Save" fullWidth onPress={handleSave} />
|
|
800
|
-
</Sheet>
|
|
395
|
+
import { Button } from '@retray-dev/ui-kit/Button' // deep
|
|
396
|
+
// vs
|
|
397
|
+
import { Button } from '@retray-dev/ui-kit' // barrel
|
|
801
398
|
```
|
|
802
399
|
|
|
803
|
-
|
|
400
|
+
Deep-import only (NOT in barrel):
|
|
401
|
+
```tsx
|
|
402
|
+
import { HolographicCard } from '@retray-dev/ui-kit/HolographicCard'
|
|
403
|
+
```
|
|
804
404
|
|
|
805
405
|
---
|
|
806
406
|
|
|
807
|
-
##
|
|
808
|
-
|
|
809
|
-
### New — IconPicker feedback pattern (REGLA 4)
|
|
407
|
+
## Anti-Patterns
|
|
810
408
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
No consumer action required — the fix is internal.
|
|
409
|
+
| Anti-pattern | Why | Fix |
|
|
410
|
+
|---|---|---|
|
|
411
|
+
| `<TextInput />` inside Sheet | Keyboard broken | Use `<Input sheetMode />` or `<SheetTextInput />` |
|
|
412
|
+
| `BottomSheet` base with `snapToIndex` | Fragile timing | Use `BottomSheetModal` with `present()` |
|
|
413
|
+
| `snapPoints` + `enableDynamicSizing` | Runtime error | Use one or the other |
|
|
414
|
+
| Static import of optional peer | Crash at module init | Dynamic `import()` in handler |
|
|
415
|
+
| Bundling native modules | Duplicate Context | Externalize in tsup |
|
|
416
|
+
| `--legacy-peer-deps` | Hides conflicts | Use `overrides` in package.json |
|
|
820
417
|
|
|
821
|
-
|
|
418
|
+
---
|
|
822
419
|
|
|
823
|
-
|
|
420
|
+
## Version
|
|
824
421
|
|
|
825
|
-
|
|
422
|
+
**Current: 13.1.0.** Requires Expo SDK 54+, React Native 0.81+, React 19.
|
|
826
423
|
|
|
827
|
-
|
|
828
|
-
- `Input` `sheetMode` prop documented in the Setup section (replaces the `SheetTextInput` boilerplate inside sheets).
|
|
829
|
-
- `CompositionScreen` example now uses `<Input sheetMode />` inside a `Sheet` to demonstrate the keyboard-friendly pattern.
|
|
424
|
+
For full setup guide, see `CONSUMER.md`. For usage examples, see `EXAMPLES.md`.
|
|
830
425
|
|
|
831
426
|
---
|
|
832
427
|
|
|
833
|
-
##
|
|
428
|
+
## Component Reference
|
|
834
429
|
|
|
835
430
|
---
|
|
836
431
|
|
|
@@ -857,18 +452,19 @@ No consumer action required — the fix is internal.
|
|
|
857
452
|
|---------|------|--------|--------------|-------------|
|
|
858
453
|
| `display-hero` | 64 | 700 | `foreground` | Large numeric displays — balance totals, stats, hero numbers |
|
|
859
454
|
| `display-xl` | 28 | 700 | `foreground` | Screen-level titles, onboarding hero headings |
|
|
860
|
-
| `display-lg` |
|
|
861
|
-
| `display-md` |
|
|
862
|
-
| `display-sm` |
|
|
863
|
-
| `title-md` |
|
|
864
|
-
| `title-sm` |
|
|
455
|
+
| `display-lg` | 24 | 600 | `foreground` | Section headings, card group labels |
|
|
456
|
+
| `display-md` | 20 | 600 | `foreground` | Card titles, dialog headings |
|
|
457
|
+
| `display-sm` | 18 | 600 | `foreground` | Sub-section titles |
|
|
458
|
+
| `title-md` | 17 | 600 | `foreground` | List row titles, navigation labels |
|
|
459
|
+
| `title-sm` | 15 | 500 | `foreground` | Secondary row titles |
|
|
865
460
|
| `body-md` | 16 | 400 | `foregroundSubtle` | Main body copy, descriptions |
|
|
866
461
|
| `body-sm` | 14 | 400 | `foregroundSubtle` | Secondary descriptions, detail text |
|
|
867
462
|
| `caption` | 14 | 500 | `foreground` | Labels above inputs, item captions |
|
|
868
463
|
| `caption-sm` | 13 | 400 | `foregroundMuted` | Timestamps, metadata, helper text |
|
|
869
464
|
| `badge-text` | 11 | 600 | `foreground` | Badge labels, small status tags |
|
|
465
|
+
| `badge-text-md` | 13 | 600 | `foreground` | Badge labels (md size) |
|
|
870
466
|
| `micro-label` | 12 | 700 | `foreground` | Micro labels, overlines |
|
|
871
|
-
| `uppercase-tag` |
|
|
467
|
+
| `uppercase-tag` | 11 | 700 | `foreground` | Decorative uppercase category labels (auto-transforms text) |
|
|
872
468
|
| `button-lg` | 16 | 500 | `foreground` | Internal use in Button component (md/lg) |
|
|
873
469
|
| `button-sm` | 14 | 500 | `foreground` | Internal use in Button component (sm) |
|
|
874
470
|
|
|
@@ -1561,6 +1157,65 @@ const [guests, setGuests] = useState(2)
|
|
|
1561
1157
|
|
|
1562
1158
|
---
|
|
1563
1159
|
|
|
1160
|
+
### AvatarGroup
|
|
1161
|
+
|
|
1162
|
+
**Import:** `import { AvatarGroup } from '@retray-dev/ui-kit'`
|
|
1163
|
+
|
|
1164
|
+
**When to use:** Stacked/overlapping avatars to show multiple users in a compact space. Common for collaborator indicators, multi-waiter tables, and team members.
|
|
1165
|
+
|
|
1166
|
+
| Prop | Type | Default | Notes |
|
|
1167
|
+
|------|------|---------|-------|
|
|
1168
|
+
| users | `{ name: string; src?: string }[]` | required | User data — name used for fallback initials |
|
|
1169
|
+
| max | `number` | `3` | Max avatars visible before `+N` overflow badge |
|
|
1170
|
+
| size | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'sm'` | Avatar size preset, passed to each `Avatar` |
|
|
1171
|
+
| overlap | `number` | `vs(8)` | Overlap in points between consecutive avatars |
|
|
1172
|
+
| onOverflowPress | `() => void` | — | Tap handler for the `+N` overflow badge |
|
|
1173
|
+
| style | `ViewStyle` | — | Outer container style |
|
|
1174
|
+
|
|
1175
|
+
**Sizes:** Same as `Avatar` — `sm` (28px), `md` (40px), `lg` (56px), `xl` (72px).
|
|
1176
|
+
|
|
1177
|
+
**Notes:**
|
|
1178
|
+
- Overflow badge uses `surfaceStrong` background with `foregroundMuted` text.
|
|
1179
|
+
- Individual avatars are not tappable — only the `+N` badge fires `onOverflowPress`.
|
|
1180
|
+
- `overlap` follows the spacing grid (defaults to `spacing.sm` = 8pt).
|
|
1181
|
+
|
|
1182
|
+
**Examples:**
|
|
1183
|
+
```tsx
|
|
1184
|
+
// Basic — 5 waiters, show max 3
|
|
1185
|
+
<AvatarGroup
|
|
1186
|
+
users={[
|
|
1187
|
+
{ name: 'Ana', src: 'https://...' },
|
|
1188
|
+
{ name: 'Bob', src: 'https://...' },
|
|
1189
|
+
{ name: 'Carlos' },
|
|
1190
|
+
{ name: 'Diana', src: 'https://...' },
|
|
1191
|
+
{ name: 'Elena' },
|
|
1192
|
+
]}
|
|
1193
|
+
max={3}
|
|
1194
|
+
/>
|
|
1195
|
+
|
|
1196
|
+
// With overflow tap
|
|
1197
|
+
<AvatarGroup
|
|
1198
|
+
users={waiters}
|
|
1199
|
+
max={3}
|
|
1200
|
+
size="sm"
|
|
1201
|
+
onOverflowPress={() => showAllWaiters()}
|
|
1202
|
+
/>
|
|
1203
|
+
|
|
1204
|
+
// In a ListItem
|
|
1205
|
+
<ListItem
|
|
1206
|
+
title="Table 4 — 5 waiters"
|
|
1207
|
+
leftRender={
|
|
1208
|
+
<AvatarGroup
|
|
1209
|
+
users={table.waiters}
|
|
1210
|
+
max={3}
|
|
1211
|
+
size="sm"
|
|
1212
|
+
/>
|
|
1213
|
+
}
|
|
1214
|
+
/>
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
---
|
|
1218
|
+
|
|
1564
1219
|
### Card
|
|
1565
1220
|
|
|
1566
1221
|
**Import:** `import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@retray-dev/ui-kit'`
|
|
@@ -1765,73 +1420,7 @@ const [guests, setGuests] = useState(2)
|
|
|
1765
1420
|
|
|
1766
1421
|
---
|
|
1767
1422
|
|
|
1768
|
-
### VirtualList
|
|
1769
|
-
|
|
1770
|
-
**Import:** `import { VirtualList } from '@retray-dev/ui-kit'`
|
|
1771
|
-
|
|
1772
|
-
**When to use:** Large lists (100+ items) where you need efficient rendering. Thin wrapper over `FlatList` with sane defaults for stable keys and optional fixed-height fast path.
|
|
1773
|
-
|
|
1774
|
-
| Prop | Type | Default | Notes |
|
|
1775
|
-
|------|------|---------|-------|
|
|
1776
|
-
| data | `T[]` | — | Array of items to render |
|
|
1777
|
-
| renderItem | `ListRenderItem<T>` | — | Render function for each item |
|
|
1778
|
-
| itemHeight | `number` | — | Fixed row height in px. Enables `getItemLayout` for performance with large datasets |
|
|
1779
|
-
| keyExtractor | `(item: T, index: number) => string` | Auto | Defaults to `item.id` or index. Override for custom keys |
|
|
1780
|
-
| ...FlatListProps | — | — | All standard FlatList props pass through |
|
|
1781
|
-
|
|
1782
|
-
**Performance optimization:** When `itemHeight` is provided, `VirtualList` enables `getItemLayout` so FlatList skips async measurement. For 10k+ rows, combine with `React.memo`-wrapped `renderItem` so only on-screen rows mount and re-render.
|
|
1783
|
-
|
|
1784
|
-
**Default key extraction:** Uses `item.id` (converted to string) or falls back to index. Override with `keyExtractor` for custom logic.
|
|
1785
|
-
|
|
1786
|
-
**Example — simple list:**
|
|
1787
|
-
```tsx
|
|
1788
|
-
<VirtualList
|
|
1789
|
-
data={items}
|
|
1790
|
-
renderItem={({ item }) => (
|
|
1791
|
-
<ListItem
|
|
1792
|
-
title={item.title}
|
|
1793
|
-
subtitle={item.subtitle}
|
|
1794
|
-
onPress={() => handlePress(item.id)}
|
|
1795
|
-
/>
|
|
1796
|
-
)}
|
|
1797
|
-
itemHeight={56}
|
|
1798
|
-
/>
|
|
1799
|
-
```
|
|
1800
|
-
|
|
1801
|
-
**Example — large dataset with memoized render:**
|
|
1802
|
-
```tsx
|
|
1803
|
-
const renderItem = useCallback(({ item }) => (
|
|
1804
|
-
<ListItem
|
|
1805
|
-
title={item.title}
|
|
1806
|
-
subtitle={item.subtitle}
|
|
1807
|
-
leftIcon="circle"
|
|
1808
|
-
showSeparator
|
|
1809
|
-
onPress={() => navigate('detail', { id: item.id })}
|
|
1810
|
-
/>
|
|
1811
|
-
), [])
|
|
1812
|
-
|
|
1813
|
-
<VirtualList
|
|
1814
|
-
data={thousands}
|
|
1815
|
-
renderItem={renderItem}
|
|
1816
|
-
itemHeight={64}
|
|
1817
|
-
/>
|
|
1818
|
-
```
|
|
1819
|
-
|
|
1820
|
-
**Example — variable height rows (omit itemHeight):**
|
|
1821
|
-
```tsx
|
|
1822
|
-
<VirtualList
|
|
1823
|
-
data={posts}
|
|
1824
|
-
renderItem={({ item }) => (
|
|
1825
|
-
<Card style={{ margin: SPACING.sm }}>
|
|
1826
|
-
<CardContent>
|
|
1827
|
-
<Text>{item.content}</Text>
|
|
1828
|
-
</CardContent>
|
|
1829
|
-
</Card>
|
|
1830
|
-
)}
|
|
1831
|
-
/>
|
|
1832
|
-
```
|
|
1833
1423
|
|
|
1834
|
-
---
|
|
1835
1424
|
|
|
1836
1425
|
### Separator
|
|
1837
1426
|
|
|
@@ -1946,7 +1535,7 @@ const renderItem = useCallback(({ item }) => (
|
|
|
1946
1535
|
// Matches <ListItem> — leading circle + title/subtitle lines
|
|
1947
1536
|
<Skeleton.ListItem /> // props: showAvatar, showSubtitle, style
|
|
1948
1537
|
|
|
1949
|
-
// Repeated list/grid placeholder — for
|
|
1538
|
+
// Repeated list/grid placeholder — for FlatList while loading.
|
|
1950
1539
|
// columns=1 → stacked ListItemSkeleton; columns>1 → grid of MediaCardSkeleton.
|
|
1951
1540
|
<Skeleton.List count={6} /> // stacked list
|
|
1952
1541
|
<Skeleton.List count={6} columns={2} /> // 2-col card grid
|
|
@@ -1954,8 +1543,8 @@ const renderItem = useCallback(({ item }) => (
|
|
|
1954
1543
|
|
|
1955
1544
|
// Also exported as named components: MediaCardSkeleton, ListItemSkeleton, ListSkeleton
|
|
1956
1545
|
|
|
1957
|
-
// As a
|
|
1958
|
-
<
|
|
1546
|
+
// As a FlatList loading state
|
|
1547
|
+
<FlatList
|
|
1959
1548
|
data={loading ? [] : rows}
|
|
1960
1549
|
renderItem={renderItem}
|
|
1961
1550
|
ListEmptyComponent={loading ? <Skeleton.List count={8} /> : <EmptyState .../>}
|
|
@@ -3854,7 +3443,8 @@ export default function Screen() {
|
|
|
3854
3443
|
| subtitle | `string` | — | Secondary line under the title |
|
|
3855
3444
|
| onBack | `() => void` | — | Shows a back button on the left when provided |
|
|
3856
3445
|
| backIconName | `string` | `'chevron-left'` | Icon for the back button |
|
|
3857
|
-
|
|
|
3446
|
+
| iconName | `string` | — | Decorative icon left of title, after back button. Ignored when `left` is provided |
|
|
3447
|
+
| left | `ReactNode` | — | Custom left content — overrides the back button and `iconName` |
|
|
3858
3448
|
| right | `ReactNode` | — | Custom right content (actions) |
|
|
3859
3449
|
| titleAlign | `'auto' \| 'left' \| 'center'` | `'auto'` | `auto` = left on narrow, centered on wide |
|
|
3860
3450
|
| bordered | `boolean` | `true` | Hairline border underneath |
|
|
@@ -3869,6 +3459,13 @@ export default function Screen() {
|
|
|
3869
3459
|
onBack={navigation.goBack}
|
|
3870
3460
|
right={<IconButton iconName="more-horizontal" variant="text" onPress={openMenu} />}
|
|
3871
3461
|
/>
|
|
3462
|
+
|
|
3463
|
+
{/* With decorative icon and back button */}
|
|
3464
|
+
<AppHeader
|
|
3465
|
+
title="Perfil"
|
|
3466
|
+
iconName="user"
|
|
3467
|
+
onBack={() => router.back()}
|
|
3468
|
+
/>
|
|
3872
3469
|
```
|
|
3873
3470
|
|
|
3874
3471
|
---
|
|
@@ -4316,7 +3913,7 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
|
|
|
4316
3913
|
|
|
4317
3914
|
**When to use:** Image selection from the device library — product photos, profile avatars, tenant logos. Shows a dashed placeholder when empty, the selected image when filled, and a loading overlay while uploading.
|
|
4318
3915
|
|
|
4319
|
-
**Requires:** `
|
|
3916
|
+
**Requires:** `react-native-image-picker` installed in the consuming app (`pnpm add react-native-image-picker`). It is an optional peer dep — consumers who don't use `ImageUpload` never pull it. It is dynamically imported at tap time.
|
|
4320
3917
|
|
|
4321
3918
|
| Prop | Type | Default | Notes |
|
|
4322
3919
|
|------|------|---------|-------|
|
|
@@ -4330,9 +3927,9 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
|
|
|
4330
3927
|
| borderRadius | `number` | `RADIUS.lg` | — |
|
|
4331
3928
|
| resizeMode | `'cover' \| 'contain' \| 'stretch'` | `'cover'` | Image resize mode |
|
|
4332
3929
|
| disabled | `boolean` | `false` | Prevents pressing |
|
|
4333
|
-
| allowsEditing | `boolean` | `true` | When `true`, iOS opens the crop/editing screen after selecting an image. Set `false` to accept the image directly without cropping |
|
|
4334
3930
|
| style | `ViewStyle` | — | — |
|
|
4335
3931
|
| accessibilityLabel | `string` | — | — |
|
|
3932
|
+
| onPickerStarting | `() => void` | — | Called synchronously when user taps the upload area, before dynamic import and permission request |
|
|
4336
3933
|
|
|
4337
3934
|
**Examples:**
|
|
4338
3935
|
```tsx
|
|
@@ -4425,10 +4022,12 @@ const [icon, setIcon] = useState<string | null>(null)
|
|
|
4425
4022
|
|
|
4426
4023
|
**Import:** `import { useConfirmDialog } from '@retray-dev/ui-kit'`
|
|
4427
4024
|
|
|
4428
|
-
**When to use:** Manage `ConfirmDialog` state without boilerplate. Replaces the `
|
|
4025
|
+
**When to use:** Manage `ConfirmDialog` state without boilerplate. Replaces the `visible` + `loading` state pattern that each screen would otherwise duplicate.
|
|
4026
|
+
|
|
4027
|
+
**Note (v13):** The generic `T`, `target`, and `dialogProps` convenience object were removed. Returns `onConfirm`/`onCancel` directly. Uses refs for callback stability and `mountedRef` for safe async cleanup.
|
|
4429
4028
|
|
|
4430
4029
|
```ts
|
|
4431
|
-
function useConfirmDialog
|
|
4030
|
+
function useConfirmDialog(options: UseConfirmDialogOptions): UseConfirmDialogResult
|
|
4432
4031
|
|
|
4433
4032
|
interface UseConfirmDialogOptions {
|
|
4434
4033
|
onConfirm: () => void | Promise<void>
|
|
@@ -4441,253 +4040,32 @@ interface UseConfirmDialogOptions {
|
|
|
4441
4040
|
| Field | Type | Notes |
|
|
4442
4041
|
|-------|------|-------|
|
|
4443
4042
|
| visible | `boolean` | Pass to `ConfirmDialog.visible` |
|
|
4444
|
-
| target | `T \| null` | The value passed to `open()` — e.g. the item being deleted |
|
|
4445
4043
|
| loading | `boolean` | Pass to `ConfirmDialog.loading` |
|
|
4446
|
-
| open | `(
|
|
4447
|
-
|
|
|
4044
|
+
| open | `() => void` | Call to show the dialog |
|
|
4045
|
+
| onConfirm | `() => void` | Pass directly to `ConfirmDialog.onConfirm` |
|
|
4046
|
+
| onCancel | `() => void` | Pass directly to `ConfirmDialog.onCancel` |
|
|
4448
4047
|
|
|
4449
4048
|
**Example:**
|
|
4450
4049
|
```tsx
|
|
4451
|
-
const { open,
|
|
4050
|
+
const { visible, loading, open, onConfirm, onCancel } = useConfirmDialog({
|
|
4452
4051
|
onConfirm: async () => {
|
|
4453
|
-
await deleteProduct(
|
|
4052
|
+
await deleteProduct(productId)
|
|
4454
4053
|
toast.success('Product deleted')
|
|
4455
4054
|
},
|
|
4456
4055
|
})
|
|
4457
4056
|
|
|
4458
|
-
// Somewhere in your
|
|
4459
|
-
<ListItem
|
|
4460
|
-
title={product.name}
|
|
4461
|
-
rightIcon="trash-2"
|
|
4462
|
-
onPress={() => open(product)}
|
|
4463
|
-
/>
|
|
4464
|
-
|
|
4465
|
-
// Once in your screen's JSX:
|
|
4057
|
+
// Somewhere in your screen:
|
|
4466
4058
|
<ConfirmDialog
|
|
4467
|
-
|
|
4468
|
-
|
|
4059
|
+
visible={visible}
|
|
4060
|
+
title="Delete product?"
|
|
4061
|
+
subtitle="This action cannot be undone."
|
|
4469
4062
|
confirmLabel="Delete"
|
|
4470
4063
|
confirmVariant="destructive"
|
|
4471
|
-
{
|
|
4064
|
+
loading={loading}
|
|
4065
|
+
onConfirm={onConfirm}
|
|
4066
|
+
onCancel={onCancel}
|
|
4472
4067
|
/>
|
|
4473
4068
|
```
|
|
4474
4069
|
|
|
4475
4070
|
---
|
|
4476
4071
|
|
|
4477
|
-
## Icon System
|
|
4478
|
-
|
|
4479
|
-
The library ships a built-in icon resolver — pass any icon name string to components without manual imports.
|
|
4480
|
-
|
|
4481
|
-
### Supported families (priority order — first match wins)
|
|
4482
|
-
|
|
4483
|
-
| Priority | Family | Best for |
|
|
4484
|
-
|---|---|---|
|
|
4485
|
-
| 1 (highest) | `Feather` | Clean line icons, UI essentials |
|
|
4486
|
-
| 2 | `AntDesign` | Semantic UI icons |
|
|
4487
|
-
| 3 | `Entypo` | Social, media, navigation icons |
|
|
4488
|
-
| 4 | `FontAwesome5` | Wide coverage |
|
|
4489
|
-
| 5 | `MaterialIcons` | Material-style icons |
|
|
4490
|
-
| 6 (lowest) | `Ionicons` | Fallback |
|
|
4491
|
-
|
|
4492
|
-
Browse all available icons: **https://icons.expo.fyi**
|
|
4493
|
-
|
|
4494
|
-
### Standalone `Icon` component
|
|
4495
|
-
|
|
4496
|
-
```tsx
|
|
4497
|
-
import { Icon } from '@retray-dev/ui-kit'
|
|
4498
|
-
|
|
4499
|
-
<Icon name="home" size={24} color={colors.foreground} />
|
|
4500
|
-
<Icon name="star" size={20} color={colors.primary} />
|
|
4501
|
-
|
|
4502
|
-
// Force a specific family when same name exists in multiple families:
|
|
4503
|
-
<Icon name="heart" size={24} color="red" family="FontAwesome5" />
|
|
4504
|
-
```
|
|
4505
|
-
|
|
4506
|
-
**Props:**
|
|
4507
|
-
|
|
4508
|
-
| Prop | Type | Required | Notes |
|
|
4509
|
-
|------|------|----------|-------|
|
|
4510
|
-
| name | `string` | yes | Icon name (e.g. `"home"`, `"arrow-right"`) |
|
|
4511
|
-
| size | `number` | yes | Icon size in points |
|
|
4512
|
-
| color | `string` | yes | Icon color |
|
|
4513
|
-
| family | `IconFamily` | no | Force a specific family |
|
|
4514
|
-
|
|
4515
|
-
Returns `null` (no crash) if name not found in any family.
|
|
4516
|
-
|
|
4517
|
-
### `renderIcon` helper
|
|
4518
|
-
|
|
4519
|
-
```tsx
|
|
4520
|
-
import { renderIcon } from '@retray-dev/ui-kit'
|
|
4521
|
-
|
|
4522
|
-
// Returns ReactNode or null
|
|
4523
|
-
const icon = renderIcon('check', 18, colors.primary)
|
|
4524
|
-
```
|
|
4525
|
-
|
|
4526
|
-
### `getValidIconNames` utility
|
|
4527
|
-
|
|
4528
|
-
```tsx
|
|
4529
|
-
import { getValidIconNames } from '@retray-dev/ui-kit'
|
|
4530
|
-
|
|
4531
|
-
// All icon names across all configured families
|
|
4532
|
-
const allIcons: string[] = getValidIconNames()
|
|
4533
|
-
// => ["home", "user", "heart", "star", ...]
|
|
4534
|
-
|
|
4535
|
-
// Scoped to specific families (does not mutate global config)
|
|
4536
|
-
const featherOnly: string[] = getValidIconNames(['Feather'])
|
|
4537
|
-
```
|
|
4538
|
-
|
|
4539
|
-
Returns `string[]` — all resolvable icon names from the configured families. Respects `configureIconFamilies()` global config. The optional `families` parameter builds a temporary cache scoped to those families without mutating global state. Use to build icon pickers, search, and galleries.
|
|
4540
|
-
|
|
4541
|
-
### `iconName` props on components
|
|
4542
|
-
|
|
4543
|
-
All components with icon slots accept `iconName` — auto-resolved size and color:
|
|
4544
|
-
|
|
4545
|
-
| Component | Prop(s) | Slot | Default color |
|
|
4546
|
-
|-----------|---------|------|---------------|
|
|
4547
|
-
| `Button` | `iconName`, `iconColor` | Left or right of label | Variant label color |
|
|
4548
|
-
| `IconButton` | `iconName`, `iconColor` | Center | Variant foreground |
|
|
4549
|
-
| `Input` | `prefixIcon`, `prefixIconColor` | Before input | `foregroundMuted` |
|
|
4550
|
-
| `Input` | `suffixIcon`, `suffixIconColor` | After input | `foregroundMuted` |
|
|
4551
|
-
| `ListItem` | `leftIcon`, `leftIconColor` | Left 44×44 slot | `foreground` |
|
|
4552
|
-
| `ListItem` | `rightIcon`, `rightIconColor` | Right slot | `foregroundMuted` |
|
|
4553
|
-
| `Badge` | `iconName`, `iconColor` | Before label | Variant foreground |
|
|
4554
|
-
| `Toggle` | `iconName`, `iconColor` | When not pressed | `foregroundMuted` |
|
|
4555
|
-
| `Toggle` | `activeIconName`, `activeIconColor` | When pressed | `primary` |
|
|
4556
|
-
| `AlertBanner` | `iconName`, `iconColor` | Left of content | Variant title color |
|
|
4557
|
-
| `EmptyState` | `iconName`, `iconColor` | Center icon slot | `foregroundMuted` |
|
|
4558
|
-
| `Toast` | `iconName`, `iconColor` | Left of message | Variant text color |
|
|
4559
|
-
| `MediaCard` | `actionIconName` | Top-right of image | `#ffffff` |
|
|
4560
|
-
| `Chip` | `iconName` | Before label | Variant foreground |
|
|
4561
|
-
|
|
4562
|
-
**Precedence:** `iconName` always takes precedence over the corresponding `ReactNode` prop when both are supplied.
|
|
4563
|
-
|
|
4564
|
-
---
|
|
4565
|
-
|
|
4566
|
-
### `getResponsiveFontSize` utility
|
|
4567
|
-
|
|
4568
|
-
Calculates a stepped-down font size for long numeric strings (currency amounts, large numbers) that would overflow at full size.
|
|
4569
|
-
|
|
4570
|
-
```tsx
|
|
4571
|
-
import { getResponsiveFontSize } from '@retray-dev/ui-kit'
|
|
4572
|
-
|
|
4573
|
-
// Default steps: ≤10 chars → max, ≤12 → max-4, ≤14 → max-6, >14 → max-8
|
|
4574
|
-
const fontSize = getResponsiveFontSize(formatCOP(amount), 48)
|
|
4575
|
-
|
|
4576
|
-
<Text style={{ fontSize }} numberOfLines={1} adjustsFontSizeToFit>
|
|
4577
|
-
{formatCOP(amount)}
|
|
4578
|
-
</Text>
|
|
4579
|
-
```
|
|
4580
|
-
|
|
4581
|
-
**Signature:**
|
|
4582
|
-
```ts
|
|
4583
|
-
getResponsiveFontSize(
|
|
4584
|
-
text: string,
|
|
4585
|
-
maxSize: number,
|
|
4586
|
-
steps?: { maxLen: number; subtract: number }[]
|
|
4587
|
-
): number
|
|
4588
|
-
```
|
|
4589
|
-
|
|
4590
|
-
Custom steps example:
|
|
4591
|
-
```tsx
|
|
4592
|
-
getResponsiveFontSize(text, 48, [
|
|
4593
|
-
{ maxLen: 8, subtract: 0 },
|
|
4594
|
-
{ maxLen: 11, subtract: 6 },
|
|
4595
|
-
{ maxLen: 14, subtract: 10 },
|
|
4596
|
-
])
|
|
4597
|
-
```
|
|
4598
|
-
|
|
4599
|
-
**Alternative:** Use `<CurrencyDisplay autoScale maxFontSize={48} />` for currency values — no manual sizing needed.
|
|
4600
|
-
|
|
4601
|
-
---
|
|
4602
|
-
|
|
4603
|
-
## Hover Support (Web)
|
|
4604
|
-
|
|
4605
|
-
```tsx
|
|
4606
|
-
import { useHover } from '@retray-dev/ui-kit'
|
|
4607
|
-
|
|
4608
|
-
function MyHoverableComponent() {
|
|
4609
|
-
const { hovered, hoverHandlers } = useHover()
|
|
4610
|
-
|
|
4611
|
-
return (
|
|
4612
|
-
<View
|
|
4613
|
-
{...hoverHandlers}
|
|
4614
|
-
style={[
|
|
4615
|
-
styles.container,
|
|
4616
|
-
hovered && { backgroundColor: colors.surfaceStrong }
|
|
4617
|
-
]}
|
|
4618
|
-
/>
|
|
4619
|
-
)
|
|
4620
|
-
}
|
|
4621
|
-
```
|
|
4622
|
-
|
|
4623
|
-
- **Web:** Returns `{ hovered: boolean, hoverHandlers: { onMouseEnter, onMouseLeave } }`
|
|
4624
|
-
- **Native:** Always returns `{ hovered: false, hoverHandlers: {} }` — no-op, no crashes
|
|
4625
|
-
|
|
4626
|
-
Built-in web hover: `MediaCard` (shadow lift), interactive components use this internally.
|
|
4627
|
-
|
|
4628
|
-
---
|
|
4629
|
-
|
|
4630
|
-
## Design System Conventions
|
|
4631
|
-
|
|
4632
|
-
### Touch targets
|
|
4633
|
-
All interactive elements maintain ≥44pt touch height per Apple HIG:
|
|
4634
|
-
- Button md: 48px, sm: 40px, lg: 56px
|
|
4635
|
-
- Input / Textarea: 56px (14px vertical padding)
|
|
4636
|
-
- IconButton md: 44px, lg: 52px
|
|
4637
|
-
- Checkbox: 24×24px box
|
|
4638
|
-
- Switch: 30px track height
|
|
4639
|
-
- MonthPicker arrows: 44×44px
|
|
4640
|
-
|
|
4641
|
-
### Haptic patterns
|
|
4642
|
-
- `impactLight` — Button, Card (press), Sheet open, Toast, MediaCard action
|
|
4643
|
-
- `selectionAsync` — Checkbox, Switch, Toggle, RadioGroup, Select, Slider steps, Accordion, ListItem, Chip, CategoryStrip, Tabs, MonthPicker
|
|
4644
|
-
- `notificationSuccess` — Toast `success`
|
|
4645
|
-
- `notificationError` — Toast `destructive`, Toast `warning`
|
|
4646
|
-
|
|
4647
|
-
### Animation press scales
|
|
4648
|
-
- `Button` → 0.95 (strong spring feedback)
|
|
4649
|
-
- `Card` → 0.98 (subtle, appropriate for large surfaces)
|
|
4650
|
-
- `ListItem` → 0.97 (between — medium row targets)
|
|
4651
|
-
- `MediaCard` → 0.98 (large surface)
|
|
4652
|
-
- `IconButton` → 0.95
|
|
4653
|
-
- `Chip`, `CategoryStrip chip` → 0.95
|
|
4654
|
-
|
|
4655
|
-
### Platform differences
|
|
4656
|
-
- `useNativeDriver` — `Platform.OS !== 'web'` everywhere (native driver disabled on web)
|
|
4657
|
-
- `Select` — wheel modal on iOS, dialog on Android, `<select>` on web
|
|
4658
|
-
- `Toast` — full width on mobile, 400px centered on web
|
|
4659
|
-
- Hover states — web only via `useHover()`
|
|
4660
|
-
- `Skeleton` shimmer highlight — adapts opacity for light/dark mode
|
|
4661
|
-
|
|
4662
|
-
### Dynamic Type
|
|
4663
|
-
All `Text` and `TextInput` components have `allowFontScaling={true}` — respects user font size accessibility settings.
|
|
4664
|
-
|
|
4665
|
-
### Scaling utilities (internal)
|
|
4666
|
-
Used internally — not exported. `s()` horizontal scale, `vs()` vertical scale, `ms()` moderate scale relative to 350×680 base.
|
|
4667
|
-
|
|
4668
|
-
---
|
|
4669
|
-
|
|
4670
|
-
## Full Composition Examples
|
|
4671
|
-
|
|
4672
|
-
The package includes **EXAMPLES.md** with complete, working code for 4 real-world app screens:
|
|
4673
|
-
|
|
4674
|
-
1. **Finance Dashboard** — MonthPicker, CurrencyDisplay, Progress, CategoryStrip, ListItem, DetailRow
|
|
4675
|
-
2. **Edit Profile** — Avatar, Badge, Input, Select, Switch, Card, AlertBanner
|
|
4676
|
-
3. **Onboarding Flow** — Badge, Progress, Input, RadioGroup, EmptyState (multi-step wizard)
|
|
4677
|
-
4. **Settings Screen** — MenuGroup, ListGroup, Form, MenuItem, ListItem, compound component patterns
|
|
4678
|
-
|
|
4679
|
-
**For AI agents:** Import EXAMPLES.md from the package for full source code and patterns:
|
|
4680
|
-
|
|
4681
|
-
```markdown
|
|
4682
|
-
## UI Kit Composition Examples
|
|
4683
|
-
@./node_modules/@retray-dev/ui-kit/EXAMPLES.md
|
|
4684
|
-
```
|
|
4685
|
-
|
|
4686
|
-
Each example includes:
|
|
4687
|
-
- Complete component code (copy-paste ready)
|
|
4688
|
-
- State management setup
|
|
4689
|
-
- Proper imports and theme usage
|
|
4690
|
-
- Common patterns (spacing, colors, navigation, toast feedback)
|
|
4691
|
-
- StyleSheet definitions
|
|
4692
|
-
|
|
4693
|
-
**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.
|