@retray-dev/ui-kit 0.1.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/COMPONENTS.md +710 -0
  2. package/LICENSE +21 -0
  3. package/README.md +150 -0
  4. package/dist/index.d.mts +345 -4
  5. package/dist/index.d.ts +345 -4
  6. package/dist/index.js +1644 -58
  7. package/dist/index.mjs +1590 -58
  8. package/package.json +44 -5
  9. package/src/components/Accordion/Accordion.tsx +173 -0
  10. package/src/components/Accordion/index.ts +2 -0
  11. package/src/components/Alert/Alert.tsx +59 -0
  12. package/src/components/Alert/index.ts +2 -0
  13. package/src/components/Avatar/Avatar.tsx +71 -0
  14. package/src/components/Avatar/index.ts +2 -0
  15. package/src/components/Badge/Badge.tsx +48 -0
  16. package/src/components/Badge/index.ts +2 -0
  17. package/src/components/Button/Button.tsx +94 -45
  18. package/src/components/Card/Card.tsx +103 -0
  19. package/src/components/Card/index.ts +9 -0
  20. package/src/components/Checkbox/Checkbox.tsx +98 -0
  21. package/src/components/Checkbox/index.ts +2 -0
  22. package/src/components/EmptyState/EmptyState.tsx +67 -0
  23. package/src/components/EmptyState/index.ts +2 -0
  24. package/src/components/Input/Input.tsx +28 -35
  25. package/src/components/Progress/Progress.tsx +52 -0
  26. package/src/components/Progress/index.ts +2 -0
  27. package/src/components/RadioGroup/RadioGroup.tsx +132 -0
  28. package/src/components/RadioGroup/index.ts +2 -0
  29. package/src/components/Select/Select.tsx +232 -0
  30. package/src/components/Select/index.ts +2 -0
  31. package/src/components/Separator/Separator.tsx +33 -0
  32. package/src/components/Separator/index.ts +2 -0
  33. package/src/components/Sheet/Sheet.tsx +115 -0
  34. package/src/components/Sheet/index.ts +2 -0
  35. package/src/components/Skeleton/Skeleton.tsx +63 -0
  36. package/src/components/Skeleton/index.ts +2 -0
  37. package/src/components/Slider/Slider.tsx +143 -0
  38. package/src/components/Slider/index.ts +2 -0
  39. package/src/components/Spinner/Spinner.tsx +21 -0
  40. package/src/components/Spinner/index.ts +2 -0
  41. package/src/components/Switch/Switch.tsx +86 -0
  42. package/src/components/Switch/index.ts +2 -0
  43. package/src/components/Tabs/Tabs.tsx +196 -0
  44. package/src/components/Tabs/index.ts +2 -0
  45. package/src/components/Text/Text.tsx +10 -4
  46. package/src/components/Textarea/Textarea.tsx +89 -0
  47. package/src/components/Textarea/index.ts +2 -0
  48. package/src/components/Toast/Toast.tsx +200 -0
  49. package/src/components/Toast/index.ts +2 -0
  50. package/src/components/Toggle/Toggle.tsx +92 -0
  51. package/src/components/Toggle/index.ts +2 -0
  52. package/src/index.ts +26 -0
  53. package/src/theme/ThemeProvider.tsx +47 -0
  54. package/src/theme/colors.ts +45 -0
  55. package/src/theme/index.ts +4 -0
  56. package/src/theme/types.ts +33 -0
