@compsych/mobile-ui 1.0.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/README.md ADDED
@@ -0,0 +1,1015 @@
1
+ # ComPsych Design System — React Native
2
+
3
+ A React Native component library built directly from the ComPsych Figma Design System, using the four-tier token architecture. All 17 components are pixel-accurate implementations synchronized with Figma.
4
+
5
+ ---
6
+
7
+ ## Getting Started
8
+
9
+ ```bash
10
+ npm install
11
+ npx expo start
12
+ ```
13
+
14
+ ---
15
+
16
+ ## Token System
17
+
18
+ All components consume `sys.*` tokens exclusively — never raw values.
19
+
20
+ ```ts
21
+ import { sys } from '@/components/ds/tokens';
22
+ const { colorRoles: cr, dimensions: dim, typeScale: ts } = sys;
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Components
28
+
29
+ ---
30
+
31
+ ### Alert
32
+
33
+ `Version: 1.0.0`
34
+
35
+ Inline feedback banner for status, warnings, errors, and success messages. Supports a title, icon, action button, and dismiss control.
36
+
37
+ ![Alert](docs/images/alert.png)
38
+
39
+ **Import**
40
+ ```tsx
41
+ import { Alert } from '@/components/ds/Alert';
42
+ ```
43
+ **Component File:** `components/ds/Alert.tsx`
44
+
45
+ #### Variants
46
+
47
+ There are six alert variants covering the full range of feedback contexts.
48
+
49
+ ```tsx
50
+ <Alert variant="informative" description="Tips, help, or guidance." />
51
+ <Alert variant="warning" description="Your session will expire in 5 minutes." />
52
+ <Alert variant="danger" description="Failed to save changes." />
53
+ <Alert variant="positive" description="Changes saved successfully." />
54
+ ```
55
+
56
+ #### With Action
57
+
58
+ An optional action button prompts the user to take a next step.
59
+
60
+ ```tsx
61
+ <Alert
62
+ variant="informative"
63
+ title="New feature available"
64
+ description="Update the app to access the latest tools."
65
+ actionLabel="Update now"
66
+ onAction={handleUpdate}
67
+ />
68
+ ```
69
+
70
+ #### Dismissible
71
+
72
+ Add a dismiss button by setting `dismissible` to `true`.
73
+
74
+ ```tsx
75
+ <Alert variant="danger" description="Failed to save." dismissible onDismiss={() => setVisible(false)} />
76
+ ```
77
+
78
+ **Props**
79
+
80
+ | Name | Type | Default | Description |
81
+ |------|------|---------|-------------|
82
+ | `variant` | `'default' \| 'elevated' \| 'informative' \| 'warning' \| 'positive' \| 'danger'` | `'default'` | Color scheme and icon |
83
+ | `size` | `'sm' \| 'lg'` | `'lg'` | `lg` shows a bold title; `sm` is compact |
84
+ | `description` | `string` | — | **Required.** Body text |
85
+ | `title` | `string` | — | Bold heading (`lg` size only) |
86
+ | `hideIcon` | `boolean` | `false` | Removes the leading icon |
87
+ | `actionLabel` | `string` | — | Label for the optional action button |
88
+ | `onAction` | `() => void` | — | Action button handler |
89
+ | `dismissible` | `boolean` | `false` | Shows a × dismiss button |
90
+ | `onDismiss` | `() => void` | — | Dismiss button handler |
91
+
92
+ ---
93
+
94
+ ### Avatar
95
+
96
+ `Version: 1.0.0`
97
+
98
+ Circular user representation — text initials, photo, or icon. Supports a presence badge and activity ring.
99
+
100
+ ![Avatar](docs/images/avatar.png)
101
+
102
+ **Import**
103
+ ```tsx
104
+ import { Avatar } from '@/components/ds/Avatar';
105
+ ```
106
+ **Component File:** `components/ds/Avatar.tsx`
107
+
108
+ #### Sizes
109
+
110
+ Avatar can be rendered in seven sizes from extra-small to triple-extra-large.
111
+
112
+ ```tsx
113
+ <Avatar variant="text" initials="CP" size="xs" />
114
+ <Avatar variant="text" initials="CP" size="md" />
115
+ <Avatar variant="text" initials="CP" size="xl" />
116
+ ```
117
+
118
+ #### Image Avatar
119
+
120
+ Display a user's photo by providing an image source.
121
+
122
+ ```tsx
123
+ <Avatar variant="image" source={{ uri: 'https://example.com/photo.jpg' }} size="lg" activityRing />
124
+ ```
125
+
126
+ #### Letter Avatar
127
+
128
+ Render up to two initials when no photo is available.
129
+
130
+ ```tsx
131
+ <Avatar variant="text" initials="JD" size="md" />
132
+ <Avatar variant="text" initials="AB" size="lg" />
133
+ ```
134
+
135
+ **Props**
136
+
137
+ | Name | Type | Default | Description |
138
+ |------|------|---------|-------------|
139
+ | `variant` | `'text' \| 'image' \| 'icon'` | `'text'` | Content type |
140
+ | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl' \| '3xl'` | `'md'` | Circle diameter |
141
+ | `initials` | `string` | — | Up to 2 characters (text variant) |
142
+ | `source` | `ImageSourcePropType` | — | Photo source (image variant) |
143
+ | `activityRing` | `boolean` | `false` | Renders a coloured ring around the circle |
144
+ | `presenceBadge` | `boolean` | `false` | Renders a shield-check badge at bottom-right |
145
+
146
+ ---
147
+
148
+ ### Badge
149
+
150
+ `Version: 1.0.0`
151
+
152
+ Small status indicator as a pill, dot, or count. Used on icons and list items to show notification states.
153
+
154
+ ![Badge](docs/images/badge.png)
155
+
156
+ **Import**
157
+ ```tsx
158
+ import { Badge } from '@/components/ds/Badge';
159
+ ```
160
+ **Component File:** `components/ds/Badge.tsx`
161
+
162
+ #### Styles
163
+
164
+ Badge supports six visual styles covering neutral, semantic, and dot presentations.
165
+
166
+ ```tsx
167
+ <Badge label={5} badgeStyle="filled" />
168
+ <Badge label="New" badgeStyle="positive" />
169
+ <Badge label="99+" badgeStyle="danger" />
170
+ <Badge label={3} badgeStyle="tonal" />
171
+ <Badge badgeStyle="dot" />
172
+ ```
173
+
174
+ #### Sizes
175
+
176
+ Badge can be rendered in three sizes.
177
+
178
+ ```tsx
179
+ <Badge label={5} size="sm" />
180
+ <Badge label={5} size="md" />
181
+ <Badge label={5} size="lg" />
182
+ ```
183
+
184
+ **Props**
185
+
186
+ | Name | Type | Default | Description |
187
+ |------|------|---------|-------------|
188
+ | `label` | `number \| string` | — | Text or count inside the badge |
189
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Controls diameter and font size |
190
+ | `badgeStyle` | `'filled' \| 'positive' \| 'danger' \| 'elevated' \| 'tonal' \| 'dot'` | `'filled'` | Color scheme and shape |
191
+
192
+ ---
193
+
194
+ ### Breadcrumb
195
+
196
+ `Version: 1.0.0`
197
+
198
+ Horizontal scrollable navigation trail with `/` dividers. The last item always renders as the current page.
199
+
200
+ ![Breadcrumb](docs/images/breadcrumb.png)
201
+
202
+ **Import**
203
+ ```tsx
204
+ import { Breadcrumb } from '@/components/ds/Breadcrumb';
205
+ ```
206
+ **Component File:** `components/ds/Breadcrumb.tsx`
207
+
208
+ #### Basic
209
+
210
+ Provide an ordered array of items. The last item is always treated as the current page.
211
+
212
+ ```tsx
213
+ <Breadcrumb
214
+ items={[
215
+ { isHome: true, onPress: goHome },
216
+ { label: 'Settings', onPress: goSettings },
217
+ { label: 'Profile' },
218
+ ]}
219
+ />
220
+ ```
221
+
222
+ #### With Overflow
223
+
224
+ Use `isOverflow: true` to collapse middle crumbs into an ellipsis.
225
+
226
+ ```tsx
227
+ <Breadcrumb
228
+ items={[
229
+ { isHome: true, onPress: goHome },
230
+ { isOverflow: true, onPress: expandCrumbs },
231
+ { label: 'Current Page' },
232
+ ]}
233
+ />
234
+ ```
235
+
236
+ **Props**
237
+
238
+ | Name | Type | Default | Description |
239
+ |------|------|---------|-------------|
240
+ | `items` | `BreadcrumbItem[]` | — | **Required.** Ordered crumb items |
241
+ | `size` | `'sm' \| 'lg'` | `'lg'` | Font size and vertical padding |
242
+
243
+ **BreadcrumbItem**
244
+
245
+ | Field | Type | Description |
246
+ |-------|------|-------------|
247
+ | `label` | `string` | Display text |
248
+ | `isHome` | `boolean` | Renders the home icon instead of text |
249
+ | `isOverflow` | `boolean` | Renders `…` for collapsed middle crumbs |
250
+ | `disabled` | `boolean` | Reduces opacity; non-interactive |
251
+ | `onPress` | `() => void` | Omit for the current/last crumb |
252
+
253
+ ---
254
+
255
+ ### Button
256
+
257
+ `Version: 1.0.0`
258
+
259
+ Buttons are touchable elements used to trigger actions. They support seven visual variants, four sizes, leading and trailing icons, a loading state, and full-width layout.
260
+
261
+ ![Button](docs/images/button.png)
262
+
263
+ **Import**
264
+ ```tsx
265
+ import { Button } from '@/components/ds/Button';
266
+ ```
267
+ **Component File:** `components/ds/Button.tsx`
268
+
269
+ #### Variants
270
+
271
+ There are solid, outline, text, tonal, elevated, danger, and danger-outlined button types.
272
+
273
+ ```tsx
274
+ <Button label="Filled" variant="filled" onPress={handlePress} />
275
+ <Button label="Tonal" variant="tonal" onPress={handlePress} />
276
+ <Button label="Outlined" variant="outlined" onPress={handlePress} />
277
+ <Button label="Text" variant="text" onPress={handlePress} />
278
+ <Button label="Danger" variant="danger" onPress={handlePress} />
279
+ ```
280
+
281
+ #### Size
282
+
283
+ Button can be small, medium, large, or extra-large.
284
+
285
+ ```tsx
286
+ <Button label="Small" size="sm" onPress={handlePress} />
287
+ <Button label="Medium" size="md" onPress={handlePress} />
288
+ <Button label="Large" size="lg" onPress={handlePress} />
289
+ ```
290
+
291
+ #### Disabled
292
+
293
+ Disabled buttons reduce opacity and ignore press interactions.
294
+
295
+ ```tsx
296
+ <Button label="Filled" variant="filled" disabled />
297
+ <Button label="Outlined" variant="outlined" disabled />
298
+ <Button label="Text" variant="text" disabled />
299
+ ```
300
+
301
+ #### Icon Button
302
+
303
+ Can contain a leading or trailing icon, or be rendered as a square icon-only button.
304
+
305
+ ```tsx
306
+ <Button label="Delete" variant="danger" leadingIcon={<Ionicons name="trash-outline" size={16} />} onPress={handleDelete} />
307
+ ```
308
+
309
+ #### Loading Spinner
310
+
311
+ Use the `loading` prop to replace the label with an activity indicator.
312
+
313
+ ```tsx
314
+ <Button label="Saving…" loading />
315
+ <Button label="Saving…" loading variant="outlined" />
316
+ ```
317
+
318
+ **Props**
319
+
320
+ | Name | Type | Default | Description |
321
+ |------|------|---------|-------------|
322
+ | `label` | `string` | — | **Required.** Button text |
323
+ | `variant` | `'filled' \| 'tonal' \| 'outlined' \| 'elevated' \| 'text' \| 'danger' \| 'danger-outlined'` | `'filled'` | Visual style |
324
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Height and padding |
325
+ | `disabled` | `boolean` | `false` | Disables interaction and reduces opacity |
326
+ | `loading` | `boolean` | `false` | Shows `ActivityIndicator` in place of label |
327
+ | `fullWidth` | `boolean` | `false` | Stretches to fill the parent |
328
+ | `iconOnly` | `boolean` | `false` | Square layout for single icons |
329
+ | `leadingIcon` | `React.ReactNode` | — | Icon before the label |
330
+ | `trailingIcon` | `React.ReactNode` | — | Icon after the label |
331
+ | `onPress` | `() => void` | — | Press handler |
332
+
333
+ ---
334
+
335
+ ### Card
336
+
337
+ `Version: 1.0.0`
338
+
339
+ Surface container for grouping related content. Supports three visual variants, four padding sizes, interactive press states, and a current/selected highlight.
340
+
341
+ **Import**
342
+ ```tsx
343
+ import { Card } from '@/components/ds/Card';
344
+ ```
345
+ **Component File:** `components/ds/Card.tsx`
346
+
347
+ #### Variants
348
+
349
+ There are outlined, filled, and gradient card types.
350
+
351
+ ```tsx
352
+ <Card variant="outlined"><Text>Outlined card</Text></Card>
353
+ <Card variant="filled"><Text>Filled card</Text></Card>
354
+ <Card variant="gradient"><Text>Gradient card</Text></Card>
355
+ ```
356
+
357
+ #### Interactive
358
+
359
+ Set `interactive` to enable press feedback and wrap the card in a `Pressable`.
360
+
361
+ ```tsx
362
+ <Card interactive onPress={handleCardPress}><Text>Tap me</Text></Card>
363
+ ```
364
+
365
+ #### Current State
366
+
367
+ The `current` prop applies a primary-colored border to indicate the selected card.
368
+
369
+ ```tsx
370
+ <Card interactive current onPress={handlePress}><Text>Selected card</Text></Card>
371
+ ```
372
+
373
+ **Props**
374
+
375
+ | Name | Type | Default | Description |
376
+ |------|------|---------|-------------|
377
+ | `variant` | `'outlined' \| 'filled' \| 'gradient'` | `'outlined'` | Visual treatment |
378
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Inner padding |
379
+ | `interactive` | `boolean` | `false` | Enables press feedback |
380
+ | `disabled` | `boolean` | `false` | Reduces opacity |
381
+ | `current` | `boolean` | `false` | Primary border (selected state) |
382
+ | `fullWidth` | `boolean` | `false` | Fills parent width |
383
+ | `onPress` | `() => void` | — | Press handler (requires `interactive`) |
384
+ | `children` | `React.ReactNode` | — | Card content |
385
+
386
+ ---
387
+
388
+ ### Checkbox
389
+
390
+ `Version: 1.0.0`
391
+
392
+ Binary or indeterminate selection control. Supports controlled and uncontrolled usage.
393
+
394
+ ![Checkbox](docs/images/checkbox.png)
395
+
396
+ **Import**
397
+ ```tsx
398
+ import { Checkbox } from '@/components/ds/Checkbox';
399
+ ```
400
+ **Component File:** `components/ds/Checkbox.tsx`
401
+
402
+ #### States
403
+
404
+ Checkbox can be unchecked, checked, indeterminate, or disabled.
405
+
406
+ ```tsx
407
+ <Checkbox defaultChecked={false} onValueChange={console.log} />
408
+ <Checkbox defaultChecked={true} onValueChange={console.log} />
409
+ <Checkbox checked="indeterminate" onValueChange={handleChange} />
410
+ <Checkbox disabled />
411
+ ```
412
+
413
+ #### Controlled
414
+
415
+ Use the `checked` prop with `onValueChange` for controlled usage.
416
+
417
+ ```tsx
418
+ const [checked, setChecked] = useState(false);
419
+ <Checkbox checked={checked} onValueChange={setChecked} />
420
+ ```
421
+
422
+ #### Sizes
423
+
424
+ Checkbox supports medium (default) and small sizes.
425
+
426
+ ```tsx
427
+ <Checkbox size="md" defaultChecked />
428
+ <Checkbox size="sm" defaultChecked />
429
+ ```
430
+
431
+ **Props**
432
+
433
+ | Name | Type | Default | Description |
434
+ |------|------|---------|-------------|
435
+ | `checked` | `boolean \| 'indeterminate'` | — | Controlled state |
436
+ | `defaultChecked` | `boolean` | `false` | Initial uncontrolled state |
437
+ | `onValueChange` | `(value: boolean \| 'indeterminate') => void` | — | Change handler |
438
+ | `size` | `'sm' \| 'md'` | `'md'` | Hit area and box size |
439
+ | `disabled` | `boolean` | `false` | Disables interaction |
440
+ | `accessibilityLabel` | `string` | — | Screen-reader label |
441
+
442
+ ---
443
+
444
+ ### Chip
445
+
446
+ `Version: 1.0.0`
447
+
448
+ Compact label pill for status, category, or filter display. Supports a leading icon, dismiss button, and badge count.
449
+
450
+ ![Chip](docs/images/chip.png)
451
+
452
+ **Import**
453
+ ```tsx
454
+ import { Chip } from '@/components/ds/Chip';
455
+ ```
456
+ **Component File:** `components/ds/Chip.tsx`
457
+
458
+ #### Colors
459
+
460
+ Chip supports neutral, informative, positive, danger, and warning semantic colors.
461
+
462
+ ```tsx
463
+ <Chip label="Neutral" usage="neutral" />
464
+ <Chip label="Info" usage="informative" />
465
+ <Chip label="Completed" usage="positive" />
466
+ <Chip label="Overdue" usage="danger" />
467
+ <Chip label="Pending" usage="warning" />
468
+ ```
469
+
470
+ #### Sizes
471
+
472
+ Chip can be rendered in four sizes.
473
+
474
+ ```tsx
475
+ <Chip label="Small" size="sm" />
476
+ <Chip label="Medium" size="md" />
477
+ <Chip label="Large" size="lg" />
478
+ ```
479
+
480
+ #### Dismissible
481
+
482
+ Add a dismiss button at the trailing end using the `dismissible` prop.
483
+
484
+ ```tsx
485
+ <Chip label="Filter" dismissible onDismiss={handleDismiss} />
486
+ ```
487
+
488
+ **Props**
489
+
490
+ | Name | Type | Default | Description |
491
+ |------|------|---------|-------------|
492
+ | `label` | `string` | — | **Required.** Chip text |
493
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Height and font |
494
+ | `usage` | `'neutral' \| 'informative' \| 'positive' \| 'danger' \| 'warning'` | `'neutral'` | Semantic color |
495
+ | `leadingIcon` | `React.ReactNode` | — | Icon before the label |
496
+ | `dismissible` | `boolean` | `false` | Shows a × button |
497
+ | `onDismiss` | `() => void` | — | Called when dismissed |
498
+ | `badge` | `number \| string` | — | Trailing count or label |
499
+
500
+ ---
501
+
502
+ ### Divider
503
+
504
+ `Version: 1.0.0`
505
+
506
+ Decorative separator — horizontal or vertical, thin or thick, solid or dashed.
507
+
508
+ ![Divider](docs/images/divider.png)
509
+
510
+ **Import**
511
+ ```tsx
512
+ import { Divider } from '@/components/ds/Divider';
513
+ ```
514
+ **Component File:** `components/ds/Divider.tsx`
515
+
516
+ #### Horizontal
517
+
518
+ A horizontal divider stretches to fill its parent width and is the default orientation.
519
+
520
+ ```tsx
521
+ <Divider />
522
+ <Divider weight="thick" />
523
+ ```
524
+
525
+ #### Vertical
526
+
527
+ A vertical divider fills its parent height and is used as a separator inside a row layout.
528
+
529
+ ```tsx
530
+ <View style={{ flexDirection: 'row', height: 40 }}>
531
+ <Text>Left</Text>
532
+ <Divider variant="vertical" />
533
+ <Text>Right</Text>
534
+ </View>
535
+ ```
536
+
537
+ #### Dashed
538
+
539
+ The dashed style is used for section breaks or to indicate optional content areas.
540
+
541
+ ```tsx
542
+ <Divider dashed />
543
+ <Divider weight="thick" dashed />
544
+ ```
545
+
546
+ **Props**
547
+
548
+ | Name | Type | Default | Description |
549
+ |------|------|---------|-------------|
550
+ | `variant` | `'horizontal' \| 'vertical'` | `'horizontal'` | Orientation |
551
+ | `weight` | `'thin' \| 'thick'` | `'thin'` | 1px or 2px line |
552
+ | `dashed` | `boolean` | `false` | Dashed line pattern |
553
+
554
+ ---
555
+
556
+ ### EmptyState
557
+
558
+ `Version: 1.0.0`
559
+
560
+ Full-panel placeholder for empty screens. Supports an icon circle or illustrated card-collage graphic with an optional action button.
561
+
562
+ ![EmptyState](docs/images/emptystate.png)
563
+
564
+ **Import**
565
+ ```tsx
566
+ import { EmptyState } from '@/components/ds/EmptyState';
567
+ ```
568
+ **Component File:** `components/ds/EmptyState.tsx`
569
+
570
+ #### Icon Style
571
+
572
+ The default style renders an icon inside a circular container above the title and description.
573
+
574
+ ```tsx
575
+ <EmptyState
576
+ title="No messages"
577
+ description="When you receive messages they will appear here."
578
+ />
579
+ ```
580
+
581
+ #### Illustration Style
582
+
583
+ The `illustration` style renders a pure-View card collage — no image assets required.
584
+
585
+ ```tsx
586
+ <EmptyState style="illustration" title="Nothing here yet" description="Get started by creating your first item." />
587
+ ```
588
+
589
+ #### With Action
590
+
591
+ An optional action button prompts the user to take a next step.
592
+
593
+ ```tsx
594
+ <EmptyState title="No results" description="Try adjusting your filters." actionLabel="Clear filters" onAction={clearFilters} />
595
+ ```
596
+
597
+ **Props**
598
+
599
+ | Name | Type | Default | Description |
600
+ |------|------|---------|-------------|
601
+ | `style` | `'icon' \| 'illustration'` | `'icon'` | Graphic treatment |
602
+ | `viewport` | `'desktop' \| 'mobile'` | `'mobile'` | Token set for type scale and sizing |
603
+ | `title` | `string` | `'No results'` | Heading text |
604
+ | `description` | `string` | `'Description if needed'` | Body copy |
605
+ | `showDescription` | `boolean` | `true` | Hides description when `false` |
606
+ | `icon` | `React.ReactNode` | — | Custom icon in the circle |
607
+ | `actionLabel` | `string` | — | Action button label |
608
+ | `onAction` | `() => void` | — | Action press handler |
609
+
610
+ ---
611
+
612
+ ### Input
613
+
614
+ `Version: 1.0.0`
615
+
616
+ Single-line text field with floating label, helper/error text, and leading/trailing icon slots.
617
+
618
+ ![Input](docs/images/input.png)
619
+
620
+ **Import**
621
+ ```tsx
622
+ import { Input } from '@/components/ds/Input';
623
+ ```
624
+ **Component File:** `components/ds/Input.tsx`
625
+
626
+ #### Basic
627
+
628
+ A simple text field with a floating label and placeholder.
629
+
630
+ ```tsx
631
+ <Input label="Email" placeholder="you@example.com" />
632
+ ```
633
+
634
+ #### Sizes
635
+
636
+ Input supports three sizes that control height, padding, and font scale.
637
+
638
+ ```tsx
639
+ <Input size="sm" label="Small" placeholder="Small field" />
640
+ <Input size="md" label="Medium" placeholder="Medium field" />
641
+ <Input size="lg" label="Large" placeholder="Large field" />
642
+ ```
643
+
644
+ #### Error State
645
+
646
+ Use `invalid` and `errorText` to communicate a validation error.
647
+
648
+ ```tsx
649
+ <Input label="Username" invalid={!!errors.username} errorText={errors.username} />
650
+ ```
651
+
652
+ #### With Icons
653
+
654
+ Leading and trailing icons can be placed inside the field.
655
+
656
+ ```tsx
657
+ <Input label="Search" leadingIcon={<Ionicons name="search" size={20} />} />
658
+ ```
659
+
660
+ **Props**
661
+
662
+ | Name | Type | Default | Description |
663
+ |------|------|---------|-------------|
664
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Height, padding, and font |
665
+ | `label` | `string` | — | Floating label |
666
+ | `helperText` | `string` | — | Hint text below the field |
667
+ | `errorText` | `string` | — | Error message; replaces `helperText` |
668
+ | `invalid` | `boolean` | `false` | Red border and error color |
669
+ | `leadingIcon` | `React.ReactNode` | — | Icon at the leading edge |
670
+ | `trailingIcon` | `React.ReactNode` | — | Icon at the trailing edge |
671
+
672
+ ---
673
+
674
+ ### Pagination
675
+
676
+ `Version: 1.0.0`
677
+
678
+ Page navigation with smart truncation. Supports numbered layout or compact prev/next-only mode.
679
+
680
+ ![Pagination](docs/images/pagination.png)
681
+
682
+ **Import**
683
+ ```tsx
684
+ import { Pagination } from '@/components/ds/Pagination';
685
+ ```
686
+ **Component File:** `components/ds/Pagination.tsx`
687
+
688
+ #### Standard
689
+
690
+ The default layout renders numbered page buttons with prev and next arrows inside a pill container.
691
+
692
+ ```tsx
693
+ const [page, setPage] = useState(1);
694
+ <Pagination totalPages={12} currentPage={page} onPageChange={setPage} />
695
+ ```
696
+
697
+ #### Compact
698
+
699
+ Compact mode renders only prev/next arrows — ideal for narrow mobile screens.
700
+
701
+ ```tsx
702
+ <Pagination compact totalPages={20} currentPage={page} onPageChange={setPage} />
703
+ ```
704
+
705
+ **Props**
706
+
707
+ | Name | Type | Default | Description |
708
+ |------|------|---------|-------------|
709
+ | `totalPages` | `number` | — | **Required.** Total pages |
710
+ | `currentPage` | `number` | — | **Required.** Active page (1-based) |
711
+ | `onPageChange` | `(page: number) => void` | — | **Required.** Page change handler |
712
+ | `size` | `'sm' \| 'lg'` | `'lg'` | `lg` inside a pill; `sm` bare |
713
+ | `siblingCount` | `number` | `1` | Pages shown on each side before `…` |
714
+ | `compact` | `boolean` | `false` | Prev/next only — no page numbers |
715
+
716
+ ---
717
+
718
+ ### ProgressTracker
719
+
720
+ `Version: 1.0.0`
721
+
722
+ Multi-step progress indicator with labelled steps and per-step progress bars.
723
+
724
+ ![ProgressTracker](docs/images/progress.png)
725
+
726
+ **Import**
727
+ ```tsx
728
+ import { ProgressTracker } from '@/components/ds/ProgressTracker';
729
+ ```
730
+ **Component File:** `components/ds/ProgressTracker.tsx`
731
+
732
+ #### Basic
733
+
734
+ Provide an array of steps with a `label` and `state` for each.
735
+
736
+ ```tsx
737
+ <ProgressTracker
738
+ steps={[
739
+ { label: 'Personal info', state: 'completed' },
740
+ { label: 'Address', state: 'completed' },
741
+ { label: 'Review', state: 'active' },
742
+ { label: 'Submit', state: 'pending' },
743
+ ]}
744
+ />
745
+ ```
746
+
747
+ #### Without Labels
748
+
749
+ Set `showLabels` to `false` for a compact bar-only indicator.
750
+
751
+ ```tsx
752
+ <ProgressTracker showLabels={false} steps={steps} />
753
+ ```
754
+
755
+ **Props**
756
+
757
+ | Name | Type | Default | Description |
758
+ |------|------|---------|-------------|
759
+ | `steps` | `TrackerStep[]` | — | **Required.** Step descriptors |
760
+ | `size` | `'sm' \| 'lg'` | `'lg'` | Label font size |
761
+ | `showLabels` | `boolean` | `true` | Show step labels below bars |
762
+
763
+ **TrackerStep**
764
+
765
+ | Field | Type | Description |
766
+ |-------|------|-------------|
767
+ | `label` | `string` | Step display name |
768
+ | `state` | `'completed' \| 'active' \| 'pending'` | 100% / 25% / 0% fill |
769
+
770
+ ---
771
+
772
+ ### RadioButton
773
+
774
+ `Version: 1.0.0`
775
+
776
+ Single-selection control. Use within a group where only one option can be selected at a time.
777
+
778
+ ![RadioButton](docs/images/radio.png)
779
+
780
+ **Import**
781
+ ```tsx
782
+ import { RadioButton } from '@/components/ds/RadioButton';
783
+ ```
784
+ **Component File:** `components/ds/RadioButton.tsx`
785
+
786
+ #### States
787
+
788
+ RadioButton can be unselected, selected, or disabled.
789
+
790
+ ```tsx
791
+ <RadioButton defaultSelected={false} />
792
+ <RadioButton defaultSelected={true} />
793
+ <RadioButton disabled />
794
+ ```
795
+
796
+ #### In a Group
797
+
798
+ Manage selection state externally and pass `selected` and `onValueChange` to each option.
799
+
800
+ ```tsx
801
+ const [selected, setSelected] = useState<string>('a');
802
+
803
+ <RadioButton selected={selected === 'a'} onValueChange={() => setSelected('a')} accessibilityLabel="Option A" />
804
+ <RadioButton selected={selected === 'b'} onValueChange={() => setSelected('b')} accessibilityLabel="Option B" />
805
+ <RadioButton selected={selected === 'c'} onValueChange={() => setSelected('c')} disabled />
806
+ ```
807
+
808
+ **Props**
809
+
810
+ | Name | Type | Default | Description |
811
+ |------|------|---------|-------------|
812
+ | `selected` | `boolean` | — | Controlled state |
813
+ | `defaultSelected` | `boolean` | `false` | Initial uncontrolled state |
814
+ | `onValueChange` | `(value: boolean) => void` | — | Change handler |
815
+ | `size` | `'sm' \| 'md'` | `'md'` | Hit area and ring size |
816
+ | `disabled` | `boolean` | `false` | Disables interaction |
817
+ | `accessibilityLabel` | `string` | — | Screen-reader label |
818
+
819
+ ---
820
+
821
+ ### Slider
822
+
823
+ `Version: 1.0.0`
824
+
825
+ Continuous value selector with a draggable thumb. Tap anywhere on the track to jump; drag to adjust. Resists gesture theft from parent `ScrollView`.
826
+
827
+ ![Slider](docs/images/slider.png)
828
+
829
+ **Import**
830
+ ```tsx
831
+ import { Slider } from '@/components/ds/Slider';
832
+ ```
833
+ **Component File:** `components/ds/Slider.tsx`
834
+
835
+ #### Basic
836
+
837
+ A simple uncontrolled slider with default `min` (0) and `max` (100).
838
+
839
+ ```tsx
840
+ <Slider onSlidingComplete={(v) => console.log(v)} />
841
+ ```
842
+
843
+ #### With Label
844
+
845
+ Use the `label` prop to render a descriptor above the track.
846
+
847
+ ```tsx
848
+ const [volume, setVolume] = useState(50);
849
+ <Slider label="Volume" value={volume} onValueChange={setVolume} />
850
+ ```
851
+
852
+ #### Step
853
+
854
+ Use the `step` prop to snap the thumb to fixed intervals.
855
+
856
+ ```tsx
857
+ <Slider defaultValue={0} min={0} max={100} step={10} showMinMax />
858
+ ```
859
+
860
+ #### Disabled
861
+
862
+ A disabled slider reduces opacity and ignores interaction.
863
+
864
+ ```tsx
865
+ <Slider value={40} disabled label="Locked setting" />
866
+ ```
867
+
868
+ **Props**
869
+
870
+ | Name | Type | Default | Description |
871
+ |------|------|---------|-------------|
872
+ | `value` | `number` | — | Controlled value |
873
+ | `defaultValue` | `number` | `min` | Initial uncontrolled value |
874
+ | `min` | `number` | `0` | Minimum |
875
+ | `max` | `number` | `100` | Maximum |
876
+ | `step` | `number` | `1` | Snap interval |
877
+ | `onValueChange` | `(value: number) => void` | — | Called while dragging |
878
+ | `onSlidingComplete` | `(value: number) => void` | — | Called on release |
879
+ | `label` | `string` | — | Label above the track |
880
+ | `showMinMax` | `boolean` | `true` | Min/max labels on each side |
881
+ | `disabled` | `boolean` | `false` | Disables interaction |
882
+
883
+ ---
884
+
885
+ ### Switch
886
+
887
+ `Version: 1.0.0`
888
+
889
+ Animated binary toggle with 150ms thumb slide and background color transition. Supports controlled and uncontrolled usage.
890
+
891
+ ![Switch](docs/images/switch.png)
892
+
893
+ **Import**
894
+ ```tsx
895
+ import { Switch } from '@/components/ds/Switch';
896
+ ```
897
+ **Component File:** `components/ds/Switch.tsx`
898
+
899
+ #### On and Off
900
+
901
+ Switch can be toggled between on and off states.
902
+
903
+ ```tsx
904
+ <Switch defaultValue={false} onValueChange={console.log} />
905
+ <Switch defaultValue={true} onValueChange={console.log} />
906
+ ```
907
+
908
+ #### Controlled
909
+
910
+ Use the `value` prop with `onValueChange` for controlled usage.
911
+
912
+ ```tsx
913
+ const [enabled, setEnabled] = useState(false);
914
+ <Switch value={enabled} onValueChange={setEnabled} accessibilityLabel="Notifications" />
915
+ ```
916
+
917
+ #### Disabled
918
+
919
+ Disabled switches reduce opacity and ignore press interactions.
920
+
921
+ ```tsx
922
+ <Switch value={false} disabled />
923
+ <Switch value={true} disabled />
924
+ ```
925
+
926
+ **Props**
927
+
928
+ | Name | Type | Default | Description |
929
+ |------|------|---------|-------------|
930
+ | `value` | `boolean` | — | Controlled value |
931
+ | `defaultValue` | `boolean` | `false` | Initial uncontrolled value |
932
+ | `onValueChange` | `(value: boolean) => void` | — | Toggle handler |
933
+ | `disabled` | `boolean` | `false` | Disables interaction |
934
+ | `accessibilityLabel` | `string` | — | Screen-reader label |
935
+
936
+ ---
937
+
938
+ ### Tooltip
939
+
940
+ `Version: 1.0.0`
941
+
942
+ Short contextual label with a directional arrow. Use to clarify icon buttons, abbreviations, or truncated text.
943
+
944
+ ![Tooltip](docs/images/tooltip.png)
945
+
946
+ **Import**
947
+ ```tsx
948
+ import { Tooltip } from '@/components/ds/Tooltip';
949
+ ```
950
+ **Component File:** `components/ds/Tooltip.tsx`
951
+
952
+ #### Filled
953
+
954
+ The filled variant uses a dark inverse surface — the default and most common style.
955
+
956
+ ```tsx
957
+ <Tooltip text="More options" />
958
+ <Tooltip text="Delete item" direction="bottom" />
959
+ ```
960
+
961
+ #### Elevated
962
+
963
+ The elevated variant uses a white surface with a border and shadow — for use on dark backgrounds.
964
+
965
+ ```tsx
966
+ <Tooltip text="Save your work" variant="elevated" direction="bottom" />
967
+ ```
968
+
969
+ #### Directions
970
+
971
+ The `direction` prop describes where the target is relative to the bubble, which determines where the arrow is drawn.
972
+
973
+ ```tsx
974
+ <Tooltip text="No arrow" direction="none" />
975
+ <Tooltip text="Arrow up" direction="top" />
976
+ <Tooltip text="Arrow down" direction="bottom" />
977
+ <Tooltip text="Arrow left" direction="left" />
978
+ <Tooltip text="Arrow right" direction="right" />
979
+ ```
980
+
981
+ **Props**
982
+
983
+ | Name | Type | Default | Description |
984
+ |------|------|---------|-------------|
985
+ | `text` | `string` | — | **Required.** Tooltip content |
986
+ | `variant` | `'filled' \| 'elevated'` | `'filled'` | Dark inverse or white elevated |
987
+ | `direction` | `'none' \| 'top' \| 'bottom' \| 'left' \| 'right'` | `'none'` | Where the target is relative to the bubble |
988
+
989
+ ---
990
+
991
+ ## Token System
992
+
993
+ All components reference only `sys.*` tokens via:
994
+
995
+ ```ts
996
+ import { sys } from '@/components/ds/tokens';
997
+ const { colorRoles: cr, dimensions: dim, typeScale: ts } = sys;
998
+ ```
999
+
1000
+ Never apply `core.*`, `product.*`, or `brand.*` tokens directly in UI — they are internal plumbing and will not respond to theme or brand changes.
1001
+
1002
+ ---
1003
+
1004
+ ## Project Structure
1005
+
1006
+ ```
1007
+ components/ds/ All 17 DS component implementations
1008
+ components/ds/tokens.ts sys.* token bridge from @javierkonpo/design-system
1009
+ app/(tabs)/index.tsx Component gallery / showcase screen
1010
+ docs/
1011
+ components.md Full component API reference (RNEUI-style)
1012
+ Components.docx Word document with Figma screenshots
1013
+ components.pdf PDF reference with Figma screenshots
1014
+ images/ Figma component screenshots
1015
+ ```