cisse-vue-ui 0.5.26 → 0.5.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +763 -763
  2. package/dist/Button.vue_vue_type_script_setup_true_lang-BHpVJnRn.js.map +1 -1
  3. package/dist/Button.vue_vue_type_script_setup_true_lang-CLmHDal2.cjs.map +1 -1
  4. package/dist/{Combobox.vue_vue_type_script_setup_true_lang-B8WioleN.cjs → Combobox.vue_vue_type_script_setup_true_lang-C2z3wwmX.cjs} +2 -2
  5. package/dist/Combobox.vue_vue_type_script_setup_true_lang-C2z3wwmX.cjs.map +1 -0
  6. package/dist/{Combobox.vue_vue_type_script_setup_true_lang-DKDJV0Ey.js → Combobox.vue_vue_type_script_setup_true_lang-Dlza3xUG.js} +2 -2
  7. package/dist/Combobox.vue_vue_type_script_setup_true_lang-Dlza3xUG.js.map +1 -0
  8. package/dist/{ConfirmDialog.vue_vue_type_script_setup_true_lang-B1zS6nTR.js → ConfirmDialog.vue_vue_type_script_setup_true_lang-ClT3hod7.js} +2 -2
  9. package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-ClT3hod7.js.map +1 -0
  10. package/dist/{ConfirmDialog.vue_vue_type_script_setup_true_lang-CvEWAzaw.cjs → ConfirmDialog.vue_vue_type_script_setup_true_lang-iJCk_Dvc.cjs} +2 -2
  11. package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-iJCk_Dvc.cjs.map +1 -0
  12. package/dist/DarkModeToggle.vue_vue_type_script_setup_true_lang-BBIkEeLJ.js.map +1 -1
  13. package/dist/DarkModeToggle.vue_vue_type_script_setup_true_lang-BHabkuFp.cjs.map +1 -1
  14. package/dist/Dropdown.vue_vue_type_script_setup_true_lang-BNOHbbm5.cjs.map +1 -1
  15. package/dist/Dropdown.vue_vue_type_script_setup_true_lang-nyND94f_.js.map +1 -1
  16. package/dist/{PageLayout.vue_vue_type_script_setup_true_lang-BgTJd526.js → PageLayout.vue_vue_type_script_setup_true_lang-CbTq_AlS.js} +3 -3
  17. package/dist/PageLayout.vue_vue_type_script_setup_true_lang-CbTq_AlS.js.map +1 -0
  18. package/dist/{PageLayout.vue_vue_type_script_setup_true_lang-6OrQy9W4.cjs → PageLayout.vue_vue_type_script_setup_true_lang-DvLrltTx.cjs} +3 -3
  19. package/dist/PageLayout.vue_vue_type_script_setup_true_lang-DvLrltTx.cjs.map +1 -0
  20. package/dist/cisse-vue-ui.css +51 -51
  21. package/dist/components/feedback/index.cjs +1 -1
  22. package/dist/components/feedback/index.js +1 -1
  23. package/dist/components/form/index.cjs +1 -1
  24. package/dist/components/form/index.js +1 -1
  25. package/dist/components/index.cjs +3 -3
  26. package/dist/components/index.js +3 -3
  27. package/dist/components/layout/index.cjs +1 -1
  28. package/dist/components/layout/index.js +1 -1
  29. package/dist/{index-D5M6ePuo.cjs → index-BbswCyjp.cjs} +4 -4
  30. package/dist/index-BbswCyjp.cjs.map +1 -0
  31. package/dist/{index-yQvianuj.js → index-C7jYhMMH.js} +4 -4
  32. package/dist/index-C7jYhMMH.js.map +1 -0
  33. package/dist/index.cjs +4 -4
  34. package/dist/index.js +4 -4
  35. package/dist/style.css +1 -1
  36. package/dist/useDropdown-DHFnd259.cjs.map +1 -1
  37. package/dist/useDropdown-iVu14E6s.js.map +1 -1
  38. package/dist/useFocusTrap-AnlJsihM.js.map +1 -1
  39. package/dist/useFocusTrap-kcxO8AeU.cjs.map +1 -1
  40. package/dist/useId-nxrBaIC9.cjs.map +1 -1
  41. package/dist/useId-xeHj7rkg.js.map +1 -1
  42. package/dist/useToast-Bk60GArg.cjs.map +1 -1
  43. package/dist/useToast-ina5g3mj.js.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/Combobox.vue_vue_type_script_setup_true_lang-B8WioleN.cjs.map +0 -1
  46. package/dist/Combobox.vue_vue_type_script_setup_true_lang-DKDJV0Ey.js.map +0 -1
  47. package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-B1zS6nTR.js.map +0 -1
  48. package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-CvEWAzaw.cjs.map +0 -1
  49. package/dist/PageLayout.vue_vue_type_script_setup_true_lang-6OrQy9W4.cjs.map +0 -1
  50. package/dist/PageLayout.vue_vue_type_script_setup_true_lang-BgTJd526.js.map +0 -1
  51. package/dist/index-D5M6ePuo.cjs.map +0 -1
  52. package/dist/index-yQvianuj.js.map +0 -1