package/COMPONENTS.md ADDED
@@ -0,0 +1,710 @@
1
+ # @retray-dev/ui-kit — Component Reference
2
+
3
+ This file is the AI reference for this package. It is shipped inside the npm package so consuming projects can import it into their `CLAUDE.md` with:
4
+
5
+ ```markdown
6
+ ## UI Components
7
+ @./node_modules/@retray-dev/ui-kit/COMPONENTS.md
8
+ ```
9
+
10
+ ---
11
+
12
+ ## Setup (Required)
13
+
14
+ Wrap your app root with all required providers in this exact order:
15
+
16
+ ```tsx
17
+ import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'
18
+ import { GestureHandlerRootView } from 'react-native-gesture-handler'
19
+ import { ThemeProvider, BottomSheetModalProvider, ToastProvider } from '@retray-dev/ui-kit'
20
+
21
+ export default function App() {
22
+ return (
23
+ <SafeAreaProvider initialMetrics={initialWindowMetrics}>
24
+ <GestureHandlerRootView style={{ flex: 1 }}>
25
+ <ThemeProvider colorScheme="system">
26
+ <BottomSheetModalProvider>
27
+ <ToastProvider>
28
+ {/* your app */}
29
+ </ToastProvider>
30
+ </BottomSheetModalProvider>
31
+ </ThemeProvider>
32
+ </GestureHandlerRootView>
33
+ </SafeAreaProvider>
34
+ )
35
+ }
36
+ ```
37
+
38
+ **Provider order is mandatory:**
39
+ - `SafeAreaProvider` must be outermost — required by `useSafeAreaInsets` in `ToastProvider`
40
+ - `initialMetrics={initialWindowMetrics}` is required on Android to avoid a "No safe area value available" crash on first render
41
+ - `GestureHandlerRootView` must wrap everything — required by `@gorhom/bottom-sheet`
42
+ - `BottomSheetModalProvider` must be inside `GestureHandlerRootView`
43
+ - `ToastProvider` must be inside `SafeAreaProvider`
44
+
45
+ ### ThemeProvider Props
46
+
47
+ | Prop | Type | Default | Notes |
48
+ |------|------|---------|-------|
49
+ | colorScheme | `'light' \| 'dark' \| 'system'` | `'system'` | `'system'` auto-detects the device setting and updates when it changes |
50
+ | theme | `{ light?: Partial<ThemeColors>, dark?: Partial<ThemeColors> }` | — | Override any subset of color tokens per scheme |
51
+
52
+ **Custom theme example:**
53
+ ```tsx
54
+ const myTheme = {
55
+ light: { primary: '#6366f1', primaryForeground: '#ffffff' },
56
+ dark: { primary: '#818cf8', primaryForeground: '#ffffff' },
57
+ }
58
+ <ThemeProvider theme={myTheme} colorScheme="system">
59
+ ```
60
+
61
+ ### useTheme Hook
62
+
63
+ Access the active color tokens and scheme inside any component:
64
+ ```tsx
65
+ import { useTheme } from '@retray-dev/ui-kit'
66
+
67
+ function MyComponent() {
68
+ const { colors, colorScheme } = useTheme()
69
+ return <View style={{ backgroundColor: colors.background }} />
70
+ }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Theme Tokens
76
+
77
+ All 18 tokens are available via `useTheme().colors`.
78
+
79
+ | Token | Light | Dark | Semantic Role |
80
+ |-------|-------|------|---------------|
81
+ | `background` | `#ffffff` | `#171717` | Screen / page background |
82
+ | `foreground` | `#171717` | `#fafafa` | Primary text color |
83
+ | `card` | `#ffffff` | `#1f1f1f` | Card / surface background |
84
+ | `cardForeground` | `#171717` | `#fafafa` | Text on cards |
85
+ | `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states) |
86
+ | `primaryForeground` | `#fafafa` | `#1a1a1a` | Text/icon on primary background |
87
+ | `secondary` | `#f5f5f5` | `#2a2a2a` | Secondary surfaces |
88
+ | `secondaryForeground` | `#1a1a1a` | `#fafafa` | Text on secondary |
89
+ | `muted` | `#f5f5f5` | `#2a2a2a` | Muted backgrounds, skeleton fills, track fills |
90
+ | `mutedForeground` | `#646464` | `#a3a3a3` | Placeholder text, helper text, captions (WCAG AA ≥4.5:1) |
91
+ | `accent` | `#f5f5f5` | `#2a2a2a` | Hover / pressed state fills |
92
+ | `accentForeground` | `#1a1a1a` | `#fafafa` | Text on accent |
93
+ | `destructive` | `#ef4444` | `#dc2626` | Error / danger / delete actions |
94
+ | `destructiveForeground` | `#fafafa` | `#fafafa` | Text on destructive |
95
+ | `border` | `#e5e5e5` | `#2a2a2a` | Borders and dividers |
96
+ | `input` | `#e5e5e5` | `#2a2a2a` | Input field border color |
97
+ | `ring` | `#a3a3a3` | `#d4d4d4` | Focus ring color |
98
+
99
+ ---
100
+
101
+ ## Components
102
+
103
+ ---
104
+
105
+ ### Text
106
+
107
+ **Import:** `import { Text } from '@retray-dev/ui-kit'`
108
+ **When to use:** All text in the app. Replaces React Native's `Text` with semantic variants.
109
+ **Extends:** `TextProps` from React Native — all native props pass through.
110
+
111
+ | Prop | Type | Default | Notes |
112
+ |------|------|---------|-------|
113
+ | variant | `'h1' \| 'h2' \| 'h3' \| 'body' \| 'caption' \| 'label'` | `'body'` | Sets font size, weight, and line height |
114
+ | color | `string` | — | Override the color. Defaults to `foreground`, except `caption` which uses `mutedForeground` |
115
+
116
+ **Sizes:**
117
+ - `h1`: 32px / 700 weight
118
+ - `h2`: 24px / 700 weight
119
+ - `h3`: 20px / 600 weight
120
+ - `body`: 16px / 400 weight
121
+ - `label`: 14px / 500 weight
122
+ - `caption`: 12px / 400 weight / `mutedForeground` color by default
123
+
124
+ **Example:**
125
+ ```tsx
126
+ <Text variant="h2">Welcome back</Text>
127
+ <Text variant="body">Your account is ready.</Text>
128
+ <Text variant="caption">Last updated 2 hours ago</Text>
129
+ <Text variant="label" color="#6366f1">Pro plan</Text>
130
+ ```
131
+
132
+ ---
133
+
134
+ ### Button
135
+
136
+ **Import:** `import { Button } from '@retray-dev/ui-kit'`
137
+ **When to use:** Any interactive action. Use `variant` to communicate intent.
138
+ **Extends:** `TouchableOpacityProps` — all RN TouchableOpacity props pass through.
139
+
140
+ | Prop | Type | Default | Notes |
141
+ |------|------|---------|-------|
142
+ | label | `string` | required | Button text |
143
+ | variant | `'primary' \| 'secondary' \| 'outline' \| 'ghost'` | `'primary'` | Visual style |
144
+ | size | `'sm' \| 'md' \| 'lg'` | `'md'` | — |
145
+ | loading | `boolean` | `false` | Replaces label with a spinner and forces disabled state |
146
+ | fullWidth | `boolean` | `false` | Stretches to container width (`alignSelf: 'stretch'`) |
147
+ | disabled | `boolean` | — | Reduces opacity to 0.45 |
148
+
149
+ **Variants:**
150
+ - `primary`: filled with `primary` token — main actions
151
+ - `secondary`: filled with `secondary` token — less prominent actions
152
+ - `outline`: transparent with `border` — alternative without fill
153
+ - `ghost`: fully transparent — in-context or low-emphasis actions
154
+
155
+ **Animations:** Scale springs to 0.97 on `onPressIn`, back to 1.0 on `onPressOut`.
156
+
157
+ **Example:**
158
+ ```tsx
159
+ <Button label="Save changes" onPress={handleSave} />
160
+ <Button label="Cancel" variant="ghost" onPress={onCancel} />
161
+ <Button label="Delete" variant="outline" size="sm" />
162
+ <Button label="Submitting..." loading fullWidth />
163
+ ```
164
+
165
+ ---
166
+
167
+ ### Input
168
+
169
+ **Import:** `import { Input } from '@retray-dev/ui-kit'`
170
+ **When to use:** Single-line text entry. Includes built-in label, error, and hint support.
171
+ **Extends:** `TextInputProps` from React Native — all native props pass through.
172
+
173
+ | Prop | Type | Default | Notes |
174
+ |------|------|---------|-------|
175
+ | label | `string` | — | Label above the input |
176
+ | error | `string` | — | Shows error text below; turns border red (`destructive` token) |
177
+ | hint | `string` | — | Helper text below (hidden when `error` is set) |
178
+
179
+ **Border colors:** `destructive` when `error` is set, `ring` when focused, `border` otherwise.
180
+
181
+ **Example:**
182
+ ```tsx
183
+ <Input label="Email" placeholder="you@example.com" keyboardType="email-address" />
184
+ <Input label="Password" secureTextEntry error="Incorrect password" />
185
+ <Input label="Username" hint="Must be at least 4 characters" />
186
+ ```
187
+
188
+ ---
189
+
190
+ ### Textarea
191
+
192
+ **Import:** `import { Textarea } from '@retray-dev/ui-kit'`
193
+ **When to use:** Multi-line text entry. Same API as `Input` plus `rows`.
194
+ **Extends:** `TextInputProps` from React Native — all native props pass through.
195
+
196
+ | Prop | Type | Default | Notes |
197
+ |------|------|---------|-------|
198
+ | label | `string` | — | Label above |
199
+ | error | `string` | — | Error text below; red border |
200
+ | hint | `string` | — | Helper text below |
201
+ | rows | `number` | `4` | Sets `minHeight` (each row ≈ 28px) |
202
+
203
+ **Example:**
204
+ ```tsx
205
+ <Textarea label="Bio" placeholder="Tell us about yourself" rows={5} />
206
+ ```
207
+
208
+ ---
209
+
210
+ ### Badge
211
+
212
+ **Import:** `import { Badge } from '@retray-dev/ui-kit'`
213
+ **When to use:** Status labels, tags, counts.
214
+
215
+ | Prop | Type | Default | Notes |
216
+ |------|------|---------|-------|
217
+ | label | `string` | required | — |
218
+ | variant | `'default' \| 'secondary' \| 'destructive' \| 'outline'` | `'default'` | — |
219
+ | style | `ViewStyle` | — | — |
220
+
221
+ **Example:**
222
+ ```tsx
223
+ <Badge label="New" />
224
+ <Badge label="Error" variant="destructive" />
225
+ <Badge label="Draft" variant="secondary" />
226
+ <Badge label="Beta" variant="outline" />
227
+ ```
228
+
229
+ ---
230
+
231
+ ### Avatar
232
+
233
+ **Import:** `import { Avatar } from '@retray-dev/ui-kit'`
234
+ **When to use:** User profile pictures with automatic fallback to initials.
235
+
236
+ | Prop | Type | Default | Notes |
237
+ |------|------|---------|-------|
238
+ | src | `string` | — | Image URI |
239
+ | fallback | `string` | — | Text shown when image fails or `src` is absent — first 2 chars, uppercased |
240
+ | size | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | sm=24, md=32, lg=48, xl=64 (diameter in pt) |
241
+ | style | `ViewStyle` | — | — |
242
+
243
+ **Example:**
244
+ ```tsx
245
+ <Avatar src="https://..." fallback="JC" size="lg" />
246
+ <Avatar fallback="AN" size="md" />
247
+ ```
248
+
249
+ ---
250
+
251
+ ### Separator
252
+
253
+ **Import:** `import { Separator } from '@retray-dev/ui-kit'`
254
+ **When to use:** Visual dividers between sections.
255
+
256
+ | Prop | Type | Default | Notes |
257
+ |------|------|---------|-------|
258
+ | orientation | `'horizontal' \| 'vertical'` | `'horizontal'` | Vertical requires a parent with a defined height |
259
+ | style | `ViewStyle` | — | — |
260
+
261
+ **Example:**
262
+ ```tsx
263
+ <Separator />
264
+ <View style={{ flexDirection: 'row', height: 40 }}>
265
+ <Text>Left</Text>
266
+ <Separator orientation="vertical" style={{ marginHorizontal: 12 }} />
267
+ <Text>Right</Text>
268
+ </View>
269
+ ```
270
+
271
+ ---
272
+
273
+ ### Spinner
274
+
275
+ **Import:** `import { Spinner } from '@retray-dev/ui-kit'`
276
+ **When to use:** Loading state indicator. Wraps React Native's `ActivityIndicator`.
277
+ **Extends:** `ActivityIndicatorProps` (except `size`).
278
+
279
+ | Prop | Type | Default | Notes |
280
+ |------|------|---------|-------|
281
+ | size | `'sm' \| 'md' \| 'lg'` | `'md'` | `sm`/`md` map to RN `'small'`, `lg` maps to `'large'` |
282
+ | color | `string` | `primary` token | Override spinner color |
283
+
284
+ **Example:**
285
+ ```tsx
286
+ <Spinner />
287
+ <Spinner size="lg" color="#6366f1" />
288
+ ```
289
+
290
+ ---
291
+
292
+ ### Skeleton
293
+
294
+ **Import:** `import { Skeleton } from '@retray-dev/ui-kit'`
295
+ **When to use:** Placeholder while content is loading. Pulses with a looping opacity animation.
296
+
297
+ | Prop | Type | Default | Notes |
298
+ |------|------|---------|-------|
299
+ | width | `number \| string` | `'100%'` | — |
300
+ | height | `number` | `16` | — |
301
+ | borderRadius | `number` | `6` | — |
302
+ | style | `ViewStyle` | — | — |
303
+
304
+ **Example:**
305
+ ```tsx
306
+ <Skeleton height={20} width="60%" />
307
+ <Skeleton height={80} borderRadius={10} />
308
+ ```
309
+
310
+ ---
311
+
312
+ ### Progress
313
+
314
+ **Import:** `import { Progress } from '@retray-dev/ui-kit'`
315
+ **When to use:** Show completion percentage for a task or upload.
316
+
317
+ | Prop | Type | Default | Notes |
318
+ |------|------|---------|-------|
319
+ | value | `number` | `0` | Current value |
320
+ | max | `number` | `100` | Maximum value |
321
+ | style | `ViewStyle` | — | — |
322
+
323
+ **Animation:** Spring-animates the fill width on `value` changes (JS thread — `useNativeDriver: false`). Uses `onLayout` to capture track pixel width and interpolates to pixels (cannot animate `width: '%'`).
324
+
325
+ **Example:**
326
+ ```tsx
327
+ <Progress value={60} />
328
+ <Progress value={3} max={10} />
329
+ ```
330
+
331
+ ---
332
+
333
+ ### Card
334
+
335
+ **Import:** `import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@retray-dev/ui-kit'`
336
+ **When to use:** Grouped content with a surface background, border, and shadow.
337
+
338
+ All sub-components accept a `style` prop for overrides.
339
+
340
+ **Example:**
341
+ ```tsx
342
+ <Card>
343
+ <CardHeader>
344
+ <CardTitle>Account</CardTitle>
345
+ <CardDescription>Manage your profile settings</CardDescription>
346
+ </CardHeader>
347
+ <CardContent>
348
+ <Input label="Name" />
349
+ </CardContent>
350
+ <CardFooter>
351
+ <Button label="Save" fullWidth />
352
+ </CardFooter>
353
+ </Card>
354
+ ```
355
+
356
+ **Notes:**
357
+ - `CardHeader` and `CardContent` have `padding: 24`
358
+ - `CardFooter` has `paddingTop: 0` so it connects naturally to `CardContent`
359
+ - `CardTitle`: 18px / 600 weight, `cardForeground` color
360
+ - `CardDescription`: 14px, `mutedForeground` color
361
+
362
+ ---
363
+
364
+ ### Alert
365
+
366
+ **Import:** `import { Alert } from '@retray-dev/ui-kit'`
367
+ **When to use:** Inline feedback messages (info, success, warning, error). Not for transient toasts.
368
+
369
+ | Prop | Type | Default | Notes |
370
+ |------|------|---------|-------|
371
+ | title | `string` | — | Bold heading |
372
+ | description | `string` | — | Detail text |
373
+ | variant | `'default' \| 'destructive'` | `'default'` | `destructive` turns border and text to `destructive` token |
374
+ | icon | `ReactNode` | — | Icon placed to the left of the text content |
375
+ | style | `ViewStyle` | — | — |
376
+
377
+ **Example:**
378
+ ```tsx
379
+ <Alert title="Success" description="Your profile has been updated." />
380
+ <Alert variant="destructive" title="Error" description="Failed to save. Try again." />
381
+ ```
382
+
383
+ ---
384
+
385
+ ### EmptyState
386
+
387
+ **Import:** `import { EmptyState } from '@retray-dev/ui-kit'`
388
+ **When to use:** When a list or section has no content yet.
389
+
390
+ | Prop | Type | Default | Notes |
391
+ |------|------|---------|-------|
392
+ | title | `string` | required | — |
393
+ | description | `string` | — | — |
394
+ | icon | `ReactNode` | — | Shown in a 48×48 muted square above the text |
395
+ | action | `ReactNode` | — | Usually a `Button`, placed below the text |
396
+ | style | `ViewStyle` | — | — |
397
+
398
+ **Example:**
399
+ ```tsx
400
+ <EmptyState
401
+ title="No notifications"
402
+ description="You're all caught up!"
403
+ action={<Button label="Refresh" variant="outline" size="sm" />}
404
+ />
405
+ ```
406
+
407
+ ---
408
+
409
+ ### Checkbox
410
+
411
+ **Import:** `import { Checkbox } from '@retray-dev/ui-kit'`
412
+
413
+ | Prop | Type | Default | Notes |
414
+ |------|------|---------|-------|
415
+ | checked | `boolean` | `false` | — |
416
+ | onCheckedChange | `(checked: boolean) => void` | — | — |
417
+ | label | `string` | — | Text to the right of the box |
418
+ | disabled | `boolean` | — | — |
419
+ | style | `ViewStyle` | — | — |
420
+
421
+ **Example:**
422
+ ```tsx
423
+ const [accepted, setAccepted] = useState(false)
424
+ <Checkbox checked={accepted} onCheckedChange={setAccepted} label="I agree to the terms" />
425
+ ```
426
+
427
+ ---
428
+
429
+ ### Switch
430
+
431
+ **Import:** `import { Switch } from '@retray-dev/ui-kit'`
432
+ **When to use:** Binary on/off settings.
433
+
434
+ | Prop | Type | Default | Notes |
435
+ |------|------|---------|-------|
436
+ | checked | `boolean` | `false` | — |
437
+ | onCheckedChange | `(checked: boolean) => void` | — | — |
438
+ | disabled | `boolean` | — | Reduces opacity to 0.45 |
439
+ | style | `ViewStyle` | — | — |
440
+
441
+ **Dimensions:** Track 56×32pt, Thumb 24×24pt with 4pt offset from edges.
442
+
443
+ **Animation:** Thumb translates via spring (bounciness: 4); track color transitions via opacity timing (150ms).
444
+
445
+ **Example:**
446
+ ```tsx
447
+ <Switch checked={notifications} onCheckedChange={setNotifications} />
448
+ ```
449
+
450
+ ---
451
+
452
+ ### Toggle
453
+
454
+ **Import:** `import { Toggle } from '@retray-dev/ui-kit'`
455
+ **When to use:** Toggleable button (e.g., bold/italic in a toolbar). Looks like a button, unlike `Switch`.
456
+
457
+ | Prop | Type | Default | Notes |
458
+ |------|------|---------|-------|
459
+ | pressed | `boolean` | `false` | — |
460
+ | onPressedChange | `(pressed: boolean) => void` | — | — |
461
+ | variant | `'default' \| 'outline'` | `'default'` | `outline` adds a border when unpressed |
462
+ | size | `'sm' \| 'md' \| 'lg'` | `'md'` | sm=minH 40pt, md=minH 44pt, lg=minH 48pt |
463
+ | label | `string` | — | Text label |
464
+ | icon | `ReactNode` | — | Icon — can be combined with `label` |
465
+
466
+ **Example:**
467
+ ```tsx
468
+ <Toggle pressed={bold} onPressedChange={setBold} label="Bold" variant="outline" />
469
+ ```
470
+
471
+ ---
472
+
473
+ ### RadioGroup
474
+
475
+ **Import:** `import { RadioGroup } from '@retray-dev/ui-kit'`
476
+
477
+ | Prop | Type | Default | Notes |
478
+ |------|------|---------|-------|
479
+ | options | `RadioOption[]` | required | Each option: `{ label: string, value: string, disabled?: boolean }` |
480
+ | value | `string` | — | Currently selected value |
481
+ | onValueChange | `(value: string) => void` | — | — |
482
+ | orientation | `'vertical' \| 'horizontal'` | `'vertical'` | — |
483
+ | style | `ViewStyle` | — | — |
484
+
485
+ **Example:**
486
+ ```tsx
487
+ <RadioGroup
488
+ options={[
489
+ { label: 'Free', value: 'free' },
490
+ { label: 'Pro', value: 'pro' },
491
+ { label: 'Enterprise', value: 'enterprise', disabled: true },
492
+ ]}
493
+ value={plan}
494
+ onValueChange={setPlan}
495
+ />
496
+ ```
497
+
498
+ ---
499
+
500
+ ### Select
501
+
502
+ **Import:** `import { Select } from '@retray-dev/ui-kit'`
503
+ **When to use:** Dropdown picker. Tapping the trigger opens a modal with a scrollable list.
504
+
505
+ | Prop | Type | Default | Notes |
506
+ |------|------|---------|-------|
507
+ | options | `SelectOption[]` | required | Each option: `{ label: string, value: string, disabled?: boolean }` |
508
+ | value | `string` | — | Selected value |
509
+ | onValueChange | `(value: string) => void` | — | — |
510
+ | placeholder | `string` | `'Select an option'` | Shown when no value is selected |
511
+ | label | `string` | — | Label above the trigger |
512
+ | error | `string` | — | Error text below the trigger |
513
+ | disabled | `boolean` | — | — |
514
+ | style | `ViewStyle` | — | — |
515
+
516
+ **Example:**
517
+ ```tsx
518
+ <Select
519
+ label="Country"
520
+ options={[{ label: 'Argentina', value: 'AR' }, { label: 'Spain', value: 'ES' }]}
521
+ value={country}
522
+ onValueChange={setCountry}
523
+ placeholder="Pick a country"
524
+ />
525
+ ```
526
+
527
+ ---
528
+
529
+ ### Slider
530
+
531
+ **Import:** `import { Slider } from '@retray-dev/ui-kit'`
532
+ **When to use:** Select a numeric value within a range by dragging.
533
+
534
+ | Prop | Type | Default | Notes |
535
+ |------|------|---------|-------|
536
+ | value | `number` | `0` | — |
537
+ | minimumValue | `number` | `0` | — |
538
+ | maximumValue | `number` | `1` | — |
539
+ | step | `number` | `0` | `0` means continuous (no snapping) |
540
+ | onValueChange | `(value: number) => void` | — | Fires while dragging |
541
+ | onSlidingComplete | `(value: number) => void` | — | Fires on finger release |
542
+ | disabled | `boolean` | — | — |
543
+ | style | `ViewStyle` | — | — |
544
+
545
+ **Dimensions:** Container height=32pt, track height=6pt, thumb 28×28pt. Uses `PanResponder` internally.
546
+
547
+ **Example:**
548
+ ```tsx
549
+ <Slider value={volume} minimumValue={0} maximumValue={100} step={1} onValueChange={setVolume} />
550
+ ```
551
+
552
+ ---
553
+
554
+ ### Tabs
555
+
556
+ **Import:** `import { Tabs, TabsContent } from '@retray-dev/ui-kit'`
557
+ **When to use:** Switching between categorized sections on the same screen.
558
+
559
+ | Prop | Type | Default | Notes |
560
+ |------|------|---------|-------|
561
+ | tabs | `TabItem[]` | required | Each item: `{ label: string, value: string }` |
562
+ | value | `string` | — | Controlled active tab |
563
+ | onValueChange | `(value: string) => void` | — | — |
564
+ | children | `ReactNode` | — | `TabsContent` components |
565
+ | style | `ViewStyle` | — | — |
566
+
567
+ **`TabsContent` Props:**
568
+
569
+ | Prop | Type | Notes |
570
+ |------|------|-------|
571
+ | value | `string` | Must match a tab value in `tabs` |
572
+ | activeValue | `string` | Pass the current active tab — content is hidden when not active |
573
+ | children | `ReactNode` | — |
574
+ | style | `ViewStyle` | — |
575
+
576
+ **Animation:** An absolutely-positioned pill slides and resizes via spring (speed: 20, bounciness: 0) to track the active tab.
577
+
578
+ **Example:**
579
+ ```tsx
580
+ const [tab, setTab] = useState('profile')
581
+
582
+ <Tabs
583
+ tabs={[{ label: 'Profile', value: 'profile' }, { label: 'Security', value: 'security' }]}
584
+ value={tab}
585
+ onValueChange={setTab}
586
+ >
587
+ <TabsContent value="profile" activeValue={tab}>
588
+ <Text>Profile content</Text>
589
+ </TabsContent>
590
+ <TabsContent value="security" activeValue={tab}>
591
+ <Text>Security content</Text>
592
+ </TabsContent>
593
+ </Tabs>
594
+ ```
595
+
596
+ ---
597
+
598
+ ### Accordion
599
+
600
+ **Import:** `import { Accordion } from '@retray-dev/ui-kit'`
601
+ **When to use:** FAQs, collapsible sections. Animated expand/collapse.
602
+
603
+ | Prop | Type | Default | Notes |
604
+ |------|------|---------|-------|
605
+ | items | `AccordionItem[]` | required | Each: `{ value: string, trigger: string, content: ReactNode }` |
606
+ | type | `'single' \| 'multiple'` | `'single'` | `single`: only one open at a time. `multiple`: any number can be open |
607
+ | defaultValue | `string \| string[]` | — | Initially open item(s). Use `string[]` with `type='multiple'` |
608
+ | style | `ViewStyle` | — | — |
609
+
610
+ **Animation:** Height and chevron rotation are animated on the UI thread via `react-native-reanimated` (`withTiming`, `useSharedValue`). `Easing.out(Easing.ease)` for expand, `Easing.in(Easing.ease)` for collapse (220ms, 60 fps).
611
+
612
+ **Example:**
613
+ ```tsx
614
+ <Accordion
615
+ type="single"
616
+ items={[
617
+ { value: 'q1', trigger: 'What is this?', content: <Text>It's a UI kit.</Text> },
618
+ { value: 'q2', trigger: 'Is it free?', content: <Text>Yes.</Text> },
619
+ ]}
620
+ />
621
+ ```
622
+
623
+ ---
624
+
625
+ ### Sheet
626
+
627
+ **Import:** `import { Sheet, BottomSheetModalProvider } from '@retray-dev/ui-kit'`
628
+ **When to use:** Bottom sheet with physics-based gestures, rubber-band overscroll, and snap points. Powered by `@gorhom/bottom-sheet`.
629
+
630
+ **Required setup** — add to your app root (see Setup section above):
631
+ ```tsx
632
+ import { GestureHandlerRootView } from 'react-native-gesture-handler'
633
+ import { BottomSheetModalProvider } from '@retray-dev/ui-kit'
634
+
635
+ <GestureHandlerRootView style={{ flex: 1 }}>
636
+ <BottomSheetModalProvider>
637
+ {/* rest of app */}
638
+ </BottomSheetModalProvider>
639
+ </GestureHandlerRootView>
640
+ ```
641
+
642
+ **Peer dependencies** (install in your app):
643
+ ```bash
644
+ pnpm add @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler react-native-worklets
645
+ ```
646
+ Add `react-native-worklets/plugin` (not `react-native-reanimated/plugin`) to your `babel.config.js` plugins.
647
+
648
+ | Prop | Type | Default | Notes |
649
+ |------|------|---------|-------|
650
+ | open | `boolean` | required | `true` presents the sheet, `false` dismisses it |
651
+ | onClose | `() => void` | required | Called on swipe-dismiss or backdrop press |
652
+ | snapPoints | `(string \| number)[]` | `['50%']` | Snap positions, e.g. `['40%', '80%']` |
653
+ | title | `string` | — | — |
654
+ | description | `string` | — | — |
655
+ | children | `ReactNode` | — | — |
656
+ | style | `ViewStyle` | — | Applied to the inner content container |
657
+
658
+ **Example:**
659
+ ```tsx
660
+ <Sheet open={open} onClose={() => setOpen(false)} title="Filters" snapPoints={['50%']}>
661
+ <RadioGroup options={sortOptions} value={sort} onValueChange={setSort} />
662
+ </Sheet>
663
+ ```
664
+
665
+ ---
666
+
667
+ ### Toast / useToast
668
+
669
+ **Import:** `import { ToastProvider, useToast } from '@retray-dev/ui-kit'`
670
+ **When to use:** Ephemeral feedback messages (save success, network error, copy confirmation).
671
+
672
+ **Required setup** — `ToastProvider` must wrap your app inside `SafeAreaProvider` (see Setup section above).
673
+
674
+ **Peer dependency:** `react-native-safe-area-context` — required for `useSafeAreaInsets` inside `ToastProvider`.
675
+
676
+ ```tsx
677
+ import { useToast } from '@retray-dev/ui-kit'
678
+
679
+ function MyComponent() {
680
+ const { toast, dismiss } = useToast()
681
+
682
+ return (
683
+ <Button
684
+ label="Save"
685
+ onPress={async () => {
686
+ await save()
687
+ toast({ title: 'Saved', description: 'Your changes were saved.', variant: 'success' })
688
+ }}
689
+ />
690
+ )
691
+ }
692
+ ```
693
+
694
+ **`toast()` options:**
695
+
696
+ | Field | Type | Default | Notes |
697
+ |-------|------|---------|-------|
698
+ | title | `string` | — | Bold heading |
699
+ | description | `string` | — | Detail text |
700
+ | variant | `'default' \| 'destructive' \| 'success'` | `'default'` | `default`: dark background. `destructive`: red. `success`: green |
701
+ | duration | `number` (ms) | `3000` | Auto-dismiss after this delay |
702
+
703
+ **`dismiss(id)`:** Dismiss a toast programmatically. The `id` is returned by the `toast()` call — store it if you need programmatic dismissal.
704
+
705
+ **Notes:**
706
+ - Max 3 toasts shown simultaneously (oldest is removed when a 4th arrives)
707
+ - Toasts appear at the top of the screen, below the status bar (dynamic safe area inset)
708
+ - Entrance: `withTiming(120ms, Easing.out(Easing.exp))` slide-down + opacity fade — fast, sharp feel
709
+ - Exit: `withTiming(200ms)` slide-up + opacity fade
710
+ - Swipe left or right to dismiss early (threshold: 80px or 800 pt/s velocity)