@retray-dev/ui-kit 3.1.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +1792 -659
- package/README.md +8 -7
- package/dist/index.d.mts +269 -89
- package/dist/index.d.ts +269 -89
- package/dist/index.js +1034 -312
- package/dist/index.mjs +1031 -314
- package/package.json +3 -2
- package/src/components/Accordion/Accordion.tsx +1 -1
- package/src/components/AlertBanner/AlertBanner.tsx +50 -45
- package/src/components/Avatar/Avatar.tsx +61 -17
- package/src/components/Badge/Badge.tsx +17 -15
- package/src/components/Button/Button.tsx +31 -42
- package/src/components/Card/Card.tsx +4 -4
- package/src/components/CategoryStrip/CategoryStrip.tsx +185 -0
- package/src/components/CategoryStrip/index.ts +2 -0
- package/src/components/Checkbox/Checkbox.tsx +44 -16
- package/src/components/Chip/Chip.tsx +1 -1
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +4 -4
- package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +1 -0
- package/src/components/CurrencyInput/CurrencyInput.tsx +6 -4
- package/src/components/EmptyState/EmptyState.tsx +9 -9
- package/src/components/IconButton/IconButton.tsx +74 -34
- package/src/components/Input/Input.tsx +15 -13
- package/src/components/LabelValue/LabelValue.tsx +1 -1
- package/src/components/ListItem/ListItem.tsx +5 -5
- package/src/components/MediaCard/MediaCard.tsx +249 -0
- package/src/components/MediaCard/index.ts +2 -0
- package/src/components/Pressable/Pressable.tsx +100 -0
- package/src/components/Pressable/index.ts +1 -0
- package/src/components/Progress/Progress.tsx +14 -7
- package/src/components/RadioGroup/RadioGroup.tsx +1 -1
- package/src/components/Select/Select.tsx +5 -5
- package/src/components/Sheet/Sheet.tsx +3 -9
- package/src/components/Skeleton/Skeleton.tsx +34 -7
- package/src/components/Slider/Slider.tsx +2 -2
- package/src/components/Spinner/Spinner.tsx +1 -1
- package/src/components/Switch/Switch.tsx +31 -4
- package/src/components/Tabs/Tabs.tsx +63 -45
- package/src/components/Text/Text.tsx +59 -10
- package/src/components/Textarea/Textarea.tsx +4 -3
- package/src/components/Toast/Toast.tsx +77 -36
- package/src/components/Toggle/Toggle.tsx +3 -3
- package/src/index.ts +8 -2
- package/src/theme/ThemeProvider.tsx +11 -10
- package/src/theme/colorUtils.ts +80 -0
- package/src/theme/colors.ts +76 -35
- package/src/theme/index.ts +2 -2
- package/src/theme/types.ts +27 -13
- package/src/tokens.ts +150 -13
- package/src/utils/hover.ts +25 -0
package/COMPONENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @retray-dev/ui-kit — Component Reference (
|
|
1
|
+
# @retray-dev/ui-kit — Component Reference (v5.0.0)
|
|
2
2
|
|
|
3
3
|
This file is the AI reference for this package. It is shipped inside the npm package so consuming projects can import it into their `CLAUDE.md` with:
|
|
4
4
|
|
|
@@ -44,7 +44,7 @@ export default function App() {
|
|
|
44
44
|
|
|
45
45
|
## Typography — Poppins (Required)
|
|
46
46
|
|
|
47
|
-
All components use **Poppins** as the font family. You **must** load it before rendering any UI kit component
|
|
47
|
+
All components use **Poppins** as the font family. You **must** load it before rendering any UI kit component.
|
|
48
48
|
|
|
49
49
|
```tsx
|
|
50
50
|
import { useFonts } from 'expo-font'
|
|
@@ -52,8 +52,7 @@ import { PoppinsFonts } from '@retray-dev/ui-kit/fonts'
|
|
|
52
52
|
|
|
53
53
|
export default function App() {
|
|
54
54
|
const [fontsLoaded] = useFonts(PoppinsFonts)
|
|
55
|
-
if (!fontsLoaded) return null
|
|
56
|
-
|
|
55
|
+
if (!fontsLoaded) return null
|
|
57
56
|
return (
|
|
58
57
|
// ... your providers and app
|
|
59
58
|
)
|
|
@@ -66,49 +65,44 @@ export default function App() {
|
|
|
66
65
|
3. Metro resolves font files from `node_modules/@retray-dev/ui-kit/src/assets/fonts/` at bundle time
|
|
67
66
|
4. All library components reference fonts by family name (e.g., `fontFamily: 'Poppins-SemiBold'`)
|
|
68
67
|
|
|
69
|
-
**Why this pattern:**
|
|
70
|
-
- Fonts are NOT bundled into `dist/` — they ship as raw `.ttf` files inside `src/assets/fonts/`
|
|
71
|
-
- Metro resolves `require()` calls to these files when it bundles your app
|
|
72
|
-
- This prevents font corruption that can occur when `.ttf` files pass through bundlers like esbuild/tsup
|
|
73
|
-
|
|
74
|
-
**Setup:**
|
|
75
|
-
- Add `expo-font` to your app: `pnpm add expo-font`
|
|
76
|
-
- The `if (!fontsLoaded) return null` guard prevents text from rendering before fonts are ready
|
|
77
|
-
- Pair with `expo-splash-screen` in production to avoid a flash:
|
|
78
|
-
```tsx
|
|
79
|
-
import * as SplashScreen from 'expo-splash-screen'
|
|
80
|
-
SplashScreen.preventAutoHideAsync()
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
if (fontsLoaded) SplashScreen.hideAsync()
|
|
84
|
-
}, [fontsLoaded])
|
|
85
|
-
```
|
|
86
|
-
|
|
87
68
|
**Included weights:**
|
|
88
69
|
- Regular: `Poppins-Thin`, `Poppins-ExtraLight`, `Poppins-Light`, `Poppins-Regular`, `Poppins-Medium`, `Poppins-SemiBold`, `Poppins-Bold`, `Poppins-ExtraBold`, `Poppins-Black`
|
|
89
70
|
- Italic: `Poppins-Italic`, `Poppins-MediumItalic`, `Poppins-SemiBoldItalic`, `Poppins-BoldItalic`
|
|
90
71
|
|
|
91
|
-
**Total: 13 font files** exported from the package.
|
|
72
|
+
**Total: 13 font files** exported from the package. Font `.ttf` files ship as raw assets in `src/assets/fonts/` — NOT bundled into `dist/`. Metro resolves `require()` calls at build time.
|
|
73
|
+
|
|
74
|
+
Pair with `expo-splash-screen` in production:
|
|
75
|
+
```tsx
|
|
76
|
+
import * as SplashScreen from 'expo-splash-screen'
|
|
77
|
+
SplashScreen.preventAutoHideAsync()
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (fontsLoaded) SplashScreen.hideAsync()
|
|
81
|
+
}, [fontsLoaded])
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Theme System
|
|
92
87
|
|
|
93
88
|
### ThemeProvider Props
|
|
94
89
|
|
|
95
90
|
| Prop | Type | Default | Notes |
|
|
96
91
|
|------|------|---------|-------|
|
|
97
|
-
| colorScheme | `'light' \| 'dark' \| 'system'` | `'system'` | `'system'` auto-detects
|
|
98
|
-
| theme | `{ light?: Partial<ThemeColors>, dark?: Partial<ThemeColors> }` | — | Override any subset of
|
|
92
|
+
| colorScheme | `'light' \| 'dark' \| 'system'` | `'system'` | `'system'` auto-detects device setting and updates when it changes |
|
|
93
|
+
| theme | `{ light?: Partial<ThemeColors>, dark?: Partial<ThemeColors> }` | — | Override any subset of the 12 public tokens per scheme |
|
|
99
94
|
|
|
100
95
|
**Custom theme example:**
|
|
101
96
|
```tsx
|
|
102
97
|
const myTheme = {
|
|
103
|
-
light: { primary: '#
|
|
104
|
-
dark: { primary: '#
|
|
98
|
+
light: { primary: '#ff385c', primaryForeground: '#ffffff' },
|
|
99
|
+
dark: { primary: '#ff385c', primaryForeground: '#ffffff' },
|
|
105
100
|
}
|
|
106
101
|
<ThemeProvider theme={myTheme} colorScheme="system">
|
|
107
102
|
```
|
|
108
103
|
|
|
109
104
|
### useTheme Hook
|
|
110
105
|
|
|
111
|
-
Access the active color tokens and scheme inside any component:
|
|
112
106
|
```tsx
|
|
113
107
|
import { useTheme } from '@retray-dev/ui-kit'
|
|
114
108
|
|
|
@@ -118,138 +112,238 @@ function MyComponent() {
|
|
|
118
112
|
}
|
|
119
113
|
```
|
|
120
114
|
|
|
115
|
+
Returns `colors` (full `ResolvedColors` palette) and `colorScheme` (`'light' | 'dark'`).
|
|
116
|
+
|
|
121
117
|
---
|
|
122
118
|
|
|
123
119
|
## Theme Tokens
|
|
124
120
|
|
|
125
|
-
|
|
121
|
+
### Public Tokens (ThemeColors) — 12 tokens consumer can override
|
|
126
122
|
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
import type { ThemeColors } from '@retray-dev/ui-kit'
|
|
130
|
-
```
|
|
123
|
+
These are the only values you need to supply when customizing the theme. The library derives all other colors internally.
|
|
131
124
|
|
|
132
|
-
| Token | Light | Dark | Semantic Role |
|
|
133
|
-
|
|
125
|
+
| Token | Light Default | Dark Default | Semantic Role |
|
|
126
|
+
|-------|--------------|--------------|---------------|
|
|
134
127
|
| `background` | `#ffffff` | `#0f0f0f` | Screen / page background |
|
|
135
|
-
| `foreground` | `#
|
|
136
|
-
| `card` | `#ffffff` | `#1c1c1c` | Card / surface background |
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
|
152
|
-
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
128
|
+
| `foreground` | `#222222` | `#fafafa` | Primary text — deep near-black, not pure black |
|
|
129
|
+
| `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface background |
|
|
130
|
+
| `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states, active indicators) |
|
|
131
|
+
| `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon placed on primary-colored backgrounds |
|
|
132
|
+
| `border` | `#dddddd` | `#303030` | Borders, dividers, input outlines |
|
|
133
|
+
| `destructive` | `#e53935` | `#ef5350` | Error / danger / delete actions |
|
|
134
|
+
| `destructiveForeground` | `#ffffff` | `#ffffff` | Text/icon on destructive backgrounds |
|
|
135
|
+
| `success` | `#1a7a45` | `#2e7d52` | Success / confirmation states |
|
|
136
|
+
| `successForeground` | `#ffffff` | `#ffffff` | Text/icon on success backgrounds |
|
|
137
|
+
| `warning` | `#e67e00` | `#f57c00` | Warning / caution states |
|
|
138
|
+
| `warningForeground` | `#ffffff` | `#ffffff` | Text/icon on warning backgrounds |
|
|
139
|
+
|
|
140
|
+
### Derived Tokens (ResolvedColors) — read-only via useTheme().colors
|
|
141
|
+
|
|
142
|
+
The full palette components consume. Never supply these directly — they are computed by `deriveColors()` from the 12 public tokens above.
|
|
143
|
+
|
|
144
|
+
| Token | Derived From | Purpose |
|
|
145
|
+
|-------|-------------|---------|
|
|
146
|
+
| `foregroundSubtle` | `foreground` @ ~55% | Body text, subtitles, secondary content |
|
|
147
|
+
| `foregroundMuted` | `foreground` @ ~38% | Captions, timestamps, placeholders |
|
|
148
|
+
| `surface` | `background` slightly off-canvas | Chip backgrounds, input fills, tag backgrounds, skeleton |
|
|
149
|
+
| `surfaceStrong` | `background` stronger offset | Pressed/hover fill states |
|
|
150
|
+
| `destructiveTint` | `destructive` blended to bg | Alert banner background, toast background |
|
|
151
|
+
| `destructiveBorder` | `destructive` @ 30% | Alert banner border, badge outline |
|
|
152
|
+
| `successTint` | `success` blended to bg | Success banner background |
|
|
153
|
+
| `successBorder` | `success` @ 30% | Success banner border |
|
|
154
|
+
| `warningTint` | `warning` blended to bg | Warning banner background |
|
|
155
|
+
| `warningBorder` | `warning` @ 30% | Warning banner border |
|
|
156
|
+
| `ring` | `= primary` | Focus ring color (always matches primary) |
|
|
157
|
+
| `input` | `= border` | Input field border (always matches border) |
|
|
158
|
+
|
|
159
|
+
**Usage example — building a custom component using derived tokens:**
|
|
160
|
+
```tsx
|
|
161
|
+
const { colors } = useTheme()
|
|
162
|
+
|
|
163
|
+
// Text hierarchy
|
|
164
|
+
<Text style={{ color: colors.foreground }}>Primary text</Text>
|
|
165
|
+
<Text style={{ color: colors.foregroundSubtle }}>Secondary text</Text>
|
|
166
|
+
<Text style={{ color: colors.foregroundMuted }}>Caption / timestamp</Text>
|
|
167
|
+
|
|
168
|
+
// Surface fills (unselected chips, inactive backgrounds)
|
|
169
|
+
<View style={{ backgroundColor: colors.surface }}>
|
|
170
|
+
|
|
171
|
+
// Warning alert banner
|
|
172
|
+
<View style={{ backgroundColor: colors.warningTint, borderColor: colors.warningBorder }}>
|
|
173
|
+
<Text style={{ color: colors.warning }}>Warning message</Text>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### deriveColors export
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { deriveColors } from '@retray-dev/ui-kit'
|
|
180
|
+
|
|
181
|
+
const resolved = deriveColors(myThemeColors, 'light')
|
|
182
|
+
// resolved contains all 24 ResolvedColors tokens
|
|
183
|
+
```
|
|
157
184
|
|
|
158
185
|
---
|
|
159
186
|
|
|
160
187
|
## Design Tokens
|
|
161
188
|
|
|
162
|
-
Static structural constants
|
|
189
|
+
Static structural constants — no context or provider needed.
|
|
163
190
|
|
|
164
191
|
```ts
|
|
165
|
-
import { SPACING, ICON_SIZES, RADIUS, SHADOWS, BREAKPOINTS } from '@retray-dev/ui-kit'
|
|
192
|
+
import { SPACING, ICON_SIZES, RADIUS, SHADOWS, BREAKPOINTS, TYPOGRAPHY } from '@retray-dev/ui-kit'
|
|
166
193
|
```
|
|
167
194
|
|
|
168
|
-
### SPACING
|
|
169
|
-
|
|
170
|
-
8pt-grid spacing scale.
|
|
195
|
+
### SPACING — 8pt grid with 2pt micro-step
|
|
171
196
|
|
|
172
|
-
| Key | Value |
|
|
173
|
-
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
197
|
+
| Key | Value | Use |
|
|
198
|
+
|-----|-------|-----|
|
|
199
|
+
| `xxs` | 2 | Micro gaps (icon to text in badges) |
|
|
200
|
+
| `xs` | 4 | Tight internal gaps |
|
|
201
|
+
| `sm` | 8 | Component internal padding |
|
|
202
|
+
| `md` | 12 | Medium gaps |
|
|
203
|
+
| `base` | 16 | Default content padding |
|
|
204
|
+
| `lg` | 24 | Section internal padding |
|
|
205
|
+
| `xl` | 32 | Between major content blocks |
|
|
206
|
+
| `xxl` | 48 | Section padding |
|
|
207
|
+
| `section` | 64 | Major band separators (Airbnb hero → grid rhythm) |
|
|
181
208
|
|
|
182
209
|
**Types:** `Spacing`, `SpacingKey`
|
|
183
210
|
|
|
184
211
|
```tsx
|
|
185
|
-
<View style={{ gap: SPACING.md, padding: SPACING.
|
|
212
|
+
<View style={{ gap: SPACING.md, padding: SPACING.base }} />
|
|
186
213
|
```
|
|
187
214
|
|
|
188
215
|
### ICON_SIZES
|
|
189
216
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
|
193
|
-
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `xl` | 28 |
|
|
198
|
-
| `2xl` | 32 |
|
|
217
|
+
| Key | Value | Use |
|
|
218
|
+
|-----|-------|-----|
|
|
219
|
+
| `sm` | 14 | Badge icons, inline micro icons |
|
|
220
|
+
| `md` | 18 | Standard component icons |
|
|
221
|
+
| `lg` | 22 | Larger inline icons |
|
|
222
|
+
| `xl` | 28 | Feature icons |
|
|
223
|
+
| `2xl` | 32 | Display icons |
|
|
199
224
|
|
|
200
225
|
**Types:** `IconSize`, `IconSizeKey`
|
|
201
226
|
|
|
202
|
-
|
|
203
|
-
<Icon name="home" size={ICON_SIZES.md} color={colors.foreground} />
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### RADIUS
|
|
207
|
-
|
|
208
|
-
Border radius scale used throughout the library.
|
|
227
|
+
### RADIUS — Airbnb shape language
|
|
209
228
|
|
|
210
229
|
| Key | Value | Used in |
|
|
211
230
|
|-----|-------|---------|
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
231
|
+
| `none` | 0 | No rounding |
|
|
232
|
+
| `xs` | 4 | Micro chips, tags |
|
|
233
|
+
| `sm` | 8 | Inputs, Textarea, Select, Checkbox |
|
|
234
|
+
| `md` | 14 | Cards, MediaCard, AlertBanner, Toast, EmptyState |
|
|
235
|
+
| `lg` | 20 | Sheet top corners |
|
|
236
|
+
| `xl` | 32 | Primary CTA buttons (pill-like) |
|
|
237
|
+
| `full` | 9999 | Circular elements, CategoryStrip chips |
|
|
217
238
|
|
|
218
239
|
**Types:** `Radius`, `RadiusKey`
|
|
219
240
|
|
|
220
241
|
```tsx
|
|
221
|
-
<View style={{ borderRadius: RADIUS.
|
|
242
|
+
<View style={{ borderRadius: RADIUS.md }} />
|
|
222
243
|
```
|
|
223
244
|
|
|
224
|
-
### SHADOWS
|
|
225
|
-
|
|
226
|
-
Cross-platform shadow presets (RN `shadow*` properties + `elevation`).
|
|
245
|
+
### SHADOWS — Cross-platform shadow presets
|
|
227
246
|
|
|
228
|
-
| Key |
|
|
229
|
-
|
|
230
|
-
| `sm` | 1 | 0.
|
|
231
|
-
| `md` |
|
|
232
|
-
| `lg` | 6 | 0.
|
|
233
|
-
| `xl` | 12 | 0.
|
|
247
|
+
| Key | shadowOffset.height | shadowOpacity | shadowRadius | elevation | Use |
|
|
248
|
+
|-----|---------------------|---------------|--------------|-----------|-----|
|
|
249
|
+
| `sm` | 1 | 0.06 | 4 | 2 | Default card shadow |
|
|
250
|
+
| `md` | 2 | 0.10 | 8 | 5 | Hover float / elevated elements |
|
|
251
|
+
| `lg` | 6 | 0.16 | 16 | 10 | Modals, overlays |
|
|
252
|
+
| `xl` | 12 | 0.24 | 24 | 18 | High-elevation dialogs |
|
|
234
253
|
|
|
235
254
|
```tsx
|
|
236
|
-
<View style={[styles.card, SHADOWS.
|
|
255
|
+
<View style={[styles.card, SHADOWS.sm]} />
|
|
256
|
+
// Hover state
|
|
257
|
+
<View style={[styles.card, hovered ? SHADOWS.md : SHADOWS.sm]} />
|
|
237
258
|
```
|
|
238
259
|
|
|
239
260
|
### BREAKPOINTS
|
|
240
261
|
|
|
241
|
-
|
|
262
|
+
| Key | Value | Use |
|
|
263
|
+
|-----|-------|-----|
|
|
264
|
+
| `wide` | 700 | Tablet / wide layout threshold |
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
const isWide = useWindowDimensions().width >= BREAKPOINTS.wide
|
|
268
|
+
```
|
|
242
269
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
270
|
+
### TYPOGRAPHY — 16 Airbnb-aligned variants
|
|
271
|
+
|
|
272
|
+
All components use these tokens for text styling. Import and use in custom components for consistency.
|
|
273
|
+
|
|
274
|
+
| Key | Size | Weight | lineHeight | letterSpacing | Use |
|
|
275
|
+
|-----|------|--------|-----------|---------------|-----|
|
|
276
|
+
| `display-hero` | 64 | 700 | 70 | -1 | Large number display, balance totals |
|
|
277
|
+
| `display-xl` | 28 | 700 | 40 | 0 | Screen titles, hero headings |
|
|
278
|
+
| `display-lg` | 22 | 500 | 26 | -0.44 | Section headings |
|
|
279
|
+
| `display-md` | 21 | 700 | 30 | 0 | Card titles, dialog titles |
|
|
280
|
+
| `display-sm` | 20 | 600 | 24 | -0.18 | Sub-section headings |
|
|
281
|
+
| `title-md` | 16 | 600 | 20 | 0 | Row titles, list item titles |
|
|
282
|
+
| `title-sm` | 16 | 500 | 20 | 0 | Secondary titles |
|
|
283
|
+
| `body-md` | 16 | 400 | 24 | 0 | Primary body copy |
|
|
284
|
+
| `body-sm` | 14 | 400 | 20 | 0 | Secondary body, descriptions |
|
|
285
|
+
| `caption` | 14 | 500 | 18 | 0 | Labels above inputs, item captions |
|
|
286
|
+
| `caption-sm` | 13 | 400 | 16 | 0 | Timestamps, metadata |
|
|
287
|
+
| `badge-text` | 11 | 600 | 13 | 0 | Badge labels, small tags |
|
|
288
|
+
| `micro-label` | 12 | 700 | 16 | 0 | Micro labels, overlines |
|
|
289
|
+
| `uppercase-tag` | 8 | 700 | 10 | 0.32 | Uppercase decorative tags (auto-uppercase) |
|
|
290
|
+
| `button-lg` | 16 | 500 | 20 | 0 | Button labels (md/lg size) |
|
|
291
|
+
| `button-sm` | 14 | 500 | 18 | 0 | Button labels (sm size) |
|
|
292
|
+
|
|
293
|
+
**Types:** `Typography`, `TypographyKey`
|
|
246
294
|
|
|
247
295
|
```tsx
|
|
248
|
-
|
|
296
|
+
import { TYPOGRAPHY } from '@retray-dev/ui-kit'
|
|
297
|
+
|
|
298
|
+
// Use in StyleSheet
|
|
299
|
+
const styles = StyleSheet.create({
|
|
300
|
+
heading: {
|
|
301
|
+
...TYPOGRAPHY['display-xl'],
|
|
302
|
+
color: colors.foreground,
|
|
303
|
+
},
|
|
304
|
+
})
|
|
249
305
|
```
|
|
250
306
|
|
|
251
307
|
---
|
|
252
308
|
|
|
309
|
+
## Migration Guide: v4 → v5
|
|
310
|
+
|
|
311
|
+
### Breaking Changes
|
|
312
|
+
|
|
313
|
+
**Button variants renamed:**
|
|
314
|
+
| v4 | v5 |
|
|
315
|
+
|----|-----|
|
|
316
|
+
| `outline` | `secondary` |
|
|
317
|
+
| `ghost` | `text` |
|
|
318
|
+
| `secondary` (filled gray) | removed — use Card/surface instead |
|
|
319
|
+
|
|
320
|
+
**Text variants replaced:**
|
|
321
|
+
| v4 | v5 equivalent |
|
|
322
|
+
|----|---------------|
|
|
323
|
+
| `h1` | `display-xl` |
|
|
324
|
+
| `h2` | `display-lg` or `display-md` |
|
|
325
|
+
| `h3` | `display-sm` |
|
|
326
|
+
| `body` | `body-md` |
|
|
327
|
+
| `label` | `title-sm` or `caption` |
|
|
328
|
+
| `caption` | `caption-sm` |
|
|
329
|
+
|
|
330
|
+
**Theme tokens changed:**
|
|
331
|
+
| v4 | v5 |
|
|
332
|
+
|----|-----|
|
|
333
|
+
| `secondary`, `secondaryForeground` | removed (use `surface` derived token) |
|
|
334
|
+
| `accent`, `accentForeground` | removed (use `surfaceStrong`) |
|
|
335
|
+
| `muted` | removed → `surface` (derived) |
|
|
336
|
+
| `mutedForeground` | removed → `foregroundSubtle` / `foregroundMuted` (derived) |
|
|
337
|
+
| Added | `warning`, `warningForeground` |
|
|
338
|
+
| Added (derived) | `warningTint`, `warningBorder` |
|
|
339
|
+
|
|
340
|
+
**IconButton variant:**
|
|
341
|
+
| v4 | v5 |
|
|
342
|
+
|----|-----|
|
|
343
|
+
| `ghost` | `text` |
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
253
347
|
## Components
|
|
254
348
|
|
|
255
349
|
---
|
|
@@ -257,28 +351,68 @@ const isWide = width >= BREAKPOINTS.wide
|
|
|
257
351
|
### Text
|
|
258
352
|
|
|
259
353
|
**Import:** `import { Text } from '@retray-dev/ui-kit'`
|
|
260
|
-
|
|
261
|
-
**
|
|
354
|
+
|
|
355
|
+
**When to use:** Every piece of text in your app. Provides Airbnb-aligned typographic hierarchy with correct font, weight, size, and line height. Replaces React Native's `Text` directly — all native `TextProps` pass through.
|
|
356
|
+
|
|
357
|
+
**Extends:** `TextProps` from React Native
|
|
262
358
|
|
|
263
359
|
| Prop | Type | Default | Notes |
|
|
264
360
|
|------|------|---------|-------|
|
|
265
|
-
| variant | `
|
|
266
|
-
| color | `string` | — | Override
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
361
|
+
| variant | `TextVariant` | `'body-md'` | Sets font, size, weight, line height, letter spacing |
|
|
362
|
+
| color | `string` | — | Override text color. Each variant has a semantic default (see table below) |
|
|
363
|
+
| children | `ReactNode` | required | — |
|
|
364
|
+
| style | `TextStyle` | — | Additional styles (merged after variant styles) |
|
|
365
|
+
| (all TextProps) | — | — | `numberOfLines`, `ellipsizeMode`, `onPress`, etc. all pass through |
|
|
366
|
+
|
|
367
|
+
**Variant reference with semantic defaults:**
|
|
368
|
+
|
|
369
|
+
| Variant | Size | Weight | Default Color | When to use |
|
|
370
|
+
|---------|------|--------|--------------|-------------|
|
|
371
|
+
| `display-hero` | 64 | 700 | `foreground` | Large numeric displays — balance totals, stats, hero numbers |
|
|
372
|
+
| `display-xl` | 28 | 700 | `foreground` | Screen-level titles, onboarding hero headings |
|
|
373
|
+
| `display-lg` | 22 | 500 | `foreground` | Section headings, card group labels |
|
|
374
|
+
| `display-md` | 21 | 700 | `foreground` | Card titles, dialog headings |
|
|
375
|
+
| `display-sm` | 20 | 600 | `foreground` | Sub-section titles |
|
|
376
|
+
| `title-md` | 16 | 600 | `foreground` | List row titles, navigation labels |
|
|
377
|
+
| `title-sm` | 16 | 500 | `foreground` | Secondary row titles |
|
|
378
|
+
| `body-md` | 16 | 400 | `foregroundSubtle` | Main body copy, descriptions |
|
|
379
|
+
| `body-sm` | 14 | 400 | `foregroundSubtle` | Secondary descriptions, detail text |
|
|
380
|
+
| `caption` | 14 | 500 | `foreground` | Labels above inputs, item captions |
|
|
381
|
+
| `caption-sm` | 13 | 400 | `foregroundMuted` | Timestamps, metadata, helper text |
|
|
382
|
+
| `badge-text` | 11 | 600 | `foreground` | Badge labels, small status tags |
|
|
383
|
+
| `micro-label` | 12 | 700 | `foreground` | Micro labels, overlines |
|
|
384
|
+
| `uppercase-tag` | 8 | 700 | `foreground` | Decorative uppercase category labels (auto-transforms text) |
|
|
385
|
+
| `button-lg` | 16 | 500 | `foreground` | Internal use in Button component (md/lg) |
|
|
386
|
+
| `button-sm` | 14 | 500 | `foreground` | Internal use in Button component (sm) |
|
|
387
|
+
|
|
388
|
+
**All text has `allowFontScaling={true}` — respects user's font size settings.**
|
|
389
|
+
|
|
390
|
+
**Examples:**
|
|
277
391
|
```tsx
|
|
278
|
-
|
|
279
|
-
<Text variant="
|
|
280
|
-
|
|
281
|
-
|
|
392
|
+
// Screen title
|
|
393
|
+
<Text variant="display-xl">Welcome back</Text>
|
|
394
|
+
|
|
395
|
+
// Financial balance display
|
|
396
|
+
<Text variant="display-hero" color={colors.primary}>$25.000</Text>
|
|
397
|
+
|
|
398
|
+
// Section heading with subtle color
|
|
399
|
+
<Text variant="display-lg" color={colors.foregroundSubtle}>Recent activity</Text>
|
|
400
|
+
|
|
401
|
+
// Body copy
|
|
402
|
+
<Text variant="body-md">Your account is ready to use.</Text>
|
|
403
|
+
|
|
404
|
+
// Caption / metadata
|
|
405
|
+
<Text variant="caption-sm" color={colors.foregroundMuted}>2 hours ago</Text>
|
|
406
|
+
|
|
407
|
+
// Input label (used automatically by Input, but also useful standalone)
|
|
408
|
+
<Text variant="caption">Email address</Text>
|
|
409
|
+
|
|
410
|
+
// Uppercase category tag
|
|
411
|
+
<Text variant="uppercase-tag">New</Text>
|
|
412
|
+
|
|
413
|
+
// Color override
|
|
414
|
+
<Text variant="title-md" color={colors.primary}>Primary colored title</Text>
|
|
415
|
+
<Text variant="body-sm" color={colors.destructive}>Error message in body</Text>
|
|
282
416
|
```
|
|
283
417
|
|
|
284
418
|
---
|
|
@@ -286,39 +420,81 @@ const isWide = width >= BREAKPOINTS.wide
|
|
|
286
420
|
### Button
|
|
287
421
|
|
|
288
422
|
**Import:** `import { Button } from '@retray-dev/ui-kit'`
|
|
289
|
-
|
|
290
|
-
**
|
|
423
|
+
|
|
424
|
+
**When to use:** Any interactive action. Use `variant` to communicate intent and visual weight. Primary is the main CTA per screen — use sparingly (one per screen ideally).
|
|
425
|
+
|
|
426
|
+
**Extends:** `TouchableOpacityProps` from React Native — all native props pass through.
|
|
291
427
|
|
|
292
428
|
| Prop | Type | Default | Notes |
|
|
293
429
|
|------|------|---------|-------|
|
|
294
430
|
| label | `string` | required | Button text |
|
|
295
|
-
| variant | `'primary' \| 'secondary' \| '
|
|
296
|
-
| size | `'sm' \| 'md' \| 'lg'` | `'md'` |
|
|
297
|
-
| loading | `boolean` | `false` | Replaces label with
|
|
431
|
+
| variant | `'primary' \| 'secondary' \| 'text' \| 'destructive'` | `'primary'` | Visual style |
|
|
432
|
+
| size | `'sm' \| 'md' \| 'lg'` | `'md'` | Controls height, padding, and icon size |
|
|
433
|
+
| loading | `boolean` | `false` | Replaces label with spinner, forces disabled state |
|
|
298
434
|
| fullWidth | `boolean` | `false` | Stretches to container width (`alignSelf: 'stretch'`) |
|
|
299
435
|
| disabled | `boolean` | — | Reduces opacity to 0.5 |
|
|
300
|
-
| icon | `React.ReactNode \| ((props: { label, size, variant }) => React.ReactNode)` | — | Icon rendered alongside
|
|
301
|
-
| iconName | `string` | — | Icon name from `@expo/vector-icons
|
|
436
|
+
| icon | `React.ReactNode \| ((props: { label, size, variant }) => React.ReactNode)` | — | Icon rendered alongside label |
|
|
437
|
+
| iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon` |
|
|
302
438
|
| iconColor | `string` | — | Override icon color. Defaults to variant label color |
|
|
303
439
|
| iconPosition | `'left' \| 'right'` | `'left'` | Side the icon appears on |
|
|
440
|
+
| style | `ViewStyle` | — | Override container style |
|
|
304
441
|
|
|
305
442
|
**Variants:**
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
443
|
+
|
|
444
|
+
| Variant | Background | Border | Text | When to use |
|
|
445
|
+
|---------|-----------|--------|------|-------------|
|
|
446
|
+
| `primary` | `primary` | none | `primaryForeground` | Main CTA — save, submit, continue |
|
|
447
|
+
| `secondary` | transparent | `primary` (1.5px) | `primary` | Alternative action alongside primary |
|
|
448
|
+
| `text` | transparent | none | `primary` | Low-emphasis — cancel, skip, see more |
|
|
449
|
+
| `destructive` | `destructive` | none | `destructiveForeground` | Irreversible actions — delete, remove |
|
|
450
|
+
|
|
451
|
+
**Sizes:**
|
|
452
|
+
|
|
453
|
+
| Size | Height | Horizontal padding | Icon size | Font |
|
|
454
|
+
|------|--------|--------------------|-----------|------|
|
|
455
|
+
| `sm` | 40px | 16px | 16px | `button-sm` (14pt/500) |
|
|
456
|
+
| `md` | 48px | 24px | 18px | `button-lg` (16pt/500) |
|
|
457
|
+
| `lg` | 56px | 28px | 20px | `button-lg` (16pt/500) |
|
|
458
|
+
|
|
459
|
+
**Shape:** `borderRadius: RADIUS.xl = 32px` — pill-shaped on all sizes.
|
|
311
460
|
|
|
312
461
|
**Animations:** Scale springs to 0.95 on `onPressIn`, back to 1.0 on `onPressOut`.
|
|
313
462
|
|
|
314
|
-
**
|
|
463
|
+
**Haptics:** `impactLight` on every press.
|
|
464
|
+
|
|
465
|
+
**Examples:**
|
|
466
|
+
```tsx
|
|
467
|
+
// Standard CTA
|
|
468
|
+
<Button label="Continue" onPress={handleContinue} />
|
|
469
|
+
|
|
470
|
+
// Destructive action
|
|
471
|
+
<Button label="Delete account" variant="destructive" onPress={handleDelete} />
|
|
472
|
+
|
|
473
|
+
// Cancel (low emphasis)
|
|
474
|
+
<Button label="Cancel" variant="text" onPress={onCancel} />
|
|
475
|
+
|
|
476
|
+
// Alternative action
|
|
477
|
+
<Button label="Save draft" variant="secondary" onPress={saveDraft} />
|
|
478
|
+
|
|
479
|
+
// Loading state
|
|
480
|
+
<Button label="Saving..." loading onPress={handleSave} />
|
|
481
|
+
|
|
482
|
+
// Full width with icon
|
|
483
|
+
<Button label="Get started" iconName="arrow-right" iconPosition="right" fullWidth />
|
|
484
|
+
|
|
485
|
+
// Destructive with icon
|
|
486
|
+
<Button label="Delete" variant="destructive" iconName="trash-2" size="sm" />
|
|
487
|
+
|
|
488
|
+
// Large with icon on left
|
|
489
|
+
<Button label="Add to trip" iconName="plus" size="lg" />
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**Composition — action pair (primary + secondary):**
|
|
315
493
|
```tsx
|
|
316
|
-
<
|
|
317
|
-
<Button label="
|
|
318
|
-
<Button label="
|
|
319
|
-
|
|
320
|
-
<Button label="Continue" iconName="arrow-right" iconPosition="right" />
|
|
321
|
-
<Button label="Delete" variant="destructive" iconName="trash-2" />
|
|
494
|
+
<View style={{ gap: SPACING.sm }}>
|
|
495
|
+
<Button label="Book now" fullWidth />
|
|
496
|
+
<Button label="Save for later" variant="secondary" fullWidth />
|
|
497
|
+
</View>
|
|
322
498
|
```
|
|
323
499
|
|
|
324
500
|
---
|
|
@@ -326,29 +502,81 @@ const isWide = width >= BREAKPOINTS.wide
|
|
|
326
502
|
### IconButton
|
|
327
503
|
|
|
328
504
|
**Import:** `import { IconButton } from '@retray-dev/ui-kit'`
|
|
329
|
-
|
|
330
|
-
**
|
|
505
|
+
|
|
506
|
+
**When to use:** Icon-only pressable actions — toolbars, FABs, navigation actions, close buttons, header actions. Use `Button` when a text label is needed.
|
|
507
|
+
|
|
508
|
+
**Extends:** `TouchableOpacityProps` from React Native
|
|
331
509
|
|
|
332
510
|
| Prop | Type | Default | Notes |
|
|
333
511
|
|------|------|---------|-------|
|
|
334
|
-
| iconName | `string` | — | Icon name from `@expo/vector-icons
|
|
335
|
-
| icon | `
|
|
512
|
+
| iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon` |
|
|
513
|
+
| icon | `ReactNode` | — | Custom icon node when `iconName` is not provided |
|
|
336
514
|
| iconColor | `string` | — | Override icon color. Defaults to variant foreground color |
|
|
337
|
-
| variant | `'primary' \| 'secondary' \| 'outline' \| '
|
|
338
|
-
| size | `'sm' \| 'md' \| 'lg'` | `'md'` |
|
|
339
|
-
| loading | `boolean` | `false` | Replaces icon with
|
|
515
|
+
| variant | `'primary' \| 'secondary' \| 'outline' \| 'text' \| 'destructive'` | `'primary'` | Visual style |
|
|
516
|
+
| size | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
|
|
517
|
+
| loading | `boolean` | `false` | Replaces icon with spinner |
|
|
518
|
+
| badge | `boolean \| number` | — | Notification badge overlay |
|
|
340
519
|
| disabled | `boolean` | — | Reduces opacity to 0.5 |
|
|
520
|
+
| style | `ViewStyle` | — | — |
|
|
521
|
+
|
|
522
|
+
**Variants:** Same token logic as Button.
|
|
341
523
|
|
|
342
|
-
|
|
524
|
+
| Variant | Background | Border | Icon color | When to use |
|
|
525
|
+
|---------|-----------|--------|-----------|-------------|
|
|
526
|
+
| `primary` | `primary` | none | `primaryForeground` | Prominent action (FAB, main toolbar action) |
|
|
527
|
+
| `secondary` | `surface` | none | `foreground` | Secondary toolbar action |
|
|
528
|
+
| `outline` | transparent | `border` | `foreground` | Tertiary action with visible boundary |
|
|
529
|
+
| `text` | transparent | none | `foreground` | Inline action, header close/back |
|
|
530
|
+
| `destructive` | `destructive` | none | `destructiveForeground` | Delete/remove action |
|
|
343
531
|
|
|
344
|
-
**
|
|
532
|
+
**Sizes:**
|
|
533
|
+
|
|
534
|
+
| Size | Dimensions | Icon size |
|
|
535
|
+
|------|-----------|-----------|
|
|
536
|
+
| `sm` | 32 × 32px | 16px |
|
|
537
|
+
| `md` | 44 × 44px | 20px |
|
|
538
|
+
| `lg` | 52 × 52px | 24px |
|
|
539
|
+
|
|
540
|
+
**Badge prop:**
|
|
541
|
+
- `true` → 8×8px dot (primary color, top-right corner)
|
|
542
|
+
- `number` → 16×16px pill with count, capped at 99 (shows "99+" for higher values)
|
|
543
|
+
- Badge is positioned absolutely — does not affect button layout
|
|
544
|
+
|
|
545
|
+
**Animations:** Scale springs to 0.95 on press.
|
|
546
|
+
|
|
547
|
+
**Haptics:** `impactLight` on press.
|
|
345
548
|
|
|
346
|
-
**
|
|
549
|
+
**Examples:**
|
|
347
550
|
```tsx
|
|
348
|
-
|
|
349
|
-
<IconButton iconName="x" variant="
|
|
551
|
+
// Close button
|
|
552
|
+
<IconButton iconName="x" variant="text" size="sm" onPress={handleClose} />
|
|
553
|
+
|
|
554
|
+
// FAB
|
|
555
|
+
<IconButton iconName="plus" size="lg" onPress={handleAdd} />
|
|
556
|
+
|
|
557
|
+
// Delete action
|
|
350
558
|
<IconButton iconName="trash-2" variant="destructive" onPress={handleDelete} />
|
|
351
|
-
|
|
559
|
+
|
|
560
|
+
// Notification bell with badge count
|
|
561
|
+
<IconButton iconName="bell" variant="text" badge={3} onPress={() => navigate('notifications')} />
|
|
562
|
+
|
|
563
|
+
// Unread dot (boolean badge)
|
|
564
|
+
<IconButton iconName="mail" variant="outline" badge={true} onPress={() => {}} />
|
|
565
|
+
|
|
566
|
+
// Header back button
|
|
567
|
+
<IconButton iconName="arrow-left" variant="text" onPress={navigation.goBack} />
|
|
568
|
+
|
|
569
|
+
// Loading state while action runs
|
|
570
|
+
<IconButton iconName="save" loading={saving} onPress={handleSave} />
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**Composition — toolbar row:**
|
|
574
|
+
```tsx
|
|
575
|
+
<View style={{ flexDirection: 'row', gap: SPACING.xs }}>
|
|
576
|
+
<IconButton iconName="share" variant="text" onPress={handleShare} />
|
|
577
|
+
<IconButton iconName="bookmark" variant="text" onPress={handleBookmark} />
|
|
578
|
+
<IconButton iconName="more-horizontal" variant="text" onPress={handleMore} />
|
|
579
|
+
</View>
|
|
352
580
|
```
|
|
353
581
|
|
|
354
582
|
---
|
|
@@ -356,36 +584,114 @@ const isWide = width >= BREAKPOINTS.wide
|
|
|
356
584
|
### Input
|
|
357
585
|
|
|
358
586
|
**Import:** `import { Input } from '@retray-dev/ui-kit'`
|
|
359
|
-
|
|
360
|
-
**
|
|
587
|
+
|
|
588
|
+
**When to use:** Single-line text entry in forms. Includes label, error, hint, and icon/prefix/suffix support. Covers email, phone, search, password, and custom numeric inputs.
|
|
589
|
+
|
|
590
|
+
**Extends:** `TextInputProps` from React Native — all native props pass through (`keyboardType`, `autoCapitalize`, `returnKeyType`, etc.)
|
|
361
591
|
|
|
362
592
|
| Prop | Type | Default | Notes |
|
|
363
593
|
|------|------|---------|-------|
|
|
364
|
-
| label | `string` | — | Label above the input |
|
|
365
|
-
| error | `string` | — |
|
|
594
|
+
| label | `string` | — | Label above the input field |
|
|
595
|
+
| error | `string` | — | Error text below — turns border `destructive` (2px) |
|
|
366
596
|
| hint | `string` | — | Helper text below (hidden when `error` is set) |
|
|
367
|
-
| prefix | `string \| ReactNode` | — | Content
|
|
368
|
-
| suffix | `string \| ReactNode` | — | Content
|
|
369
|
-
| prefixStyle | `TextStyle` | — | Style
|
|
370
|
-
| suffixStyle | `TextStyle` | — | Style
|
|
371
|
-
| prefixIcon | `string` | — | Icon name
|
|
372
|
-
| suffixIcon | `string` | — | Icon name
|
|
373
|
-
| prefixIconColor | `string` | — | Override prefix icon color. Defaults to `
|
|
374
|
-
| suffixIconColor | `string` | — | Override suffix icon color. Defaults to `
|
|
375
|
-
| type | `'text' \| 'password'` | `'text'` |
|
|
376
|
-
| containerStyle | `ViewStyle` | — |
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
**
|
|
597
|
+
| prefix | `string \| ReactNode` | — | Content before the input text (e.g. `"$"`, icon node) |
|
|
598
|
+
| suffix | `string \| ReactNode` | — | Content after the input text (e.g. `"kg"`, icon node) |
|
|
599
|
+
| prefixStyle | `TextStyle` | — | Style for prefix string (no effect on ReactNode) |
|
|
600
|
+
| suffixStyle | `TextStyle` | — | Style for suffix string (no effect on ReactNode) |
|
|
601
|
+
| prefixIcon | `string` | — | Icon name rendered before input. Takes precedence over `prefix` |
|
|
602
|
+
| suffixIcon | `string` | — | Icon name rendered after input. Takes precedence over `suffix` unless `type="password"` |
|
|
603
|
+
| prefixIconColor | `string` | — | Override prefix icon color. Defaults to `foregroundMuted` |
|
|
604
|
+
| suffixIconColor | `string` | — | Override suffix icon color. Defaults to `foregroundMuted` |
|
|
605
|
+
| type | `'text' \| 'password'` | `'text'` | Password shows eye/eye-off toggle button in suffix slot |
|
|
606
|
+
| containerStyle | `ViewStyle` | — | Outer container View style |
|
|
607
|
+
| inputWrapperStyle | `ViewStyle` | — | The bordered wrapper around the input row |
|
|
608
|
+
| style | `TextStyle` | — | TextInput element style |
|
|
609
|
+
|
|
610
|
+
**Dimensions:** 56px height (14px vertical padding), 8px border radius.
|
|
611
|
+
|
|
612
|
+
**Border states:**
|
|
613
|
+
- Default: `border` token (1px)
|
|
614
|
+
- Focused: `primary` (2px)
|
|
615
|
+
- Error: `destructive` (2px)
|
|
616
|
+
- Focused + error: `destructive` (2px)
|
|
617
|
+
|
|
618
|
+
**Examples:**
|
|
381
619
|
```tsx
|
|
382
|
-
|
|
620
|
+
// Standard email input
|
|
621
|
+
<Input label="Email" placeholder="you@example.com" keyboardType="email-address" autoCapitalize="none" />
|
|
622
|
+
|
|
623
|
+
// Password with toggle
|
|
383
624
|
<Input label="Password" type="password" error="Incorrect password" />
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
<Input label="
|
|
625
|
+
|
|
626
|
+
// With hint text
|
|
627
|
+
<Input label="Username" hint="4–20 characters, letters and numbers only" />
|
|
628
|
+
|
|
629
|
+
// Currency prefix (string)
|
|
630
|
+
<Input label="Amount" prefix="$" placeholder="0.00" keyboardType="decimal-pad" />
|
|
631
|
+
|
|
632
|
+
// Unit suffix
|
|
633
|
+
<Input label="Weight" suffix="kg" placeholder="0" keyboardType="numeric" />
|
|
634
|
+
|
|
635
|
+
// Search (no label, prefix icon)
|
|
387
636
|
<Input placeholder="Search..." prefixIcon="search" />
|
|
637
|
+
|
|
638
|
+
// With both icons
|
|
388
639
|
<Input placeholder="Amount" prefixIcon="dollar-sign" suffixIcon="check" />
|
|
640
|
+
|
|
641
|
+
// With custom ReactNode prefix
|
|
642
|
+
<Input
|
|
643
|
+
label="Phone"
|
|
644
|
+
prefix={<Text variant="body-md">+1</Text>}
|
|
645
|
+
keyboardType="phone-pad"
|
|
646
|
+
/>
|
|
647
|
+
|
|
648
|
+
// Controlled with error
|
|
649
|
+
<Input
|
|
650
|
+
label="Email"
|
|
651
|
+
value={email}
|
|
652
|
+
onChangeText={setEmail}
|
|
653
|
+
error={emailError}
|
|
654
|
+
keyboardType="email-address"
|
|
655
|
+
/>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Composition — form fields:**
|
|
659
|
+
```tsx
|
|
660
|
+
<View style={{ gap: SPACING.md }}>
|
|
661
|
+
<Input label="First name" value={firstName} onChangeText={setFirstName} />
|
|
662
|
+
<Input label="Last name" value={lastName} onChangeText={setLastName} />
|
|
663
|
+
<Input label="Email" value={email} onChangeText={setEmail} error={emailError} keyboardType="email-address" />
|
|
664
|
+
<Input label="Password" type="password" value={password} onChangeText={setPassword} />
|
|
665
|
+
<Button label="Create account" fullWidth onPress={handleSubmit} />
|
|
666
|
+
</View>
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
### Textarea
|
|
672
|
+
|
|
673
|
+
**Import:** `import { Textarea } from '@retray-dev/ui-kit'`
|
|
674
|
+
|
|
675
|
+
**When to use:** Multi-line text entry — bios, descriptions, notes, reviews. Same visual design as `Input` but taller with multi-line support.
|
|
676
|
+
|
|
677
|
+
**Extends:** `TextInputProps` from React Native — all native props pass through.
|
|
678
|
+
|
|
679
|
+
| Prop | Type | Default | Notes |
|
|
680
|
+
|------|------|---------|-------|
|
|
681
|
+
| label | `string` | — | Label above the textarea |
|
|
682
|
+
| error | `string` | — | Error text below; turns border destructive (2px) |
|
|
683
|
+
| hint | `string` | — | Helper text below (hidden when `error` is set) |
|
|
684
|
+
| rows | `number` | `4` | Minimum row count — each row ≈ 30px. Sets `numberOfLines` |
|
|
685
|
+
| containerStyle | `ViewStyle` | — | Outer container style |
|
|
686
|
+
| style | `TextStyle` | — | TextInput element style |
|
|
687
|
+
|
|
688
|
+
**Border states:** Identical to Input (focused=primary 2px, error=destructive 2px).
|
|
689
|
+
|
|
690
|
+
**Examples:**
|
|
691
|
+
```tsx
|
|
692
|
+
<Textarea label="Bio" placeholder="Tell us about yourself" rows={5} />
|
|
693
|
+
<Textarea label="Review" hint="Be honest and helpful" />
|
|
694
|
+
<Textarea label="Notes" error="Notes are required" rows={3} />
|
|
389
695
|
```
|
|
390
696
|
|
|
391
697
|
---
|
|
@@ -393,29 +699,32 @@ const isWide = width >= BREAKPOINTS.wide
|
|
|
393
699
|
### CurrencyInput
|
|
394
700
|
|
|
395
701
|
**Import:** `import { CurrencyInput } from '@retray-dev/ui-kit'`
|
|
396
|
-
|
|
702
|
+
|
|
703
|
+
**When to use:** Monetary or numeric entries that need thousands-separator formatting while typing. Amount entry, price fields, transfer amounts. Wraps `Input` — shares the same label, error, hint, and visual design.
|
|
397
704
|
|
|
398
705
|
| Prop | Type | Default | Notes |
|
|
399
706
|
|------|------|---------|-------|
|
|
400
|
-
| value | `string` | — | Controlled display value
|
|
401
|
-
| onChangeText | `(formatted: string) => void` | — | Called with the formatted display string |
|
|
707
|
+
| value | `string` | — | Controlled display value including prefix (e.g. `'$1.234'`) |
|
|
708
|
+
| onChangeText | `(formatted: string) => void` | — | Called with the formatted display string on each keystroke |
|
|
402
709
|
| onChangeValue | `(raw: number) => void` | — | Called with the parsed number (no separators, no prefix) |
|
|
403
|
-
| prefix | `string` | `'$'` | Symbol prepended to
|
|
404
|
-
| thousandsSeparator | `'.' \| ','` | `'.'` | Character
|
|
405
|
-
| size | `'default' \| 'large'` | `'default'` | `'large'` uses 36px font
|
|
406
|
-
| label | `string` | — | Label above
|
|
407
|
-
| error | `string` | — | Error text below
|
|
710
|
+
| prefix | `string` | `'$'` | Symbol prepended to formatted value |
|
|
711
|
+
| thousandsSeparator | `'.' \| ','` | `'.'` | Character separating groups of three digits |
|
|
712
|
+
| size | `'default' \| 'large'` | `'default'` | `'large'` uses 36px font for prominent payment screens |
|
|
713
|
+
| label | `string` | — | Label above input |
|
|
714
|
+
| error | `string` | — | Error text below |
|
|
408
715
|
| hint | `string` | — | Helper text below (hidden when `error` is set) |
|
|
409
716
|
| placeholder | `string` | `'$0'` | Defaults to `prefix + '0'` |
|
|
410
|
-
| editable | `boolean` | — | Pass `false` to disable
|
|
411
|
-
| containerStyle | `ViewStyle` | — |
|
|
717
|
+
| editable | `boolean` | — | Pass `false` to disable |
|
|
718
|
+
| containerStyle | `ViewStyle` | — | Outer container style |
|
|
719
|
+
|
|
720
|
+
**Formatting behavior:** Strips all non-digit characters from input, then re-applies thousands separator every 3 digits from the right, then prepends `prefix`. Decimal point input is not supported — this is for whole-number monetary amounts.
|
|
412
721
|
|
|
413
|
-
**
|
|
722
|
+
**Examples:**
|
|
414
723
|
```tsx
|
|
415
724
|
const [display, setDisplay] = useState('')
|
|
416
725
|
const [amount, setAmount] = useState(0)
|
|
417
726
|
|
|
418
|
-
//
|
|
727
|
+
// Standard with dot separator ($1.234.567)
|
|
419
728
|
<CurrencyInput
|
|
420
729
|
label="Amount"
|
|
421
730
|
value={display}
|
|
@@ -424,7 +733,7 @@ const [amount, setAmount] = useState(0)
|
|
|
424
733
|
hint={`Parsed: ${amount}`}
|
|
425
734
|
/>
|
|
426
735
|
|
|
427
|
-
// Comma
|
|
736
|
+
// Comma separator ($1,234,567)
|
|
428
737
|
<CurrencyInput
|
|
429
738
|
prefix="$"
|
|
430
739
|
thousandsSeparator=","
|
|
@@ -432,27 +741,67 @@ const [amount, setAmount] = useState(0)
|
|
|
432
741
|
onChangeText={setDisplay}
|
|
433
742
|
onChangeValue={setAmount}
|
|
434
743
|
/>
|
|
744
|
+
|
|
745
|
+
// Large variant for payment screens
|
|
746
|
+
<CurrencyInput
|
|
747
|
+
size="large"
|
|
748
|
+
label="Transfer amount"
|
|
749
|
+
value={display}
|
|
750
|
+
onChangeText={setDisplay}
|
|
751
|
+
onChangeValue={setAmount}
|
|
752
|
+
/>
|
|
753
|
+
|
|
754
|
+
// Euro with error
|
|
755
|
+
<CurrencyInput
|
|
756
|
+
prefix="€"
|
|
757
|
+
label="Price"
|
|
758
|
+
value={display}
|
|
759
|
+
onChangeText={setDisplay}
|
|
760
|
+
onChangeValue={setAmount}
|
|
761
|
+
error={amount < 1000 ? "Minimum amount is €1.000" : undefined}
|
|
762
|
+
/>
|
|
435
763
|
```
|
|
436
764
|
|
|
437
765
|
---
|
|
438
766
|
|
|
439
|
-
###
|
|
767
|
+
### CurrencyDisplay
|
|
440
768
|
|
|
441
|
-
**Import:** `import {
|
|
442
|
-
|
|
443
|
-
**
|
|
769
|
+
**Import:** `import { CurrencyDisplay } from '@retray-dev/ui-kit'`
|
|
770
|
+
|
|
771
|
+
**When to use:** Prominent read-only display of monetary values — account balances, totals, summary screens. Uses `display-hero` typography (56pt bold) for visual impact.
|
|
444
772
|
|
|
445
773
|
| Prop | Type | Default | Notes |
|
|
446
774
|
|------|------|---------|-------|
|
|
447
|
-
|
|
|
448
|
-
|
|
|
449
|
-
|
|
|
450
|
-
|
|
|
451
|
-
|
|
|
775
|
+
| value | `number \| string` | required | Numeric value to display |
|
|
776
|
+
| prefix | `string` | `'$'` | Symbol prepended to formatted value |
|
|
777
|
+
| showDecimals | `boolean` | `false` | Show two decimal places with comma separator (e.g. `$25.000,00`) |
|
|
778
|
+
| textColor | `string` | — | Override text color. Defaults to `foreground` token |
|
|
779
|
+
| style | `ViewStyle` | — | Outer container style |
|
|
780
|
+
|
|
781
|
+
**Format:** Dot (`.`) as thousands separator, comma (`,`) as decimal separator — Latin American / European format.
|
|
452
782
|
|
|
453
|
-
**
|
|
783
|
+
**Examples:**
|
|
454
784
|
```tsx
|
|
455
|
-
<
|
|
785
|
+
<CurrencyDisplay value={25000} />
|
|
786
|
+
// → $25.000
|
|
787
|
+
|
|
788
|
+
<CurrencyDisplay value={25000000.5} showDecimals />
|
|
789
|
+
// → $25.000.000,50
|
|
790
|
+
|
|
791
|
+
<CurrencyDisplay value={1500} prefix="€" />
|
|
792
|
+
// → €1.500
|
|
793
|
+
|
|
794
|
+
<CurrencyDisplay value={balance} textColor={balance < 0 ? colors.destructive : colors.foreground} />
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
**Composition — balance card:**
|
|
798
|
+
```tsx
|
|
799
|
+
<Card>
|
|
800
|
+
<CardContent>
|
|
801
|
+
<Text variant="caption-sm" color={colors.foregroundMuted}>Available balance</Text>
|
|
802
|
+
<CurrencyDisplay value={12500} />
|
|
803
|
+
</CardContent>
|
|
804
|
+
</Card>
|
|
456
805
|
```
|
|
457
806
|
|
|
458
807
|
---
|
|
@@ -460,27 +809,59 @@ const [amount, setAmount] = useState(0)
|
|
|
460
809
|
### Badge
|
|
461
810
|
|
|
462
811
|
**Import:** `import { Badge } from '@retray-dev/ui-kit'`
|
|
463
|
-
|
|
812
|
+
|
|
813
|
+
**When to use:** Status labels, counts, category tags, feature flags. Pill-shaped with optional icon.
|
|
464
814
|
|
|
465
815
|
| Prop | Type | Default | Notes |
|
|
466
816
|
|------|------|---------|-------|
|
|
467
|
-
| label | `string` | — | Badge text
|
|
817
|
+
| label | `string` | — | Badge text. Can be omitted when using `children` |
|
|
468
818
|
| children | `ReactNode` | — | Alternative to `label` for custom content |
|
|
469
|
-
| variant | `
|
|
819
|
+
| variant | `BadgeVariant` | `'default'` | Visual style |
|
|
470
820
|
| size | `'sm' \| 'md' \| 'lg'` | `'md'` | Controls padding and font size |
|
|
471
|
-
| icon | `ReactNode` | — | Icon rendered before
|
|
821
|
+
| icon | `ReactNode` | — | Icon rendered before label |
|
|
472
822
|
| iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon` |
|
|
473
823
|
| iconColor | `string` | — | Override icon color. Defaults to variant foreground color |
|
|
474
824
|
| style | `ViewStyle` | — | — |
|
|
475
825
|
|
|
476
|
-
**
|
|
826
|
+
**Variants:**
|
|
827
|
+
|
|
828
|
+
| Variant | Background | Text | Border | When to use |
|
|
829
|
+
|---------|-----------|------|--------|-------------|
|
|
830
|
+
| `default` | `primary` | `primaryForeground` | none | Primary status — active, featured |
|
|
831
|
+
| `secondary` | `surface` | `foregroundSubtle` | `border` | Neutral — draft, inactive, secondary status |
|
|
832
|
+
| `destructive` | `destructive` | `destructiveForeground` | none | Error, failed, danger |
|
|
833
|
+
| `outline` | transparent | `foreground` | `border` | Subtle tag, no emphasis |
|
|
834
|
+
| `success` | `success` | `successForeground` | none | Completed, verified, paid |
|
|
835
|
+
| `warning` | `warning` | `warningForeground` | none | Pending, caution, expiring |
|
|
836
|
+
| `successOutline` | `successTint` | `success` | `successBorder` | Soft success (less visual weight) |
|
|
837
|
+
| `destructiveOutline` | `destructiveTint` | `destructive` | `destructiveBorder` | Soft error (less visual weight) |
|
|
838
|
+
| `warningOutline` | `warningTint` | `warning` | `warningBorder` | Soft warning (less visual weight) |
|
|
839
|
+
|
|
840
|
+
**Sizes:**
|
|
841
|
+
|
|
842
|
+
| Size | Padding (V×H) | Font size | Icon size |
|
|
843
|
+
|------|--------------|-----------|-----------|
|
|
844
|
+
| `sm` | 2 × 8 | 11pt | 10px |
|
|
845
|
+
| `md` | 4 × 10 | 13pt | 12px |
|
|
846
|
+
| `lg` | 6 × 12 | 15pt | 14px |
|
|
847
|
+
|
|
848
|
+
**All badges have `borderRadius: 9999` (pill-shaped).**
|
|
849
|
+
|
|
850
|
+
**Examples:**
|
|
477
851
|
```tsx
|
|
478
852
|
<Badge label="New" />
|
|
479
853
|
<Badge label="Error" variant="destructive" />
|
|
480
854
|
<Badge label="Draft" variant="secondary" size="sm" />
|
|
481
855
|
<Badge label="Beta" variant="outline" />
|
|
482
|
-
<Badge label="
|
|
483
|
-
<Badge label="
|
|
856
|
+
<Badge label="Paid" variant="success" iconName="check" />
|
|
857
|
+
<Badge label="Pending" variant="warningOutline" />
|
|
858
|
+
<Badge label="3" variant="destructive" size="sm" />
|
|
859
|
+
|
|
860
|
+
// With status in ListItem
|
|
861
|
+
<ListItem
|
|
862
|
+
title="Payment"
|
|
863
|
+
rightRender={<Badge label="Pending" variant="warningOutline" size="sm" />}
|
|
864
|
+
/>
|
|
484
865
|
```
|
|
485
866
|
|
|
486
867
|
---
|
|
@@ -488,245 +869,452 @@ const [amount, setAmount] = useState(0)
|
|
|
488
869
|
### Avatar
|
|
489
870
|
|
|
490
871
|
**Import:** `import { Avatar } from '@retray-dev/ui-kit'`
|
|
491
|
-
|
|
872
|
+
|
|
873
|
+
**When to use:** User profile pictures with automatic fallback to initials. Works with remote URIs and local images.
|
|
492
874
|
|
|
493
875
|
| Prop | Type | Default | Notes |
|
|
494
876
|
|------|------|---------|-------|
|
|
495
|
-
| src | `string` | — | Image URI |
|
|
496
|
-
| fallback | `string` | — | Text shown when image fails or `src` is absent — first 2
|
|
497
|
-
| size | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` |
|
|
877
|
+
| src | `string` | — | Image URI (remote or local) |
|
|
878
|
+
| fallback | `string` | — | Text shown when image fails or `src` is absent — first 2 characters shown, uppercased |
|
|
879
|
+
| size | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Diameter in points |
|
|
880
|
+
| status | `'online' \| 'offline' \| 'busy' \| 'away'` | — | Status indicator dot, bottom-right |
|
|
498
881
|
| style | `ViewStyle` | — | — |
|
|
499
882
|
|
|
500
|
-
**
|
|
883
|
+
**Sizes:**
|
|
884
|
+
|
|
885
|
+
| Size | Diameter | Fallback font | Status dot size |
|
|
886
|
+
|------|---------|--------------|----------------|
|
|
887
|
+
| `sm` | 28px | 12pt | 8px |
|
|
888
|
+
| `md` | 40px | 16pt | 10px |
|
|
889
|
+
| `lg` | 56px | 22pt | 13px |
|
|
890
|
+
| `xl` | 72px | 28pt | 16px |
|
|
891
|
+
|
|
892
|
+
**Status dot colors:**
|
|
893
|
+
- `online` → `#22c55e` (green)
|
|
894
|
+
- `offline` → transparent fill, `border` color outline only
|
|
895
|
+
- `busy` → `destructive` token
|
|
896
|
+
- `away` → `warning` token
|
|
897
|
+
|
|
898
|
+
**Status dot is absolutely positioned at bottom-right with a 2px white border.**
|
|
899
|
+
|
|
900
|
+
**Examples:**
|
|
501
901
|
```tsx
|
|
502
902
|
<Avatar src="https://..." fallback="JC" size="lg" />
|
|
503
903
|
<Avatar fallback="AN" size="md" />
|
|
904
|
+
<Avatar src={user.avatar} fallback={user.initials} size="xl" status="online" />
|
|
905
|
+
<Avatar fallback="BU" size="sm" status="busy" />
|
|
906
|
+
|
|
907
|
+
// In a ListItem
|
|
908
|
+
<ListItem
|
|
909
|
+
leftRender={<Avatar src={user.avatar} fallback={user.initials} status="online" />}
|
|
910
|
+
title={user.name}
|
|
911
|
+
subtitle={user.role}
|
|
912
|
+
/>
|
|
504
913
|
```
|
|
505
914
|
|
|
506
915
|
---
|
|
507
916
|
|
|
508
|
-
###
|
|
917
|
+
### Card
|
|
509
918
|
|
|
510
|
-
**Import:** `import {
|
|
511
|
-
|
|
919
|
+
**Import:** `import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@retray-dev/ui-kit'`
|
|
920
|
+
|
|
921
|
+
**When to use:** Grouped content with a surface background. Use `Card` as the container and sub-components for structured layout.
|
|
922
|
+
|
|
923
|
+
**`Card` Props:**
|
|
512
924
|
|
|
513
925
|
| Prop | Type | Default | Notes |
|
|
514
926
|
|------|------|---------|-------|
|
|
515
|
-
|
|
|
927
|
+
| variant | `'elevated' \| 'outlined' \| 'filled'` | `'elevated'` | Visual style |
|
|
928
|
+
| onPress | `() => void` | — | Makes card pressable with scale animation + haptics |
|
|
929
|
+
| children | `ReactNode` | required | — |
|
|
516
930
|
| style | `ViewStyle` | — | — |
|
|
517
931
|
|
|
518
|
-
**
|
|
932
|
+
**Variants:**
|
|
933
|
+
|
|
934
|
+
| Variant | Background | Border | Shadow | When to use |
|
|
935
|
+
|---------|-----------|--------|--------|-------------|
|
|
936
|
+
| `elevated` | `card` | `border` (1px) | `SHADOWS.sm` | Default card — standard content groups |
|
|
937
|
+
| `outlined` | `card` | `border` (1px) | none | Flat bordered cards, lists of cards |
|
|
938
|
+
| `filled` | `surfaceStrong` | none | none | Background fill cards, stat blocks, sidebar items |
|
|
939
|
+
|
|
940
|
+
**Sub-components (all accept `style` prop):**
|
|
941
|
+
|
|
942
|
+
| Sub-component | Padding | Notes |
|
|
943
|
+
|--------------|---------|-------|
|
|
944
|
+
| `CardHeader` | 16px all, 0 bottom | Use for title + description |
|
|
945
|
+
| `CardTitle` | — | 18pt / SemiBold / `cardForeground` |
|
|
946
|
+
| `CardDescription` | — | 14pt / Regular / `foregroundMuted` |
|
|
947
|
+
| `CardContent` | 16px all (12px top) | Main content area |
|
|
948
|
+
| `CardFooter` | 16px horizontal, 14px bottom | Row layout, use for buttons |
|
|
949
|
+
|
|
950
|
+
**Shape:** `borderRadius: RADIUS.md = 14px`
|
|
951
|
+
|
|
952
|
+
**Press animation:** Scale springs to 0.98 (subtler than button — appropriate for large targets).
|
|
953
|
+
|
|
954
|
+
**Haptics:** `impactLight` when `onPress` is provided.
|
|
955
|
+
|
|
956
|
+
**Examples:**
|
|
519
957
|
```tsx
|
|
520
|
-
|
|
521
|
-
<
|
|
522
|
-
<
|
|
523
|
-
|
|
524
|
-
|
|
958
|
+
// Standard card
|
|
959
|
+
<Card>
|
|
960
|
+
<CardHeader>
|
|
961
|
+
<CardTitle>Account settings</CardTitle>
|
|
962
|
+
<CardDescription>Manage your profile and preferences</CardDescription>
|
|
963
|
+
</CardHeader>
|
|
964
|
+
<CardContent>
|
|
965
|
+
<Input label="Display name" value={name} onChangeText={setName} />
|
|
966
|
+
</CardContent>
|
|
967
|
+
<CardFooter>
|
|
968
|
+
<Button label="Save" fullWidth />
|
|
969
|
+
</CardFooter>
|
|
970
|
+
</Card>
|
|
971
|
+
|
|
972
|
+
// Pressable navigation card
|
|
973
|
+
<Card variant="outlined" onPress={() => navigate('profile')}>
|
|
974
|
+
<CardContent>
|
|
975
|
+
<Text variant="title-md">Profile</Text>
|
|
976
|
+
<Text variant="body-sm">Edit your information</Text>
|
|
977
|
+
</CardContent>
|
|
978
|
+
</Card>
|
|
979
|
+
|
|
980
|
+
// Stat block (filled)
|
|
981
|
+
<Card variant="filled">
|
|
982
|
+
<CardContent>
|
|
983
|
+
<Text variant="caption-sm" color={colors.foregroundMuted}>Total spent</Text>
|
|
984
|
+
<CurrencyDisplay value={totalSpent} />
|
|
985
|
+
</CardContent>
|
|
986
|
+
</Card>
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
**Composition — dashboard grid:**
|
|
990
|
+
```tsx
|
|
991
|
+
<View style={{ flexDirection: 'row', gap: SPACING.md }}>
|
|
992
|
+
<Card variant="filled" style={{ flex: 1 }}>
|
|
993
|
+
<CardContent>
|
|
994
|
+
<Text variant="micro-label" color={colors.foregroundMuted}>INCOME</Text>
|
|
995
|
+
<Text variant="display-md" color={colors.success}>$8.500</Text>
|
|
996
|
+
</CardContent>
|
|
997
|
+
</Card>
|
|
998
|
+
<Card variant="filled" style={{ flex: 1 }}>
|
|
999
|
+
<CardContent>
|
|
1000
|
+
<Text variant="micro-label" color={colors.foregroundMuted}>EXPENSES</Text>
|
|
1001
|
+
<Text variant="display-md" color={colors.destructive}>$3.200</Text>
|
|
1002
|
+
</CardContent>
|
|
1003
|
+
</Card>
|
|
525
1004
|
</View>
|
|
526
1005
|
```
|
|
527
1006
|
|
|
528
1007
|
---
|
|
529
1008
|
|
|
530
|
-
###
|
|
1009
|
+
### MediaCard
|
|
531
1010
|
|
|
532
|
-
**Import:** `import {
|
|
533
|
-
|
|
534
|
-
**
|
|
1011
|
+
**Import:** `import { MediaCard } from '@retray-dev/ui-kit'`
|
|
1012
|
+
|
|
1013
|
+
**When to use:** Image-first content cards — product listings, properties, experiences, photo-based content. Think Airbnb listing card or marketplace item.
|
|
535
1014
|
|
|
536
1015
|
| Prop | Type | Default | Notes |
|
|
537
1016
|
|------|------|---------|-------|
|
|
538
|
-
|
|
|
539
|
-
|
|
|
540
|
-
|
|
|
1017
|
+
| imageSource | `ImageSourcePropType` | — | Image — `{ uri: '...' }` or `require('./img.jpg')` |
|
|
1018
|
+
| aspectRatio | `MediaCardAspectRatio` | `'4:3'` | Controls image height relative to card width |
|
|
1019
|
+
| badge | `ReactNode` | — | Floating content top-left of image |
|
|
1020
|
+
| actionIcon | `ReactNode` | — | Icon top-right of image (defaults to heart outline) |
|
|
1021
|
+
| actionIconName | `string` | — | Icon name. Overrides `actionIcon` |
|
|
1022
|
+
| actionActive | `boolean` | `false` | Whether action is in active/filled state |
|
|
1023
|
+
| onActionPress | `() => void` | — | Press handler for action icon. Stops event propagation |
|
|
1024
|
+
| title | `string` | — | Below image — primary label |
|
|
1025
|
+
| subtitle | `string` | — | Secondary line below title |
|
|
1026
|
+
| caption | `string` | — | Tertiary metadata line |
|
|
1027
|
+
| footer | `ReactNode` | — | Custom content below caption (price, rating, etc.) |
|
|
1028
|
+
| onPress | `() => void` | — | Press the card body |
|
|
1029
|
+
| style | `ViewStyle` | — | Outer card style |
|
|
1030
|
+
| imageStyle | `ViewStyle` | — | Image container override |
|
|
541
1031
|
|
|
542
|
-
**
|
|
543
|
-
```tsx
|
|
544
|
-
<Spinner />
|
|
545
|
-
<Spinner size="lg" color="#6366f1" />
|
|
546
|
-
<Spinner size="lg" label="Loading..." />
|
|
547
|
-
```
|
|
1032
|
+
**Aspect ratios (`MediaCardAspectRatio`):**
|
|
548
1033
|
|
|
549
|
-
|
|
1034
|
+
| Value | Width:Height | When to use |
|
|
1035
|
+
|-------|-------------|-------------|
|
|
1036
|
+
| `'1:1'` | Square | Profile cards, avatars, square products |
|
|
1037
|
+
| `'4:3'` | Classic | Default — general listings |
|
|
1038
|
+
| `'16:9'` | Wide | Videos, wide landscape imagery |
|
|
1039
|
+
| `'4:5'` | Portrait | Tall product photos, fashion |
|
|
1040
|
+
| `'3:2'` | Photo | Standard photography ratio |
|
|
550
1041
|
|
|
551
|
-
|
|
1042
|
+
**Image implementation:** Uses `paddingTop` percentage for cross-platform consistent aspect ratios with `position: absolute` image fill — no fixed height needed.
|
|
552
1043
|
|
|
553
|
-
**
|
|
554
|
-
**When to use:** Placeholder while content is loading. Pulses with a looping opacity animation.
|
|
1044
|
+
**Shape:** `borderRadius: RADIUS.md = 14px`, `overflow: hidden`.
|
|
555
1045
|
|
|
556
|
-
|
|
557
|
-
|------|------|---------|-------|
|
|
558
|
-
| width | `number \| string` | `'100%'` | — |
|
|
559
|
-
| height | `number` | `16` | — |
|
|
560
|
-
| borderRadius | `number` | `6` | — |
|
|
561
|
-
| style | `ViewStyle` | — | — |
|
|
1046
|
+
**Action button:** 32×32px circle, semi-transparent background (24% opacity), top-right corner 8px from edges.
|
|
562
1047
|
|
|
563
|
-
**
|
|
564
|
-
```tsx
|
|
565
|
-
<Skeleton height={20} width="60%" />
|
|
566
|
-
<Skeleton height={80} borderRadius={10} />
|
|
567
|
-
```
|
|
1048
|
+
**Badge:** Positioned top-left 8px from edges — pass any ReactNode (Badge component recommended).
|
|
568
1049
|
|
|
569
|
-
|
|
1050
|
+
**Web hover:** Lifts shadow from `SHADOWS.sm` to `SHADOWS.md` on hover.
|
|
570
1051
|
|
|
571
|
-
|
|
1052
|
+
**Press animation:** Scale springs to 0.98.
|
|
572
1053
|
|
|
573
|
-
**
|
|
574
|
-
**When to use:** Show completion percentage for a task or upload.
|
|
1054
|
+
**Haptics:** `impactLight` on `onActionPress`.
|
|
575
1055
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
1056
|
+
**Examples:**
|
|
1057
|
+
```tsx
|
|
1058
|
+
// Basic listing card
|
|
1059
|
+
<MediaCard
|
|
1060
|
+
imageSource={{ uri: listing.photo }}
|
|
1061
|
+
title={listing.name}
|
|
1062
|
+
subtitle={listing.location}
|
|
1063
|
+
caption={`$${listing.price} / night`}
|
|
1064
|
+
onPress={() => navigate('listing', { id: listing.id })}
|
|
1065
|
+
/>
|
|
581
1066
|
|
|
582
|
-
|
|
1067
|
+
// With save action and aspect ratio
|
|
1068
|
+
<MediaCard
|
|
1069
|
+
imageSource={{ uri: product.image }}
|
|
1070
|
+
aspectRatio="4:5"
|
|
1071
|
+
title={product.name}
|
|
1072
|
+
subtitle={product.brand}
|
|
1073
|
+
actionActive={saved}
|
|
1074
|
+
onActionPress={() => toggleSave(product.id)}
|
|
1075
|
+
onPress={() => navigate('product', { id: product.id })}
|
|
1076
|
+
/>
|
|
1077
|
+
|
|
1078
|
+
// With badge overlay
|
|
1079
|
+
<MediaCard
|
|
1080
|
+
imageSource={{ uri: item.photo }}
|
|
1081
|
+
badge={<Badge label="New" size="sm" />}
|
|
1082
|
+
title={item.name}
|
|
1083
|
+
onPress={() => {}}
|
|
1084
|
+
/>
|
|
1085
|
+
|
|
1086
|
+
// With custom footer (price + rating)
|
|
1087
|
+
<MediaCard
|
|
1088
|
+
imageSource={{ uri: place.photo }}
|
|
1089
|
+
aspectRatio="4:3"
|
|
1090
|
+
title={place.name}
|
|
1091
|
+
footer={
|
|
1092
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
1093
|
+
<Text variant="title-sm">$120 / night</Text>
|
|
1094
|
+
<Text variant="caption-sm">★ 4.8</Text>
|
|
1095
|
+
</View>
|
|
1096
|
+
}
|
|
1097
|
+
onPress={() => navigate('place', { id: place.id })}
|
|
1098
|
+
/>
|
|
1099
|
+
```
|
|
583
1100
|
|
|
584
|
-
**
|
|
1101
|
+
**Composition — listing grid:**
|
|
585
1102
|
```tsx
|
|
586
|
-
<
|
|
587
|
-
|
|
1103
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: SPACING.md }}>
|
|
1104
|
+
{listings.map((item) => (
|
|
1105
|
+
<MediaCard
|
|
1106
|
+
key={item.id}
|
|
1107
|
+
style={{ width: (screenWidth - SPACING.base * 2 - SPACING.md) / 2 }}
|
|
1108
|
+
imageSource={{ uri: item.photo }}
|
|
1109
|
+
title={item.name}
|
|
1110
|
+
caption={item.price}
|
|
1111
|
+
actionActive={savedIds.includes(item.id)}
|
|
1112
|
+
onActionPress={() => toggleSave(item.id)}
|
|
1113
|
+
onPress={() => navigate('listing', { id: item.id })}
|
|
1114
|
+
/>
|
|
1115
|
+
))}
|
|
1116
|
+
</View>
|
|
588
1117
|
```
|
|
589
1118
|
|
|
590
1119
|
---
|
|
591
1120
|
|
|
592
|
-
###
|
|
1121
|
+
### Separator
|
|
593
1122
|
|
|
594
|
-
**Import:** `import {
|
|
595
|
-
|
|
1123
|
+
**Import:** `import { Separator } from '@retray-dev/ui-kit'`
|
|
1124
|
+
|
|
1125
|
+
**When to use:** Visual divider between sections, list items, or columns.
|
|
596
1126
|
|
|
597
1127
|
| Prop | Type | Default | Notes |
|
|
598
1128
|
|------|------|---------|-------|
|
|
599
|
-
|
|
|
600
|
-
|
|
|
601
|
-
| showDecimals | `boolean` | `false` | When `true`, shows two decimal places separated by a comma (e.g. `$25.000,00`) |
|
|
602
|
-
| textColor | `string` | — | Override the color of the formatted text. Defaults to the `foreground` token |
|
|
603
|
-
| style | `ViewStyle` | — | Style override for outer container |
|
|
1129
|
+
| orientation | `'horizontal' \| 'vertical'` | `'horizontal'` | Direction of the line |
|
|
1130
|
+
| style | `ViewStyle` | — | — |
|
|
604
1131
|
|
|
605
|
-
**
|
|
1132
|
+
**Styling:** 1px thickness, `border` token color.
|
|
1133
|
+
|
|
1134
|
+
**Examples:**
|
|
606
1135
|
```tsx
|
|
607
|
-
|
|
608
|
-
|
|
1136
|
+
// Section divider
|
|
1137
|
+
<Separator />
|
|
609
1138
|
|
|
610
|
-
|
|
611
|
-
|
|
1139
|
+
// Vertical divider (parent must have defined height)
|
|
1140
|
+
<View style={{ flexDirection: 'row', height: 40, alignItems: 'center' }}>
|
|
1141
|
+
<Text>Left</Text>
|
|
1142
|
+
<Separator orientation="vertical" style={{ marginHorizontal: 12 }} />
|
|
1143
|
+
<Text>Right</Text>
|
|
1144
|
+
</View>
|
|
612
1145
|
|
|
613
|
-
|
|
614
|
-
|
|
1146
|
+
// With custom spacing
|
|
1147
|
+
<Separator style={{ marginVertical: SPACING.lg }} />
|
|
615
1148
|
```
|
|
616
1149
|
|
|
617
|
-
**Notes:**
|
|
618
|
-
- Uses dot (`.`) as thousands separator and comma (`,`) as decimal separator — Latin American / European format.
|
|
619
|
-
- Intended as a display-only component (not editable).
|
|
620
|
-
- `allowFontScaling={true}` is set on the inner `Text`.
|
|
621
|
-
|
|
622
1150
|
---
|
|
623
1151
|
|
|
624
|
-
###
|
|
1152
|
+
### Spinner
|
|
1153
|
+
|
|
1154
|
+
**Import:** `import { Spinner } from '@retray-dev/ui-kit'`
|
|
1155
|
+
|
|
1156
|
+
**When to use:** Loading state indicator. Use inline (within buttons or content areas) or full-screen.
|
|
625
1157
|
|
|
626
|
-
**
|
|
627
|
-
**When to use:** Large, form-style monetary input for prominent entry screens (payments, transfers). Same formatting behavior as `CurrencyInput` but with 36px font size.
|
|
1158
|
+
**Extends:** `ActivityIndicatorProps` (except `size` — use the string size prop instead)
|
|
628
1159
|
|
|
629
1160
|
| Prop | Type | Default | Notes |
|
|
630
1161
|
|------|------|---------|-------|
|
|
631
|
-
|
|
|
632
|
-
|
|
|
633
|
-
|
|
|
634
|
-
| label | `string` | — | Label above the input |
|
|
635
|
-
| prefix | `string` | `'$'` | Currency prefix |
|
|
636
|
-
| thousandsSeparator | `'.' \| ','` | `'.'` | Separator used to format groups of three digits |
|
|
637
|
-
| error | `string` | — | Error text below; turns border red |
|
|
638
|
-
| hint | `string` | — | Helper text below (hidden when `error` is set) |
|
|
639
|
-
| placeholder | `string` | `'$0'` | Defaults to `prefix + '0'` |
|
|
640
|
-
| editable | `boolean` | — | Pass `false` to disable editing |
|
|
641
|
-
| containerStyle | `ViewStyle` | — | Style for the outer container |
|
|
1162
|
+
| size | `'sm' \| 'md' \| 'lg'` | `'md'` | `sm`/`md` → RN `'small'`, `lg` → RN `'large'` |
|
|
1163
|
+
| color | `string` | `primary` token | Override spinner color |
|
|
1164
|
+
| label | `string` | — | Text displayed below spinner |
|
|
642
1165
|
|
|
643
|
-
**
|
|
1166
|
+
**Examples:**
|
|
644
1167
|
```tsx
|
|
645
|
-
|
|
646
|
-
|
|
1168
|
+
<Spinner />
|
|
1169
|
+
<Spinner size="lg" />
|
|
1170
|
+
<Spinner size="lg" label="Loading..." />
|
|
1171
|
+
<Spinner color={colors.foregroundMuted} />
|
|
647
1172
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
onChangeValue={setAmount}
|
|
653
|
-
/>
|
|
654
|
-
// Typing "25000" produces "$25.000"
|
|
1173
|
+
// Full-screen loading overlay
|
|
1174
|
+
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
|
1175
|
+
<Spinner size="lg" label="Fetching data..." />
|
|
1176
|
+
</View>
|
|
655
1177
|
```
|
|
656
1178
|
|
|
657
1179
|
---
|
|
658
1180
|
|
|
659
|
-
###
|
|
1181
|
+
### Skeleton
|
|
660
1182
|
|
|
661
|
-
**Import:** `import {
|
|
662
|
-
**When to use:** Grouped content with a surface background, border, and shadow.
|
|
1183
|
+
**Import:** `import { Skeleton } from '@retray-dev/ui-kit'`
|
|
663
1184
|
|
|
664
|
-
|
|
1185
|
+
**When to use:** Placeholder while content is loading. Use to match the shape of the content that will appear — creates a visual loading skeleton that prevents layout shift.
|
|
665
1186
|
|
|
666
1187
|
| Prop | Type | Default | Notes |
|
|
667
1188
|
|------|------|---------|-------|
|
|
668
|
-
|
|
|
669
|
-
|
|
|
670
|
-
|
|
|
1189
|
+
| width | `number \| string` | `'100%'` | Width of placeholder |
|
|
1190
|
+
| height | `number` | `16` | Height of placeholder |
|
|
1191
|
+
| borderRadius | `number` | `6` | Corner radius |
|
|
1192
|
+
| preset | `'base' \| 'circle' \| 'text'` | `'base'` | Convenience preset |
|
|
1193
|
+
| diameter | `number` | `40` | Used by `'circle'` preset — overrides width/height |
|
|
671
1194
|
| style | `ViewStyle` | — | — |
|
|
672
1195
|
|
|
673
|
-
|
|
1196
|
+
**Presets:**
|
|
1197
|
+
- `'base'` — Uses `width`, `height`, `borderRadius` exactly as specified
|
|
1198
|
+
- `'circle'` — Forces square dimensions from `diameter`, `borderRadius: 9999`
|
|
1199
|
+
- `'text'` — 60% width, 14px height, 4px border radius (typical text line shape)
|
|
1200
|
+
|
|
1201
|
+
**Animation:** Shimmer sweep loops every 1200ms. Highlight color adapts to light/dark mode.
|
|
674
1202
|
|
|
675
|
-
**
|
|
1203
|
+
**Examples:**
|
|
676
1204
|
```tsx
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1205
|
+
// Text line placeholders
|
|
1206
|
+
<Skeleton height={20} width="80%" />
|
|
1207
|
+
<Skeleton height={16} width="60%" style={{ marginTop: 8 }} />
|
|
1208
|
+
|
|
1209
|
+
// Circle avatar placeholder
|
|
1210
|
+
<Skeleton preset="circle" diameter={40} />
|
|
1211
|
+
|
|
1212
|
+
// Text preset (auto-sized)
|
|
1213
|
+
<Skeleton preset="text" />
|
|
1214
|
+
|
|
1215
|
+
// Custom card skeleton
|
|
1216
|
+
<View style={{ gap: SPACING.sm }}>
|
|
1217
|
+
<Skeleton height={180} borderRadius={14} /> {/* image */}
|
|
1218
|
+
<Skeleton height={18} width="70%" /> {/* title */}
|
|
1219
|
+
<Skeleton preset="text" /> {/* subtitle */}
|
|
1220
|
+
<Skeleton height={14} width="40%" /> {/* caption */}
|
|
1221
|
+
</View>
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
**Composition — skeleton for a ListItem:**
|
|
1225
|
+
```tsx
|
|
1226
|
+
<View style={{ flexDirection: 'row', gap: SPACING.md, padding: SPACING.base }}>
|
|
1227
|
+
<Skeleton preset="circle" diameter={40} />
|
|
1228
|
+
<View style={{ flex: 1, gap: SPACING.xs }}>
|
|
1229
|
+
<Skeleton height={16} width="60%" />
|
|
1230
|
+
<Skeleton preset="text" />
|
|
1231
|
+
</View>
|
|
1232
|
+
</View>
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
---
|
|
1236
|
+
|
|
1237
|
+
### Progress
|
|
1238
|
+
|
|
1239
|
+
**Import:** `import { Progress } from '@retray-dev/ui-kit'`
|
|
1240
|
+
|
|
1241
|
+
**When to use:** Show completion percentage for tasks, uploads, onboarding steps, multi-step flows.
|
|
1242
|
+
|
|
1243
|
+
| Prop | Type | Default | Notes |
|
|
1244
|
+
|------|------|---------|-------|
|
|
1245
|
+
| value | `number` | `0` | Current progress value |
|
|
1246
|
+
| max | `number` | `100` | Maximum value — `value/max` determines fill percentage |
|
|
1247
|
+
| variant | `'default' \| 'success' \| 'warning' \| 'destructive'` | `'default'` | Indicator color |
|
|
1248
|
+
| style | `ViewStyle` | — | Outer container style |
|
|
689
1249
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
</Card>
|
|
696
|
-
```
|
|
1250
|
+
**Variants — indicator color:**
|
|
1251
|
+
- `default` → `primary` token
|
|
1252
|
+
- `success` → `success` token
|
|
1253
|
+
- `warning` → `warning` token
|
|
1254
|
+
- `destructive` → `destructive` token
|
|
697
1255
|
|
|
698
|
-
**
|
|
699
|
-
|
|
700
|
-
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1256
|
+
**Styling:** 8px height, `borderRadius: 9999` (pill). Track uses `surface` background.
|
|
1257
|
+
|
|
1258
|
+
**Animation:** Spring-animates fill width on `value` changes (JS thread). Uses `onLayout` to capture pixel width.
|
|
1259
|
+
|
|
1260
|
+
**Examples:**
|
|
1261
|
+
```tsx
|
|
1262
|
+
<Progress value={60} />
|
|
1263
|
+
<Progress value={3} max={10} />
|
|
1264
|
+
<Progress value={uploadProgress} variant="success" />
|
|
1265
|
+
<Progress value={daysRemaining} max={30} variant="warning" />
|
|
1266
|
+
<Progress value={errors} max={100} variant="destructive" />
|
|
1267
|
+
|
|
1268
|
+
// Labeled progress
|
|
1269
|
+
<View style={{ gap: SPACING.xs }}>
|
|
1270
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
1271
|
+
<Text variant="caption">Profile completion</Text>
|
|
1272
|
+
<Text variant="caption">{progress}%</Text>
|
|
1273
|
+
</View>
|
|
1274
|
+
<Progress value={progress} />
|
|
1275
|
+
</View>
|
|
1276
|
+
```
|
|
704
1277
|
|
|
705
1278
|
---
|
|
706
1279
|
|
|
707
1280
|
### AlertBanner
|
|
708
1281
|
|
|
709
1282
|
**Import:** `import { AlertBanner } from '@retray-dev/ui-kit'`
|
|
710
|
-
|
|
1283
|
+
|
|
1284
|
+
**When to use:** Inline feedback messages that persist in the UI — info notices, form-level errors, feature announcements, status alerts. Not for ephemeral feedback — use `Toast` for temporary messages.
|
|
711
1285
|
|
|
712
1286
|
> **Note:** Named `AlertBanner` (not `Alert`) to avoid collision with React Native's built-in `Alert` module.
|
|
713
1287
|
|
|
714
1288
|
| Prop | Type | Default | Notes |
|
|
715
1289
|
|------|------|---------|-------|
|
|
716
1290
|
| title | `string` | required | Bold heading |
|
|
717
|
-
| description | `string` | — |
|
|
718
|
-
| variant | `'default' \| 'destructive' \| 'success'` | `'default'` | Controls
|
|
719
|
-
| icon | `ReactNode` | — |
|
|
720
|
-
| iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon
|
|
721
|
-
| iconColor | `string` | — | Override icon color. Defaults to
|
|
1291
|
+
| description | `string` | — | Supporting detail text below title |
|
|
1292
|
+
| variant | `'default' \| 'destructive' \| 'success' \| 'warning'` | `'default'` | Controls colors and default icon |
|
|
1293
|
+
| icon | `ReactNode` | — | Override default icon with custom ReactNode |
|
|
1294
|
+
| iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon`, but still falls back to variant default when neither is set |
|
|
1295
|
+
| iconColor | `string` | — | Override icon color. Defaults to variant title color |
|
|
722
1296
|
| style | `ViewStyle` | — | — |
|
|
723
1297
|
|
|
724
|
-
**
|
|
1298
|
+
**Variants:**
|
|
1299
|
+
|
|
1300
|
+
| Variant | Background | Border | Icon | When to use |
|
|
1301
|
+
|---------|-----------|--------|------|-------------|
|
|
1302
|
+
| `default` | `card` | `border` | ℹ️ info | Neutral notices, informational messages |
|
|
1303
|
+
| `destructive` | `destructiveTint` | `destructiveBorder` | ⚠️ error | Form errors, action failures |
|
|
1304
|
+
| `success` | `successTint` | `successBorder` | ✓ check | Confirmations, completed states |
|
|
1305
|
+
| `warning` | `warningTint` | `warningBorder` | ⚠️ warning | Cautions, expiring states, pending actions |
|
|
1306
|
+
|
|
1307
|
+
**Styling:** 12px border radius, 14px horizontal + 12px vertical padding, 1px border.
|
|
1308
|
+
|
|
1309
|
+
**Examples:**
|
|
725
1310
|
```tsx
|
|
726
|
-
<AlertBanner title="
|
|
727
|
-
<AlertBanner variant="destructive" title="
|
|
728
|
-
<AlertBanner variant="success" title="
|
|
729
|
-
<AlertBanner
|
|
1311
|
+
<AlertBanner title="Your session expires in 5 minutes" />
|
|
1312
|
+
<AlertBanner variant="destructive" title="Payment failed" description="Please check your card details." />
|
|
1313
|
+
<AlertBanner variant="success" title="Profile updated" description="Your changes have been saved." />
|
|
1314
|
+
<AlertBanner variant="warning" title="ID verification required" description="Complete verification to unlock transfers." />
|
|
1315
|
+
|
|
1316
|
+
// Custom icon
|
|
1317
|
+
<AlertBanner iconName="bell" title="Reminder" description="Complete your profile to get started." />
|
|
730
1318
|
```
|
|
731
1319
|
|
|
732
1320
|
---
|
|
@@ -734,28 +1322,48 @@ All sub-components (`CardHeader`, `CardTitle`, `CardDescription`, `CardContent`,
|
|
|
734
1322
|
### EmptyState
|
|
735
1323
|
|
|
736
1324
|
**Import:** `import { EmptyState } from '@retray-dev/ui-kit'`
|
|
737
|
-
|
|
1325
|
+
|
|
1326
|
+
**When to use:** When a list, section, or screen has no content. Provides a centered illustration/icon, message, and optional CTA.
|
|
738
1327
|
|
|
739
1328
|
| Prop | Type | Default | Notes |
|
|
740
1329
|
|------|------|---------|-------|
|
|
741
|
-
| title | `string` | required |
|
|
742
|
-
| description | `string` | — |
|
|
743
|
-
| icon | `ReactNode` | — |
|
|
744
|
-
| iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon
|
|
745
|
-
| iconColor | `string` | — | Override icon color. Defaults to `
|
|
746
|
-
| action | `ReactNode` | — |
|
|
747
|
-
| size | `'default' \| 'compact'` | `'default'` | `compact`
|
|
1330
|
+
| title | `string` | required | Primary message |
|
|
1331
|
+
| description | `string` | — | Supporting text (hidden in `compact` size) |
|
|
1332
|
+
| icon | `ReactNode` | — | Icon in the top circle container |
|
|
1333
|
+
| iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon` |
|
|
1334
|
+
| iconColor | `string` | — | Override icon color. Defaults to `foregroundMuted` |
|
|
1335
|
+
| action | `ReactNode` | — | CTA below text — typically a `Button`. Hidden in `compact` size |
|
|
1336
|
+
| size | `'default' \| 'compact'` | `'default'` | `compact` is smaller, hides description and action |
|
|
748
1337
|
| style | `ViewStyle` | — | — |
|
|
749
1338
|
|
|
750
|
-
**
|
|
1339
|
+
**Sizes:**
|
|
1340
|
+
|
|
1341
|
+
| Size | Icon container | Title font | Description | Action |
|
|
1342
|
+
|------|---------------|-----------|-------------|--------|
|
|
1343
|
+
| `default` | 48×48px, 12px radius | 18pt / Medium | shown | shown |
|
|
1344
|
+
| `compact` | 36×36px, 8px radius | 15pt / Medium | hidden | hidden |
|
|
1345
|
+
|
|
1346
|
+
**Styling:** Centered layout, dashed border container, `surface` icon background, 32px padding.
|
|
1347
|
+
|
|
1348
|
+
**Examples:**
|
|
751
1349
|
```tsx
|
|
1350
|
+
// Full empty state with action
|
|
752
1351
|
<EmptyState
|
|
1352
|
+
iconName="inbox"
|
|
753
1353
|
title="No notifications"
|
|
754
|
-
description="You're all caught up!"
|
|
755
|
-
action={<Button label="Refresh" variant="
|
|
1354
|
+
description="You're all caught up! Check back later."
|
|
1355
|
+
action={<Button label="Refresh" variant="secondary" size="sm" onPress={refresh} />}
|
|
1356
|
+
/>
|
|
1357
|
+
|
|
1358
|
+
// Search no results
|
|
1359
|
+
<EmptyState
|
|
1360
|
+
iconName="search"
|
|
1361
|
+
title="No results for "coffee""
|
|
1362
|
+
description="Try a different search term."
|
|
756
1363
|
/>
|
|
757
|
-
|
|
758
|
-
|
|
1364
|
+
|
|
1365
|
+
// Compact inline empty state
|
|
1366
|
+
<EmptyState iconName="file-text" title="No transactions" size="compact" />
|
|
759
1367
|
```
|
|
760
1368
|
|
|
761
1369
|
---
|
|
@@ -764,18 +1372,39 @@ All sub-components (`CardHeader`, `CardTitle`, `CardDescription`, `CardContent`,
|
|
|
764
1372
|
|
|
765
1373
|
**Import:** `import { Checkbox } from '@retray-dev/ui-kit'`
|
|
766
1374
|
|
|
1375
|
+
**When to use:** Binary boolean choices in forms — terms acceptance, feature toggles, multi-select item selection.
|
|
1376
|
+
|
|
767
1377
|
| Prop | Type | Default | Notes |
|
|
768
1378
|
|------|------|---------|-------|
|
|
769
|
-
| checked | `boolean` | `false` |
|
|
770
|
-
| onCheckedChange | `(checked: boolean) => void` | — |
|
|
771
|
-
| label | `string` | — | Text to the right of the
|
|
772
|
-
| disabled | `boolean` | — |
|
|
1379
|
+
| checked | `boolean` | `false` | Controlled checked state |
|
|
1380
|
+
| onCheckedChange | `(checked: boolean) => void` | — | Called with new boolean on press |
|
|
1381
|
+
| label | `string` | — | Text to the right of the checkbox |
|
|
1382
|
+
| disabled | `boolean` | — | Reduces opacity to 0.5, disables interaction |
|
|
773
1383
|
| style | `ViewStyle` | — | — |
|
|
774
1384
|
|
|
775
|
-
**
|
|
1385
|
+
**Styling:** 24×24px box, 4px border radius, 1.5px border. Checkmark: 12×7px L-shape.
|
|
1386
|
+
|
|
1387
|
+
**Animation:** Scale spring on press (0.95).
|
|
1388
|
+
|
|
1389
|
+
**Haptics:** `selectionAsync` on change.
|
|
1390
|
+
|
|
1391
|
+
**Examples:**
|
|
776
1392
|
```tsx
|
|
777
1393
|
const [accepted, setAccepted] = useState(false)
|
|
778
|
-
|
|
1394
|
+
|
|
1395
|
+
<Checkbox checked={accepted} onCheckedChange={setAccepted} label="I agree to the terms and conditions" />
|
|
1396
|
+
<Checkbox checked={rememberMe} onCheckedChange={setRememberMe} label="Remember me" />
|
|
1397
|
+
<Checkbox checked={true} onCheckedChange={() => {}} label="Can't uncheck this" disabled />
|
|
1398
|
+
|
|
1399
|
+
// Multi-select list
|
|
1400
|
+
{options.map((opt) => (
|
|
1401
|
+
<Checkbox
|
|
1402
|
+
key={opt.id}
|
|
1403
|
+
checked={selected.includes(opt.id)}
|
|
1404
|
+
onCheckedChange={(checked) => toggleSelected(opt.id, checked)}
|
|
1405
|
+
label={opt.label}
|
|
1406
|
+
/>
|
|
1407
|
+
))}
|
|
779
1408
|
```
|
|
780
1409
|
|
|
781
1410
|
---
|
|
@@ -783,22 +1412,47 @@ const [accepted, setAccepted] = useState(false)
|
|
|
783
1412
|
### Switch
|
|
784
1413
|
|
|
785
1414
|
**Import:** `import { Switch } from '@retray-dev/ui-kit'`
|
|
786
|
-
|
|
1415
|
+
|
|
1416
|
+
**When to use:** Binary on/off settings that take effect immediately — notifications, dark mode, feature flags. When the action is immediate (no form submit), prefer Switch over Checkbox.
|
|
787
1417
|
|
|
788
1418
|
| Prop | Type | Default | Notes |
|
|
789
1419
|
|------|------|---------|-------|
|
|
790
|
-
| checked | `boolean` | `false` |
|
|
791
|
-
| onCheckedChange | `(checked: boolean) => void` | — |
|
|
792
|
-
| disabled | `boolean` | — | Reduces opacity to 0.45 |
|
|
1420
|
+
| checked | `boolean` | `false` | Controlled state |
|
|
1421
|
+
| onCheckedChange | `(checked: boolean) => void` | — | Called with new state on press |
|
|
1422
|
+
| disabled | `boolean` | — | Reduces opacity to 0.45, disables interaction |
|
|
793
1423
|
| style | `ViewStyle` | — | — |
|
|
794
1424
|
|
|
795
|
-
**Dimensions:** Track
|
|
1425
|
+
**Dimensions:** Track 52×30px (pill), Thumb 24×24px with 3px offset.
|
|
796
1426
|
|
|
797
|
-
**Animation:** Thumb translates via spring
|
|
1427
|
+
**Animation:** Thumb translates via spring; track color transitions via opacity (150ms).
|
|
798
1428
|
|
|
799
|
-
**
|
|
1429
|
+
**Haptics:** `selectionAsync` on toggle.
|
|
1430
|
+
|
|
1431
|
+
**Examples:**
|
|
800
1432
|
```tsx
|
|
801
1433
|
<Switch checked={notifications} onCheckedChange={setNotifications} />
|
|
1434
|
+
|
|
1435
|
+
// In a settings row
|
|
1436
|
+
<ListItem
|
|
1437
|
+
title="Push notifications"
|
|
1438
|
+
subtitle="Get alerts for new activity"
|
|
1439
|
+
rightRender={<Switch checked={push} onCheckedChange={setPush} />}
|
|
1440
|
+
/>
|
|
1441
|
+
|
|
1442
|
+
// Full settings list
|
|
1443
|
+
{settings.map((setting) => (
|
|
1444
|
+
<ListItem
|
|
1445
|
+
key={setting.key}
|
|
1446
|
+
title={setting.label}
|
|
1447
|
+
rightRender={
|
|
1448
|
+
<Switch
|
|
1449
|
+
checked={prefs[setting.key]}
|
|
1450
|
+
onCheckedChange={(v) => updatePref(setting.key, v)}
|
|
1451
|
+
/>
|
|
1452
|
+
}
|
|
1453
|
+
showSeparator
|
|
1454
|
+
/>
|
|
1455
|
+
))}
|
|
802
1456
|
```
|
|
803
1457
|
|
|
804
1458
|
---
|
|
@@ -806,36 +1460,64 @@ const [accepted, setAccepted] = useState(false)
|
|
|
806
1460
|
### Toggle
|
|
807
1461
|
|
|
808
1462
|
**Import:** `import { Toggle } from '@retray-dev/ui-kit'`
|
|
809
|
-
|
|
1463
|
+
|
|
1464
|
+
**When to use:** Toggleable button that shows its active state visually — bold/italic/underline in toolbars, favorite/bookmark actions, filter toggles. Looks like a button (not a switch). Use `Switch` when a pure on/off control is needed.
|
|
810
1465
|
|
|
811
1466
|
| Prop | Type | Default | Notes |
|
|
812
1467
|
|------|------|---------|-------|
|
|
813
|
-
| pressed | `boolean` | `false` |
|
|
814
|
-
| onPressedChange | `(pressed: boolean) => void` | — |
|
|
815
|
-
| variant | `'default' \| 'outline'` | `'default'` | `outline` adds
|
|
816
|
-
| size | `'sm' \| 'md' \| 'lg'` | `'md'` |
|
|
1468
|
+
| pressed | `boolean` | `false` | Controlled pressed state |
|
|
1469
|
+
| onPressedChange | `(pressed: boolean) => void` | — | Called with new state on press |
|
|
1470
|
+
| variant | `'default' \| 'outline'` | `'default'` | `outline` adds border when unpressed |
|
|
1471
|
+
| size | `'sm' \| 'md' \| 'lg'` | `'md'` | Controls minimum height and padding |
|
|
817
1472
|
| label | `string` | — | Text label |
|
|
818
|
-
| icon | `ReactNode \| ((pressed: boolean) => ReactNode)` | — | Icon
|
|
819
|
-
| activeIcon | `ReactNode \| ((pressed: boolean) => ReactNode)` | — | Icon
|
|
820
|
-
| iconName | `string` | — | Icon name
|
|
821
|
-
| activeIconName | `string` | — | Icon name
|
|
822
|
-
| iconColor | `string` | — |
|
|
823
|
-
| activeIconColor | `string` | — |
|
|
1473
|
+
| icon | `ReactNode \| ((pressed: boolean) => ReactNode)` | — | Icon when not pressed |
|
|
1474
|
+
| activeIcon | `ReactNode \| ((pressed: boolean) => ReactNode)` | — | Icon when pressed. Defaults to check-circle |
|
|
1475
|
+
| iconName | `string` | — | Icon name when not pressed. Takes precedence over `icon` |
|
|
1476
|
+
| activeIconName | `string` | — | Icon name when pressed. Takes precedence over `activeIcon` |
|
|
1477
|
+
| iconColor | `string` | — | Inactive icon color. Defaults to `foregroundMuted` |
|
|
1478
|
+
| activeIconColor | `string` | — | Active icon color. Defaults to `primary` |
|
|
1479
|
+
| disabled | `boolean` | — | — |
|
|
1480
|
+
| style | `ViewStyle` | — | — |
|
|
1481
|
+
|
|
1482
|
+
**Sizes:**
|
|
1483
|
+
|
|
1484
|
+
| Size | Min height | Horizontal padding | Icon size |
|
|
1485
|
+
|------|-----------|-------------------|-----------|
|
|
1486
|
+
| `sm` | 40px | 12px | 16px |
|
|
1487
|
+
| `md` | 44px | 16px | 18px |
|
|
1488
|
+
| `lg` | 48px | 20px | 20px |
|
|
824
1489
|
|
|
825
|
-
**Animation:** `borderColor
|
|
1490
|
+
**Animation:** `borderColor`, `backgroundColor`, `textColor` animate via `Animated.timing` (150ms) on state change. `borderWidth` stays fixed at 2pt to prevent layout shifts.
|
|
826
1491
|
|
|
827
|
-
**
|
|
1492
|
+
**Haptics:** `selectionAsync` on press.
|
|
1493
|
+
|
|
1494
|
+
**Examples:**
|
|
828
1495
|
```tsx
|
|
829
1496
|
<Toggle pressed={bold} onPressedChange={setBold} label="Bold" variant="outline" />
|
|
830
|
-
<Toggle pressed={muted} onPressedChange={setMuted} iconName="volume-x" activeIconName="volume" />
|
|
831
1497
|
|
|
832
|
-
// With
|
|
1498
|
+
// With icon names
|
|
1499
|
+
<Toggle
|
|
1500
|
+
pressed={muted}
|
|
1501
|
+
onPressedChange={setMuted}
|
|
1502
|
+
iconName="volume-x"
|
|
1503
|
+
activeIconName="volume"
|
|
1504
|
+
/>
|
|
1505
|
+
|
|
1506
|
+
// Bookmark
|
|
833
1507
|
<Toggle
|
|
834
|
-
pressed={
|
|
835
|
-
onPressedChange={
|
|
836
|
-
|
|
837
|
-
|
|
1508
|
+
pressed={saved}
|
|
1509
|
+
onPressedChange={setSaved}
|
|
1510
|
+
iconName="bookmark"
|
|
1511
|
+
activeIconName="bookmark"
|
|
1512
|
+
activeIconColor={colors.primary}
|
|
838
1513
|
/>
|
|
1514
|
+
|
|
1515
|
+
// Toolbar row
|
|
1516
|
+
<View style={{ flexDirection: 'row', gap: SPACING.xs }}>
|
|
1517
|
+
<Toggle pressed={bold} onPressedChange={setBold} iconName="bold" variant="outline" size="sm" />
|
|
1518
|
+
<Toggle pressed={italic} onPressedChange={setItalic} iconName="italic" variant="outline" size="sm" />
|
|
1519
|
+
<Toggle pressed={underline} onPressedChange={setUnderline} iconName="underline" variant="outline" size="sm" />
|
|
1520
|
+
</View>
|
|
839
1521
|
```
|
|
840
1522
|
|
|
841
1523
|
---
|
|
@@ -844,15 +1526,28 @@ const [accepted, setAccepted] = useState(false)
|
|
|
844
1526
|
|
|
845
1527
|
**Import:** `import { RadioGroup } from '@retray-dev/ui-kit'`
|
|
846
1528
|
|
|
1529
|
+
**When to use:** Single selection from a small set of options (2–6). When options are mutually exclusive and all should be visible simultaneously.
|
|
1530
|
+
|
|
847
1531
|
| Prop | Type | Default | Notes |
|
|
848
1532
|
|------|------|---------|-------|
|
|
849
|
-
| options | `RadioOption[]` | required |
|
|
1533
|
+
| options | `RadioOption[]` | required | `{ label: string, value: string, disabled?: boolean }` |
|
|
850
1534
|
| value | `string` | — | Currently selected value |
|
|
851
|
-
| onValueChange | `(value: string) => void` | — |
|
|
852
|
-
| orientation | `'vertical' \| 'horizontal'` | `'vertical'` |
|
|
1535
|
+
| onValueChange | `(value: string) => void` | — | Called on selection change |
|
|
1536
|
+
| orientation | `'vertical' \| 'horizontal'` | `'vertical'` | Layout direction |
|
|
853
1537
|
| style | `ViewStyle` | — | — |
|
|
854
1538
|
|
|
855
|
-
**
|
|
1539
|
+
**RadioOption type:**
|
|
1540
|
+
```ts
|
|
1541
|
+
{ label: string; value: string; disabled?: boolean }
|
|
1542
|
+
```
|
|
1543
|
+
|
|
1544
|
+
**Styling:** Radio circle 24×24px, 1.5px border. Filled dot 10×10px when selected. 12px gap between radio and label. 8px gap between options (vertical), 16px (horizontal).
|
|
1545
|
+
|
|
1546
|
+
**Animation:** Scale spring on press.
|
|
1547
|
+
|
|
1548
|
+
**Haptics:** `selectionAsync` on selection.
|
|
1549
|
+
|
|
1550
|
+
**Examples:**
|
|
856
1551
|
```tsx
|
|
857
1552
|
<RadioGroup
|
|
858
1553
|
options={[
|
|
@@ -863,6 +1558,14 @@ const [accepted, setAccepted] = useState(false)
|
|
|
863
1558
|
value={plan}
|
|
864
1559
|
onValueChange={setPlan}
|
|
865
1560
|
/>
|
|
1561
|
+
|
|
1562
|
+
// Horizontal orientation
|
|
1563
|
+
<RadioGroup
|
|
1564
|
+
options={[{ label: 'Yes', value: 'yes' }, { label: 'No', value: 'no' }]}
|
|
1565
|
+
value={answer}
|
|
1566
|
+
onValueChange={setAnswer}
|
|
1567
|
+
orientation="horizontal"
|
|
1568
|
+
/>
|
|
866
1569
|
```
|
|
867
1570
|
|
|
868
1571
|
---
|
|
@@ -870,27 +1573,47 @@ const [accepted, setAccepted] = useState(false)
|
|
|
870
1573
|
### Select
|
|
871
1574
|
|
|
872
1575
|
**Import:** `import { Select } from '@retray-dev/ui-kit'`
|
|
873
|
-
|
|
1576
|
+
|
|
1577
|
+
**When to use:** Dropdown selection from a list too long for RadioGroup (7+ options), or options that don't need to all be visible at once. Uses platform-native picker UI.
|
|
1578
|
+
|
|
1579
|
+
**Peer dependency:** `@react-native-picker/picker` must be installed.
|
|
874
1580
|
|
|
875
1581
|
| Prop | Type | Default | Notes |
|
|
876
1582
|
|------|------|---------|-------|
|
|
877
|
-
| options | `SelectOption[]` | required |
|
|
878
|
-
| value | `string` | — |
|
|
1583
|
+
| options | `SelectOption[]` | required | `{ label: string, value: string, disabled?: boolean }` |
|
|
1584
|
+
| value | `string` | — | Currently selected value |
|
|
879
1585
|
| onValueChange | `(value: string) => void` | — | — |
|
|
880
1586
|
| placeholder | `string` | `'Select an option'` | Shown when no value is selected |
|
|
881
1587
|
| label | `string` | — | Label above the trigger |
|
|
882
|
-
| error | `string` | — | Error text below
|
|
1588
|
+
| error | `string` | — | Error text below trigger |
|
|
883
1589
|
| disabled | `boolean` | — | — |
|
|
884
|
-
| style | `ViewStyle` | — |
|
|
1590
|
+
| style | `ViewStyle` | — | Outer container style |
|
|
1591
|
+
|
|
1592
|
+
**Platform behavior:**
|
|
1593
|
+
- **iOS:** Modal sheet with wheel `Picker`, "Done" button, transparent backdrop, opens on trigger press
|
|
1594
|
+
- **Android:** Native dialog picker opened via `TextInput.focus()`
|
|
1595
|
+
- **Web:** Native HTML `<select>` element — full keyboard navigation
|
|
885
1596
|
|
|
886
|
-
**
|
|
1597
|
+
**Styling:** 8px border radius trigger, 14px horizontal + 11px vertical padding. Error state: 2px destructive border.
|
|
1598
|
+
|
|
1599
|
+
**Haptics:** `selectionAsync` on open and confirm.
|
|
1600
|
+
|
|
1601
|
+
**Examples:**
|
|
887
1602
|
```tsx
|
|
888
1603
|
<Select
|
|
889
1604
|
label="Country"
|
|
890
|
-
options={
|
|
1605
|
+
options={countries.map((c) => ({ label: c.name, value: c.code }))}
|
|
891
1606
|
value={country}
|
|
892
1607
|
onValueChange={setCountry}
|
|
893
|
-
placeholder="
|
|
1608
|
+
placeholder="Select a country"
|
|
1609
|
+
/>
|
|
1610
|
+
|
|
1611
|
+
<Select
|
|
1612
|
+
label="Category"
|
|
1613
|
+
options={categories}
|
|
1614
|
+
value={category}
|
|
1615
|
+
onValueChange={setCategory}
|
|
1616
|
+
error={categoryError}
|
|
894
1617
|
/>
|
|
895
1618
|
```
|
|
896
1619
|
|
|
@@ -899,37 +1622,55 @@ const [accepted, setAccepted] = useState(false)
|
|
|
899
1622
|
### Slider
|
|
900
1623
|
|
|
901
1624
|
**Import:** `import { Slider } from '@retray-dev/ui-kit'`
|
|
902
|
-
|
|
1625
|
+
|
|
1626
|
+
**When to use:** Selecting a numeric value in a continuous range by dragging — volume, brightness, price range, percentage.
|
|
1627
|
+
|
|
1628
|
+
**Peer dependency:** `@react-native-community/slider` must be installed.
|
|
903
1629
|
|
|
904
1630
|
| Prop | Type | Default | Notes |
|
|
905
1631
|
|------|------|---------|-------|
|
|
906
|
-
| value | `number` | `0` |
|
|
1632
|
+
| value | `number` | `0` | Controlled current value |
|
|
907
1633
|
| minimumValue | `number` | `0` | — |
|
|
908
1634
|
| maximumValue | `number` | `1` | — |
|
|
909
|
-
| step | `number` | `0` | `0`
|
|
1635
|
+
| step | `number` | `0` | `0` = continuous. Any positive number = snapping + haptic per step |
|
|
910
1636
|
| onValueChange | `(value: number) => void` | — | Fires while dragging |
|
|
911
1637
|
| onSlidingComplete | `(value: number) => void` | — | Fires on finger release |
|
|
1638
|
+
| label | `string` | — | Label text above slider |
|
|
1639
|
+
| showValue | `boolean` | `false` | Displays current value top-right |
|
|
1640
|
+
| formatValue | `(value: number) => string` | `toFixed(2)` | Custom display formatter |
|
|
1641
|
+
| accessibilityLabel | `string` | — | Screen reader label |
|
|
912
1642
|
| disabled | `boolean` | — | — |
|
|
913
|
-
| label | `string` | — | Label text displayed above the slider |
|
|
914
|
-
| showValue | `boolean` | `false` | When `true`, displays the current value at the top right |
|
|
915
|
-
| formatValue | `(value: number) => string` | — | Custom formatter for the displayed value |
|
|
916
|
-
| accessibilityLabel | `string` | — | Accessibility label for screen readers |
|
|
917
1643
|
| style | `ViewStyle` | — | — |
|
|
918
1644
|
|
|
919
|
-
**
|
|
1645
|
+
**Haptics:** `selectionAsync` on each step (only when `step > 0`).
|
|
1646
|
+
|
|
1647
|
+
**Theming:** `minimumTrackTintColor = primary`, `maximumTrackTintColor = surface`, `thumbTintColor = primary`.
|
|
920
1648
|
|
|
921
|
-
**
|
|
1649
|
+
**Examples:**
|
|
922
1650
|
```tsx
|
|
923
1651
|
<Slider value={volume} minimumValue={0} maximumValue={100} step={1} onValueChange={setVolume} />
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1652
|
+
|
|
1653
|
+
<Slider
|
|
1654
|
+
label="Volume"
|
|
1655
|
+
showValue
|
|
1656
|
+
value={volume}
|
|
1657
|
+
minimumValue={0}
|
|
1658
|
+
maximumValue={100}
|
|
930
1659
|
step={5}
|
|
931
1660
|
formatValue={(v) => `${v}%`}
|
|
932
|
-
onValueChange={setVolume}
|
|
1661
|
+
onValueChange={setVolume}
|
|
1662
|
+
/>
|
|
1663
|
+
|
|
1664
|
+
<Slider
|
|
1665
|
+
label="Price range"
|
|
1666
|
+
showValue
|
|
1667
|
+
value={price}
|
|
1668
|
+
minimumValue={50}
|
|
1669
|
+
maximumValue={500}
|
|
1670
|
+
step={10}
|
|
1671
|
+
formatValue={(v) => `$${v}`}
|
|
1672
|
+
onValueChange={setPrice}
|
|
1673
|
+
onSlidingComplete={applyFilter}
|
|
933
1674
|
/>
|
|
934
1675
|
```
|
|
935
1676
|
|
|
@@ -938,43 +1679,81 @@ const [accepted, setAccepted] = useState(false)
|
|
|
938
1679
|
### Tabs
|
|
939
1680
|
|
|
940
1681
|
**Import:** `import { Tabs, TabsContent } from '@retray-dev/ui-kit'`
|
|
941
|
-
|
|
1682
|
+
|
|
1683
|
+
**When to use:** Switching between categorized sections on the same screen — profile/activity/settings, different data views, content filters.
|
|
942
1684
|
|
|
943
1685
|
| Prop | Type | Default | Notes |
|
|
944
1686
|
|------|------|---------|-------|
|
|
945
|
-
| tabs | `TabItem[]` | required |
|
|
1687
|
+
| tabs | `TabItem[]` | required | Tab definitions |
|
|
1688
|
+
| variant | `'pill' \| 'underline'` | `'pill'` | Visual style |
|
|
946
1689
|
| value | `string` | — | Controlled active tab |
|
|
947
1690
|
| onValueChange | `(value: string) => void` | — | — |
|
|
948
1691
|
| children | `ReactNode` | — | `TabsContent` components |
|
|
949
1692
|
| style | `ViewStyle` | — | — |
|
|
950
1693
|
|
|
1694
|
+
**TabItem type:**
|
|
1695
|
+
```ts
|
|
1696
|
+
{ label: string; value: string; icon?: ReactNode | ((isActive: boolean) => ReactNode) }
|
|
1697
|
+
```
|
|
1698
|
+
|
|
1699
|
+
**Variants:**
|
|
1700
|
+
- `pill` — Animated background pill slides to active tab. Full-width distributed tabs. Active: filled `primary` + `primaryForeground` text. Default for top-of-screen navigation.
|
|
1701
|
+
- `underline` — 2px bottom border on active tab, no background. Tabs don't stretch — natural width. Airbnb-style filter tabs.
|
|
1702
|
+
|
|
951
1703
|
**`TabsContent` Props:**
|
|
952
1704
|
|
|
953
1705
|
| Prop | Type | Notes |
|
|
954
1706
|
|------|------|-------|
|
|
955
1707
|
| value | `string` | Must match a tab value in `tabs` |
|
|
956
|
-
| activeValue | `string` | Pass the current active tab — content
|
|
1708
|
+
| activeValue | `string` | Pass the current active tab — content hidden when not active |
|
|
957
1709
|
| children | `ReactNode` | — |
|
|
958
1710
|
| style | `ViewStyle` | — |
|
|
959
1711
|
|
|
960
|
-
**Animation:**
|
|
1712
|
+
**Animation (pill variant):** Absolutely-positioned pill slides via spring (speed: 20, bounciness: 0) to track active tab layout.
|
|
1713
|
+
|
|
1714
|
+
**Haptics:** `selectionAsync` on tab change.
|
|
961
1715
|
|
|
962
|
-
**
|
|
1716
|
+
**Examples:**
|
|
963
1717
|
```tsx
|
|
964
1718
|
const [tab, setTab] = useState('profile')
|
|
965
1719
|
|
|
1720
|
+
// Pill variant (default)
|
|
966
1721
|
<Tabs
|
|
967
|
-
tabs={[
|
|
1722
|
+
tabs={[
|
|
1723
|
+
{ label: 'Profile', value: 'profile' },
|
|
1724
|
+
{ label: 'Activity', value: 'activity' },
|
|
1725
|
+
{ label: 'Settings', value: 'settings' },
|
|
1726
|
+
]}
|
|
968
1727
|
value={tab}
|
|
969
1728
|
onValueChange={setTab}
|
|
970
1729
|
>
|
|
971
1730
|
<TabsContent value="profile" activeValue={tab}>
|
|
972
1731
|
<Text>Profile content</Text>
|
|
973
1732
|
</TabsContent>
|
|
974
|
-
<TabsContent value="
|
|
975
|
-
<Text>
|
|
1733
|
+
<TabsContent value="activity" activeValue={tab}>
|
|
1734
|
+
<Text>Activity content</Text>
|
|
1735
|
+
</TabsContent>
|
|
1736
|
+
<TabsContent value="settings" activeValue={tab}>
|
|
1737
|
+
<Text>Settings content</Text>
|
|
976
1738
|
</TabsContent>
|
|
977
1739
|
</Tabs>
|
|
1740
|
+
|
|
1741
|
+
// Underline variant with icons
|
|
1742
|
+
<Tabs
|
|
1743
|
+
variant="underline"
|
|
1744
|
+
tabs={[
|
|
1745
|
+
{ label: 'All', value: 'all' },
|
|
1746
|
+
{ label: 'Trips', value: 'trips' },
|
|
1747
|
+
{ label: 'Experiences', value: 'experiences' },
|
|
1748
|
+
]}
|
|
1749
|
+
value={filter}
|
|
1750
|
+
onValueChange={setFilter}
|
|
1751
|
+
/>
|
|
1752
|
+
|
|
1753
|
+
// Content-only (no TabsContent — manage content externally)
|
|
1754
|
+
<Tabs tabs={tabs} value={tab} onValueChange={setTab} />
|
|
1755
|
+
{tab === 'profile' && <ProfileSection />}
|
|
1756
|
+
{tab === 'activity' && <ActivitySection />}
|
|
978
1757
|
```
|
|
979
1758
|
|
|
980
1759
|
---
|
|
@@ -982,26 +1761,47 @@ const [tab, setTab] = useState('profile')
|
|
|
982
1761
|
### Accordion
|
|
983
1762
|
|
|
984
1763
|
**Import:** `import { Accordion } from '@retray-dev/ui-kit'`
|
|
985
|
-
|
|
1764
|
+
|
|
1765
|
+
**When to use:** FAQs, expandable settings sections, collapsible help content. Animated expand/collapse.
|
|
986
1766
|
|
|
987
1767
|
| Prop | Type | Default | Notes |
|
|
988
1768
|
|------|------|---------|-------|
|
|
989
|
-
| items | `AccordionItem[]` | required |
|
|
990
|
-
| type | `'single' \| 'multiple'` | `'single'` | `single`:
|
|
991
|
-
| defaultValue | `string \| string[]` | — | Initially open
|
|
1769
|
+
| items | `AccordionItem[]` | required | Accordion entries |
|
|
1770
|
+
| type | `'single' \| 'multiple'` | `'single'` | `single`: one item open at a time. `multiple`: any number |
|
|
1771
|
+
| defaultValue | `string \| string[]` | — | Initially open items. Use `string[]` with `type='multiple'` |
|
|
992
1772
|
| style | `ViewStyle` | — | — |
|
|
993
1773
|
|
|
994
|
-
**
|
|
1774
|
+
**AccordionItem type:**
|
|
1775
|
+
```ts
|
|
1776
|
+
{ value: string; trigger: string; content: ReactNode }
|
|
1777
|
+
```
|
|
1778
|
+
|
|
1779
|
+
**Animation:** `react-native-reanimated` `withTiming` (220ms) for height and chevron rotation (180°). Press scale uses `withSpring`. Runs on UI thread at 60fps.
|
|
1780
|
+
|
|
1781
|
+
**Haptics:** `selectionAsync` on toggle.
|
|
995
1782
|
|
|
996
|
-
**
|
|
1783
|
+
**Examples:**
|
|
997
1784
|
```tsx
|
|
998
1785
|
<Accordion
|
|
999
|
-
type="single"
|
|
1000
1786
|
items={[
|
|
1001
|
-
{
|
|
1002
|
-
|
|
1787
|
+
{
|
|
1788
|
+
value: 'q1',
|
|
1789
|
+
trigger: 'What payment methods do you accept?',
|
|
1790
|
+
content: <Text variant="body-sm">We accept Visa, Mastercard, and bank transfers.</Text>,
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
value: 'q2',
|
|
1794
|
+
trigger: 'Can I cancel my booking?',
|
|
1795
|
+
content: <Text variant="body-sm">Yes — free cancellation within 48 hours of booking.</Text>,
|
|
1796
|
+
},
|
|
1003
1797
|
]}
|
|
1004
1798
|
/>
|
|
1799
|
+
|
|
1800
|
+
// Multiple open simultaneously
|
|
1801
|
+
<Accordion type="multiple" defaultValue={['q1']} items={faqItems} />
|
|
1802
|
+
|
|
1803
|
+
// Pre-opened
|
|
1804
|
+
<Accordion defaultValue="getting-started" items={helpSections} />
|
|
1005
1805
|
```
|
|
1006
1806
|
|
|
1007
1807
|
---
|
|
@@ -1009,45 +1809,66 @@ const [tab, setTab] = useState('profile')
|
|
|
1009
1809
|
### Sheet
|
|
1010
1810
|
|
|
1011
1811
|
**Import:** `import { Sheet, BottomSheetModalProvider } from '@retray-dev/ui-kit'`
|
|
1012
|
-
**When to use:** Bottom sheet with physics-based gestures, rubber-band overscroll, and snap points. Powered by `@gorhom/bottom-sheet`.
|
|
1013
1812
|
|
|
1014
|
-
**
|
|
1015
|
-
```tsx
|
|
1016
|
-
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
1017
|
-
import { BottomSheetModalProvider } from '@retray-dev/ui-kit'
|
|
1813
|
+
**When to use:** Bottom sheet for contextual actions, filters, pickers, or detail views that don't need a full screen. Auto-sizes to content — no snap points needed.
|
|
1018
1814
|
|
|
1019
|
-
|
|
1020
|
-
<BottomSheetModalProvider>
|
|
1021
|
-
{/* rest of app */}
|
|
1022
|
-
</BottomSheetModalProvider>
|
|
1023
|
-
</GestureHandlerRootView>
|
|
1024
|
-
```
|
|
1815
|
+
**Required setup** — `BottomSheetModalProvider` must wrap app at root (inside `GestureHandlerRootView`). See Setup section above.
|
|
1025
1816
|
|
|
1026
|
-
**Peer dependencies
|
|
1817
|
+
**Peer dependencies:**
|
|
1027
1818
|
```bash
|
|
1028
1819
|
pnpm add @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler react-native-worklets
|
|
1029
1820
|
```
|
|
1030
|
-
Add `react-native-worklets/plugin` (not `react-native-reanimated/plugin`) to
|
|
1031
|
-
|
|
1032
|
-
> **Important — Expo managed workflow:** Expo's install tool may downgrade `@gorhom/bottom-sheet` to an incompatible version (v4). Pin it in `app.json` to prevent this:
|
|
1033
|
-
> ```json
|
|
1034
|
-
> { "expo": { "install": { "exclude": ["@gorhom/bottom-sheet"] } } }
|
|
1035
|
-
> ```
|
|
1821
|
+
Add `react-native-worklets/plugin` (not `react-native-reanimated/plugin`) to `babel.config.js`.
|
|
1036
1822
|
|
|
1037
1823
|
| Prop | Type | Default | Notes |
|
|
1038
1824
|
|------|------|---------|-------|
|
|
1039
1825
|
| open | `boolean` | required | `true` presents the sheet, `false` dismisses it |
|
|
1040
1826
|
| onClose | `() => void` | required | Called on swipe-dismiss or backdrop press |
|
|
1041
|
-
|
|
|
1042
|
-
|
|
|
1043
|
-
|
|
|
1044
|
-
|
|
|
1045
|
-
| style | `ViewStyle` | — | Applied to the inner content container |
|
|
1827
|
+
| title | `string` | — | Sheet heading |
|
|
1828
|
+
| description | `string` | — | Supporting text below title |
|
|
1829
|
+
| children | `ReactNode` | — | Sheet content |
|
|
1830
|
+
| style | `ViewStyle` | — | Inner content container style |
|
|
1046
1831
|
|
|
1047
|
-
**
|
|
1832
|
+
**Features:**
|
|
1833
|
+
- `enableDynamicSizing` — height auto-fits content, no `snapPoints` needed
|
|
1834
|
+
- `enablePanDownToClose` — swipe down to dismiss
|
|
1835
|
+
- Backdrop press dismisses
|
|
1836
|
+
|
|
1837
|
+
**Styling:** `RADIUS.lg = 20px` top corners, 16px horizontal + 20px bottom padding.
|
|
1838
|
+
|
|
1839
|
+
**Haptics:** `impactLight` on open.
|
|
1840
|
+
|
|
1841
|
+
**Examples:**
|
|
1048
1842
|
```tsx
|
|
1049
|
-
|
|
1050
|
-
|
|
1843
|
+
const [open, setOpen] = useState(false)
|
|
1844
|
+
|
|
1845
|
+
<Sheet open={open} onClose={() => setOpen(false)} title="Sort by">
|
|
1846
|
+
<RadioGroup
|
|
1847
|
+
options={[
|
|
1848
|
+
{ label: 'Newest', value: 'newest' },
|
|
1849
|
+
{ label: 'Price: Low to High', value: 'price_asc' },
|
|
1850
|
+
{ label: 'Price: High to Low', value: 'price_desc' },
|
|
1851
|
+
{ label: 'Rating', value: 'rating' },
|
|
1852
|
+
]}
|
|
1853
|
+
value={sort}
|
|
1854
|
+
onValueChange={(v) => { setSort(v); setOpen(false) }}
|
|
1855
|
+
/>
|
|
1856
|
+
</Sheet>
|
|
1857
|
+
|
|
1858
|
+
// Filter sheet
|
|
1859
|
+
<Sheet open={filterOpen} onClose={() => setFilterOpen(false)} title="Filters">
|
|
1860
|
+
<View style={{ gap: SPACING.lg }}>
|
|
1861
|
+
<View>
|
|
1862
|
+
<Text variant="title-sm">Price range</Text>
|
|
1863
|
+
<Slider value={maxPrice} minimumValue={0} maximumValue={1000} step={50} onValueChange={setMaxPrice} showValue formatValue={(v) => `$${v}`} />
|
|
1864
|
+
</View>
|
|
1865
|
+
<Separator />
|
|
1866
|
+
<View>
|
|
1867
|
+
<Text variant="title-sm">Category</Text>
|
|
1868
|
+
<ChipGroup options={categoryOptions} value={selectedCategory} onValueChange={setSelectedCategory} />
|
|
1869
|
+
</View>
|
|
1870
|
+
<Button label="Apply filters" fullWidth onPress={() => { applyFilters(); setFilterOpen(false) }} />
|
|
1871
|
+
</View>
|
|
1051
1872
|
</Sheet>
|
|
1052
1873
|
```
|
|
1053
1874
|
|
|
@@ -1056,15 +1877,13 @@ Add `react-native-worklets/plugin` (not `react-native-reanimated/plugin`) to you
|
|
|
1056
1877
|
### Toast / useToast
|
|
1057
1878
|
|
|
1058
1879
|
**Import:** `import { ToastProvider, useToast } from '@retray-dev/ui-kit'`
|
|
1059
|
-
**When to use:** Ephemeral feedback messages (save success, network error, copy confirmation).
|
|
1060
1880
|
|
|
1061
|
-
**
|
|
1881
|
+
**When to use:** Ephemeral feedback messages — save confirmations, errors, copy notifications, background process updates. Auto-dismiss after duration.
|
|
1062
1882
|
|
|
1063
|
-
**
|
|
1883
|
+
**Required setup:** `ToastProvider` must wrap app inside `SafeAreaProvider`. See Setup section above.
|
|
1064
1884
|
|
|
1885
|
+
**Usage:**
|
|
1065
1886
|
```tsx
|
|
1066
|
-
import { useToast } from '@retray-dev/ui-kit'
|
|
1067
|
-
|
|
1068
1887
|
function MyComponent() {
|
|
1069
1888
|
const { toast, dismiss } = useToast()
|
|
1070
1889
|
|
|
@@ -1073,10 +1892,7 @@ function MyComponent() {
|
|
|
1073
1892
|
label="Save"
|
|
1074
1893
|
onPress={async () => {
|
|
1075
1894
|
await save()
|
|
1076
|
-
toast({ title: 'Saved',
|
|
1077
|
-
// With custom icon name
|
|
1078
|
-
toast({ title: 'Saved', variant: 'success', iconName: 'check-circle' })
|
|
1079
|
-
toast({ title: 'Oops', variant: 'destructive', iconName: 'x-circle' })
|
|
1895
|
+
toast({ title: 'Saved', variant: 'success' })
|
|
1080
1896
|
}}
|
|
1081
1897
|
/>
|
|
1082
1898
|
)
|
|
@@ -1088,90 +1904,223 @@ function MyComponent() {
|
|
|
1088
1904
|
| Field | Type | Default | Notes |
|
|
1089
1905
|
|-------|------|---------|-------|
|
|
1090
1906
|
| title | `string` | — | Bold heading |
|
|
1091
|
-
| description | `string` | — | Detail text |
|
|
1092
|
-
| variant | `'default' \| 'destructive' \| 'success'` | `'default'` |
|
|
1093
|
-
| duration | `number` (ms) | `3000` | Auto-dismiss
|
|
1094
|
-
| icon | `ReactNode` | — | Custom icon
|
|
1907
|
+
| description | `string` | — | Detail text below title |
|
|
1908
|
+
| variant | `'default' \| 'destructive' \| 'success' \| 'warning'` | `'default'` | Background and icon color |
|
|
1909
|
+
| duration | `number` (ms) | `3000` | Auto-dismiss delay. Pass `Infinity` to prevent auto-dismiss |
|
|
1910
|
+
| icon | `ReactNode` | — | Custom icon. Defaults to variant symbol |
|
|
1095
1911
|
| iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon` |
|
|
1096
|
-
| iconColor | `string` | — | Override icon color
|
|
1912
|
+
| iconColor | `string` | — | Override icon color |
|
|
1913
|
+
| action | `{ label: string, onPress: () => void }` | — | Optional action button beside dismiss |
|
|
1914
|
+
|
|
1915
|
+
**`dismiss(id)`:** Dismiss a specific toast programmatically. `id` is returned by the `toast()` call.
|
|
1916
|
+
|
|
1917
|
+
**Variant details:**
|
|
1918
|
+
- `default` — dark/primary background, `impactLight` haptic
|
|
1919
|
+
- `success` — `success` token background, `notificationSuccess` haptic
|
|
1920
|
+
- `destructive` — `destructive` token background, `notificationError` haptic
|
|
1921
|
+
- `warning` — `warning` token background, `notificationError` haptic
|
|
1922
|
+
|
|
1923
|
+
**Behavior:**
|
|
1924
|
+
- Max 3 toasts shown simultaneously — oldest removed when 4th arrives
|
|
1925
|
+
- Appear at top of screen below status bar (dynamic safe area inset)
|
|
1926
|
+
- Swipe left or right to dismiss early (threshold: 80px or 800pt/s velocity)
|
|
1927
|
+
- **Web:** 400px max width, centered
|
|
1928
|
+
|
|
1929
|
+
**Animation:** Entrance: `withTiming(120ms, Easing.out(Easing.exp))` slide-down + opacity. Exit: `withTiming(200ms)` slide-up + opacity fade.
|
|
1930
|
+
|
|
1931
|
+
**Examples:**
|
|
1932
|
+
```tsx
|
|
1933
|
+
const { toast, dismiss } = useToast()
|
|
1934
|
+
|
|
1935
|
+
// Basic variants
|
|
1936
|
+
toast({ title: 'Saved', variant: 'success' })
|
|
1937
|
+
toast({ title: 'Connection error', variant: 'destructive' })
|
|
1938
|
+
toast({ title: 'Check your email', variant: 'warning' })
|
|
1939
|
+
toast({ title: 'Link copied' })
|
|
1940
|
+
|
|
1941
|
+
// With description
|
|
1942
|
+
toast({
|
|
1943
|
+
title: 'Payment sent',
|
|
1944
|
+
description: '$250 sent to John Doe',
|
|
1945
|
+
variant: 'success',
|
|
1946
|
+
iconName: 'check-circle',
|
|
1947
|
+
})
|
|
1948
|
+
|
|
1949
|
+
// With action button
|
|
1950
|
+
toast({
|
|
1951
|
+
title: 'Message deleted',
|
|
1952
|
+
action: { label: 'Undo', onPress: () => restoreMessage() },
|
|
1953
|
+
})
|
|
1954
|
+
|
|
1955
|
+
// Custom duration (longer)
|
|
1956
|
+
toast({ title: 'Processing your request...', duration: 8000 })
|
|
1957
|
+
|
|
1958
|
+
// Programmatic dismiss
|
|
1959
|
+
const id = toast({ title: 'Uploading...', duration: Infinity })
|
|
1960
|
+
await upload()
|
|
1961
|
+
dismiss(id)
|
|
1962
|
+
toast({ title: 'Upload complete', variant: 'success' })
|
|
1963
|
+
```
|
|
1097
1964
|
|
|
1098
|
-
|
|
1965
|
+
---
|
|
1966
|
+
|
|
1967
|
+
### ConfirmDialog
|
|
1968
|
+
|
|
1969
|
+
**Import:** `import { ConfirmDialog } from '@retray-dev/ui-kit'`
|
|
1970
|
+
|
|
1971
|
+
**When to use:** Confirmation prompts before irreversible or destructive actions — delete, send money, discard changes. Always confirm before actions that can't be undone.
|
|
1972
|
+
|
|
1973
|
+
**Requires:** `BottomSheetModalProvider` at app root (same as `Sheet`).
|
|
1974
|
+
|
|
1975
|
+
| Prop | Type | Default | Notes |
|
|
1976
|
+
|------|------|---------|-------|
|
|
1977
|
+
| visible | `boolean` | required | Controls dialog visibility |
|
|
1978
|
+
| title | `string` | required | Dialog heading |
|
|
1979
|
+
| description | `string` | — | Supporting text — describe what exactly will happen |
|
|
1980
|
+
| confirmLabel | `string` | `'Confirm'` | Confirm button text |
|
|
1981
|
+
| cancelLabel | `string` | `'Cancel'` | Cancel button text |
|
|
1982
|
+
| confirmVariant | `'primary' \| 'destructive'` | `'primary'` | Use `'destructive'` for delete/remove actions |
|
|
1983
|
+
| onConfirm | `() => void` | required | Called when confirm is tapped |
|
|
1984
|
+
| onCancel | `() => void` | required | Called when cancel is tapped or backdrop pressed |
|
|
1099
1985
|
|
|
1100
1986
|
**Notes:**
|
|
1101
|
-
-
|
|
1102
|
-
-
|
|
1103
|
-
-
|
|
1104
|
-
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1987
|
+
- Powered by `@gorhom/bottom-sheet` with `enableDynamicSizing`
|
|
1988
|
+
- Buttons are full-width, stacked vertically (confirm on top)
|
|
1989
|
+
- Cancel shows X icon, confirm shows check (primary) or trash-2 (destructive) icon
|
|
1990
|
+
- Swipe down or backdrop press calls `onCancel`
|
|
1991
|
+
|
|
1992
|
+
**Haptics:** `impactLight` on open.
|
|
1993
|
+
|
|
1994
|
+
**Examples:**
|
|
1995
|
+
```tsx
|
|
1996
|
+
// Delete confirmation
|
|
1997
|
+
<ConfirmDialog
|
|
1998
|
+
visible={showDelete}
|
|
1999
|
+
title="Delete transaction?"
|
|
2000
|
+
description="$45.000 · Mercado · 12 mar · This action cannot be undone."
|
|
2001
|
+
confirmLabel="Delete"
|
|
2002
|
+
confirmVariant="destructive"
|
|
2003
|
+
onConfirm={handleDelete}
|
|
2004
|
+
onCancel={() => setShowDelete(false)}
|
|
2005
|
+
/>
|
|
2006
|
+
|
|
2007
|
+
// Send money confirmation
|
|
2008
|
+
<ConfirmDialog
|
|
2009
|
+
visible={showConfirm}
|
|
2010
|
+
title="Send payment?"
|
|
2011
|
+
description={`Send $${amount} to ${recipientName}`}
|
|
2012
|
+
confirmLabel="Send"
|
|
2013
|
+
onConfirm={handleSend}
|
|
2014
|
+
onCancel={() => setShowConfirm(false)}
|
|
2015
|
+
/>
|
|
2016
|
+
|
|
2017
|
+
// Discard changes
|
|
2018
|
+
<ConfirmDialog
|
|
2019
|
+
visible={showDiscard}
|
|
2020
|
+
title="Discard changes?"
|
|
2021
|
+
description="All unsaved changes will be lost."
|
|
2022
|
+
confirmLabel="Discard"
|
|
2023
|
+
confirmVariant="destructive"
|
|
2024
|
+
onConfirm={() => { setShowDiscard(false); goBack() }}
|
|
2025
|
+
onCancel={() => setShowDiscard(false)}
|
|
2026
|
+
/>
|
|
2027
|
+
```
|
|
1107
2028
|
|
|
1108
2029
|
---
|
|
1109
2030
|
|
|
1110
2031
|
### ListItem
|
|
1111
2032
|
|
|
1112
2033
|
**Import:** `import { ListItem } from '@retray-dev/ui-kit'`
|
|
1113
|
-
|
|
2034
|
+
|
|
2035
|
+
**When to use:** Rows in lists, feeds, menus, settings screens. The most compositional component — composes left slot, text block, and right slot in a standard horizontal layout. Supports plain (list items) and card (standalone cards) variants.
|
|
1114
2036
|
|
|
1115
2037
|
| Prop | Type | Default | Notes |
|
|
1116
2038
|
|------|------|---------|-------|
|
|
1117
|
-
| title | `string` | required | Primary text
|
|
1118
|
-
| subtitle | `string` | — | Secondary line below
|
|
1119
|
-
| caption | `string` | — | Tertiary
|
|
1120
|
-
| leftRender | `ReactNode` | — |
|
|
1121
|
-
| rightRender | `string \| ReactNode` | — | Content on the right edge. Strings
|
|
1122
|
-
| leftIcon | `string` | — | Icon name
|
|
1123
|
-
| rightIcon | `string` | — | Icon name
|
|
1124
|
-
| leftIconColor | `string` | — | Override left icon color |
|
|
1125
|
-
| rightIconColor | `string` | — | Override right icon color |
|
|
1126
|
-
| variant | `'plain' \| 'card'` | `'plain'` | `plain`: no background
|
|
1127
|
-
| showChevron | `boolean` | `false` |
|
|
1128
|
-
| showSeparator | `boolean` | `false` |
|
|
1129
|
-
| onPress | `() => void` | — | Makes
|
|
2039
|
+
| title | `string` | required | Primary text |
|
|
2040
|
+
| subtitle | `string` | — | Secondary line below title |
|
|
2041
|
+
| caption | `string` | — | Tertiary line below subtitle |
|
|
2042
|
+
| leftRender | `ReactNode` | — | Content in the fixed 44×44pt left slot |
|
|
2043
|
+
| rightRender | `string \| ReactNode` | — | Content on the right edge. Strings auto-styled as muted text |
|
|
2044
|
+
| leftIcon | `string` | — | Icon name for left slot. Takes precedence over `leftRender` |
|
|
2045
|
+
| rightIcon | `string` | — | Icon name for right slot. Takes precedence over `rightRender` |
|
|
2046
|
+
| leftIconColor | `string` | — | Override left icon color (default: `foreground`) |
|
|
2047
|
+
| rightIconColor | `string` | — | Override right icon color (default: `foregroundMuted`) |
|
|
2048
|
+
| variant | `'plain' \| 'card'` | `'plain'` | `plain`: no background. `card`: surface with border and shadow |
|
|
2049
|
+
| showChevron | `boolean` | `false` | Right-pointing chevron. Ignored when `rightRender` is set |
|
|
2050
|
+
| showSeparator | `boolean` | `false` | Hairline separator at bottom. Useful for stacking plain items |
|
|
2051
|
+
| onPress | `() => void` | — | Makes row pressable (scale animation + haptics) |
|
|
1130
2052
|
| disabled | `boolean` | — | Reduces opacity to 0.45 |
|
|
1131
|
-
| style | `ViewStyle` | — |
|
|
1132
|
-
| titleStyle | `TextStyle` | — |
|
|
1133
|
-
| subtitleStyle | `TextStyle` | — |
|
|
1134
|
-
| captionStyle | `TextStyle` | — |
|
|
2053
|
+
| style | `ViewStyle` | — | Outer container style |
|
|
2054
|
+
| titleStyle | `TextStyle` | — | Override title text style |
|
|
2055
|
+
| subtitleStyle | `TextStyle` | — | Override subtitle text style |
|
|
2056
|
+
| captionStyle | `TextStyle` | — | Override caption text style |
|
|
1135
2057
|
| icon | `ReactNode` | — | **Deprecated** — use `leftRender` |
|
|
1136
2058
|
| trailing | `string \| ReactNode` | — | **Deprecated** — use `rightRender` |
|
|
1137
2059
|
|
|
1138
|
-
**
|
|
2060
|
+
**Slots:**
|
|
2061
|
+
- `leftRender` / `leftIcon` — 44×44pt fixed container, centered. Good for Avatar, icons, thumbnails
|
|
2062
|
+
- `rightRender` / `rightIcon` — max 160pt wide, right-aligned. Good for Badge, price text, Switch
|
|
2063
|
+
- `showChevron` — `›` chevron, 24pt, `foregroundMuted` color. Only shows when `rightRender` is absent
|
|
2064
|
+
|
|
2065
|
+
**Separator inset:** Aligns to text block — `marginLeft` adjusts to skip over left slot when present.
|
|
2066
|
+
|
|
2067
|
+
**Animation:** Scale springs to 0.97 on press (subtler than Button's 0.95).
|
|
1139
2068
|
|
|
1140
|
-
**
|
|
2069
|
+
**Haptics:** `selectionAsync` on press.
|
|
2070
|
+
|
|
2071
|
+
**Examples:**
|
|
1141
2072
|
```tsx
|
|
1142
|
-
// Simple row
|
|
2073
|
+
// Simple settings row
|
|
2074
|
+
<ListItem title="Profile" showChevron onPress={() => navigate('profile')} />
|
|
2075
|
+
|
|
2076
|
+
// With subtitle and chevron
|
|
1143
2077
|
<ListItem
|
|
1144
|
-
title="
|
|
1145
|
-
subtitle="
|
|
2078
|
+
title="Notifications"
|
|
2079
|
+
subtitle="Push, email, and SMS"
|
|
1146
2080
|
showChevron
|
|
1147
|
-
onPress={() => navigate('
|
|
2081
|
+
onPress={() => navigate('notifications')}
|
|
1148
2082
|
/>
|
|
1149
2083
|
|
|
1150
|
-
//
|
|
2084
|
+
// Icon left slot + chevron
|
|
2085
|
+
<ListItem title="Profile" leftIcon="user" showChevron onPress={() => navigate('profile')} />
|
|
2086
|
+
|
|
2087
|
+
// Icon left + badge right
|
|
1151
2088
|
<ListItem
|
|
1152
|
-
|
|
1153
|
-
title=
|
|
1154
|
-
subtitle=
|
|
1155
|
-
|
|
2089
|
+
leftIcon="bell"
|
|
2090
|
+
title="Notifications"
|
|
2091
|
+
subtitle="3 unread"
|
|
2092
|
+
rightRender={<Badge label="3" variant="destructive" size="sm" />}
|
|
2093
|
+
onPress={() => navigate('notifications')}
|
|
2094
|
+
/>
|
|
2095
|
+
|
|
2096
|
+
// Avatar + full text stack + right content
|
|
2097
|
+
<ListItem
|
|
2098
|
+
leftRender={<Avatar src={user.avatar} fallback={user.initials} status="online" />}
|
|
2099
|
+
title={user.name}
|
|
2100
|
+
subtitle={user.role}
|
|
2101
|
+
caption="Active 2 min ago"
|
|
1156
2102
|
rightRender={
|
|
1157
2103
|
<View style={{ alignItems: 'flex-end', gap: 4 }}>
|
|
1158
|
-
<Text variant="
|
|
1159
|
-
<Badge label={
|
|
2104
|
+
<Text variant="title-sm">${amount}</Text>
|
|
2105
|
+
<Badge label={status} variant="warningOutline" size="sm" />
|
|
1160
2106
|
</View>
|
|
1161
2107
|
}
|
|
1162
|
-
onPress={() => navigate('
|
|
2108
|
+
onPress={() => navigate('user', { id: user.id })}
|
|
1163
2109
|
/>
|
|
1164
2110
|
|
|
1165
|
-
//
|
|
1166
|
-
<ListItem
|
|
1167
|
-
|
|
2111
|
+
// Switch in right slot
|
|
2112
|
+
<ListItem
|
|
2113
|
+
leftIcon="moon"
|
|
2114
|
+
title="Dark mode"
|
|
2115
|
+
rightRender={<Switch checked={darkMode} onCheckedChange={setDarkMode} />}
|
|
2116
|
+
/>
|
|
1168
2117
|
|
|
1169
2118
|
// Card variant (standalone surface)
|
|
1170
2119
|
<ListItem
|
|
1171
2120
|
variant="card"
|
|
1172
2121
|
leftIcon="credit-card"
|
|
1173
|
-
title="
|
|
1174
|
-
subtitle="Available
|
|
2122
|
+
title="Checking account"
|
|
2123
|
+
subtitle="Available balance"
|
|
1175
2124
|
rightRender="$12.500"
|
|
1176
2125
|
/>
|
|
1177
2126
|
|
|
@@ -1179,113 +2128,173 @@ function MyComponent() {
|
|
|
1179
2128
|
{transactions.map((t, i) => (
|
|
1180
2129
|
<ListItem
|
|
1181
2130
|
key={t.id}
|
|
1182
|
-
|
|
2131
|
+
leftRender={<Avatar fallback={t.merchant[0]} size="sm" />}
|
|
2132
|
+
title={t.merchant}
|
|
1183
2133
|
subtitle={t.date}
|
|
1184
|
-
rightRender={
|
|
2134
|
+
rightRender={
|
|
2135
|
+
<Text variant="title-sm" color={t.amount < 0 ? colors.destructive : colors.success}>
|
|
2136
|
+
{t.amount < 0 ? '-' : '+'}${Math.abs(t.amount)}
|
|
2137
|
+
</Text>
|
|
2138
|
+
}
|
|
1185
2139
|
showSeparator={i < transactions.length - 1}
|
|
1186
|
-
onPress={() => navigate('
|
|
2140
|
+
onPress={() => navigate('transaction', { id: t.id })}
|
|
1187
2141
|
/>
|
|
1188
2142
|
))}
|
|
1189
2143
|
```
|
|
1190
2144
|
|
|
1191
|
-
**Notes:**
|
|
1192
|
-
- `leftRender` renders inside a fixed 44×44pt container (centered) — no need to wrap in a sized View
|
|
1193
|
-
- `rightRender` container has `maxWidth: 160` and aligns content to the right edge
|
|
1194
|
-
- The separator inset aligns to the text block: `marginLeft: 16 + 44 + 12` when `leftRender` is set, `marginLeft: 16` otherwise
|
|
1195
|
-
|
|
1196
2145
|
---
|
|
1197
2146
|
|
|
1198
2147
|
### Chip / ChipGroup
|
|
1199
2148
|
|
|
1200
2149
|
**Import:** `import { Chip, ChipGroup } from '@retray-dev/ui-kit'`
|
|
1201
|
-
|
|
2150
|
+
|
|
2151
|
+
**When to use:** Inline filter options, quick selections, multi-select toggles. Use `Chip` for standalone custom logic; use `ChipGroup` for managed selection (single or multi).
|
|
1202
2152
|
|
|
1203
2153
|
**`Chip` Props:**
|
|
1204
2154
|
|
|
1205
2155
|
| Prop | Type | Default | Notes |
|
|
1206
2156
|
|------|------|---------|-------|
|
|
1207
2157
|
| label | `string` | required | — |
|
|
1208
|
-
| selected | `boolean` | `false` | Controls fill
|
|
2158
|
+
| selected | `boolean` | `false` | Controls fill style |
|
|
1209
2159
|
| onPress | `() => void` | — | — |
|
|
1210
|
-
| icon | `ReactNode` | — | Custom icon
|
|
1211
|
-
| iconName | `string` | — | Icon name
|
|
2160
|
+
| icon | `ReactNode` | — | Custom icon before label |
|
|
2161
|
+
| iconName | `string` | — | Icon name. Takes precedence over `icon` |
|
|
1212
2162
|
| style | `ViewStyle` | — | — |
|
|
1213
2163
|
|
|
1214
2164
|
**`ChipGroup` Props:**
|
|
1215
2165
|
|
|
1216
2166
|
| Prop | Type | Default | Notes |
|
|
1217
2167
|
|------|------|---------|-------|
|
|
1218
|
-
| options | `ChipOption[]` | required |
|
|
1219
|
-
| value | `string \| number \| (string \| number)[]` | — |
|
|
1220
|
-
| onValueChange | `(value:
|
|
1221
|
-
| multiSelect | `boolean` | `false` |
|
|
1222
|
-
| style | `ViewStyle` | — |
|
|
2168
|
+
| options | `ChipOption[]` | required | `{ label: string, value: string \| number }` |
|
|
2169
|
+
| value | `string \| number \| (string \| number)[]` | — | Selected value(s) |
|
|
2170
|
+
| onValueChange | `(value: ...) => void` | — | Returns single value or array depending on `multiSelect` |
|
|
2171
|
+
| multiSelect | `boolean` | `false` | Allow multiple chips selected simultaneously |
|
|
2172
|
+
| style | `ViewStyle` | — | Wrapping row style |
|
|
2173
|
+
|
|
2174
|
+
**Styling:** Pill-shaped (borderRadius: 9999), 14px horizontal + 5px vertical padding, 1px border. Unselected: `surface` bg / `foreground` text / `border` border. Selected: `primary` bg / `primaryForeground` text / `primary` border.
|
|
1223
2175
|
|
|
1224
|
-
**Animation:**
|
|
2176
|
+
**Animation:** Spring scale 0.95 on press; background/text/border color transitions (150ms timing) on selection change.
|
|
1225
2177
|
|
|
1226
|
-
**
|
|
2178
|
+
**Haptics:** `selectionAsync` on change.
|
|
2179
|
+
|
|
2180
|
+
**Examples:**
|
|
1227
2181
|
```tsx
|
|
1228
|
-
// Single select
|
|
2182
|
+
// Single select percentage
|
|
1229
2183
|
const [pct, setPct] = useState(50)
|
|
1230
|
-
|
|
1231
2184
|
<ChipGroup
|
|
1232
|
-
options={[
|
|
1233
|
-
{ label: '40%', value: 40 },
|
|
1234
|
-
{ label: '50%', value: 50 },
|
|
1235
|
-
{ label: '100%', value: 100 },
|
|
1236
|
-
]}
|
|
2185
|
+
options={[{ label: '25%', value: 25 }, { label: '50%', value: 50 }, { label: '100%', value: 100 }]}
|
|
1237
2186
|
value={pct}
|
|
1238
2187
|
onValueChange={setPct}
|
|
1239
2188
|
/>
|
|
1240
2189
|
|
|
1241
|
-
// Multi select
|
|
2190
|
+
// Multi select categories
|
|
1242
2191
|
const [categories, setCategories] = useState<number[]>([1, 3])
|
|
1243
|
-
|
|
1244
2192
|
<ChipGroup
|
|
1245
2193
|
multiSelect
|
|
1246
2194
|
options={[
|
|
1247
2195
|
{ label: 'Food', value: 1 },
|
|
1248
2196
|
{ label: 'Transport', value: 2 },
|
|
1249
2197
|
{ label: 'Entertainment', value: 3 },
|
|
2198
|
+
{ label: 'Health', value: 4 },
|
|
1250
2199
|
]}
|
|
1251
2200
|
value={categories}
|
|
1252
2201
|
onValueChange={setCategories}
|
|
1253
2202
|
/>
|
|
2203
|
+
|
|
2204
|
+
// Standalone chips (custom logic)
|
|
2205
|
+
<View style={{ flexDirection: 'row', gap: SPACING.sm, flexWrap: 'wrap' }}>
|
|
2206
|
+
{filters.map((f) => (
|
|
2207
|
+
<Chip
|
|
2208
|
+
key={f.value}
|
|
2209
|
+
label={f.label}
|
|
2210
|
+
selected={activeFilter === f.value}
|
|
2211
|
+
onPress={() => setActiveFilter(f.value)}
|
|
2212
|
+
/>
|
|
2213
|
+
))}
|
|
2214
|
+
</View>
|
|
1254
2215
|
```
|
|
1255
2216
|
|
|
1256
2217
|
---
|
|
1257
2218
|
|
|
1258
|
-
###
|
|
2219
|
+
### CategoryStrip
|
|
1259
2220
|
|
|
1260
|
-
**Import:** `import {
|
|
1261
|
-
|
|
2221
|
+
**Import:** `import { CategoryStrip } from '@retray-dev/ui-kit'`
|
|
2222
|
+
|
|
2223
|
+
**When to use:** Horizontal scrollable filter/category bar at the top of browse screens — marketplace categories, content type filters, location tabs. Airbnb-style pill chips that scroll horizontally.
|
|
1262
2224
|
|
|
1263
2225
|
| Prop | Type | Default | Notes |
|
|
1264
2226
|
|------|------|---------|-------|
|
|
1265
|
-
|
|
|
1266
|
-
|
|
|
1267
|
-
|
|
|
1268
|
-
|
|
|
1269
|
-
|
|
|
1270
|
-
|
|
|
1271
|
-
|
|
1272
|
-
|
|
2227
|
+
| categories | `CategoryItem[]` | required | Category definitions |
|
|
2228
|
+
| value | `string \| string[]` | — | Selected category value(s) |
|
|
2229
|
+
| onValueChange | `(value: string \| string[]) => void` | — | Called with new selection |
|
|
2230
|
+
| multiSelect | `boolean` | `false` | Allow multiple simultaneous selections |
|
|
2231
|
+
| style | `ViewStyle` | — | ScrollView content container style |
|
|
2232
|
+
| itemStyle | `ViewStyle` | — | Style applied to each chip's wrapper |
|
|
2233
|
+
|
|
2234
|
+
**CategoryItem type:**
|
|
2235
|
+
```ts
|
|
2236
|
+
{
|
|
2237
|
+
label: string
|
|
2238
|
+
value: string
|
|
2239
|
+
icon?: ReactNode | string // Icon or icon name (16px, auto-colored)
|
|
2240
|
+
badge?: number // Count badge on chip, capped at 99
|
|
2241
|
+
}
|
|
2242
|
+
```
|
|
1273
2243
|
|
|
1274
|
-
**
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
- The cancel and confirm buttons are full-width stacked vertically.
|
|
2244
|
+
**Single select behavior:** Pressing selected item again deselects it (value becomes `''`).
|
|
2245
|
+
|
|
2246
|
+
**Multi select behavior:** Toggles item in/out of value array.
|
|
1278
2247
|
|
|
1279
|
-
**
|
|
2248
|
+
**Styling:** Pill-shaped chips (`borderRadius: RADIUS.full = 9999`), 14px horizontal + 8px vertical padding, 8px gap between chips. Selected: `primary` bg + `primaryForeground` text. Unselected: `surface` bg + `foregroundSubtle` text + `border` border.
|
|
2249
|
+
|
|
2250
|
+
**Animation:** Scale spring 0.95 on press.
|
|
2251
|
+
|
|
2252
|
+
**Haptics:** `selectionAsync` on change.
|
|
2253
|
+
|
|
2254
|
+
**Web:** Horizontal scroll with `showsHorizontalScrollIndicator={false}`.
|
|
2255
|
+
|
|
2256
|
+
**Examples:**
|
|
1280
2257
|
```tsx
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
2258
|
+
// Basic category filter
|
|
2259
|
+
<CategoryStrip
|
|
2260
|
+
categories={[
|
|
2261
|
+
{ label: 'All', value: '' },
|
|
2262
|
+
{ label: 'Trips', value: 'trips' },
|
|
2263
|
+
{ label: 'Experiences', value: 'experiences' },
|
|
2264
|
+
{ label: 'Restaurants', value: 'restaurants' },
|
|
2265
|
+
]}
|
|
2266
|
+
value={category}
|
|
2267
|
+
onValueChange={setCategory}
|
|
2268
|
+
/>
|
|
2269
|
+
|
|
2270
|
+
// With icons
|
|
2271
|
+
<CategoryStrip
|
|
2272
|
+
categories={[
|
|
2273
|
+
{ label: 'Nearby', value: 'nearby', icon: 'map-pin' },
|
|
2274
|
+
{ label: 'Popular', value: 'popular', icon: 'trending-up' },
|
|
2275
|
+
{ label: 'New', value: 'new', icon: 'star' },
|
|
2276
|
+
]}
|
|
2277
|
+
value={filter}
|
|
2278
|
+
onValueChange={setFilter}
|
|
2279
|
+
/>
|
|
2280
|
+
|
|
2281
|
+
// With badge counts
|
|
2282
|
+
<CategoryStrip
|
|
2283
|
+
categories={[
|
|
2284
|
+
{ label: 'Inbox', value: 'inbox', badge: 3 },
|
|
2285
|
+
{ label: 'Sent', value: 'sent' },
|
|
2286
|
+
{ label: 'Archived', value: 'archived' },
|
|
2287
|
+
]}
|
|
2288
|
+
value={tab}
|
|
2289
|
+
onValueChange={setTab}
|
|
2290
|
+
/>
|
|
2291
|
+
|
|
2292
|
+
// Multi-select
|
|
2293
|
+
<CategoryStrip
|
|
2294
|
+
multiSelect
|
|
2295
|
+
categories={amenities}
|
|
2296
|
+
value={selectedAmenities}
|
|
2297
|
+
onValueChange={setSelectedAmenities}
|
|
1289
2298
|
/>
|
|
1290
2299
|
```
|
|
1291
2300
|
|
|
@@ -1294,19 +2303,32 @@ const [categories, setCategories] = useState<number[]>([1, 3])
|
|
|
1294
2303
|
### LabelValue
|
|
1295
2304
|
|
|
1296
2305
|
**Import:** `import { LabelValue } from '@retray-dev/ui-kit'`
|
|
1297
|
-
|
|
2306
|
+
|
|
2307
|
+
**When to use:** Key-value display rows in receipts, transaction details, order summaries, profile information. Label on left (muted caption), value on right (medium weight).
|
|
1298
2308
|
|
|
1299
2309
|
| Prop | Type | Default | Notes |
|
|
1300
2310
|
|------|------|---------|-------|
|
|
1301
|
-
| label | `string` | required | Caption
|
|
1302
|
-
| value | `string \| ReactNode` | required | Value on the right. Strings
|
|
2311
|
+
| label | `string` | required | Caption label on the left |
|
|
2312
|
+
| value | `string \| ReactNode` | required | Value on the right. Strings auto-styled; pass `ReactNode` for custom content |
|
|
1303
2313
|
| style | `ViewStyle` | — | — |
|
|
1304
2314
|
|
|
1305
|
-
**
|
|
2315
|
+
**Styling:** Row layout, `justifyContent: 'space-between'`. Label: 13pt / Regular / `foregroundMuted`. Value: 14pt / Medium / `foreground`. 12px gap.
|
|
2316
|
+
|
|
2317
|
+
**Examples:**
|
|
1306
2318
|
```tsx
|
|
1307
|
-
<LabelValue label="
|
|
1308
|
-
<LabelValue label="
|
|
1309
|
-
<LabelValue label="
|
|
2319
|
+
<LabelValue label="Date" value="12 mar 2025" />
|
|
2320
|
+
<LabelValue label="Category" value="Food & drink" />
|
|
2321
|
+
<LabelValue label="Status" value={<Badge label="Pending" variant="warningOutline" size="sm" />} />
|
|
2322
|
+
<LabelValue label="Amount" value="$45.000" />
|
|
2323
|
+
|
|
2324
|
+
// Transaction detail
|
|
2325
|
+
<View style={{ gap: SPACING.sm }}>
|
|
2326
|
+
<LabelValue label="Merchant" value={transaction.merchant} />
|
|
2327
|
+
<LabelValue label="Date" value={formatDate(transaction.date)} />
|
|
2328
|
+
<LabelValue label="Category" value={transaction.category} />
|
|
2329
|
+
<Separator />
|
|
2330
|
+
<LabelValue label="Amount" value={`$${transaction.amount}`} />
|
|
2331
|
+
</View>
|
|
1310
2332
|
```
|
|
1311
2333
|
|
|
1312
2334
|
---
|
|
@@ -1314,42 +2336,121 @@ const [categories, setCategories] = useState<number[]>([1, 3])
|
|
|
1314
2336
|
### MonthPicker
|
|
1315
2337
|
|
|
1316
2338
|
**Import:** `import { MonthPicker } from '@retray-dev/ui-kit'`
|
|
1317
|
-
|
|
2339
|
+
|
|
2340
|
+
**When to use:** Month/year navigation — finance apps filtering by period, date pickers, reporting periods. Compact left/right navigation UI.
|
|
1318
2341
|
|
|
1319
2342
|
| Prop | Type | Default | Notes |
|
|
1320
2343
|
|------|------|---------|-------|
|
|
1321
|
-
| value | `MonthPickerValue` | required | `{ month:
|
|
1322
|
-
| onChange | `(value: MonthPickerValue) => void` | required | Called on
|
|
2344
|
+
| value | `MonthPickerValue` | required | `{ month: 1–12, year: number }` |
|
|
2345
|
+
| onChange | `(value: MonthPickerValue) => void` | required | Called on navigation |
|
|
1323
2346
|
| style | `ViewStyle` | — | — |
|
|
1324
2347
|
|
|
1325
|
-
|
|
2348
|
+
**MonthPickerValue type:**
|
|
2349
|
+
```ts
|
|
2350
|
+
{ month: number; year: number } // month is 1-indexed (January = 1)
|
|
2351
|
+
```
|
|
2352
|
+
|
|
2353
|
+
**Navigation:** Year wraps correctly at boundaries (December → January increments year, January → December decrements year). Fully controlled — no internal state.
|
|
1326
2354
|
|
|
1327
|
-
**
|
|
2355
|
+
**Styling:** Centered row with `←` / `→` buttons (44×44px each). Label: 17pt / Medium / 160px min-width, centered.
|
|
1328
2356
|
|
|
1329
|
-
**
|
|
2357
|
+
**Haptics:** `selectionAsync` on navigation.
|
|
2358
|
+
|
|
2359
|
+
**Examples:**
|
|
1330
2360
|
```tsx
|
|
1331
|
-
const [period, setPeriod] = useState({
|
|
2361
|
+
const [period, setPeriod] = useState({
|
|
2362
|
+
month: new Date().getMonth() + 1,
|
|
2363
|
+
year: new Date().getFullYear(),
|
|
2364
|
+
})
|
|
1332
2365
|
|
|
1333
2366
|
<MonthPicker value={period} onChange={setPeriod} />
|
|
1334
|
-
// Displays: "
|
|
2367
|
+
// Displays: "May 2026"
|
|
2368
|
+
|
|
2369
|
+
// In a transactions header
|
|
2370
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
2371
|
+
<Text variant="display-md">Transactions</Text>
|
|
2372
|
+
<MonthPicker value={period} onChange={setPeriod} />
|
|
2373
|
+
</View>
|
|
1335
2374
|
```
|
|
1336
2375
|
|
|
1337
2376
|
---
|
|
1338
2377
|
|
|
1339
|
-
|
|
2378
|
+
### Pressable
|
|
1340
2379
|
|
|
1341
|
-
|
|
2380
|
+
**Import:** `import { Pressable } from '@retray-dev/ui-kit'`
|
|
1342
2381
|
|
|
1343
|
-
|
|
2382
|
+
**When to use:** Custom interactive content that needs beautiful spring bounce effect matching MediaCard. Use when you need a pressable wrapper around custom layouts that aren't covered by existing button components.
|
|
1344
2383
|
|
|
1345
|
-
|
|
2384
|
+
**Extends:** `TouchableOpacityProps` (all native props pass through except `activeOpacity`)
|
|
2385
|
+
|
|
2386
|
+
| Prop | Type | Default | Notes |
|
|
2387
|
+
|------|------|---------|-------|
|
|
2388
|
+
| children | `ReactNode` | required | Content to render inside the pressable |
|
|
2389
|
+
| onPress | `() => void` | — | Press handler |
|
|
2390
|
+
| pressScale | `number` | `0.98` | Scale value on press (MediaCard-style) |
|
|
2391
|
+
| bounciness | `number` | `4` | Spring bounciness on release |
|
|
2392
|
+
| haptics | `boolean` | `true` | Enable haptic feedback on press |
|
|
2393
|
+
| hoverScale | `number` | `1.02` | Hover scale (web only). Set to `1` to disable |
|
|
2394
|
+
| disabled | `boolean` | `false` | Disable interaction |
|
|
2395
|
+
| style | `ViewStyle` | — | Animated wrapper style |
|
|
2396
|
+
|
|
2397
|
+
**Behavior:**
|
|
2398
|
+
- Press: springs to `pressScale` (default 0.98) with `speed: 40, bounciness: 0`
|
|
2399
|
+
- Release: springs back to 1.0 with `speed: 40, bounciness: 4`
|
|
2400
|
+
- Web: optional hover scale (default 1.02)
|
|
2401
|
+
- Haptics: `impactLight` on press (unless `haptics={false}`)
|
|
2402
|
+
|
|
2403
|
+
**Use cases:**
|
|
2404
|
+
- Custom card layouts
|
|
2405
|
+
- Complex pressable rows that need consistent feel
|
|
2406
|
+
- Non-standard interactive elements that aren't buttons
|
|
2407
|
+
- Wrapping groups of elements as a single pressable unit
|
|
2408
|
+
|
|
2409
|
+
**Examples:**
|
|
2410
|
+
```tsx
|
|
2411
|
+
// Custom card
|
|
2412
|
+
<Pressable onPress={() => navigate('detail')} style={{ padding: 16, borderRadius: 12, backgroundColor: colors.card }}>
|
|
2413
|
+
<Text variant="title-md">Custom Card</Text>
|
|
2414
|
+
<Text variant="body-sm">Press me for beautiful bounce effect</Text>
|
|
2415
|
+
</Pressable>
|
|
2416
|
+
|
|
2417
|
+
// Wrapping complex layout
|
|
2418
|
+
<Pressable onPress={handleSelect} pressScale={0.96} bounciness={8}>
|
|
2419
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, padding: 16 }}>
|
|
2420
|
+
<Avatar src={user.avatar} size="md" />
|
|
2421
|
+
<View style={{ flex: 1 }}>
|
|
2422
|
+
<Text variant="title-sm">{user.name}</Text>
|
|
2423
|
+
<Text variant="caption-sm">{user.role}</Text>
|
|
2424
|
+
</View>
|
|
2425
|
+
<Icon name="chevron-right" size={20} color={colors.foregroundMuted} />
|
|
2426
|
+
</View>
|
|
2427
|
+
</Pressable>
|
|
2428
|
+
|
|
2429
|
+
// Disable haptics
|
|
2430
|
+
<Pressable onPress={handleQuickAction} haptics={false}>
|
|
2431
|
+
{/* content */}
|
|
2432
|
+
</Pressable>
|
|
2433
|
+
|
|
2434
|
+
// Custom press scale (deeper press)
|
|
2435
|
+
<Pressable onPress={handlePress} pressScale={0.92} bounciness={6}>
|
|
2436
|
+
{/* content */}
|
|
2437
|
+
</Pressable>
|
|
2438
|
+
```
|
|
2439
|
+
|
|
2440
|
+
---
|
|
2441
|
+
|
|
2442
|
+
## Icon System
|
|
2443
|
+
|
|
2444
|
+
The library ships a built-in icon resolver — pass any icon name string to components without manual imports.
|
|
2445
|
+
|
|
2446
|
+
### Supported families (priority order — first match wins)
|
|
1346
2447
|
|
|
1347
2448
|
| Priority | Family | Best for |
|
|
1348
2449
|
|---|---|---|
|
|
1349
2450
|
| 1 (highest) | `Feather` | Clean line icons, UI essentials |
|
|
1350
2451
|
| 2 | `AntDesign` | Semantic UI icons |
|
|
1351
2452
|
| 3 | `Entypo` | Social, media, navigation icons |
|
|
1352
|
-
| 4 | `FontAwesome5` | Wide coverage
|
|
2453
|
+
| 4 | `FontAwesome5` | Wide coverage |
|
|
1353
2454
|
| 5 | `MaterialIcons` | Material-style icons |
|
|
1354
2455
|
| 6 (lowest) | `Ionicons` | Fallback |
|
|
1355
2456
|
|
|
@@ -1360,87 +2461,119 @@ Browse all available icons: **https://icons.expo.fyi**
|
|
|
1360
2461
|
```tsx
|
|
1361
2462
|
import { Icon } from '@retray-dev/ui-kit'
|
|
1362
2463
|
|
|
1363
|
-
<Icon name="home" size={24} color=
|
|
2464
|
+
<Icon name="home" size={24} color={colors.foreground} />
|
|
1364
2465
|
<Icon name="star" size={20} color={colors.primary} />
|
|
1365
2466
|
|
|
1366
|
-
// Force a specific family when
|
|
2467
|
+
// Force a specific family when same name exists in multiple families:
|
|
1367
2468
|
<Icon name="heart" size={24} color="red" family="FontAwesome5" />
|
|
1368
2469
|
```
|
|
1369
2470
|
|
|
1370
2471
|
**Props:**
|
|
1371
2472
|
|
|
1372
|
-
| Prop | Type | Required |
|
|
1373
|
-
|
|
1374
|
-
|
|
|
1375
|
-
|
|
|
1376
|
-
|
|
|
1377
|
-
|
|
|
2473
|
+
| Prop | Type | Required | Notes |
|
|
2474
|
+
|------|------|----------|-------|
|
|
2475
|
+
| name | `string` | yes | Icon name (e.g. `"home"`, `"arrow-right"`) |
|
|
2476
|
+
| size | `number` | yes | Icon size in points |
|
|
2477
|
+
| color | `string` | yes | Icon color |
|
|
2478
|
+
| family | `IconFamily` | no | Force a specific family |
|
|
1378
2479
|
|
|
1379
|
-
Returns `null` (no crash) if
|
|
2480
|
+
Returns `null` (no crash) if name not found in any family.
|
|
1380
2481
|
|
|
1381
2482
|
### `renderIcon` helper
|
|
1382
2483
|
|
|
1383
2484
|
```tsx
|
|
1384
2485
|
import { renderIcon } from '@retray-dev/ui-kit'
|
|
1385
2486
|
|
|
1386
|
-
|
|
2487
|
+
// Returns ReactNode or null
|
|
2488
|
+
const icon = renderIcon('check', 18, colors.primary)
|
|
1387
2489
|
```
|
|
1388
2490
|
|
|
1389
2491
|
### `iconName` props on components
|
|
1390
2492
|
|
|
1391
|
-
|
|
2493
|
+
All components with icon slots accept `iconName` — auto-resolved size and color:
|
|
2494
|
+
|
|
2495
|
+
| Component | Prop(s) | Slot | Default color |
|
|
2496
|
+
|-----------|---------|------|---------------|
|
|
2497
|
+
| `Button` | `iconName`, `iconColor` | Left or right of label | Variant label color |
|
|
2498
|
+
| `IconButton` | `iconName`, `iconColor` | Center | Variant foreground |
|
|
2499
|
+
| `Input` | `prefixIcon`, `prefixIconColor` | Before input | `foregroundMuted` |
|
|
2500
|
+
| `Input` | `suffixIcon`, `suffixIconColor` | After input | `foregroundMuted` |
|
|
2501
|
+
| `ListItem` | `leftIcon`, `leftIconColor` | Left 44×44 slot | `foreground` |
|
|
2502
|
+
| `ListItem` | `rightIcon`, `rightIconColor` | Right slot | `foregroundMuted` |
|
|
2503
|
+
| `Badge` | `iconName`, `iconColor` | Before label | Variant foreground |
|
|
2504
|
+
| `Toggle` | `iconName`, `iconColor` | When not pressed | `foregroundMuted` |
|
|
2505
|
+
| `Toggle` | `activeIconName`, `activeIconColor` | When pressed | `primary` |
|
|
2506
|
+
| `AlertBanner` | `iconName`, `iconColor` | Left of content | Variant title color |
|
|
2507
|
+
| `EmptyState` | `iconName`, `iconColor` | Center icon slot | `foregroundMuted` |
|
|
2508
|
+
| `Toast` | `iconName`, `iconColor` | Left of message | Variant text color |
|
|
2509
|
+
| `MediaCard` | `actionIconName` | Top-right of image | `#ffffff` |
|
|
2510
|
+
| `Chip` | `iconName` | Before label | Variant foreground |
|
|
2511
|
+
|
|
2512
|
+
**Precedence:** `iconName` always takes precedence over the corresponding `ReactNode` prop when both are supplied.
|
|
1392
2513
|
|
|
1393
|
-
|
|
1394
|
-
|---|---|---|---|---|
|
|
1395
|
-
| `Button` | `iconName`, `iconColor` | Left or right of label | sm=16 / md=18 / lg=20 | Variant label color |
|
|
1396
|
-
| `IconButton` | `iconName`, `iconColor` | Center (icon-only) | sm=18 / md=20 / lg=24 | Variant foreground color |
|
|
1397
|
-
| `Input` | `prefixIcon`, `prefixIconColor` | Before input text | 20 | `mutedForeground` |
|
|
1398
|
-
| `Input` | `suffixIcon`, `suffixIconColor` | After input text | 20 | `mutedForeground` |
|
|
1399
|
-
| `ListItem` | `leftIcon`, `leftIconColor` | Left 44×44 slot | 24 | `foreground` |
|
|
1400
|
-
| `ListItem` | `rightIcon`, `rightIconColor` | Right slot | 24 | `mutedForeground` |
|
|
1401
|
-
| `Badge` | `iconName`, `iconColor` | Before label | sm=10 / md=12 / lg=14 | Variant foreground |
|
|
1402
|
-
| `Toggle` | `iconName`, `iconColor` | When not pressed | sm=16 / md=18 / lg=20 | `mutedForeground` |
|
|
1403
|
-
| `Toggle` | `activeIconName`, `activeIconColor` | When pressed | sm=16 / md=18 / lg=20 | `primary` |
|
|
1404
|
-
| `AlertBanner` | `iconName`, `iconColor` | Left of content | 18 | Variant title color |
|
|
1405
|
-
| `EmptyState` | `iconName`, `iconColor` | Center icon slot | default=48 / compact=32 | `mutedForeground` |
|
|
1406
|
-
| `Toast` | `iconName`, `iconColor` | Left of message | 22 | Variant text color |
|
|
1407
|
-
|
|
1408
|
-
**Precedence:** `iconName` takes precedence over the corresponding ReactNode prop (`icon`, `prefix`, `suffix`, etc.) when both are supplied.
|
|
2514
|
+
---
|
|
1409
2515
|
|
|
1410
|
-
|
|
2516
|
+
## Hover Support (Web)
|
|
1411
2517
|
|
|
1412
|
-
**Example — Button with icon:**
|
|
1413
2518
|
```tsx
|
|
1414
|
-
|
|
1415
|
-
<Button label="Delete" variant="destructive" iconName="trash-2" />
|
|
1416
|
-
```
|
|
2519
|
+
import { useHover } from '@retray-dev/ui-kit'
|
|
1417
2520
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
<Input placeholder="Search..." prefixIcon="search" />
|
|
1421
|
-
<Input placeholder="Amount" prefixIcon="dollar-sign" suffixIcon="check" />
|
|
1422
|
-
```
|
|
2521
|
+
function MyHoverableComponent() {
|
|
2522
|
+
const { hovered, hoverHandlers } = useHover()
|
|
1423
2523
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2524
|
+
return (
|
|
2525
|
+
<View
|
|
2526
|
+
{...hoverHandlers}
|
|
2527
|
+
style={[
|
|
2528
|
+
styles.container,
|
|
2529
|
+
hovered && { backgroundColor: colors.surfaceStrong }
|
|
2530
|
+
]}
|
|
2531
|
+
/>
|
|
2532
|
+
)
|
|
2533
|
+
}
|
|
1428
2534
|
```
|
|
1429
2535
|
|
|
1430
|
-
**
|
|
1431
|
-
|
|
1432
|
-
<Badge label="Active" variant="default" iconName="check" size="sm" />
|
|
1433
|
-
<Badge label="Error" variant="destructive" iconName="alert-circle" />
|
|
1434
|
-
```
|
|
2536
|
+
- **Web:** Returns `{ hovered: boolean, hoverHandlers: { onMouseEnter, onMouseLeave } }`
|
|
2537
|
+
- **Native:** Always returns `{ hovered: false, hoverHandlers: {} }` — no-op, no crashes
|
|
1435
2538
|
|
|
1436
|
-
|
|
1437
|
-
```tsx
|
|
1438
|
-
const { toast } = useToast()
|
|
1439
|
-
toast({ title: "Saved", variant: "success", iconName: "check-circle" })
|
|
1440
|
-
toast({ title: "Oops", variant: "destructive", iconName: "x-circle" })
|
|
1441
|
-
```
|
|
2539
|
+
Built-in web hover: `MediaCard` (shadow lift), interactive components use this internally.
|
|
1442
2540
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
2541
|
+
---
|
|
2542
|
+
|
|
2543
|
+
## Design System Conventions
|
|
2544
|
+
|
|
2545
|
+
### Touch targets
|
|
2546
|
+
All interactive elements maintain ≥44pt touch height per Apple HIG:
|
|
2547
|
+
- Button md: 48px, sm: 40px, lg: 56px
|
|
2548
|
+
- Input / Textarea: 56px (14px vertical padding)
|
|
2549
|
+
- IconButton md: 44px, lg: 52px
|
|
2550
|
+
- Checkbox: 24×24px box
|
|
2551
|
+
- Switch: 30px track height
|
|
2552
|
+
- MonthPicker arrows: 44×44px
|
|
2553
|
+
|
|
2554
|
+
### Haptic patterns
|
|
2555
|
+
- `impactLight` — Button, Card (press), Sheet open, Toast, MediaCard action
|
|
2556
|
+
- `selectionAsync` — Checkbox, Switch, Toggle, RadioGroup, Select, Slider steps, Accordion, ListItem, Chip, CategoryStrip, Tabs, MonthPicker
|
|
2557
|
+
- `notificationSuccess` — Toast `success`
|
|
2558
|
+
- `notificationError` — Toast `destructive`, Toast `warning`
|
|
2559
|
+
|
|
2560
|
+
### Animation press scales
|
|
2561
|
+
- `Button` → 0.95 (strong spring feedback)
|
|
2562
|
+
- `Card` → 0.98 (subtle, appropriate for large surfaces)
|
|
2563
|
+
- `ListItem` → 0.97 (between — medium row targets)
|
|
2564
|
+
- `MediaCard` → 0.98 (large surface)
|
|
2565
|
+
- `IconButton` → 0.95
|
|
2566
|
+
- `Chip`, `CategoryStrip chip` → 0.95
|
|
2567
|
+
|
|
2568
|
+
### Platform differences
|
|
2569
|
+
- `useNativeDriver` — `Platform.OS !== 'web'` everywhere (native driver disabled on web)
|
|
2570
|
+
- `Select` — wheel modal on iOS, dialog on Android, `<select>` on web
|
|
2571
|
+
- `Toast` — full width on mobile, 400px centered on web
|
|
2572
|
+
- Hover states — web only via `useHover()`
|
|
2573
|
+
- `Skeleton` shimmer highlight — adapts opacity for light/dark mode
|
|
2574
|
+
|
|
2575
|
+
### Dynamic Type
|
|
2576
|
+
All `Text` and `TextInput` components have `allowFontScaling={true}` — respects user font size accessibility settings.
|
|
2577
|
+
|
|
2578
|
+
### Scaling utilities (internal)
|
|
2579
|
+
Used internally — not exported. `s()` horizontal scale, `vs()` vertical scale, `ms()` moderate scale relative to 350×680 base.
|