cisse-vue-ui 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +666 -666
- package/dist/BadgeType.vue_vue_type_script_setup_true_lang-CJb63H1I.cjs.map +1 -1
- package/dist/BadgeType.vue_vue_type_script_setup_true_lang-CnB5eNEM.js.map +1 -1
- package/dist/Checkbox.vue_vue_type_script_setup_true_lang-B-nLCCNY.js.map +1 -1
- package/dist/Checkbox.vue_vue_type_script_setup_true_lang-DIoHDji4.cjs.map +1 -1
- package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-B9DsCY8M.js → Dropdown.vue_vue_type_script_setup_true_lang-C3pr8BwC.js} +2 -2
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-C3pr8BwC.js.map +1 -0
- package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-nMP2OxXp.cjs → Dropdown.vue_vue_type_script_setup_true_lang-DKxcVBKu.cjs} +2 -2
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-DKxcVBKu.cjs.map +1 -0
- package/dist/{PageLayout.vue_vue_type_script_setup_true_lang-Bnw5L-xO.cjs → PageLayout.vue_vue_type_script_setup_true_lang-1rNUMab6.cjs} +2 -2
- package/dist/PageLayout.vue_vue_type_script_setup_true_lang-1rNUMab6.cjs.map +1 -0
- package/dist/{PageLayout.vue_vue_type_script_setup_true_lang-D8uD3-Fe.js → PageLayout.vue_vue_type_script_setup_true_lang-DLfqj6EP.js} +2 -2
- package/dist/PageLayout.vue_vue_type_script_setup_true_lang-DLfqj6EP.js.map +1 -0
- package/dist/{DatePicker.vue_vue_type_script_setup_true_lang-CxddOiV4.cjs → RangeSlider.vue_vue_type_script_setup_true_lang-BRNkkx89.cjs} +715 -100
- package/dist/RangeSlider.vue_vue_type_script_setup_true_lang-BRNkkx89.cjs.map +1 -0
- package/dist/{DatePicker.vue_vue_type_script_setup_true_lang-DKDcnO28.js → RangeSlider.vue_vue_type_script_setup_true_lang-DD9UxnCU.js} +716 -101
- package/dist/RangeSlider.vue_vue_type_script_setup_true_lang-DD9UxnCU.js.map +1 -0
- package/dist/{Skeleton.vue_vue_type_script_setup_true_lang-DRC4EADS.js → Skeleton.vue_vue_type_script_setup_true_lang-BcWF7mwz.js} +2 -2
- package/dist/Skeleton.vue_vue_type_script_setup_true_lang-BcWF7mwz.js.map +1 -0
- package/dist/{Skeleton.vue_vue_type_script_setup_true_lang-D2S5g9s5.cjs → Skeleton.vue_vue_type_script_setup_true_lang-D-2qAhyG.cjs} +2 -2
- package/dist/Skeleton.vue_vue_type_script_setup_true_lang-D-2qAhyG.cjs.map +1 -0
- package/dist/{Popover.vue_vue_type_script_setup_true_lang-Q7gRZsT9.js → Timeline.vue_vue_type_script_setup_true_lang-BWIfzQOG.js} +591 -135
- package/dist/Timeline.vue_vue_type_script_setup_true_lang-BWIfzQOG.js.map +1 -0
- package/dist/{Popover.vue_vue_type_script_setup_true_lang-dvlDTcf1.cjs → Timeline.vue_vue_type_script_setup_true_lang-C2Mq4XOO.cjs} +578 -122
- package/dist/Timeline.vue_vue_type_script_setup_true_lang-C2Mq4XOO.cjs.map +1 -0
- package/dist/cisse-vue-ui.css +51 -51
- package/dist/components/core/Accordion.stories.d.ts +11 -0
- package/dist/components/core/Accordion.test.d.ts +1 -0
- package/dist/components/core/Accordion.vue.d.ts +27 -0
- package/dist/components/core/AccordionItem.vue.d.ts +27 -0
- package/dist/components/core/AutocompleteComponent.stories.d.ts +14 -0
- package/dist/components/core/AutocompleteComponent.test.d.ts +1 -0
- package/dist/components/core/Avatar.test.d.ts +1 -0
- package/dist/components/core/Breadcrumb.stories.d.ts +11 -0
- package/dist/components/core/Breadcrumb.test.d.ts +1 -0
- package/dist/components/core/Breadcrumb.vue.d.ts +26 -0
- package/dist/components/core/CardComponent.test.d.ts +1 -0
- package/dist/components/core/CollapsibleCard.test.d.ts +1 -0
- package/dist/components/core/Drawer.stories.d.ts +12 -0
- package/dist/components/core/Drawer.test.d.ts +1 -0
- package/dist/components/core/Drawer.vue.d.ts +50 -0
- package/dist/components/core/Dropdown.test.d.ts +1 -0
- package/dist/components/core/MenuItem.stories.d.ts +13 -0
- package/dist/components/core/MenuItem.test.d.ts +1 -0
- package/dist/components/core/MobileList.test.d.ts +1 -0
- package/dist/components/core/Popover.test.d.ts +1 -0
- package/dist/components/core/ResponsiveList.test.d.ts +1 -0
- package/dist/components/core/StatusBadge.test.d.ts +1 -0
- package/dist/components/core/Stepper.test.d.ts +1 -0
- package/dist/components/core/TabPanel.test.d.ts +1 -0
- package/dist/components/core/TableAction.test.d.ts +1 -0
- package/dist/components/core/TableComponent.stories.d.ts +14 -0
- package/dist/components/core/TableComponent.test.d.ts +1 -0
- package/dist/components/core/Tabs.test.d.ts +1 -0
- package/dist/components/core/Timeline.stories.d.ts +11 -0
- package/dist/components/core/Timeline.test.d.ts +1 -0
- package/dist/components/core/Timeline.vue.d.ts +34 -0
- package/dist/components/core/index.cjs +21 -16
- package/dist/components/core/index.cjs.map +1 -1
- package/dist/components/core/index.d.ts +9 -0
- package/dist/components/core/index.js +7 -2
- package/dist/components/feedback/Alert.test.d.ts +1 -0
- package/dist/components/feedback/EmptyState.test.d.ts +1 -0
- package/dist/components/feedback/LoadingSpinner.test.d.ts +1 -0
- package/dist/components/feedback/Modal.test.d.ts +1 -0
- package/dist/components/feedback/NotificationComponent.test.d.ts +1 -0
- package/dist/components/feedback/NotificationList.test.d.ts +1 -0
- package/dist/components/feedback/PaginationControls.test.d.ts +1 -0
- package/dist/components/feedback/Toast.test.d.ts +1 -0
- package/dist/components/feedback/ToastContainer.test.d.ts +1 -0
- package/dist/components/feedback/index.cjs +1 -1
- package/dist/components/feedback/index.js +1 -1
- package/dist/components/form/Checkbox.test.d.ts +1 -0
- package/dist/components/form/ColorPicker.stories.d.ts +12 -0
- package/dist/components/form/ColorPicker.test.d.ts +1 -0
- package/dist/components/form/ColorPicker.vue.d.ts +23 -0
- package/dist/components/form/DatePicker.test.d.ts +1 -0
- package/dist/components/form/FileUpload.stories.d.ts +12 -0
- package/dist/components/form/FileUpload.test.d.ts +1 -0
- package/dist/components/form/FileUpload.vue.d.ts +40 -0
- package/dist/components/form/FormGroup.test.d.ts +1 -0
- package/dist/components/form/FormHelp.test.d.ts +1 -0
- package/dist/components/form/FormInput.test.d.ts +1 -0
- package/dist/components/form/FormLabel.test.d.ts +1 -0
- package/dist/components/form/FormSelect.test.d.ts +1 -0
- package/dist/components/form/RangeSlider.stories.d.ts +13 -0
- package/dist/components/form/RangeSlider.test.d.ts +1 -0
- package/dist/components/form/RangeSlider.vue.d.ts +35 -0
- package/dist/components/form/Rating.stories.d.ts +16 -0
- package/dist/components/form/Rating.test.d.ts +1 -0
- package/dist/components/form/Rating.vue.d.ts +43 -0
- package/dist/components/form/SearchInput.stories.d.ts +13 -0
- package/dist/components/form/SearchInput.test.d.ts +1 -0
- package/dist/components/form/Slider.test.d.ts +1 -0
- package/dist/components/form/Switch.test.d.ts +1 -0
- package/dist/components/form/index.cjs +14 -10
- package/dist/components/form/index.cjs.map +1 -1
- package/dist/components/form/index.d.ts +6 -0
- package/dist/components/form/index.js +5 -1
- package/dist/components/index.cjs +37 -28
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +16 -7
- package/dist/components/layout/BaseLayout.test.d.ts +1 -0
- package/dist/components/layout/PageLayout.test.d.ts +1 -0
- package/dist/components/layout/PageLayout.vue.d.ts +3 -3
- package/dist/components/layout/index.cjs +1 -1
- package/dist/components/layout/index.d.ts +1 -1
- package/dist/components/layout/index.js +1 -1
- package/dist/components/type/BadgeType.test.d.ts +1 -0
- package/dist/components/type/BooleanType.test.d.ts +1 -0
- package/dist/components/type/DateType.test.d.ts +1 -0
- package/dist/components/type/NumberType.test.d.ts +1 -0
- package/dist/components/type/TextType.test.d.ts +1 -0
- package/dist/index-B4NFaDHr.cjs +67 -0
- package/dist/index-B4NFaDHr.cjs.map +1 -0
- package/dist/index-C2DRkEjb.js +68 -0
- package/dist/index-C2DRkEjb.js.map +1 -0
- package/dist/index.cjs +38 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +17 -8
- package/dist/style.css +1 -1
- package/dist/types/components.d.ts +1 -1
- package/dist/useDropdown-DHFnd259.cjs.map +1 -1
- package/dist/useDropdown-iVu14E6s.js.map +1 -1
- package/dist/useToast-DT9hFfpM.js.map +1 -1
- package/dist/useToast-nJXpFz_M.cjs.map +1 -1
- package/package.json +7 -2
- package/dist/DatePicker.vue_vue_type_script_setup_true_lang-CxddOiV4.cjs.map +0 -1
- package/dist/DatePicker.vue_vue_type_script_setup_true_lang-DKDcnO28.js.map +0 -1
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-B9DsCY8M.js.map +0 -1
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-nMP2OxXp.cjs.map +0 -1
- package/dist/PageLayout.vue_vue_type_script_setup_true_lang-Bnw5L-xO.cjs.map +0 -1
- package/dist/PageLayout.vue_vue_type_script_setup_true_lang-D8uD3-Fe.js.map +0 -1
- package/dist/Popover.vue_vue_type_script_setup_true_lang-Q7gRZsT9.js.map +0 -1
- package/dist/Popover.vue_vue_type_script_setup_true_lang-dvlDTcf1.cjs.map +0 -1
- package/dist/Skeleton.vue_vue_type_script_setup_true_lang-D2S5g9s5.cjs.map +0 -1
- package/dist/Skeleton.vue_vue_type_script_setup_true_lang-DRC4EADS.js.map +0 -1
- package/dist/index-DNndxsOE.cjs +0 -58
- package/dist/index-DNndxsOE.cjs.map +0 -1
- package/dist/index-_NEu9rf2.js +0 -59
- package/dist/index-_NEu9rf2.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,666 +1,666 @@
|
|
|
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
|
-
|
|
111
|
-
### Form
|
|
112
|
-
|
|
113
|
-
| Component | Description |
|
|
114
|
-
|-----------|-------------|
|
|
115
|
-
| `FormInput` | Text input with label, validation, and icons |
|
|
116
|
-
| `FormSelect` | Select dropdown with label and validation |
|
|
117
|
-
| `FormGroup` | Form field wrapper with label and help text |
|
|
118
|
-
| `FormLabel` | Styled form label |
|
|
119
|
-
| `FormHelp` | Help text for form fields |
|
|
120
|
-
| `SearchInput` | Search input with icon and clear button |
|
|
121
|
-
| `Switch` | Toggle switch with label and description |
|
|
122
|
-
| `Checkbox` | Checkbox with label and description |
|
|
123
|
-
|
|
124
|
-
### Feedback
|
|
125
|
-
|
|
126
|
-
| Component | Description |
|
|
127
|
-
|-----------|-------------|
|
|
128
|
-
| `Modal` | Modal dialog with header, body, footer slots |
|
|
129
|
-
| `Alert` | Alert banner with variants (info, success, warning, error) |
|
|
130
|
-
| `LoadingSpinner` | Loading indicator with size variants |
|
|
131
|
-
| `PaginationControls` | Pagination with page numbers and navigation |
|
|
132
|
-
| `NotificationList` | Toast notification container |
|
|
133
|
-
| `NotificationComponent` | Individual toast notification |
|
|
134
|
-
| `EmptyState` | Placeholder for empty content with icon and action slot |
|
|
135
|
-
|
|
136
|
-
### Layout
|
|
137
|
-
|
|
138
|
-
| Component | Description |
|
|
139
|
-
|-----------|-------------|
|
|
140
|
-
| `BaseLayout` | App shell with sidebar, header, main content area, and route-aware menu |
|
|
141
|
-
| `PageLayout` | Page wrapper with breadcrumbs |
|
|
142
|
-
|
|
143
|
-
### Type Display
|
|
144
|
-
|
|
145
|
-
| Component | Description |
|
|
146
|
-
|-----------|-------------|
|
|
147
|
-
| `TextType` | Text value display |
|
|
148
|
-
| `NumberType` | Formatted number display |
|
|
149
|
-
| `DateType` | Formatted date display |
|
|
150
|
-
| `BooleanType` | Boolean value display (check/cross icons) |
|
|
151
|
-
| `BadgeType` | Badge value display with colors |
|
|
152
|
-
|
|
153
|
-
## Composables
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
import { useNotifications, useDarkMode, useExportCSV, useDropdown, useModal } from 'cisse-vue-ui/composables'
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### useModal
|
|
160
|
-
|
|
161
|
-
Manage modal state with data support:
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
import { useModal } from 'cisse-vue-ui/composables'
|
|
165
|
-
|
|
166
|
-
// Simple modal
|
|
167
|
-
const createModal = useModal()
|
|
168
|
-
createModal.open()
|
|
169
|
-
createModal.close()
|
|
170
|
-
|
|
171
|
-
// Modal with data (e.g., for editing)
|
|
172
|
-
const editModal = useModal<User>()
|
|
173
|
-
editModal.open(selectedUser)
|
|
174
|
-
// Access editModal.data.value in template
|
|
175
|
-
|
|
176
|
-
// With callbacks
|
|
177
|
-
const deleteModal = useModal<Item>({
|
|
178
|
-
onOpen: (data) => console.log('Opening with:', data),
|
|
179
|
-
onClose: () => refetchData()
|
|
180
|
-
})
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
```vue
|
|
184
|
-
<template>
|
|
185
|
-
<!-- Use isOpen for v-model binding -->
|
|
186
|
-
<Modal v-model="editModal.isOpen.value" title="Edit User">
|
|
187
|
-
<FormInput v-model="editModal.data.value.name" label="Name" />
|
|
188
|
-
<template #footer>
|
|
189
|
-
<Button @click="editModal.close()">Cancel</Button>
|
|
190
|
-
<Button variant="primary" @click="save">Save</Button>
|
|
191
|
-
</template>
|
|
192
|
-
</Modal>
|
|
193
|
-
</template>
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### useDropdown
|
|
197
|
-
|
|
198
|
-
Shared dropdown logic for custom dropdown components (used internally by Dropdown, FormSelect, AutocompleteComponent):
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
import { useDropdown } from 'cisse-vue-ui/composables'
|
|
202
|
-
import { ref } from 'vue'
|
|
203
|
-
|
|
204
|
-
const triggerRef = ref<HTMLElement>()
|
|
205
|
-
const dropdownRef = ref<HTMLElement>()
|
|
206
|
-
|
|
207
|
-
const {
|
|
208
|
-
isOpen,
|
|
209
|
-
highlightedIndex,
|
|
210
|
-
dropdownStyle,
|
|
211
|
-
open,
|
|
212
|
-
close,
|
|
213
|
-
toggle,
|
|
214
|
-
handleKeydown,
|
|
215
|
-
scrollToHighlighted,
|
|
216
|
-
} = useDropdown(triggerRef, dropdownRef, {
|
|
217
|
-
teleport: true,
|
|
218
|
-
align: 'left',
|
|
219
|
-
gap: 8,
|
|
220
|
-
onOpen: () => console.log('Opened'),
|
|
221
|
-
onClose: () => console.log('Closed'),
|
|
222
|
-
})
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### useNotifications
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
const { notifications, addNotification, removeNotification } = useNotifications()
|
|
229
|
-
|
|
230
|
-
addNotification({
|
|
231
|
-
type: 'success',
|
|
232
|
-
title: 'Saved',
|
|
233
|
-
message: 'Your changes have been saved.'
|
|
234
|
-
})
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### useDarkMode
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
const { isDark, toggle, enable, disable } = useDarkMode({
|
|
241
|
-
selector: 'html', // Element to add .dark class
|
|
242
|
-
storageKey: 'theme', // localStorage key
|
|
243
|
-
defaultDark: false // Default state
|
|
244
|
-
})
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### useExportCSV
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
const { exportToCSV } = useExportCSV()
|
|
251
|
-
|
|
252
|
-
exportToCSV(data, columns, 'export.csv')
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
## Types
|
|
256
|
-
|
|
257
|
-
```typescript
|
|
258
|
-
import type { Property, Notification, Breadcrumb } from 'cisse-vue-ui/types'
|
|
259
|
-
|
|
260
|
-
// Table column definition
|
|
261
|
-
const columns: Property[] = [
|
|
262
|
-
{ key: 'name', label: 'Name', sortable: true },
|
|
263
|
-
{ key: 'email', label: 'Email' },
|
|
264
|
-
{ key: 'status', label: 'Status', type: 'badge' }
|
|
265
|
-
]
|
|
266
|
-
|
|
267
|
-
// Breadcrumb navigation
|
|
268
|
-
const breadcrumbs: Breadcrumb[] = [
|
|
269
|
-
{ label: 'Home', to: '/' },
|
|
270
|
-
{ label: 'Users', to: '/users' },
|
|
271
|
-
{ label: 'Edit' }
|
|
272
|
-
]
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
## Component Examples
|
|
276
|
-
|
|
277
|
-
### Button
|
|
278
|
-
|
|
279
|
-
```vue
|
|
280
|
-
<Button variant="primary" size="md" :loading="isLoading">
|
|
281
|
-
Save Changes
|
|
282
|
-
</Button>
|
|
283
|
-
|
|
284
|
-
<Button variant="outline" icon="lucide:plus">
|
|
285
|
-
Add Item
|
|
286
|
-
</Button>
|
|
287
|
-
|
|
288
|
-
<Button variant="danger" icon="lucide:trash">
|
|
289
|
-
Delete
|
|
290
|
-
</Button>
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Tabs
|
|
294
|
-
|
|
295
|
-
```vue
|
|
296
|
-
<script setup>
|
|
297
|
-
import { ref } from 'vue'
|
|
298
|
-
import { Tabs, TabPanel } from 'cisse-vue-ui'
|
|
299
|
-
|
|
300
|
-
const activeTab = ref('profile')
|
|
301
|
-
const tabs = [
|
|
302
|
-
{ key: 'profile', label: 'Profile' },
|
|
303
|
-
{ key: 'settings', label: 'Settings' },
|
|
304
|
-
{ key: 'notifications', label: 'Notifications' }
|
|
305
|
-
]
|
|
306
|
-
</script>
|
|
307
|
-
|
|
308
|
-
<template>
|
|
309
|
-
<Tabs v-model="activeTab" :tabs="tabs" variant="underline">
|
|
310
|
-
<TabPanel value="profile">Profile content</TabPanel>
|
|
311
|
-
<TabPanel value="settings">Settings content</TabPanel>
|
|
312
|
-
<TabPanel value="notifications">Notifications content</TabPanel>
|
|
313
|
-
</Tabs>
|
|
314
|
-
</template>
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Switch
|
|
318
|
-
|
|
319
|
-
```vue
|
|
320
|
-
<Switch
|
|
321
|
-
v-model="emailNotifications"
|
|
322
|
-
label="Email notifications"
|
|
323
|
-
description="Receive email updates about your account"
|
|
324
|
-
/>
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### Alert
|
|
328
|
-
|
|
329
|
-
```vue
|
|
330
|
-
<Alert variant="success" title="Success!" dismissible>
|
|
331
|
-
Your changes have been saved successfully.
|
|
332
|
-
</Alert>
|
|
333
|
-
|
|
334
|
-
<Alert variant="error" title="Error">
|
|
335
|
-
Something went wrong. Please try again.
|
|
336
|
-
</Alert>
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### Dropdown
|
|
340
|
-
|
|
341
|
-
```vue
|
|
342
|
-
<script setup>
|
|
343
|
-
import { Dropdown } from 'cisse-vue-ui'
|
|
344
|
-
|
|
345
|
-
const items = [
|
|
346
|
-
{ key: 'edit', label: 'Edit', icon: 'lucide:edit' },
|
|
347
|
-
{ key: 'duplicate', label: 'Duplicate', icon: 'lucide:copy' },
|
|
348
|
-
{ key: 'divider', divider: true },
|
|
349
|
-
{ key: 'delete', label: 'Delete', icon: 'lucide:trash', danger: true }
|
|
350
|
-
]
|
|
351
|
-
|
|
352
|
-
const handleSelect = (item) => {
|
|
353
|
-
console.log('Selected:', item.key)
|
|
354
|
-
}
|
|
355
|
-
</script>
|
|
356
|
-
|
|
357
|
-
<template>
|
|
358
|
-
<Dropdown :items="items" @select="handleSelect">
|
|
359
|
-
<template #trigger-label>Actions</template>
|
|
360
|
-
</Dropdown>
|
|
361
|
-
</template>
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### Stepper
|
|
365
|
-
|
|
366
|
-
```vue
|
|
367
|
-
<script setup>
|
|
368
|
-
import { ref } from 'vue'
|
|
369
|
-
import { Stepper } from 'cisse-vue-ui'
|
|
370
|
-
|
|
371
|
-
const currentStep = ref('step2')
|
|
372
|
-
const steps = [
|
|
373
|
-
{ key: 'step1', title: 'Account', description: 'Create account', icon: 'lucide:user' },
|
|
374
|
-
{ key: 'step2', title: 'Profile', description: 'Set up profile', icon: 'lucide:settings' },
|
|
375
|
-
{ key: 'step3', title: 'Complete', description: 'Ready to go!', icon: 'lucide:check' }
|
|
376
|
-
]
|
|
377
|
-
</script>
|
|
378
|
-
|
|
379
|
-
<template>
|
|
380
|
-
<Stepper v-model="currentStep" :steps="steps" />
|
|
381
|
-
</template>
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
### EmptyState
|
|
385
|
-
|
|
386
|
-
```vue
|
|
387
|
-
<EmptyState
|
|
388
|
-
title="No results found"
|
|
389
|
-
message="Try adjusting your search or filters"
|
|
390
|
-
icon="lucide:search-x"
|
|
391
|
-
>
|
|
392
|
-
<template #action>
|
|
393
|
-
<Button variant="primary" size="sm">Clear filters</Button>
|
|
394
|
-
</template>
|
|
395
|
-
</EmptyState>
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### Checkbox
|
|
399
|
-
|
|
400
|
-
```vue
|
|
401
|
-
<Checkbox
|
|
402
|
-
v-model="accepted"
|
|
403
|
-
label="Accept terms"
|
|
404
|
-
description="I agree to the terms and conditions"
|
|
405
|
-
/>
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
### TableComponent
|
|
409
|
-
|
|
410
|
-
```vue
|
|
411
|
-
<script setup>
|
|
412
|
-
import { ref } from 'vue'
|
|
413
|
-
import { TableComponent } from 'cisse-vue-ui'
|
|
414
|
-
|
|
415
|
-
const properties = [
|
|
416
|
-
{ name: 'name', label: 'Name', main: true },
|
|
417
|
-
{ name: 'email', label: 'Email' },
|
|
418
|
-
{ name: 'role', label: 'Role', type: 'badge' }
|
|
419
|
-
]
|
|
420
|
-
|
|
421
|
-
const items = [
|
|
422
|
-
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
|
|
423
|
-
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
|
|
424
|
-
]
|
|
425
|
-
|
|
426
|
-
// Selection support
|
|
427
|
-
const selectedItems = ref(new Set())
|
|
428
|
-
const toggleSelect = (id) => {
|
|
429
|
-
if (selectedItems.value.has(id)) {
|
|
430
|
-
selectedItems.value.delete(id)
|
|
431
|
-
} else {
|
|
432
|
-
selectedItems.value.add(id)
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
</script>
|
|
436
|
-
|
|
437
|
-
<template>
|
|
438
|
-
<TableComponent
|
|
439
|
-
:properties="properties"
|
|
440
|
-
:items="items"
|
|
441
|
-
selectable
|
|
442
|
-
:selected-items="selectedItems"
|
|
443
|
-
@select="toggleSelect"
|
|
444
|
-
@select-all="toggleSelectAll"
|
|
445
|
-
>
|
|
446
|
-
<template #action="{ item }">
|
|
447
|
-
<TableAction icon="lucide:edit" @click="edit(item)" />
|
|
448
|
-
<TableAction icon="lucide:trash" variant="danger" @click="delete(item)" />
|
|
449
|
-
</template>
|
|
450
|
-
</TableComponent>
|
|
451
|
-
</template>
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
### ResponsiveList
|
|
455
|
-
|
|
456
|
-
A component that automatically switches between a mobile card layout and a desktop table layout based on screen size.
|
|
457
|
-
|
|
458
|
-
```vue
|
|
459
|
-
<script setup>
|
|
460
|
-
import { ref } from 'vue'
|
|
461
|
-
import { ResponsiveList } from 'cisse-vue-ui'
|
|
462
|
-
|
|
463
|
-
const columns = [
|
|
464
|
-
{ key: 'name', label: 'Name' },
|
|
465
|
-
{ key: 'email', label: 'Email' },
|
|
466
|
-
{ key: 'status', label: 'Status' }
|
|
467
|
-
]
|
|
468
|
-
|
|
469
|
-
const items = [
|
|
470
|
-
{ id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
|
|
471
|
-
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' }
|
|
472
|
-
]
|
|
473
|
-
|
|
474
|
-
const selectedItems = ref(new Set())
|
|
475
|
-
|
|
476
|
-
const toggleSelect = (id) => {
|
|
477
|
-
if (selectedItems.value.has(id)) {
|
|
478
|
-
selectedItems.value.delete(id)
|
|
479
|
-
} else {
|
|
480
|
-
selectedItems.value.add(id)
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const toggleSelectAll = () => {
|
|
485
|
-
if (selectedItems.value.size === items.length) {
|
|
486
|
-
selectedItems.value.clear()
|
|
487
|
-
} else {
|
|
488
|
-
items.forEach(item => selectedItems.value.add(String(item.id)))
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
</script>
|
|
492
|
-
|
|
493
|
-
<template>
|
|
494
|
-
<ResponsiveList
|
|
495
|
-
:items="items"
|
|
496
|
-
:columns="columns"
|
|
497
|
-
key-field="id"
|
|
498
|
-
selectable
|
|
499
|
-
:selected-items="selectedItems"
|
|
500
|
-
breakpoint="lg"
|
|
501
|
-
@select="toggleSelect"
|
|
502
|
-
@select-all="toggleSelectAll"
|
|
503
|
-
>
|
|
504
|
-
<!-- Mobile view: avatar -->
|
|
505
|
-
<template #avatar="{ item }">
|
|
506
|
-
<div class="w-10 h-10 rounded-full bg-primary-500 flex items-center justify-center text-white">
|
|
507
|
-
{{ item.name[0] }}
|
|
508
|
-
</div>
|
|
509
|
-
</template>
|
|
510
|
-
|
|
511
|
-
<!-- Mobile view: content -->
|
|
512
|
-
<template #mobileContent="{ item }">
|
|
513
|
-
<h3 class="font-semibold">{{ item.name }}</h3>
|
|
514
|
-
<p class="text-sm text-gray-500">{{ item.email }}</p>
|
|
515
|
-
</template>
|
|
516
|
-
|
|
517
|
-
<!-- Mobile view: actions -->
|
|
518
|
-
<template #mobileActions="{ item }">
|
|
519
|
-
<button @click="viewItem(item)">View</button>
|
|
520
|
-
</template>
|
|
521
|
-
|
|
522
|
-
<!-- Desktop table: custom cell rendering -->
|
|
523
|
-
<template #cell-name="{ item }">
|
|
524
|
-
<span class="font-medium">{{ item.name }}</span>
|
|
525
|
-
</template>
|
|
526
|
-
|
|
527
|
-
<template #cell-status="{ item }">
|
|
528
|
-
<span :class="item.status === 'active' ? 'text-green-600' : 'text-red-600'">
|
|
529
|
-
{{ item.status }}
|
|
530
|
-
</span>
|
|
531
|
-
</template>
|
|
532
|
-
|
|
533
|
-
<!-- Desktop table: actions column -->
|
|
534
|
-
<template #actions="{ item }">
|
|
535
|
-
<Button size="sm" variant="ghost" @click="edit(item)">Edit</Button>
|
|
536
|
-
</template>
|
|
537
|
-
|
|
538
|
-
<!-- Empty state -->
|
|
539
|
-
<template #empty>
|
|
540
|
-
<EmptyState title="No items" message="No items to display" />
|
|
541
|
-
</template>
|
|
542
|
-
</ResponsiveList>
|
|
543
|
-
</template>
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
#### ResponsiveList Props
|
|
547
|
-
|
|
548
|
-
| Prop | Type | Default | Description |
|
|
549
|
-
|------|------|---------|-------------|
|
|
550
|
-
| `items` | `Array` | required | Array of items to display |
|
|
551
|
-
| `columns` | `Array` | required | Column definitions with `key` or `name`, `label`, and optional `type` |
|
|
552
|
-
| `keyField` | `string` | `'id'` | Field to use as unique key for items |
|
|
553
|
-
| `selectable` | `boolean` | `false` | Enable selection mode |
|
|
554
|
-
| `selectedItems` | `Set<string>` | - | Set of selected item keys |
|
|
555
|
-
| `selectableFilter` | `Function` | - | Filter function to determine if an item is selectable |
|
|
556
|
-
| `breakpoint` | `string` | `'lg'` | Breakpoint for switching views: `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'` |
|
|
557
|
-
|
|
558
|
-
### MobileList
|
|
559
|
-
|
|
560
|
-
A mobile-optimized card-based list component with selection support.
|
|
561
|
-
|
|
562
|
-
```vue
|
|
563
|
-
<script setup>
|
|
564
|
-
import { MobileList } from 'cisse-vue-ui'
|
|
565
|
-
</script>
|
|
566
|
-
|
|
567
|
-
<template>
|
|
568
|
-
<MobileList
|
|
569
|
-
:items="items"
|
|
570
|
-
key-field="id"
|
|
571
|
-
selectable
|
|
572
|
-
:selected-items="selectedItems"
|
|
573
|
-
@select="toggleSelect"
|
|
574
|
-
@select-all="toggleSelectAll"
|
|
575
|
-
>
|
|
576
|
-
<template #avatar="{ item }">
|
|
577
|
-
<div class="w-12 h-12 rounded-full bg-blue-500" />
|
|
578
|
-
</template>
|
|
579
|
-
|
|
580
|
-
<template #content="{ item }">
|
|
581
|
-
<h3>{{ item.name }}</h3>
|
|
582
|
-
<p>{{ item.description }}</p>
|
|
583
|
-
</template>
|
|
584
|
-
|
|
585
|
-
<template #actions="{ item }">
|
|
586
|
-
<button>View</button>
|
|
587
|
-
</template>
|
|
588
|
-
</MobileList>
|
|
589
|
-
</template>
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
### MenuItem
|
|
593
|
-
|
|
594
|
-
```vue
|
|
595
|
-
<script setup>
|
|
596
|
-
import { useRoute } from 'vue-router'
|
|
597
|
-
import { MenuItem } from 'cisse-vue-ui'
|
|
598
|
-
|
|
599
|
-
const route = useRoute()
|
|
600
|
-
|
|
601
|
-
const menuItem = {
|
|
602
|
-
label: 'Dashboard',
|
|
603
|
-
link: '/dashboard',
|
|
604
|
-
icon: 'lucide:layout-dashboard'
|
|
605
|
-
}
|
|
606
|
-
</script>
|
|
607
|
-
|
|
608
|
-
<template>
|
|
609
|
-
<!-- Auto-detect active state from current route -->
|
|
610
|
-
<MenuItem :menu-item="menuItem" :current-path="route.path" />
|
|
611
|
-
|
|
612
|
-
<!-- Or manually control active state -->
|
|
613
|
-
<MenuItem :menu-item="menuItem" :active="true" />
|
|
614
|
-
</template>
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
### BaseLayout
|
|
618
|
-
|
|
619
|
-
```vue
|
|
620
|
-
<script setup>
|
|
621
|
-
import { useRoute } from 'vue-router'
|
|
622
|
-
import { BaseLayout } from 'cisse-vue-ui'
|
|
623
|
-
|
|
624
|
-
const route = useRoute()
|
|
625
|
-
|
|
626
|
-
const menuItems = [
|
|
627
|
-
{ label: 'Dashboard', link: '/', icon: 'lucide:home' },
|
|
628
|
-
{ label: 'Users', link: '/users', icon: 'lucide:users' },
|
|
629
|
-
{ label: 'Settings', link: '/settings', icon: 'lucide:settings' }
|
|
630
|
-
]
|
|
631
|
-
</script>
|
|
632
|
-
|
|
633
|
-
<template>
|
|
634
|
-
<BaseLayout
|
|
635
|
-
:menu-items="menuItems"
|
|
636
|
-
:current-path="route.path"
|
|
637
|
-
:show-dark-toggle="true"
|
|
638
|
-
>
|
|
639
|
-
<template #logo>
|
|
640
|
-
<img src="/logo.svg" alt="Logo" class="h-8" />
|
|
641
|
-
</template>
|
|
642
|
-
|
|
643
|
-
<RouterView />
|
|
644
|
-
</BaseLayout>
|
|
645
|
-
</template>
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
## Dark Mode
|
|
649
|
-
|
|
650
|
-
Components support dark mode via the `.dark` class on a parent element:
|
|
651
|
-
|
|
652
|
-
```html
|
|
653
|
-
<html class="dark">
|
|
654
|
-
<!-- Components will use dark theme -->
|
|
655
|
-
</html>
|
|
656
|
-
```
|
|
657
|
-
|
|
658
|
-
Use the `useDarkMode` composable or implement your own toggle:
|
|
659
|
-
|
|
660
|
-
```typescript
|
|
661
|
-
const { isDark, toggle } = useDarkMode()
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
## License
|
|
665
|
-
|
|
666
|
-
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
|
+
|
|
111
|
+
### Form
|
|
112
|
+
|
|
113
|
+
| Component | Description |
|
|
114
|
+
|-----------|-------------|
|
|
115
|
+
| `FormInput` | Text input with label, validation, and icons |
|
|
116
|
+
| `FormSelect` | Select dropdown with label and validation |
|
|
117
|
+
| `FormGroup` | Form field wrapper with label and help text |
|
|
118
|
+
| `FormLabel` | Styled form label |
|
|
119
|
+
| `FormHelp` | Help text for form fields |
|
|
120
|
+
| `SearchInput` | Search input with icon and clear button |
|
|
121
|
+
| `Switch` | Toggle switch with label and description |
|
|
122
|
+
| `Checkbox` | Checkbox with label and description |
|
|
123
|
+
|
|
124
|
+
### Feedback
|
|
125
|
+
|
|
126
|
+
| Component | Description |
|
|
127
|
+
|-----------|-------------|
|
|
128
|
+
| `Modal` | Modal dialog with header, body, footer slots |
|
|
129
|
+
| `Alert` | Alert banner with variants (info, success, warning, error) |
|
|
130
|
+
| `LoadingSpinner` | Loading indicator with size variants |
|
|
131
|
+
| `PaginationControls` | Pagination with page numbers and navigation |
|
|
132
|
+
| `NotificationList` | Toast notification container |
|
|
133
|
+
| `NotificationComponent` | Individual toast notification |
|
|
134
|
+
| `EmptyState` | Placeholder for empty content with icon and action slot |
|
|
135
|
+
|
|
136
|
+
### Layout
|
|
137
|
+
|
|
138
|
+
| Component | Description |
|
|
139
|
+
|-----------|-------------|
|
|
140
|
+
| `BaseLayout` | App shell with sidebar, header, main content area, and route-aware menu |
|
|
141
|
+
| `PageLayout` | Page wrapper with breadcrumbs |
|
|
142
|
+
|
|
143
|
+
### Type Display
|
|
144
|
+
|
|
145
|
+
| Component | Description |
|
|
146
|
+
|-----------|-------------|
|
|
147
|
+
| `TextType` | Text value display |
|
|
148
|
+
| `NumberType` | Formatted number display |
|
|
149
|
+
| `DateType` | Formatted date display |
|
|
150
|
+
| `BooleanType` | Boolean value display (check/cross icons) |
|
|
151
|
+
| `BadgeType` | Badge value display with colors |
|
|
152
|
+
|
|
153
|
+
## Composables
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { useNotifications, useDarkMode, useExportCSV, useDropdown, useModal } from 'cisse-vue-ui/composables'
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### useModal
|
|
160
|
+
|
|
161
|
+
Manage modal state with data support:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { useModal } from 'cisse-vue-ui/composables'
|
|
165
|
+
|
|
166
|
+
// Simple modal
|
|
167
|
+
const createModal = useModal()
|
|
168
|
+
createModal.open()
|
|
169
|
+
createModal.close()
|
|
170
|
+
|
|
171
|
+
// Modal with data (e.g., for editing)
|
|
172
|
+
const editModal = useModal<User>()
|
|
173
|
+
editModal.open(selectedUser)
|
|
174
|
+
// Access editModal.data.value in template
|
|
175
|
+
|
|
176
|
+
// With callbacks
|
|
177
|
+
const deleteModal = useModal<Item>({
|
|
178
|
+
onOpen: (data) => console.log('Opening with:', data),
|
|
179
|
+
onClose: () => refetchData()
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```vue
|
|
184
|
+
<template>
|
|
185
|
+
<!-- Use isOpen for v-model binding -->
|
|
186
|
+
<Modal v-model="editModal.isOpen.value" title="Edit User">
|
|
187
|
+
<FormInput v-model="editModal.data.value.name" label="Name" />
|
|
188
|
+
<template #footer>
|
|
189
|
+
<Button @click="editModal.close()">Cancel</Button>
|
|
190
|
+
<Button variant="primary" @click="save">Save</Button>
|
|
191
|
+
</template>
|
|
192
|
+
</Modal>
|
|
193
|
+
</template>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### useDropdown
|
|
197
|
+
|
|
198
|
+
Shared dropdown logic for custom dropdown components (used internally by Dropdown, FormSelect, AutocompleteComponent):
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { useDropdown } from 'cisse-vue-ui/composables'
|
|
202
|
+
import { ref } from 'vue'
|
|
203
|
+
|
|
204
|
+
const triggerRef = ref<HTMLElement>()
|
|
205
|
+
const dropdownRef = ref<HTMLElement>()
|
|
206
|
+
|
|
207
|
+
const {
|
|
208
|
+
isOpen,
|
|
209
|
+
highlightedIndex,
|
|
210
|
+
dropdownStyle,
|
|
211
|
+
open,
|
|
212
|
+
close,
|
|
213
|
+
toggle,
|
|
214
|
+
handleKeydown,
|
|
215
|
+
scrollToHighlighted,
|
|
216
|
+
} = useDropdown(triggerRef, dropdownRef, {
|
|
217
|
+
teleport: true,
|
|
218
|
+
align: 'left',
|
|
219
|
+
gap: 8,
|
|
220
|
+
onOpen: () => console.log('Opened'),
|
|
221
|
+
onClose: () => console.log('Closed'),
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### useNotifications
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const { notifications, addNotification, removeNotification } = useNotifications()
|
|
229
|
+
|
|
230
|
+
addNotification({
|
|
231
|
+
type: 'success',
|
|
232
|
+
title: 'Saved',
|
|
233
|
+
message: 'Your changes have been saved.'
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### useDarkMode
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
const { isDark, toggle, enable, disable } = useDarkMode({
|
|
241
|
+
selector: 'html', // Element to add .dark class
|
|
242
|
+
storageKey: 'theme', // localStorage key
|
|
243
|
+
defaultDark: false // Default state
|
|
244
|
+
})
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### useExportCSV
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const { exportToCSV } = useExportCSV()
|
|
251
|
+
|
|
252
|
+
exportToCSV(data, columns, 'export.csv')
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Types
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import type { Property, Notification, Breadcrumb } from 'cisse-vue-ui/types'
|
|
259
|
+
|
|
260
|
+
// Table column definition
|
|
261
|
+
const columns: Property[] = [
|
|
262
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
263
|
+
{ key: 'email', label: 'Email' },
|
|
264
|
+
{ key: 'status', label: 'Status', type: 'badge' }
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
// Breadcrumb navigation
|
|
268
|
+
const breadcrumbs: Breadcrumb[] = [
|
|
269
|
+
{ label: 'Home', to: '/' },
|
|
270
|
+
{ label: 'Users', to: '/users' },
|
|
271
|
+
{ label: 'Edit' }
|
|
272
|
+
]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Component Examples
|
|
276
|
+
|
|
277
|
+
### Button
|
|
278
|
+
|
|
279
|
+
```vue
|
|
280
|
+
<Button variant="primary" size="md" :loading="isLoading">
|
|
281
|
+
Save Changes
|
|
282
|
+
</Button>
|
|
283
|
+
|
|
284
|
+
<Button variant="outline" icon="lucide:plus">
|
|
285
|
+
Add Item
|
|
286
|
+
</Button>
|
|
287
|
+
|
|
288
|
+
<Button variant="danger" icon="lucide:trash">
|
|
289
|
+
Delete
|
|
290
|
+
</Button>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Tabs
|
|
294
|
+
|
|
295
|
+
```vue
|
|
296
|
+
<script setup>
|
|
297
|
+
import { ref } from 'vue'
|
|
298
|
+
import { Tabs, TabPanel } from 'cisse-vue-ui'
|
|
299
|
+
|
|
300
|
+
const activeTab = ref('profile')
|
|
301
|
+
const tabs = [
|
|
302
|
+
{ key: 'profile', label: 'Profile' },
|
|
303
|
+
{ key: 'settings', label: 'Settings' },
|
|
304
|
+
{ key: 'notifications', label: 'Notifications' }
|
|
305
|
+
]
|
|
306
|
+
</script>
|
|
307
|
+
|
|
308
|
+
<template>
|
|
309
|
+
<Tabs v-model="activeTab" :tabs="tabs" variant="underline">
|
|
310
|
+
<TabPanel value="profile">Profile content</TabPanel>
|
|
311
|
+
<TabPanel value="settings">Settings content</TabPanel>
|
|
312
|
+
<TabPanel value="notifications">Notifications content</TabPanel>
|
|
313
|
+
</Tabs>
|
|
314
|
+
</template>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Switch
|
|
318
|
+
|
|
319
|
+
```vue
|
|
320
|
+
<Switch
|
|
321
|
+
v-model="emailNotifications"
|
|
322
|
+
label="Email notifications"
|
|
323
|
+
description="Receive email updates about your account"
|
|
324
|
+
/>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Alert
|
|
328
|
+
|
|
329
|
+
```vue
|
|
330
|
+
<Alert variant="success" title="Success!" dismissible>
|
|
331
|
+
Your changes have been saved successfully.
|
|
332
|
+
</Alert>
|
|
333
|
+
|
|
334
|
+
<Alert variant="error" title="Error">
|
|
335
|
+
Something went wrong. Please try again.
|
|
336
|
+
</Alert>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Dropdown
|
|
340
|
+
|
|
341
|
+
```vue
|
|
342
|
+
<script setup>
|
|
343
|
+
import { Dropdown } from 'cisse-vue-ui'
|
|
344
|
+
|
|
345
|
+
const items = [
|
|
346
|
+
{ key: 'edit', label: 'Edit', icon: 'lucide:edit' },
|
|
347
|
+
{ key: 'duplicate', label: 'Duplicate', icon: 'lucide:copy' },
|
|
348
|
+
{ key: 'divider', divider: true },
|
|
349
|
+
{ key: 'delete', label: 'Delete', icon: 'lucide:trash', danger: true }
|
|
350
|
+
]
|
|
351
|
+
|
|
352
|
+
const handleSelect = (item) => {
|
|
353
|
+
console.log('Selected:', item.key)
|
|
354
|
+
}
|
|
355
|
+
</script>
|
|
356
|
+
|
|
357
|
+
<template>
|
|
358
|
+
<Dropdown :items="items" @select="handleSelect">
|
|
359
|
+
<template #trigger-label>Actions</template>
|
|
360
|
+
</Dropdown>
|
|
361
|
+
</template>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Stepper
|
|
365
|
+
|
|
366
|
+
```vue
|
|
367
|
+
<script setup>
|
|
368
|
+
import { ref } from 'vue'
|
|
369
|
+
import { Stepper } from 'cisse-vue-ui'
|
|
370
|
+
|
|
371
|
+
const currentStep = ref('step2')
|
|
372
|
+
const steps = [
|
|
373
|
+
{ key: 'step1', title: 'Account', description: 'Create account', icon: 'lucide:user' },
|
|
374
|
+
{ key: 'step2', title: 'Profile', description: 'Set up profile', icon: 'lucide:settings' },
|
|
375
|
+
{ key: 'step3', title: 'Complete', description: 'Ready to go!', icon: 'lucide:check' }
|
|
376
|
+
]
|
|
377
|
+
</script>
|
|
378
|
+
|
|
379
|
+
<template>
|
|
380
|
+
<Stepper v-model="currentStep" :steps="steps" />
|
|
381
|
+
</template>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### EmptyState
|
|
385
|
+
|
|
386
|
+
```vue
|
|
387
|
+
<EmptyState
|
|
388
|
+
title="No results found"
|
|
389
|
+
message="Try adjusting your search or filters"
|
|
390
|
+
icon="lucide:search-x"
|
|
391
|
+
>
|
|
392
|
+
<template #action>
|
|
393
|
+
<Button variant="primary" size="sm">Clear filters</Button>
|
|
394
|
+
</template>
|
|
395
|
+
</EmptyState>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Checkbox
|
|
399
|
+
|
|
400
|
+
```vue
|
|
401
|
+
<Checkbox
|
|
402
|
+
v-model="accepted"
|
|
403
|
+
label="Accept terms"
|
|
404
|
+
description="I agree to the terms and conditions"
|
|
405
|
+
/>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### TableComponent
|
|
409
|
+
|
|
410
|
+
```vue
|
|
411
|
+
<script setup>
|
|
412
|
+
import { ref } from 'vue'
|
|
413
|
+
import { TableComponent } from 'cisse-vue-ui'
|
|
414
|
+
|
|
415
|
+
const properties = [
|
|
416
|
+
{ name: 'name', label: 'Name', main: true },
|
|
417
|
+
{ name: 'email', label: 'Email' },
|
|
418
|
+
{ name: 'role', label: 'Role', type: 'badge' }
|
|
419
|
+
]
|
|
420
|
+
|
|
421
|
+
const items = [
|
|
422
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
|
|
423
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
|
|
424
|
+
]
|
|
425
|
+
|
|
426
|
+
// Selection support
|
|
427
|
+
const selectedItems = ref(new Set())
|
|
428
|
+
const toggleSelect = (id) => {
|
|
429
|
+
if (selectedItems.value.has(id)) {
|
|
430
|
+
selectedItems.value.delete(id)
|
|
431
|
+
} else {
|
|
432
|
+
selectedItems.value.add(id)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
</script>
|
|
436
|
+
|
|
437
|
+
<template>
|
|
438
|
+
<TableComponent
|
|
439
|
+
:properties="properties"
|
|
440
|
+
:items="items"
|
|
441
|
+
selectable
|
|
442
|
+
:selected-items="selectedItems"
|
|
443
|
+
@select="toggleSelect"
|
|
444
|
+
@select-all="toggleSelectAll"
|
|
445
|
+
>
|
|
446
|
+
<template #action="{ item }">
|
|
447
|
+
<TableAction icon="lucide:edit" @click="edit(item)" />
|
|
448
|
+
<TableAction icon="lucide:trash" variant="danger" @click="delete(item)" />
|
|
449
|
+
</template>
|
|
450
|
+
</TableComponent>
|
|
451
|
+
</template>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### ResponsiveList
|
|
455
|
+
|
|
456
|
+
A component that automatically switches between a mobile card layout and a desktop table layout based on screen size.
|
|
457
|
+
|
|
458
|
+
```vue
|
|
459
|
+
<script setup>
|
|
460
|
+
import { ref } from 'vue'
|
|
461
|
+
import { ResponsiveList } from 'cisse-vue-ui'
|
|
462
|
+
|
|
463
|
+
const columns = [
|
|
464
|
+
{ key: 'name', label: 'Name' },
|
|
465
|
+
{ key: 'email', label: 'Email' },
|
|
466
|
+
{ key: 'status', label: 'Status' }
|
|
467
|
+
]
|
|
468
|
+
|
|
469
|
+
const items = [
|
|
470
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
|
|
471
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' }
|
|
472
|
+
]
|
|
473
|
+
|
|
474
|
+
const selectedItems = ref(new Set())
|
|
475
|
+
|
|
476
|
+
const toggleSelect = (id) => {
|
|
477
|
+
if (selectedItems.value.has(id)) {
|
|
478
|
+
selectedItems.value.delete(id)
|
|
479
|
+
} else {
|
|
480
|
+
selectedItems.value.add(id)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const toggleSelectAll = () => {
|
|
485
|
+
if (selectedItems.value.size === items.length) {
|
|
486
|
+
selectedItems.value.clear()
|
|
487
|
+
} else {
|
|
488
|
+
items.forEach(item => selectedItems.value.add(String(item.id)))
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
</script>
|
|
492
|
+
|
|
493
|
+
<template>
|
|
494
|
+
<ResponsiveList
|
|
495
|
+
:items="items"
|
|
496
|
+
:columns="columns"
|
|
497
|
+
key-field="id"
|
|
498
|
+
selectable
|
|
499
|
+
:selected-items="selectedItems"
|
|
500
|
+
breakpoint="lg"
|
|
501
|
+
@select="toggleSelect"
|
|
502
|
+
@select-all="toggleSelectAll"
|
|
503
|
+
>
|
|
504
|
+
<!-- Mobile view: avatar -->
|
|
505
|
+
<template #avatar="{ item }">
|
|
506
|
+
<div class="w-10 h-10 rounded-full bg-primary-500 flex items-center justify-center text-white">
|
|
507
|
+
{{ item.name[0] }}
|
|
508
|
+
</div>
|
|
509
|
+
</template>
|
|
510
|
+
|
|
511
|
+
<!-- Mobile view: content -->
|
|
512
|
+
<template #mobileContent="{ item }">
|
|
513
|
+
<h3 class="font-semibold">{{ item.name }}</h3>
|
|
514
|
+
<p class="text-sm text-gray-500">{{ item.email }}</p>
|
|
515
|
+
</template>
|
|
516
|
+
|
|
517
|
+
<!-- Mobile view: actions -->
|
|
518
|
+
<template #mobileActions="{ item }">
|
|
519
|
+
<button @click="viewItem(item)">View</button>
|
|
520
|
+
</template>
|
|
521
|
+
|
|
522
|
+
<!-- Desktop table: custom cell rendering -->
|
|
523
|
+
<template #cell-name="{ item }">
|
|
524
|
+
<span class="font-medium">{{ item.name }}</span>
|
|
525
|
+
</template>
|
|
526
|
+
|
|
527
|
+
<template #cell-status="{ item }">
|
|
528
|
+
<span :class="item.status === 'active' ? 'text-green-600' : 'text-red-600'">
|
|
529
|
+
{{ item.status }}
|
|
530
|
+
</span>
|
|
531
|
+
</template>
|
|
532
|
+
|
|
533
|
+
<!-- Desktop table: actions column -->
|
|
534
|
+
<template #actions="{ item }">
|
|
535
|
+
<Button size="sm" variant="ghost" @click="edit(item)">Edit</Button>
|
|
536
|
+
</template>
|
|
537
|
+
|
|
538
|
+
<!-- Empty state -->
|
|
539
|
+
<template #empty>
|
|
540
|
+
<EmptyState title="No items" message="No items to display" />
|
|
541
|
+
</template>
|
|
542
|
+
</ResponsiveList>
|
|
543
|
+
</template>
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
#### ResponsiveList Props
|
|
547
|
+
|
|
548
|
+
| Prop | Type | Default | Description |
|
|
549
|
+
|------|------|---------|-------------|
|
|
550
|
+
| `items` | `Array` | required | Array of items to display |
|
|
551
|
+
| `columns` | `Array` | required | Column definitions with `key` or `name`, `label`, and optional `type` |
|
|
552
|
+
| `keyField` | `string` | `'id'` | Field to use as unique key for items |
|
|
553
|
+
| `selectable` | `boolean` | `false` | Enable selection mode |
|
|
554
|
+
| `selectedItems` | `Set<string>` | - | Set of selected item keys |
|
|
555
|
+
| `selectableFilter` | `Function` | - | Filter function to determine if an item is selectable |
|
|
556
|
+
| `breakpoint` | `string` | `'lg'` | Breakpoint for switching views: `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'` |
|
|
557
|
+
|
|
558
|
+
### MobileList
|
|
559
|
+
|
|
560
|
+
A mobile-optimized card-based list component with selection support.
|
|
561
|
+
|
|
562
|
+
```vue
|
|
563
|
+
<script setup>
|
|
564
|
+
import { MobileList } from 'cisse-vue-ui'
|
|
565
|
+
</script>
|
|
566
|
+
|
|
567
|
+
<template>
|
|
568
|
+
<MobileList
|
|
569
|
+
:items="items"
|
|
570
|
+
key-field="id"
|
|
571
|
+
selectable
|
|
572
|
+
:selected-items="selectedItems"
|
|
573
|
+
@select="toggleSelect"
|
|
574
|
+
@select-all="toggleSelectAll"
|
|
575
|
+
>
|
|
576
|
+
<template #avatar="{ item }">
|
|
577
|
+
<div class="w-12 h-12 rounded-full bg-blue-500" />
|
|
578
|
+
</template>
|
|
579
|
+
|
|
580
|
+
<template #content="{ item }">
|
|
581
|
+
<h3>{{ item.name }}</h3>
|
|
582
|
+
<p>{{ item.description }}</p>
|
|
583
|
+
</template>
|
|
584
|
+
|
|
585
|
+
<template #actions="{ item }">
|
|
586
|
+
<button>View</button>
|
|
587
|
+
</template>
|
|
588
|
+
</MobileList>
|
|
589
|
+
</template>
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### MenuItem
|
|
593
|
+
|
|
594
|
+
```vue
|
|
595
|
+
<script setup>
|
|
596
|
+
import { useRoute } from 'vue-router'
|
|
597
|
+
import { MenuItem } from 'cisse-vue-ui'
|
|
598
|
+
|
|
599
|
+
const route = useRoute()
|
|
600
|
+
|
|
601
|
+
const menuItem = {
|
|
602
|
+
label: 'Dashboard',
|
|
603
|
+
link: '/dashboard',
|
|
604
|
+
icon: 'lucide:layout-dashboard'
|
|
605
|
+
}
|
|
606
|
+
</script>
|
|
607
|
+
|
|
608
|
+
<template>
|
|
609
|
+
<!-- Auto-detect active state from current route -->
|
|
610
|
+
<MenuItem :menu-item="menuItem" :current-path="route.path" />
|
|
611
|
+
|
|
612
|
+
<!-- Or manually control active state -->
|
|
613
|
+
<MenuItem :menu-item="menuItem" :active="true" />
|
|
614
|
+
</template>
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### BaseLayout
|
|
618
|
+
|
|
619
|
+
```vue
|
|
620
|
+
<script setup>
|
|
621
|
+
import { useRoute } from 'vue-router'
|
|
622
|
+
import { BaseLayout } from 'cisse-vue-ui'
|
|
623
|
+
|
|
624
|
+
const route = useRoute()
|
|
625
|
+
|
|
626
|
+
const menuItems = [
|
|
627
|
+
{ label: 'Dashboard', link: '/', icon: 'lucide:home' },
|
|
628
|
+
{ label: 'Users', link: '/users', icon: 'lucide:users' },
|
|
629
|
+
{ label: 'Settings', link: '/settings', icon: 'lucide:settings' }
|
|
630
|
+
]
|
|
631
|
+
</script>
|
|
632
|
+
|
|
633
|
+
<template>
|
|
634
|
+
<BaseLayout
|
|
635
|
+
:menu-items="menuItems"
|
|
636
|
+
:current-path="route.path"
|
|
637
|
+
:show-dark-toggle="true"
|
|
638
|
+
>
|
|
639
|
+
<template #logo>
|
|
640
|
+
<img src="/logo.svg" alt="Logo" class="h-8" />
|
|
641
|
+
</template>
|
|
642
|
+
|
|
643
|
+
<RouterView />
|
|
644
|
+
</BaseLayout>
|
|
645
|
+
</template>
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## Dark Mode
|
|
649
|
+
|
|
650
|
+
Components support dark mode via the `.dark` class on a parent element:
|
|
651
|
+
|
|
652
|
+
```html
|
|
653
|
+
<html class="dark">
|
|
654
|
+
<!-- Components will use dark theme -->
|
|
655
|
+
</html>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
Use the `useDarkMode` composable or implement your own toggle:
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
const { isDark, toggle } = useDarkMode()
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
## License
|
|
665
|
+
|
|
666
|
+
MIT
|