@zvoove/unity-ui 2.20.1 → 2.22.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/dist/llms.txt ADDED
@@ -0,0 +1,1442 @@
1
+ # Unity UI (@zvoove/unity-ui) - AI Component Reference
2
+
3
+ > This file describes all available components in the Unity UI design system.
4
+ > When building UIs, use ONLY these components. Do NOT create custom components for functionality already provided here.
5
+
6
+ ## Setup
7
+
8
+ Install:
9
+ ```bash
10
+ npm install @zvoove/unity-ui
11
+ ```
12
+
13
+ Import styles (required, add to your app entry point):
14
+ ```css
15
+ @import '@zvoove/unity-ui/theme.css';
16
+ @import '@zvoove/unity-ui/unity-ui.css';
17
+ ```
18
+
19
+ Import components:
20
+ ```tsx
21
+ import { Button, Card, TextField, Typography } from '@zvoove/unity-ui';
22
+ ```
23
+
24
+ Font (add to HTML head):
25
+ ```html
26
+ <link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet" />
27
+ ```
28
+
29
+ Dark mode: add `data-theme="dark"` to a parent element.
30
+
31
+ ## Responsive Props
32
+
33
+ Many props accept `ResponsiveType<T>`, meaning either a direct value OR a breakpoint object.
34
+ Breakpoints (from smallest to largest): `minimum` (0px), `mobile` (320px), `tablet` (768px), `laptop` (1024px), `desktop` (1440px).
35
+ Values cascade upward — setting `mobile` applies to all larger breakpoints until overridden.
36
+
37
+ ```tsx
38
+ // Direct value — applies to all screen sizes
39
+ <Button size="md" />
40
+
41
+ // Responsive object — values cascade up from smallest to largest
42
+ <Button size={{ mobile: 'sm', tablet: 'md', desktop: 'lg' }} />
43
+ // mobile: sm → tablet: md → laptop: md (inherited) → desktop: lg
44
+
45
+ // Common patterns:
46
+ <Stack direction={{ mobile: 'column', tablet: 'row' }} /> // Stack vertically on mobile, horizontally on tablet+
47
+ <Grid columns={{ mobile: 1, tablet: 2, desktop: 3 }} /> // Responsive grid columns
48
+ <Card padding={{ mobile: 'sm', tablet: 'md', desktop: 'lg' }} /> // Responsive padding
49
+ <Button hideLabel={{ mobile: true, tablet: false }} icon="Plus" /> // Icon-only on mobile, label on tablet+
50
+ <Dialog size={{ mobile: 'fullscreen', tablet: 'md' }} /> // Fullscreen on mobile, modal on tablet+
51
+ ```
52
+
53
+ ---
54
+
55
+ ## LAYOUT COMPONENTS
56
+
57
+ ### Stack
58
+ **Primary layout component.** Use Stack for ALL layout needs — arranging children in rows or columns with consistent spacing. Do NOT use Card or raw divs for layout.
59
+ ```tsx
60
+ import { Stack } from '@zvoove/unity-ui';
61
+
62
+ <Stack
63
+ direction="column" // ResponsiveType<'row' | 'row-reverse' | 'column' | 'column-reverse'>
64
+ gap="md" // ResponsiveType<SpacingKeys> - none|xs2|xs|sm|md|lg|xl|xl2|xl3|xl4|xl5|xl6|xl7
65
+ align="center" // ResponsiveType<AlignItems> - default: 'baseline'
66
+ justify="space-between" // ResponsiveType<JustifyContent>
67
+ wrap="wrap" // ResponsiveType<FlexWrap>
68
+ padding="md" // ResponsiveType<Padding>
69
+ margin="none" // ResponsiveType<Margin>
70
+ width="100%" // ResponsiveType<number | string>
71
+ height="auto" // ResponsiveType<number | string>
72
+ minWidth, maxWidth, minHeight, maxHeight // ResponsiveType<number | string>
73
+ >
74
+ {children}
75
+ </Stack>
76
+ ```
77
+
78
+ Common patterns:
79
+ ```tsx
80
+ // Page layout — stacks vertically on mobile, horizontally on tablet+
81
+ <Stack direction={{ mobile: 'column', tablet: 'row' }} gap="lg">
82
+ <Sidebar />
83
+ <MainContent />
84
+ </Stack>
85
+
86
+ // Form fields in a row
87
+ <Stack direction="row" gap="md" align="flex-end">
88
+ <TextField label="First name" name="first" />
89
+ <TextField label="Last name" name="last" />
90
+ </Stack>
91
+
92
+ // Action bar — right-aligned buttons
93
+ <Stack direction="row" gap="sm" justify="flex-end">
94
+ <Button variant="outlined">Cancel</Button>
95
+ <Button variant="filled">Save</Button>
96
+ </Stack>
97
+
98
+ // Centered content
99
+ <Stack direction="column" align="center" justify="center" gap="md" height="100%">
100
+ <Icon name="check-circle" size="2xl" />
101
+ <Typography variant="headline" size="medium">Success!</Typography>
102
+ </Stack>
103
+ ```
104
+
105
+ ### Grid
106
+ CSS Grid container with Grid.Item children. Use Grid when you need multi-column layouts. Use Stack for simpler row/column arrangements.
107
+ ```tsx
108
+ import { Grid } from '@zvoove/unity-ui';
109
+
110
+ <Grid
111
+ columns={3} // ResponsiveType<number | string> - default: 1
112
+ rows={1} // ResponsiveType<number | string> - default: 1
113
+ gap="md" // ResponsiveType<SpacingKeys>
114
+ padding="none" // ResponsiveType<Padding>
115
+ margin="none" // ResponsiveType<Margin>
116
+ width="100%" // ResponsiveType<number | string>
117
+ height="auto" // ResponsiveType<number | string>
118
+ >
119
+ <Grid.Item colSpan={2} rowSpan={1}>Content</Grid.Item>
120
+ <Grid.Item colSpan={1}>Content</Grid.Item>
121
+ </Grid>
122
+ ```
123
+
124
+ Common patterns:
125
+ ```tsx
126
+ // Responsive card grid — 1 col on mobile, 2 on tablet, 3 on desktop
127
+ <Grid columns={{ mobile: 1, tablet: 2, desktop: 3 }} gap="lg">
128
+ <Grid.Item><Card>Item 1</Card></Grid.Item>
129
+ <Grid.Item><Card>Item 2</Card></Grid.Item>
130
+ <Grid.Item><Card>Item 3</Card></Grid.Item>
131
+ </Grid>
132
+
133
+ // Form layout — 2 fields side by side on tablet+, stacked on mobile
134
+ <Grid columns={{ mobile: 1, tablet: 2 }} gap="md">
135
+ <Grid.Item><TextField label="First name" name="first" /></Grid.Item>
136
+ <Grid.Item><TextField label="Last name" name="last" /></Grid.Item>
137
+ </Grid>
138
+
139
+ // Wide + narrow column layout
140
+ <Grid columns={3} gap="md">
141
+ <Grid.Item colSpan={2}><Card>Main content</Card></Grid.Item>
142
+ <Grid.Item colSpan={1}><Card>Sidebar</Card></Grid.Item>
143
+ </Grid>
144
+ ```
145
+
146
+ ### Card
147
+ **Visual container** with background, elevation, and border. Use Card to group content visually (e.g., a profile card, a settings panel, a content section). Do NOT use Card for layout — use Stack or Grid instead. Card is a surface, not a layout tool.
148
+ ```tsx
149
+ import { Card } from '@zvoove/unity-ui';
150
+
151
+ <Card
152
+ variant="filled" // 'outlined' | 'filled' (default: 'filled')
153
+ padding="md" // ResponsiveType<Padding> - default: 'md'. Use "none" for edge-to-edge content
154
+ margin="none" // ResponsiveType<Margin>
155
+ elevation={1} // 0 | 1 | 2 | 3 | 4 | 5 (default: 0)
156
+ borderRadius="md" // 'none' | 'sm' | 'md' | 'lg' | 'xl'
157
+ bgColor="surface" // BackgroundColors (default: 'surface')
158
+ overflow="visible" // ResponsiveType<Overflow>
159
+ width, height, minWidth, maxWidth, minHeight, maxHeight // ResponsiveType
160
+ >
161
+ {children}
162
+ </Card>
163
+ ```
164
+
165
+ Common patterns:
166
+ ```tsx
167
+ // Content card with internal layout using Stack (NOT Card for layout)
168
+ <Card variant="outlined" padding="lg" elevation={1}>
169
+ <Stack direction="column" gap="md">
170
+ <Typography variant="title" size="large">Profile</Typography>
171
+ <Typography variant="body" size="medium">User details here.</Typography>
172
+ </Stack>
173
+ </Card>
174
+
175
+ // Card with no padding — useful for full-width content like images or tables
176
+ <Card variant="outlined" padding="none">
177
+ <img src="/banner.jpg" style={{ width: '100%' }} />
178
+ <Stack padding="md" gap="sm">
179
+ <Typography variant="title" size="medium">Article title</Typography>
180
+ <Typography variant="body" size="medium">Article excerpt...</Typography>
181
+ </Stack>
182
+ </Card>
183
+
184
+ // Responsive padding — tighter on mobile, more spacious on desktop
185
+ <Card padding={{ mobile: 'sm', tablet: 'md', desktop: 'xl' }} elevation={2}>
186
+ {children}
187
+ </Card>
188
+
189
+ // WRONG: Do NOT use Card for layout. Use Stack instead.
190
+ // Bad: <Card><TextField /><TextField /></Card>
191
+ // Good: <Stack direction="column" gap="md"><TextField /><TextField /></Stack>
192
+ ```
193
+
194
+ ### Divider
195
+ Horizontal separator line.
196
+ ```tsx
197
+ import { Divider } from '@zvoove/unity-ui';
198
+
199
+ <Divider variant="fullWidth" /> // 'fullWidth' | 'inset' | 'middle'
200
+ ```
201
+
202
+ ### Expandable
203
+ Animated collapsible container.
204
+ ```tsx
205
+ import { Expandable } from '@zvoove/unity-ui';
206
+
207
+ <Expandable isOpen={true} as="div">
208
+ {children}
209
+ </Expandable>
210
+ ```
211
+
212
+ ### ContentBlock
213
+ Simple content wrapper.
214
+ ```tsx
215
+ import { ContentBlock } from '@zvoove/unity-ui';
216
+
217
+ <ContentBlock>{children}</ContentBlock>
218
+ ```
219
+
220
+ ---
221
+
222
+ ## TYPOGRAPHY
223
+
224
+ ### Typography
225
+ Text rendering component with variants. Use Typography for ALL text — never use raw `<p>`, `<h1>`, `<span>` tags.
226
+ ```tsx
227
+ import { Typography } from '@zvoove/unity-ui';
228
+
229
+ <Typography
230
+ variant="body" // 'display' | 'headline' | 'title' | 'body' | 'label'
231
+ size="medium" // 'small' | 'medium' | 'large'
232
+ as="p" // 'p' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
233
+ color="on-surface" // ForegroundColors
234
+ textAlign="left" // 'left' | 'center' | 'right'
235
+ textTransform="uppercase" // 'uppercase' | 'lowercase' | 'capitalize'
236
+ truncate={2} // number - max lines before truncation
237
+ hyphenate={true} // boolean (default: true)
238
+ wrap={true} // boolean
239
+ >
240
+ Text content
241
+ </Typography>
242
+ ```
243
+
244
+ Variant guide:
245
+ ```tsx
246
+ // Page heading
247
+ <Typography variant="display" size="large" as="h1">Page Title</Typography>
248
+
249
+ // Section heading
250
+ <Typography variant="headline" size="medium" as="h2">Section Title</Typography>
251
+
252
+ // Card/dialog title
253
+ <Typography variant="title" size="large">Card Title</Typography>
254
+
255
+ // Body text (default)
256
+ <Typography variant="body" size="medium">Paragraph text goes here.</Typography>
257
+
258
+ // Small labels, captions
259
+ <Typography variant="label" size="small" color="on-surface-variant">Updated 2 hours ago</Typography>
260
+
261
+ // Truncate long text to 2 lines
262
+ <Typography variant="body" size="medium" truncate={2}>Very long text that will be cut off...</Typography>
263
+ ```
264
+
265
+ ---
266
+
267
+ ## FORM COMPONENTS
268
+
269
+ ### TextField
270
+ Text input with label, validation, and icon support.
271
+ ```tsx
272
+ import { TextField } from '@zvoove/unity-ui';
273
+
274
+ <TextField
275
+ label="Email" // string (required)
276
+ name="email" // string
277
+ value={value} // string
278
+ onChange={handleChange} // (event: ChangeEvent<HTMLInputElement>) => void
279
+ placeholder="Enter email" // string
280
+ error={false} // boolean
281
+ errorMessage="Invalid email" // string
282
+ disabled={false} // boolean
283
+ hideLabel={false} // boolean
284
+ clearable={false} // boolean
285
+ icon="MagnifyingGlass" // CommonIconNames
286
+ iconPosition="right" // 'left' | 'right'
287
+ onIconClick={handleClick} // () => void
288
+ density="default" // ResponsiveType<'default' | '-2' | '-4'>
289
+ type="text" // standard HTML input types
290
+ required={false} // boolean
291
+ />
292
+ ```
293
+
294
+ ### Textarea
295
+ Multi-line text input.
296
+ ```tsx
297
+ import { Textarea } from '@zvoove/unity-ui';
298
+
299
+ <Textarea
300
+ value={value} // string
301
+ onChange={handleChange} // ChangeEventHandler<HTMLTextAreaElement>
302
+ error={false} // boolean
303
+ errorMessage="Too long" // string
304
+ maxLength={500} // number
305
+ actions={<Button>Save</Button>} // ReactNode
306
+ rows={4} // number (standard HTML)
307
+ placeholder="Enter text" // string
308
+ disabled={false} // boolean
309
+ />
310
+ ```
311
+
312
+ ### Select
313
+ Dropdown select with search and multi-select support.
314
+ ```tsx
315
+ import { Select } from '@zvoove/unity-ui';
316
+
317
+ <Select
318
+ name="country" // string (required)
319
+ label="Country" // string
320
+ options={[ // SelectOption[] (required)
321
+ { value: 'de', label: 'Germany', icon: 'Flag' },
322
+ { value: 'us', label: 'United States' },
323
+ ]}
324
+ value="de" // string | string[]
325
+ onChange={handleChange} // ChangeEventHandler<HTMLSelectElement>
326
+ multiple={false} // boolean
327
+ searchable={false} // boolean
328
+ error={false} // boolean
329
+ errorMessage="Required" // string
330
+ disabled={false} // boolean
331
+ hideLabel={false} // boolean
332
+ />
333
+ ```
334
+
335
+ Common patterns:
336
+ ```tsx
337
+ // Searchable select — for long option lists
338
+ <Select name="country" label="Country" searchable={true} options={countries} />
339
+
340
+ // Multi-select
341
+ <Select
342
+ name="tags"
343
+ label="Tags"
344
+ multiple={true}
345
+ value={['react', 'typescript']}
346
+ options={[
347
+ { value: 'react', label: 'React' },
348
+ { value: 'typescript', label: 'TypeScript' },
349
+ { value: 'node', label: 'Node.js' },
350
+ ]}
351
+ />
352
+
353
+ // With icons
354
+ <Select
355
+ name="priority"
356
+ label="Priority"
357
+ options={[
358
+ { value: 'high', label: 'High', icon: 'arrow-up' },
359
+ { value: 'medium', label: 'Medium', icon: 'minus' },
360
+ { value: 'low', label: 'Low', icon: 'arrow-down' },
361
+ ]}
362
+ />
363
+ ```
364
+
365
+ ### Checkbox
366
+ Checkbox input with label.
367
+ ```tsx
368
+ import { Checkbox } from '@zvoove/unity-ui';
369
+
370
+ <Checkbox
371
+ label="Accept terms" // ReactNode (required)
372
+ name="terms" // string
373
+ checked={false} // boolean
374
+ onChange={handleChange} // ChangeEventHandler<HTMLInputElement>
375
+ disabled={false} // boolean
376
+ error={false} // boolean
377
+ indeterminate={false} // boolean
378
+ value="accepted" // string
379
+ />
380
+ ```
381
+
382
+ ### Radio (RadioGroup + RadioButton)
383
+ Radio button group for single selection.
384
+ ```tsx
385
+ import { RadioGroup, RadioButton } from '@zvoove/unity-ui';
386
+
387
+ <RadioGroup
388
+ name="size" // string (required)
389
+ value={selectedValue} // string
390
+ onChange={handleChange} // (e: ChangeEvent<HTMLInputElement>) => void
391
+ row={false} // boolean - horizontal layout
392
+ disabled={false} // boolean
393
+ >
394
+ <RadioButton label="Small" value="sm" />
395
+ <RadioButton label="Medium" value="md" />
396
+ <RadioButton label="Large" value="lg" />
397
+ </RadioGroup>
398
+ ```
399
+
400
+ ### Switch
401
+ Toggle switch input.
402
+ ```tsx
403
+ import { Switch } from '@zvoove/unity-ui';
404
+
405
+ <Switch
406
+ checked={false} // boolean
407
+ onChange={handleChange} // ChangeEventHandler<HTMLInputElement>
408
+ label="Enable notifications" // string | { on: string; off: string }
409
+ labelPlacement="left" // 'left' | 'right' (default: 'left')
410
+ justify="space-between" // JustifyContent
411
+ disabled={false} // boolean
412
+ withIcons={false} // boolean | { on: IconName; off: IconName }
413
+ name="notifications" // string
414
+ value="enabled" // string
415
+ />
416
+ ```
417
+
418
+ ### DatePicker
419
+ Date input with calendar popup.
420
+ ```tsx
421
+ import { DatePicker } from '@zvoove/unity-ui';
422
+
423
+ <DatePicker
424
+ label="Start date" // string (required)
425
+ name="startDate" // string (required)
426
+ value="2024-01-15" // string (YYYY-MM-DD)
427
+ onChange={handleChange} // (e: ChangeEvent<HTMLInputElement>, date?: Date) => void
428
+ locale="en-US" // string (default: 'de-DE')
429
+ minDate="2024-01-01" // string (YYYY-MM-DD)
430
+ maxDate="2024-12-31" // string (YYYY-MM-DD)
431
+ openOnFocus={false} // boolean
432
+ closeOnSelect={false} // boolean
433
+ placeholder="Select date" // string
434
+ error={false} // boolean
435
+ errorMessage="Invalid date" // string
436
+ disabled={false} // boolean
437
+ buttonsTranslations={{ cancel: 'Cancel', clear: 'Clear', ok: 'OK' }}
438
+ />
439
+ ```
440
+
441
+ ### Uploader
442
+ File upload component with drag-and-drop.
443
+ ```tsx
444
+ import { Uploader } from '@zvoove/unity-ui';
445
+
446
+ <Uploader
447
+ accept="image/*,.pdf" // string
448
+ multiple={true} // boolean (default: true)
449
+ maxFileSize={5000000} // number (bytes)
450
+ maxTotalSize={20000000} // number (bytes)
451
+ maxCount={10} // number
452
+ value={files} // FileItem[]
453
+ onChange={handleChange} // (event: ChangeEvent) => void
454
+ onValidation={handleError} // (event: ValidationEvent) => void
455
+ disabled={false} // boolean
456
+ canDeleteFile={true} // boolean
457
+ selectionMode="append" // 'append' | 'replace' (default: 'append')
458
+ showFileList={true} // boolean (default: true)
459
+ name="files" // string
460
+ required={false} // boolean
461
+ />
462
+ ```
463
+
464
+ ### FormLabel
465
+ Label for form fields.
466
+ ```tsx
467
+ import { FormLabel } from '@zvoove/unity-ui';
468
+
469
+ <FormLabel value="Email address" htmlFor="email" required={true} />
470
+ ```
471
+
472
+ ---
473
+
474
+ ## ACTION COMPONENTS
475
+
476
+ ### Button
477
+ Primary action component with multiple variants.
478
+ ```tsx
479
+ import { Button } from '@zvoove/unity-ui';
480
+
481
+ <Button
482
+ variant="filled" // ResponsiveType<'filled' | 'outlined' | 'text' | 'textSecondary' | 'elevated' | 'tonal' | 'linkPrimary' | 'linkSecondary' | 'positive' | 'negative'>
483
+ size="lg" // ResponsiveType<'sm' | 'md' | 'lg'> (default: 'lg')
484
+ icon="Plus" // CommonIconNames | Omit<IconProps, 'size' | 'color'>
485
+ hideLabel={false} // ResponsiveType<boolean>
486
+ onClick={handleClick} // (event: MouseEvent) => void
487
+ isLoading={false} // boolean
488
+ disabled={false} // boolean
489
+ as="button" // 'a' | 'button' | 'span' (default: 'button')
490
+ linkComponent={Link} // React.ElementType (for router links)
491
+ >
492
+ Click me
493
+ </Button>
494
+ ```
495
+
496
+ Variant guide:
497
+ ```tsx
498
+ // Primary action → filled
499
+ <Button variant="filled">Save</Button>
500
+
501
+ // Secondary action → outlined
502
+ <Button variant="outlined">Cancel</Button>
503
+
504
+ // Destructive action → negative
505
+ <Button variant="negative" icon="delete">Delete</Button>
506
+
507
+ // Success action → positive
508
+ <Button variant="positive" icon="check">Confirm</Button>
509
+
510
+ // Subtle/tertiary action → text
511
+ <Button variant="text">Learn more</Button>
512
+
513
+ // Link-style button → linkPrimary / linkSecondary
514
+ <Button variant="linkPrimary">View details</Button>
515
+
516
+ // Icon-only button (must have aria-label)
517
+ <Button variant="outlined" icon="edit" aria-label="Edit item" />
518
+
519
+ // Responsive: icon-only on mobile, full label on tablet+
520
+ <Button variant="filled" icon="add" hideLabel={{ mobile: true, tablet: false }}>
521
+ Add item
522
+ </Button>
523
+
524
+ // Loading state
525
+ <Button variant="filled" isLoading={true}>Saving...</Button>
526
+
527
+ // As a router link
528
+ <Button variant="filled" linkComponent={Link} href="/settings">Settings</Button>
529
+ ```
530
+
531
+ ### Segment (SegmentGroup + SegmentButton)
532
+ Segmented control for toggling between options.
533
+ ```tsx
534
+ import { SegmentGroup, SegmentButton } from '@zvoove/unity-ui';
535
+
536
+ <SegmentGroup
537
+ activeButton="list" // V | V[]
538
+ onChange={handleChange} // (value: V | V[]) => void
539
+ stretch={false} // boolean
540
+ multiSelect={false} // boolean
541
+ disabled={false} // boolean
542
+ >
543
+ <SegmentButton value="list" label="List" icon="List" />
544
+ <SegmentButton value="grid" label="Grid" icon="GridFour" />
545
+ </SegmentGroup>
546
+ ```
547
+
548
+ ### PopUpMenu
549
+ Context menu / dropdown menu.
550
+ ```tsx
551
+ import { PopUpMenu } from '@zvoove/unity-ui';
552
+
553
+ <PopUpMenu
554
+ items={[ // PopUpMenuItem[] (required)
555
+ { id: '1', label: 'Edit', icon: 'Pencil' },
556
+ { id: '2', label: 'Delete', icon: 'Trash', variant: 'error' },
557
+ { id: 'divider', isDivider: true },
558
+ { id: '3', label: 'Settings' },
559
+ ]}
560
+ trigger="click" // 'hover' | 'click' | 'right-click' (default: 'click')
561
+ placement="bottom-left" // PopUpMenuPlacement (default: 'bottom-left')
562
+ selectable="none" // 'single' | 'multiple' | 'none' (default: 'none')
563
+ selectedItem={selected} // string | string[]
564
+ onItemClick={handleClick} // (item, selectedItem?) => void
565
+ density="default" // 'default' | '-2' | '-4'
566
+ showSearch={false} // boolean
567
+ searchPlaceholder="Search" // string
568
+ disabled={false} // boolean
569
+ >
570
+ <Button>Actions</Button>
571
+ </PopUpMenu>
572
+ ```
573
+
574
+ ---
575
+
576
+ ## DATA DISPLAY COMPONENTS
577
+
578
+ ### Table
579
+ Data table with sorting, expandable rows, and actions.
580
+ ```tsx
581
+ import { Table } from '@zvoove/unity-ui';
582
+
583
+ const columns = [
584
+ { id: 'name', label: 'Name', orderable: true },
585
+ { id: 'email', label: 'Email' },
586
+ { id: 'role', label: 'Role', align: 'right' as const },
587
+ ] as const;
588
+
589
+ <Table
590
+ columns={columns} // TableColumnProps[] (required)
591
+ data={[ // TableRowData[] (required)
592
+ { id: '1', name: 'John Doe', email: 'john@example.com', role: 'Admin' },
593
+ ]}
594
+ title="Users" // string
595
+ subtitle="All active users" // string
596
+ actions={<Button>Add user</Button>} // ReactNode
597
+ filters={filterComponent} // ReactNode
598
+ loading={false} // boolean
599
+ orderBy={{ column: 'name', direction: 'asc' }}
600
+ onOrderByChange={handleSort} // ({ columnId, direction }) => void
601
+ hiddenColumns={['email']} // ColumnId[]
602
+ expandedRows={['1']} // string[]
603
+ emptyState="No data found" // ReactNode
604
+ footer={footerContent} // ReactNode
605
+ />
606
+ ```
607
+
608
+ ### Tag
609
+ Colored label/badge for categorization.
610
+ ```tsx
611
+ import { Tag } from '@zvoove/unity-ui';
612
+
613
+ <Tag
614
+ label="Active" // string (required)
615
+ variant="solid" // 'solid' | 'outlined' | 'ghost'
616
+ color="green" // 'green' | 'yellow' | 'pink' | 'steel-blue' | 'error' | 'primary' | 'neutral'
617
+ tone="dark" // 'dark' | 'light' (only with variant='solid')
618
+ size="medium" // 'large' | 'medium' | 'small'
619
+ icon="Check" // CommonIconNames
620
+ />
621
+ ```
622
+
623
+ ### Chip
624
+ Interactive chip for filters, selections, and suggestions.
625
+ ```tsx
626
+ import { Chip } from '@zvoove/unity-ui';
627
+
628
+ <Chip
629
+ label="React" // string (required)
630
+ type="filter" // 'input' | 'filter' | 'assistive' | 'suggestion' | 'choice'
631
+ icon="Code" // CommonIconNames
632
+ variant="primary" // 'primary' | 'secondary' | 'pending' | 'confirmed' | 'rejected' | 'skipped'
633
+ elevated={false} // boolean
634
+ readonly={false} // boolean
635
+ disabled={false} // boolean
636
+ size="medium" // 'medium' | 'large'
637
+ onDelete={handleDelete} // () => void
638
+ showDropdownIcon={true} // boolean
639
+ onToggleOpen={handleToggle} // () => void
640
+ />
641
+ ```
642
+
643
+ ### Avatar & AvatarGroup
644
+ User avatar display.
645
+ ```tsx
646
+ import { Avatar, AvatarGroup } from '@zvoove/unity-ui';
647
+
648
+ <Avatar
649
+ size="large" // ResponsiveType<'small' | 'medium' | 'large'>
650
+ type="initials" // 'initials' | 'check' | 'avatar' | 'image'
651
+ name="John Doe" // string
652
+ image="/path/to/photo.jpg" // string (for type='image')
653
+ initialsAmount={2} // 1 | 2
654
+ variant="round" // 'round' | 'square'
655
+ />
656
+
657
+ <AvatarGroup maxLength={3} total={10}>
658
+ <Avatar name="Alice" type="initials" />
659
+ <Avatar name="Bob" type="initials" />
660
+ <Avatar name="Charlie" type="initials" />
661
+ </AvatarGroup>
662
+ ```
663
+
664
+ ### Badge
665
+ Notification badge wrapper.
666
+ ```tsx
667
+ import { Badge } from '@zvoove/unity-ui';
668
+
669
+ <Badge
670
+ variant="primary" // 'primary' | 'secondary'
671
+ content={5} // ReactNode
672
+ dot={false} // boolean
673
+ >
674
+ <Icon name="Bell" />
675
+ </Badge>
676
+ ```
677
+
678
+ ### Icon
679
+ Phosphor icon renderer.
680
+ ```tsx
681
+ import { Icon } from '@zvoove/unity-ui';
682
+
683
+ <Icon
684
+ name="MagnifyingGlass" // IconNames (Phosphor icon names)
685
+ size="lg" // '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' (default: 'lg')
686
+ color="on-surface" // ForegroundColors
687
+ weight="regular" // IconWeight
688
+ featured={false} // boolean
689
+ />
690
+ ```
691
+
692
+ ### Skeleton
693
+ Loading placeholder.
694
+ ```tsx
695
+ import { Skeleton } from '@zvoove/unity-ui';
696
+
697
+ <Skeleton
698
+ width={200} // number | string
699
+ height={20} // number | string
700
+ animation="pulse" // 'pulse' | 'wave' | false (default: 'pulse')
701
+ borderRadius="xs" // 'xs' | 'sm' | 'md' | 'lg' | 'full' (default: 'xs')
702
+ />
703
+ ```
704
+
705
+ ### ProgressIndicator
706
+ Linear or circular progress indicator.
707
+ ```tsx
708
+ import { ProgressIndicator } from '@zvoove/unity-ui';
709
+
710
+ <ProgressIndicator
711
+ value={75} // number (0-100, default: 0)
712
+ variant="linear" // 'linear' | 'circular' (default: 'linear')
713
+ indeterminate={false} // boolean
714
+ />
715
+ ```
716
+
717
+ ### CodeBlock
718
+ Code display with syntax highlighting.
719
+ ```tsx
720
+ import { CodeBlock } from '@zvoove/unity-ui';
721
+
722
+ <CodeBlock
723
+ code="const x = 42;" // string (required)
724
+ filename="example.ts" // string
725
+ />
726
+ ```
727
+
728
+ ---
729
+
730
+ ## NAVIGATION COMPONENTS
731
+
732
+ ### Tabs
733
+ Tab navigation with optional panels.
734
+ ```tsx
735
+ import { Tabs } from '@zvoove/unity-ui';
736
+
737
+ <Tabs
738
+ items={[ // TabItem[] (required)
739
+ { id: 'tab1', label: 'Overview', icon: 'House' },
740
+ { id: 'tab2', label: 'Details', hasActivity: true },
741
+ { id: 'tab3', label: 'Settings', icon: 'Gear', iconPosition: 'top' },
742
+ ]}
743
+ activeItem="tab1" // string (required)
744
+ onChange={handleChange} // (item: TabItem) => void
745
+ panels={[ // Panel[]
746
+ { id: 'tab1', children: <div>Overview content</div> },
747
+ { id: 'tab2', children: <div>Details content</div> },
748
+ ]}
749
+ isSticky={false} // boolean
750
+ hideBorder={false} // boolean
751
+ itemsAlignment="left" // 'left' | 'center' | 'right' | 'between' | 'around' | 'evenly'
752
+ linkComponent={Link} // React.ElementType
753
+ />
754
+ ```
755
+
756
+ ### Breadcrumbs
757
+ Navigation breadcrumb trail.
758
+ ```tsx
759
+ import { Breadcrumbs } from '@zvoove/unity-ui';
760
+
761
+ <Breadcrumbs
762
+ items={[ // BreadcrumbItem[]
763
+ { label: 'Home', href: '/' },
764
+ { label: 'Products', href: '/products' },
765
+ { label: 'Details' },
766
+ ]}
767
+ linkComponent={Link} // React.ElementType
768
+ />
769
+ ```
770
+
771
+ ### SideNavigation
772
+ Collapsible sidebar navigation.
773
+ ```tsx
774
+ import { SideNavigation } from '@zvoove/unity-ui';
775
+
776
+ <SideNavigation
777
+ menuItems={[ // MenuItem[] (required)
778
+ { id: 'home', label: 'Home', icon: 'House' },
779
+ { id: 'users', label: 'Users', icon: 'Users' },
780
+ ]}
781
+ utilityItems={[ // MenuItem[]
782
+ { id: 'settings', label: 'Settings', icon: 'Gear' },
783
+ ]}
784
+ activeItem="home" // string
785
+ open={true} // boolean
786
+ onToggleOpen={handleToggle} // (open: boolean) => void
787
+ onItemClick={handleClick} // (item: MenuItem) => void
788
+ linkComponent={Link} // React.ElementType
789
+ />
790
+ ```
791
+
792
+ ### Pagination
793
+ Page navigation control.
794
+ ```tsx
795
+ import { Pagination } from '@zvoove/unity-ui';
796
+
797
+ <Pagination
798
+ currentPage={1} // number (required)
799
+ totalPages={10} // number (required)
800
+ onPageChange={handlePage} // (page: number) => void (required)
801
+ rowsPerPage={20} // 10 | 20 | 50 | 100 | 200
802
+ onRowsPerPageChange={handleRows} // (rowsPerPage: RowsPerPage) => void
803
+ rowsPerPageLabel="Rows per page" // string
804
+ pageOfPagesLabel="of" // string
805
+ disabled={false} // boolean
806
+ />
807
+ ```
808
+
809
+ ### TopBar
810
+ Application top bar/header.
811
+ ```tsx
812
+ import { TopBar } from '@zvoove/unity-ui';
813
+
814
+ <TopBar
815
+ leftTopContent={<Logo />} // ReactNode
816
+ rightTopContent={<Avatar />} // ReactNode
817
+ leftBottomContent="Dashboard" // string | ReactNode
818
+ rightBottomContent={actions} // ReactNode
819
+ subtitle="Overview" // string
820
+ density="default" // 'default' | '-2' | '-4'
821
+ />
822
+ ```
823
+
824
+ ---
825
+
826
+ ## FEEDBACK COMPONENTS
827
+
828
+ ### Dialog
829
+ Modal dialog overlay. Use Dialog for modals — never create custom overlays.
830
+ ```tsx
831
+ import { Dialog } from '@zvoove/unity-ui';
832
+
833
+ <Dialog
834
+ open={isOpen} // boolean (required)
835
+ onClose={handleClose} // () => void (required)
836
+ size="md" // ResponsiveType<'sm' | 'md' | 'lg' | 'none' | 'fullscreen'>
837
+ closeOnBackdropClick={true} // boolean
838
+ closeOnEsc={true} // boolean
839
+ padding="md" // CardProps['padding'] — use "none" for edge-to-edge content
840
+ zIndex={20} // number
841
+ >
842
+ <Typography variant="title" size="large">Dialog Title</Typography>
843
+ <Typography>Dialog content goes here.</Typography>
844
+ <Stack direction="row" gap="sm" justify="flex-end">
845
+ <Button variant="outlined" onClick={handleClose}>Cancel</Button>
846
+ <Button onClick={handleConfirm}>Confirm</Button>
847
+ </Stack>
848
+ </Dialog>
849
+ ```
850
+
851
+ Common patterns:
852
+ ```tsx
853
+ // Responsive: fullscreen on mobile, centered modal on tablet+
854
+ <Dialog
855
+ open={isOpen}
856
+ onClose={handleClose}
857
+ size={{ mobile: 'fullscreen', tablet: 'md' }}
858
+ >
859
+ <Stack direction="column" gap="lg">
860
+ <Typography variant="title" size="large">Edit Profile</Typography>
861
+ <TextField label="Name" name="name" value={name} onChange={setName} />
862
+ <TextField label="Email" name="email" value={email} onChange={setEmail} />
863
+ <Stack direction="row" gap="sm" justify="flex-end">
864
+ <Button variant="outlined" onClick={handleClose}>Cancel</Button>
865
+ <Button variant="filled" onClick={handleSave}>Save</Button>
866
+ </Stack>
867
+ </Stack>
868
+ </Dialog>
869
+
870
+ // Confirmation dialog (small)
871
+ <Dialog open={showConfirm} onClose={() => setShowConfirm(false)} size="sm">
872
+ <Stack direction="column" gap="md">
873
+ <Typography variant="title" size="medium">Delete item?</Typography>
874
+ <Typography variant="body" size="medium">This action cannot be undone.</Typography>
875
+ <Stack direction="row" gap="sm" justify="flex-end">
876
+ <Button variant="outlined" onClick={() => setShowConfirm(false)}>Cancel</Button>
877
+ <Button variant="negative" icon="delete" onClick={handleDelete}>Delete</Button>
878
+ </Stack>
879
+ </Stack>
880
+ </Dialog>
881
+
882
+ // Dialog with no padding (for full-width content like tables/images)
883
+ <Dialog open={isOpen} onClose={handleClose} size="lg" padding="none">
884
+ <Table columns={columns} data={data} />
885
+ </Dialog>
886
+ ```
887
+
888
+ ### Sheet
889
+ Bottom or side sheet overlay. Use Sheet for slide-in panels — never create custom side panels.
890
+ ```tsx
891
+ import { Sheet } from '@zvoove/unity-ui';
892
+
893
+ <Sheet
894
+ open={isOpen} // boolean (required)
895
+ onClose={handleClose} // () => void (required)
896
+ title="Sheet Title" // string
897
+ subTitle="Optional subtitle" // string
898
+ placement="bottom" // 'bottom' | 'right' (default: 'bottom')
899
+ size="md" // 'sm' | 'md' | 'lg'
900
+ padding="lg" // SpacingKeys (default: 'lg'). Use "none" for edge-to-edge content
901
+ closeOnBackdropClick={true} // boolean
902
+ closeOnEsc={false} // boolean
903
+ showHandle={true} // boolean
904
+ showCloseButton={false} // boolean
905
+ showBackArrow={false} // boolean
906
+ stickyHeader={true} // boolean
907
+ stickyFooter={true} // boolean
908
+ headerActions={actions} // ReactNode
909
+ footerActions={buttons} // ReactNode
910
+ // Right placement extras:
911
+ resizable={false} // boolean
912
+ width={600} // number | string
913
+ minWidth={570} // number | string
914
+ maxWidth={900} // number | string
915
+ onResizeEnd={handleResize} // (width: number) => void
916
+ >
917
+ {children}
918
+ </Sheet>
919
+ ```
920
+
921
+ Common patterns:
922
+ ```tsx
923
+ // Detail panel from the right
924
+ <Sheet
925
+ open={isOpen}
926
+ onClose={handleClose}
927
+ title="User Details"
928
+ placement="right"
929
+ showCloseButton={true}
930
+ footerActions={
931
+ <Stack direction="row" gap="sm" justify="flex-end">
932
+ <Button variant="outlined" onClick={handleClose}>Cancel</Button>
933
+ <Button variant="filled" onClick={handleSave}>Save</Button>
934
+ </Stack>
935
+ }
936
+ >
937
+ <Stack direction="column" gap="md">
938
+ <TextField label="Name" name="name" />
939
+ <TextField label="Email" name="email" />
940
+ </Stack>
941
+ </Sheet>
942
+
943
+ // Mobile bottom sheet for actions
944
+ <Sheet
945
+ open={isOpen}
946
+ onClose={handleClose}
947
+ title="Sort by"
948
+ placement="bottom"
949
+ size="sm"
950
+ showHandle={true}
951
+ >
952
+ <Stack direction="column" gap="xs">
953
+ <Button variant="text" onClick={() => sort('name')}>Name</Button>
954
+ <Button variant="text" onClick={() => sort('date')}>Date</Button>
955
+ <Button variant="text" onClick={() => sort('status')}>Status</Button>
956
+ </Stack>
957
+ </Sheet>
958
+ ```
959
+
960
+ ### Snackbar
961
+ Toast notification system. Requires `<Snackbar />` provider in your app tree.
962
+ ```tsx
963
+ import { Snackbar, useSnackbar } from '@zvoove/unity-ui';
964
+
965
+ // In your app root:
966
+ <Snackbar />
967
+
968
+ // In any component:
969
+ const { addSnackbar } = useSnackbar();
970
+
971
+ addSnackbar('Operation successful', {
972
+ variant: 'positive', // 'default' | 'positive' | 'warning' | 'error' | 'subtle'
973
+ placement: 'top-right', // 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
974
+ icon: 'Check', // CommonIconNames
975
+ showCloseButton: true, // boolean
976
+ actionLabel: 'Undo', // string
977
+ onAction: handleUndo, // (id: string) => void
978
+ onClose: handleClose, // (id: string) => void
979
+ duration: 5000, // number (ms)
980
+ });
981
+ ```
982
+
983
+ ### InfoBox
984
+ Inline informational message.
985
+ ```tsx
986
+ import { InfoBox } from '@zvoove/unity-ui';
987
+
988
+ <InfoBox
989
+ message="Your changes have been saved." // string (required)
990
+ variant="positive" // 'default' | 'positive' | 'warning' | 'error' | 'subtle' | 'neutral' | 'outlined'
991
+ icon="CheckCircle" // CommonIconNames
992
+ elevated={true} // boolean (default: true)
993
+ />
994
+ ```
995
+
996
+ ### ConfirmationCard
997
+ Status card with actions.
998
+ ```tsx
999
+ import { ConfirmationCard } from '@zvoove/unity-ui';
1000
+
1001
+ <ConfirmationCard
1002
+ title="Delete item?" // string (required)
1003
+ variant="warning" // 'info' | 'success' | 'warning' | 'error'
1004
+ icon="Warning" // CommonIconNames
1005
+ status="confirmed" // 'confirmed' | 'rejected' | 'skipped'
1006
+ statusLabel="Approved" // string
1007
+ actions={[ // ConfirmationCardAction[]
1008
+ { label: 'Cancel', variant: 'outlined', onClick: handleCancel },
1009
+ { label: 'Delete', variant: 'negative', icon: 'Trash', onClick: handleDelete },
1010
+ ]}
1011
+ >
1012
+ This action cannot be undone.
1013
+ </ConfirmationCard>
1014
+ ```
1015
+
1016
+ ### Tooltip
1017
+ Hover tooltip.
1018
+ ```tsx
1019
+ import { Tooltip } from '@zvoove/unity-ui';
1020
+
1021
+ <Tooltip
1022
+ content="Helpful description" // ReactNode (required)
1023
+ placement="top" // 'top' | 'bottom' | 'left' | 'right'
1024
+ disabled={false} // boolean
1025
+ isOpen={undefined} // boolean (controlled mode)
1026
+ >
1027
+ <Button>Hover me</Button>
1028
+ </Tooltip>
1029
+ ```
1030
+
1031
+ ---
1032
+
1033
+ ## COMPOUND COMPONENTS
1034
+
1035
+ ### Accordion
1036
+ Expandable accordion panels.
1037
+ ```tsx
1038
+ import { Accordion } from '@zvoove/unity-ui';
1039
+
1040
+ <Accordion
1041
+ items={[ // AccordionItem[] (required)
1042
+ { id: '1', title: 'Section 1', supportingText: 'Details', children: <div>Content</div> },
1043
+ { id: '2', title: 'Section 2', children: <div>Content</div>, disabled: true },
1044
+ ]}
1045
+ openItems={['1']} // string | string[]
1046
+ onToggle={handleToggle} // (item: AccordionItem) => void
1047
+ allowMultipleOpenItems={false} // boolean
1048
+ />
1049
+ ```
1050
+
1051
+ ---
1052
+
1053
+ ## HOOKS
1054
+
1055
+ ### useBreakpoint
1056
+ Responsive breakpoint detection.
1057
+ ```tsx
1058
+ import { useBreakpoint } from '@zvoove/unity-ui';
1059
+
1060
+ const { currentBreakpoint, isBiggerThan, isSmallerThan, isBetween } = useBreakpoint();
1061
+
1062
+ if (isBiggerThan('tablet')) { /* tablet and above */ }
1063
+ if (isSmallerThan('laptop')) { /* below laptop */ }
1064
+ if (isBetween('tablet', 'desktop')) { /* between tablet and desktop */ }
1065
+ ```
1066
+
1067
+ ### useClickOutside
1068
+ Detect clicks outside a referenced element.
1069
+ ```tsx
1070
+ import { useClickOutside } from '@zvoove/unity-ui';
1071
+
1072
+ const ref = useRef(null);
1073
+ useClickOutside(ref, () => setOpen(false), isOpen);
1074
+ ```
1075
+
1076
+ ---
1077
+
1078
+ ## STYLING
1079
+
1080
+ When a UI element has no matching Unity UI component, use **Tailwind CSS v4** with the Unity UI design tokens. **Never use inline styles or arbitrary CSS values.**
1081
+
1082
+ ### Setup
1083
+
1084
+ ```bash
1085
+ npm install tailwindcss tailwind-variants tailwind-merge
1086
+ ```
1087
+
1088
+ In your Tailwind CSS entry file:
1089
+ ```css
1090
+ @import 'tailwindcss';
1091
+ @import '@zvoove/unity-ui/theme.css'; /* registers all design tokens as Tailwind utilities */
1092
+ ```
1093
+
1094
+ ### Design Tokens as Tailwind Classes
1095
+
1096
+ All tokens from `theme.css` are available as Tailwind utilities. Use **semantic** tokens, not raw palette tokens.
1097
+
1098
+ **Colors (semantic — always prefer these)**
1099
+ | Token | Tailwind utility | Use for |
1100
+ |-------|-----------------|---------|
1101
+ | `--color-primary` | `bg-primary` / `text-primary` / `border-primary` | Primary brand color |
1102
+ | `--color-on-primary` | `text-on-primary` | Text on primary backgrounds |
1103
+ | `--color-surface` | `bg-surface` | Card/panel backgrounds |
1104
+ | `--color-surface-container` | `bg-surface-container` | Nested container backgrounds |
1105
+ | `--color-on-surface` | `text-on-surface` | Body text |
1106
+ | `--color-on-surface-variant` | `text-on-surface-variant` | Secondary/muted text |
1107
+ | `--color-outline` | `border-outline` | Default borders |
1108
+ | `--color-outline-variant` | `border-outline-variant` | Subtle dividers |
1109
+ | `--color-error` | `bg-error` / `text-error` | Error states |
1110
+ | `--color-background` | `bg-background` | Page background |
1111
+
1112
+ **Spacing** (for gap, padding, margin, width, height)
1113
+ `none`(0) • `xs2`(4px) • `xs`(8px) • `sm`(12px) • `md`(16px) • `lg`(20px) • `xl`(24px) • `xl2`(32px) • `xl3`(40px) • `xl4`(48px) • `xl5`(64px) • `xl6`(80px) • `xl7`(96px)
1114
+
1115
+ Examples: `p-md`, `gap-lg`, `mx-xl`, `w-xl4`
1116
+
1117
+ **Typography**
1118
+ `text-body-small` • `text-body-medium` • `text-body-large`
1119
+ `text-label-small` • `text-label-medium` • `text-label-large`
1120
+ `text-title-small` • `text-title-medium` • `text-title-large`
1121
+ `text-headline-small` • `text-headline-medium` • `text-headline-large`
1122
+ `text-display-small` • `text-display-medium` • `text-display-large`
1123
+
1124
+ **Border radius**: `rounded-none` • `rounded-xs` • `rounded-sm` • `rounded-md` • `rounded-lg` • `rounded-xl` • `rounded-full`
1125
+
1126
+ **Shadows**: `shadow-elevation1` • `shadow-elevation2` • `shadow-elevation3` • `shadow-elevation4` • `shadow-elevation5`
1127
+ Dark mode shadows: `dark:shadow-elevation1-dark` (replace `elevation1` with the level you need)
1128
+
1129
+ **Dark mode**: use `dark:` Tailwind prefix. Activated by `data-theme="dark"` on a parent element — not via `prefers-color-scheme`.
1130
+
1131
+ ### tailwind-variants for Component Variants
1132
+
1133
+ Use `tailwind-variants` when a custom UI element has multiple visual variants.
1134
+
1135
+ Configure `tv` with Unity UI spacing tokens so class merging works correctly:
1136
+ ```ts
1137
+ import { createTV } from 'tailwind-variants';
1138
+
1139
+ export const tv = createTV({
1140
+ twMergeConfig: {
1141
+ theme: {
1142
+ spacing: ['none', 'xs2', 'xs', 'sm', 'md', 'lg', 'xl', 'xl2', 'xl3', 'xl4', 'xl5', 'xl6', 'xl7'],
1143
+ borderRadius: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'full'],
1144
+ },
1145
+ },
1146
+ });
1147
+ ```
1148
+
1149
+ Usage:
1150
+ ```ts
1151
+ const badge = tv({
1152
+ base: ['inline-flex', 'items-center', 'rounded-full', 'px-sm', 'py-xs2', 'text-label-small'],
1153
+ variants: {
1154
+ tone: {
1155
+ primary: ['bg-primary-container', 'text-on-primary-container'],
1156
+ error: ['bg-error-container', 'text-on-error-container'],
1157
+ surface: ['bg-surface-container', 'text-on-surface'],
1158
+ },
1159
+ },
1160
+ defaultVariants: { tone: 'surface' },
1161
+ });
1162
+
1163
+ // Apply:
1164
+ <div className={badge({ tone: 'primary' })}>Label</div>
1165
+ ```
1166
+
1167
+ ### Class Merging
1168
+
1169
+ Use `tailwind-merge` for ad-hoc merging (e.g. accepting a `className` prop):
1170
+ ```ts
1171
+ import { twMerge } from 'tailwind-merge';
1172
+
1173
+ const className = twMerge('p-md bg-surface', props.className);
1174
+ ```
1175
+
1176
+ ### Rules
1177
+
1178
+ - NEVER use inline styles: `style={{ color: '#ff0000', padding: '13px' }}`
1179
+ - NEVER use arbitrary Tailwind values: `p-[13px]`, `text-[#ff0000]`, `bg-[rgba(0,0,0,0.5)]`
1180
+ - NEVER use generic Tailwind size utilities for spacing — use design tokens: `p-md` not `p-4`
1181
+ - NEVER use generic Tailwind text utilities for typography — use design tokens: `text-body-medium` not `text-sm`
1182
+ - ALWAYS use semantic color tokens (`bg-primary`, `text-on-surface`) not palette tokens (`bg-primary-40`)
1183
+ - Use `tv()` from `tailwind-variants` when a custom element has multiple visual variants
1184
+ - Use `dark:` prefix for dark mode — never hard-code colors per theme in two separate class sets
1185
+
1186
+ ---
1187
+
1188
+ ## CUSTOM COMPONENT
1189
+
1190
+ When you need UI that no existing Unity UI component covers, create a custom component that follows the same conventions so it stays consistent with the design system.
1191
+
1192
+ ### File Structure
1193
+
1194
+ ```
1195
+ src/components/MyComponent/
1196
+ MyComponent.tsx # Component implementation
1197
+ MyComponent.styled.ts # tailwind-variants styles
1198
+ MyComponent.types.ts # TypeScript props interface
1199
+ MyComponent.test.tsx # Vitest + RTL tests (optional but recommended)
1200
+ index.ts # Re-exports
1201
+ ```
1202
+
1203
+ ### `MyComponent.types.ts`
1204
+
1205
+ ```ts
1206
+ import { ReactNode } from 'react';
1207
+
1208
+ export interface MyComponentProps {
1209
+ /**
1210
+ * Content to render inside the component.
1211
+ */
1212
+ children?: ReactNode;
1213
+
1214
+ /**
1215
+ * Visual variant.
1216
+ * @default 'primary'
1217
+ */
1218
+ variant?: 'primary' | 'secondary';
1219
+ }
1220
+ ```
1221
+
1222
+ Rules:
1223
+ - JSDoc on every prop; `@default` on every defaulted prop
1224
+ - Named export only
1225
+
1226
+ ### `MyComponent.styled.ts`
1227
+
1228
+ ```ts
1229
+ import { createTV } from 'tailwind-variants';
1230
+
1231
+ // Configure tv with Unity UI spacing/radius tokens for correct class merging
1232
+ const tv = createTV({
1233
+ twMergeConfig: {
1234
+ theme: {
1235
+ spacing: ['none', 'xs2', 'xs', 'sm', 'md', 'lg', 'xl', 'xl2', 'xl3', 'xl4', 'xl5', 'xl6', 'xl7'],
1236
+ borderRadius: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'full'],
1237
+ },
1238
+ },
1239
+ });
1240
+
1241
+ export const myComponentStyles = tv({
1242
+ base: ['text-body-medium', 'rounded-sm'],
1243
+ variants: {
1244
+ variant: {
1245
+ primary: ['bg-primary', 'text-on-primary'],
1246
+ secondary: ['bg-surface-container', 'text-on-surface'],
1247
+ },
1248
+ },
1249
+ defaultVariants: {
1250
+ variant: 'primary',
1251
+ },
1252
+ });
1253
+ ```
1254
+
1255
+ Tip: define the configured `tv` once in a shared `lib/tv.ts` file in your project and import from there instead of re-configuring in every component.
1256
+
1257
+ ### `MyComponent.tsx` — Simple
1258
+
1259
+ ```tsx
1260
+ import { myComponentStyles } from './MyComponent.styled';
1261
+ import { MyComponentProps } from './MyComponent.types';
1262
+
1263
+ const MyComponent = ({
1264
+ children,
1265
+ variant = 'primary',
1266
+ }: MyComponentProps) => {
1267
+ return (
1268
+ <div className={myComponentStyles({ variant })}>
1269
+ {children}
1270
+ </div>
1271
+ );
1272
+ };
1273
+
1274
+ MyComponent.displayName = 'MyComponent';
1275
+
1276
+ export default MyComponent;
1277
+ ```
1278
+
1279
+ ### `MyComponent.tsx` — With `forwardRef`
1280
+
1281
+ Use when the root is a DOM element consumers may need a ref for:
1282
+
1283
+ ```tsx
1284
+ import { forwardRef } from 'react';
1285
+
1286
+ import { myComponentStyles } from './MyComponent.styled';
1287
+ import { MyComponentProps } from './MyComponent.types';
1288
+
1289
+ const MyComponent = forwardRef<HTMLDivElement, MyComponentProps>(
1290
+ ({ children, variant = 'primary', ...props }, ref) => {
1291
+ return (
1292
+ <div
1293
+ ref={ref}
1294
+ className={myComponentStyles({ variant })}
1295
+ {...props}
1296
+ >
1297
+ {children}
1298
+ </div>
1299
+ );
1300
+ }
1301
+ );
1302
+
1303
+ MyComponent.displayName = 'MyComponent';
1304
+
1305
+ export default MyComponent;
1306
+ ```
1307
+
1308
+ ### `index.ts`
1309
+
1310
+ ```ts
1311
+ export { default as MyComponent } from './MyComponent';
1312
+ export type { MyComponentProps } from './MyComponent.types';
1313
+ ```
1314
+
1315
+ ### Using Unity UI Components Inside Custom Components
1316
+
1317
+ ```tsx
1318
+ import { Icon, Stack, Typography } from '@zvoove/unity-ui';
1319
+ ```
1320
+
1321
+ Always compose with existing Unity UI components rather than re-implementing their functionality.
1322
+
1323
+ ### Rules
1324
+
1325
+ - Always set `ComponentName.displayName = 'ComponentName'`
1326
+ - Always JSDoc every prop with `@default` on defaulted props
1327
+ - Use `tv()` from `tailwind-variants` (configured with Unity UI tokens — see `unity-ui-styling` skill)
1328
+ - Use design tokens as Tailwind utilities — no arbitrary values, no inline styles
1329
+ - Use `forwardRef` when the root is a DOM element that callers may need to reference
1330
+ - Import Unity UI components from `@zvoove/unity-ui`
1331
+ - Never recreate what an existing Unity UI component already does
1332
+
1333
+ ---
1334
+
1335
+ ## SPACING SCALE
1336
+
1337
+ Use these values for gap, padding, and margin props:
1338
+ - `none` = 0px
1339
+ - `xs2` = 4px
1340
+ - `xs` = 8px
1341
+ - `sm` = 12px
1342
+ - `md` = 16px
1343
+ - `lg` = 20px
1344
+ - `xl` = 24px
1345
+ - `xl2` = 32px
1346
+ - `xl3` = 40px
1347
+ - `xl4` = 48px
1348
+ - `xl5` = 64px
1349
+ - `xl6` = 80px
1350
+ - `xl7` = 96px
1351
+
1352
+ ---
1353
+
1354
+ ## ICON NAMES
1355
+
1356
+ Unity UI uses its own semantic icon names (not raw Phosphor icon names). Pass them via `<Icon name="icon-name" />`.
1357
+
1358
+ ### Common Icons
1359
+
1360
+ add, add-circle, add-file, agent, airplane, archive, arrow-back, arrow-bend, arrow-down, arrow-forward, arrow-left-right, arrow-up, article, attachment, automate, backspace, bank, bicycle, billing, binoculars, break, bus, calendar, calendar-blank, calendar-check, calendar-dot, calendar-x, camera, car, cart, cash-money, certificate, chat-bubble, chats, check, check-circle, checkbox, checkbox-empty, checkbox-indefinitely, checks, chevron-down, chevron-left, chevron-right, chevron-up, circle-notch, close, close-circle, clock-countdown, clock-person, cloud-download, cloud-upload, columns, copy, dark-mode, deactivate, delete, diagram-view, download, drag, edit, error, exclamation-mark, expand, expenses, face-id, file, filter, filters, first-page, folder, grid-view, hard-hat, help, hide, home, images, info, invoice, job, keyboard, knowledge, language, last-page, light-mode, list-view, location, location-pin, lock, menu, metadata, microphone, minus, more-horizontal, more-vertical, moped, navigation-arrow, note, notches, notifications, numpad, open-in-new-tab, order, organization, pause, phone, piggy-bank, plant, printer, privacy, qr-code, refresh, remark, save, search, send-message, settings, setup-time, shapes, show, sick, sidebar, sign-out, signature, skip-forward, smartphone, sparkle, star, start, stop, table, taxi, text-align-center, text-align-justify, text-align-left, text-align-right, text-t, time, time-sheet-download, time-sheet-upload, timer, train, translate, travel, unfold, upload, user, user-account, users, vacation, wallet, warning, wrench.
1361
+
1362
+ ### File Type Icons
1363
+
1364
+ file-empty, file-pdf, file-folder, file-image, file-video, file-video-2, file-audio, file-code, file-document, file-spreadsheet, file-image-img, file-image-jpg, file-image-jpeg, file-image-png, file-image-webp, file-image-tiff, file-image-gif, file-image-svg, file-image-eps, file-document-pdf, file-document-doc, file-document-docx, file-document-txt, file-document-csv, file-document-xls, file-document-xlsx, file-document-ppt, file-document-pptx, file-design-fig, file-design-ai, file-design-psd, file-design-indd, file-design-aep, file-media-mp3, file-media-wav, file-media-mp4, file-media-mpeg, file-media-avi, file-media-mkv, file-development-html, file-development-css, file-development-rss, file-development-sql, file-development-js, file-development-json, file-development-java, file-development-xml, file-development-exe, file-development-dmg, file-archive-zip, file-archive-rar.
1365
+
1366
+ Use `getIconForFileExtension(extension)` to resolve a file extension (e.g., "pdf") to its icon name automatically.
1367
+
1368
+ ---
1369
+
1370
+ ## EXAMPLE: Contact Form
1371
+
1372
+ ```tsx
1373
+ import {
1374
+ Card,
1375
+ Stack,
1376
+ Typography,
1377
+ TextField,
1378
+ Textarea,
1379
+ Select,
1380
+ Checkbox,
1381
+ Button,
1382
+ } from '@zvoove/unity-ui';
1383
+
1384
+ function ContactForm() {
1385
+ return (
1386
+ <Card padding="xl" elevation={1} variant="outlined" maxWidth={600}>
1387
+ <Stack direction="column" gap="lg">
1388
+ <Typography variant="headline" size="medium" as="h2">
1389
+ Contact Us
1390
+ </Typography>
1391
+
1392
+ <Grid columns={{ mobile: 1, tablet: 2 }} gap="md">
1393
+ <Grid.Item>
1394
+ <TextField label="First name" name="firstName" required />
1395
+ </Grid.Item>
1396
+ <Grid.Item>
1397
+ <TextField label="Last name" name="lastName" required />
1398
+ </Grid.Item>
1399
+ </Grid>
1400
+
1401
+ <TextField label="Email" name="email" type="email" icon="Envelope" required />
1402
+
1403
+ <Select
1404
+ name="subject"
1405
+ label="Subject"
1406
+ options={[
1407
+ { value: 'general', label: 'General inquiry' },
1408
+ { value: 'support', label: 'Technical support' },
1409
+ { value: 'billing', label: 'Billing' },
1410
+ ]}
1411
+ />
1412
+
1413
+ <Textarea placeholder="Your message..." maxLength={1000} rows={5} />
1414
+
1415
+ <Checkbox label="I agree to the privacy policy" name="privacy" />
1416
+
1417
+ <Stack direction="row" gap="sm" justify="flex-end">
1418
+ <Button variant="outlined">Cancel</Button>
1419
+ <Button variant="filled" icon="PaperPlaneRight">Send</Button>
1420
+ </Stack>
1421
+ </Stack>
1422
+ </Card>
1423
+ );
1424
+ }
1425
+ ```
1426
+
1427
+ ---
1428
+
1429
+ ## RULES FOR AI AGENTS
1430
+
1431
+ 1. **Always import from `@zvoove/unity-ui`** - never recreate components that exist in this library
1432
+ 2. **Use Stack and Grid for layout** - do not use Card, div, or CSS for layout. Stack is for rows/columns, Grid is for multi-column layouts
1433
+ 3. **Card is a visual container, NOT a layout tool** - Card provides background, elevation, and border. Wrap layout content in Stack/Grid INSIDE a Card. Never use Card to arrange items
1434
+ 4. **Use Typography for all text** - never use raw `<p>`, `<h1>`, `<span>` etc.
1435
+ 5. **Use the spacing scale** for gap/padding/margin - never use arbitrary pixel values
1436
+ 6. **Use Button variants** appropriately - primary actions: `filled`, secondary: `outlined`, destructive: `negative`, success: `positive`
1437
+ 7. **Use TextField/Select/Checkbox/Radio for forms** - never create custom form inputs
1438
+ 8. **Use Dialog for modals, Sheet for panels** - never create custom overlays
1439
+ 9. **Use Snackbar for notifications** - never create custom toast systems
1440
+ 10. **Use responsive props** when the UI should adapt to screen sizes. Values cascade up from smallest breakpoint. Common pattern: `{{ mobile: 'column', tablet: 'row' }}`
1441
+ 11. **Card, Dialog, Sheet padding can be "none"** - use `padding="none"` for edge-to-edge content like images, tables, or dividers inside these components
1442
+ 12. **Icons use semantic names** as strings (e.g., `"search"`, `"delete"`, `"edit"`) - never import Phosphor icon components directly