@l3mpire/ui 2.5.1

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/USAGE.md ADDED
@@ -0,0 +1,1025 @@
1
+ # lemDS — Usage Guide
2
+
3
+ Reference for developers and AI agents consuming the `@l3mpire/ui` and `@l3mpire/icons` packages.
4
+
5
+ ---
6
+
7
+ ## Setup
8
+
9
+ ```tsx
10
+ // 1. Import global styles (once, at app entry)
11
+ import "@l3mpire/ui/globals.css";
12
+
13
+ // 2. Import components
14
+ import { Button, Badge, TextInput } from "@l3mpire/ui";
15
+
16
+ // 3. Import icons (always both variants)
17
+ import { faHomeSolid, faHomeOutline } from "@l3mpire/icons";
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Icons
23
+
24
+ ### Naming convention
25
+
26
+ Every icon has **two variants**, always suffixed:
27
+
28
+ | Variant | Suffix | Source package |
29
+ |---|---|---|
30
+ | Outline (regular) | `faXxxOutline` | `@fortawesome/pro-regular-svg-icons` v7 |
31
+ | Solid (filled) | `faXxxSolid` | `@fortawesome/pro-solid-svg-icons` v7 |
32
+
33
+ **No unsuffixed icon names.** Always import both when you need switching.
34
+
35
+ ```tsx
36
+ import { Icon, faHomeOutline, faHomeSolid } from "@l3mpire/icons";
37
+
38
+ // Simple usage — pick one variant
39
+ <Icon icon={faHomeSolid} size="md" />
40
+
41
+ // Switchable usage — pass both, toggle with `solid`
42
+ <Icon icon={faHomeOutline} solidIcon={faHomeSolid} solid={isActive} size="sm" />
43
+ ```
44
+
45
+ ### Icon sizes
46
+
47
+ | `size` prop | Rendered size |
48
+ |---|---|
49
+ | `"xs"` | 12px |
50
+ | `"sm"` | 14px |
51
+ | `"md"` | 16px |
52
+ | `"lg"` | 20px |
53
+ | `"xl"` | 24px |
54
+
55
+ ### Available icons
56
+
57
+ All icons come in `Outline` + `Solid` pairs:
58
+
59
+ `faCheck`, `faXmark`, `faPlus`, `faMinus`, `faPen`, `faTrash`, `faTrashCan`, `faMagnifyingGlass`, `faChevronDown`, `faChevronUp`, `faChevronLeft`, `faChevronRight`, `faArrowLeft`, `faArrowRight`, `faArrowUp`, `faArrowDown`, `faEllipsis`, `faEllipsisVertical`, `faSpinner`, `faCircleNotch`, `faTriangleExclamation`, `faCircleExclamation`, `faCircleCheck`, `faCircleInfo`, `faCircleXmark`, `faBell`, `faGear`, `faUser`, `faUsers`, `faEnvelope`, `faPaperPlane`, `faLink`, `faCopy`, `faDownload`, `faUpload`, `faFilter`, `faSort`, `faStar`, `faHeart`, `faEye`, `faEyeSlash`, `faLock`, `faUnlock`, `faCalendar`, `faClock`, `faHome`, `faFolder`, `faFile`, `faBookmark`, `faComment`, `faMessage`, `faSquarePlus`, `faSquareCheck`, `faPenToSquare`, `faPaperPlaneTop`, `faAddressBook`, `faBuildings`, `faChartLineUp`, `faInbox`, `faPhone`, `faVideo`, `faCoin`, `faStars`, `faArrowLeftFromLine`, `faGripDotsVertical`
60
+
61
+ ---
62
+
63
+ ## Spacing
64
+
65
+ Use design token spacing classes — never arbitrary pixel values.
66
+
67
+ | Class | Value |
68
+ |---|---|
69
+ | `p-2xs` / `gap-2xs` | 2px |
70
+ | `p-xs` / `gap-xs` | 4px |
71
+ | `p-sm` / `gap-sm` | 6px |
72
+ | `p-base` / `gap-base` | 8px |
73
+ | `p-md` / `gap-md` | 12px |
74
+ | `p-lg` / `gap-lg` | 16px |
75
+ | `p-xl` / `gap-xl` | 20px |
76
+ | `p-2xl` / `gap-2xl` | 24px |
77
+ | `p-3xl` / `gap-3xl` | 32px |
78
+ | `p-4xl` / `gap-4xl` | 40px |
79
+ | `p-5xl` / `gap-5xl` | 48px |
80
+
81
+ Works with all Tailwind spacing prefixes: `p-`, `px-`, `py-`, `m-`, `mx-`, `my-`, `gap-`, `space-x-`, `space-y-`, `w-`, `h-`, `top-`, `left-`, etc.
82
+
83
+ ---
84
+
85
+ ## Border Radius
86
+
87
+ | Class | Value |
88
+ |---|---|
89
+ | `rounded-none` | 0 |
90
+ | `rounded-2xs` | 2px |
91
+ | `rounded-xs` | 4px |
92
+ | `rounded-sm` | 6px |
93
+ | `rounded-base` | 8px |
94
+ | `rounded-md` | 12px |
95
+ | `rounded-lg` | 16px |
96
+ | `rounded-xl` | 20px |
97
+ | `rounded-full` | 9999px |
98
+
99
+ ---
100
+
101
+ ## Typography
102
+
103
+ ### Tailwind classes
104
+
105
+ | Class | Size |
106
+ |---|---|
107
+ | `text-xxs` | 10px |
108
+ | `text-xs` | 12px |
109
+ | `text-sm` | 14px |
110
+ | `text-base` | 16px |
111
+ | `text-md` | 18px |
112
+ | `text-lg` | 20px |
113
+ | `text-xl` | 24px |
114
+
115
+ **Line heights:** `leading-2xs` (14px), `leading-xs` (16px), `leading-sm` (20px), `leading-base` (24px), `leading-md` (28px), `leading-lg` (32px)
116
+
117
+ **Font weights:** `font-regular` (400), `font-medium` (500), `font-bold` (700)
118
+
119
+ ### Typography component
120
+
121
+ ```tsx
122
+ import { Typography } from "@l3mpire/ui";
123
+
124
+ <Typography variant="h1">Heading 1</Typography>
125
+ <Typography variant="md" weight="medium">Body text</Typography>
126
+ <Typography variant="sm" weight="regular">Small text</Typography>
127
+ ```
128
+
129
+ **Variants:** `h1`, `h2`, `h3`, `lg`, `md`, `sm`, `xs`
130
+ **Weights:** `regular`, `medium`, `bold`
131
+
132
+ ---
133
+
134
+ ## Shadows
135
+
136
+ | Class | Usage |
137
+ |---|---|
138
+ | `shadow-sm` | Subtle elevation |
139
+ | `shadow-md` | Cards, dropdowns |
140
+ | `shadow-lg` | Modals, popovers |
141
+ | `shadow-focus-ring` | Focus state ring (3px blue) |
142
+
143
+ ---
144
+
145
+ ## Dark Mode
146
+
147
+ Activate with `data-theme="dark"` on any ancestor:
148
+
149
+ ```html
150
+ <div data-theme="dark">
151
+ <!-- All descendants render in dark mode -->
152
+ </div>
153
+ ```
154
+
155
+ All component tokens automatically switch between light and dark values.
156
+
157
+ ---
158
+
159
+ ## Components
160
+
161
+ ### Button
162
+
163
+ ```tsx
164
+ import { Button } from "@l3mpire/ui";
165
+ import { faPaperPlaneOutline } from "@l3mpire/icons";
166
+
167
+ <Button appearance="solid" intent="brand" size="md">
168
+ Send
169
+ </Button>
170
+
171
+ <Button appearance="outlined" intent="neutral" size="sm" leftIcon={faPaperPlaneOutline}>
172
+ Send
173
+ </Button>
174
+
175
+ <Button appearance="ghost" intent="alert" size="lg" loading>
176
+ Deleting...
177
+ </Button>
178
+
179
+ <Button appearance="solid" intent="brand" size="md" iconOnly leftIcon={faPaperPlaneOutline} />
180
+ ```
181
+
182
+ | Prop | Values |
183
+ |---|---|
184
+ | `appearance` | `"solid"`, `"outlined"`, `"ghost"` |
185
+ | `intent` | `"brand"`, `"neutral"`, `"alert"` |
186
+ | `size` | `"sm"`, `"md"`, `"lg"` |
187
+ | `leftIcon` | `IconDefinition` |
188
+ | `rightIcon` | `IconDefinition` |
189
+ | `iconOnly` | `boolean` |
190
+ | `loading` | `boolean` |
191
+ | `disabled` | `boolean` |
192
+ | `asChild` | `boolean` (render as child element) |
193
+
194
+ ---
195
+
196
+ ### Badge
197
+
198
+ ```tsx
199
+ import { Badge } from "@l3mpire/ui";
200
+ import { faStarOutline } from "@l3mpire/icons";
201
+
202
+ <Badge variant="solid" type="primary">New</Badge>
203
+ <Badge variant="light" type="success" size="sm">Active</Badge>
204
+ <Badge variant="outlined" type="critical" icon={faStarOutline}>99</Badge>
205
+ ```
206
+
207
+ | Prop | Values |
208
+ |---|---|
209
+ | `variant` | `"solid"`, `"light"`, `"outlined"` |
210
+ | `type` | `"primary"`, `"success"`, `"critical"`, `"warning"`, `"neutral"` |
211
+ | `size` | `"sm"`, `"md"`, `"lg"` |
212
+ | `icon` | `IconDefinition` (optional) |
213
+
214
+ ---
215
+
216
+ ### Tag
217
+
218
+ ```tsx
219
+ import { Tag } from "@l3mpire/ui";
220
+ import { faPaperPlaneOutline } from "@l3mpire/icons";
221
+
222
+ <Tag variant="brand">Label</Tag>
223
+ <Tag variant="neutral" icon={faPaperPlaneOutline} onClose={() => {}}>
224
+ Campaign
225
+ </Tag>
226
+ ```
227
+
228
+ | Prop | Values |
229
+ |---|---|
230
+ | `variant` | `"brand"`, `"neutral"` |
231
+ | `size` | `"sm"`, `"md"` |
232
+ | `icon` | `IconDefinition` |
233
+ | `onClose` | `() => void` (shows remove button when provided) |
234
+
235
+ ---
236
+
237
+ ### Link
238
+
239
+ ```tsx
240
+ import { Link } from "@l3mpire/ui";
241
+ import { faArrowRightSolid } from "@l3mpire/icons";
242
+
243
+ <Link intent="brand" rightIcon={faArrowRightSolid}>Learn more</Link>
244
+ <Link intent="neutral" size="sm">Settings</Link>
245
+ <Link intent="alert">Delete account</Link>
246
+ <Link intent="success">Confirmed</Link>
247
+ ```
248
+
249
+ | Prop | Values |
250
+ |---|---|
251
+ | `intent` | `"neutral"`, `"brand"`, `"alert"`, `"success"`, `"warning"` |
252
+ | `size` | `"sm"`, `"md"` |
253
+ | `leftIcon` | `IconDefinition` |
254
+ | `rightIcon` | `IconDefinition` |
255
+ | `disabled` | `boolean` |
256
+ | `asChild` | `boolean` |
257
+
258
+ ---
259
+
260
+ ### TextInput
261
+
262
+ ```tsx
263
+ import { TextInput } from "@l3mpire/ui";
264
+ import { faMagnifyingGlassSolid, faEnvelopeSolid } from "@l3mpire/icons";
265
+
266
+ <TextInput label="Email" placeholder="you@example.com" iconLeft={faEnvelopeSolid} />
267
+ <TextInput label="Search" iconLeft={faMagnifyingGlassSolid} onClear={() => {}} />
268
+ <TextInput label="Name" error errorMessage="This field is required" />
269
+ <TextInput label="Add item" hasButton onButtonClick={() => {}} />
270
+ ```
271
+
272
+ | Prop | Values |
273
+ |---|---|
274
+ | `size` | `"sm"`, `"md"` |
275
+ | `label` | `string` |
276
+ | `error` | `boolean` |
277
+ | `errorMessage` | `string` (shown below input when `error` is true) |
278
+ | `success` | `boolean` |
279
+ | `iconLeft` | `IconDefinition` |
280
+ | `iconRight` | `IconDefinition` |
281
+ | `onClear` | `() => void` (shows clear button when provided) |
282
+ | `hasButton` | `boolean` |
283
+ | `buttonIcon` | `IconDefinition` |
284
+ | `onButtonClick` | `() => void` |
285
+ | `disabled` | `boolean` |
286
+
287
+ ---
288
+
289
+ ### TextArea
290
+
291
+ ```tsx
292
+ import { TextArea } from "@l3mpire/ui";
293
+
294
+ <TextArea label="Message" placeholder="Type here..." />
295
+ <TextArea label="Bio" error errorMessage="Too long" characterLimit="200" />
296
+ ```
297
+
298
+ | Prop | Values |
299
+ |---|---|
300
+ | `label` | `string` |
301
+ | `error` | `boolean` |
302
+ | `errorMessage` | `string` |
303
+ | `success` | `boolean` |
304
+ | `characterLimit` | `string` (shows counter) |
305
+ | `disabled` | `boolean` |
306
+
307
+ ---
308
+
309
+ ### Checkbox
310
+
311
+ ```tsx
312
+ import { Checkbox } from "@l3mpire/ui";
313
+
314
+ <Checkbox label="Accept terms" />
315
+ <Checkbox label="Disabled" disabled />
316
+ <Checkbox label="Pre-checked" defaultChecked />
317
+ ```
318
+
319
+ | Prop | Values |
320
+ |---|---|
321
+ | `label` | `string` |
322
+ | `checked` | `boolean` (controlled) |
323
+ | `defaultChecked` | `boolean` (uncontrolled) |
324
+ | `onCheckedChange` | `(checked: boolean) => void` |
325
+ | `disabled` | `boolean` |
326
+
327
+ ---
328
+
329
+ ### Switch
330
+
331
+ ```tsx
332
+ import { Switch } from "@l3mpire/ui";
333
+
334
+ <Switch label="Notifications" />
335
+ <Switch label="Disabled" disabled />
336
+ ```
337
+
338
+ | Prop | Values |
339
+ |---|---|
340
+ | `label` | `string` |
341
+ | `checked` | `boolean` (controlled) |
342
+ | `defaultChecked` | `boolean` (uncontrolled) |
343
+ | `onCheckedChange` | `(checked: boolean) => void` |
344
+ | `disabled` | `boolean` |
345
+
346
+ ---
347
+
348
+ ### Select
349
+
350
+ ```tsx
351
+ import { Select } from "@l3mpire/ui";
352
+
353
+ <Select
354
+ label="Country"
355
+ placeholder="Choose..."
356
+ size="md"
357
+ >
358
+ {/* DropdownMenu content inside */}
359
+ </Select>
360
+ ```
361
+
362
+ | Prop | Values |
363
+ |---|---|
364
+ | `size` | `"sm"`, `"md"` |
365
+ | `label` | `string` |
366
+ | `placeholder` | `string` |
367
+ | `value` | `string` |
368
+ | `error` | `boolean` |
369
+ | `errorMessage` | `string` |
370
+ | `icon` | `IconDefinition` |
371
+ | `isOpen` | `boolean` (controlled) |
372
+ | `onOpenChange` | `(open: boolean) => void` |
373
+ | `disabled` | `boolean` |
374
+
375
+ ---
376
+
377
+ ### DropdownMenu
378
+
379
+ Static/presentational dropdown list:
380
+
381
+ ```tsx
382
+ import {
383
+ DropdownMenu,
384
+ DropdownMenuList,
385
+ DropdownMenuItem,
386
+ DropdownMenuHeading,
387
+ DropdownMenuClear,
388
+ } from "@l3mpire/ui";
389
+
390
+ <DropdownMenu>
391
+ <DropdownMenuList>
392
+ <DropdownMenuHeading>Section</DropdownMenuHeading>
393
+ <DropdownMenuItem icon={faGearSolid} label="Settings" />
394
+ <DropdownMenuItem label="Profile" selected />
395
+ <DropdownMenuClear label="Clear all" />
396
+ </DropdownMenuList>
397
+ </DropdownMenu>
398
+ ```
399
+
400
+ Interactive Radix-powered dropdown:
401
+
402
+ ```tsx
403
+ import {
404
+ DropdownMenuRoot,
405
+ DropdownMenuTrigger,
406
+ DropdownMenuContent,
407
+ DropdownMenuRadixItem,
408
+ } from "@l3mpire/ui";
409
+
410
+ <DropdownMenuRoot>
411
+ <DropdownMenuTrigger asChild>
412
+ <Button>Open</Button>
413
+ </DropdownMenuTrigger>
414
+ <DropdownMenuContent>
415
+ <DropdownMenuRadixItem label="Option 1" />
416
+ <DropdownMenuRadixItem label="Option 2" />
417
+ </DropdownMenuContent>
418
+ </DropdownMenuRoot>
419
+ ```
420
+
421
+ ---
422
+
423
+ ### Tooltip
424
+
425
+ ```tsx
426
+ import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "@l3mpire/ui";
427
+
428
+ <TooltipProvider>
429
+ <Tooltip>
430
+ <TooltipTrigger asChild>
431
+ <Button>Hover me</Button>
432
+ </TooltipTrigger>
433
+ <TooltipContent type="default" side="top">
434
+ Tooltip text
435
+ </TooltipContent>
436
+ </Tooltip>
437
+ </TooltipProvider>
438
+ ```
439
+
440
+ | Prop (TooltipContent) | Values |
441
+ |---|---|
442
+ | `type` | `"default"`, `"invert"` |
443
+ | `side` | `"top"`, `"right"`, `"bottom"`, `"left"` |
444
+ | `hasArrow` | `boolean` (default: true) |
445
+
446
+ ---
447
+
448
+ ### Toast
449
+
450
+ ```tsx
451
+ import { Toast } from "@l3mpire/ui";
452
+
453
+ <Toast type="success" title="Saved!" subtitle="Your changes have been saved." />
454
+ <Toast type="alert" title="Error" subtitle="Something went wrong." onClose={() => {}} />
455
+ ```
456
+
457
+ | Prop | Values |
458
+ |---|---|
459
+ | `type` | `"info"`, `"success"`, `"alert"`, `"warning"` |
460
+ | `title` | `React.ReactNode` (required) |
461
+ | `subtitle` | `React.ReactNode` |
462
+ | `onClose` | `() => void` (shows close button when provided) |
463
+ | `actions` | `React.ReactNode` (action buttons) |
464
+
465
+ ---
466
+
467
+ ### InfoMessage
468
+
469
+ ```tsx
470
+ import { InfoMessage } from "@l3mpire/ui";
471
+
472
+ <InfoMessage type="info" title="Heads up" description="This is informational." />
473
+ <InfoMessage type="warning" title="Warning" description="Check your settings." onClose={() => {}} />
474
+ <InfoMessage
475
+ type="success"
476
+ title="Done!"
477
+ description="Your account was created."
478
+ linkLabel="View dashboard"
479
+ linkHref="/dashboard"
480
+ />
481
+ ```
482
+
483
+ | Prop | Values |
484
+ |---|---|
485
+ | `type` | `"info"`, `"success"`, `"alert"`, `"warning"`, `"empty"` |
486
+ | `title` | `React.ReactNode` (required) |
487
+ | `description` | `React.ReactNode` |
488
+ | `onClose` | `() => void` (shows close button when provided) |
489
+ | `action` | `React.ReactNode` (e.g., a Button) |
490
+ | `linkLabel` | `string` (shows a semantically-colored link) |
491
+ | `linkHref` | `string` |
492
+ | `onLinkClick` | `() => void` |
493
+
494
+ ---
495
+
496
+ ### Avatar
497
+
498
+ ```tsx
499
+ import { Avatar } from "@l3mpire/ui";
500
+
501
+ <Avatar size="md" src="https://example.com/photo.jpg" initials="AD" />
502
+ <Avatar size="sm" initials="PY" />
503
+ ```
504
+
505
+ | Prop | Values |
506
+ |---|---|
507
+ | `size` | `"sm"`, `"md"`, `"lg"`, `"xl"` |
508
+ | `shape` | `"rounded"`, `"squared"` |
509
+ | `src` | `string` (image URL) |
510
+ | `alt` | `string` |
511
+ | `initials` | `string` (fallback when no image, default: "??") |
512
+ | `badge` | `React.ReactNode` (only visible at `xl` size) |
513
+
514
+ ---
515
+
516
+ ### SearchBar
517
+
518
+ ```tsx
519
+ import { SearchBar } from "@l3mpire/ui";
520
+
521
+ <SearchBar placeholder="Search for anything" hasShortcut />
522
+ <SearchBar variant="grey" size="sm" onClear={() => {}} />
523
+ ```
524
+
525
+ | Prop | Values |
526
+ |---|---|
527
+ | `variant` | `"white"`, `"grey"` |
528
+ | `size` | `"sm"`, `"md"` |
529
+ | `hasShortcut` | `boolean` (shows ⌘K badge) |
530
+ | `placeholder` | `string` |
531
+ | `onClear` | `() => void` (shows clear button when provided) |
532
+
533
+ ---
534
+
535
+ ### InputLabel
536
+
537
+ ```tsx
538
+ import { InputLabel } from "@l3mpire/ui";
539
+
540
+ <InputLabel label="Email" />
541
+ <InputLabel label="Bio" type="optional" />
542
+ <InputLabel label="Name" type="info" infoTooltip="Your full name" />
543
+ ```
544
+
545
+ | Prop | Values |
546
+ |---|---|
547
+ | `type` | `"default"`, `"optional"`, `"mandatory"`, `"info"` |
548
+ | `disabled` | `boolean` |
549
+ | `infoIcon` | `IconDefinition` |
550
+
551
+ ---
552
+
553
+ ### Sidebar
554
+
555
+ ```tsx
556
+ import {
557
+ Sidebar,
558
+ SidebarSection,
559
+ SidebarFooter,
560
+ SidebarHeadingItem,
561
+ SidebarItem,
562
+ } from "@l3mpire/ui";
563
+ import { faHomeOutline, faHomeSolid } from "@l3mpire/icons";
564
+
565
+ <Sidebar isCollapsed={false}>
566
+ <SidebarItem icon={faHomeOutline} solidIcon={faHomeSolid} label="Home" />
567
+
568
+ <SidebarSection>
569
+ <SidebarHeadingItem label="Section Title" />
570
+ <SidebarItem icon={faGearOutline} solidIcon={faGearSolid} label="Settings" />
571
+ </SidebarSection>
572
+
573
+ <SidebarFooter>
574
+ <SidebarItem icon={faUserOutline} solidIcon={faUserSolid} label="Profile" />
575
+ </SidebarFooter>
576
+ </Sidebar>
577
+ ```
578
+
579
+ `SidebarItem` auto-detects collapsed state from `Sidebar` context. Icons switch from outline to solid on hover and when `state="active"`.
580
+
581
+ | Prop (SidebarItem) | Values |
582
+ |---|---|
583
+ | `icon` | `IconDefinition` (outline variant) |
584
+ | `solidIcon` | `IconDefinition` (solid variant) |
585
+ | `label` | `string` |
586
+ | `state` | `"default"`, `"hover"`, `"active"` |
587
+ | `hasBadge` | `boolean` |
588
+ | `badgeCount` | `string` |
589
+ | `hasSubItems` | `boolean` |
590
+ | `isNew` | `boolean` |
591
+
592
+ ---
593
+
594
+ ### Tabs
595
+
596
+ ```tsx
597
+ import { Tabs, TabList, TabTrigger, TabContent } from "@l3mpire/ui";
598
+
599
+ <Tabs defaultValue="tab1">
600
+ <TabList>
601
+ <TabTrigger value="tab1">Tab 1</TabTrigger>
602
+ <TabTrigger value="tab2" badge="3">Tab 2</TabTrigger>
603
+ </TabList>
604
+ <TabContent value="tab1">Content 1</TabContent>
605
+ <TabContent value="tab2">Content 2</TabContent>
606
+ </Tabs>
607
+ ```
608
+
609
+ | Prop (TabTrigger) | Values |
610
+ |---|---|
611
+ | `value` | `string` (required) |
612
+ | `badge` | `string` (count badge) |
613
+ | `disabled` | `boolean` |
614
+
615
+ | Prop (TabList) | Values |
616
+ |---|---|
617
+ | `hasOffset` | `boolean` (adds left padding) |
618
+
619
+ ---
620
+
621
+ ### BrowserTab
622
+
623
+ ```tsx
624
+ import { BrowserTab, BrowserTabItem } from "@l3mpire/ui";
625
+
626
+ <BrowserTab activeTabId="1" onTabSelect={(id) => {}}>
627
+ <BrowserTabItem id="1" label="Page 1" onClose={(id) => {}} />
628
+ <BrowserTabItem id="2" label="Page 2" onClose={(id) => {}} />
629
+ </BrowserTab>
630
+ ```
631
+
632
+ | Prop (BrowserTab) | Values |
633
+ |---|---|
634
+ | `activeTabId` | `string` |
635
+ | `onTabSelect` | `(id: string) => void` |
636
+ | `onNewTab` | `() => void` (shows + button) |
637
+ | `onReorder` | `(ids: string[]) => void` |
638
+
639
+ | Prop (BrowserTabItem) | Values |
640
+ |---|---|
641
+ | `id` | `string` (required) |
642
+ | `label` | `string` |
643
+ | `icon` | `IconDefinition` |
644
+ | `onClose` | `(id: string) => void` |
645
+
646
+ ---
647
+
648
+ ### Modal
649
+
650
+ ```tsx
651
+ import {
652
+ Modal, ModalTrigger, ModalContent,
653
+ ModalHeader, ModalTitle, ModalDescription,
654
+ ModalBody, ModalFooter, ModalClose,
655
+ Button,
656
+ } from "@l3mpire/ui";
657
+
658
+ <Modal>
659
+ <ModalTrigger asChild>
660
+ <Button appearance="solid" intent="brand" size="md">Open</Button>
661
+ </ModalTrigger>
662
+ <ModalContent size="md">
663
+ <ModalHeader onClose={() => {}}>
664
+ <ModalTitle>Title</ModalTitle>
665
+ <ModalDescription>Optional description</ModalDescription>
666
+ </ModalHeader>
667
+ <ModalBody>Content goes here</ModalBody>
668
+ <ModalFooter>
669
+ <ModalClose asChild>
670
+ <Button appearance="ghost" intent="brand" size="md">Cancel</Button>
671
+ </ModalClose>
672
+ <Button appearance="solid" intent="brand" size="md">Confirm</Button>
673
+ </ModalFooter>
674
+ </ModalContent>
675
+ </Modal>
676
+ ```
677
+
678
+ | Prop (ModalContent) | Values |
679
+ |---|---|
680
+ | `size` | `"sm"` (500px), `"md"` (800px), `"lg"` (1100px), `"full"` (100% − 32px) |
681
+
682
+ | Prop (ModalHeader) | Values |
683
+ |---|---|
684
+ | `showBorder` | `boolean` (default: true) |
685
+ | `onClose` | `() => void` (shows close button when provided) |
686
+
687
+ | Prop (ModalFooter) | Values |
688
+ |---|---|
689
+ | `showBorder` | `boolean` (default: true) |
690
+ | `infoMessage` | `string` (shows info icon + text on the left) |
691
+
692
+ **Sub-components:** `Modal`, `ModalTrigger`, `ModalContent`, `ModalHeader`, `ModalTitle`, `ModalDescription`, `ModalBody`, `ModalFooter`, `ModalClose`
693
+
694
+ ---
695
+
696
+ ### Dialog
697
+
698
+ Pre-composed confirmation dialog (Modal SM without header border). Supports `brand` and `alert` intents.
699
+
700
+ ```tsx
701
+ import { Dialog, Button } from "@l3mpire/ui";
702
+
703
+ const [open, setOpen] = React.useState(false);
704
+
705
+ <Button onClick={() => setOpen(true)}>Delete</Button>
706
+
707
+ <Dialog
708
+ open={open}
709
+ onOpenChange={setOpen}
710
+ title="Delete Item"
711
+ description="This action cannot be undone."
712
+ intent="alert"
713
+ primaryLabel="Delete"
714
+ cancelLabel="Cancel"
715
+ onPrimaryAction={() => { deleteItem(); setOpen(false); }}
716
+ />
717
+ ```
718
+
719
+ | Prop | Values |
720
+ |---|---|
721
+ | `open` | `boolean` (controlled) |
722
+ | `onOpenChange` | `(open: boolean) => void` |
723
+ | `title` | `string` (required) |
724
+ | `description` | `string` |
725
+ | `intent` | `"brand"`, `"alert"` |
726
+ | `primaryLabel` | `string` (default: "Confirm") |
727
+ | `onPrimaryAction` | `() => void` |
728
+ | `primaryDisabled` | `boolean` |
729
+ | `cancelLabel` | `string` (default: "Cancel") |
730
+ | `onCancel` | `() => void` |
731
+ | `secondaryLabel` | `string` (shows third button) |
732
+ | `onSecondaryAction` | `() => void` |
733
+
734
+ ---
735
+
736
+ ### SidePanel
737
+
738
+ Full-height panel that slides in from the right. Reuses `ModalHeader`, `ModalBody`, `ModalFooter` for composition.
739
+
740
+ ```tsx
741
+ import {
742
+ SidePanel, SidePanelTrigger, SidePanelContent,
743
+ ModalHeader, ModalTitle, ModalDescription,
744
+ ModalBody, ModalFooter, ModalClose,
745
+ Button,
746
+ } from "@l3mpire/ui";
747
+
748
+ <SidePanel>
749
+ <SidePanelTrigger asChild>
750
+ <Button appearance="solid" intent="brand" size="md">Open</Button>
751
+ </SidePanelTrigger>
752
+ <SidePanelContent>
753
+ <ModalHeader onClose={() => {}}>
754
+ <ModalTitle>Title</ModalTitle>
755
+ <ModalDescription>Description</ModalDescription>
756
+ </ModalHeader>
757
+ <ModalBody>Content</ModalBody>
758
+ <ModalFooter>
759
+ <ModalClose asChild>
760
+ <Button appearance="ghost" intent="brand" size="md">Cancel</Button>
761
+ </ModalClose>
762
+ <Button appearance="solid" intent="brand" size="md">Save</Button>
763
+ </ModalFooter>
764
+ </SidePanelContent>
765
+ </SidePanel>
766
+ ```
767
+
768
+ | Prop (SidePanelContent) | Values |
769
+ |---|---|
770
+ | `overlay` | `boolean` (default: true — shows dimmed overlay) |
771
+ | `className` | Override positioning (e.g., `"absolute inset-y-0 right-0"` for container-relative) |
772
+
773
+ Header and footer are optional — compose with `ModalHeader`, `ModalBody`, `ModalFooter` as needed.
774
+
775
+ ---
776
+
777
+ ### UserMenu
778
+
779
+ ```tsx
780
+ import { UserMenu, UserMenuInfoRow, UserMenuSection } from "@l3mpire/ui";
781
+
782
+ <UserMenu>
783
+ <UserMenuInfoRow
784
+ name="John Doe"
785
+ email="john@example.com"
786
+ avatarSrc="https://..."
787
+ />
788
+ <UserMenuSection>
789
+ <DropdownMenuItem icon={faGearSolid} label="Settings" />
790
+ <DropdownMenuItem icon={faArrowRightSolid} label="Sign out" />
791
+ </UserMenuSection>
792
+ </UserMenu>
793
+ ```
794
+
795
+ ---
796
+
797
+ ### ProductLogo
798
+
799
+ Renders product logos for lemlist, lemwarm, and lemcal. Supports icon-only or full logotype (icon + text) modes. Automatically switches between light and dark SVGs based on `data-theme`.
800
+
801
+ ```tsx
802
+ import { ProductLogo } from "@l3mpire/ui";
803
+
804
+ // Full logotype (icon + text)
805
+ <ProductLogo product="lemlist" size="lg" hasText />
806
+
807
+ // Icon only
808
+ <ProductLogo product="lemcal" size="md" hasText={false} />
809
+ ```
810
+
811
+ | Prop | Values |
812
+ |---|---|
813
+ | `product` | `"lemlist"`, `"lemwarm"`, `"lemcal"` |
814
+ | `size` | `"sm"` (20px), `"md"` (28px), `"lg"` (32px), `"xl"` (40px, icon-only) |
815
+ | `hasText` | `boolean` — `true` = logotype, `false` = icon only |
816
+
817
+ **Notes:**
818
+ - `xl` size is icon-only regardless of `hasText`
819
+ - Automatically detects dark mode via `data-theme="dark"` attribute
820
+
821
+ ---
822
+
823
+ ### Table (Low-Level)
824
+
825
+ ```tsx
826
+ import {
827
+ Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
828
+ } from "@l3mpire/ui";
829
+
830
+ <Table>
831
+ <TableHeader>
832
+ <TableRow>
833
+ <TableHead>Name</TableHead>
834
+ <TableHead>Email</TableHead>
835
+ </TableRow>
836
+ </TableHeader>
837
+ <TableBody>
838
+ <TableRow>
839
+ <TableCell>John</TableCell>
840
+ <TableCell>john@example.com</TableCell>
841
+ </TableRow>
842
+ </TableBody>
843
+ </Table>
844
+ ```
845
+
846
+ ---
847
+
848
+ ### DataTable
849
+
850
+ Data-driven table powered by TanStack Table. Wraps the low-level `Table` primitives with opt-in features.
851
+
852
+ ```tsx
853
+ import { DataTable, DataTablePagination, type ColumnDef } from "@l3mpire/ui";
854
+
855
+ const columns: ColumnDef<Person>[] = [
856
+ { accessorKey: "name", header: "Name" },
857
+ { accessorKey: "email", header: "Email" },
858
+ ];
859
+
860
+ <DataTable
861
+ columns={columns}
862
+ data={people}
863
+ enableSorting
864
+ enablePagination
865
+ enableColumnResizing
866
+ bordered
867
+ >
868
+ {(table) => <DataTablePagination table={table} />}
869
+ </DataTable>
870
+ ```
871
+
872
+ | Prop | Default | Description |
873
+ |---|---|---|
874
+ | `columns` | *required* | TanStack Table column definitions |
875
+ | `data` | *required* | Array of row data |
876
+ | `enableSorting` | `false` | Client-side column sorting |
877
+ | `enableFiltering` | `false` | Client-side column filtering with popover |
878
+ | `enablePagination` | `false` | Client-side pagination |
879
+ | `enableRowSelection` | `false` | Row selection via checkbox |
880
+ | `enableColumnVisibility` | `false` | Toggle column visibility |
881
+ | `enableColumnPinning` | `false` | Pin columns left/right |
882
+ | `enableColumnResizing` | `false` | Drag to resize columns |
883
+ | `enableColumnDrag` | `false` | Drag to reorder columns |
884
+ | `bordered` | `false` | Border-top on header (full-width layouts) |
885
+ | `emptyState` | — | `ReactNode` shown when `data` is empty |
886
+ | `sorting` | — | Controlled sorting state |
887
+ | `onSortingChange` | — | Controlled sorting callback |
888
+ | `columnFilters` | — | Controlled filter state |
889
+ | `onColumnFiltersChange` | — | Controlled filter callback |
890
+ | `rowSelection` | — | Controlled selection state |
891
+ | `onRowSelectionChange` | — | Controlled selection callback |
892
+ | `children` | — | Render prop `(table) => ReactNode` for pagination |
893
+
894
+ #### Column Meta
895
+
896
+ Use `meta` on column definitions for extended features:
897
+
898
+ ```tsx
899
+ const columns: ColumnDef<Item>[] = [
900
+ {
901
+ accessorKey: "name",
902
+ header: "Name",
903
+ meta: {
904
+ icon: faUserOutline, // Leading icon in header
905
+ enableDrag: true, // Draggable (requires enableColumnDrag)
906
+ filterType: "string", // Filter popover type: "string" | "number" | "date" | "select"
907
+ filterOptions: ["A", "B"], // Options for "select" filter type
908
+ },
909
+ },
910
+ ];
911
+ ```
912
+
913
+ #### Cell Renderers
914
+
915
+ Pre-built cell components for common patterns:
916
+
917
+ ```tsx
918
+ import {
919
+ AvatarCell, StatusCell, NumberCell, DateCell,
920
+ EmailCell, LinkCell, ButtonCell, EditableCell, RowActions,
921
+ } from "@l3mpire/ui";
922
+
923
+ // In column definitions:
924
+ { cell: ({ row }) => <AvatarCell name={row.original.name} src={row.original.avatar} /> }
925
+ { cell: ({ row }) => <StatusCell status="active" /> }
926
+ { cell: ({ row }) => <NumberCell value={row.original.amount} /> }
927
+ { cell: ({ row }) => <DateCell value={row.original.date} /> }
928
+ { cell: ({ row }) => <EmailCell value={row.original.email} /> }
929
+ { cell: ({ row }) => <EditableCell value={row.original.name} onSave={(v) => {}} /> }
930
+ { cell: ({ row }) => <RowActions actions={[{ label: "Edit", onClick: () => {} }]} /> }
931
+ ```
932
+
933
+ ---
934
+
935
+ ### EmptyState
936
+
937
+ ```tsx
938
+ import { EmptyState } from "@l3mpire/ui";
939
+ import { faUsersSolid } from "@l3mpire/icons";
940
+
941
+ <EmptyState
942
+ icon={faUsersSolid}
943
+ title="No results"
944
+ description="Try a different search query."
945
+ size="md"
946
+ />
947
+
948
+ <EmptyState
949
+ icon={faUsersSolid}
950
+ title="No contacts yet"
951
+ size="lg"
952
+ action={<Button appearance="solid" intent="brand" size="md">Add contact</Button>}
953
+ />
954
+ ```
955
+
956
+ | Prop | Values |
957
+ |---|---|
958
+ | `icon` | `IconDefinition` |
959
+ | `title` | `string` (required) |
960
+ | `description` | `string` |
961
+ | `size` | `"sm"`, `"md"`, `"lg"` |
962
+ | `action` | `ReactNode` (e.g., a Button) |
963
+
964
+ ---
965
+
966
+ ## Size Availability
967
+
968
+ | Component | sm | md | lg | xl |
969
+ |---|---|---|---|---|
970
+ | Button | x | x | x | |
971
+ | Badge | x | x | x | |
972
+ | Tag | x | x | | |
973
+ | Link | x | x | | |
974
+ | TextInput | x | x | | |
975
+ | Select | x | x | | |
976
+ | SearchBar | x | x | | |
977
+ | Avatar | x | x | x | x |
978
+ | Modal | x | x | x | full |
979
+ | EmptyState | x | x | x | |
980
+
981
+ ---
982
+
983
+ ## Color Token Patterns
984
+
985
+ Component colors follow this convention:
986
+
987
+ ```
988
+ Token CSS var: --comp-{component}-{variant}-{property}
989
+ Theme class: {component}-{variant}-{property} (comp- prefix dropped)
990
+ Tailwind usage: bg-{component}-{variant}-{property}
991
+ text-{component}-{variant}-{property}
992
+ border-{component}-{variant}-{property}
993
+ ```
994
+
995
+ Examples:
996
+ - `bg-badge-solid-primary-bg` → badge solid primary background
997
+ - `text-toast-title` → toast title text color
998
+ - `border-text-input-border-default` → text input default border
999
+
1000
+ ---
1001
+
1002
+ ## Utility: `cn()`
1003
+
1004
+ Merge Tailwind classes safely (handles conflicts):
1005
+
1006
+ ```tsx
1007
+ import { cn } from "@l3mpire/ui";
1008
+
1009
+ <div className={cn("p-base text-sm", isActive && "bg-primary", className)} />
1010
+ ```
1011
+
1012
+ ---
1013
+
1014
+ ## Build Order
1015
+
1016
+ ```
1017
+ tokens → icons → ui → storybook
1018
+ ```
1019
+
1020
+ ```bash
1021
+ pnpm --filter @l3mpire/tokens build
1022
+ pnpm --filter @l3mpire/icons build
1023
+ pnpm --filter @l3mpire/ui build
1024
+ pnpm --filter @l3mpire/storybook dev # launches Storybook
1025
+ ```