package/README.md CHANGED
@@ -1,763 +1,763 @@
1
- # cisse-vue-ui
2
-
3
- A Vue 3 component library built with TypeScript and Tailwind CSS v4.
4
-
5
- **[View Storybook Documentation](https://moulayecisse.github.io/cisse-vue-ui/)**
6
-
7
- ## Installation
8
-
9
- ```bash
10
- npm install cisse-vue-ui
11
- # or
12
- bun add cisse-vue-ui
13
- ```
14
-
15
- ### Peer Dependencies
16
-
17
- ```bash
18
- npm install vue@^3.4 tailwindcss@^4 @iconify/vue@^4
19
- ```
20
-
21
- ## Setup
22
-
23
- ### 1. Import Styles
24
-
25
- Add the pre-compiled CSS to your main CSS file:
26
-
27
- ```css
28
- @import 'cisse-vue-ui/style.css';
29
- @import 'tailwindcss';
30
- ```
31
-
32
- ### 2. Configure Primary Color (Optional)
33
-
34
- Override the default primary color in your CSS:
35
-
36
- ```css
37
- @theme {
38
- --color-primary-50: oklch(97% 0.02 142);
39
- --color-primary-100: oklch(94% 0.05 142);
40
- --color-primary-200: oklch(88% 0.10 142);
41
- --color-primary-300: oklch(78% 0.15 142);
42
- --color-primary-400: oklch(65% 0.20 142);
43
- --color-primary-500: oklch(55% 0.22 142);
44
- --color-primary-600: oklch(48% 0.20 142);
45
- --color-primary-700: oklch(40% 0.17 142);
46
- --color-primary-800: oklch(32% 0.14 142);
47
- --color-primary-900: oklch(25% 0.10 142);
48
- --color-primary-950: oklch(18% 0.08 142);
49
- }
50
- ```
51
-
52
- ## Usage
53
-
54
- ### Tree-Shaken Imports (Recommended)
55
-
56
- ```vue
57
- <script setup lang="ts">
58
- import { Button, CardComponent, FormInput } from 'cisse-vue-ui'
59
- </script>
60
- ```
61
-
62
- ### Category Imports
63
-
64
- ```typescript
65
- import { Button, Tabs, TabPanel } from 'cisse-vue-ui/components/core'
66
- import { FormInput, FormSelect, Switch } from 'cisse-vue-ui/components/form'
67
- import { Modal, Alert, LoadingSpinner } from 'cisse-vue-ui/components/feedback'
68
- import { BaseLayout, PageLayout } from 'cisse-vue-ui/components/layout'
69
- ```
70
-
71
- ### Global Registration (Vue Plugin)
72
-
73
- ```typescript
74
- import { createApp } from 'vue'
75
- import { VueTailwindUI } from 'cisse-vue-ui'
76
-
77
- const app = createApp(App)
78
-
79
- // Register all components
80
- app.use(VueTailwindUI)
81
-
82
- // Or with a prefix
83
- app.use(VueTailwindUI, { prefix: 'Ui' }) // <UiButton>, <UiCard>, etc.
84
-
85
- // Or specific components only
86
- app.use(VueTailwindUI, { components: ['Button', 'CardComponent'] })
87
- ```
88
-
89
- ## Components
90
-
91
- ### Core
92
-
93
- | Component | Description |
94
- |-----------|-------------|
95
- | `Button` | Button with variants (primary, secondary, outline, ghost, danger, success), sizes, icons, loading state |
96
- | `CardComponent` | Card container with header, content, and footer slots |
97
- | `TableComponent` | Data table with sorting, selection, actions, and custom column rendering |
98
- | `MobileList` | Mobile-optimized card-based list with selection support |
99
- | `ResponsiveList` | Combines MobileList (mobile) and TableComponent (desktop) with automatic breakpoint switching |
100
- | `Tabs` | Tab navigation with variants (underline, pills, boxed) |
101
- | `TabPanel` | Tab content panel (use with Tabs) |
102
- | `Dropdown` | Dropdown menu with items, icons, and dividers |
103
- | `Avatar` | User avatar with image, initials, or icon fallback |
104
- | `AutocompleteComponent` | Searchable select with keyboard navigation |
105
- | `MenuItem` | Navigation menu item with icon, active state detection, and route support |
106
- | `StatusBadge` | Colored status indicator badge |
107
- | `TableAction` | Icon button for table row actions |
108
- | `Stepper` | Multi-step progress indicator with horizontal/vertical orientation |
109
- | `CollapsibleCard` | Card that can expand/collapse its content |
110
- | `Accordion` | Expandable content sections with single/multiple mode |
111
- | `Breadcrumb` | Navigation breadcrumb trail |
112
- | `Drawer` | Slide-out panel from any edge (left, right, top, bottom) |
113
- | `Popover` | Floating content panel triggered by click or hover |
114
- | `Timeline` | Vertical timeline for events/history display |
115
- | `Tooltip` | Hover tooltip with customizable position |
116
-
117
- ### Form
118
-
119
- | Component | Description |
120
- |-----------|-------------|
121
- | `FormInput` | Text input with validation states and ARIA support |
122
- | `FormSelect` | Select dropdown with search, multi-select, and validation |
123
- | `FormGroup` | Form field wrapper with label, help text, and error states |
124
- | `FormLabel` | Styled form label with required indicator |
125
- | `FormHelp` | Help/error text for form fields |
126
- | `SearchInput` | Search input with icon and clear button |
127
- | `Switch` | Toggle switch with label and description |
128
- | `Checkbox` | Checkbox with label, description, and indeterminate state |
129
- | `Combobox` | Multi-select combobox with search and tags |
130
- | `DatePicker` | Calendar date picker with min/max dates |
131
- | `ColorPicker` | Color selection with swatches and custom input |
132
- | `FileUpload` | Drag-and-drop file upload with preview |
133
- | `Rating` | Star rating input with half-star support |
134
- | `Slider` | Single value slider input |
135
- | `RangeSlider` | Dual-handle range slider |
136
-
137
- ### Feedback
138
-
139
- | Component | Description |
140
- |-----------|-------------|
141
- | `Modal` | Modal dialog with focus trap, ARIA support, and slots |
142
- | `ConfirmDialog` | Confirmation modal with customizable actions |
143
- | `Alert` | Alert banner with variants (info, success, warning, error) |
144
- | `Toast` | Individual toast notification with auto-dismiss |
145
- | `ToastContainer` | Toast notification container with positioning |
146
- | `LoadingSpinner` | Loading indicator with size variants |
147
- | `Progress` | Progress bar with percentage display |
148
- | `Skeleton` | Loading placeholder with animation |
149
- | `CardSkeleton` | Card loading skeleton |
150
- | `ListSkeleton` | List loading skeleton |
151
- | `TableSkeleton` | Table loading skeleton |
152
- | `PaginationControls` | Pagination with page numbers and navigation |
153
- | `NotificationList` | Notification list container |
154
- | `NotificationComponent` | Individual notification item |
155
- | `EmptyState` | Placeholder for empty content with icon and action slot |
156
-
157
- ### Layout
158
-
159
- | Component | Description |
160
- |-----------|-------------|
161
- | `BaseLayout` | App shell with sidebar, header, main content area, and route-aware menu |
162
- | `PageLayout` | Page wrapper with breadcrumbs |
163
-
164
- ### Type Display
165
-
166
- | Component | Description |
167
- |-----------|-------------|
168
- | `TextType` | Text value display |
169
- | `NumberType` | Formatted number display |
170
- | `DateType` | Formatted date display |
171
- | `BooleanType` | Boolean value display (check/cross icons) |
172
- | `BadgeType` | Badge value display with colors |
173
-
174
- ## Composables
175
-
176
- ```typescript
177
- import {
178
- useNotifications,
179
- useDarkMode,
180
- useExportCSV,
181
- useDropdown,
182
- useModal,
183
- useToast,
184
- useFocusTrap,
185
- useId
186
- } from 'cisse-vue-ui/composables'
187
- ```
188
-
189
- ### useModal
190
-
191
- Manage modal state with data support:
192
-
193
- ```typescript
194
- import { useModal } from 'cisse-vue-ui/composables'
195
-
196
- // Simple modal
197
- const createModal = useModal()
198
- createModal.open()
199
- createModal.close()
200
-
201
- // Modal with data (e.g., for editing)
202
- const editModal = useModal<User>()
203
- editModal.open(selectedUser)
204
- // Access editModal.data.value in template
205
-
206
- // With callbacks
207
- const deleteModal = useModal<Item>({
208
- onOpen: (data) => console.log('Opening with:', data),
209
- onClose: () => refetchData()
210
- })
211
- ```
212
-
213
- ```vue
214
- <template>
215
- <!-- Use isOpen for v-model binding -->
216
- <Modal v-model="editModal.isOpen.value" title="Edit User">
217
- <FormInput v-model="editModal.data.value.name" label="Name" />
218
- <template #footer>
219
- <Button @click="editModal.close()">Cancel</Button>
220
- <Button variant="primary" @click="save">Save</Button>
221
- </template>
222
- </Modal>
223
- </template>
224
- ```
225
-
226
- ### useDropdown
227
-
228
- Shared dropdown logic for custom dropdown components (used internally by Dropdown, FormSelect, AutocompleteComponent):
229
-
230
- ```typescript
231
- import { useDropdown } from 'cisse-vue-ui/composables'
232
- import { ref } from 'vue'
233
-
234
- const triggerRef = ref<HTMLElement>()
235
- const dropdownRef = ref<HTMLElement>()
236
-
237
- const {
238
- isOpen,
239
- highlightedIndex,
240
- dropdownStyle,
241
- open,
242
- close,
243
- toggle,
244
- handleKeydown,
245
- scrollToHighlighted,
246
- } = useDropdown(triggerRef, dropdownRef, {
247
- teleport: true,
248
- align: 'left',
249
- gap: 8,
250
- onOpen: () => console.log('Opened'),
251
- onClose: () => console.log('Closed'),
252
- })
253
- ```
254
-
255
- ### useNotifications
256
-
257
- ```typescript
258
- const { notifications, addNotification, removeNotification } = useNotifications()
259
-
260
- addNotification({
261
- type: 'success',
262
- title: 'Saved',
263
- message: 'Your changes have been saved.'
264
- })
265
- ```
266
-
267
- ### useDarkMode
268
-
269
- ```typescript
270
- const { isDark, toggle, enable, disable } = useDarkMode({
271
- selector: 'html', // Element to add .dark class
272
- storageKey: 'theme', // localStorage key
273
- defaultDark: false // Default state
274
- })
275
- ```
276
-
277
- ### useExportCSV
278
-
279
- ```typescript
280
- const { exportToCSV } = useExportCSV()
281
-
282
- exportToCSV(data, columns, 'export.csv')
283
- ```
284
-
285
- ### useToast
286
-
287
- Toast notification system with positioning and auto-dismiss:
288
-
289
- ```typescript
290
- import { useToast } from 'cisse-vue-ui/composables'
291
-
292
- const { toasts, addToast, removeToast, clearToasts } = useToast()
293
-
294
- // Add a toast
295
- addToast({
296
- type: 'success',
297
- title: 'Success!',
298
- message: 'Your changes have been saved.',
299
- duration: 5000 // auto-dismiss after 5s
300
- })
301
-
302
- // Different toast types
303
- addToast({ type: 'error', title: 'Error', message: 'Something went wrong' })
304
- addToast({ type: 'warning', title: 'Warning', message: 'Please review' })
305
- addToast({ type: 'info', title: 'Info', message: 'New update available' })
306
- ```
307
-
308
- ```vue
309
- <template>
310
- <!-- Add ToastContainer to your app root -->
311
- <ToastContainer position="top-right" />
312
- </template>
313
- ```
314
-
315
- ### useFocusTrap
316
-
317
- Trap focus within a container (used internally by Modal):
318
-
319
- ```typescript
320
- import { useFocusTrap } from 'cisse-vue-ui/composables'
321
- import { ref } from 'vue'
322
-
323
- const isActive = ref(true)
324
- const { containerRef } = useFocusTrap({
325
- active: isActive,
326
- focusFirst: true, // Focus first focusable element on activate
327
- restoreFocus: true // Restore focus on deactivate
328
- })
329
- ```
330
-
331
- ### useId
332
-
333
- Generate unique IDs for accessibility (ARIA relationships):
334
-
335
- ```typescript
336
- import { useId } from 'cisse-vue-ui/composables'
337
-
338
- const { id, related } = useId({ prefix: 'modal' })
339
- // id.value = 'cisse-modal-1'
340
- // related('title') = 'cisse-modal-1-title'
341
- // related('description') = 'cisse-modal-1-description'
342
- ```
343
-
344
- ```vue
345
- <template>
346
- <div :id="id" role="dialog" :aria-labelledby="related('title')">
347
- <h2 :id="related('title')">Dialog Title</h2>
348
- </div>
349
- </template>
350
- ```
351
-
352
- ## Types
353
-
354
- ```typescript
355
- import type { Property, Notification, Breadcrumb } from 'cisse-vue-ui/types'
356
-
357
- // Table column definition
358
- const columns: Property[] = [
359
- { key: 'name', label: 'Name', sortable: true },
360
- { key: 'email', label: 'Email' },
361
- { key: 'status', label: 'Status', type: 'badge' }
362
- ]
363
-
364
- // Breadcrumb navigation
365
- const breadcrumbs: Breadcrumb[] = [
366
- { label: 'Home', to: '/' },
367
- { label: 'Users', to: '/users' },
368
- { label: 'Edit' }
369
- ]
370
- ```
371
-
372
- ## Component Examples
373
-
374
- ### Button
375
-
376
- ```vue
377
- <Button variant="primary" size="md" :loading="isLoading">
378
- Save Changes
379
- </Button>
380
-
381
- <Button variant="outline" icon="lucide:plus">
382
- Add Item
383
- </Button>
384
-
385
- <Button variant="danger" icon="lucide:trash">
386
- Delete
387
- </Button>
388
- ```
389
-
390
- ### Tabs
391
-
392
- ```vue
393
- <script setup>
394
- import { ref } from 'vue'
395
- import { Tabs, TabPanel } from 'cisse-vue-ui'
396
-
397
- const activeTab = ref('profile')
398
- const tabs = [
399
- { key: 'profile', label: 'Profile' },
400
- { key: 'settings', label: 'Settings' },
401
- { key: 'notifications', label: 'Notifications' }
402
- ]
403
- </script>
404
-
405
- <template>
406
- <Tabs v-model="activeTab" :tabs="tabs" variant="underline">
407
- <TabPanel value="profile">Profile content</TabPanel>
408
- <TabPanel value="settings">Settings content</TabPanel>
409
- <TabPanel value="notifications">Notifications content</TabPanel>
410
- </Tabs>
411
- </template>
412
- ```
413
-
414
- ### Switch
415
-
416
- ```vue
417
- <Switch
418
- v-model="emailNotifications"
419
- label="Email notifications"
420
- description="Receive email updates about your account"
421
- />
422
- ```
423
-
424
- ### Alert
425
-
426
- ```vue
427
- <Alert variant="success" title="Success!" dismissible>
428
- Your changes have been saved successfully.
429
- </Alert>
430
-
431
- <Alert variant="error" title="Error">
432
- Something went wrong. Please try again.
433
- </Alert>
434
- ```
435
-
436
- ### Dropdown
437
-
438
- ```vue
439
- <script setup>
440
- import { Dropdown } from 'cisse-vue-ui'
441
-
442
- const items = [
443
- { key: 'edit', label: 'Edit', icon: 'lucide:edit' },
444
- { key: 'duplicate', label: 'Duplicate', icon: 'lucide:copy' },
445
- { key: 'divider', divider: true },
446
- { key: 'delete', label: 'Delete', icon: 'lucide:trash', danger: true }
447
- ]
448
-
449
- const handleSelect = (item) => {
450
- console.log('Selected:', item.key)
451
- }
452
- </script>
453
-
454
- <template>
455
- <Dropdown :items="items" @select="handleSelect">
456
- <template #trigger-label>Actions</template>
457
- </Dropdown>
458
- </template>
459
- ```
460
-
461
- ### Stepper
462
-
463
- ```vue
464
- <script setup>
465
- import { ref } from 'vue'
466
- import { Stepper } from 'cisse-vue-ui'
467
-
468
- const currentStep = ref('step2')
469
- const steps = [
470
- { key: 'step1', title: 'Account', description: 'Create account', icon: 'lucide:user' },
471
- { key: 'step2', title: 'Profile', description: 'Set up profile', icon: 'lucide:settings' },
472
- { key: 'step3', title: 'Complete', description: 'Ready to go!', icon: 'lucide:check' }
473
- ]
474
- </script>
475
-
476
- <template>
477
- <Stepper v-model="currentStep" :steps="steps" />
478
- </template>
479
- ```
480
-
481
- ### EmptyState
482
-
483
- ```vue
484
- <EmptyState
485
- title="No results found"
486
- message="Try adjusting your search or filters"
487
- icon="lucide:search-x"
488
- >
489
- <template #action>
490
- <Button variant="primary" size="sm">Clear filters</Button>
491
- </template>
492
- </EmptyState>
493
- ```
494
-
495
- ### Checkbox
496
-
497
- ```vue
498
- <Checkbox
499
- v-model="accepted"
500
- label="Accept terms"
501
- description="I agree to the terms and conditions"
502
- />
503
- ```
504
-
505
- ### TableComponent
506
-
507
- ```vue
508
- <script setup>
509
- import { ref } from 'vue'
510
- import { TableComponent } from 'cisse-vue-ui'
511
-
512
- const properties = [
513
- { name: 'name', label: 'Name', main: true },
514
- { name: 'email', label: 'Email' },
515
- { name: 'role', label: 'Role', type: 'badge' }
516
- ]
517
-
518
- const items = [
519
- { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
520
- { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
521
- ]
522
-
523
- // Selection support
524
- const selectedItems = ref(new Set())
525
- const toggleSelect = (id) => {
526
- if (selectedItems.value.has(id)) {
527
- selectedItems.value.delete(id)
528
- } else {
529
- selectedItems.value.add(id)
530
- }
531
- }
532
- </script>
533
-
534
- <template>
535
- <TableComponent
536
- :properties="properties"
537
- :items="items"
538
- selectable
539
- :selected-items="selectedItems"
540
- @select="toggleSelect"
541
- @select-all="toggleSelectAll"
542
- >
543
- <template #action="{ item }">
544
- <TableAction icon="lucide:edit" @click="edit(item)" />
545
- <TableAction icon="lucide:trash" variant="danger" @click="delete(item)" />
546
- </template>
547
- </TableComponent>
548
- </template>
549
- ```
550
-
551
- ### ResponsiveList
552
-
553
- A component that automatically switches between a mobile card layout and a desktop table layout based on screen size.
554
-
555
- ```vue
556
- <script setup>
557
- import { ref } from 'vue'
558
- import { ResponsiveList } from 'cisse-vue-ui'
559
-
560
- const columns = [
561
- { key: 'name', label: 'Name' },
562
- { key: 'email', label: 'Email' },
563
- { key: 'status', label: 'Status' }
564
- ]
565
-
566
- const items = [
567
- { id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
568
- { id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' }
569
- ]
570
-
571
- const selectedItems = ref(new Set())
572
-
573
- const toggleSelect = (id) => {
574
- if (selectedItems.value.has(id)) {
575
- selectedItems.value.delete(id)
576
- } else {
577
- selectedItems.value.add(id)
578
- }
579
- }
580
-
581
- const toggleSelectAll = () => {
582
- if (selectedItems.value.size === items.length) {
583
- selectedItems.value.clear()
584
- } else {
585
- items.forEach(item => selectedItems.value.add(String(item.id)))
586
- }
587
- }
588
- </script>
589
-
590
- <template>
591
- <ResponsiveList
592
- :items="items"
593
- :columns="columns"
594
- key-field="id"
595
- selectable
596
- :selected-items="selectedItems"
597
- breakpoint="lg"
598
- @select="toggleSelect"
599
- @select-all="toggleSelectAll"
600
- >
601
- <!-- Mobile view: avatar -->
602
- <template #avatar="{ item }">
603
- <div class="w-10 h-10 rounded-full bg-primary-500 flex items-center justify-center text-white">
604
- {{ item.name[0] }}
605
- </div>
606
- </template>
607
-
608
- <!-- Mobile view: content -->
609
- <template #mobileContent="{ item }">
610
- <h3 class="font-semibold">{{ item.name }}</h3>
611
- <p class="text-sm text-gray-500">{{ item.email }}</p>
612
- </template>
613
-
614
- <!-- Mobile view: actions -->
615
- <template #mobileActions="{ item }">
616
- <button @click="viewItem(item)">View</button>
617
- </template>
618
-
619
- <!-- Desktop table: custom cell rendering -->
620
- <template #cell-name="{ item }">
621
- <span class="font-medium">{{ item.name }}</span>
622
- </template>
623
-
624
- <template #cell-status="{ item }">
625
- <span :class="item.status === 'active' ? 'text-green-600' : 'text-red-600'">
626
- {{ item.status }}
627
- </span>
628
- </template>
629
-
630
- <!-- Desktop table: actions column -->
631
- <template #actions="{ item }">
632
- <Button size="sm" variant="ghost" @click="edit(item)">Edit</Button>
633
- </template>
634
-
635
- <!-- Empty state -->
636
- <template #empty>
637
- <EmptyState title="No items" message="No items to display" />
638
- </template>
639
- </ResponsiveList>
640
- </template>
641
- ```
642
-
643
- #### ResponsiveList Props
644
-
645
- | Prop | Type | Default | Description |
646
- |------|------|---------|-------------|
647
- | `items` | `Array` | required | Array of items to display |
648
- | `columns` | `Array` | required | Column definitions with `key` or `name`, `label`, and optional `type` |
649
- | `keyField` | `string` | `'id'` | Field to use as unique key for items |
650
- | `selectable` | `boolean` | `false` | Enable selection mode |
651
- | `selectedItems` | `Set<string>` | - | Set of selected item keys |
652
- | `selectableFilter` | `Function` | - | Filter function to determine if an item is selectable |
653
- | `breakpoint` | `string` | `'lg'` | Breakpoint for switching views: `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'` |
654
-
655
- ### MobileList
656
-
657
- A mobile-optimized card-based list component with selection support.
658
-
659
- ```vue
660
- <script setup>
661
- import { MobileList } from 'cisse-vue-ui'
662
- </script>
663
-
664
- <template>
665
- <MobileList
666
- :items="items"
667
- key-field="id"
668
- selectable
669
- :selected-items="selectedItems"
670
- @select="toggleSelect"
671
- @select-all="toggleSelectAll"
672
- >
673
- <template #avatar="{ item }">
674
- <div class="w-12 h-12 rounded-full bg-blue-500" />
675
- </template>
676
-
677
- <template #content="{ item }">
678
- <h3>{{ item.name }}</h3>
679
- <p>{{ item.description }}</p>
680
- </template>
681
-
682
- <template #actions="{ item }">
683
- <button>View</button>
684
- </template>
685
- </MobileList>
686
- </template>
687
- ```
688
-
689
- ### MenuItem
690
-
691
- ```vue
692
- <script setup>
693
- import { useRoute } from 'vue-router'
694
- import { MenuItem } from 'cisse-vue-ui'
695
-
696
- const route = useRoute()
697
-
698
- const menuItem = {
699
- label: 'Dashboard',
700
- link: '/dashboard',
701
- icon: 'lucide:layout-dashboard'
702
- }
703
- </script>
704
-
705
- <template>
706
- <!-- Auto-detect active state from current route -->
707
- <MenuItem :menu-item="menuItem" :current-path="route.path" />
708
-
709
- <!-- Or manually control active state -->
710
- <MenuItem :menu-item="menuItem" :active="true" />
711
- </template>
712
- ```
713
-
714
- ### BaseLayout
715
-
716
- ```vue
717
- <script setup>
718
- import { useRoute } from 'vue-router'
719
- import { BaseLayout } from 'cisse-vue-ui'
720
-
721
- const route = useRoute()
722
-
723
- const menuItems = [
724
- { label: 'Dashboard', link: '/', icon: 'lucide:home' },
725
- { label: 'Users', link: '/users', icon: 'lucide:users' },
726
- { label: 'Settings', link: '/settings', icon: 'lucide:settings' }
727
- ]
728
- </script>
729
-
730
- <template>
731
- <BaseLayout
732
- :menu-items="menuItems"
733
- :current-path="route.path"
734
- :show-dark-toggle="true"
735
- >
736
- <template #logo>
737
- <img src="/logo.svg" alt="Logo" class="h-8" />
738
- </template>
739
-
740
- <RouterView />
741
- </BaseLayout>
742
- </template>
743
- ```
744
-
745
- ## Dark Mode
746
-
747
- Components support dark mode via the `.dark` class on a parent element:
748
-
749
- ```html
750
- <html class="dark">
751
- <!-- Components will use dark theme -->
752
- </html>
753
- ```
754
-
755
- Use the `useDarkMode` composable or implement your own toggle:
756
-
757
- ```typescript
758
- const { isDark, toggle } = useDarkMode()
759
- ```
760
-
761
- ## License
762
-
763
- MIT
1
+ # cisse-vue-ui
2
+
3
+ A Vue 3 component library built with TypeScript and Tailwind CSS v4.
4
+
5
+ **[View Storybook Documentation](https://moulayecisse.github.io/cisse-vue-ui/)**
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install cisse-vue-ui
11
+ # or
12
+ bun add cisse-vue-ui
13
+ ```
14
+
15
+ ### Peer Dependencies
16
+
17
+ ```bash
18
+ npm install vue@^3.4 tailwindcss@^4 @iconify/vue@^4
19
+ ```
20
+
21
+ ## Setup
22
+
23
+ ### 1. Import Styles
24
+
25
+ Add the pre-compiled CSS to your main CSS file:
26
+
27
+ ```css
28
+ @import 'cisse-vue-ui/style.css';
29
+ @import 'tailwindcss';
30
+ ```
31
+
32
+ ### 2. Configure Primary Color (Optional)
33
+
34
+ Override the default primary color in your CSS:
35
+
36
+ ```css
37
+ @theme {
38
+ --color-primary-50: oklch(97% 0.02 142);
39
+ --color-primary-100: oklch(94% 0.05 142);
40
+ --color-primary-200: oklch(88% 0.10 142);
41
+ --color-primary-300: oklch(78% 0.15 142);
42
+ --color-primary-400: oklch(65% 0.20 142);
43
+ --color-primary-500: oklch(55% 0.22 142);
44
+ --color-primary-600: oklch(48% 0.20 142);
45
+ --color-primary-700: oklch(40% 0.17 142);
46
+ --color-primary-800: oklch(32% 0.14 142);
47
+ --color-primary-900: oklch(25% 0.10 142);
48
+ --color-primary-950: oklch(18% 0.08 142);
49
+ }
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ### Tree-Shaken Imports (Recommended)
55
+
56
+ ```vue
57
+ <script setup lang="ts">
58
+ import { Button, CardComponent, FormInput } from 'cisse-vue-ui'
59
+ </script>
60
+ ```
61
+
62
+ ### Category Imports
63
+
64
+ ```typescript
65
+ import { Button, Tabs, TabPanel } from 'cisse-vue-ui/components/core'
66
+ import { FormInput, FormSelect, Switch } from 'cisse-vue-ui/components/form'
67
+ import { Modal, Alert, LoadingSpinner } from 'cisse-vue-ui/components/feedback'
68
+ import { BaseLayout, PageLayout } from 'cisse-vue-ui/components/layout'
69
+ ```
70
+
71
+ ### Global Registration (Vue Plugin)
72
+
73
+ ```typescript
74
+ import { createApp } from 'vue'
75
+ import { VueTailwindUI } from 'cisse-vue-ui'
76
+
77
+ const app = createApp(App)
78
+
79
+ // Register all components
80
+ app.use(VueTailwindUI)
81
+
82
+ // Or with a prefix
83
+ app.use(VueTailwindUI, { prefix: 'Ui' }) // <UiButton>, <UiCard>, etc.
84
+
85
+ // Or specific components only
86
+ app.use(VueTailwindUI, { components: ['Button', 'CardComponent'] })
87
+ ```
88
+
89
+ ## Components
90
+
91
+ ### Core
92
+
93
+ | Component | Description |
94
+ |-----------|-------------|
95
+ | `Button` | Button with variants (primary, secondary, outline, ghost, danger, success), sizes, icons, loading state |
96
+ | `CardComponent` | Card container with header, content, and footer slots |
97
+ | `TableComponent` | Data table with sorting, selection, actions, and custom column rendering |
98
+ | `MobileList` | Mobile-optimized card-based list with selection support |
99
+ | `ResponsiveList` | Combines MobileList (mobile) and TableComponent (desktop) with automatic breakpoint switching |
100
+ | `Tabs` | Tab navigation with variants (underline, pills, boxed) |
101
+ | `TabPanel` | Tab content panel (use with Tabs) |
102
+ | `Dropdown` | Dropdown menu with items, icons, and dividers |
103
+ | `Avatar` | User avatar with image, initials, or icon fallback |
104
+ | `AutocompleteComponent` | Searchable select with keyboard navigation |
105
+ | `MenuItem` | Navigation menu item with icon, active state detection, and route support |
106
+ | `StatusBadge` | Colored status indicator badge |
107
+ | `TableAction` | Icon button for table row actions |
108
+ | `Stepper` | Multi-step progress indicator with horizontal/vertical orientation |
109
+ | `CollapsibleCard` | Card that can expand/collapse its content |
110
+ | `Accordion` | Expandable content sections with single/multiple mode |
111
+ | `Breadcrumb` | Navigation breadcrumb trail |
112
+ | `Drawer` | Slide-out panel from any edge (left, right, top, bottom) |
113
+ | `Popover` | Floating content panel triggered by click or hover |
114
+ | `Timeline` | Vertical timeline for events/history display |
115
+ | `Tooltip` | Hover tooltip with customizable position |
116
+
117
+ ### Form
118
+
119
+ | Component | Description |
120
+ |-----------|-------------|
121
+ | `FormInput` | Text input with validation states and ARIA support |
122
+ | `FormSelect` | Select dropdown with search, multi-select, and validation |
123
+ | `FormGroup` | Form field wrapper with label, help text, and error states |
124
+ | `FormLabel` | Styled form label with required indicator |
125
+ | `FormHelp` | Help/error text for form fields |
126
+ | `SearchInput` | Search input with icon and clear button |
127
+ | `Switch` | Toggle switch with label and description |
128
+ | `Checkbox` | Checkbox with label, description, and indeterminate state |
129
+ | `Combobox` | Multi-select combobox with search and tags |
130
+ | `DatePicker` | Calendar date picker with min/max dates |
131
+ | `ColorPicker` | Color selection with swatches and custom input |
132
+ | `FileUpload` | Drag-and-drop file upload with preview |
133
+ | `Rating` | Star rating input with half-star support |
134
+ | `Slider` | Single value slider input |
135
+ | `RangeSlider` | Dual-handle range slider |
136
+
137
+ ### Feedback
138
+
139
+ | Component | Description |
140
+ |-----------|-------------|
141
+ | `Modal` | Modal dialog with focus trap, ARIA support, and slots |
142
+ | `ConfirmDialog` | Confirmation modal with customizable actions |
143
+ | `Alert` | Alert banner with variants (info, success, warning, error) |
144
+ | `Toast` | Individual toast notification with auto-dismiss |
145
+ | `ToastContainer` | Toast notification container with positioning |
146
+ | `LoadingSpinner` | Loading indicator with size variants |
147
+ | `Progress` | Progress bar with percentage display |
148
+ | `Skeleton` | Loading placeholder with animation |
149
+ | `CardSkeleton` | Card loading skeleton |
150
+ | `ListSkeleton` | List loading skeleton |
151
+ | `TableSkeleton` | Table loading skeleton |
152
+ | `PaginationControls` | Pagination with page numbers and navigation |
153
+ | `NotificationList` | Notification list container |
154
+ | `NotificationComponent` | Individual notification item |
155
+ | `EmptyState` | Placeholder for empty content with icon and action slot |
156
+
157
+ ### Layout
158
+
159
+ | Component | Description |
160
+ |-----------|-------------|
161
+ | `BaseLayout` | App shell with sidebar, header, main content area, and route-aware menu |
162
+ | `PageLayout` | Page wrapper with breadcrumbs |
163
+
164
+ ### Type Display
165
+
166
+ | Component | Description |
167
+ |-----------|-------------|
168
+ | `TextType` | Text value display |
169
+ | `NumberType` | Formatted number display |
170
+ | `DateType` | Formatted date display |
171
+ | `BooleanType` | Boolean value display (check/cross icons) |
172
+ | `BadgeType` | Badge value display with colors |
173
+
174
+ ## Composables
175
+
176
+ ```typescript
177
+ import {
178
+ useNotifications,
179
+ useDarkMode,
180
+ useExportCSV,
181
+ useDropdown,
182
+ useModal,
183
+ useToast,
184
+ useFocusTrap,
185
+ useId
186
+ } from 'cisse-vue-ui/composables'
187
+ ```
188
+
189
+ ### useModal
190
+
191
+ Manage modal state with data support:
192
+
193
+ ```typescript
194
+ import { useModal } from 'cisse-vue-ui/composables'
195
+
196
+ // Simple modal
197
+ const createModal = useModal()
198
+ createModal.open()
199
+ createModal.close()
200
+
201
+ // Modal with data (e.g., for editing)
202
+ const editModal = useModal<User>()
203
+ editModal.open(selectedUser)
204
+ // Access editModal.data.value in template
205
+
206
+ // With callbacks
207
+ const deleteModal = useModal<Item>({
208
+ onOpen: (data) => console.log('Opening with:', data),
209
+ onClose: () => refetchData()
210
+ })
211
+ ```
212
+
213
+ ```vue
214
+ <template>
215
+ <!-- Use isOpen for v-model binding -->
216
+ <Modal v-model="editModal.isOpen.value" title="Edit User">
217
+ <FormInput v-model="editModal.data.value.name" label="Name" />
218
+ <template #footer>
219
+ <Button @click="editModal.close()">Cancel</Button>
220
+ <Button variant="primary" @click="save">Save</Button>
221
+ </template>
222
+ </Modal>
223
+ </template>
224
+ ```
225
+
226
+ ### useDropdown
227
+
228
+ Shared dropdown logic for custom dropdown components (used internally by Dropdown, FormSelect, AutocompleteComponent):
229
+
230
+ ```typescript
231
+ import { useDropdown } from 'cisse-vue-ui/composables'
232
+ import { ref } from 'vue'
233
+
234
+ const triggerRef = ref<HTMLElement>()
235
+ const dropdownRef = ref<HTMLElement>()
236
+
237
+ const {
238
+ isOpen,
239
+ highlightedIndex,
240
+ dropdownStyle,
241
+ open,
242
+ close,
243
+ toggle,
244
+ handleKeydown,
245
+ scrollToHighlighted,
246
+ } = useDropdown(triggerRef, dropdownRef, {
247
+ teleport: true,
248
+ align: 'left',
249
+ gap: 8,
250
+ onOpen: () => console.log('Opened'),
251
+ onClose: () => console.log('Closed'),
252
+ })
253
+ ```
254
+
255
+ ### useNotifications
256
+
257
+ ```typescript
258
+ const { notifications, addNotification, removeNotification } = useNotifications()
259
+
260
+ addNotification({
261
+ type: 'success',
262
+ title: 'Saved',
263
+ message: 'Your changes have been saved.'
264
+ })
265
+ ```
266
+
267
+ ### useDarkMode
268
+
269
+ ```typescript
270
+ const { isDark, toggle, enable, disable } = useDarkMode({
271
+ selector: 'html', // Element to add .dark class
272
+ storageKey: 'theme', // localStorage key
273
+ defaultDark: false // Default state
274
+ })
275
+ ```
276
+
277
+ ### useExportCSV
278
+
279
+ ```typescript
280
+ const { exportToCSV } = useExportCSV()
281
+
282
+ exportToCSV(data, columns, 'export.csv')
283
+ ```
284
+
285
+ ### useToast
286
+
287
+ Toast notification system with positioning and auto-dismiss:
288
+
289
+ ```typescript
290
+ import { useToast } from 'cisse-vue-ui/composables'
291
+
292
+ const { toasts, addToast, removeToast, clearToasts } = useToast()
293
+
294
+ // Add a toast
295
+ addToast({
296
+ type: 'success',
297
+ title: 'Success!',
298
+ message: 'Your changes have been saved.',
299
+ duration: 5000 // auto-dismiss after 5s
300
+ })
301
+
302
+ // Different toast types
303
+ addToast({ type: 'error', title: 'Error', message: 'Something went wrong' })
304
+ addToast({ type: 'warning', title: 'Warning', message: 'Please review' })
305
+ addToast({ type: 'info', title: 'Info', message: 'New update available' })
306
+ ```
307
+
308
+ ```vue
309
+ <template>
310
+ <!-- Add ToastContainer to your app root -->
311
+ <ToastContainer position="top-right" />
312
+ </template>
313
+ ```
314
+
315
+ ### useFocusTrap
316
+
317
+ Trap focus within a container (used internally by Modal):
318
+
319
+ ```typescript
320
+ import { useFocusTrap } from 'cisse-vue-ui/composables'
321
+ import { ref } from 'vue'
322
+
323
+ const isActive = ref(true)
324
+ const { containerRef } = useFocusTrap({
325
+ active: isActive,
326
+ focusFirst: true, // Focus first focusable element on activate
327
+ restoreFocus: true // Restore focus on deactivate
328
+ })
329
+ ```
330
+
331
+ ### useId
332
+
333
+ Generate unique IDs for accessibility (ARIA relationships):
334
+
335
+ ```typescript
336
+ import { useId } from 'cisse-vue-ui/composables'
337
+
338
+ const { id, related } = useId({ prefix: 'modal' })
339
+ // id.value = 'cisse-modal-1'
340
+ // related('title') = 'cisse-modal-1-title'
341
+ // related('description') = 'cisse-modal-1-description'
342
+ ```
343
+
344
+ ```vue
345
+ <template>
346
+ <div :id="id" role="dialog" :aria-labelledby="related('title')">
347
+ <h2 :id="related('title')">Dialog Title</h2>
348
+ </div>
349
+ </template>
350
+ ```
351
+
352
+ ## Types
353
+
354
+ ```typescript
355
+ import type { Property, Notification, Breadcrumb } from 'cisse-vue-ui/types'
356
+
357
+ // Table column definition
358
+ const columns: Property[] = [
359
+ { key: 'name', label: 'Name', sortable: true },
360
+ { key: 'email', label: 'Email' },
361
+ { key: 'status', label: 'Status', type: 'badge' }
362
+ ]
363
+
364
+ // Breadcrumb navigation
365
+ const breadcrumbs: Breadcrumb[] = [
366
+ { label: 'Home', to: '/' },
367
+ { label: 'Users', to: '/users' },
368
+ { label: 'Edit' }
369
+ ]
370
+ ```
371
+
372
+ ## Component Examples
373
+
374
+ ### Button
375
+
376
+ ```vue
377
+ <Button variant="primary" size="md" :loading="isLoading">
378
+ Save Changes
379
+ </Button>
380
+
381
+ <Button variant="outline" icon="lucide:plus">
382
+ Add Item
383
+ </Button>
384
+
385
+ <Button variant="danger" icon="lucide:trash">
386
+ Delete
387
+ </Button>
388
+ ```
389
+
390
+ ### Tabs
391
+
392
+ ```vue
393
+ <script setup>
394
+ import { ref } from 'vue'
395
+ import { Tabs, TabPanel } from 'cisse-vue-ui'
396
+
397
+ const activeTab = ref('profile')
398
+ const tabs = [
399
+ { key: 'profile', label: 'Profile' },
400
+ { key: 'settings', label: 'Settings' },
401
+ { key: 'notifications', label: 'Notifications' }
402
+ ]
403
+ </script>
404
+
405
+ <template>
406
+ <Tabs v-model="activeTab" :tabs="tabs" variant="underline">
407
+ <TabPanel value="profile">Profile content</TabPanel>
408
+ <TabPanel value="settings">Settings content</TabPanel>
409
+ <TabPanel value="notifications">Notifications content</TabPanel>
410
+ </Tabs>
411
+ </template>
412
+ ```
413
+
414
+ ### Switch
415
+
416
+ ```vue
417
+ <Switch
418
+ v-model="emailNotifications"
419
+ label="Email notifications"
420
+ description="Receive email updates about your account"
421
+ />
422
+ ```
423
+
424
+ ### Alert
425
+
426
+ ```vue
427
+ <Alert variant="success" title="Success!" dismissible>
428
+ Your changes have been saved successfully.
429
+ </Alert>
430
+
431
+ <Alert variant="error" title="Error">
432
+ Something went wrong. Please try again.
433
+ </Alert>
434
+ ```
435
+
436
+ ### Dropdown
437
+
438
+ ```vue
439
+ <script setup>
440
+ import { Dropdown } from 'cisse-vue-ui'
441
+
442
+ const items = [
443
+ { key: 'edit', label: 'Edit', icon: 'lucide:edit' },
444
+ { key: 'duplicate', label: 'Duplicate', icon: 'lucide:copy' },
445
+ { key: 'divider', divider: true },
446
+ { key: 'delete', label: 'Delete', icon: 'lucide:trash', danger: true }
447
+ ]
448
+
449
+ const handleSelect = (item) => {
450
+ console.log('Selected:', item.key)
451
+ }
452
+ </script>
453
+
454
+ <template>
455
+ <Dropdown :items="items" @select="handleSelect">
456
+ <template #trigger-label>Actions</template>
457
+ </Dropdown>
458
+ </template>
459
+ ```
460
+
461
+ ### Stepper
462
+
463
+ ```vue
464
+ <script setup>
465
+ import { ref } from 'vue'
466
+ import { Stepper } from 'cisse-vue-ui'
467
+
468
+ const currentStep = ref('step2')
469
+ const steps = [
470
+ { key: 'step1', title: 'Account', description: 'Create account', icon: 'lucide:user' },
471
+ { key: 'step2', title: 'Profile', description: 'Set up profile', icon: 'lucide:settings' },
472
+ { key: 'step3', title: 'Complete', description: 'Ready to go!', icon: 'lucide:check' }
473
+ ]
474
+ </script>
475
+
476
+ <template>
477
+ <Stepper v-model="currentStep" :steps="steps" />
478
+ </template>
479
+ ```
480
+
481
+ ### EmptyState
482
+
483
+ ```vue
484
+ <EmptyState
485
+ title="No results found"
486
+ message="Try adjusting your search or filters"
487
+ icon="lucide:search-x"
488
+ >
489
+ <template #action>
490
+ <Button variant="primary" size="sm">Clear filters</Button>
491
+ </template>
492
+ </EmptyState>
493
+ ```
494
+
495
+ ### Checkbox
496
+
497
+ ```vue
498
+ <Checkbox
499
+ v-model="accepted"
500
+ label="Accept terms"
501
+ description="I agree to the terms and conditions"
502
+ />
503
+ ```
504
+
505
+ ### TableComponent
506
+
507
+ ```vue
508
+ <script setup>
509
+ import { ref } from 'vue'
510
+ import { TableComponent } from 'cisse-vue-ui'
511
+
512
+ const properties = [
513
+ { name: 'name', label: 'Name', main: true },
514
+ { name: 'email', label: 'Email' },
515
+ { name: 'role', label: 'Role', type: 'badge' }
516
+ ]
517
+
518
+ const items = [
519
+ { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
520
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
521
+ ]
522
+
523
+ // Selection support
524
+ const selectedItems = ref(new Set())
525
+ const toggleSelect = (id) => {
526
+ if (selectedItems.value.has(id)) {
527
+ selectedItems.value.delete(id)
528
+ } else {
529
+ selectedItems.value.add(id)
530
+ }
531
+ }
532
+ </script>
533
+
534
+ <template>
535
+ <TableComponent
536
+ :properties="properties"
537
+ :items="items"
538
+ selectable
539
+ :selected-items="selectedItems"
540
+ @select="toggleSelect"
541
+ @select-all="toggleSelectAll"
542
+ >
543
+ <template #action="{ item }">
544
+ <TableAction icon="lucide:edit" @click="edit(item)" />
545
+ <TableAction icon="lucide:trash" variant="danger" @click="delete(item)" />
546
+ </template>
547
+ </TableComponent>
548
+ </template>
549
+ ```
550
+
551
+ ### ResponsiveList
552
+
553
+ A component that automatically switches between a mobile card layout and a desktop table layout based on screen size.
554
+
555
+ ```vue
556
+ <script setup>
557
+ import { ref } from 'vue'
558
+ import { ResponsiveList } from 'cisse-vue-ui'
559
+
560
+ const columns = [
561
+ { key: 'name', label: 'Name' },
562
+ { key: 'email', label: 'Email' },
563
+ { key: 'status', label: 'Status' }
564
+ ]
565
+
566
+ const items = [
567
+ { id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
568
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' }
569
+ ]
570
+
571
+ const selectedItems = ref(new Set())
572
+
573
+ const toggleSelect = (id) => {
574
+ if (selectedItems.value.has(id)) {
575
+ selectedItems.value.delete(id)
576
+ } else {
577
+ selectedItems.value.add(id)
578
+ }
579
+ }
580
+
581
+ const toggleSelectAll = () => {
582
+ if (selectedItems.value.size === items.length) {
583
+ selectedItems.value.clear()
584
+ } else {
585
+ items.forEach(item => selectedItems.value.add(String(item.id)))
586
+ }
587
+ }
588
+ </script>
589
+
590
+ <template>
591
+ <ResponsiveList
592
+ :items="items"
593
+ :columns="columns"
594
+ key-field="id"
595
+ selectable
596
+ :selected-items="selectedItems"
597
+ breakpoint="lg"
598
+ @select="toggleSelect"
599
+ @select-all="toggleSelectAll"
600
+ >
601
+ <!-- Mobile view: avatar -->
602
+ <template #avatar="{ item }">
603
+ <div class="w-10 h-10 rounded-full bg-primary-500 flex items-center justify-center text-white">
604
+ {{ item.name[0] }}
605
+ </div>
606
+ </template>
607
+
608
+ <!-- Mobile view: content -->
609
+ <template #mobileContent="{ item }">
610
+ <h3 class="font-semibold">{{ item.name }}</h3>
611
+ <p class="text-sm text-gray-500">{{ item.email }}</p>
612
+ </template>
613
+
614
+ <!-- Mobile view: actions -->
615
+ <template #mobileActions="{ item }">
616
+ <button @click="viewItem(item)">View</button>
617
+ </template>
618
+
619
+ <!-- Desktop table: custom cell rendering -->
620
+ <template #cell-name="{ item }">
621
+ <span class="font-medium">{{ item.name }}</span>
622
+ </template>
623
+
624
+ <template #cell-status="{ item }">
625
+ <span :class="item.status === 'active' ? 'text-green-600' : 'text-red-600'">
626
+ {{ item.status }}
627
+ </span>
628
+ </template>
629
+
630
+ <!-- Desktop table: actions column -->
631
+ <template #actions="{ item }">
632
+ <Button size="sm" variant="ghost" @click="edit(item)">Edit</Button>
633
+ </template>
634
+
635
+ <!-- Empty state -->
636
+ <template #empty>
637
+ <EmptyState title="No items" message="No items to display" />
638
+ </template>
639
+ </ResponsiveList>
640
+ </template>
641
+ ```
642
+
643
+ #### ResponsiveList Props
644
+
645
+ | Prop | Type | Default | Description |
646
+ |------|------|---------|-------------|
647
+ | `items` | `Array` | required | Array of items to display |
648
+ | `columns` | `Array` | required | Column definitions with `key` or `name`, `label`, and optional `type` |
649
+ | `keyField` | `string` | `'id'` | Field to use as unique key for items |
650
+ | `selectable` | `boolean` | `false` | Enable selection mode |
651
+ | `selectedItems` | `Set<string>` | - | Set of selected item keys |
652
+ | `selectableFilter` | `Function` | - | Filter function to determine if an item is selectable |
653
+ | `breakpoint` | `string` | `'lg'` | Breakpoint for switching views: `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'` |
654
+
655
+ ### MobileList
656
+
657
+ A mobile-optimized card-based list component with selection support.
658
+
659
+ ```vue
660
+ <script setup>
661
+ import { MobileList } from 'cisse-vue-ui'
662
+ </script>
663
+
664
+ <template>
665
+ <MobileList
666
+ :items="items"
667
+ key-field="id"
668
+ selectable
669
+ :selected-items="selectedItems"
670
+ @select="toggleSelect"
671
+ @select-all="toggleSelectAll"
672
+ >
673
+ <template #avatar="{ item }">
674
+ <div class="w-12 h-12 rounded-full bg-blue-500" />
675
+ </template>
676
+
677
+ <template #content="{ item }">
678
+ <h3>{{ item.name }}</h3>
679
+ <p>{{ item.description }}</p>
680
+ </template>
681
+
682
+ <template #actions="{ item }">
683
+ <button>View</button>
684
+ </template>
685
+ </MobileList>
686
+ </template>
687
+ ```
688
+
689
+ ### MenuItem
690
+
691
+ ```vue
692
+ <script setup>
693
+ import { useRoute } from 'vue-router'
694
+ import { MenuItem } from 'cisse-vue-ui'
695
+
696
+ const route = useRoute()
697
+
698
+ const menuItem = {
699
+ label: 'Dashboard',
700
+ link: '/dashboard',
701
+ icon: 'lucide:layout-dashboard'
702
+ }
703
+ </script>
704
+
705
+ <template>
706
+ <!-- Auto-detect active state from current route -->
707
+ <MenuItem :menu-item="menuItem" :current-path="route.path" />
708
+
709
+ <!-- Or manually control active state -->
710
+ <MenuItem :menu-item="menuItem" :active="true" />
711
+ </template>
712
+ ```
713
+
714
+ ### BaseLayout
715
+
716
+ ```vue
717
+ <script setup>
718
+ import { useRoute } from 'vue-router'
719
+ import { BaseLayout } from 'cisse-vue-ui'
720
+
721
+ const route = useRoute()
722
+
723
+ const menuItems = [
724
+ { label: 'Dashboard', link: '/', icon: 'lucide:home' },
725
+ { label: 'Users', link: '/users', icon: 'lucide:users' },
726
+ { label: 'Settings', link: '/settings', icon: 'lucide:settings' }
727
+ ]
728
+ </script>
729
+
730
+ <template>
731
+ <BaseLayout
732
+ :menu-items="menuItems"
733
+ :current-path="route.path"
734
+ :show-dark-toggle="true"
735
+ >
736
+ <template #logo>
737
+ <img src="/logo.svg" alt="Logo" class="h-8" />
738
+ </template>
739
+
740
+ <RouterView />
741
+ </BaseLayout>
742
+ </template>
743
+ ```
744
+
745
+ ## Dark Mode
746
+
747
+ Components support dark mode via the `.dark` class on a parent element:
748
+
749
+ ```html
750
+ <html class="dark">
751
+ <!-- Components will use dark theme -->
752
+ </html>
753
+ ```
754
+
755
+ Use the `useDarkMode` composable or implement your own toggle:
756
+
757
+ ```typescript
758
+ const { isDark, toggle } = useDarkMode()
759
+ ```
760
+
761
+ ## License
762
+
763
+ MIT