cisse-vue-ui 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +879 -879
- package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-hHJdvUYS.js → CheckboxGroup.vue_vue_type_script_setup_true_lang-BPALlche.js} +3 -3
- package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-BPALlche.js.map +1 -0
- package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-DJbuHoDj.cjs → CheckboxGroup.vue_vue_type_script_setup_true_lang-CYnPpiRd.cjs} +3 -3
- package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-CYnPpiRd.cjs.map +1 -0
- package/dist/{ConfirmDialog.vue_vue_type_script_setup_true_lang-Bine-xfp.cjs → ConfirmDialog.vue_vue_type_script_setup_true_lang-BGUoa5fT.cjs} +58 -9
- package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-BGUoa5fT.cjs.map +1 -0
- package/dist/{ConfirmDialog.vue_vue_type_script_setup_true_lang-DqkA1Zr-.js → ConfirmDialog.vue_vue_type_script_setup_true_lang-DWs2V7xX.js} +58 -9
- package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-DWs2V7xX.js.map +1 -0
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-56CxoSmj.js.map +1 -1
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-Dd3ySRNB.cjs.map +1 -1
- package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-Bj3I5Sn7.cjs.map +1 -1
- package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-DzLwUVCW.js.map +1 -1
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-BwtEbaiT.js.map +1 -1
- package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-DtwwmfWr.cjs.map +1 -1
- package/dist/PageHero.vue_vue_type_script_setup_true_lang-Bi97ypMD.cjs.map +1 -1
- package/dist/PageHero.vue_vue_type_script_setup_true_lang-DQQGYAw0.js.map +1 -1
- package/dist/cisse-vue-ui.css +57 -57
- package/dist/components/feedback/PaginationControls.stories.d.ts +5 -0
- package/dist/components/feedback/PaginationControls.vue.d.ts +2 -0
- package/dist/components/feedback/index.cjs +1 -1
- package/dist/components/feedback/index.js +1 -1
- package/dist/components/form/index.cjs +1 -1
- package/dist/components/form/index.js +1 -1
- package/dist/components/index.cjs +2 -2
- package/dist/components/index.js +2 -2
- package/dist/{index-rBD1MYh-.js → index-CE90-_mh.js} +3 -3
- package/dist/index-CE90-_mh.js.map +1 -0
- package/dist/{index-DwqCXgDx.cjs → index-QKwLtDJE.cjs} +3 -3
- package/dist/{index-DwqCXgDx.cjs.map → index-QKwLtDJE.cjs.map} +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.js +3 -3
- package/dist/style.css +1 -1
- package/dist/useDropdown-DHFnd259.cjs.map +1 -1
- package/dist/useDropdown-iVu14E6s.js.map +1 -1
- package/dist/useFocusTrap-AnlJsihM.js.map +1 -1
- package/dist/useFocusTrap-kcxO8AeU.cjs.map +1 -1
- package/dist/useId-nxrBaIC9.cjs.map +1 -1
- package/dist/useId-xeHj7rkg.js.map +1 -1
- package/dist/useToast-Bk60GArg.cjs.map +1 -1
- package/dist/useToast-ina5g3mj.js.map +1 -1
- package/package.json +169 -169
- package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-DJbuHoDj.cjs.map +0 -1
- package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-hHJdvUYS.js.map +0 -1
- package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-Bine-xfp.cjs.map +0 -1
- package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-DqkA1Zr-.js.map +0 -1
- package/dist/index-rBD1MYh-.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,879 +1,879 @@
|
|
|
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
|
-
| `AuthLayout` | Split-panel authentication layout with branding and form sections |
|
|
162
|
-
| `BaseLayout` | App shell with sidebar, header, main content area, and route-aware menu |
|
|
163
|
-
| `PageLayout` | Page wrapper with breadcrumbs |
|
|
164
|
-
|
|
165
|
-
### Type Display
|
|
166
|
-
|
|
167
|
-
| Component | Description |
|
|
168
|
-
|-----------|-------------|
|
|
169
|
-
| `TextType` | Text value display |
|
|
170
|
-
| `NumberType` | Formatted number display |
|
|
171
|
-
| `DateType` | Formatted date display |
|
|
172
|
-
| `BooleanType` | Boolean value display (check/cross icons) |
|
|
173
|
-
| `BadgeType` | Badge value display with colors |
|
|
174
|
-
|
|
175
|
-
## Composables
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
import {
|
|
179
|
-
useNotifications,
|
|
180
|
-
useDarkMode,
|
|
181
|
-
useExportCSV,
|
|
182
|
-
useDropdown,
|
|
183
|
-
useModal,
|
|
184
|
-
useToast,
|
|
185
|
-
useFocusTrap,
|
|
186
|
-
useId
|
|
187
|
-
} from 'cisse-vue-ui/composables'
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### useModal
|
|
191
|
-
|
|
192
|
-
Manage modal state with data support:
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
import { useModal } from 'cisse-vue-ui/composables'
|
|
196
|
-
|
|
197
|
-
// Simple modal
|
|
198
|
-
const createModal = useModal()
|
|
199
|
-
createModal.open()
|
|
200
|
-
createModal.close()
|
|
201
|
-
|
|
202
|
-
// Modal with data (e.g., for editing)
|
|
203
|
-
const editModal = useModal<User>()
|
|
204
|
-
editModal.open(selectedUser)
|
|
205
|
-
// Access editModal.data.value in template
|
|
206
|
-
|
|
207
|
-
// With callbacks
|
|
208
|
-
const deleteModal = useModal<Item>({
|
|
209
|
-
onOpen: (data) => console.log('Opening with:', data),
|
|
210
|
-
onClose: () => refetchData()
|
|
211
|
-
})
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
```vue
|
|
215
|
-
<template>
|
|
216
|
-
<!-- Use isOpen for v-model binding -->
|
|
217
|
-
<Modal v-model="editModal.isOpen.value" title="Edit User">
|
|
218
|
-
<FormInput v-model="editModal.data.value.name" label="Name" />
|
|
219
|
-
<template #footer>
|
|
220
|
-
<Button @click="editModal.close()">Cancel</Button>
|
|
221
|
-
<Button variant="primary" @click="save">Save</Button>
|
|
222
|
-
</template>
|
|
223
|
-
</Modal>
|
|
224
|
-
</template>
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### useDropdown
|
|
228
|
-
|
|
229
|
-
Shared dropdown logic for custom dropdown components (used internally by Dropdown, FormSelect, AutocompleteComponent):
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
import { useDropdown } from 'cisse-vue-ui/composables'
|
|
233
|
-
import { ref } from 'vue'
|
|
234
|
-
|
|
235
|
-
const triggerRef = ref<HTMLElement>()
|
|
236
|
-
const dropdownRef = ref<HTMLElement>()
|
|
237
|
-
|
|
238
|
-
const {
|
|
239
|
-
isOpen,
|
|
240
|
-
highlightedIndex,
|
|
241
|
-
dropdownStyle,
|
|
242
|
-
open,
|
|
243
|
-
close,
|
|
244
|
-
toggle,
|
|
245
|
-
handleKeydown,
|
|
246
|
-
scrollToHighlighted,
|
|
247
|
-
} = useDropdown(triggerRef, dropdownRef, {
|
|
248
|
-
teleport: true,
|
|
249
|
-
align: 'left',
|
|
250
|
-
gap: 8,
|
|
251
|
-
onOpen: () => console.log('Opened'),
|
|
252
|
-
onClose: () => console.log('Closed'),
|
|
253
|
-
})
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### useNotifications
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
const { notifications, addNotification, removeNotification } = useNotifications()
|
|
260
|
-
|
|
261
|
-
addNotification({
|
|
262
|
-
type: 'success',
|
|
263
|
-
title: 'Saved',
|
|
264
|
-
message: 'Your changes have been saved.'
|
|
265
|
-
})
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### useDarkMode
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
const { isDark, toggle, enable, disable } = useDarkMode({
|
|
272
|
-
selector: 'html', // Element to add .dark class
|
|
273
|
-
storageKey: 'theme', // localStorage key
|
|
274
|
-
defaultDark: false // Default state
|
|
275
|
-
})
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### useExportCSV
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
const { exportToCSV } = useExportCSV()
|
|
282
|
-
|
|
283
|
-
exportToCSV(data, columns, 'export.csv')
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### useToast
|
|
287
|
-
|
|
288
|
-
Toast notification system with positioning and auto-dismiss:
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
import { useToast } from 'cisse-vue-ui/composables'
|
|
292
|
-
|
|
293
|
-
const { toasts, addToast, removeToast, clearToasts } = useToast()
|
|
294
|
-
|
|
295
|
-
// Add a toast
|
|
296
|
-
addToast({
|
|
297
|
-
type: 'success',
|
|
298
|
-
title: 'Success!',
|
|
299
|
-
message: 'Your changes have been saved.',
|
|
300
|
-
duration: 5000 // auto-dismiss after 5s
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
// Different toast types
|
|
304
|
-
addToast({ type: 'error', title: 'Error', message: 'Something went wrong' })
|
|
305
|
-
addToast({ type: 'warning', title: 'Warning', message: 'Please review' })
|
|
306
|
-
addToast({ type: 'info', title: 'Info', message: 'New update available' })
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
```vue
|
|
310
|
-
<template>
|
|
311
|
-
<!-- Add ToastContainer to your app root -->
|
|
312
|
-
<ToastContainer position="top-right" />
|
|
313
|
-
</template>
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### useFocusTrap
|
|
317
|
-
|
|
318
|
-
Trap focus within a container (used internally by Modal):
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
import { useFocusTrap } from 'cisse-vue-ui/composables'
|
|
322
|
-
import { ref } from 'vue'
|
|
323
|
-
|
|
324
|
-
const isActive = ref(true)
|
|
325
|
-
const { containerRef } = useFocusTrap({
|
|
326
|
-
active: isActive,
|
|
327
|
-
focusFirst: true, // Focus first focusable element on activate
|
|
328
|
-
restoreFocus: true // Restore focus on deactivate
|
|
329
|
-
})
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
### useId
|
|
333
|
-
|
|
334
|
-
Generate unique IDs for accessibility (ARIA relationships):
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
import { useId } from 'cisse-vue-ui/composables'
|
|
338
|
-
|
|
339
|
-
const { id, related } = useId({ prefix: 'modal' })
|
|
340
|
-
// id.value = 'cisse-modal-1'
|
|
341
|
-
// related('title') = 'cisse-modal-1-title'
|
|
342
|
-
// related('description') = 'cisse-modal-1-description'
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
```vue
|
|
346
|
-
<template>
|
|
347
|
-
<div :id="id" role="dialog" :aria-labelledby="related('title')">
|
|
348
|
-
<h2 :id="related('title')">Dialog Title</h2>
|
|
349
|
-
</div>
|
|
350
|
-
</template>
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
## Types
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
import type { Property, Notification, Breadcrumb } from 'cisse-vue-ui/types'
|
|
357
|
-
|
|
358
|
-
// Table column definition
|
|
359
|
-
const columns: Property[] = [
|
|
360
|
-
{ key: 'name', label: 'Name', sortable: true },
|
|
361
|
-
{ key: 'email', label: 'Email' },
|
|
362
|
-
{ key: 'status', label: 'Status', type: 'badge' }
|
|
363
|
-
]
|
|
364
|
-
|
|
365
|
-
// Breadcrumb navigation
|
|
366
|
-
const breadcrumbs: Breadcrumb[] = [
|
|
367
|
-
{ label: 'Home', to: '/' },
|
|
368
|
-
{ label: 'Users', to: '/users' },
|
|
369
|
-
{ label: 'Edit' }
|
|
370
|
-
]
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
## Component Examples
|
|
374
|
-
|
|
375
|
-
### Button
|
|
376
|
-
|
|
377
|
-
```vue
|
|
378
|
-
<Button variant="primary" size="md" :loading="isLoading">
|
|
379
|
-
Save Changes
|
|
380
|
-
</Button>
|
|
381
|
-
|
|
382
|
-
<Button variant="outline" icon="lucide:plus">
|
|
383
|
-
Add Item
|
|
384
|
-
</Button>
|
|
385
|
-
|
|
386
|
-
<Button variant="danger" icon="lucide:trash">
|
|
387
|
-
Delete
|
|
388
|
-
</Button>
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### Tabs
|
|
392
|
-
|
|
393
|
-
```vue
|
|
394
|
-
<script setup>
|
|
395
|
-
import { ref } from 'vue'
|
|
396
|
-
import { Tabs, TabPanel } from 'cisse-vue-ui'
|
|
397
|
-
|
|
398
|
-
const activeTab = ref('profile')
|
|
399
|
-
const tabs = [
|
|
400
|
-
{ key: 'profile', label: 'Profile' },
|
|
401
|
-
{ key: 'settings', label: 'Settings' },
|
|
402
|
-
{ key: 'notifications', label: 'Notifications' }
|
|
403
|
-
]
|
|
404
|
-
</script>
|
|
405
|
-
|
|
406
|
-
<template>
|
|
407
|
-
<Tabs v-model="activeTab" :tabs="tabs" variant="underline">
|
|
408
|
-
<TabPanel value="profile">Profile content</TabPanel>
|
|
409
|
-
<TabPanel value="settings">Settings content</TabPanel>
|
|
410
|
-
<TabPanel value="notifications">Notifications content</TabPanel>
|
|
411
|
-
</Tabs>
|
|
412
|
-
</template>
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### Switch
|
|
416
|
-
|
|
417
|
-
```vue
|
|
418
|
-
<Switch
|
|
419
|
-
v-model="emailNotifications"
|
|
420
|
-
label="Email notifications"
|
|
421
|
-
description="Receive email updates about your account"
|
|
422
|
-
/>
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
### Alert
|
|
426
|
-
|
|
427
|
-
```vue
|
|
428
|
-
<Alert variant="success" title="Success!" dismissible>
|
|
429
|
-
Your changes have been saved successfully.
|
|
430
|
-
</Alert>
|
|
431
|
-
|
|
432
|
-
<Alert variant="error" title="Error">
|
|
433
|
-
Something went wrong. Please try again.
|
|
434
|
-
</Alert>
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### Dropdown
|
|
438
|
-
|
|
439
|
-
```vue
|
|
440
|
-
<script setup>
|
|
441
|
-
import { Dropdown } from 'cisse-vue-ui'
|
|
442
|
-
|
|
443
|
-
const items = [
|
|
444
|
-
{ key: 'edit', label: 'Edit', icon: 'lucide:edit' },
|
|
445
|
-
{ key: 'duplicate', label: 'Duplicate', icon: 'lucide:copy' },
|
|
446
|
-
{ key: 'divider', divider: true },
|
|
447
|
-
{ key: 'delete', label: 'Delete', icon: 'lucide:trash', danger: true }
|
|
448
|
-
]
|
|
449
|
-
|
|
450
|
-
const handleSelect = (item) => {
|
|
451
|
-
console.log('Selected:', item.key)
|
|
452
|
-
}
|
|
453
|
-
</script>
|
|
454
|
-
|
|
455
|
-
<template>
|
|
456
|
-
<Dropdown :items="items" @select="handleSelect">
|
|
457
|
-
<template #trigger-label>Actions</template>
|
|
458
|
-
</Dropdown>
|
|
459
|
-
</template>
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
### Stepper
|
|
463
|
-
|
|
464
|
-
```vue
|
|
465
|
-
<script setup>
|
|
466
|
-
import { ref } from 'vue'
|
|
467
|
-
import { Stepper } from 'cisse-vue-ui'
|
|
468
|
-
|
|
469
|
-
const currentStep = ref('step2')
|
|
470
|
-
const steps = [
|
|
471
|
-
{ key: 'step1', title: 'Account', description: 'Create account', icon: 'lucide:user' },
|
|
472
|
-
{ key: 'step2', title: 'Profile', description: 'Set up profile', icon: 'lucide:settings' },
|
|
473
|
-
{ key: 'step3', title: 'Complete', description: 'Ready to go!', icon: 'lucide:check' }
|
|
474
|
-
]
|
|
475
|
-
</script>
|
|
476
|
-
|
|
477
|
-
<template>
|
|
478
|
-
<Stepper v-model="currentStep" :steps="steps" />
|
|
479
|
-
</template>
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### EmptyState
|
|
483
|
-
|
|
484
|
-
```vue
|
|
485
|
-
<EmptyState
|
|
486
|
-
title="No results found"
|
|
487
|
-
message="Try adjusting your search or filters"
|
|
488
|
-
icon="lucide:search-x"
|
|
489
|
-
>
|
|
490
|
-
<template #action>
|
|
491
|
-
<Button variant="primary" size="sm">Clear filters</Button>
|
|
492
|
-
</template>
|
|
493
|
-
</EmptyState>
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
### Checkbox
|
|
497
|
-
|
|
498
|
-
```vue
|
|
499
|
-
<Checkbox
|
|
500
|
-
v-model="accepted"
|
|
501
|
-
label="Accept terms"
|
|
502
|
-
description="I agree to the terms and conditions"
|
|
503
|
-
/>
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
### TableComponent
|
|
507
|
-
|
|
508
|
-
```vue
|
|
509
|
-
<script setup>
|
|
510
|
-
import { ref } from 'vue'
|
|
511
|
-
import { TableComponent } from 'cisse-vue-ui'
|
|
512
|
-
|
|
513
|
-
const properties = [
|
|
514
|
-
{ name: 'name', label: 'Name', main: true },
|
|
515
|
-
{ name: 'email', label: 'Email' },
|
|
516
|
-
{ name: 'role', label: 'Role', type: 'badge' }
|
|
517
|
-
]
|
|
518
|
-
|
|
519
|
-
const items = [
|
|
520
|
-
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
|
|
521
|
-
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
|
|
522
|
-
]
|
|
523
|
-
|
|
524
|
-
// Selection support
|
|
525
|
-
const selectedItems = ref(new Set())
|
|
526
|
-
const toggleSelect = (id) => {
|
|
527
|
-
if (selectedItems.value.has(id)) {
|
|
528
|
-
selectedItems.value.delete(id)
|
|
529
|
-
} else {
|
|
530
|
-
selectedItems.value.add(id)
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
</script>
|
|
534
|
-
|
|
535
|
-
<template>
|
|
536
|
-
<TableComponent
|
|
537
|
-
:properties="properties"
|
|
538
|
-
:items="items"
|
|
539
|
-
selectable
|
|
540
|
-
:selected-items="selectedItems"
|
|
541
|
-
@select="toggleSelect"
|
|
542
|
-
@select-all="toggleSelectAll"
|
|
543
|
-
>
|
|
544
|
-
<template #action="{ item }">
|
|
545
|
-
<TableAction icon="lucide:edit" @click="edit(item)" />
|
|
546
|
-
<TableAction icon="lucide:trash" variant="danger" @click="delete(item)" />
|
|
547
|
-
</template>
|
|
548
|
-
</TableComponent>
|
|
549
|
-
</template>
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
### ResponsiveList
|
|
553
|
-
|
|
554
|
-
A component that automatically switches between a mobile card layout and a desktop table layout based on screen size.
|
|
555
|
-
|
|
556
|
-
```vue
|
|
557
|
-
<script setup>
|
|
558
|
-
import { ref } from 'vue'
|
|
559
|
-
import { ResponsiveList } from 'cisse-vue-ui'
|
|
560
|
-
|
|
561
|
-
const columns = [
|
|
562
|
-
{ key: 'name', label: 'Name' },
|
|
563
|
-
{ key: 'email', label: 'Email' },
|
|
564
|
-
{ key: 'status', label: 'Status' }
|
|
565
|
-
]
|
|
566
|
-
|
|
567
|
-
const items = [
|
|
568
|
-
{ id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
|
|
569
|
-
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' }
|
|
570
|
-
]
|
|
571
|
-
|
|
572
|
-
const selectedItems = ref(new Set())
|
|
573
|
-
|
|
574
|
-
const toggleSelect = (id) => {
|
|
575
|
-
if (selectedItems.value.has(id)) {
|
|
576
|
-
selectedItems.value.delete(id)
|
|
577
|
-
} else {
|
|
578
|
-
selectedItems.value.add(id)
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
const toggleSelectAll = () => {
|
|
583
|
-
if (selectedItems.value.size === items.length) {
|
|
584
|
-
selectedItems.value.clear()
|
|
585
|
-
} else {
|
|
586
|
-
items.forEach(item => selectedItems.value.add(String(item.id)))
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
</script>
|
|
590
|
-
|
|
591
|
-
<template>
|
|
592
|
-
<ResponsiveList
|
|
593
|
-
:items="items"
|
|
594
|
-
:columns="columns"
|
|
595
|
-
key-field="id"
|
|
596
|
-
selectable
|
|
597
|
-
:selected-items="selectedItems"
|
|
598
|
-
breakpoint="lg"
|
|
599
|
-
@select="toggleSelect"
|
|
600
|
-
@select-all="toggleSelectAll"
|
|
601
|
-
>
|
|
602
|
-
<!-- Mobile view: avatar -->
|
|
603
|
-
<template #avatar="{ item }">
|
|
604
|
-
<div class="w-10 h-10 rounded-full bg-primary-500 flex items-center justify-center text-white">
|
|
605
|
-
{{ item.name[0] }}
|
|
606
|
-
</div>
|
|
607
|
-
</template>
|
|
608
|
-
|
|
609
|
-
<!-- Mobile view: content -->
|
|
610
|
-
<template #mobileContent="{ item }">
|
|
611
|
-
<h3 class="font-semibold">{{ item.name }}</h3>
|
|
612
|
-
<p class="text-sm text-gray-500">{{ item.email }}</p>
|
|
613
|
-
</template>
|
|
614
|
-
|
|
615
|
-
<!-- Mobile view: actions -->
|
|
616
|
-
<template #mobileActions="{ item }">
|
|
617
|
-
<button @click="viewItem(item)">View</button>
|
|
618
|
-
</template>
|
|
619
|
-
|
|
620
|
-
<!-- Desktop table: custom cell rendering -->
|
|
621
|
-
<template #cell-name="{ item }">
|
|
622
|
-
<span class="font-medium">{{ item.name }}</span>
|
|
623
|
-
</template>
|
|
624
|
-
|
|
625
|
-
<template #cell-status="{ item }">
|
|
626
|
-
<span :class="item.status === 'active' ? 'text-green-600' : 'text-red-600'">
|
|
627
|
-
{{ item.status }}
|
|
628
|
-
</span>
|
|
629
|
-
</template>
|
|
630
|
-
|
|
631
|
-
<!-- Desktop table: actions column -->
|
|
632
|
-
<template #actions="{ item }">
|
|
633
|
-
<Button size="sm" variant="ghost" @click="edit(item)">Edit</Button>
|
|
634
|
-
</template>
|
|
635
|
-
|
|
636
|
-
<!-- Empty state -->
|
|
637
|
-
<template #empty>
|
|
638
|
-
<EmptyState title="No items" message="No items to display" />
|
|
639
|
-
</template>
|
|
640
|
-
</ResponsiveList>
|
|
641
|
-
</template>
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
#### ResponsiveList Props
|
|
645
|
-
|
|
646
|
-
| Prop | Type | Default | Description |
|
|
647
|
-
|------|------|---------|-------------|
|
|
648
|
-
| `items` | `Array` | required | Array of items to display |
|
|
649
|
-
| `columns` | `Array` | required | Column definitions with `key` or `name`, `label`, and optional `type` |
|
|
650
|
-
| `keyField` | `string` | `'id'` | Field to use as unique key for items |
|
|
651
|
-
| `selectable` | `boolean` | `false` | Enable selection mode |
|
|
652
|
-
| `selectedItems` | `Set<string>` | - | Set of selected item keys |
|
|
653
|
-
| `selectableFilter` | `Function` | - | Filter function to determine if an item is selectable |
|
|
654
|
-
| `breakpoint` | `string` | `'lg'` | Breakpoint for switching views: `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'` |
|
|
655
|
-
|
|
656
|
-
### MobileList
|
|
657
|
-
|
|
658
|
-
A mobile-optimized card-based list component with selection support.
|
|
659
|
-
|
|
660
|
-
```vue
|
|
661
|
-
<script setup>
|
|
662
|
-
import { MobileList } from 'cisse-vue-ui'
|
|
663
|
-
</script>
|
|
664
|
-
|
|
665
|
-
<template>
|
|
666
|
-
<MobileList
|
|
667
|
-
:items="items"
|
|
668
|
-
key-field="id"
|
|
669
|
-
selectable
|
|
670
|
-
:selected-items="selectedItems"
|
|
671
|
-
@select="toggleSelect"
|
|
672
|
-
@select-all="toggleSelectAll"
|
|
673
|
-
>
|
|
674
|
-
<template #avatar="{ item }">
|
|
675
|
-
<div class="w-12 h-12 rounded-full bg-blue-500" />
|
|
676
|
-
</template>
|
|
677
|
-
|
|
678
|
-
<template #content="{ item }">
|
|
679
|
-
<h3>{{ item.name }}</h3>
|
|
680
|
-
<p>{{ item.description }}</p>
|
|
681
|
-
</template>
|
|
682
|
-
|
|
683
|
-
<template #actions="{ item }">
|
|
684
|
-
<button>View</button>
|
|
685
|
-
</template>
|
|
686
|
-
</MobileList>
|
|
687
|
-
</template>
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
### MenuItem
|
|
691
|
-
|
|
692
|
-
```vue
|
|
693
|
-
<script setup>
|
|
694
|
-
import { useRoute } from 'vue-router'
|
|
695
|
-
import { MenuItem } from 'cisse-vue-ui'
|
|
696
|
-
|
|
697
|
-
const route = useRoute()
|
|
698
|
-
|
|
699
|
-
const menuItem = {
|
|
700
|
-
label: 'Dashboard',
|
|
701
|
-
link: '/dashboard',
|
|
702
|
-
icon: 'lucide:layout-dashboard'
|
|
703
|
-
}
|
|
704
|
-
</script>
|
|
705
|
-
|
|
706
|
-
<template>
|
|
707
|
-
<!-- Auto-detect active state from current route -->
|
|
708
|
-
<MenuItem :menu-item="menuItem" :current-path="route.path" />
|
|
709
|
-
|
|
710
|
-
<!-- Or manually control active state -->
|
|
711
|
-
<MenuItem :menu-item="menuItem" :active="true" />
|
|
712
|
-
</template>
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
### AuthLayout
|
|
716
|
-
|
|
717
|
-
Split-panel layout for authentication pages (login, register, password reset).
|
|
718
|
-
|
|
719
|
-
```vue
|
|
720
|
-
<script setup>
|
|
721
|
-
import { ref } from 'vue'
|
|
722
|
-
import { AuthLayout, type AuthFeature } from 'cisse-vue-ui'
|
|
723
|
-
|
|
724
|
-
const email = ref('')
|
|
725
|
-
const password = ref('')
|
|
726
|
-
|
|
727
|
-
const features: AuthFeature[] = [
|
|
728
|
-
{ icon: 'lucide:shield', text: 'Secure authentication' },
|
|
729
|
-
{ icon: 'lucide:zap', text: 'Fast login' },
|
|
730
|
-
{ icon: 'lucide:users', text: 'Team collaboration' },
|
|
731
|
-
]
|
|
732
|
-
|
|
733
|
-
function handleSubmit() {
|
|
734
|
-
// Handle login
|
|
735
|
-
}
|
|
736
|
-
</script>
|
|
737
|
-
|
|
738
|
-
<template>
|
|
739
|
-
<AuthLayout
|
|
740
|
-
app-name="My App"
|
|
741
|
-
app-icon="lucide:box"
|
|
742
|
-
headline="Welcome to"
|
|
743
|
-
sub-headline="My Application"
|
|
744
|
-
description="Sign in to access your account."
|
|
745
|
-
:features="features"
|
|
746
|
-
form-title="Sign In"
|
|
747
|
-
form-subtitle="Enter your credentials"
|
|
748
|
-
home-link="/"
|
|
749
|
-
>
|
|
750
|
-
<form @submit.prevent="handleSubmit" class="space-y-4">
|
|
751
|
-
<input v-model="email" type="email" placeholder="Email" class="..." />
|
|
752
|
-
<input v-model="password" type="password" placeholder="Password" class="..." />
|
|
753
|
-
<button type="submit">Sign In</button>
|
|
754
|
-
</form>
|
|
755
|
-
|
|
756
|
-
<template #form-footer>
|
|
757
|
-
<p class="text-center mt-6">
|
|
758
|
-
Don't have an account? <a href="/register">Sign up</a>
|
|
759
|
-
</p>
|
|
760
|
-
</template>
|
|
761
|
-
</AuthLayout>
|
|
762
|
-
</template>
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
#### AuthLayout Props
|
|
766
|
-
|
|
767
|
-
| Prop | Type | Default | Description |
|
|
768
|
-
|------|------|---------|-------------|
|
|
769
|
-
| `appName` | `string` | `''` | Application name displayed in logo |
|
|
770
|
-
| `appIcon` | `string` | `'lucide:box'` | Iconify icon for app logo |
|
|
771
|
-
| `headline` | `string` | `''` | First line of headline |
|
|
772
|
-
| `subHeadline` | `string` | `''` | Second line with decorative underline |
|
|
773
|
-
| `description` | `string` | `''` | Description paragraph |
|
|
774
|
-
| `features` | `AuthFeature[]` | `[]` | List of features with icon and text |
|
|
775
|
-
| `gradientFrom` | `string` | `'from-primary-700'` | Tailwind gradient start class |
|
|
776
|
-
| `gradientVia` | `string` | `''` | Tailwind gradient middle class |
|
|
777
|
-
| `gradientTo` | `string` | `'to-primary-800'` | Tailwind gradient end class |
|
|
778
|
-
| `showDecorations` | `boolean` | `true` | Show floating decorative shapes |
|
|
779
|
-
| `showPattern` | `boolean` | `true` | Show dot pattern overlay |
|
|
780
|
-
| `underlineColor` | `string` | `'rgba(165, 180, 252, 0.5)'` | Sub-headline underline color |
|
|
781
|
-
| `formTitle` | `string` | `''` | Form panel title |
|
|
782
|
-
| `formSubtitle` | `string` | `''` | Form panel subtitle |
|
|
783
|
-
| `homeLink` | `string` | `'/'` | Mobile logo link URL |
|
|
784
|
-
| `brandingAnimation` | `string` | `''` | CSS class for branding animation |
|
|
785
|
-
| `formAnimation` | `string` | `''` | CSS class for form panel animation |
|
|
786
|
-
|
|
787
|
-
#### AuthLayout Slots
|
|
788
|
-
|
|
789
|
-
| Slot | Description |
|
|
790
|
-
|------|-------------|
|
|
791
|
-
| `default` | Form content (inside the white card) |
|
|
792
|
-
| `branding-panel` | Complete override of the branding panel |
|
|
793
|
-
| `branding-logo` | Custom logo in branding panel |
|
|
794
|
-
| `branding-headline` | Custom headline content |
|
|
795
|
-
| `branding-features` | Custom features list |
|
|
796
|
-
| `branding-content` | Additional content after features |
|
|
797
|
-
| `mobile-logo` | Custom mobile logo |
|
|
798
|
-
| `form-header` | Content above the form card |
|
|
799
|
-
| `form-footer` | Content below the form card |
|
|
800
|
-
|
|
801
|
-
### BaseLayout
|
|
802
|
-
|
|
803
|
-
```vue
|
|
804
|
-
<script setup>
|
|
805
|
-
import { useRoute } from 'vue-router'
|
|
806
|
-
import { BaseLayout } from 'cisse-vue-ui'
|
|
807
|
-
|
|
808
|
-
const route = useRoute()
|
|
809
|
-
|
|
810
|
-
const menuItems = [
|
|
811
|
-
{ label: 'Dashboard', link: '/', icon: 'lucide:home' },
|
|
812
|
-
{ label: 'Users', link: '/users', icon: 'lucide:users' },
|
|
813
|
-
{ label: 'Settings', link: '/settings', icon: 'lucide:settings' }
|
|
814
|
-
]
|
|
815
|
-
</script>
|
|
816
|
-
|
|
817
|
-
<template>
|
|
818
|
-
<BaseLayout
|
|
819
|
-
:menu-items="menuItems"
|
|
820
|
-
:current-path="route.path"
|
|
821
|
-
:show-dark-toggle="true"
|
|
822
|
-
menu-position="top"
|
|
823
|
-
>
|
|
824
|
-
<template #logo>
|
|
825
|
-
<img src="/logo.svg" alt="Logo" class="h-8" />
|
|
826
|
-
</template>
|
|
827
|
-
|
|
828
|
-
<RouterView />
|
|
829
|
-
</BaseLayout>
|
|
830
|
-
</template>
|
|
831
|
-
```
|
|
832
|
-
|
|
833
|
-
#### BaseLayout Props
|
|
834
|
-
|
|
835
|
-
| Prop | Type | Default | Description |
|
|
836
|
-
|------|------|---------|-------------|
|
|
837
|
-
| `menuItems` | `MenuItemProps[]` | `[]` | Menu items for the sidebar |
|
|
838
|
-
| `appName` | `string` | `'App'` | App/brand name displayed in sidebar |
|
|
839
|
-
| `appIcon` | `string` | `'lucide:box'` | App icon (Iconify icon name) |
|
|
840
|
-
| `sidebarOpen` | `boolean` | `true` | Whether sidebar is open (v-model:sidebarOpen) |
|
|
841
|
-
| `dark` | `boolean` | `false` | Whether dark mode is enabled (v-model:dark) |
|
|
842
|
-
| `showDarkToggle` | `boolean` | `true` | Show dark mode toggle in header |
|
|
843
|
-
| `sidebarClass` | `string` | `'bg-[#172b4c]...'` | CSS classes for sidebar background |
|
|
844
|
-
| `currentPath` | `string` | - | Current route path for menu active state |
|
|
845
|
-
| `userName` | `string` | - | User display name |
|
|
846
|
-
| `userAvatar` | `string` | - | User avatar (initials or image URL) |
|
|
847
|
-
| `userMenuItems` | `UserMenuItem[]` | `[]` | User menu dropdown items |
|
|
848
|
-
| `menuPosition` | `'top' \| 'center' \| 'bottom'` | `'top'` | Menu vertical position in sidebar |
|
|
849
|
-
|
|
850
|
-
#### BaseLayout Slots
|
|
851
|
-
|
|
852
|
-
| Slot | Description |
|
|
853
|
-
|------|-------------|
|
|
854
|
-
| `default` | Main content area (or renders RouterView if available) |
|
|
855
|
-
| `logo` | Custom logo in sidebar header |
|
|
856
|
-
| `menu` | Custom menu content (receives currentPath) |
|
|
857
|
-
| `sidebar-footer` | Content at bottom of sidebar |
|
|
858
|
-
| `header-center` | Center content in header |
|
|
859
|
-
| `header-actions` | Action buttons in header (before dark toggle) |
|
|
860
|
-
|
|
861
|
-
## Dark Mode
|
|
862
|
-
|
|
863
|
-
Components support dark mode via the `.dark` class on a parent element:
|
|
864
|
-
|
|
865
|
-
```html
|
|
866
|
-
<html class="dark">
|
|
867
|
-
<!-- Components will use dark theme -->
|
|
868
|
-
</html>
|
|
869
|
-
```
|
|
870
|
-
|
|
871
|
-
Use the `useDarkMode` composable or implement your own toggle:
|
|
872
|
-
|
|
873
|
-
```typescript
|
|
874
|
-
const { isDark, toggle } = useDarkMode()
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
## License
|
|
878
|
-
|
|
879
|
-
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
|
+
| `AuthLayout` | Split-panel authentication layout with branding and form sections |
|
|
162
|
+
| `BaseLayout` | App shell with sidebar, header, main content area, and route-aware menu |
|
|
163
|
+
| `PageLayout` | Page wrapper with breadcrumbs |
|
|
164
|
+
|
|
165
|
+
### Type Display
|
|
166
|
+
|
|
167
|
+
| Component | Description |
|
|
168
|
+
|-----------|-------------|
|
|
169
|
+
| `TextType` | Text value display |
|
|
170
|
+
| `NumberType` | Formatted number display |
|
|
171
|
+
| `DateType` | Formatted date display |
|
|
172
|
+
| `BooleanType` | Boolean value display (check/cross icons) |
|
|
173
|
+
| `BadgeType` | Badge value display with colors |
|
|
174
|
+
|
|
175
|
+
## Composables
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import {
|
|
179
|
+
useNotifications,
|
|
180
|
+
useDarkMode,
|
|
181
|
+
useExportCSV,
|
|
182
|
+
useDropdown,
|
|
183
|
+
useModal,
|
|
184
|
+
useToast,
|
|
185
|
+
useFocusTrap,
|
|
186
|
+
useId
|
|
187
|
+
} from 'cisse-vue-ui/composables'
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### useModal
|
|
191
|
+
|
|
192
|
+
Manage modal state with data support:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { useModal } from 'cisse-vue-ui/composables'
|
|
196
|
+
|
|
197
|
+
// Simple modal
|
|
198
|
+
const createModal = useModal()
|
|
199
|
+
createModal.open()
|
|
200
|
+
createModal.close()
|
|
201
|
+
|
|
202
|
+
// Modal with data (e.g., for editing)
|
|
203
|
+
const editModal = useModal<User>()
|
|
204
|
+
editModal.open(selectedUser)
|
|
205
|
+
// Access editModal.data.value in template
|
|
206
|
+
|
|
207
|
+
// With callbacks
|
|
208
|
+
const deleteModal = useModal<Item>({
|
|
209
|
+
onOpen: (data) => console.log('Opening with:', data),
|
|
210
|
+
onClose: () => refetchData()
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```vue
|
|
215
|
+
<template>
|
|
216
|
+
<!-- Use isOpen for v-model binding -->
|
|
217
|
+
<Modal v-model="editModal.isOpen.value" title="Edit User">
|
|
218
|
+
<FormInput v-model="editModal.data.value.name" label="Name" />
|
|
219
|
+
<template #footer>
|
|
220
|
+
<Button @click="editModal.close()">Cancel</Button>
|
|
221
|
+
<Button variant="primary" @click="save">Save</Button>
|
|
222
|
+
</template>
|
|
223
|
+
</Modal>
|
|
224
|
+
</template>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### useDropdown
|
|
228
|
+
|
|
229
|
+
Shared dropdown logic for custom dropdown components (used internally by Dropdown, FormSelect, AutocompleteComponent):
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { useDropdown } from 'cisse-vue-ui/composables'
|
|
233
|
+
import { ref } from 'vue'
|
|
234
|
+
|
|
235
|
+
const triggerRef = ref<HTMLElement>()
|
|
236
|
+
const dropdownRef = ref<HTMLElement>()
|
|
237
|
+
|
|
238
|
+
const {
|
|
239
|
+
isOpen,
|
|
240
|
+
highlightedIndex,
|
|
241
|
+
dropdownStyle,
|
|
242
|
+
open,
|
|
243
|
+
close,
|
|
244
|
+
toggle,
|
|
245
|
+
handleKeydown,
|
|
246
|
+
scrollToHighlighted,
|
|
247
|
+
} = useDropdown(triggerRef, dropdownRef, {
|
|
248
|
+
teleport: true,
|
|
249
|
+
align: 'left',
|
|
250
|
+
gap: 8,
|
|
251
|
+
onOpen: () => console.log('Opened'),
|
|
252
|
+
onClose: () => console.log('Closed'),
|
|
253
|
+
})
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### useNotifications
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const { notifications, addNotification, removeNotification } = useNotifications()
|
|
260
|
+
|
|
261
|
+
addNotification({
|
|
262
|
+
type: 'success',
|
|
263
|
+
title: 'Saved',
|
|
264
|
+
message: 'Your changes have been saved.'
|
|
265
|
+
})
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### useDarkMode
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
const { isDark, toggle, enable, disable } = useDarkMode({
|
|
272
|
+
selector: 'html', // Element to add .dark class
|
|
273
|
+
storageKey: 'theme', // localStorage key
|
|
274
|
+
defaultDark: false // Default state
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### useExportCSV
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const { exportToCSV } = useExportCSV()
|
|
282
|
+
|
|
283
|
+
exportToCSV(data, columns, 'export.csv')
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### useToast
|
|
287
|
+
|
|
288
|
+
Toast notification system with positioning and auto-dismiss:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { useToast } from 'cisse-vue-ui/composables'
|
|
292
|
+
|
|
293
|
+
const { toasts, addToast, removeToast, clearToasts } = useToast()
|
|
294
|
+
|
|
295
|
+
// Add a toast
|
|
296
|
+
addToast({
|
|
297
|
+
type: 'success',
|
|
298
|
+
title: 'Success!',
|
|
299
|
+
message: 'Your changes have been saved.',
|
|
300
|
+
duration: 5000 // auto-dismiss after 5s
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
// Different toast types
|
|
304
|
+
addToast({ type: 'error', title: 'Error', message: 'Something went wrong' })
|
|
305
|
+
addToast({ type: 'warning', title: 'Warning', message: 'Please review' })
|
|
306
|
+
addToast({ type: 'info', title: 'Info', message: 'New update available' })
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
```vue
|
|
310
|
+
<template>
|
|
311
|
+
<!-- Add ToastContainer to your app root -->
|
|
312
|
+
<ToastContainer position="top-right" />
|
|
313
|
+
</template>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### useFocusTrap
|
|
317
|
+
|
|
318
|
+
Trap focus within a container (used internally by Modal):
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { useFocusTrap } from 'cisse-vue-ui/composables'
|
|
322
|
+
import { ref } from 'vue'
|
|
323
|
+
|
|
324
|
+
const isActive = ref(true)
|
|
325
|
+
const { containerRef } = useFocusTrap({
|
|
326
|
+
active: isActive,
|
|
327
|
+
focusFirst: true, // Focus first focusable element on activate
|
|
328
|
+
restoreFocus: true // Restore focus on deactivate
|
|
329
|
+
})
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### useId
|
|
333
|
+
|
|
334
|
+
Generate unique IDs for accessibility (ARIA relationships):
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
import { useId } from 'cisse-vue-ui/composables'
|
|
338
|
+
|
|
339
|
+
const { id, related } = useId({ prefix: 'modal' })
|
|
340
|
+
// id.value = 'cisse-modal-1'
|
|
341
|
+
// related('title') = 'cisse-modal-1-title'
|
|
342
|
+
// related('description') = 'cisse-modal-1-description'
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
```vue
|
|
346
|
+
<template>
|
|
347
|
+
<div :id="id" role="dialog" :aria-labelledby="related('title')">
|
|
348
|
+
<h2 :id="related('title')">Dialog Title</h2>
|
|
349
|
+
</div>
|
|
350
|
+
</template>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Types
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import type { Property, Notification, Breadcrumb } from 'cisse-vue-ui/types'
|
|
357
|
+
|
|
358
|
+
// Table column definition
|
|
359
|
+
const columns: Property[] = [
|
|
360
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
361
|
+
{ key: 'email', label: 'Email' },
|
|
362
|
+
{ key: 'status', label: 'Status', type: 'badge' }
|
|
363
|
+
]
|
|
364
|
+
|
|
365
|
+
// Breadcrumb navigation
|
|
366
|
+
const breadcrumbs: Breadcrumb[] = [
|
|
367
|
+
{ label: 'Home', to: '/' },
|
|
368
|
+
{ label: 'Users', to: '/users' },
|
|
369
|
+
{ label: 'Edit' }
|
|
370
|
+
]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Component Examples
|
|
374
|
+
|
|
375
|
+
### Button
|
|
376
|
+
|
|
377
|
+
```vue
|
|
378
|
+
<Button variant="primary" size="md" :loading="isLoading">
|
|
379
|
+
Save Changes
|
|
380
|
+
</Button>
|
|
381
|
+
|
|
382
|
+
<Button variant="outline" icon="lucide:plus">
|
|
383
|
+
Add Item
|
|
384
|
+
</Button>
|
|
385
|
+
|
|
386
|
+
<Button variant="danger" icon="lucide:trash">
|
|
387
|
+
Delete
|
|
388
|
+
</Button>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Tabs
|
|
392
|
+
|
|
393
|
+
```vue
|
|
394
|
+
<script setup>
|
|
395
|
+
import { ref } from 'vue'
|
|
396
|
+
import { Tabs, TabPanel } from 'cisse-vue-ui'
|
|
397
|
+
|
|
398
|
+
const activeTab = ref('profile')
|
|
399
|
+
const tabs = [
|
|
400
|
+
{ key: 'profile', label: 'Profile' },
|
|
401
|
+
{ key: 'settings', label: 'Settings' },
|
|
402
|
+
{ key: 'notifications', label: 'Notifications' }
|
|
403
|
+
]
|
|
404
|
+
</script>
|
|
405
|
+
|
|
406
|
+
<template>
|
|
407
|
+
<Tabs v-model="activeTab" :tabs="tabs" variant="underline">
|
|
408
|
+
<TabPanel value="profile">Profile content</TabPanel>
|
|
409
|
+
<TabPanel value="settings">Settings content</TabPanel>
|
|
410
|
+
<TabPanel value="notifications">Notifications content</TabPanel>
|
|
411
|
+
</Tabs>
|
|
412
|
+
</template>
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Switch
|
|
416
|
+
|
|
417
|
+
```vue
|
|
418
|
+
<Switch
|
|
419
|
+
v-model="emailNotifications"
|
|
420
|
+
label="Email notifications"
|
|
421
|
+
description="Receive email updates about your account"
|
|
422
|
+
/>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Alert
|
|
426
|
+
|
|
427
|
+
```vue
|
|
428
|
+
<Alert variant="success" title="Success!" dismissible>
|
|
429
|
+
Your changes have been saved successfully.
|
|
430
|
+
</Alert>
|
|
431
|
+
|
|
432
|
+
<Alert variant="error" title="Error">
|
|
433
|
+
Something went wrong. Please try again.
|
|
434
|
+
</Alert>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Dropdown
|
|
438
|
+
|
|
439
|
+
```vue
|
|
440
|
+
<script setup>
|
|
441
|
+
import { Dropdown } from 'cisse-vue-ui'
|
|
442
|
+
|
|
443
|
+
const items = [
|
|
444
|
+
{ key: 'edit', label: 'Edit', icon: 'lucide:edit' },
|
|
445
|
+
{ key: 'duplicate', label: 'Duplicate', icon: 'lucide:copy' },
|
|
446
|
+
{ key: 'divider', divider: true },
|
|
447
|
+
{ key: 'delete', label: 'Delete', icon: 'lucide:trash', danger: true }
|
|
448
|
+
]
|
|
449
|
+
|
|
450
|
+
const handleSelect = (item) => {
|
|
451
|
+
console.log('Selected:', item.key)
|
|
452
|
+
}
|
|
453
|
+
</script>
|
|
454
|
+
|
|
455
|
+
<template>
|
|
456
|
+
<Dropdown :items="items" @select="handleSelect">
|
|
457
|
+
<template #trigger-label>Actions</template>
|
|
458
|
+
</Dropdown>
|
|
459
|
+
</template>
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Stepper
|
|
463
|
+
|
|
464
|
+
```vue
|
|
465
|
+
<script setup>
|
|
466
|
+
import { ref } from 'vue'
|
|
467
|
+
import { Stepper } from 'cisse-vue-ui'
|
|
468
|
+
|
|
469
|
+
const currentStep = ref('step2')
|
|
470
|
+
const steps = [
|
|
471
|
+
{ key: 'step1', title: 'Account', description: 'Create account', icon: 'lucide:user' },
|
|
472
|
+
{ key: 'step2', title: 'Profile', description: 'Set up profile', icon: 'lucide:settings' },
|
|
473
|
+
{ key: 'step3', title: 'Complete', description: 'Ready to go!', icon: 'lucide:check' }
|
|
474
|
+
]
|
|
475
|
+
</script>
|
|
476
|
+
|
|
477
|
+
<template>
|
|
478
|
+
<Stepper v-model="currentStep" :steps="steps" />
|
|
479
|
+
</template>
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### EmptyState
|
|
483
|
+
|
|
484
|
+
```vue
|
|
485
|
+
<EmptyState
|
|
486
|
+
title="No results found"
|
|
487
|
+
message="Try adjusting your search or filters"
|
|
488
|
+
icon="lucide:search-x"
|
|
489
|
+
>
|
|
490
|
+
<template #action>
|
|
491
|
+
<Button variant="primary" size="sm">Clear filters</Button>
|
|
492
|
+
</template>
|
|
493
|
+
</EmptyState>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Checkbox
|
|
497
|
+
|
|
498
|
+
```vue
|
|
499
|
+
<Checkbox
|
|
500
|
+
v-model="accepted"
|
|
501
|
+
label="Accept terms"
|
|
502
|
+
description="I agree to the terms and conditions"
|
|
503
|
+
/>
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### TableComponent
|
|
507
|
+
|
|
508
|
+
```vue
|
|
509
|
+
<script setup>
|
|
510
|
+
import { ref } from 'vue'
|
|
511
|
+
import { TableComponent } from 'cisse-vue-ui'
|
|
512
|
+
|
|
513
|
+
const properties = [
|
|
514
|
+
{ name: 'name', label: 'Name', main: true },
|
|
515
|
+
{ name: 'email', label: 'Email' },
|
|
516
|
+
{ name: 'role', label: 'Role', type: 'badge' }
|
|
517
|
+
]
|
|
518
|
+
|
|
519
|
+
const items = [
|
|
520
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
|
|
521
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
|
|
522
|
+
]
|
|
523
|
+
|
|
524
|
+
// Selection support
|
|
525
|
+
const selectedItems = ref(new Set())
|
|
526
|
+
const toggleSelect = (id) => {
|
|
527
|
+
if (selectedItems.value.has(id)) {
|
|
528
|
+
selectedItems.value.delete(id)
|
|
529
|
+
} else {
|
|
530
|
+
selectedItems.value.add(id)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
</script>
|
|
534
|
+
|
|
535
|
+
<template>
|
|
536
|
+
<TableComponent
|
|
537
|
+
:properties="properties"
|
|
538
|
+
:items="items"
|
|
539
|
+
selectable
|
|
540
|
+
:selected-items="selectedItems"
|
|
541
|
+
@select="toggleSelect"
|
|
542
|
+
@select-all="toggleSelectAll"
|
|
543
|
+
>
|
|
544
|
+
<template #action="{ item }">
|
|
545
|
+
<TableAction icon="lucide:edit" @click="edit(item)" />
|
|
546
|
+
<TableAction icon="lucide:trash" variant="danger" @click="delete(item)" />
|
|
547
|
+
</template>
|
|
548
|
+
</TableComponent>
|
|
549
|
+
</template>
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### ResponsiveList
|
|
553
|
+
|
|
554
|
+
A component that automatically switches between a mobile card layout and a desktop table layout based on screen size.
|
|
555
|
+
|
|
556
|
+
```vue
|
|
557
|
+
<script setup>
|
|
558
|
+
import { ref } from 'vue'
|
|
559
|
+
import { ResponsiveList } from 'cisse-vue-ui'
|
|
560
|
+
|
|
561
|
+
const columns = [
|
|
562
|
+
{ key: 'name', label: 'Name' },
|
|
563
|
+
{ key: 'email', label: 'Email' },
|
|
564
|
+
{ key: 'status', label: 'Status' }
|
|
565
|
+
]
|
|
566
|
+
|
|
567
|
+
const items = [
|
|
568
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
|
|
569
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' }
|
|
570
|
+
]
|
|
571
|
+
|
|
572
|
+
const selectedItems = ref(new Set())
|
|
573
|
+
|
|
574
|
+
const toggleSelect = (id) => {
|
|
575
|
+
if (selectedItems.value.has(id)) {
|
|
576
|
+
selectedItems.value.delete(id)
|
|
577
|
+
} else {
|
|
578
|
+
selectedItems.value.add(id)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const toggleSelectAll = () => {
|
|
583
|
+
if (selectedItems.value.size === items.length) {
|
|
584
|
+
selectedItems.value.clear()
|
|
585
|
+
} else {
|
|
586
|
+
items.forEach(item => selectedItems.value.add(String(item.id)))
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
</script>
|
|
590
|
+
|
|
591
|
+
<template>
|
|
592
|
+
<ResponsiveList
|
|
593
|
+
:items="items"
|
|
594
|
+
:columns="columns"
|
|
595
|
+
key-field="id"
|
|
596
|
+
selectable
|
|
597
|
+
:selected-items="selectedItems"
|
|
598
|
+
breakpoint="lg"
|
|
599
|
+
@select="toggleSelect"
|
|
600
|
+
@select-all="toggleSelectAll"
|
|
601
|
+
>
|
|
602
|
+
<!-- Mobile view: avatar -->
|
|
603
|
+
<template #avatar="{ item }">
|
|
604
|
+
<div class="w-10 h-10 rounded-full bg-primary-500 flex items-center justify-center text-white">
|
|
605
|
+
{{ item.name[0] }}
|
|
606
|
+
</div>
|
|
607
|
+
</template>
|
|
608
|
+
|
|
609
|
+
<!-- Mobile view: content -->
|
|
610
|
+
<template #mobileContent="{ item }">
|
|
611
|
+
<h3 class="font-semibold">{{ item.name }}</h3>
|
|
612
|
+
<p class="text-sm text-gray-500">{{ item.email }}</p>
|
|
613
|
+
</template>
|
|
614
|
+
|
|
615
|
+
<!-- Mobile view: actions -->
|
|
616
|
+
<template #mobileActions="{ item }">
|
|
617
|
+
<button @click="viewItem(item)">View</button>
|
|
618
|
+
</template>
|
|
619
|
+
|
|
620
|
+
<!-- Desktop table: custom cell rendering -->
|
|
621
|
+
<template #cell-name="{ item }">
|
|
622
|
+
<span class="font-medium">{{ item.name }}</span>
|
|
623
|
+
</template>
|
|
624
|
+
|
|
625
|
+
<template #cell-status="{ item }">
|
|
626
|
+
<span :class="item.status === 'active' ? 'text-green-600' : 'text-red-600'">
|
|
627
|
+
{{ item.status }}
|
|
628
|
+
</span>
|
|
629
|
+
</template>
|
|
630
|
+
|
|
631
|
+
<!-- Desktop table: actions column -->
|
|
632
|
+
<template #actions="{ item }">
|
|
633
|
+
<Button size="sm" variant="ghost" @click="edit(item)">Edit</Button>
|
|
634
|
+
</template>
|
|
635
|
+
|
|
636
|
+
<!-- Empty state -->
|
|
637
|
+
<template #empty>
|
|
638
|
+
<EmptyState title="No items" message="No items to display" />
|
|
639
|
+
</template>
|
|
640
|
+
</ResponsiveList>
|
|
641
|
+
</template>
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
#### ResponsiveList Props
|
|
645
|
+
|
|
646
|
+
| Prop | Type | Default | Description |
|
|
647
|
+
|------|------|---------|-------------|
|
|
648
|
+
| `items` | `Array` | required | Array of items to display |
|
|
649
|
+
| `columns` | `Array` | required | Column definitions with `key` or `name`, `label`, and optional `type` |
|
|
650
|
+
| `keyField` | `string` | `'id'` | Field to use as unique key for items |
|
|
651
|
+
| `selectable` | `boolean` | `false` | Enable selection mode |
|
|
652
|
+
| `selectedItems` | `Set<string>` | - | Set of selected item keys |
|
|
653
|
+
| `selectableFilter` | `Function` | - | Filter function to determine if an item is selectable |
|
|
654
|
+
| `breakpoint` | `string` | `'lg'` | Breakpoint for switching views: `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'` |
|
|
655
|
+
|
|
656
|
+
### MobileList
|
|
657
|
+
|
|
658
|
+
A mobile-optimized card-based list component with selection support.
|
|
659
|
+
|
|
660
|
+
```vue
|
|
661
|
+
<script setup>
|
|
662
|
+
import { MobileList } from 'cisse-vue-ui'
|
|
663
|
+
</script>
|
|
664
|
+
|
|
665
|
+
<template>
|
|
666
|
+
<MobileList
|
|
667
|
+
:items="items"
|
|
668
|
+
key-field="id"
|
|
669
|
+
selectable
|
|
670
|
+
:selected-items="selectedItems"
|
|
671
|
+
@select="toggleSelect"
|
|
672
|
+
@select-all="toggleSelectAll"
|
|
673
|
+
>
|
|
674
|
+
<template #avatar="{ item }">
|
|
675
|
+
<div class="w-12 h-12 rounded-full bg-blue-500" />
|
|
676
|
+
</template>
|
|
677
|
+
|
|
678
|
+
<template #content="{ item }">
|
|
679
|
+
<h3>{{ item.name }}</h3>
|
|
680
|
+
<p>{{ item.description }}</p>
|
|
681
|
+
</template>
|
|
682
|
+
|
|
683
|
+
<template #actions="{ item }">
|
|
684
|
+
<button>View</button>
|
|
685
|
+
</template>
|
|
686
|
+
</MobileList>
|
|
687
|
+
</template>
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### MenuItem
|
|
691
|
+
|
|
692
|
+
```vue
|
|
693
|
+
<script setup>
|
|
694
|
+
import { useRoute } from 'vue-router'
|
|
695
|
+
import { MenuItem } from 'cisse-vue-ui'
|
|
696
|
+
|
|
697
|
+
const route = useRoute()
|
|
698
|
+
|
|
699
|
+
const menuItem = {
|
|
700
|
+
label: 'Dashboard',
|
|
701
|
+
link: '/dashboard',
|
|
702
|
+
icon: 'lucide:layout-dashboard'
|
|
703
|
+
}
|
|
704
|
+
</script>
|
|
705
|
+
|
|
706
|
+
<template>
|
|
707
|
+
<!-- Auto-detect active state from current route -->
|
|
708
|
+
<MenuItem :menu-item="menuItem" :current-path="route.path" />
|
|
709
|
+
|
|
710
|
+
<!-- Or manually control active state -->
|
|
711
|
+
<MenuItem :menu-item="menuItem" :active="true" />
|
|
712
|
+
</template>
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### AuthLayout
|
|
716
|
+
|
|
717
|
+
Split-panel layout for authentication pages (login, register, password reset).
|
|
718
|
+
|
|
719
|
+
```vue
|
|
720
|
+
<script setup>
|
|
721
|
+
import { ref } from 'vue'
|
|
722
|
+
import { AuthLayout, type AuthFeature } from 'cisse-vue-ui'
|
|
723
|
+
|
|
724
|
+
const email = ref('')
|
|
725
|
+
const password = ref('')
|
|
726
|
+
|
|
727
|
+
const features: AuthFeature[] = [
|
|
728
|
+
{ icon: 'lucide:shield', text: 'Secure authentication' },
|
|
729
|
+
{ icon: 'lucide:zap', text: 'Fast login' },
|
|
730
|
+
{ icon: 'lucide:users', text: 'Team collaboration' },
|
|
731
|
+
]
|
|
732
|
+
|
|
733
|
+
function handleSubmit() {
|
|
734
|
+
// Handle login
|
|
735
|
+
}
|
|
736
|
+
</script>
|
|
737
|
+
|
|
738
|
+
<template>
|
|
739
|
+
<AuthLayout
|
|
740
|
+
app-name="My App"
|
|
741
|
+
app-icon="lucide:box"
|
|
742
|
+
headline="Welcome to"
|
|
743
|
+
sub-headline="My Application"
|
|
744
|
+
description="Sign in to access your account."
|
|
745
|
+
:features="features"
|
|
746
|
+
form-title="Sign In"
|
|
747
|
+
form-subtitle="Enter your credentials"
|
|
748
|
+
home-link="/"
|
|
749
|
+
>
|
|
750
|
+
<form @submit.prevent="handleSubmit" class="space-y-4">
|
|
751
|
+
<input v-model="email" type="email" placeholder="Email" class="..." />
|
|
752
|
+
<input v-model="password" type="password" placeholder="Password" class="..." />
|
|
753
|
+
<button type="submit">Sign In</button>
|
|
754
|
+
</form>
|
|
755
|
+
|
|
756
|
+
<template #form-footer>
|
|
757
|
+
<p class="text-center mt-6">
|
|
758
|
+
Don't have an account? <a href="/register">Sign up</a>
|
|
759
|
+
</p>
|
|
760
|
+
</template>
|
|
761
|
+
</AuthLayout>
|
|
762
|
+
</template>
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
#### AuthLayout Props
|
|
766
|
+
|
|
767
|
+
| Prop | Type | Default | Description |
|
|
768
|
+
|------|------|---------|-------------|
|
|
769
|
+
| `appName` | `string` | `''` | Application name displayed in logo |
|
|
770
|
+
| `appIcon` | `string` | `'lucide:box'` | Iconify icon for app logo |
|
|
771
|
+
| `headline` | `string` | `''` | First line of headline |
|
|
772
|
+
| `subHeadline` | `string` | `''` | Second line with decorative underline |
|
|
773
|
+
| `description` | `string` | `''` | Description paragraph |
|
|
774
|
+
| `features` | `AuthFeature[]` | `[]` | List of features with icon and text |
|
|
775
|
+
| `gradientFrom` | `string` | `'from-primary-700'` | Tailwind gradient start class |
|
|
776
|
+
| `gradientVia` | `string` | `''` | Tailwind gradient middle class |
|
|
777
|
+
| `gradientTo` | `string` | `'to-primary-800'` | Tailwind gradient end class |
|
|
778
|
+
| `showDecorations` | `boolean` | `true` | Show floating decorative shapes |
|
|
779
|
+
| `showPattern` | `boolean` | `true` | Show dot pattern overlay |
|
|
780
|
+
| `underlineColor` | `string` | `'rgba(165, 180, 252, 0.5)'` | Sub-headline underline color |
|
|
781
|
+
| `formTitle` | `string` | `''` | Form panel title |
|
|
782
|
+
| `formSubtitle` | `string` | `''` | Form panel subtitle |
|
|
783
|
+
| `homeLink` | `string` | `'/'` | Mobile logo link URL |
|
|
784
|
+
| `brandingAnimation` | `string` | `''` | CSS class for branding animation |
|
|
785
|
+
| `formAnimation` | `string` | `''` | CSS class for form panel animation |
|
|
786
|
+
|
|
787
|
+
#### AuthLayout Slots
|
|
788
|
+
|
|
789
|
+
| Slot | Description |
|
|
790
|
+
|------|-------------|
|
|
791
|
+
| `default` | Form content (inside the white card) |
|
|
792
|
+
| `branding-panel` | Complete override of the branding panel |
|
|
793
|
+
| `branding-logo` | Custom logo in branding panel |
|
|
794
|
+
| `branding-headline` | Custom headline content |
|
|
795
|
+
| `branding-features` | Custom features list |
|
|
796
|
+
| `branding-content` | Additional content after features |
|
|
797
|
+
| `mobile-logo` | Custom mobile logo |
|
|
798
|
+
| `form-header` | Content above the form card |
|
|
799
|
+
| `form-footer` | Content below the form card |
|
|
800
|
+
|
|
801
|
+
### BaseLayout
|
|
802
|
+
|
|
803
|
+
```vue
|
|
804
|
+
<script setup>
|
|
805
|
+
import { useRoute } from 'vue-router'
|
|
806
|
+
import { BaseLayout } from 'cisse-vue-ui'
|
|
807
|
+
|
|
808
|
+
const route = useRoute()
|
|
809
|
+
|
|
810
|
+
const menuItems = [
|
|
811
|
+
{ label: 'Dashboard', link: '/', icon: 'lucide:home' },
|
|
812
|
+
{ label: 'Users', link: '/users', icon: 'lucide:users' },
|
|
813
|
+
{ label: 'Settings', link: '/settings', icon: 'lucide:settings' }
|
|
814
|
+
]
|
|
815
|
+
</script>
|
|
816
|
+
|
|
817
|
+
<template>
|
|
818
|
+
<BaseLayout
|
|
819
|
+
:menu-items="menuItems"
|
|
820
|
+
:current-path="route.path"
|
|
821
|
+
:show-dark-toggle="true"
|
|
822
|
+
menu-position="top"
|
|
823
|
+
>
|
|
824
|
+
<template #logo>
|
|
825
|
+
<img src="/logo.svg" alt="Logo" class="h-8" />
|
|
826
|
+
</template>
|
|
827
|
+
|
|
828
|
+
<RouterView />
|
|
829
|
+
</BaseLayout>
|
|
830
|
+
</template>
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
#### BaseLayout Props
|
|
834
|
+
|
|
835
|
+
| Prop | Type | Default | Description |
|
|
836
|
+
|------|------|---------|-------------|
|
|
837
|
+
| `menuItems` | `MenuItemProps[]` | `[]` | Menu items for the sidebar |
|
|
838
|
+
| `appName` | `string` | `'App'` | App/brand name displayed in sidebar |
|
|
839
|
+
| `appIcon` | `string` | `'lucide:box'` | App icon (Iconify icon name) |
|
|
840
|
+
| `sidebarOpen` | `boolean` | `true` | Whether sidebar is open (v-model:sidebarOpen) |
|
|
841
|
+
| `dark` | `boolean` | `false` | Whether dark mode is enabled (v-model:dark) |
|
|
842
|
+
| `showDarkToggle` | `boolean` | `true` | Show dark mode toggle in header |
|
|
843
|
+
| `sidebarClass` | `string` | `'bg-[#172b4c]...'` | CSS classes for sidebar background |
|
|
844
|
+
| `currentPath` | `string` | - | Current route path for menu active state |
|
|
845
|
+
| `userName` | `string` | - | User display name |
|
|
846
|
+
| `userAvatar` | `string` | - | User avatar (initials or image URL) |
|
|
847
|
+
| `userMenuItems` | `UserMenuItem[]` | `[]` | User menu dropdown items |
|
|
848
|
+
| `menuPosition` | `'top' \| 'center' \| 'bottom'` | `'top'` | Menu vertical position in sidebar |
|
|
849
|
+
|
|
850
|
+
#### BaseLayout Slots
|
|
851
|
+
|
|
852
|
+
| Slot | Description |
|
|
853
|
+
|------|-------------|
|
|
854
|
+
| `default` | Main content area (or renders RouterView if available) |
|
|
855
|
+
| `logo` | Custom logo in sidebar header |
|
|
856
|
+
| `menu` | Custom menu content (receives currentPath) |
|
|
857
|
+
| `sidebar-footer` | Content at bottom of sidebar |
|
|
858
|
+
| `header-center` | Center content in header |
|
|
859
|
+
| `header-actions` | Action buttons in header (before dark toggle) |
|
|
860
|
+
|
|
861
|
+
## Dark Mode
|
|
862
|
+
|
|
863
|
+
Components support dark mode via the `.dark` class on a parent element:
|
|
864
|
+
|
|
865
|
+
```html
|
|
866
|
+
<html class="dark">
|
|
867
|
+
<!-- Components will use dark theme -->
|
|
868
|
+
</html>
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
Use the `useDarkMode` composable or implement your own toggle:
|
|
872
|
+
|
|
873
|
+
```typescript
|
|
874
|
+
const { isDark, toggle } = useDarkMode()
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
## License
|
|
878
|
+
|
|
879
|
+
MIT
|