@retray-dev/ui-kit 13.0.0 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONSUMER.md +24 -9
- package/README.md +9 -10
- package/{COMPONENTS.md → SKILL.md} +296 -860
- package/dist/Accordion.d.mts +2 -0
- package/dist/Accordion.d.ts +2 -0
- package/dist/Accordion.js +1 -0
- package/dist/Accordion.mjs +1 -2
- package/dist/AlertBanner.mjs +0 -1
- package/dist/AppHeader.d.mts +5 -2
- package/dist/AppHeader.d.ts +5 -2
- package/dist/AppHeader.js +8 -4
- package/dist/AppHeader.mjs +1 -2
- package/dist/Avatar.mjs +0 -1
- package/dist/Badge.mjs +0 -1
- package/dist/Button.mjs +0 -1
- package/dist/ButtonGroup.mjs +0 -1
- package/dist/Card.mjs +0 -1
- package/dist/CategoryStrip.mjs +0 -1
- package/dist/Checkbox.d.mts +2 -1
- package/dist/Checkbox.d.ts +2 -1
- package/dist/Checkbox.js +3 -1
- package/dist/Checkbox.mjs +1 -2
- package/dist/Chip.mjs +0 -1
- package/dist/ConfirmDialog.d.mts +2 -1
- package/dist/ConfirmDialog.d.ts +2 -1
- package/dist/ConfirmDialog.js +26 -14
- package/dist/ConfirmDialog.mjs +1 -2
- package/dist/CurrencyDisplay.mjs +0 -1
- package/dist/CurrencyInput.mjs +0 -1
- package/dist/DetailRow.mjs +0 -1
- package/dist/EmptyState.mjs +0 -1
- package/dist/ErrorBoundary.mjs +0 -1
- package/dist/Form.mjs +0 -1
- package/dist/HolographicCard.mjs +0 -1
- package/dist/IconButton.mjs +0 -1
- package/dist/IconPicker.mjs +0 -1
- package/dist/ImageUpload.d.mts +1 -3
- package/dist/ImageUpload.d.ts +1 -3
- package/dist/ImageUpload.js +27 -26
- package/dist/ImageUpload.mjs +1 -2
- package/dist/ImageViewer.mjs +0 -1
- package/dist/Input.mjs +0 -1
- package/dist/LabelValue.mjs +0 -1
- package/dist/ListGroup.mjs +0 -1
- package/dist/ListItem.d.mts +2 -1
- package/dist/ListItem.d.ts +2 -1
- package/dist/ListItem.js +3 -1
- package/dist/ListItem.mjs +1 -2
- package/dist/MediaCard.mjs +0 -1
- package/dist/MenuGroup.mjs +0 -1
- package/dist/MenuItem.d.mts +2 -1
- package/dist/MenuItem.d.ts +2 -1
- package/dist/MenuItem.js +3 -1
- package/dist/MenuItem.mjs +1 -2
- package/dist/MonthPicker.mjs +0 -1
- package/dist/NumberStepper.d.mts +2 -1
- package/dist/NumberStepper.d.ts +2 -1
- package/dist/NumberStepper.js +4 -1
- package/dist/NumberStepper.mjs +1 -2
- package/dist/PagerDots.mjs +0 -1
- package/dist/Pressable.mjs +0 -1
- package/dist/PricingCard.mjs +0 -1
- package/dist/Progress.mjs +0 -1
- package/dist/RadioGroup.mjs +0 -1
- package/dist/RetrayProvider.mjs +0 -1
- package/dist/Select.d.mts +2 -1
- package/dist/Select.d.ts +2 -1
- package/dist/Select.js +3 -1
- package/dist/Select.mjs +1 -2
- package/dist/SelectableCard.mjs +0 -1
- package/dist/SelectableGrid.js +0 -1
- package/dist/SelectableGrid.mjs +1 -2
- package/dist/Separator.mjs +0 -1
- package/dist/Sheet.d.mts +1 -1
- package/dist/Sheet.d.ts +1 -1
- package/dist/Sheet.js +26 -13
- package/dist/Sheet.mjs +1 -2
- package/dist/SheetSelect.mjs +0 -1
- package/dist/Skeleton.mjs +0 -1
- package/dist/Slider.d.mts +2 -1
- package/dist/Slider.d.ts +2 -1
- package/dist/Slider.js +2 -0
- package/dist/Slider.mjs +1 -2
- package/dist/Spinner.mjs +0 -1
- package/dist/Stats.mjs +0 -1
- package/dist/Switch.d.mts +2 -1
- package/dist/Switch.d.ts +2 -1
- package/dist/Switch.js +2 -1
- package/dist/Switch.mjs +1 -2
- package/dist/TabBar.mjs +0 -1
- package/dist/Tabs.mjs +0 -1
- package/dist/Text.mjs +0 -1
- package/dist/Textarea.mjs +0 -1
- package/dist/Toast.d.mts +12 -10
- package/dist/Toast.d.ts +12 -10
- package/dist/Toast.mjs +0 -1
- package/dist/Toggle.mjs +0 -1
- package/dist/{chunk-TETMEKZE.mjs → chunk-2QXJDRVU.mjs} +4 -1
- package/dist/{chunk-CBIZLRYH.mjs → chunk-422IVD3H.mjs} +1 -0
- package/dist/{chunk-4ZO5PTKF.mjs → chunk-77UOVFIS.mjs} +3 -1
- package/dist/{chunk-2QOHHBJC.mjs → chunk-7BZJRB77.mjs} +25 -15
- package/dist/{chunk-UOKFSFNJ.mjs → chunk-C5ZRMR2E.mjs} +2 -0
- package/dist/{chunk-E4EQSCKR.mjs → chunk-COA2YZOX.mjs} +3 -1
- package/dist/{chunk-6QLBHUEG.mjs → chunk-CZN6L2QU.mjs} +3 -1
- package/dist/{chunk-BTUW5LSG.mjs → chunk-E2PONRJG.mjs} +2 -1
- package/dist/{chunk-6CR4S6W2.mjs → chunk-H6MQL7PS.mjs} +9 -4
- package/dist/{chunk-EROPDCB5.mjs → chunk-HHOOFDBA.mjs} +26 -21
- package/dist/{chunk-URIH43IJ.mjs → chunk-IDVUZIVY.mjs} +3 -1
- package/dist/{chunk-MP7GLMIR.mjs → chunk-NPCBNGNE.mjs} +0 -1
- package/dist/{chunk-V2ZB2XNS.mjs → chunk-UMZTPUB3.mjs} +27 -15
- package/dist/fonts.mjs +0 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +108 -64
- package/dist/index.mjs +13 -14
- package/package.json +14 -12
- package/src/components/Accordion/Accordion.tsx +3 -0
- package/src/components/AppHeader/AppHeader.tsx +25 -10
- package/src/components/Checkbox/Checkbox.tsx +3 -0
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +30 -14
- package/src/components/ImageUpload/ImageUpload.tsx +33 -25
- package/src/components/ListItem/ListItem.tsx +3 -0
- package/src/components/MenuItem/MenuItem.tsx +3 -0
- package/src/components/NumberStepper/NumberStepper.tsx +4 -0
- package/src/components/Select/Select.tsx +3 -0
- package/src/components/SelectableGrid/SelectableGrid.tsx +0 -1
- package/src/components/Sheet/Sheet.tsx +27 -14
- package/src/components/Sheet/index.ts +2 -2
- package/src/components/Slider/Slider.tsx +3 -0
- package/src/components/Switch/Switch.tsx +3 -1
- package/src/components/Text/Text.tsx +1 -0
- package/dist/chunk-Y6FXYEAI.mjs +0 -8
|
@@ -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
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
| Peer | Required? | Used by | Notes |
|
|
88
|
-
|------|-----------|---------|-------|
|
|
89
|
-
| `pressto` | **Required** | Every interactive component | Press-scale animations (`src/utils/pressable.ts`). Static import via the barrel — omitting it crashes the module load, not just one component. |
|
|
90
|
-
| `sonner-native` | **Required** | `Toast` | Also pulls `react-native-svg` + `react-native-screens`. |
|
|
91
|
-
| `react-native-reanimated` (≥4.0) | **Required** | Animations, `Sheet`, pressables | Needs the `react-native-worklets/plugin` Babel plugin. |
|
|
92
|
-
| `react-native-gesture-handler` | **Required** | `Sheet`, pressables | Wrap app in `GestureHandlerRootView`. |
|
|
93
|
-
| `@gorhom/bottom-sheet` (≥5.2.0) | **Required** | `Sheet`, `ConfirmDialog` | 5.1.x crashes on Reanimated v4. |
|
|
94
|
-
| `expo-haptics` | **Required** | Haptic feedback (all interactions) | Web-safe wrapper, no-op on web. |
|
|
95
|
-
| `expo-font` | **Required** | All `Text` | Load `SohneFonts` at app root before rendering. |
|
|
96
|
-
| `expo-linear-gradient` | **Required** | `Skeleton` shimmer | |
|
|
97
|
-
| `@react-native-picker/picker` | **Required** | `Select` | |
|
|
98
|
-
| `@react-native-community/slider` | **Required** | `Slider` | |
|
|
99
|
-
| `@expo/vector-icons` | **Required** | Icons everywhere | |
|
|
100
|
-
| `react-native-size-matters` | **Required** | Responsive scaling | |
|
|
101
|
-
| `react-native-ease` | **Required** | `Checkbox`, `Chip`, `CategoryStrip`, `Switch`, `RadioGroup`, `Toggle` | Declarative `EaseView` animation layer. Static import in source — omitting it crashes the module load. `pnpm add react-native-ease` |
|
|
102
|
-
|
|
103
|
-
| `@shopify/react-native-skia` + `expo-sensors` | **Optional** | Deep-import `HolographicCard` only | `pnpm add @shopify/react-native-skia expo-sensors` |
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
### Troubleshooting: `react-native-screens` codegen on RN 0.83
|
|
108
|
-
|
|
109
|
-
Toast pulls `sonner-native`, which imports `react-native-screens` via its `src/*` path. On **React Native 0.83** that source trips a `@react-native/codegen` parse error (`CT.WithDefault` not parseable). It is a `react-native-screens` issue, not the kit — but since Toast surfaces it, add this resolver to your `metro.config.js` to force the compiled `lib/commonjs` build:
|
|
110
|
-
|
|
111
|
-
```js
|
|
112
|
-
// metro.config.js
|
|
113
|
-
const { getDefaultConfig } = require('expo/metro-config')
|
|
114
|
-
const config = getDefaultConfig(__dirname)
|
|
115
|
-
|
|
116
|
-
const defaultResolveRequest = config.resolver.resolveRequest
|
|
117
|
-
config.resolver.resolveRequest = (context, moduleName, platform) => {
|
|
118
|
-
// Force react-native-screens to its compiled output, bypassing the src codegen bug.
|
|
119
|
-
if (moduleName.startsWith('react-native-screens/src')) {
|
|
120
|
-
moduleName = moduleName.replace('react-native-screens/src', 'react-native-screens/lib/commonjs')
|
|
121
|
-
}
|
|
122
|
-
return (defaultResolveRequest ?? context.resolveRequest)(context, moduleName, platform)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
module.exports = config
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Typography — Sohne (Required)
|
|
129
|
-
|
|
130
|
-
All components use **Sohne** as the font family. You **must** load it before rendering any UI kit component.
|
|
131
|
-
|
|
132
|
-
### How it works
|
|
133
|
-
|
|
134
|
-
1. When you install `@retray-dev/ui-kit`, the **postinstall script** automatically copies 28 `.otf` font files to `assets/fonts/sohne/` in your project root
|
|
135
|
-
2. You must define `SohneFonts` with static `require()` calls in your `App.tsx` (see boilerplate below)
|
|
136
|
-
3. Pass it to `expo-font`'s `useFonts()` hook at your app root
|
|
137
|
-
4. All library components reference fonts by family name (e.g., `fontFamily: 'Sohne-SemiBold'`)
|
|
138
|
-
|
|
139
|
-
### SohneFonts boilerplate — copy this into your App.tsx
|
|
140
|
-
|
|
141
|
-
```tsx
|
|
142
|
-
import { useFonts } from 'expo-font'
|
|
143
|
-
|
|
144
|
-
// Fonts copied to assets/fonts/sohne/ by @retray-dev/ui-kit postinstall
|
|
145
|
-
const SohneFonts = {
|
|
146
|
-
'Sohne-ExtraLight': require('./assets/fonts/sohne/Sohne-ExtraLight.otf'),
|
|
147
|
-
'Sohne-ExtraLightItalic': require('./assets/fonts/sohne/Sohne-ExtraLightItalic.otf'),
|
|
148
|
-
'Sohne-Light': require('./assets/fonts/sohne/Sohne-Light.otf'),
|
|
149
|
-
'Sohne-LightItalic': require('./assets/fonts/sohne/Sohne-LightItalic.otf'),
|
|
150
|
-
'Sohne-Regular': require('./assets/fonts/sohne/Sohne-Regular.otf'),
|
|
151
|
-
'Sohne-Italic': require('./assets/fonts/sohne/Sohne-Italic.otf'),
|
|
152
|
-
'Sohne-Medium': require('./assets/fonts/sohne/Sohne-Medium.otf'),
|
|
153
|
-
'Sohne-MediumItalic': require('./assets/fonts/sohne/Sohne-MediumItalic.otf'),
|
|
154
|
-
'Sohne-SemiBold': require('./assets/fonts/sohne/Sohne-SemiBold.otf'),
|
|
155
|
-
'Sohne-SemiBoldItalic': require('./assets/fonts/sohne/Sohne-SemiBoldItalic.otf'),
|
|
156
|
-
'Sohne-Bold': require('./assets/fonts/sohne/Sohne-Bold.otf'),
|
|
157
|
-
'Sohne-BoldItalic': require('./assets/fonts/sohne/Sohne-BoldItalic.otf'),
|
|
158
|
-
'Sohne-ExtraBold': require('./assets/fonts/sohne/Sohne-ExtraBold.otf'),
|
|
159
|
-
'Sohne-ExtraBoldItalic': require('./assets/fonts/sohne/Sohne-ExtraBoldItalic.otf'),
|
|
160
|
-
'SohneMono-ExtraLight': require('./assets/fonts/sohne/SohneMono-ExtraLight.otf'),
|
|
161
|
-
'SohneMono-ExtraLightItalic': require('./assets/fonts/sohne/SohneMono-ExtraLightItalic.otf'),
|
|
162
|
-
'SohneMono-Light': require('./assets/fonts/sohne/SohneMono-Light.otf'),
|
|
163
|
-
'SohneMono-LightItalic': require('./assets/fonts/sohne/SohneMono-LightItalic.otf'),
|
|
164
|
-
'SohneMono-Regular': require('./assets/fonts/sohne/SohneMono-Regular.otf'),
|
|
165
|
-
'SohneMono-Italic': require('./assets/fonts/sohne/SohneMono-Italic.otf'),
|
|
166
|
-
'SohneMono-Medium': require('./assets/fonts/sohne/SohneMono-Medium.otf'),
|
|
167
|
-
'SohneMono-MediumItalic': require('./assets/fonts/sohne/SohneMono-MediumItalic.otf'),
|
|
168
|
-
'SohneMono-SemiBold': require('./assets/fonts/sohne/SohneMono-SemiBold.otf'),
|
|
169
|
-
'SohneMono-SemiBoldItalic': require('./assets/fonts/sohne/SohneMono-SemiBoldItalic.otf'),
|
|
170
|
-
'SohneMono-Bold': require('./assets/fonts/sohne/SohneMono-Bold.otf'),
|
|
171
|
-
'SohneMono-BoldItalic': require('./assets/fonts/sohne/SohneMono-BoldItalic.otf'),
|
|
172
|
-
'SohneMono-ExtraBold': require('./assets/fonts/sohne/SohneMono-ExtraBold.otf'),
|
|
173
|
-
'SohneMono-ExtraBoldItalic': require('./assets/fonts/sohne/SohneMono-ExtraBoldItalic.otf'),
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export default function App() {
|
|
177
|
-
const [fontsLoaded] = useFonts(SohneFonts)
|
|
178
|
-
if (!fontsLoaded) return null
|
|
179
|
-
return (
|
|
180
|
-
// ... your providers and app
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### .gitignore recommendation
|
|
186
|
-
|
|
187
|
-
Fonts are copied to `assets/fonts/sohne/` on install. You can either:
|
|
188
|
-
- **Commit them** (no network needed during CI builds)
|
|
189
|
-
- **Ignore them** (re-copied on every `pnpm install`)
|
|
190
|
-
|
|
191
|
-
```gitignore
|
|
192
|
-
# Sohne fonts — copied by @retray-dev/ui-kit postinstall
|
|
193
|
-
# Either commit these or ignore them (re-copied on install)
|
|
194
|
-
assets/fonts/sohne/
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
**Included weights (28 files):**
|
|
198
|
-
- Sohne: `Sohne-ExtraLight`, `Sohne-Light`, `Sohne-Regular`, `Sohne-Medium`, `Sohne-SemiBold`, `Sohne-Bold`, `Sohne-ExtraBold` + italic variants
|
|
199
|
-
- SohneMono: `SohneMono-ExtraLight`, `SohneMono-Light`, `SohneMono-Regular`, `SohneMono-Medium`, `SohneMono-SemiBold`, `SohneMono-Bold`, `SohneMono-ExtraBold` + italic variants
|
|
200
|
-
|
|
201
|
-
Pair with `expo-splash-screen` in production:
|
|
202
|
-
```tsx
|
|
203
|
-
import * as SplashScreen from 'expo-splash-screen'
|
|
204
|
-
SplashScreen.preventAutoHideAsync()
|
|
205
|
-
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
if (fontsLoaded) SplashScreen.hideAsync()
|
|
208
|
-
}, [fontsLoaded])
|
|
46
|
+
plugins: ['react-native-worklets/plugin']
|
|
47
|
+
// NOT react-native-reanimated/plugin
|
|
209
48
|
```
|
|
210
49
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
## Theme System
|
|
214
|
-
|
|
215
|
-
### ThemeProvider Props
|
|
216
|
-
|
|
217
|
-
| Prop | Type | Default | Notes |
|
|
218
|
-
|------|------|---------|-------|
|
|
219
|
-
| colorScheme | `'light' \| 'dark' \| 'system'` | `'system'` | `'system'` auto-detects device setting and updates when it changes |
|
|
220
|
-
| theme | `{ light?: Partial<ThemeColors>, dark?: Partial<ThemeColors> }` | — | Override any subset of the 12 public tokens per scheme |
|
|
221
|
-
|
|
222
|
-
**Custom theme example:**
|
|
223
|
-
```tsx
|
|
224
|
-
const myTheme = {
|
|
225
|
-
light: { primary: '#ff385c', primaryForeground: '#ffffff' },
|
|
226
|
-
dark: { primary: '#ff385c', primaryForeground: '#ffffff' },
|
|
227
|
-
}
|
|
228
|
-
<ThemeProvider theme={myTheme} colorScheme="system">
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### useTheme Hook
|
|
232
|
-
|
|
233
|
-
```tsx
|
|
234
|
-
import { useTheme } from '@retray-dev/ui-kit'
|
|
235
|
-
|
|
236
|
-
function MyComponent() {
|
|
237
|
-
const { colors, colorScheme } = useTheme()
|
|
238
|
-
return <View style={{ backgroundColor: colors.background }} />
|
|
239
|
-
}
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
Returns `colors` (full `ResolvedColors` palette) and `colorScheme` (`'light' | 'dark'`).
|
|
50
|
+
### Fonts (Sohne)
|
|
51
|
+
Postinstall copies 28 `.otf` to `assets/fonts/sohne/`. Consumer defines static `require()` calls in App.tsx with `expo-font`'s `useFonts()`.
|
|
243
52
|
|
|
244
53
|
---
|
|
245
54
|
|
|
246
|
-
##
|
|
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
|
|
162
|
+
**SHADOWS — Cross-platform**
|
|
399
163
|
|
|
400
|
-
| Key |
|
|
401
|
-
|
|
402
|
-
| `sm` |
|
|
403
|
-
| `md` |
|
|
404
|
-
| `lg` |
|
|
405
|
-
| `xl` |
|
|
406
|
-
|
|
407
|
-
```tsx
|
|
408
|
-
<View style={[styles.card, SHADOWS.sm]} />
|
|
409
|
-
// Hover state
|
|
410
|
-
<View style={[styles.card, hovered ? SHADOWS.md : SHADOWS.sm]} />
|
|
411
|
-
```
|
|
164
|
+
| Key | opacity | radius | elevation | Use |
|
|
165
|
+
|-----|---------|--------|-----------|-----|
|
|
166
|
+
| `sm` | 0.06 | 4 | 2 | Default card |
|
|
167
|
+
| `md` | 0.10 | 8 | 5 | Hover float |
|
|
168
|
+
| `lg` | 0.16 | 16 | 10 | Modals |
|
|
169
|
+
| `xl` | 0.24 | 24 | 18 | High-elevation |
|
|
412
170
|
|
|
413
|
-
|
|
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
|
-
```
|
|
422
|
-
|
|
423
|
-
### TYPOGRAPHY — 16 Airbnb-aligned variants
|
|
177
|
+
**TYPOGRAPHY — 17 variants**
|
|
424
178
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
|
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
|
-
```
|
|
189
|
+
| `body-sm` | 14 | 400 | 20 | 0 | Secondary body |
|
|
190
|
+
| `caption` | 14 | 500 | 18 | 0 | Input labels |
|
|
191
|
+
| `caption-sm` | 13 | 400 | 16 | 0 | Timestamps |
|
|
192
|
+
| `badge-text` | 11 | 600 | 14 | 0 | Badge labels |
|
|
193
|
+
| `badge-text-md` | 13 | 600 | 16 | 0 | Badge labels (md size) |
|
|
194
|
+
| `micro-label` | 12 | 700 | 16 | 0 | Overlines |
|
|
195
|
+
| `uppercase-tag` | 11 | 700 | 14 | 0.6 | Decorative tags (auto-uppercase) |
|
|
196
|
+
| `button-lg` | 16 | 500 | 22 | 0 | Button (md/lg) |
|
|
197
|
+
| `button-sm` | 14 | 500 | 18 | 0 | Button (sm) |
|
|
459
198
|
|
|
460
199
|
---
|
|
461
200
|
|
|
462
|
-
##
|
|
463
|
-
|
|
464
|
-
### Breaking Changes
|
|
465
|
-
|
|
466
|
-
**Button variants renamed:**
|
|
467
|
-
| v4 | v5 |
|
|
468
|
-
|----|-----|
|
|
469
|
-
| `outline` | `secondary` |
|
|
470
|
-
| `ghost` | `text` |
|
|
471
|
-
| `secondary` (filled gray) | removed — use Card/surface instead |
|
|
472
|
-
|
|
473
|
-
**Text variants replaced:**
|
|
474
|
-
| v4 | v5 equivalent |
|
|
475
|
-
|----|---------------|
|
|
476
|
-
| `h1` | `display-xl` |
|
|
477
|
-
| `h2` | `display-lg` or `display-md` |
|
|
478
|
-
| `h3` | `display-sm` |
|
|
479
|
-
| `body` | `body-md` |
|
|
480
|
-
| `label` | `title-sm` or `caption` |
|
|
481
|
-
| `caption` | `caption-sm` |
|
|
482
|
-
|
|
483
|
-
**Theme tokens changed:**
|
|
484
|
-
| v4 | v5 |
|
|
485
|
-
|----|-----|
|
|
486
|
-
| `secondary`, `secondaryForeground` | removed (use `surface` derived token) |
|
|
487
|
-
| `accent`, `accentForeground` | removed (use `surfaceStrong`) |
|
|
488
|
-
| `muted` | removed → `surface` (derived) |
|
|
489
|
-
| `mutedForeground` | removed → `foregroundSubtle` / `foregroundMuted` (derived) |
|
|
490
|
-
| Added | `warning`, `warningForeground` |
|
|
491
|
-
| Added (derived) | `warningTint`, `warningBorder` |
|
|
492
|
-
|
|
493
|
-
**IconButton variant:**
|
|
494
|
-
| v4 | v5 |
|
|
495
|
-
|----|-----|
|
|
496
|
-
| `ghost` | `text` |
|
|
497
|
-
|
|
498
|
-
---
|
|
499
|
-
|
|
500
|
-
## Migration Guide: v5 → v6
|
|
501
|
-
|
|
502
|
-
### New Components
|
|
503
|
-
|
|
504
|
-
**`MenuItem`** — Navigation row with icon, label, and optional right slot (`rightRender`). Replaces ad-hoc `ListItem` usage for settings/nav menus. Zero horizontal padding by design — consumer controls spacing.
|
|
201
|
+
## Icons
|
|
505
202
|
|
|
203
|
+
### Icon Component
|
|
506
204
|
```tsx
|
|
507
|
-
import {
|
|
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'` |
|
|
213
|
+
**Feather is first** — most names resolve there (outlined style). Names with `-outline` suffix resolve to Ionicons (no Feather collision).
|
|
520
214
|
|
|
521
|
-
|
|
522
|
-
|
|
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
|
|
248
|
+
## Animations & Interactions
|
|
556
249
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
|
562
|
-
|
|
563
|
-
| `
|
|
564
|
-
| `
|
|
565
|
-
| `title-md` | 16px / 600 | 17px / 600 | Now visibly distinct from title-sm |
|
|
566
|
-
| `title-sm` | 16px / 500 | 15px / 500 | Was same px as title-md |
|
|
567
|
-
| `uppercase-tag` | 10px / 0.8 letterSpacing | 11px / 0.6 | Below Apple HIG 11pt minimum |
|
|
568
|
-
| `badge-text-md` | (missing) | 13px / 600 | New canonical token for Badge md |
|
|
569
|
-
|
|
570
|
-
**Default color values changed (WCAG AA fixes):**
|
|
571
|
-
|
|
572
|
-
| Token | v6 | v7 | WCAG impact |
|
|
573
|
-
|-------|----|----|-------------|
|
|
574
|
-
| `foregroundMuted` opacity | 0.38 (~#ababab) | 0.62 (~#767676) | 2.2:1 ❌ → 4.5:1 ✓ |
|
|
575
|
-
| `foregroundSubtle` opacity | 0.55 (~#858585) | 0.70 (~#646464) | 3.5:1 ❌ → 5.9:1 ✓ |
|
|
576
|
-
| `warning` (light) | `#e67e00` | `#9a5200` | 2.86:1 ❌ → 5.86:1 ✓ |
|
|
577
|
-
| `warningForeground` (dark) | `#ffffff` | `#0f0f0f` | Dark text on amber, 8.6:1 ✓ |
|
|
578
|
-
| `destructive` (light) | `#e53935` | `#c72828` | 4.22:1 ❌ → 5.59:1 ✓ |
|
|
579
|
-
| `accent` (light) | `= primary` | `#d4561d` | Explicit brand accent |
|
|
580
|
-
| `accent` (dark) | `= primary` | `#e87645` | Warm accent for dark surfaces |
|
|
250
|
+
### Pressables (pressto)
|
|
251
|
+
| Pressable | Scale | Used by |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| `PressableButton` | 0.95 | Button, IconButton, Toggle, Checkbox |
|
|
254
|
+
| `PressableCard` | 0.98 | Card, MediaCard, Stats, Pressable |
|
|
255
|
+
| `PressableRow` | 0.97 | ListItem, MenuItem |
|
|
256
|
+
| `PressableChip` | 0.94 | Chip |
|
|
257
|
+
| `PressableTab` | 0.95 | Tabs triggers |
|
|
581
258
|
|
|
582
|
-
|
|
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
|
|
607
|
-
|
|
608
|
-
**1. New required peer dependency: `sonner-native`**
|
|
609
|
-
Toast (`ToastProvider` / `toast` / `useToast`) imports `sonner-native`, but it was never declared as a peer — installs silently lacked it and Metro failed with `Unable to resolve "sonner-native"`. It is now a declared peer. Install it:
|
|
610
|
-
```bash
|
|
611
|
-
pnpm add sonner-native
|
|
612
|
-
```
|
|
613
|
-
(You already need its companions `react-native-svg` and `react-native-screens`.)
|
|
614
|
-
|
|
615
|
-
**2. `@gorhom/bottom-sheet` peer range tightened to `>=5.2.0`**
|
|
616
|
-
5.1.x crashes with Reanimated v4 (`useWorkletCallback is not a function`), which broke `Sheet`, `ConfirmDialog`, and `Select` on open. The range no longer admits broken versions. Pin `5.2.8` if you also use Worklets `0.5.x`.
|
|
617
|
-
|
|
618
|
-
**3. `./fonts` stays at `src/fonts.ts` (unchanged)**
|
|
619
|
-
The `./fonts` export deliberately points at `src/fonts.ts`, not `dist`. The `.otf` files ship as raw assets in `src/assets/fonts/` and are resolved by Metro at your build time via `require()`. Compiling this entry to `dist` and repointing the `require()` paths breaks Metro asset resolution under pnpm symlinked workspaces (`requiring unknown module ../src/assets/fonts/...`), so it is intentionally left as source. No API change — import as before:
|
|
620
|
-
```ts
|
|
621
|
-
import { SohneFonts } from '@retray-dev/ui-kit/fonts'
|
|
622
|
-
```
|
|
278
|
+
## Sheets (BottomSheetModal)
|
|
623
279
|
|
|
624
|
-
###
|
|
280
|
+
### Strict rules (from @gorhom/bottom-sheet)
|
|
281
|
+
1. **Always `BottomSheetModal`** — never `BottomSheet` base. `present()` in handler directly (not `useEffect`).
|
|
282
|
+
2. **`enableDynamicSizing`** — no `snapPoints` (mutually exclusive)
|
|
283
|
+
3. **`topInset={insets.top}`** — prevents notch overlap
|
|
284
|
+
4. **`keyboardBehavior="interactive"`** + **`android_keyboardInputMode="adjustPan"`** — keyboard
|
|
285
|
+
5. **`SheetTextInput`** inside sheets — never `<TextInput />`
|
|
286
|
+
6. **`onDismiss`** for cleanup, not `onClose`
|
|
287
|
+
7. **`renderBackdrop`** wrapped in `useCallback`
|
|
288
|
+
8. **`enableBlurKeyboardOnGesture={true}`** — dismiss keyboard on drag
|
|
289
|
+
9. **`BottomSheetModalProvider`** re-exported, included in `RetrayProvider`
|
|
625
290
|
|
|
626
|
-
|
|
627
|
-
|
|
291
|
+
### Input inside Sheet
|
|
292
|
+
Use `<Input sheetMode />` — transparently swaps to `BottomSheetTextInput`. For low-level: `SheetTextInput`.
|
|
628
293
|
|
|
629
|
-
|
|
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.
|
|
734
|
-
|
|
735
|
-
**1. Removed `responsive` / `dialogMaxWidth` props from `Sheet` and `ConfirmDialog`**
|
|
736
|
-
|
|
737
|
-
The previous wide-screen fallback that bypassed gorhom with a native `Modal` + `ScrollView` was a partial workaround (see REGLA 1). It has been deleted. `@gorhom/bottom-sheet` handles responsive behavior natively — the modal simply renders inside its modal layer at the device width.
|
|
738
|
-
|
|
739
|
-
```diff
|
|
740
|
-
<Sheet
|
|
741
|
-
open={open}
|
|
742
|
-
onClose={() => setOpen(false)}
|
|
743
|
-
title="Options"
|
|
744
|
-
- responsive
|
|
745
|
-
- dialogMaxWidth={600}
|
|
746
|
-
/>
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
**2. `onClose` is the only close handler**
|
|
347
|
+
## Data Display Patterns
|
|
750
348
|
|
|
751
|
-
|
|
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)
|
|
810
|
-
|
|
811
|
-
`IconPicker` now follows the "no frozen screen" rule. When the user taps the trigger, the sheet presents **immediately** (no `useEffect` delay). While the inner grid measures its container, a centered `<Spinner />` is shown inside the sheet so the user sees visible feedback. The grid fades in as soon as `onLayout` fires.
|
|
812
|
-
|
|
813
|
-
This is now the canonical pattern for any sheet whose content needs to measure or load before rendering — apply it to new overlay components.
|
|
814
|
-
|
|
815
|
-
### New — race-condition fix in `Sheet` and `ConfirmDialog`
|
|
407
|
+
## Anti-Patterns
|
|
816
408
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
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
|
|
|
@@ -3847,7 +3443,8 @@ export default function Screen() {
|
|
|
3847
3443
|
| subtitle | `string` | — | Secondary line under the title |
|
|
3848
3444
|
| onBack | `() => void` | — | Shows a back button on the left when provided |
|
|
3849
3445
|
| backIconName | `string` | `'chevron-left'` | Icon for the back button |
|
|
3850
|
-
|
|
|
3446
|
+
| iconName | `string` | — | Decorative icon left of title, after back button. Ignored when `left` is provided |
|
|
3447
|
+
| left | `ReactNode` | — | Custom left content — overrides the back button and `iconName` |
|
|
3851
3448
|
| right | `ReactNode` | — | Custom right content (actions) |
|
|
3852
3449
|
| titleAlign | `'auto' \| 'left' \| 'center'` | `'auto'` | `auto` = left on narrow, centered on wide |
|
|
3853
3450
|
| bordered | `boolean` | `true` | Hairline border underneath |
|
|
@@ -3862,6 +3459,13 @@ export default function Screen() {
|
|
|
3862
3459
|
onBack={navigation.goBack}
|
|
3863
3460
|
right={<IconButton iconName="more-horizontal" variant="text" onPress={openMenu} />}
|
|
3864
3461
|
/>
|
|
3462
|
+
|
|
3463
|
+
{/* With decorative icon and back button */}
|
|
3464
|
+
<AppHeader
|
|
3465
|
+
title="Perfil"
|
|
3466
|
+
iconName="user"
|
|
3467
|
+
onBack={() => router.back()}
|
|
3468
|
+
/>
|
|
3865
3469
|
```
|
|
3866
3470
|
|
|
3867
3471
|
---
|
|
@@ -4309,7 +3913,7 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
|
|
|
4309
3913
|
|
|
4310
3914
|
**When to use:** Image selection from the device library — product photos, profile avatars, tenant logos. Shows a dashed placeholder when empty, the selected image when filled, and a loading overlay while uploading.
|
|
4311
3915
|
|
|
4312
|
-
**Requires:** `
|
|
3916
|
+
**Requires:** `react-native-image-picker` installed in the consuming app (`pnpm add react-native-image-picker`). It is an optional peer dep — consumers who don't use `ImageUpload` never pull it. It is dynamically imported at tap time.
|
|
4313
3917
|
|
|
4314
3918
|
| Prop | Type | Default | Notes |
|
|
4315
3919
|
|------|------|---------|-------|
|
|
@@ -4323,9 +3927,9 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
|
|
|
4323
3927
|
| borderRadius | `number` | `RADIUS.lg` | — |
|
|
4324
3928
|
| resizeMode | `'cover' \| 'contain' \| 'stretch'` | `'cover'` | Image resize mode |
|
|
4325
3929
|
| disabled | `boolean` | `false` | Prevents pressing |
|
|
4326
|
-
| allowsEditing | `boolean` | `true` | When `true`, iOS opens the crop/editing screen after selecting an image. Set `false` to accept the image directly without cropping |
|
|
4327
3930
|
| style | `ViewStyle` | — | — |
|
|
4328
3931
|
| accessibilityLabel | `string` | — | — |
|
|
3932
|
+
| onPickerStarting | `() => void` | — | Called synchronously when user taps the upload area, before dynamic import and permission request |
|
|
4329
3933
|
|
|
4330
3934
|
**Examples:**
|
|
4331
3935
|
```tsx
|
|
@@ -4465,171 +4069,3 @@ const { visible, loading, open, onConfirm, onCancel } = useConfirmDialog({
|
|
|
4465
4069
|
|
|
4466
4070
|
---
|
|
4467
4071
|
|
|
4468
|
-
## Icon System
|
|
4469
|
-
|
|
4470
|
-
The library ships a built-in icon resolver — pass any icon name string to components without manual imports.
|
|
4471
|
-
|
|
4472
|
-
### Supported families (priority order — first match wins)
|
|
4473
|
-
|
|
4474
|
-
| Priority | Family | Best for |
|
|
4475
|
-
|---|---|---|
|
|
4476
|
-
| 1 | `Feather` | Clean line icons, UI essentials (first match wins) |
|
|
4477
|
-
| 2 | `AntDesign` | Semantic UI icons |
|
|
4478
|
-
| 3 | `Entypo` | Social, media, navigation icons |
|
|
4479
|
-
| 4 | `FontAwesome5` | Wide coverage |
|
|
4480
|
-
| 5 | `MaterialIcons` | Material-style icons |
|
|
4481
|
-
| 6 | `Ionicons` | Fallback |
|
|
4482
|
-
|
|
4483
|
-
Browse all available icons: **https://icons.expo.fyi**
|
|
4484
|
-
|
|
4485
|
-
### Standalone `Icon` component
|
|
4486
|
-
|
|
4487
|
-
```tsx
|
|
4488
|
-
import { Icon } from '@retray-dev/ui-kit'
|
|
4489
|
-
|
|
4490
|
-
<Icon name="home" size={24} color={colors.foreground} />
|
|
4491
|
-
<Icon name="star" size={20} color={colors.primary} />
|
|
4492
|
-
|
|
4493
|
-
// Force a specific family when same name exists in multiple families:
|
|
4494
|
-
<Icon name="heart" size={24} color="red" family="FontAwesome5" />
|
|
4495
|
-
```
|
|
4496
|
-
|
|
4497
|
-
**Props:**
|
|
4498
|
-
|
|
4499
|
-
| Prop | Type | Required | Notes |
|
|
4500
|
-
|------|------|----------|-------|
|
|
4501
|
-
| name | `string` | yes | Icon name (e.g. `"home"`, `"arrow-right"`) |
|
|
4502
|
-
| size | `number` | yes | Icon size in points |
|
|
4503
|
-
| color | `string` | yes | Icon color |
|
|
4504
|
-
| family | `IconFamily` | no | Force a specific family |
|
|
4505
|
-
|
|
4506
|
-
Returns `null` (no crash) if name not found in any family.
|
|
4507
|
-
|
|
4508
|
-
> **v13:** `renderIcon`, `getValidIconNames`, and `configureIconFamilies` were removed. Use `Icon` component directly for all icon rendering.
|
|
4509
|
-
|
|
4510
|
-
### `iconName` props on components
|
|
4511
|
-
|
|
4512
|
-
All components with icon slots accept `iconName` — auto-resolved size and color:
|
|
4513
|
-
|
|
4514
|
-
| Component | Prop(s) | Slot | Default color |
|
|
4515
|
-
|-----------|---------|------|---------------|
|
|
4516
|
-
| `Button` | `iconName`, `iconColor` | Left or right of label | Variant label color |
|
|
4517
|
-
| `IconButton` | `iconName`, `iconColor` | Center | Variant foreground |
|
|
4518
|
-
| `Input` | `prefixIcon`, `prefixIconColor` | Before input | `foregroundMuted` |
|
|
4519
|
-
| `Input` | `suffixIcon`, `suffixIconColor` | After input | `foregroundMuted` |
|
|
4520
|
-
| `ListItem` | `leftIcon`, `leftIconColor` | Left 44×44 slot | `foreground` |
|
|
4521
|
-
| `ListItem` | `rightIcon`, `rightIconColor` | Right slot | `foregroundMuted` |
|
|
4522
|
-
| `Badge` | `iconName`, `iconColor` | Before label | Variant foreground |
|
|
4523
|
-
| `Toggle` | `iconName`, `iconColor` | When not pressed | `foregroundMuted` |
|
|
4524
|
-
| `Toggle` | `activeIconName`, `activeIconColor` | When pressed | `primary` |
|
|
4525
|
-
| `AlertBanner` | `iconName`, `iconColor` | Left of content | Variant title color |
|
|
4526
|
-
| `EmptyState` | `iconName`, `iconColor` | Center icon slot | `foregroundMuted` |
|
|
4527
|
-
| `Toast` | `iconName`, `iconColor` | Left of message | Variant text color |
|
|
4528
|
-
| `MediaCard` | `actionIconName` | Top-right of image | `#ffffff` |
|
|
4529
|
-
| `Chip` | `iconName` | Before label | Variant foreground |
|
|
4530
|
-
|
|
4531
|
-
**Precedence:** `iconName` always takes precedence over the corresponding `ReactNode` prop when both are supplied.
|
|
4532
|
-
|
|
4533
|
-
---
|
|
4534
|
-
|
|
4535
|
-
### `getResponsiveFontSize` utility
|
|
4536
|
-
|
|
4537
|
-
Calculates a stepped-down font size for long numeric strings (currency amounts, large numbers) that would overflow at full size.
|
|
4538
|
-
|
|
4539
|
-
```tsx
|
|
4540
|
-
import { getResponsiveFontSize } from '@retray-dev/ui-kit'
|
|
4541
|
-
|
|
4542
|
-
// Default steps: ≤10 chars → max, ≤12 → max-4, ≤14 → max-6, >14 → max-8
|
|
4543
|
-
const fontSize = getResponsiveFontSize(formatCOP(amount), 48)
|
|
4544
|
-
|
|
4545
|
-
<Text style={{ fontSize }} numberOfLines={1} adjustsFontSizeToFit>
|
|
4546
|
-
{formatCOP(amount)}
|
|
4547
|
-
</Text>
|
|
4548
|
-
```
|
|
4549
|
-
|
|
4550
|
-
**Signature:**
|
|
4551
|
-
```ts
|
|
4552
|
-
getResponsiveFontSize(
|
|
4553
|
-
text: string,
|
|
4554
|
-
maxSize: number,
|
|
4555
|
-
steps?: { maxLen: number; subtract: number }[]
|
|
4556
|
-
): number
|
|
4557
|
-
```
|
|
4558
|
-
|
|
4559
|
-
Custom steps example:
|
|
4560
|
-
```tsx
|
|
4561
|
-
getResponsiveFontSize(text, 48, [
|
|
4562
|
-
{ maxLen: 8, subtract: 0 },
|
|
4563
|
-
{ maxLen: 11, subtract: 6 },
|
|
4564
|
-
{ maxLen: 14, subtract: 10 },
|
|
4565
|
-
])
|
|
4566
|
-
```
|
|
4567
|
-
|
|
4568
|
-
**Alternative:** Use `<CurrencyDisplay autoScale maxFontSize={48} />` for currency values — no manual sizing needed.
|
|
4569
|
-
|
|
4570
|
-
---
|
|
4571
|
-
|
|
4572
|
-
## Design System Conventions
|
|
4573
|
-
|
|
4574
|
-
### Touch targets
|
|
4575
|
-
All interactive elements maintain ≥44pt touch height per Apple HIG:
|
|
4576
|
-
- Button md: 48px, sm: 40px, lg: 56px
|
|
4577
|
-
- Input / Textarea: 56px (14px vertical padding)
|
|
4578
|
-
- IconButton md: 44px, lg: 52px
|
|
4579
|
-
- Checkbox: 24×24px box
|
|
4580
|
-
- Switch: 30px track height
|
|
4581
|
-
- MonthPicker arrows: 44×44px
|
|
4582
|
-
|
|
4583
|
-
### Haptic patterns
|
|
4584
|
-
- `impactLight` — Button, Card (press), Sheet open, Toast, MediaCard action
|
|
4585
|
-
- `selectionAsync` — Checkbox, Switch, Toggle, RadioGroup, Select, Slider steps, Accordion, ListItem, Chip, CategoryStrip, Tabs, MonthPicker
|
|
4586
|
-
- `notificationSuccess` — Toast `success`
|
|
4587
|
-
- `notificationError` — Toast `destructive`, Toast `warning`
|
|
4588
|
-
|
|
4589
|
-
### Animation press scales
|
|
4590
|
-
- `Button` → 0.95 (strong spring feedback)
|
|
4591
|
-
- `Card` → 0.98 (subtle, appropriate for large surfaces)
|
|
4592
|
-
- `ListItem` → 0.97 (between — medium row targets)
|
|
4593
|
-
- `MediaCard` → 0.98 (large surface)
|
|
4594
|
-
- `IconButton` → 0.95
|
|
4595
|
-
- `Chip`, `CategoryStrip chip` → 0.95
|
|
4596
|
-
|
|
4597
|
-
### Platform differences
|
|
4598
|
-
- `useNativeDriver` — `Platform.OS !== 'web'` everywhere (native driver disabled on web)
|
|
4599
|
-
- `Select` — wheel modal on iOS, dialog on Android, `<select>` on web
|
|
4600
|
-
- `Toast` — full width on mobile, 400px centered on web
|
|
4601
|
-
- Hover states — web only
|
|
4602
|
-
- `Skeleton` shimmer highlight — adapts opacity for light/dark mode
|
|
4603
|
-
|
|
4604
|
-
### Dynamic Type
|
|
4605
|
-
All `Text` and `TextInput` components have `allowFontScaling={true}` — respects user font size accessibility settings.
|
|
4606
|
-
|
|
4607
|
-
### Scaling utilities (internal)
|
|
4608
|
-
Used internally — not exported. `s()` horizontal scale, `vs()` vertical scale, `ms()` moderate scale relative to 350×680 base.
|
|
4609
|
-
|
|
4610
|
-
---
|
|
4611
|
-
|
|
4612
|
-
## Full Composition Examples
|
|
4613
|
-
|
|
4614
|
-
The package includes **EXAMPLES.md** with complete, working code for 4 real-world app screens:
|
|
4615
|
-
|
|
4616
|
-
1. **Finance Dashboard** — MonthPicker, CurrencyDisplay, Progress, CategoryStrip, ListItem, DetailRow
|
|
4617
|
-
2. **Edit Profile** — Avatar, Badge, Input, Select, Switch, Card, AlertBanner
|
|
4618
|
-
3. **Onboarding Flow** — Badge, Progress, Input, RadioGroup, EmptyState (multi-step wizard)
|
|
4619
|
-
4. **Settings Screen** — MenuGroup, ListGroup, Form, MenuItem, ListItem, compound component patterns
|
|
4620
|
-
|
|
4621
|
-
**For AI agents:** Import EXAMPLES.md from the package for full source code and patterns:
|
|
4622
|
-
|
|
4623
|
-
```markdown
|
|
4624
|
-
## UI Kit Composition Examples
|
|
4625
|
-
@./node_modules/@retray-dev/ui-kit/EXAMPLES.md
|
|
4626
|
-
```
|
|
4627
|
-
|
|
4628
|
-
Each example includes:
|
|
4629
|
-
- Complete component code (copy-paste ready)
|
|
4630
|
-
- State management setup
|
|
4631
|
-
- Proper imports and theme usage
|
|
4632
|
-
- Common patterns (spacing, colors, navigation, toast feedback)
|
|
4633
|
-
- StyleSheet definitions
|
|
4634
|
-
|
|
4635
|
-
**For human developers:** Examples are also live in the `example/` app. Clone the repo, run `pnpm install && pnpm build`, then `cd example && pnpm start` to see them in action.
|