@buillaume.biondo/fab-ui 0.1.0 → 0.1.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/README.md ADDED
@@ -0,0 +1,2149 @@
1
+ # fab-ui
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@buillaume.biondo/fab-ui)](https://www.npmjs.com/package/@buillaume.biondo/fab-ui)
4
+ [![license](https://img.shields.io/npm/l/@buillaume.biondo/fab-ui)](./LICENSE)
5
+
6
+ A Vue 3 component library built on top of [reka-ui](https://reka-ui.com/) headless primitives, styled with [Tailwind CSS v4](https://tailwindcss.com/) and [class-variance-authority](https://cva.style/). It provides a complete, accessible, and themeable design system — including layout components, form controls, overlays, data display, and navigation — ready to drop into any Vue 3 + Tailwind v4 application.
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ - [Prerequisites](#prerequisites)
13
+ - [Installation](#installation)
14
+ - [Configuration](#configuration)
15
+ - [Design System](#design-system)
16
+ - [Components](#components)
17
+ - [Button](#button)
18
+ - [Badge](#badge)
19
+ - [OverlayBadge](#overlaybadge)
20
+ - [Alert](#alert)
21
+ - [Avatar](#avatar)
22
+ - [Card](#card)
23
+ - [Checkbox](#checkbox)
24
+ - [Radio / RadioGroup](#radio--radiogroup)
25
+ - [Label](#label)
26
+ - [Input](#input)
27
+ - [InputPassword](#inputpassword)
28
+ - [InputNumber](#inputnumber)
29
+ - [InputArea](#inputarea)
30
+ - [InputOTP](#inputotp)
31
+ - [Select](#select)
32
+ - [Slider](#slider)
33
+ - [Toggle](#toggle)
34
+ - [ToggleSwitch](#toggleswitch)
35
+ - [ProgressBar](#progressbar)
36
+ - [Skeleton](#skeleton)
37
+ - [Separator](#separator)
38
+ - [Spinner](#spinner)
39
+ - [Code](#code)
40
+ - [Image](#image)
41
+ - [Carousel](#carousel)
42
+ - [DataTable](#datatable)
43
+ - [Toast / Toaster](#toast--toaster)
44
+ - [SpeedDial](#speeddial)
45
+ - [SplitButton](#splitbutton)
46
+ - [Tooltip](#tooltip)
47
+ - [Collapsible](#collapsible)
48
+ - [DropdownMenu](#dropdownmenu)
49
+ - [ContextMenu](#contextmenu)
50
+ - [Dialog](#dialog)
51
+ - [Sheet](#sheet)
52
+ - [Breadcrumb](#breadcrumb)
53
+ - [NavigationMenu](#navigationmenu)
54
+ - [Sidebar](#sidebar)
55
+ - [Tabs](#tabs)
56
+ - [Composables](#composables)
57
+ - [useToast](#usetoast)
58
+ - [useAppearance](#useappearance)
59
+ - [Roadmap](#roadmap)
60
+ - [License](#license)
61
+
62
+ ---
63
+
64
+ ## Prerequisites
65
+
66
+ | Dependency | Version |
67
+ |---|---|
68
+ | `vue` | `^3.5` |
69
+ | `tailwindcss` | `^4.0` |
70
+ | `reka-ui` | `^2.0` |
71
+ | `lucide-vue-next` | `>=0.400` |
72
+ | `class-variance-authority` | `^0.7` |
73
+ | `@vueuse/core` | `^12.0` |
74
+ | `vue-input-otp` | `^0.3` (optional, for `InputOTP`) |
75
+
76
+ ---
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ npm install @buillaume.biondo/fab-ui
82
+ ```
83
+
84
+ Install the required peer dependencies if not already present:
85
+
86
+ ```bash
87
+ npm install vue reka-ui lucide-vue-next class-variance-authority @vueuse/core tailwindcss
88
+ ```
89
+
90
+ For the `InputOTP` component (optional):
91
+
92
+ ```bash
93
+ npm install vue-input-otp
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Configuration
99
+
100
+ ### CSS
101
+
102
+ In your main CSS file (e.g. `src/assets/app.css`), import Tailwind and the library styles:
103
+
104
+ ```css
105
+ @import 'tailwindcss';
106
+ @import '@buillaume.biondo/fab-ui/style';
107
+
108
+ /* Optional: Marianne font (French government design system) */
109
+ /* @import '@buillaume.biondo/fab-ui/style/marianne'; */
110
+ ```
111
+
112
+ ### Fonts
113
+
114
+ Two fonts are loaded automatically via Google Fonts when you import the library stylesheet:
115
+
116
+ - **DM Sans** — body text and UI
117
+ - **JetBrains Mono** — code blocks and monospaced metrics
118
+
119
+ An optional **Marianne** font (used by the French government design system) is available as local `woff`/`woff2` files in `node_modules/@buillaume.biondo/fab-ui/fonts/marianne/`. Uncomment the import above to activate it.
120
+
121
+ ### Toaster
122
+
123
+ Mount the `Toaster` component once in your root layout to enable toast notifications throughout the application:
124
+
125
+ ```vue
126
+ <script setup>
127
+ import { Toaster } from '@buillaume.biondo/fab-ui'
128
+ </script>
129
+
130
+ <template>
131
+ <Toaster />
132
+ </template>
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Design System
138
+
139
+ ### Severity (color variants)
140
+
141
+ All components that accept a `variant` prop use the following semantic severity scale:
142
+
143
+ | Value | Meaning |
144
+ |---|---|
145
+ | `primary` | Brand action color |
146
+ | `secondary` | Neutral / subdued |
147
+ | `success` | Positive feedback |
148
+ | `danger` | Destructive / error |
149
+ | `info` | Informational |
150
+ | `warning` | Caution |
151
+ | `help` | Guidance / purple |
152
+ | `contrast` | High contrast / dark |
153
+
154
+ ### Size scale
155
+
156
+ Most components accept a `size` prop with the following values (smallest to largest):
157
+
158
+ `tiny` | `small` | `default` | `large` | `extra`
159
+
160
+ ### CSS custom properties
161
+
162
+ The library uses semantic CSS variables. You can override any of them in your CSS to customize the theme:
163
+
164
+ ```css
165
+ :root {
166
+ --color-action: #2563eb;
167
+ --color-brand: #1d4ed8;
168
+ --color-bg: #ffffff;
169
+ --color-bg-subtle: #f8fafc;
170
+ --color-bg-raised: #ffffff;
171
+ --color-text: #0f172a;
172
+ --color-text-muted: #64748b;
173
+ --color-text-disabled: #94a3b8;
174
+ --color-border: #e2e8f0;
175
+ --color-border-strong: #cbd5e1;
176
+ --color-success: #16a34a;
177
+ --color-danger: #dc2626;
178
+ --color-warning: #d97706;
179
+ --color-info: #2563eb;
180
+ --color-help: #7c3aed;
181
+ --color-contrast: #1f2937;
182
+ }
183
+ ```
184
+
185
+ ### Dark mode
186
+
187
+ Add the `.dark` class to your `<html>` element to activate dark mode:
188
+
189
+ ```html
190
+ <html class="dark">
191
+ ```
192
+
193
+ ### Square radius mode
194
+
195
+ Add `data-radius="square"` to your `<html>` element to remove all border radii:
196
+
197
+ ```html
198
+ <html data-radius="square">
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Components
204
+
205
+ ### Button
206
+
207
+ A versatile button component with 8 severity variants, 4 shapes, 5 sizes, and optional loading, icon, and badge states. Renders as a `<button>` by default but can be changed via the `as` prop (e.g. `as="a"` for links).
208
+
209
+ **Props**
210
+
211
+ | Prop | Type | Default | Description |
212
+ |---|---|---|---|
213
+ | `variant` | `Severity` | `'primary'` | Color variant |
214
+ | `shape` | `'plain' \| 'outline' \| 'ghost' \| 'link'` | `'plain'` | Visual style |
215
+ | `size` | `Size` | `'default'` | Button size |
216
+ | `fluid` | `boolean` | `false` | Full-width |
217
+ | `rounded` | `boolean` | `false` | Fully rounded (pill) |
218
+ | `icon` | `boolean` | `false` | Icon-only mode (square / circle) |
219
+ | `loading` | `boolean` | `false` | Shows a spinner, disables interaction |
220
+ | `disabled` | `boolean` | `false` | Disables the button |
221
+ | `badge` | `number \| null` | `null` | Numeric overlay badge (top-right) |
222
+ | `iconLeft` | `Component` | — | Icon component rendered before the label |
223
+ | `iconRight` | `Component` | — | Icon component rendered after the label |
224
+ | `as` | `string \| Component` | `'button'` | Rendered HTML element or component |
225
+ | `class` | `string` | — | Additional CSS classes |
226
+
227
+ ```vue
228
+ <script setup>
229
+ import { Button } from '@buillaume.biondo/fab-ui'
230
+ import { PlusIcon, TrashIcon } from 'lucide-vue-next'
231
+ </script>
232
+
233
+ <template>
234
+ <!-- Basic -->
235
+ <Button>Save</Button>
236
+
237
+ <!-- Variants and shapes -->
238
+ <Button variant="danger" shape="outline">Delete</Button>
239
+ <Button variant="success" shape="ghost">Confirm</Button>
240
+
241
+ <!-- With icons -->
242
+ <Button :icon-left="PlusIcon">Add item</Button>
243
+ <Button :icon-right="TrashIcon" variant="danger" shape="outline">Remove</Button>
244
+
245
+ <!-- Icon-only -->
246
+ <Button :icon="true" variant="secondary" shape="ghost">
247
+ <PlusIcon />
248
+ </Button>
249
+
250
+ <!-- Loading state -->
251
+ <Button :loading="isSaving">Saving…</Button>
252
+
253
+ <!-- With badge -->
254
+ <Button :badge="5">Notifications</Button>
255
+
256
+ <!-- As a link -->
257
+ <Button as="a" href="/dashboard" variant="primary" shape="link">Go to dashboard</Button>
258
+ </template>
259
+ ```
260
+
261
+ ---
262
+
263
+ ### Badge
264
+
265
+ Inline label component for statuses, tags, and counts. Supports 8 severity variants × 3 shapes, with an optional leading dot.
266
+
267
+ **Props**
268
+
269
+ | Prop | Type | Default | Description |
270
+ |---|---|---|---|
271
+ | `variant` | `Severity` | `'primary'` | Color variant |
272
+ | `shape` | `'filled' \| 'outline' \| 'ghost'` | `'filled'` | Visual style |
273
+ | `size` | `Size` | `'default'` | Badge size |
274
+ | `fluid` | `boolean` | `false` | Full-width with centered text |
275
+ | `dot` | `boolean` | `false` | Prepends a colored dot |
276
+ | `class` | `string` | — | Additional CSS classes |
277
+
278
+ ```vue
279
+ <script setup>
280
+ import { Badge } from '@buillaume.biondo/fab-ui'
281
+ </script>
282
+
283
+ <template>
284
+ <Badge>Primary</Badge>
285
+ <Badge variant="success">Active</Badge>
286
+ <Badge variant="danger" shape="outline">Error</Badge>
287
+ <Badge variant="warning" shape="ghost" dot>Pending</Badge>
288
+ <Badge variant="info" size="small">3 new</Badge>
289
+ </template>
290
+ ```
291
+
292
+ ---
293
+
294
+ ### OverlayBadge
295
+
296
+ Wrapper that positions a badge (dot or numeric) as an absolute overlay on top of any element — ideal for notification counts on icons or avatars.
297
+
298
+ **Props**
299
+
300
+ | Prop | Type | Default | Description |
301
+ |---|---|---|---|
302
+ | `value` | `number \| null` | — | Numeric value. `0` or absent renders a dot; `null` hides the badge |
303
+ | `variant` | `Severity` | `'danger'` | Color variant |
304
+ | `position` | `'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left'` | `'top-right'` | Badge position |
305
+ | `class` | `string` | — | Additional CSS classes on the wrapper |
306
+
307
+ ```vue
308
+ <script setup>
309
+ import { OverlayBadge, Button } from '@buillaume.biondo/fab-ui'
310
+ import { BellIcon } from 'lucide-vue-next'
311
+ </script>
312
+
313
+ <template>
314
+ <!-- Numeric badge -->
315
+ <OverlayBadge :value="12">
316
+ <Button :icon="true" shape="ghost" variant="secondary">
317
+ <BellIcon />
318
+ </Button>
319
+ </OverlayBadge>
320
+
321
+ <!-- Dot badge -->
322
+ <OverlayBadge :value="0" variant="success">
323
+ <Button :icon="true" shape="ghost" variant="secondary">
324
+ <BellIcon />
325
+ </Button>
326
+ </OverlayBadge>
327
+ </template>
328
+ ```
329
+
330
+ ---
331
+
332
+ ### Alert
333
+
334
+ Dismissible alert banner with icon, title, body slot, and optional auto-close. Animated with a slide + fade transition.
335
+
336
+ **Props**
337
+
338
+ | Prop | Type | Default | Description |
339
+ |---|---|---|---|
340
+ | `variant` | `Severity` | `'info'` | Color variant |
341
+ | `title` | `string` | — | Bold heading text |
342
+ | `dismissible` | `boolean` | `false` | Shows a close button |
343
+ | `icon` | `boolean` | `true` | Displays the default variant icon |
344
+ | `iconComponent` | `Component` | — | Replaces the default icon |
345
+ | `delay` | `number` | `0` | Auto-close delay in milliseconds (0 = never) |
346
+ | `class` | `string` | — | Additional CSS classes |
347
+
348
+ **Slots**
349
+
350
+ | Slot | Description |
351
+ |---|---|
352
+ | `default` | Alert body content |
353
+ | `#icon` | Custom icon replacing the default one |
354
+
355
+ **Emits**
356
+
357
+ | Event | Description |
358
+ |---|---|
359
+ | `close` | Fired when the alert is dismissed or auto-closes |
360
+
361
+ ```vue
362
+ <script setup>
363
+ import { Alert } from '@buillaume.biondo/fab-ui'
364
+ import { RocketIcon } from 'lucide-vue-next'
365
+ </script>
366
+
367
+ <template>
368
+ <Alert title="Heads up!">
369
+ Your trial expires in 3 days.
370
+ </Alert>
371
+
372
+ <Alert variant="success" :dismissible="true" title="Saved!">
373
+ Your changes have been saved successfully.
374
+ </Alert>
375
+
376
+ <Alert variant="danger" :icon-component="RocketIcon">
377
+ Deployment failed. Check the logs.
378
+ </Alert>
379
+
380
+ <!-- Auto-close after 5 seconds -->
381
+ <Alert variant="info" :delay="5000" @close="onClose">
382
+ This message will disappear automatically.
383
+ </Alert>
384
+ </template>
385
+ ```
386
+
387
+ ---
388
+
389
+ ### Avatar
390
+
391
+ Circular avatar container wrapping reka-ui's `AvatarRoot`. Use with `AvatarImage` and `AvatarFallback` from `reka-ui` for image with initials fallback.
392
+
393
+ **Props**
394
+
395
+ | Prop | Type | Default | Description |
396
+ |---|---|---|---|
397
+ | `size` | `'tiny' \| 'small' \| 'default' \| 'large' \| 'extra'` | `'default'` | Avatar size |
398
+ | `class` | `string` | — | Additional CSS classes |
399
+
400
+ ```vue
401
+ <script setup>
402
+ import { Avatar } from '@buillaume.biondo/fab-ui'
403
+ import { AvatarImage, AvatarFallback } from 'reka-ui'
404
+ </script>
405
+
406
+ <template>
407
+ <Avatar size="large">
408
+ <AvatarImage src="/avatars/john.jpg" alt="John Doe" />
409
+ <AvatarFallback>JD</AvatarFallback>
410
+ </Avatar>
411
+ </template>
412
+ ```
413
+
414
+ ---
415
+
416
+ ### Card
417
+
418
+ A flexible card container with optional border color variants, shadow, hover effects, and named slots for header and footer.
419
+
420
+ **Props**
421
+
422
+ | Prop | Type | Default | Description |
423
+ |---|---|---|---|
424
+ | `variant` | `'default' \| Severity` | `'default'` | Border color (9 options including `default`) |
425
+ | `padded` | `boolean` | `true` | Adds vertical padding and inner gap |
426
+ | `bordered` | `boolean` | `true` | Renders a border |
427
+ | `hoverable` | `boolean` | `false` | Elevates shadow on hover |
428
+ | `raised` | `boolean` | `false` | Uses a larger shadow by default |
429
+ | `class` | `string` | — | Additional CSS classes |
430
+
431
+ **Slots**
432
+
433
+ | Slot | Description |
434
+ |---|---|
435
+ | `#header` | Card header area |
436
+ | `default` | Card body |
437
+ | `#footer` | Card footer area |
438
+
439
+ **CardHeader props**
440
+
441
+ | Prop | Type | Default | Description |
442
+ |---|---|---|---|
443
+ | `flush` | `boolean` | `false` | Removes padding; clips content (e.g. cover images) to card corners |
444
+
445
+ ```vue
446
+ <script setup>
447
+ import { Card, CardHeader } from '@buillaume.biondo/fab-ui'
448
+ </script>
449
+
450
+ <template>
451
+ <Card variant="primary" :hoverable="true">
452
+ <template #header>
453
+ <CardHeader>
454
+ <h3 class="font-semibold text-lg">Card Title</h3>
455
+ <p class="text-sm text-muted">Card subtitle</p>
456
+ </CardHeader>
457
+ </template>
458
+
459
+ <p>Card body content goes here.</p>
460
+
461
+ <template #footer>
462
+ <div class="flex justify-end gap-2">
463
+ <Button variant="secondary" shape="outline">Cancel</Button>
464
+ <Button>Confirm</Button>
465
+ </div>
466
+ </template>
467
+ </Card>
468
+ </template>
469
+ ```
470
+
471
+ ---
472
+
473
+ ### Checkbox
474
+
475
+ Accessible checkbox built on reka-ui's `CheckboxRoot`. Fully styled with action color when checked and error state support.
476
+
477
+ **Props**
478
+
479
+ Accepts all props from reka-ui's `CheckboxRootProps` plus:
480
+
481
+ | Prop | Type | Description |
482
+ |---|---|---|
483
+ | `class` | `string` | Additional CSS classes |
484
+
485
+ ```vue
486
+ <script setup>
487
+ import { Checkbox, Label } from '@buillaume.biondo/fab-ui'
488
+ import { ref } from 'vue'
489
+
490
+ const checked = ref(false)
491
+ </script>
492
+
493
+ <template>
494
+ <div class="flex items-center gap-2">
495
+ <Checkbox id="terms" v-model:checked="checked" />
496
+ <Label for="terms">I agree to the terms</Label>
497
+ </div>
498
+ </template>
499
+ ```
500
+
501
+ ---
502
+
503
+ ### Radio / RadioGroup
504
+
505
+ Accessible radio button group built on reka-ui's `RadioGroupRoot` / `RadioGroupItem` primitives. `RadioGroup` is the container that manages the selection state; `Radio` is the individual item with an optional inline label.
506
+
507
+ #### RadioGroup
508
+
509
+ **Props**
510
+
511
+ Accepts all props from reka-ui's `RadioGroupRootProps` plus:
512
+
513
+ | Prop | Type | Default | Description |
514
+ |---|---|---|---|
515
+ | `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | Layout direction of the items |
516
+ | `modelValue` | `string` | — | Controlled selected value (`v-model`) |
517
+ | `defaultValue` | `string` | — | Initial selected value (uncontrolled) |
518
+ | `disabled` | `boolean` | `false` | Disables the entire group |
519
+ | `name` | `string` | — | Native `name` attribute forwarded to the hidden inputs |
520
+ | `class` | `string` | — | Additional CSS classes on the wrapper |
521
+
522
+ **Emits**
523
+
524
+ | Event | Payload | Description |
525
+ |---|---|---|
526
+ | `update:modelValue` | `string` | Fired when the selected value changes |
527
+
528
+ #### Radio
529
+
530
+ **Props**
531
+
532
+ Accepts all props from reka-ui's `RadioGroupItemProps` plus:
533
+
534
+ | Prop | Type | Default | Description |
535
+ |---|---|---|---|
536
+ | `value` | `string` | **required** | The value this item represents |
537
+ | `variant` | `Severity` | `'primary'` | Color of the checked indicator |
538
+ | `size` | `'small' \| 'default' \| 'large'` | `'default'` | Size of the circle and label text |
539
+ | `label` | `string` | — | Text rendered to the right of the button |
540
+ | `disabled` | `boolean` | `false` | Disables this individual item |
541
+ | `class` | `string` | — | Applied to the wrapper `<label>` |
542
+
543
+ ```vue
544
+ <script setup>
545
+ import { RadioGroup, Radio } from '@buillaume.biondo/fab-ui'
546
+ import { ref } from 'vue'
547
+
548
+ const plan = ref('pro')
549
+ </script>
550
+
551
+ <template>
552
+ <!-- Vertical (default) -->
553
+ <RadioGroup v-model="plan">
554
+ <Radio value="free" label="Free" />
555
+ <Radio value="pro" label="Pro" />
556
+ <Radio value="teams" label="Teams" />
557
+ </RadioGroup>
558
+
559
+ <!-- Horizontal -->
560
+ <RadioGroup v-model="plan" orientation="horizontal">
561
+ <Radio value="light" label="Light" />
562
+ <Radio value="dark" label="Dark" />
563
+ </RadioGroup>
564
+
565
+ <!-- Semantic colors -->
566
+ <RadioGroup v-model="plan">
567
+ <Radio value="ok" variant="success" label="Approved" />
568
+ <Radio value="warn" variant="warning" label="Pending" />
569
+ <Radio value="err" variant="danger" label="Rejected" />
570
+ </RadioGroup>
571
+
572
+ <!-- Sizes -->
573
+ <RadioGroup v-model="plan">
574
+ <Radio value="s" size="small" label="Small" />
575
+ <Radio value="m" size="default" label="Default" />
576
+ <Radio value="l" size="large" label="Large" />
577
+ </RadioGroup>
578
+
579
+ <!-- Disabled states -->
580
+ <RadioGroup v-model="plan">
581
+ <Radio value="a" label="Available" />
582
+ <Radio value="b" label="Unavailable" disabled />
583
+ </RadioGroup>
584
+
585
+ <!-- Disabled group -->
586
+ <RadioGroup v-model="plan" disabled>
587
+ <Radio value="x" label="Option X" />
588
+ <Radio value="y" label="Option Y" />
589
+ </RadioGroup>
590
+ </template>
591
+ ```
592
+
593
+ ---
594
+
595
+ ### Label
596
+
597
+ Semantic `<label>` element styled with the design system's text and font styles. Automatically dims when associated with a disabled input.
598
+
599
+ **Props**
600
+
601
+ Accepts all props from reka-ui's `LabelProps` plus:
602
+
603
+ | Prop | Type | Description |
604
+ |---|---|---|
605
+ | `class` | `string` | Additional CSS classes |
606
+
607
+ ```vue
608
+ <script setup>
609
+ import { Label, Input } from '@buillaume.biondo/fab-ui'
610
+ </script>
611
+
612
+ <template>
613
+ <div class="flex flex-col gap-1.5">
614
+ <Label for="email">Email address</Label>
615
+ <Input id="email" type="email" placeholder="you@example.com" />
616
+ </div>
617
+ </template>
618
+ ```
619
+
620
+ ---
621
+
622
+ ### Input
623
+
624
+ Standard text input with focus ring, error state, disabled styling, and file input support.
625
+
626
+ **Props**
627
+
628
+ | Prop | Type | Default | Description |
629
+ |---|---|---|---|
630
+ | `modelValue` | `string \| number` | — | Bound value (`v-model`) |
631
+ | `defaultValue` | `string \| number` | — | Uncontrolled default value |
632
+ | `type` | `string` | `'text'` | HTML input type |
633
+ | `placeholder` | `string` | — | Placeholder text |
634
+ | `disabled` | `boolean` | — | Disables the input |
635
+ | `required` | `boolean` | — | Marks the field as required |
636
+ | `error` | `string` | — | Triggers the error visual state |
637
+ | `class` | `string` | — | Additional CSS classes |
638
+
639
+ ```vue
640
+ <script setup>
641
+ import { Input } from '@buillaume.biondo/fab-ui'
642
+ import { ref } from 'vue'
643
+
644
+ const email = ref('')
645
+ </script>
646
+
647
+ <template>
648
+ <Input v-model="email" type="email" placeholder="you@example.com" />
649
+
650
+ <!-- With error -->
651
+ <Input v-model="email" error="Invalid email address" />
652
+ </template>
653
+ ```
654
+
655
+ ---
656
+
657
+ ### InputPassword
658
+
659
+ Password input with a toggle button to show or hide the value. Shares the same props as `Input` (minus `type`).
660
+
661
+ **Props**
662
+
663
+ | Prop | Type | Default | Description |
664
+ |---|---|---|---|
665
+ | `modelValue` | `string` | — | Bound value (`v-model`) |
666
+ | `defaultValue` | `string` | — | Uncontrolled default value |
667
+ | `placeholder` | `string` | — | Placeholder text |
668
+ | `disabled` | `boolean` | — | Disables the input |
669
+ | `required` | `boolean` | — | Marks the field as required |
670
+ | `error` | `string` | — | Triggers the error visual state |
671
+ | `class` | `string` | — | Applied to the wrapper div |
672
+
673
+ ```vue
674
+ <script setup>
675
+ import { InputPassword } from '@buillaume.biondo/fab-ui'
676
+ import { ref } from 'vue'
677
+
678
+ const password = ref('')
679
+ </script>
680
+
681
+ <template>
682
+ <InputPassword v-model="password" placeholder="Enter your password" />
683
+ </template>
684
+ ```
685
+
686
+ ---
687
+
688
+ ### InputNumber
689
+
690
+ Numeric input with optional `+` / `−` controls on each side. Enforces `min`, `max`, and `step` constraints.
691
+
692
+ **Props**
693
+
694
+ | Prop | Type | Default | Description |
695
+ |---|---|---|---|
696
+ | `modelValue` | `number` | — | Bound value (`v-model`) |
697
+ | `defaultValue` | `number` | — | Uncontrolled default value |
698
+ | `min` | `number` | — | Minimum allowed value |
699
+ | `max` | `number` | — | Maximum allowed value |
700
+ | `step` | `number` | `1` | Increment/decrement step |
701
+ | `controls` | `boolean` | `true` | Show the `+` / `−` buttons |
702
+ | `placeholder` | `string` | — | Placeholder text |
703
+ | `disabled` | `boolean` | — | Disables the input |
704
+ | `required` | `boolean` | — | Marks the field as required |
705
+ | `error` | `string` | — | Triggers the error visual state |
706
+ | `class` | `string` | — | Applied to the wrapper div |
707
+
708
+ ```vue
709
+ <script setup>
710
+ import { InputNumber } from '@buillaume.biondo/fab-ui'
711
+ import { ref } from 'vue'
712
+
713
+ const quantity = ref(1)
714
+ </script>
715
+
716
+ <template>
717
+ <InputNumber v-model="quantity" :min="1" :max="99" />
718
+
719
+ <!-- Without stepper controls -->
720
+ <InputNumber v-model="quantity" :controls="false" />
721
+ </template>
722
+ ```
723
+
724
+ ---
725
+
726
+ ### InputArea
727
+
728
+ Multi-line textarea with configurable resize behavior and an optional character counter.
729
+
730
+ **Props**
731
+
732
+ | Prop | Type | Default | Description |
733
+ |---|---|---|---|
734
+ | `modelValue` | `string` | — | Bound value (`v-model`) |
735
+ | `defaultValue` | `string` | — | Uncontrolled default value |
736
+ | `rows` | `number` | `3` | Number of visible rows |
737
+ | `cols` | `number` | — | Fixed column width (makes the wrapper `inline-flex`) |
738
+ | `maxlength` | `number` | — | Max characters; shows a live counter |
739
+ | `resize` | `'none' \| 'vertical' \| 'horizontal' \| 'both'` | `'vertical'` | Resize handle behavior |
740
+ | `placeholder` | `string` | — | Placeholder text |
741
+ | `disabled` | `boolean` | — | Disables the textarea |
742
+ | `required` | `boolean` | — | Marks the field as required |
743
+ | `error` | `string` | — | Triggers the error visual state |
744
+ | `class` | `string` | — | Applied to the wrapper div |
745
+
746
+ ```vue
747
+ <script setup>
748
+ import { InputArea } from '@buillaume.biondo/fab-ui'
749
+ import { ref } from 'vue'
750
+
751
+ const bio = ref('')
752
+ </script>
753
+
754
+ <template>
755
+ <InputArea
756
+ v-model="bio"
757
+ placeholder="Tell us about yourself…"
758
+ :rows="5"
759
+ :maxlength="280"
760
+ resize="none"
761
+ />
762
+ </template>
763
+ ```
764
+
765
+ ---
766
+
767
+ ### InputOTP
768
+
769
+ One-time password input built on [vue-input-otp](https://github.com/wobsoriano/vue-input-otp). Compose `InputOTP`, `InputOTPGroup`, `InputOTPSlot`, and `InputOTPSeparator` to build any OTP layout.
770
+
771
+ > Requires the optional peer dependency `vue-input-otp`.
772
+
773
+ **Exported components**
774
+
775
+ `InputOTP`, `InputOTPGroup`, `InputOTPSlot`, `InputOTPSeparator`
776
+
777
+ ```vue
778
+ <script setup>
779
+ import {
780
+ InputOTP,
781
+ InputOTPGroup,
782
+ InputOTPSlot,
783
+ InputOTPSeparator,
784
+ } from '@buillaume.biondo/fab-ui'
785
+ import { ref } from 'vue'
786
+
787
+ const otp = ref('')
788
+ </script>
789
+
790
+ <template>
791
+ <InputOTP v-model="otp" :max-length="6">
792
+ <InputOTPGroup>
793
+ <InputOTPSlot :index="0" />
794
+ <InputOTPSlot :index="1" />
795
+ <InputOTPSlot :index="2" />
796
+ </InputOTPGroup>
797
+ <InputOTPSeparator />
798
+ <InputOTPGroup>
799
+ <InputOTPSlot :index="3" />
800
+ <InputOTPSlot :index="4" />
801
+ <InputOTPSlot :index="5" />
802
+ </InputOTPGroup>
803
+ </InputOTP>
804
+ </template>
805
+ ```
806
+
807
+ ---
808
+
809
+ ### Select
810
+
811
+ Native-feeling select dropdown built on reka-ui. Compose the subcomponents for full control over options, groups, and labels.
812
+
813
+ **Exported components**
814
+
815
+ `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectScrollUpButton`, `SelectScrollDownButton`, `SelectItemText`
816
+
817
+ **SelectTrigger props**
818
+
819
+ | Prop | Type | Default | Description |
820
+ |---|---|---|---|
821
+ | `size` | `'sm' \| 'default'` | `'default'` | Trigger height |
822
+ | `class` | `string` | — | Additional CSS classes |
823
+
824
+ ```vue
825
+ <script setup>
826
+ import {
827
+ Select,
828
+ SelectTrigger,
829
+ SelectValue,
830
+ SelectContent,
831
+ SelectGroup,
832
+ SelectLabel,
833
+ SelectItem,
834
+ SelectSeparator,
835
+ } from '@buillaume.biondo/fab-ui'
836
+ import { ref } from 'vue'
837
+
838
+ const role = ref('')
839
+ </script>
840
+
841
+ <template>
842
+ <Select v-model="role">
843
+ <SelectTrigger class="w-48">
844
+ <SelectValue placeholder="Select a role" />
845
+ </SelectTrigger>
846
+ <SelectContent>
847
+ <SelectGroup>
848
+ <SelectLabel>Roles</SelectLabel>
849
+ <SelectItem value="admin">Admin</SelectItem>
850
+ <SelectItem value="editor">Editor</SelectItem>
851
+ <SelectSeparator />
852
+ <SelectItem value="viewer">Viewer</SelectItem>
853
+ </SelectGroup>
854
+ </SelectContent>
855
+ </Select>
856
+ </template>
857
+ ```
858
+
859
+ ---
860
+
861
+ ### Slider
862
+
863
+ Range slider built on reka-ui's `SliderRoot`. Supports single value, ranges (multiple thumbs), and all 8 severity variants.
864
+
865
+ **Props**
866
+
867
+ Accepts all props from reka-ui's `SliderRootProps` plus:
868
+
869
+ | Prop | Type | Default | Description |
870
+ |---|---|---|---|
871
+ | `variant` | `Severity` | `'primary'` | Track and thumb color |
872
+ | `size` | `'small' \| 'default' \| 'large'` | `'default'` | Track and thumb dimensions |
873
+ | `class` | `string` | — | Additional CSS classes |
874
+
875
+ ```vue
876
+ <script setup>
877
+ import { Slider } from '@buillaume.biondo/fab-ui'
878
+ import { ref } from 'vue'
879
+
880
+ const value = ref([50])
881
+ const range = ref([20, 80])
882
+ </script>
883
+
884
+ <template>
885
+ <!-- Single thumb -->
886
+ <Slider v-model="value" :min="0" :max="100" :step="1" />
887
+
888
+ <!-- Range (two thumbs) -->
889
+ <Slider v-model="range" variant="success" size="large" />
890
+ </template>
891
+ ```
892
+
893
+ ---
894
+
895
+ ### Toggle
896
+
897
+ Toggleable button built on reka-ui's `Toggle` primitive. Useful for toolbar buttons, filter chips, and binary options.
898
+
899
+ **Props**
900
+
901
+ Accepts all props from reka-ui's `ToggleProps` plus:
902
+
903
+ | Prop | Type | Default | Description |
904
+ |---|---|---|---|
905
+ | `variant` | `Severity` | `'primary'` | Active color |
906
+ | `shape` | `'ghost' \| 'outline' \| 'plain'` | `'ghost'` | Visual style |
907
+ | `size` | `Size` | `'default'` | Button size |
908
+ | `onValue` | `string` | — | Label when pressed (overrides slot) |
909
+ | `offValue` | `string` | — | Label when not pressed (overrides slot) |
910
+ | `class` | `string` | — | Additional CSS classes |
911
+
912
+ ```vue
913
+ <script setup>
914
+ import { Toggle } from '@buillaume.biondo/fab-ui'
915
+ import { BoldIcon } from 'lucide-vue-next'
916
+ import { ref } from 'vue'
917
+
918
+ const bold = ref(false)
919
+ </script>
920
+
921
+ <template>
922
+ <Toggle v-model:pressed="bold" shape="outline">
923
+ <BoldIcon />
924
+ </Toggle>
925
+
926
+ <!-- With text labels -->
927
+ <Toggle on-value="On" off-value="Off" v-model:pressed="bold" />
928
+ </template>
929
+ ```
930
+
931
+ ---
932
+
933
+ ### ToggleSwitch
934
+
935
+ iOS-style toggle switch built on reka-ui's `SwitchRoot`. Supports an inline label.
936
+
937
+ **Props**
938
+
939
+ Accepts all props from reka-ui's `SwitchRootProps` plus:
940
+
941
+ | Prop | Type | Default | Description |
942
+ |---|---|---|---|
943
+ | `variant` | `Severity` | `'primary'` | Track color when checked |
944
+ | `size` | `'small' \| 'default' \| 'large'` | `'default'` | Switch dimensions |
945
+ | `label` | `string` | — | Label text rendered to the right |
946
+ | `class` | `string` | — | Applied to the wrapper `<label>` |
947
+
948
+ ```vue
949
+ <script setup>
950
+ import { ToggleSwitch } from '@buillaume.biondo/fab-ui'
951
+ import { ref } from 'vue'
952
+
953
+ const notifications = ref(true)
954
+ </script>
955
+
956
+ <template>
957
+ <ToggleSwitch v-model:checked="notifications" label="Email notifications" />
958
+
959
+ <ToggleSwitch
960
+ v-model:checked="notifications"
961
+ variant="success"
962
+ size="large"
963
+ label="Enable feature"
964
+ />
965
+ </template>
966
+ ```
967
+
968
+ ---
969
+
970
+ ### ProgressBar
971
+
972
+ Horizontal progress bar with determinate and indeterminate modes.
973
+
974
+ **Props**
975
+
976
+ | Prop | Type | Default | Description |
977
+ |---|---|---|---|
978
+ | `value` | `number` | — | Progress from 0 to 100. Omit for indeterminate animation |
979
+ | `variant` | `Severity` | `'primary'` | Fill color |
980
+ | `size` | `'tiny' \| 'small' \| 'default' \| 'large'` | `'default'` | Bar height |
981
+ | `label` | `boolean` | `false` | Shows percentage text to the right |
982
+ | `class` | `string` | — | Additional CSS classes |
983
+
984
+ ```vue
985
+ <script setup>
986
+ import { ProgressBar } from '@buillaume.biondo/fab-ui'
987
+ </script>
988
+
989
+ <template>
990
+ <!-- Determinate -->
991
+ <ProgressBar :value="72" variant="success" :label="true" />
992
+
993
+ <!-- Indeterminate -->
994
+ <ProgressBar variant="info" />
995
+
996
+ <!-- Sizes -->
997
+ <ProgressBar :value="40" size="tiny" />
998
+ <ProgressBar :value="40" size="large" variant="warning" />
999
+ </template>
1000
+ ```
1001
+
1002
+ ---
1003
+
1004
+ ### Skeleton
1005
+
1006
+ A pulsing placeholder block used to represent loading content. Size and shape are controlled entirely via `class`.
1007
+
1008
+ **Props**
1009
+
1010
+ | Prop | Type | Description |
1011
+ |---|---|---|
1012
+ | `class` | `string` | CSS classes for size and shape (e.g. `h-4 w-32 rounded-full`) |
1013
+
1014
+ ```vue
1015
+ <script setup>
1016
+ import { Skeleton } from '@buillaume.biondo/fab-ui'
1017
+ </script>
1018
+
1019
+ <template>
1020
+ <div class="flex items-center gap-3">
1021
+ <Skeleton class="size-10 rounded-full" />
1022
+ <div class="flex flex-col gap-2">
1023
+ <Skeleton class="h-4 w-48" />
1024
+ <Skeleton class="h-3 w-32" />
1025
+ </div>
1026
+ </div>
1027
+ </template>
1028
+ ```
1029
+
1030
+ ---
1031
+
1032
+ ### Separator
1033
+
1034
+ A thin horizontal or vertical divider line built on reka-ui.
1035
+
1036
+ **Props**
1037
+
1038
+ Accepts all props from reka-ui's `SeparatorProps` plus:
1039
+
1040
+ | Prop | Type | Default | Description |
1041
+ |---|---|---|---|
1042
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direction |
1043
+ | `decorative` | `boolean` | `true` | Marks the separator as decorative (aria hidden) |
1044
+ | `class` | `string` | — | Additional CSS classes |
1045
+
1046
+ ```vue
1047
+ <script setup>
1048
+ import { Separator } from '@buillaume.biondo/fab-ui'
1049
+ </script>
1050
+
1051
+ <template>
1052
+ <Separator />
1053
+
1054
+ <!-- Vertical -->
1055
+ <div class="flex h-8 items-center gap-4">
1056
+ <span>Item A</span>
1057
+ <Separator orientation="vertical" />
1058
+ <span>Item B</span>
1059
+ </div>
1060
+ </template>
1061
+ ```
1062
+
1063
+ ---
1064
+
1065
+ ### Spinner
1066
+
1067
+ A minimal animated loading indicator using a spinning `Loader2` icon.
1068
+
1069
+ **Props**
1070
+
1071
+ | Prop | Type | Description |
1072
+ |---|---|---|
1073
+ | `class` | `string` | CSS classes for size (default `size-4`) and color |
1074
+
1075
+ ```vue
1076
+ <script setup>
1077
+ import { Spinner } from '@buillaume.biondo/fab-ui'
1078
+ </script>
1079
+
1080
+ <template>
1081
+ <Spinner />
1082
+ <Spinner class="size-8 text-blue-600" />
1083
+ </template>
1084
+ ```
1085
+
1086
+ ---
1087
+
1088
+ ### Code
1089
+
1090
+ Syntax-highlighted code block powered by [highlight.js](https://highlightjs.org/) with the Atom One Dark theme. Supports automatic language detection, line numbers, a copy-to-clipboard button, and a macOS-style window chrome.
1091
+
1092
+ **Props**
1093
+
1094
+ | Prop | Type | Default | Description |
1095
+ |---|---|---|---|
1096
+ | `code` | `string` | — | Code string to display (or use the default slot) |
1097
+ | `language` | `string` | — | highlight.js language identifier (auto-detected if omitted) |
1098
+ | `filename` | `string` | — | Filename shown in the header bar |
1099
+ | `copyable` | `boolean` | `true` | Shows the copy-to-clipboard button |
1100
+ | `lineNumbers` | `boolean` | `false` | Displays line numbers |
1101
+ | `maxHeight` | `string` | — | CSS max-height for scrollable blocks (e.g. `'400px'`) |
1102
+ | `class` | `string` | — | Additional CSS classes |
1103
+
1104
+ ```vue
1105
+ <script setup>
1106
+ import { Code } from '@buillaume.biondo/fab-ui'
1107
+ </script>
1108
+
1109
+ <template>
1110
+ <Code
1111
+ language="javascript"
1112
+ filename="utils.js"
1113
+ :line-numbers="true"
1114
+ max-height="300px"
1115
+ :code="`function greet(name) {\n return 'Hello, ' + name\n}`"
1116
+ />
1117
+
1118
+ <!-- Via default slot -->
1119
+ <Code language="bash">
1120
+ npm install @buillaume.biondo/fab-ui
1121
+ </Code>
1122
+ </template>
1123
+ ```
1124
+
1125
+ ---
1126
+
1127
+ ### Image
1128
+
1129
+ Smart image container with lazy loading, skeleton placeholder, aspect ratio enforcement, object-fit control, and a fallback for broken images.
1130
+
1131
+ **Props**
1132
+
1133
+ | Prop | Type | Default | Description |
1134
+ |---|---|---|---|
1135
+ | `src` | `string` | — | Image URL (required) |
1136
+ | `alt` | `string` | `''` | Alt text |
1137
+ | `ratio` | `'1/1' \| '4/3' \| '16/9' \| '3/2' \| '2/3' \| '9/16'` | — | Fixed aspect ratio |
1138
+ | `fit` | `'cover' \| 'contain' \| 'fill' \| 'none'` | `'cover'` | CSS `object-fit` value |
1139
+ | `skeleton` | `boolean` | `true` | Shows a pulsing placeholder while loading |
1140
+ | `fallback` | `string` | — | Fallback image URL shown on error |
1141
+ | `rounded` | `boolean \| 'full'` | `false` | Border radius (`true` = card radius, `'full'` = circle) |
1142
+ | `class` | `string` | — | Applied to the container div |
1143
+
1144
+ **Emits**
1145
+
1146
+ | Event | Description |
1147
+ |---|---|
1148
+ | `load` | Native `load` event from `<img>` |
1149
+ | `error` | Native `error` event from `<img>` |
1150
+
1151
+ ```vue
1152
+ <script setup>
1153
+ import { Image } from '@buillaume.biondo/fab-ui'
1154
+ </script>
1155
+
1156
+ <template>
1157
+ <Image
1158
+ src="/photos/hero.jpg"
1159
+ alt="Hero image"
1160
+ ratio="16/9"
1161
+ fallback="/photos/placeholder.jpg"
1162
+ :rounded="true"
1163
+ />
1164
+
1165
+ <!-- Avatar-style -->
1166
+ <Image
1167
+ src="/avatars/user.jpg"
1168
+ alt="User avatar"
1169
+ ratio="1/1"
1170
+ rounded="full"
1171
+ class="w-16"
1172
+ />
1173
+ </template>
1174
+ ```
1175
+
1176
+ ---
1177
+
1178
+ ### Carousel
1179
+
1180
+ A touch-friendly slideshow with optional navigation arrows, dot indicators, autoplay, loop, and multi-slide view support.
1181
+
1182
+ **Props**
1183
+
1184
+ | Prop | Type | Default | Description |
1185
+ |---|---|---|---|
1186
+ | `arrows` | `boolean` | `true` | Show previous/next arrow buttons |
1187
+ | `dots` | `boolean` | `true` | Show dot indicator buttons |
1188
+ | `autoplay` | `number` | `0` | Autoplay interval in ms (`0` = disabled) |
1189
+ | `loop` | `boolean` | `false` | Wrap around from last to first slide |
1190
+ | `perView` | `number` | `1` | Number of slides visible at once |
1191
+ | `gap` | `string` | `'gap-4'` | Tailwind gap class between slides |
1192
+ | `class` | `string` | — | Additional CSS classes |
1193
+
1194
+ ```vue
1195
+ <script setup>
1196
+ import { Carousel, CarouselSlide } from '@buillaume.biondo/fab-ui'
1197
+ </script>
1198
+
1199
+ <template>
1200
+ <Carousel :autoplay="3000" :loop="true" :dots="true">
1201
+ <CarouselSlide>
1202
+ <Image src="/slides/1.jpg" ratio="16/9" />
1203
+ </CarouselSlide>
1204
+ <CarouselSlide>
1205
+ <Image src="/slides/2.jpg" ratio="16/9" />
1206
+ </CarouselSlide>
1207
+ <CarouselSlide>
1208
+ <Image src="/slides/3.jpg" ratio="16/9" />
1209
+ </CarouselSlide>
1210
+ </Carousel>
1211
+
1212
+ <!-- Multi-slide -->
1213
+ <Carousel :per-view="3" gap="gap-6">
1214
+ <CarouselSlide v-for="item in items" :key="item.id">
1215
+ <Card>{{ item.title }}</Card>
1216
+ </CarouselSlide>
1217
+ </Carousel>
1218
+ </template>
1219
+ ```
1220
+
1221
+ ---
1222
+
1223
+ ### DataTable
1224
+
1225
+ A feature-complete data table with client-side sorting, global search with debounce, pagination with ellipsis, row selection, custom cell rendering via named slots, and a loading skeleton.
1226
+
1227
+ **Props**
1228
+
1229
+ | Prop | Type | Default | Description |
1230
+ |---|---|---|---|
1231
+ | `columns` | `DataTableColumn[]` | — | Column definitions (required) |
1232
+ | `data` | `T[]` | — | Row data (required) |
1233
+ | `searchable` | `boolean` | — | Renders a search input above the table |
1234
+ | `selectable` | `boolean` | — | Renders a checkbox column |
1235
+ | `pageSize` | `number` | `15` | Initial rows per page |
1236
+ | `pageSizes` | `number[]` | `[10, 25, 50, 100]` | Page size options in the selector |
1237
+ | `loading` | `boolean` | — | Shows a skeleton instead of data |
1238
+ | `emptyText` | `string` | `'Aucun résultat.'` | Text displayed when there are no rows |
1239
+ | `striped` | `boolean` | — | Alternating row background |
1240
+ | `hoverable` | `boolean` | `true` | Highlights rows on hover |
1241
+ | `class` | `string` | — | Additional CSS classes |
1242
+
1243
+ **`DataTableColumn` definition**
1244
+
1245
+ ```ts
1246
+ interface DataTableColumn<Row> {
1247
+ key: string // Row property key
1248
+ label: string // Column header text
1249
+ sortable?: boolean // Enables client-side sorting
1250
+ width?: string // CSS column width
1251
+ align?: 'left' | 'center' | 'right'
1252
+ format?: (value: unknown, row: Row) => string // Custom cell formatter
1253
+ }
1254
+ ```
1255
+
1256
+ **Models**
1257
+
1258
+ | Model | Type | Description |
1259
+ |---|---|---|
1260
+ | `v-model:selected` | `T[]` | Two-way binding for selected rows. The checkbox column appears automatically when bound |
1261
+
1262
+ **Emits**
1263
+
1264
+ | Event | Payload | Description |
1265
+ |---|---|---|
1266
+ | `row-click` | `row: T` | Fired when a row is clicked |
1267
+ | `cell-click` | `key: string, row: T` | Fired when a cell is clicked |
1268
+
1269
+ **Slots**
1270
+
1271
+ | Slot | Props | Description |
1272
+ |---|---|---|
1273
+ | `#column-{key}` | `{ row, value }` | Custom cell renderer for the given column key |
1274
+ | `#empty` | — | Custom empty state content |
1275
+
1276
+ ```vue
1277
+ <script setup>
1278
+ import { DataTable } from '@buillaume.biondo/fab-ui'
1279
+ import { Badge } from '@buillaume.biondo/fab-ui'
1280
+ import { ref } from 'vue'
1281
+
1282
+ const columns = [
1283
+ { key: 'name', label: 'Name', sortable: true },
1284
+ { key: 'email', label: 'Email', sortable: true },
1285
+ { key: 'role', label: 'Role' },
1286
+ { key: 'status', label: 'Status' },
1287
+ ]
1288
+
1289
+ const users = ref([
1290
+ { name: 'Alice', email: 'alice@example.com', role: 'Admin', status: 'active' },
1291
+ { name: 'Bob', email: 'bob@example.com', role: 'Editor', status: 'inactive' },
1292
+ ])
1293
+
1294
+ const selected = ref([])
1295
+ </script>
1296
+
1297
+ <template>
1298
+ <DataTable
1299
+ :columns="columns"
1300
+ :data="users"
1301
+ v-model:selected="selected"
1302
+ :searchable="true"
1303
+ :striped="true"
1304
+ @row-click="(row) => console.log(row)"
1305
+ >
1306
+ <!-- Custom status cell -->
1307
+ <template #column-status="{ value }">
1308
+ <Badge :variant="value === 'active' ? 'success' : 'secondary'">
1309
+ {{ value }}
1310
+ </Badge>
1311
+ </template>
1312
+ </DataTable>
1313
+ </template>
1314
+ ```
1315
+
1316
+ ---
1317
+
1318
+ ### Toast / Toaster
1319
+
1320
+ Toast notifications managed through the `useToast` composable. Mount `Toaster` once in your layout — it renders at the bottom-right of the viewport with animated transitions and a progress bar.
1321
+
1322
+ **Toast variants:** `info` | `success` | `warning` | `error`
1323
+
1324
+ **Default durations:**
1325
+ - `info`, `success`: 4 seconds
1326
+ - `warning`: 6 seconds
1327
+ - `error`: persistent (no auto-dismiss)
1328
+
1329
+ See the [useToast](#usetoast) composable section for the full API.
1330
+
1331
+ ```vue
1332
+ <!-- Root layout -->
1333
+ <script setup>
1334
+ import { Toaster } from '@buillaume.biondo/fab-ui'
1335
+ </script>
1336
+
1337
+ <template>
1338
+ <slot />
1339
+ <Toaster />
1340
+ </template>
1341
+ ```
1342
+
1343
+ ```vue
1344
+ <!-- Anywhere in your app -->
1345
+ <script setup>
1346
+ import { useToast } from '@buillaume.biondo/fab-ui'
1347
+
1348
+ const toast = useToast()
1349
+ </script>
1350
+
1351
+ <template>
1352
+ <Button @click="toast.success('Saved successfully!')">Save</Button>
1353
+ <Button @click="toast.error('Something went wrong.')">Fail</Button>
1354
+ </template>
1355
+ ```
1356
+
1357
+ ---
1358
+
1359
+ ### SpeedDial
1360
+
1361
+ A floating action button (FAB) that expands into a list of action buttons in any direction. Actions appear with a staggered animation and display labels as tooltips on hover.
1362
+
1363
+ **`SpeedDialItem` interface**
1364
+
1365
+ ```ts
1366
+ interface SpeedDialItem {
1367
+ label: string // Tooltip and accessible label
1368
+ icon: Component // Lucide icon component
1369
+ onClick: () => void // Click handler
1370
+ disabled?: boolean
1371
+ variant?: Severity // Button color (default: 'secondary')
1372
+ }
1373
+ ```
1374
+
1375
+ **Props**
1376
+
1377
+ | Prop | Type | Default | Description |
1378
+ |---|---|---|---|
1379
+ | `items` | `SpeedDialItem[]` | — | Action buttons (required) |
1380
+ | `direction` | `'up' \| 'down' \| 'left' \| 'right'` | `'up'` | Direction the actions expand |
1381
+ | `variant` | `Severity` | `'primary'` | Main FAB button color |
1382
+ | `size` | `'small' \| 'default' \| 'large'` | `'default'` | Button size |
1383
+ | `icon` | `Component` | — | Custom icon for the main button (defaults to `+` / `×`) |
1384
+ | `disabled` | `boolean` | — | Disables the FAB |
1385
+ | `class` | `string` | — | Additional CSS classes on the wrapper |
1386
+
1387
+ ```vue
1388
+ <script setup>
1389
+ import { SpeedDial } from '@buillaume.biondo/fab-ui'
1390
+ import { PencilIcon, TrashIcon, ShareIcon } from 'lucide-vue-next'
1391
+
1392
+ const actions = [
1393
+ { label: 'Edit', icon: PencilIcon, onClick: () => edit(), variant: 'primary' },
1394
+ { label: 'Share', icon: ShareIcon, onClick: () => share(), variant: 'info' },
1395
+ { label: 'Delete', icon: TrashIcon, onClick: () => remove(), variant: 'danger' },
1396
+ ]
1397
+ </script>
1398
+
1399
+ <template>
1400
+ <SpeedDial :items="actions" direction="up" />
1401
+ </template>
1402
+ ```
1403
+
1404
+ ---
1405
+
1406
+ ### SplitButton
1407
+
1408
+ A composite button with a primary action and a dropdown arrow that reveals additional options.
1409
+
1410
+ **Props**
1411
+
1412
+ | Prop | Type | Default | Description |
1413
+ |---|---|---|---|
1414
+ | `variant` | `Severity` | `'primary'` | Color variant |
1415
+ | `shape` | `'plain' \| 'outline' \| 'ghost'` | `'plain'` | Visual style |
1416
+ | `size` | `Size` | `'default'` | Button size |
1417
+ | `fluid` | `boolean` | `false` | Full-width |
1418
+ | `disabled` | `boolean` | `false` | Disables both parts |
1419
+ | `loading` | `boolean` | `false` | Shows spinner on the main button |
1420
+ | `iconLeft` | `Component` | — | Icon component on the main button |
1421
+ | `class` | `string` | — | Additional CSS classes |
1422
+
1423
+ **Slots**
1424
+
1425
+ | Slot | Description |
1426
+ |---|---|
1427
+ | `default` | Label of the main button |
1428
+ | `#dropdown` | `DropdownMenuItem` components for the dropdown |
1429
+
1430
+ **Emits**
1431
+
1432
+ | Event | Description |
1433
+ |---|---|
1434
+ | `click` | Fired when the main button is clicked |
1435
+
1436
+ ```vue
1437
+ <script setup>
1438
+ import {
1439
+ SplitButton,
1440
+ DropdownMenuItem,
1441
+ DropdownMenuSeparator,
1442
+ } from '@buillaume.biondo/fab-ui'
1443
+ </script>
1444
+
1445
+ <template>
1446
+ <SplitButton @click="save()">
1447
+ Save
1448
+
1449
+ <template #dropdown>
1450
+ <DropdownMenuItem @click="saveAndPublish()">Save & publish</DropdownMenuItem>
1451
+ <DropdownMenuSeparator />
1452
+ <DropdownMenuItem @click="saveDraft()">Save as draft</DropdownMenuItem>
1453
+ </template>
1454
+ </SplitButton>
1455
+ </template>
1456
+ ```
1457
+
1458
+ ---
1459
+
1460
+ ### Tooltip
1461
+
1462
+ Simple tooltip built on reka-ui. Wrap your trigger with `TooltipProvider`, use `Tooltip` + `TooltipTrigger` + `TooltipContent`.
1463
+
1464
+ **Exported components**
1465
+
1466
+ `TooltipProvider`, `Tooltip`, `TooltipTrigger`, `TooltipContent`
1467
+
1468
+ ```vue
1469
+ <script setup>
1470
+ import {
1471
+ Tooltip,
1472
+ TooltipContent,
1473
+ TooltipProvider,
1474
+ TooltipTrigger,
1475
+ Button,
1476
+ } from '@buillaume.biondo/fab-ui'
1477
+ import { SettingsIcon } from 'lucide-vue-next'
1478
+ </script>
1479
+
1480
+ <template>
1481
+ <TooltipProvider>
1482
+ <Tooltip>
1483
+ <TooltipTrigger as-child>
1484
+ <Button :icon="true" shape="ghost" variant="secondary">
1485
+ <SettingsIcon />
1486
+ </Button>
1487
+ </TooltipTrigger>
1488
+ <TooltipContent>Settings</TooltipContent>
1489
+ </Tooltip>
1490
+ </TooltipProvider>
1491
+ </template>
1492
+ ```
1493
+
1494
+ ---
1495
+
1496
+ ### Collapsible
1497
+
1498
+ Expandable / collapsible section built on reka-ui. `CollapsibleContent` supports an `overlay` mode that floats the content above the document flow.
1499
+
1500
+ **Exported components**
1501
+
1502
+ `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent`
1503
+
1504
+ **`CollapsibleContent` props**
1505
+
1506
+ | Prop | Type | Default | Description |
1507
+ |---|---|---|---|
1508
+ | `overlay` | `boolean` | `false` | Positions content as an absolute overlay (`z-20`) |
1509
+ | `class` | `string` | — | Additional CSS classes |
1510
+
1511
+ ```vue
1512
+ <script setup>
1513
+ import {
1514
+ Collapsible,
1515
+ CollapsibleTrigger,
1516
+ CollapsibleContent,
1517
+ Button,
1518
+ } from '@buillaume.biondo/fab-ui'
1519
+ import { ref } from 'vue'
1520
+
1521
+ const open = ref(false)
1522
+ </script>
1523
+
1524
+ <template>
1525
+ <Collapsible v-model:open="open">
1526
+ <CollapsibleTrigger as-child>
1527
+ <Button shape="ghost">Toggle section</Button>
1528
+ </CollapsibleTrigger>
1529
+ <CollapsibleContent>
1530
+ <p class="mt-2 text-sm">Hidden content revealed on toggle.</p>
1531
+ </CollapsibleContent>
1532
+ </Collapsible>
1533
+ </template>
1534
+ ```
1535
+
1536
+ ---
1537
+
1538
+ ### DropdownMenu
1539
+
1540
+ Full-featured dropdown menu built on reka-ui with support for items, checkboxes, radio groups, sub-menus, separators, and keyboard shortcuts.
1541
+
1542
+ **Exported components**
1543
+
1544
+ `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSeparator`, `DropdownMenuShortcut`, `DropdownMenuGroup`, `DropdownMenuSub`, `DropdownMenuSubTrigger`, `DropdownMenuSubContent`, `DropdownMenuCheckboxItem`, `DropdownMenuRadioGroup`, `DropdownMenuRadioItem`, `DropdownMenuPortal`
1545
+
1546
+ ```vue
1547
+ <script setup>
1548
+ import {
1549
+ DropdownMenu,
1550
+ DropdownMenuTrigger,
1551
+ DropdownMenuContent,
1552
+ DropdownMenuLabel,
1553
+ DropdownMenuItem,
1554
+ DropdownMenuSeparator,
1555
+ DropdownMenuShortcut,
1556
+ Button,
1557
+ } from '@buillaume.biondo/fab-ui'
1558
+ </script>
1559
+
1560
+ <template>
1561
+ <DropdownMenu>
1562
+ <DropdownMenuTrigger as-child>
1563
+ <Button shape="outline" variant="secondary">Options</Button>
1564
+ </DropdownMenuTrigger>
1565
+ <DropdownMenuContent class="w-48">
1566
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
1567
+ <DropdownMenuSeparator />
1568
+ <DropdownMenuItem>
1569
+ Profile
1570
+ <DropdownMenuShortcut>⌘P</DropdownMenuShortcut>
1571
+ </DropdownMenuItem>
1572
+ <DropdownMenuItem>Settings</DropdownMenuItem>
1573
+ <DropdownMenuSeparator />
1574
+ <DropdownMenuItem class="text-danger">Log out</DropdownMenuItem>
1575
+ </DropdownMenuContent>
1576
+ </DropdownMenu>
1577
+ </template>
1578
+ ```
1579
+
1580
+ ---
1581
+
1582
+ ### ContextMenu
1583
+
1584
+ Right-click context menu with the same feature set as `DropdownMenu`. `ContextMenuItem` accepts a `variant="danger"` prop for destructive actions.
1585
+
1586
+ **Exported components**
1587
+
1588
+ `ContextMenu`, `ContextMenuTrigger`, `ContextMenuContent`, `ContextMenuItem`, `ContextMenuLabel`, `ContextMenuSeparator`, `ContextMenuShortcut`, `ContextMenuGroup`, `ContextMenuSub`, `ContextMenuSubTrigger`, `ContextMenuSubContent`, `ContextMenuCheckboxItem`, `ContextMenuRadioGroup`, `ContextMenuRadioItem`
1589
+
1590
+ **`ContextMenuItem` extra props**
1591
+
1592
+ | Prop | Type | Default | Description |
1593
+ |---|---|---|---|
1594
+ | `variant` | `'default' \| 'danger'` | `'default'` | Renders the item in danger color |
1595
+ | `inset` | `boolean` | — | Adds extra left padding for alignment without an icon |
1596
+
1597
+ ```vue
1598
+ <script setup>
1599
+ import {
1600
+ ContextMenu,
1601
+ ContextMenuTrigger,
1602
+ ContextMenuContent,
1603
+ ContextMenuItem,
1604
+ ContextMenuSeparator,
1605
+ } from '@buillaume.biondo/fab-ui'
1606
+ </script>
1607
+
1608
+ <template>
1609
+ <ContextMenu>
1610
+ <ContextMenuTrigger>
1611
+ <div class="p-8 border rounded">Right-click me</div>
1612
+ </ContextMenuTrigger>
1613
+ <ContextMenuContent>
1614
+ <ContextMenuItem>Edit</ContextMenuItem>
1615
+ <ContextMenuItem>Duplicate</ContextMenuItem>
1616
+ <ContextMenuSeparator />
1617
+ <ContextMenuItem variant="danger">Delete</ContextMenuItem>
1618
+ </ContextMenuContent>
1619
+ </ContextMenu>
1620
+ </template>
1621
+ ```
1622
+
1623
+ ---
1624
+
1625
+ ### Dialog
1626
+
1627
+ Modal dialog built on reka-ui with animated overlay, a close button, and a scrollable variant.
1628
+
1629
+ **Exported components**
1630
+
1631
+ `Dialog`, `DialogTrigger`, `DialogContent`, `DialogScrollContent`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`, `DialogClose`, `DialogOverlay`
1632
+
1633
+ **`DialogContent` extra props**
1634
+
1635
+ | Prop | Type | Default | Description |
1636
+ |---|---|---|---|
1637
+ | `showCloseButton` | `boolean` | `true` | Renders the `×` close button in the top-right corner |
1638
+
1639
+ ```vue
1640
+ <script setup>
1641
+ import {
1642
+ Dialog,
1643
+ DialogTrigger,
1644
+ DialogContent,
1645
+ DialogHeader,
1646
+ DialogTitle,
1647
+ DialogDescription,
1648
+ DialogFooter,
1649
+ Button,
1650
+ } from '@buillaume.biondo/fab-ui'
1651
+ </script>
1652
+
1653
+ <template>
1654
+ <Dialog>
1655
+ <DialogTrigger as-child>
1656
+ <Button>Open dialog</Button>
1657
+ </DialogTrigger>
1658
+ <DialogContent>
1659
+ <DialogHeader>
1660
+ <DialogTitle>Confirm deletion</DialogTitle>
1661
+ <DialogDescription>
1662
+ This action cannot be undone. Are you sure?
1663
+ </DialogDescription>
1664
+ </DialogHeader>
1665
+ <DialogFooter>
1666
+ <Button shape="outline" variant="secondary">Cancel</Button>
1667
+ <Button variant="danger">Delete</Button>
1668
+ </DialogFooter>
1669
+ </DialogContent>
1670
+ </Dialog>
1671
+ </template>
1672
+ ```
1673
+
1674
+ ---
1675
+
1676
+ ### Sheet
1677
+
1678
+ Slide-in panel (drawer) that opens from any side of the screen. Built on the same Dialog primitives as the `Dialog` component.
1679
+
1680
+ **Exported components**
1681
+
1682
+ `Sheet`, `SheetTrigger`, `SheetContent`, `SheetHeader`, `SheetFooter`, `SheetTitle`, `SheetDescription`, `SheetClose`, `SheetOverlay`
1683
+
1684
+ **`SheetContent` extra props**
1685
+
1686
+ | Prop | Type | Default | Description |
1687
+ |---|---|---|---|
1688
+ | `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | The edge the sheet slides in from |
1689
+
1690
+ ```vue
1691
+ <script setup>
1692
+ import {
1693
+ Sheet,
1694
+ SheetTrigger,
1695
+ SheetContent,
1696
+ SheetHeader,
1697
+ SheetTitle,
1698
+ SheetDescription,
1699
+ Button,
1700
+ } from '@buillaume.biondo/fab-ui'
1701
+ </script>
1702
+
1703
+ <template>
1704
+ <Sheet>
1705
+ <SheetTrigger as-child>
1706
+ <Button shape="outline">Open panel</Button>
1707
+ </SheetTrigger>
1708
+ <SheetContent side="right">
1709
+ <SheetHeader>
1710
+ <SheetTitle>Settings</SheetTitle>
1711
+ <SheetDescription>Adjust your preferences.</SheetDescription>
1712
+ </SheetHeader>
1713
+ <p class="py-4">Sheet content goes here.</p>
1714
+ </SheetContent>
1715
+ </Sheet>
1716
+ </template>
1717
+ ```
1718
+
1719
+ ---
1720
+
1721
+ ### Breadcrumb
1722
+
1723
+ Semantic breadcrumb navigation with built-in separator, ellipsis, and current page support.
1724
+
1725
+ **Exported components**
1726
+
1727
+ `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage`, `BreadcrumbSeparator`, `BreadcrumbEllipsis`
1728
+
1729
+ ```vue
1730
+ <script setup>
1731
+ import {
1732
+ Breadcrumb,
1733
+ BreadcrumbList,
1734
+ BreadcrumbItem,
1735
+ BreadcrumbLink,
1736
+ BreadcrumbSeparator,
1737
+ BreadcrumbPage,
1738
+ BreadcrumbEllipsis,
1739
+ } from '@buillaume.biondo/fab-ui'
1740
+ </script>
1741
+
1742
+ <template>
1743
+ <Breadcrumb>
1744
+ <BreadcrumbList>
1745
+ <BreadcrumbItem>
1746
+ <BreadcrumbLink href="/">Home</BreadcrumbLink>
1747
+ </BreadcrumbItem>
1748
+ <BreadcrumbSeparator />
1749
+ <BreadcrumbItem>
1750
+ <BreadcrumbEllipsis />
1751
+ </BreadcrumbItem>
1752
+ <BreadcrumbSeparator />
1753
+ <BreadcrumbItem>
1754
+ <BreadcrumbLink href="/docs">Docs</BreadcrumbLink>
1755
+ </BreadcrumbItem>
1756
+ <BreadcrumbSeparator />
1757
+ <BreadcrumbItem>
1758
+ <BreadcrumbPage>Getting started</BreadcrumbPage>
1759
+ </BreadcrumbItem>
1760
+ </BreadcrumbList>
1761
+ </Breadcrumb>
1762
+ </template>
1763
+ ```
1764
+
1765
+ ---
1766
+
1767
+ ### NavigationMenu
1768
+
1769
+ Horizontal navigation menu with animated content panels, built on reka-ui.
1770
+
1771
+ **Exported components**
1772
+
1773
+ `NavigationMenu`, `NavigationMenuList`, `NavigationMenuItem`, `NavigationMenuTrigger`, `NavigationMenuContent`, `NavigationMenuLink`, `NavigationMenuIndicator`, `NavigationMenuViewport`
1774
+
1775
+ Also exports `navigationMenuTriggerStyle` — a CVA function that returns the trigger button classes.
1776
+
1777
+ ```vue
1778
+ <script setup>
1779
+ import {
1780
+ NavigationMenu,
1781
+ NavigationMenuList,
1782
+ NavigationMenuItem,
1783
+ NavigationMenuTrigger,
1784
+ NavigationMenuContent,
1785
+ NavigationMenuLink,
1786
+ navigationMenuTriggerStyle,
1787
+ } from '@buillaume.biondo/fab-ui'
1788
+ </script>
1789
+
1790
+ <template>
1791
+ <NavigationMenu>
1792
+ <NavigationMenuList>
1793
+ <NavigationMenuItem>
1794
+ <NavigationMenuTrigger>Products</NavigationMenuTrigger>
1795
+ <NavigationMenuContent>
1796
+ <ul class="grid w-[400px] gap-3 p-4">
1797
+ <li><NavigationMenuLink href="/products/alpha">Alpha</NavigationMenuLink></li>
1798
+ <li><NavigationMenuLink href="/products/beta">Beta</NavigationMenuLink></li>
1799
+ </ul>
1800
+ </NavigationMenuContent>
1801
+ </NavigationMenuItem>
1802
+ <NavigationMenuItem>
1803
+ <NavigationMenuLink :class="navigationMenuTriggerStyle()" href="/about">
1804
+ About
1805
+ </NavigationMenuLink>
1806
+ </NavigationMenuItem>
1807
+ </NavigationMenuList>
1808
+ </NavigationMenu>
1809
+ </template>
1810
+ ```
1811
+
1812
+ ---
1813
+
1814
+ ### Sidebar
1815
+
1816
+ A complete collapsible application sidebar system. It supports three visual variants (`sidebar`, `floating`, `inset`), two collapse modes (`offcanvas`, `icon`), and a full set of composition components for menus, groups, badges, and sub-menus.
1817
+
1818
+ **Exported components**
1819
+
1820
+ `SidebarProvider`, `Sidebar`, `SidebarTrigger`, `SidebarRail`, `SidebarInset`, `SidebarHeader`, `SidebarContent`, `SidebarFooter`, `SidebarSeparator`, `SidebarGroup`, `SidebarGroupLabel`, `SidebarGroupAction`, `SidebarGroupContent`, `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton`, `SidebarMenuAction`, `SidebarMenuBadge`, `SidebarMenuSkeleton`, `SidebarMenuSub`, `SidebarMenuSubItem`, `SidebarMenuSubButton`, `SidebarInput`
1821
+
1822
+ Also exports the `useSidebar` composable.
1823
+
1824
+ **`SidebarProvider` / `Sidebar` key props**
1825
+
1826
+ | Prop | Type | Default | Description |
1827
+ |---|---|---|---|
1828
+ | `side` | `'left' \| 'right'` | `'left'` | Sidebar position |
1829
+ | `variant` | `'sidebar' \| 'floating' \| 'inset'` | `'sidebar'` | Visual style |
1830
+ | `collapsible` | `'offcanvas' \| 'icon' \| 'none'` | `'offcanvas'` | Collapse behavior |
1831
+
1832
+ ```vue
1833
+ <script setup>
1834
+ import {
1835
+ SidebarProvider,
1836
+ Sidebar,
1837
+ SidebarHeader,
1838
+ SidebarContent,
1839
+ SidebarGroup,
1840
+ SidebarGroupLabel,
1841
+ SidebarGroupContent,
1842
+ SidebarMenu,
1843
+ SidebarMenuItem,
1844
+ SidebarMenuButton,
1845
+ SidebarTrigger,
1846
+ SidebarInset,
1847
+ } from '@buillaume.biondo/fab-ui'
1848
+ import { HomeIcon, SettingsIcon } from 'lucide-vue-next'
1849
+ </script>
1850
+
1851
+ <template>
1852
+ <SidebarProvider>
1853
+ <Sidebar collapsible="icon">
1854
+ <SidebarHeader>
1855
+ <span class="font-semibold">My App</span>
1856
+ </SidebarHeader>
1857
+ <SidebarContent>
1858
+ <SidebarGroup>
1859
+ <SidebarGroupLabel>Navigation</SidebarGroupLabel>
1860
+ <SidebarGroupContent>
1861
+ <SidebarMenu>
1862
+ <SidebarMenuItem>
1863
+ <SidebarMenuButton as-child>
1864
+ <a href="/"><HomeIcon /> Dashboard</a>
1865
+ </SidebarMenuButton>
1866
+ </SidebarMenuItem>
1867
+ <SidebarMenuItem>
1868
+ <SidebarMenuButton as-child>
1869
+ <a href="/settings"><SettingsIcon /> Settings</a>
1870
+ </SidebarMenuButton>
1871
+ </SidebarMenuItem>
1872
+ </SidebarMenu>
1873
+ </SidebarGroupContent>
1874
+ </SidebarGroup>
1875
+ </SidebarContent>
1876
+ </Sidebar>
1877
+
1878
+ <SidebarInset>
1879
+ <header class="flex items-center gap-2 p-4">
1880
+ <SidebarTrigger />
1881
+ <h1>Page title</h1>
1882
+ </header>
1883
+ <main class="p-4">
1884
+ <slot />
1885
+ </main>
1886
+ </SidebarInset>
1887
+ </SidebarProvider>
1888
+ </template>
1889
+ ```
1890
+
1891
+ ---
1892
+
1893
+ ### Tabs
1894
+
1895
+ Accessible tabbed interface built on reka-ui's `TabsRoot` / `TabsList` / `TabsTrigger` / `TabsContent` primitives. The `placement` prop controls whether tabs appear on top, left, or right of the content panel; an animated indicator bar slides to the active tab automatically.
1896
+
1897
+ The component tree is: `Tabs` → `TabsList` → `TabsTrigger` (×n) + `TabsContent` (×n).
1898
+
1899
+ #### Tabs
1900
+
1901
+ Root container. Manages selection state, sets the CSS color variable `--tabs-color`, and provides `placement`, `variant`, and `size` to child components via `provide/inject`.
1902
+
1903
+ **Props**
1904
+
1905
+ Accepts all props from reka-ui's `TabsRootProps` (except `orientation`, derived automatically) plus:
1906
+
1907
+ | Prop | Type | Default | Description |
1908
+ |---|---|---|---|
1909
+ | `modelValue` | `string` | — | Controlled active tab value (`v-model`) |
1910
+ | `defaultValue` | `string` | — | Initial active tab (uncontrolled) |
1911
+ | `placement` | `'top' \| 'left' \| 'right'` | `'top'` | Position of the tab list relative to the panels |
1912
+ | `variant` | `Severity` | `'primary'` | Color of the active indicator and trigger text |
1913
+ | `size` | `'small' \| 'default' \| 'large'` | `'default'` | Size of the trigger buttons |
1914
+ | `activationMode` | `'automatic' \| 'manual'` | `'automatic'` | Whether tabs activate on focus or on click |
1915
+ | `class` | `string` | — | Additional CSS classes on the root element |
1916
+
1917
+ **Emits**
1918
+
1919
+ | Event | Payload | Description |
1920
+ |---|---|---|
1921
+ | `update:modelValue` | `string` | Fired when the active tab changes |
1922
+
1923
+ #### TabsList
1924
+
1925
+ Wrapper for the row or column of `TabsTrigger` buttons. Includes a built-in `TabsIndicator` that slides to the active trigger with a CSS transition.
1926
+
1927
+ **Props**
1928
+
1929
+ Accepts all props from reka-ui's `TabsListProps` plus:
1930
+
1931
+ | Prop | Type | Default | Description |
1932
+ |---|---|---|---|
1933
+ | `loop` | `boolean` | `false` | Keyboard navigation wraps from last to first trigger |
1934
+ | `class` | `string` | — | Additional CSS classes |
1935
+
1936
+ #### TabsTrigger
1937
+
1938
+ Individual tab button. Reads `size` and `placement` from the parent `Tabs` context.
1939
+
1940
+ **Props**
1941
+
1942
+ Accepts all props from reka-ui's `TabsTriggerProps` plus:
1943
+
1944
+ | Prop | Type | Default | Description |
1945
+ |---|---|---|---|
1946
+ | `value` | `string` | **required** | Unique identifier matching a `TabsContent` value |
1947
+ | `disabled` | `boolean` | `false` | Prevents selection and dims the trigger |
1948
+ | `class` | `string` | — | Additional CSS classes |
1949
+
1950
+ #### TabsContent
1951
+
1952
+ Panel revealed when the associated trigger is active. Takes `flex-1` to fill the available space alongside a vertical tab list.
1953
+
1954
+ **Props**
1955
+
1956
+ Accepts all props from reka-ui's `TabsContentProps` plus:
1957
+
1958
+ | Prop | Type | Default | Description |
1959
+ |---|---|---|---|
1960
+ | `value` | `string` | **required** | Must match the corresponding `TabsTrigger` value |
1961
+ | `forceMount` | `boolean` | `false` | Keeps the panel mounted even when inactive (useful for animation libraries) |
1962
+ | `class` | `string` | — | Additional CSS classes |
1963
+
1964
+ ```vue
1965
+ <script setup>
1966
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@buillaume.biondo/fab-ui'
1967
+ import { ref } from 'vue'
1968
+
1969
+ const active = ref('account')
1970
+ </script>
1971
+
1972
+ <template>
1973
+ <!-- Top (default) -->
1974
+ <Tabs v-model="active" placement="top">
1975
+ <TabsList>
1976
+ <TabsTrigger value="account">Account</TabsTrigger>
1977
+ <TabsTrigger value="security">Security</TabsTrigger>
1978
+ <TabsTrigger value="billing">Billing</TabsTrigger>
1979
+ <TabsTrigger value="disabled" disabled>Disabled</TabsTrigger>
1980
+ </TabsList>
1981
+ <TabsContent value="account" class="p-4">Account settings…</TabsContent>
1982
+ <TabsContent value="security" class="p-4">Security settings…</TabsContent>
1983
+ <TabsContent value="billing" class="p-4">Billing settings…</TabsContent>
1984
+ </Tabs>
1985
+
1986
+ <!-- Left placement -->
1987
+ <Tabs v-model="active" placement="left" variant="success">
1988
+ <TabsList>
1989
+ <TabsTrigger value="profile">Profile</TabsTrigger>
1990
+ <TabsTrigger value="team">Team</TabsTrigger>
1991
+ </TabsList>
1992
+ <TabsContent value="profile" class="p-6">Profile panel…</TabsContent>
1993
+ <TabsContent value="team" class="p-6">Team panel…</TabsContent>
1994
+ </Tabs>
1995
+
1996
+ <!-- Right placement, large size, danger color -->
1997
+ <Tabs v-model="active" placement="right" variant="danger" size="large">
1998
+ <TabsList>
1999
+ <TabsTrigger value="logs">Logs</TabsTrigger>
2000
+ <TabsTrigger value="alerts">Alerts</TabsTrigger>
2001
+ </TabsList>
2002
+ <TabsContent value="logs" class="p-6">Logs panel…</TabsContent>
2003
+ <TabsContent value="alerts" class="p-6">Alerts panel…</TabsContent>
2004
+ </Tabs>
2005
+
2006
+ <!-- Uncontrolled with defaultValue -->
2007
+ <Tabs default-value="tab1">
2008
+ <TabsList>
2009
+ <TabsTrigger value="tab1">Tab 1</TabsTrigger>
2010
+ <TabsTrigger value="tab2">Tab 2</TabsTrigger>
2011
+ </TabsList>
2012
+ <TabsContent value="tab1" class="p-4">Content 1</TabsContent>
2013
+ <TabsContent value="tab2" class="p-4">Content 2</TabsContent>
2014
+ </Tabs>
2015
+ </template>
2016
+ ```
2017
+
2018
+ ---
2019
+
2020
+ ## Composables
2021
+
2022
+ ### useToast
2023
+
2024
+ Global toast notification state. The state is shared at the module level — calling `useToast()` anywhere returns the same reactive instance.
2025
+
2026
+ ```ts
2027
+ import { useToast } from '@buillaume.biondo/fab-ui'
2028
+
2029
+ const toast = useToast()
2030
+ ```
2031
+
2032
+ **Returned methods**
2033
+
2034
+ | Method | Signature | Description |
2035
+ |---|---|---|
2036
+ | `success` | `(message: string, options?: ToastOptions) => string` | Shows a success toast (auto-dismisses after 4s) |
2037
+ | `info` | `(message: string, options?: ToastOptions) => string` | Shows an info toast (auto-dismisses after 4s) |
2038
+ | `warning` | `(message: string, options?: ToastOptions) => string` | Shows a warning toast (auto-dismisses after 6s) |
2039
+ | `error` | `(message: string, options?: ToastOptions) => string` | Shows a persistent error toast |
2040
+ | `dismiss` | `(id: string) => void` | Programmatically removes a toast by its ID |
2041
+ | `toasts` | `Toast[]` (reactive) | The current list of active toasts |
2042
+
2043
+ **`ToastOptions`**
2044
+
2045
+ ```ts
2046
+ interface ToastOptions {
2047
+ duration?: number // Override auto-dismiss duration in ms
2048
+ persistent?: boolean // Prevent auto-dismiss
2049
+ }
2050
+ ```
2051
+
2052
+ ```ts
2053
+ const toast = useToast()
2054
+
2055
+ // Basic usage
2056
+ toast.success('Your profile has been updated.')
2057
+ toast.error('Failed to connect to the server.')
2058
+
2059
+ // Custom duration
2060
+ toast.warning('Session expiring soon.', { duration: 10_000 })
2061
+
2062
+ // Persistent toast with manual dismiss
2063
+ const id = toast.info('Processing your request…', { persistent: true })
2064
+ await doSomething()
2065
+ toast.dismiss(id)
2066
+ ```
2067
+
2068
+ ---
2069
+
2070
+ ### useAppearance
2071
+
2072
+ Manages the application's visual theme (light / dark / system) and border radius mode (default / square). Persists preferences to `localStorage` and a cookie for SSR compatibility.
2073
+
2074
+ ```ts
2075
+ import { useAppearance } from '@buillaume.biondo/fab-ui'
2076
+
2077
+ const {
2078
+ appearance,
2079
+ resolvedAppearance,
2080
+ updateAppearance,
2081
+ radiusMode,
2082
+ updateRadiusMode,
2083
+ } = useAppearance()
2084
+ ```
2085
+
2086
+ **Returned values**
2087
+
2088
+ | Property | Type | Description |
2089
+ |---|---|---|
2090
+ | `appearance` | `Ref<'light' \| 'dark' \| 'system'>` | Current preference |
2091
+ | `resolvedAppearance` | `ComputedRef<'light' \| 'dark'>` | Resolved value (resolves `'system'` against `prefers-color-scheme`) |
2092
+ | `updateAppearance` | `(value: Appearance) => void` | Updates and persists the theme |
2093
+ | `radiusMode` | `Ref<'default' \| 'square'>` | Current border radius mode |
2094
+ | `updateRadiusMode` | `(value: RadiusMode) => void` | Updates and persists the radius mode |
2095
+
2096
+ Also exports `initializeTheme()` — call it once in your app entry point (before mounting) to apply the persisted theme immediately and avoid a flash of unstyled content.
2097
+
2098
+ ```ts
2099
+ // main.ts
2100
+ import { createApp } from 'vue'
2101
+ import { initializeTheme } from '@buillaume.biondo/fab-ui'
2102
+ import App from './App.vue'
2103
+
2104
+ initializeTheme()
2105
+ createApp(App).mount('#app')
2106
+ ```
2107
+
2108
+ ```vue
2109
+ <script setup>
2110
+ import { useAppearance } from '@buillaume.biondo/fab-ui'
2111
+
2112
+ const { appearance, updateAppearance, radiusMode, updateRadiusMode } = useAppearance()
2113
+ </script>
2114
+
2115
+ <template>
2116
+ <select :value="appearance" @change="updateAppearance($event.target.value)">
2117
+ <option value="light">Light</option>
2118
+ <option value="dark">Dark</option>
2119
+ <option value="system">System</option>
2120
+ </select>
2121
+
2122
+ <label>
2123
+ <input
2124
+ type="checkbox"
2125
+ :checked="radiusMode === 'square'"
2126
+ @change="updateRadiusMode(radiusMode === 'square' ? 'default' : 'square')"
2127
+ />
2128
+ Square corners
2129
+ </label>
2130
+ </template>
2131
+ ```
2132
+
2133
+ ---
2134
+
2135
+ ## Roadmap
2136
+
2137
+ The following components are planned for upcoming releases:
2138
+
2139
+ - **Radio** — Styled radio buttons with severity/variant support and a radio group wrapper
2140
+ - **Divider** — Horizontal/vertical separator with an optional centered label
2141
+ - **Tabs** — Tab navigation with `Tab`, `TabList`, and `TabPanel` subcomponents and multiple visual variants
2142
+
2143
+ Contributions are welcome. Feel free to open an issue to discuss a feature or submit a pull request.
2144
+
2145
+ ---
2146
+
2147
+ ## License
2148
+
2149
+ [MIT](./LICENSE) — Guillaume Biondo