nicklabs-ui 1.0.4 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,1604 @@
1
+ # nicklabs-ui
2
+
3
+ A Vue 3 component library with glassmorphism design, built for modern web applications.
4
+
5
+ **Version**: 1.0.6 | **Framework**: Vue 3.5+
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Setup](#setup)
13
+ - [Components](#components)
14
+ - [Form Components](#form-components)
15
+ - [NButton](#nbutton)
16
+ - [NInput](#ninput)
17
+ - [NTextarea](#ntextarea)
18
+ - [NCheckbox](#ncheckbox)
19
+ - [NSelect](#nselect)
20
+ - [NFileSelect](#nfileselect)
21
+ - [NSwitch](#nswitch)
22
+ - [Data Display](#data-display)
23
+ - [NTable](#ntable)
24
+ - [NList](#nlist)
25
+ - [NTag](#ntag)
26
+ - [NEmpty](#nempty)
27
+ - [NCode](#ncode)
28
+ - [Modals & Overlays](#modals--overlays)
29
+ - [NModal](#nmodal)
30
+ - [NAlert](#nalert)
31
+ - [NToast](#ntoast)
32
+ - [NTooltip](#ntooltip)
33
+ - [NLoading](#nloading)
34
+ - [Layout](#layout)
35
+ - [NLayout](#nlayout)
36
+ - [NNavigation](#nnavigation)
37
+ - [NSidebar](#nsidebar)
38
+ - [NCard](#ncard)
39
+ - [NForm](#nform)
40
+ - [NLoginLayout](#nloginlayout)
41
+ - [NBreadcrumb](#nbreadcrumb)
42
+ - [NPaginate](#npaginate)
43
+ - [NHeroSection](#nherosection)
44
+ - [NSideFilter](#nsidefilter)
45
+ - [Composables](#composables)
46
+ - [useToast](#usetoast)
47
+ - [useAlert](#usealert)
48
+ - [useDisclosure](#usedisclosure)
49
+ - [useSidebarManager](#usesidebarmanager)
50
+ - [Type Reference](#type-reference)
51
+ - [CSS Variables](#css-variables)
52
+
53
+ ---
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ npm install nicklabs-ui
59
+ # or
60
+ pnpm add nicklabs-ui
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Setup
66
+
67
+ ### 1. Import Styles
68
+
69
+ In your main entry file (e.g., `main.ts`):
70
+
71
+ ```typescript
72
+ import { createApp } from 'vue'
73
+ import App from './App.vue'
74
+
75
+ // Required: reset and CSS variables
76
+ import 'nicklabs-ui/reset.css'
77
+ import 'nicklabs-ui/variables.css'
78
+
79
+ // Required: component styles
80
+ import 'nicklabs-ui/nicklabs-ui.css'
81
+
82
+ createApp(App).mount('#app')
83
+ ```
84
+
85
+ ### 2. Import Components
86
+
87
+ Import components individually as needed:
88
+
89
+ ```typescript
90
+ import { NButton, NInput, NModal, useToast } from 'nicklabs-ui'
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Components
96
+
97
+ ---
98
+
99
+ ### Form Components
100
+
101
+ ---
102
+
103
+ #### NButton
104
+
105
+ A versatile button supporting multiple visual variants and semantic intents.
106
+
107
+ **Props**
108
+
109
+ | Prop | Type | Default | Description |
110
+ |------|------|---------|-------------|
111
+ | `variant` | `"none" \| "solid" \| "outline" \| "ghost" \| "mute"` | `"solid"` | Visual style |
112
+ | `intent` | `"none" \| "primary" \| "error" \| "success" \| "warning" \| "info"` | `"none"` | Semantic color intent |
113
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Button size |
114
+ | `radiusSize` | `"sm" \| "md" \| "lg" \| "xl" \| "full"` | `"md"` | Border radius |
115
+ | `disabled` | `boolean` | `false` | Disable interaction |
116
+ | `type` | `"button" \| "submit" \| "reset"` | `"button"` | HTML button type |
117
+ | `square` | `boolean` | `false` | Equal width/height (icon button) |
118
+ | `padding` | `string` | — | Custom padding override |
119
+ | `width` | `string` | — | Custom width override |
120
+ | `height` | `string` | — | Custom height override |
121
+
122
+ **Usage**
123
+
124
+ ```vue
125
+ <template>
126
+ <!-- Basic -->
127
+ <NButton>Click me</NButton>
128
+
129
+ <!-- Variants -->
130
+ <NButton variant="solid" intent="primary">Primary</NButton>
131
+ <NButton variant="outline" intent="success">Success</NButton>
132
+ <NButton variant="ghost" intent="warning">Warning</NButton>
133
+ <NButton variant="mute" intent="error">Danger</NButton>
134
+
135
+ <!-- Sizes -->
136
+ <NButton size="sm">Small</NButton>
137
+ <NButton size="md">Medium</NButton>
138
+ <NButton size="lg">Large</NButton>
139
+
140
+ <!-- Submit button -->
141
+ <NButton type="submit" intent="primary">Submit</NButton>
142
+
143
+ <!-- Disabled -->
144
+ <NButton disabled>Disabled</NButton>
145
+
146
+ <!-- Icon button (square) -->
147
+ <NButton square size="sm">
148
+ <svg>...</svg>
149
+ </NButton>
150
+ </template>
151
+
152
+ <script setup>
153
+ import { NButton } from 'nicklabs-ui'
154
+ </script>
155
+ ```
156
+
157
+ ---
158
+
159
+ #### NInput
160
+
161
+ A full-featured text input with support for password visibility, number controls, and clearing.
162
+
163
+ **Props**
164
+
165
+ | Prop | Type | Default | Description |
166
+ |------|------|---------|-------------|
167
+ | `modelValue` | `string \| number` | — | Bound value (v-model) |
168
+ | `type` | `string` | `"text"` | HTML input type |
169
+ | `placeholder` | `string` | — | Placeholder text |
170
+ | `disabled` | `boolean` | `false` | Disable input |
171
+ | `readonly` | `boolean` | `false` | Read-only mode |
172
+ | `clearable` | `boolean` | `false` | Show clear button |
173
+ | `maxlength` | `number` | — | Maximum character length |
174
+ | `min` | `number` | — | Minimum value (for type="number") |
175
+ | `max` | `number` | — | Maximum value (for type="number") |
176
+ | `title` | `string` | — | Label above the input |
177
+ | `autocomplete` | `string` | — | HTML autocomplete attribute |
178
+
179
+ **Events**
180
+
181
+ | Event | Payload | Description |
182
+ |-------|---------|-------------|
183
+ | `update:modelValue` | `string \| number` | Value changed |
184
+ | `focus` | `FocusEvent` | Input focused |
185
+ | `blur` | `FocusEvent` | Input blurred |
186
+ | `input` | `Event` | Input event |
187
+ | `change` | `Event` | Change event |
188
+ | `keydown` | `KeyboardEvent` | Key pressed |
189
+ | `clear` | — | Clear button clicked |
190
+
191
+ **Usage**
192
+
193
+ ```vue
194
+ <template>
195
+ <NInput v-model="username" placeholder="Enter username" title="Username" />
196
+
197
+ <!-- Password with visibility toggle -->
198
+ <NInput v-model="password" type="password" placeholder="Enter password" />
199
+
200
+ <!-- Clearable -->
201
+ <NInput v-model="search" placeholder="Search..." clearable />
202
+
203
+ <!-- Number with min/max -->
204
+ <NInput v-model="age" type="number" :min="0" :max="120" title="Age" />
205
+ </template>
206
+
207
+ <script setup>
208
+ import { ref } from 'vue'
209
+ import { NInput } from 'nicklabs-ui'
210
+
211
+ const username = ref('')
212
+ const password = ref('')
213
+ const search = ref('')
214
+ const age = ref(0)
215
+ </script>
216
+ ```
217
+
218
+ ---
219
+
220
+ #### NTextarea
221
+
222
+ Multi-line text input with optional character count display.
223
+
224
+ **Props**
225
+
226
+ | Prop | Type | Default | Description |
227
+ |------|------|---------|-------------|
228
+ | `modelValue` | `string` | — | Bound value (v-model) |
229
+ | `placeholder` | `string` | — | Placeholder text |
230
+ | `disabled` | `boolean` | `false` | Disable textarea |
231
+ | `readonly` | `boolean` | `false` | Read-only mode |
232
+ | `rows` | `number` | `4` | Visible row count |
233
+ | `maxLength` | `number` | — | Maximum character count |
234
+ | `title` | `string` | — | Label above the textarea |
235
+ | `showCount` | `boolean` | `false` | Show character counter |
236
+ | `wrap` | `"soft" \| "off"` | `"soft"` | Text wrapping behavior |
237
+ | `autofocus` | `boolean` | `false` | Auto-focus on mount |
238
+ | `autocomplete` | `string` | — | HTML autocomplete attribute |
239
+
240
+ **Events**
241
+
242
+ | Event | Payload | Description |
243
+ |-------|---------|-------------|
244
+ | `update:modelValue` | `string` | Value changed |
245
+ | `focus` | `FocusEvent` | Textarea focused |
246
+ | `blur` | `FocusEvent` | Textarea blurred |
247
+ | `input` | `Event` | Input event |
248
+ | `change` | `Event` | Change event |
249
+ | `keydown` | `KeyboardEvent` | Key pressed |
250
+ | `paste` | `ClipboardEvent` | Content pasted |
251
+
252
+ **Usage**
253
+
254
+ ```vue
255
+ <template>
256
+ <NTextarea
257
+ v-model="description"
258
+ title="Description"
259
+ placeholder="Enter description..."
260
+ :rows="5"
261
+ :maxLength="500"
262
+ showCount
263
+ />
264
+ </template>
265
+
266
+ <script setup>
267
+ import { ref } from 'vue'
268
+ import { NTextarea } from 'nicklabs-ui'
269
+
270
+ const description = ref('')
271
+ </script>
272
+ ```
273
+
274
+ ---
275
+
276
+ #### NCheckbox
277
+
278
+ Checkbox input supporting both single and multi-option modes.
279
+
280
+ **Props**
281
+
282
+ | Prop | Type | Default | Description |
283
+ |------|------|---------|-------------|
284
+ | `modelValue` | `boolean \| any[]` | — | Bound value (v-model) |
285
+ | `multiple` | `boolean` | `false` | Enable multi-option mode |
286
+ | `options` | `OptionItem[]` | — | Options for multi mode |
287
+ | `disabled` | `boolean` | `false` | Disable input |
288
+ | `title` | `string` | — | Label |
289
+ | `direction` | `"row" \| "column"` | `"row"` | Layout direction for options |
290
+ | `autofocus` | `boolean` | `false` | Auto-focus on mount |
291
+ | `ariaLabel` | `string` | — | Accessibility label |
292
+
293
+ **Events**
294
+
295
+ | Event | Description |
296
+ |-------|-------------|
297
+ | `update:modelValue` | Value changed |
298
+ | `change` | Generic change event |
299
+ | `change:item` | Selected OptionItem changed |
300
+ | `change:value` | Selected value changed |
301
+ | `change:values` | Selected values array changed (multiple mode) |
302
+
303
+ **Usage**
304
+
305
+ ```vue
306
+ <template>
307
+ <!-- Single checkbox -->
308
+ <NCheckbox v-model="agreed" title="I agree to terms" />
309
+
310
+ <!-- Multiple checkboxes -->
311
+ <NCheckbox
312
+ v-model="selected"
313
+ multiple
314
+ :options="options"
315
+ direction="column"
316
+ title="Select interests"
317
+ />
318
+ </template>
319
+
320
+ <script setup>
321
+ import { ref } from 'vue'
322
+ import { NCheckbox } from 'nicklabs-ui'
323
+
324
+ const agreed = ref(false)
325
+ const selected = ref([])
326
+ const options = [
327
+ { label: 'Vue', value: 'vue' },
328
+ { label: 'React', value: 'react' },
329
+ { label: 'Angular', value: 'angular' },
330
+ ]
331
+ </script>
332
+ ```
333
+
334
+ ---
335
+
336
+ #### NSelect
337
+
338
+ Dropdown select with search, multi-select, and clear support.
339
+
340
+ **Props**
341
+
342
+ | Prop | Type | Default | Description |
343
+ |------|------|---------|-------------|
344
+ | `modelValue` | `any \| any[]` | — | Bound value (v-model) |
345
+ | `options` | `OptionItem[]` | `[]` | Dropdown options |
346
+ | `multiple` | `boolean` | `false` | Allow multiple selection |
347
+ | `searchable` | `boolean` | `false` | Enable search/filter |
348
+ | `clearable` | `boolean` | `false` | Show clear button |
349
+ | `placeholder` | `string` | — | Placeholder text |
350
+ | `disabled` | `boolean` | `false` | Disable input |
351
+ | `title` | `string` | — | Label above dropdown |
352
+ | `multipleDisplay` | `"count" \| "tags"` | `"tags"` | How selected items display |
353
+
354
+ **Events**
355
+
356
+ | Event | Description |
357
+ |-------|-------------|
358
+ | `update:modelValue` | Value changed |
359
+ | `change` | Generic change event |
360
+ | `change:item` | Selected OptionItem |
361
+ | `change:value` | Selected value |
362
+ | `change:values` | Selected values array (multiple mode) |
363
+
364
+ **Usage**
365
+
366
+ ```vue
367
+ <template>
368
+ <!-- Basic select -->
369
+ <NSelect v-model="city" :options="cities" placeholder="Select city" title="City" />
370
+
371
+ <!-- Searchable + clearable -->
372
+ <NSelect v-model="country" :options="countries" searchable clearable />
373
+
374
+ <!-- Multiple selection -->
375
+ <NSelect
376
+ v-model="tags"
377
+ :options="tagOptions"
378
+ multiple
379
+ multipleDisplay="tags"
380
+ title="Tags"
381
+ />
382
+ </template>
383
+
384
+ <script setup>
385
+ import { ref } from 'vue'
386
+ import { NSelect } from 'nicklabs-ui'
387
+
388
+ const city = ref('')
389
+ const country = ref(null)
390
+ const tags = ref([])
391
+
392
+ const cities = [
393
+ { label: 'Taipei', value: 'taipei' },
394
+ { label: 'Tokyo', value: 'tokyo' },
395
+ { label: 'Seoul', value: 'seoul' },
396
+ ]
397
+ const countries = [
398
+ { label: 'Taiwan', value: 'TW' },
399
+ { label: 'Japan', value: 'JP' },
400
+ ]
401
+ const tagOptions = [
402
+ { label: 'Frontend', value: 'frontend' },
403
+ { label: 'Backend', value: 'backend' },
404
+ { label: 'DevOps', value: 'devops' },
405
+ ]
406
+ </script>
407
+ ```
408
+
409
+ ---
410
+
411
+ #### NFileSelect
412
+
413
+ Drag-and-drop file selector.
414
+
415
+ **Props**
416
+
417
+ | Prop | Type | Default | Description |
418
+ |------|------|---------|-------------|
419
+ | `multiple` | `boolean` | `false` | Allow multiple file selection |
420
+ | `disabled` | `boolean` | `false` | Disable input |
421
+ | `label` | `string` | — | Button/drop zone label |
422
+ | `hint` | `string` | — | Helper text below |
423
+ | `accept` | `string` | — | Accepted MIME types (e.g. `"image/*"`) |
424
+ | `title` | `string` | — | Label above component |
425
+
426
+ **Events**
427
+
428
+ | Event | Payload | Description |
429
+ |-------|---------|-------------|
430
+ | `change:file` | `File` | Single file selected |
431
+ | `change:files` | `File[]` | Files selected (multiple mode) |
432
+
433
+ **Usage**
434
+
435
+ ```vue
436
+ <template>
437
+ <NFileSelect
438
+ title="Upload Avatar"
439
+ label="Drop image here or click to browse"
440
+ hint="Accepts PNG, JPG up to 2MB"
441
+ accept="image/*"
442
+ @change:file="handleFile"
443
+ />
444
+
445
+ <!-- Multiple files -->
446
+ <NFileSelect multiple accept=".pdf,.doc" @change:files="handleFiles" />
447
+ </template>
448
+
449
+ <script setup>
450
+ import { NFileSelect } from 'nicklabs-ui'
451
+
452
+ function handleFile(file: File) {
453
+ console.log('Selected:', file.name)
454
+ }
455
+
456
+ function handleFiles(files: File[]) {
457
+ console.log('Selected:', files.length, 'files')
458
+ }
459
+ </script>
460
+ ```
461
+
462
+ ---
463
+
464
+ #### NSwitch
465
+
466
+ Animated toggle switch.
467
+
468
+ **Props**
469
+
470
+ | Prop | Type | Default | Description |
471
+ |------|------|---------|-------------|
472
+ | `modelValue` | `boolean` | `false` | Bound value (v-model) |
473
+ | `disabled` | `boolean` | `false` | Disable switch |
474
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Switch size |
475
+ | `label` | `string` | — | Label text |
476
+
477
+ **Events**
478
+
479
+ | Event | Payload | Description |
480
+ |-------|---------|-------------|
481
+ | `update:modelValue` | `boolean` | Value changed |
482
+ | `change` | `boolean` | Value changed |
483
+
484
+ **Usage**
485
+
486
+ ```vue
487
+ <template>
488
+ <NSwitch v-model="enabled" label="Enable notifications" />
489
+ <NSwitch v-model="darkMode" size="lg" label="Dark mode" />
490
+ </template>
491
+
492
+ <script setup>
493
+ import { ref } from 'vue'
494
+ import { NSwitch } from 'nicklabs-ui'
495
+
496
+ const enabled = ref(false)
497
+ const darkMode = ref(false)
498
+ </script>
499
+ ```
500
+
501
+ ---
502
+
503
+ ### Data Display
504
+
505
+ ---
506
+
507
+ #### NTable
508
+
509
+ A type-safe, sortable data table with support for custom formatters and action slots.
510
+
511
+ **Props**
512
+
513
+ | Prop | Type | Default | Description |
514
+ |------|------|---------|-------------|
515
+ | `columns` | `NTableColumn<T>[]` | `[]` | Column definitions |
516
+ | `rows` | `T[]` | `[]` | Row data |
517
+ | `striped` | `boolean` | `false` | Alternating row colors |
518
+ | `bordered` | `boolean` | `false` | Show borders |
519
+ | `hoverable` | `boolean` | `true` | Highlight rows on hover |
520
+ | `loading` | `boolean` | `false` | Show loading state |
521
+ | `emptyText` | `string` | — | Message when no data |
522
+ | `rowKey` | `string` | `"id"` | Unique key field |
523
+
524
+ **Events**
525
+
526
+ | Event | Payload | Description |
527
+ |-------|---------|-------------|
528
+ | `sort` | `NTableSortState` | Column sort changed |
529
+ | `row-click` | `T` | Row was clicked |
530
+
531
+ **Slots**
532
+
533
+ | Slot | Description |
534
+ |------|-------------|
535
+ | `action` | Custom action buttons per row (scoped: `{ row }`) |
536
+
537
+ **NTableColumn Interface**
538
+
539
+ ```typescript
540
+ interface NTableColumn<T = any> {
541
+ key: string // Data field key
542
+ label: string // Column header text
543
+ width?: string // CSS width (e.g. "120px")
544
+ align?: "left" | "center" | "right"
545
+ sortable?: boolean // Enable column sorting
546
+ formatter?: (value: any, row: T) => string // Custom cell renderer
547
+ }
548
+ ```
549
+
550
+ **Usage**
551
+
552
+ ```vue
553
+ <template>
554
+ <NTable
555
+ :columns="columns"
556
+ :rows="users"
557
+ striped
558
+ hoverable
559
+ @sort="handleSort"
560
+ @row-click="handleRowClick"
561
+ >
562
+ <template #action="{ row }">
563
+ <NButton size="sm" variant="ghost" intent="primary" @click="edit(row)">Edit</NButton>
564
+ <NButton size="sm" variant="ghost" intent="error" @click="remove(row)">Delete</NButton>
565
+ </template>
566
+ </NTable>
567
+ </template>
568
+
569
+ <script setup lang="ts">
570
+ import { ref } from 'vue'
571
+ import { NTable, NButton } from 'nicklabs-ui'
572
+ import type { NTableColumn, NTableSortState } from 'nicklabs-ui'
573
+
574
+ interface User {
575
+ id: number
576
+ name: string
577
+ email: string
578
+ role: string
579
+ createdAt: string
580
+ }
581
+
582
+ const columns: NTableColumn<User>[] = [
583
+ { key: 'name', label: 'Name', sortable: true },
584
+ { key: 'email', label: 'Email' },
585
+ { key: 'role', label: 'Role', align: 'center' },
586
+ {
587
+ key: 'createdAt',
588
+ label: 'Created',
589
+ formatter: (value) => new Date(value).toLocaleDateString(),
590
+ },
591
+ ]
592
+
593
+ const users = ref<User[]>([
594
+ { id: 1, name: 'Alice', email: 'alice@example.com', role: 'Admin', createdAt: '2024-01-01' },
595
+ { id: 2, name: 'Bob', email: 'bob@example.com', role: 'User', createdAt: '2024-02-15' },
596
+ ])
597
+
598
+ function handleSort(state: NTableSortState) {
599
+ console.log('Sort by:', state.key, state.order)
600
+ }
601
+
602
+ function handleRowClick(row: User) {
603
+ console.log('Clicked:', row)
604
+ }
605
+ </script>
606
+ ```
607
+
608
+ ---
609
+
610
+ #### NList
611
+
612
+ A full-featured data management component combining table, pagination, filtering, and CRUD operations.
613
+
614
+ **Props**
615
+
616
+ | Prop | Type | Default | Description |
617
+ |------|------|---------|-------------|
618
+ | `items` | `T[]` | `[]` | Data items |
619
+ | `columns` | `NTableColumn<T>[]` | `[]` | Column definitions |
620
+ | `pageSize` | `number` | `10` | Items per page |
621
+ | `maxPageButtons` | `number` | `5` | Visible page buttons |
622
+ | `filterable` | `boolean` | `false` | Show filter panel |
623
+ | `rowEditable` | `boolean` | `false` | Show row edit button |
624
+ | `rowDeletable` | `boolean` | `false` | Show row delete button |
625
+ | `creatable` | `boolean` | `false` | Show create button |
626
+ | `batchDeletable` | `boolean` | `false` | Enable batch delete |
627
+ | `refreshable` | `boolean` | `false` | Show refresh button |
628
+
629
+ **Events**
630
+
631
+ | Event | Payload | Description |
632
+ |-------|---------|-------------|
633
+ | `onRowEdit` | `T` | Row edit clicked |
634
+ | `onRowDelete` | `T` | Row delete clicked |
635
+ | `onPageChange` | `number` | Page changed |
636
+ | `onCreate` | — | Create clicked |
637
+ | `onBatchDelete` | `T[]` | Batch delete initiated |
638
+ | `onRefresh` | — | Refresh clicked |
639
+ | `onFilter` | — | Filter applied |
640
+ | `row-click` | `T` | Row clicked |
641
+ | `sort` | `NTableSortState` | Sort changed |
642
+
643
+ **Usage**
644
+
645
+ ```vue
646
+ <template>
647
+ <NList
648
+ :items="users"
649
+ :columns="columns"
650
+ :page-size="20"
651
+ filterable
652
+ row-editable
653
+ row-deletable
654
+ creatable
655
+ refreshable
656
+ @on-row-edit="handleEdit"
657
+ @on-row-delete="handleDelete"
658
+ @on-create="handleCreate"
659
+ @on-refresh="loadUsers"
660
+ />
661
+ </template>
662
+
663
+ <script setup lang="ts">
664
+ import { NList } from 'nicklabs-ui'
665
+
666
+ // ... same column/data setup as NTable
667
+ </script>
668
+ ```
669
+
670
+ ---
671
+
672
+ #### NTag
673
+
674
+ Semantic tag/badge component.
675
+
676
+ **Props**
677
+
678
+ | Prop | Type | Default | Description |
679
+ |------|------|---------|-------------|
680
+ | `intent` | `"none" \| "primary" \| "success" \| "warning" \| "error" \| "info"` | `"none"` | Color intent |
681
+ | `variant` | `"solid" \| "light" \| "outline"` | `"light"` | Visual style |
682
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Tag size |
683
+ | `closable` | `boolean` | `false` | Show close button |
684
+ | `round` | `boolean` | `false` | Fully rounded (pill shape) |
685
+
686
+ **Events**
687
+
688
+ | Event | Description |
689
+ |-------|-------------|
690
+ | `close` | Close button clicked |
691
+
692
+ **Usage**
693
+
694
+ ```vue
695
+ <template>
696
+ <NTag intent="success">Active</NTag>
697
+ <NTag intent="warning" variant="outline">Pending</NTag>
698
+ <NTag intent="error" variant="solid" round>Blocked</NTag>
699
+ <NTag intent="info" closable @close="removeTag">Vue 3</NTag>
700
+ </template>
701
+
702
+ <script setup>
703
+ import { NTag } from 'nicklabs-ui'
704
+ </script>
705
+ ```
706
+
707
+ ---
708
+
709
+ #### NEmpty
710
+
711
+ Empty state placeholder component.
712
+
713
+ **Props**
714
+
715
+ | Prop | Type | Default | Description |
716
+ |------|------|---------|-------------|
717
+ | `title` | `string` | — | Main message |
718
+ | `description` | `string` | — | Sub-message |
719
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Component size |
720
+ | `icon` | `string` | — | Custom SVG string |
721
+
722
+ **Usage**
723
+
724
+ ```vue
725
+ <template>
726
+ <NEmpty
727
+ title="No results found"
728
+ description="Try adjusting your search filters"
729
+ size="lg"
730
+ />
731
+ </template>
732
+
733
+ <script setup>
734
+ import { NEmpty } from 'nicklabs-ui'
735
+ </script>
736
+ ```
737
+
738
+ ---
739
+
740
+ #### NCode
741
+
742
+ Syntax-highlighted code display with copy-to-clipboard.
743
+
744
+ **Props**
745
+
746
+ | Prop | Type | Default | Description |
747
+ |------|------|---------|-------------|
748
+ | `code` | `string` | — | Code content to display |
749
+ | `language` | `string` | — | Language for highlighting |
750
+ | `showLineNumbers` | `boolean` | `false` | Show line numbers |
751
+
752
+ **Usage**
753
+
754
+ ```vue
755
+ <template>
756
+ <NCode
757
+ :code="snippet"
758
+ language="typescript"
759
+ showLineNumbers
760
+ />
761
+ </template>
762
+
763
+ <script setup>
764
+ import { NCode } from 'nicklabs-ui'
765
+
766
+ const snippet = `const greeting = (name: string) => {
767
+ return \`Hello, \${name}!\`
768
+ }`
769
+ </script>
770
+ ```
771
+
772
+ ---
773
+
774
+ ### Modals & Overlays
775
+
776
+ ---
777
+
778
+ #### NModal
779
+
780
+ A teleported modal dialog.
781
+
782
+ **Props**
783
+
784
+ | Prop | Type | Default | Description |
785
+ |------|------|---------|-------------|
786
+ | `show` | `boolean` | `false` | Visibility (v-model:show) |
787
+ | `title` | `string` | — | Modal title |
788
+ | `width` | `string` | `"500px"` | Modal width |
789
+ | `closeOnClickOverlay` | `boolean` | `true` | Close when backdrop clicked |
790
+ | `showClose` | `boolean` | `true` | Show close button |
791
+ | `zIndex` | `number` | — | Custom z-index |
792
+
793
+ **Events**
794
+
795
+ | Event | Description |
796
+ |-------|-------------|
797
+ | `update:show` | Visibility changed |
798
+ | `close` | Modal closed |
799
+ | `open` | Modal opened |
800
+
801
+ **Slots**
802
+
803
+ | Slot | Description |
804
+ |------|-------------|
805
+ | `default` | Modal body content |
806
+ | `footer` | Footer actions area |
807
+
808
+ **Usage**
809
+
810
+ ```vue
811
+ <template>
812
+ <NButton @click="open">Open Modal</NButton>
813
+
814
+ <NModal v-model:show="isOpen" title="Confirm Action" width="400px">
815
+ <p>Are you sure you want to proceed?</p>
816
+
817
+ <template #footer>
818
+ <NButton variant="ghost" @click="isOpen = false">Cancel</NButton>
819
+ <NButton intent="primary" @click="confirm">Confirm</NButton>
820
+ </template>
821
+ </NModal>
822
+ </template>
823
+
824
+ <script setup>
825
+ import { ref } from 'vue'
826
+ import { NModal, NButton } from 'nicklabs-ui'
827
+
828
+ const isOpen = ref(false)
829
+
830
+ function open() { isOpen.value = true }
831
+ function confirm() {
832
+ // do something
833
+ isOpen.value = false
834
+ }
835
+ </script>
836
+ ```
837
+
838
+ ---
839
+
840
+ #### NAlert
841
+
842
+ Programmatic alert and confirm dialogs via the `useAlert` composable.
843
+
844
+ > `NAlert` must be mounted once at the app root level.
845
+
846
+ **Setup**
847
+
848
+ ```vue
849
+ <!-- App.vue -->
850
+ <template>
851
+ <RouterView />
852
+ <NAlert />
853
+ </template>
854
+
855
+ <script setup>
856
+ import { NAlert } from 'nicklabs-ui'
857
+ </script>
858
+ ```
859
+
860
+ **Usage via `useAlert`**
861
+
862
+ See [useAlert composable](#usealert) below.
863
+
864
+ ---
865
+
866
+ #### NToast
867
+
868
+ Notification toasts via the `useToast` composable.
869
+
870
+ > `NToast` must be mounted once at the app root level.
871
+
872
+ **Setup**
873
+
874
+ ```vue
875
+ <!-- App.vue -->
876
+ <template>
877
+ <RouterView />
878
+ <NToast />
879
+ </template>
880
+
881
+ <script setup>
882
+ import { NToast } from 'nicklabs-ui'
883
+ </script>
884
+ ```
885
+
886
+ **Usage via `useToast`**
887
+
888
+ See [useToast composable](#usetoast) below.
889
+
890
+ ---
891
+
892
+ #### NTooltip
893
+
894
+ Hover/focus tooltip with directional positioning.
895
+
896
+ **Props**
897
+
898
+ | Prop | Type | Default | Description |
899
+ |------|------|---------|-------------|
900
+ | `content` | `string` | — | Tooltip text |
901
+ | `position` | `"top" \| "right" \| "bottom" \| "left"` | `"top"` | Tooltip position |
902
+ | `disabled` | `boolean` | `false` | Disable tooltip |
903
+
904
+ **Usage**
905
+
906
+ ```vue
907
+ <template>
908
+ <NTooltip content="Delete this item" position="top">
909
+ <NButton variant="ghost" intent="error" square>
910
+ <svg>...</svg>
911
+ </NButton>
912
+ </NTooltip>
913
+ </template>
914
+
915
+ <script setup>
916
+ import { NTooltip, NButton } from 'nicklabs-ui'
917
+ </script>
918
+ ```
919
+
920
+ ---
921
+
922
+ #### NLoading
923
+
924
+ Loading indicator for inline use or full-page overlay.
925
+
926
+ **Props**
927
+
928
+ | Prop | Type | Default | Description |
929
+ |------|------|---------|-------------|
930
+ | `loading` | `boolean` | `false` | Show loading state |
931
+ | `title` | `string` | — | Loading message |
932
+ | `variant` | `"spinner" \| "dots"` | `"spinner"` | Animation style |
933
+ | `overlay` | `boolean` | `false` | Full-page overlay mode |
934
+
935
+ **Usage**
936
+
937
+ ```vue
938
+ <template>
939
+ <!-- Inline -->
940
+ <NLoading :loading="isFetching" title="Loading data..." />
941
+
942
+ <!-- Full-page overlay -->
943
+ <NLoading :loading="isSubmitting" overlay title="Saving..." variant="dots" />
944
+ </template>
945
+
946
+ <script setup>
947
+ import { ref } from 'vue'
948
+ import { NLoading } from 'nicklabs-ui'
949
+
950
+ const isFetching = ref(true)
951
+ const isSubmitting = ref(false)
952
+ </script>
953
+ ```
954
+
955
+ ---
956
+
957
+ ### Layout
958
+
959
+ ---
960
+
961
+ #### NLayout
962
+
963
+ Main application shell integrating sidebar and navigation.
964
+
965
+ **Props**
966
+
967
+ | Prop | Type | Default | Description |
968
+ |------|------|---------|-------------|
969
+ | `menus` | `Menu[]` | `[]` | Sidebar menu items |
970
+ | `isShowSidebar` | `boolean` | `true` | Show/hide sidebar |
971
+ | `copyright` | `string` | — | Footer copyright text |
972
+ | `currentPath` | `string` | — | Active route path |
973
+
974
+ **Events**
975
+
976
+ | Event | Description |
977
+ |-------|-------------|
978
+ | `logout` | Logout triggered |
979
+ | `navigate` | Menu item clicked |
980
+
981
+ **Menu Type**
982
+
983
+ ```typescript
984
+ type Menu = {
985
+ icon: string // SVG string
986
+ title: string
987
+ children?: {
988
+ icon: string
989
+ title: string
990
+ route: string
991
+ }[]
992
+ }
993
+ ```
994
+
995
+ **Usage**
996
+
997
+ ```vue
998
+ <template>
999
+ <NLayout
1000
+ :menus="menus"
1001
+ :current-path="$route.path"
1002
+ copyright="© 2025 MyApp"
1003
+ @logout="handleLogout"
1004
+ @navigate="handleNavigate"
1005
+ >
1006
+ <RouterView />
1007
+ </NLayout>
1008
+ </template>
1009
+
1010
+ <script setup>
1011
+ import { NLayout } from 'nicklabs-ui'
1012
+
1013
+ const menus = [
1014
+ {
1015
+ icon: '<svg>...</svg>',
1016
+ title: 'Dashboard',
1017
+ children: [
1018
+ { icon: '<svg>...</svg>', title: 'Overview', route: '/dashboard' },
1019
+ ],
1020
+ },
1021
+ ]
1022
+ </script>
1023
+ ```
1024
+
1025
+ ---
1026
+
1027
+ #### NNavigation
1028
+
1029
+ Top navigation bar with sidebar toggle, fullscreen, and user controls.
1030
+
1031
+ **Props**
1032
+
1033
+ | Prop | Type | Default | Description |
1034
+ |------|------|---------|-------------|
1035
+ | `isShowSidebar` | `boolean` | `true` | Show sidebar toggle button |
1036
+ | `isShowFullscreen` | `boolean` | `true` | Show fullscreen button |
1037
+ | `isShowUser` | `boolean` | `true` | Show user pill |
1038
+ | `isShowLogoutButton` | `boolean` | `true` | Show logout button |
1039
+
1040
+ **Events**
1041
+
1042
+ | Event | Description |
1043
+ |-------|-------------|
1044
+ | `toggleSidebar` | Sidebar toggle clicked |
1045
+ | `logout` | Logout clicked |
1046
+
1047
+ ---
1048
+
1049
+ #### NSidebar
1050
+
1051
+ Collapsible side navigation menu.
1052
+
1053
+ **Props**
1054
+
1055
+ | Prop | Type | Default | Description |
1056
+ |------|------|---------|-------------|
1057
+ | `isOpen` | `boolean` | `true` | Sidebar open state |
1058
+ | `menus` | `Menu[]` | `[]` | Menu items |
1059
+ | `currentPath` | `string` | — | Active route path |
1060
+
1061
+ **Events**
1062
+
1063
+ | Event | Description |
1064
+ |-------|-------------|
1065
+ | `update:isOpen` | Open state changed |
1066
+ | `logout` | Logout triggered |
1067
+ | `navigate` | Menu item clicked |
1068
+
1069
+ ---
1070
+
1071
+ #### NCard
1072
+
1073
+ Content card with glassmorphism styling.
1074
+
1075
+ **Props**
1076
+
1077
+ | Prop | Type | Default | Description |
1078
+ |------|------|---------|-------------|
1079
+ | `size` | `"none" \| "sm" \| "md" \| "lg"` | `"md"` | Padding size |
1080
+ | `radius` | `"none" \| "sm" \| "md" \| "lg" \| "xl"` | `"lg"` | Border radius |
1081
+
1082
+ **Usage**
1083
+
1084
+ ```vue
1085
+ <template>
1086
+ <NCard size="lg" radius="xl">
1087
+ <h2>Card Title</h2>
1088
+ <p>Card content goes here.</p>
1089
+ </NCard>
1090
+ </template>
1091
+
1092
+ <script setup>
1093
+ import { NCard } from 'nicklabs-ui'
1094
+ </script>
1095
+ ```
1096
+
1097
+ ---
1098
+
1099
+ #### NForm
1100
+
1101
+ Form wrapper with optional tab navigation and hero section header.
1102
+
1103
+ **Props**
1104
+
1105
+ | Prop | Type | Default | Description |
1106
+ |------|------|---------|-------------|
1107
+ | `model` | `object` | — | Form data object |
1108
+ | `disabled` | `boolean` | `false` | Disable all inputs |
1109
+ | `title` | `string` | — | Form header title |
1110
+ | `icon` | `string` | — | Header icon (SVG string) |
1111
+ | `tabs` | `string[]` | — | Tab labels for multi-section forms |
1112
+
1113
+ **Events**
1114
+
1115
+ | Event | Description |
1116
+ |-------|-------------|
1117
+ | `submit` | Form submitted |
1118
+ | `reset` | Form reset |
1119
+
1120
+ **Usage**
1121
+
1122
+ ```vue
1123
+ <template>
1124
+ <NForm
1125
+ :model="formData"
1126
+ title="User Profile"
1127
+ :tabs="['Basic Info', 'Security', 'Preferences']"
1128
+ @submit="handleSubmit"
1129
+ >
1130
+ <!-- Content renders based on active tab -->
1131
+ <NInput v-model="formData.name" title="Name" />
1132
+ </NForm>
1133
+ </template>
1134
+
1135
+ <script setup>
1136
+ import { reactive } from 'vue'
1137
+ import { NForm, NInput } from 'nicklabs-ui'
1138
+
1139
+ const formData = reactive({ name: '' })
1140
+
1141
+ function handleSubmit() {
1142
+ console.log('Submitted:', formData)
1143
+ }
1144
+ </script>
1145
+ ```
1146
+
1147
+ ---
1148
+
1149
+ #### NLoginLayout
1150
+
1151
+ Full-screen login page wrapper with animated card.
1152
+
1153
+ **Props**
1154
+
1155
+ | Prop | Type | Default | Description |
1156
+ |------|------|---------|-------------|
1157
+ | `backgroundImage` | `string` | — | Background image URL |
1158
+ | `logo` | `string` | — | Logo image URL or SVG |
1159
+ | `title` | `string` | — | Application name |
1160
+ | `description` | `string` | — | Subtitle/tagline |
1161
+
1162
+ **Usage**
1163
+
1164
+ ```vue
1165
+ <template>
1166
+ <NLoginLayout
1167
+ title="MyApp"
1168
+ description="Sign in to your account"
1169
+ background-image="/images/bg.jpg"
1170
+ logo="/images/logo.png"
1171
+ >
1172
+ <NInput v-model="email" type="email" placeholder="Email" />
1173
+ <NInput v-model="password" type="password" placeholder="Password" />
1174
+ <NButton type="submit" intent="primary" style="width: 100%">Sign In</NButton>
1175
+ </NLoginLayout>
1176
+ </template>
1177
+
1178
+ <script setup>
1179
+ import { ref } from 'vue'
1180
+ import { NLoginLayout, NInput, NButton } from 'nicklabs-ui'
1181
+
1182
+ const email = ref('')
1183
+ const password = ref('')
1184
+ </script>
1185
+ ```
1186
+
1187
+ ---
1188
+
1189
+ #### NBreadcrumb
1190
+
1191
+ Static breadcrumb navigation display.
1192
+
1193
+ **Usage**
1194
+
1195
+ ```vue
1196
+ <template>
1197
+ <NBreadcrumb />
1198
+ </template>
1199
+
1200
+ <script setup>
1201
+ import { NBreadcrumb } from 'nicklabs-ui'
1202
+ </script>
1203
+ ```
1204
+
1205
+ ---
1206
+
1207
+ #### NPaginate
1208
+
1209
+ Pagination control with smart ellipsis.
1210
+
1211
+ **Props**
1212
+
1213
+ | Prop | Type | Default | Description |
1214
+ |------|------|---------|-------------|
1215
+ | `totalItems` | `number` | `0` | Total number of items |
1216
+ | `pageSize` | `number` | `10` | Items per page |
1217
+ | `maxPageButtons` | `number` | `5` | Max visible page buttons |
1218
+
1219
+ **Events**
1220
+
1221
+ | Event | Payload | Description |
1222
+ |-------|---------|-------------|
1223
+ | `onPageChange` | `number` | New page number |
1224
+
1225
+ **Usage**
1226
+
1227
+ ```vue
1228
+ <template>
1229
+ <NPaginate
1230
+ :total-items="totalCount"
1231
+ :page-size="20"
1232
+ :max-page-buttons="7"
1233
+ @on-page-change="loadPage"
1234
+ />
1235
+ </template>
1236
+
1237
+ <script setup>
1238
+ import { ref } from 'vue'
1239
+ import { NPaginate } from 'nicklabs-ui'
1240
+
1241
+ const totalCount = ref(350)
1242
+
1243
+ function loadPage(page: number) {
1244
+ // fetch page data
1245
+ }
1246
+ </script>
1247
+ ```
1248
+
1249
+ ---
1250
+
1251
+ #### NHeroSection
1252
+
1253
+ Page header with icon, title, breadcrumb, and toolbar slot.
1254
+
1255
+ **Props**
1256
+
1257
+ | Prop | Type | Default | Description |
1258
+ |------|------|---------|-------------|
1259
+ | `title` | `string` | — | Section title |
1260
+ | `icon` | `string` | — | Icon (SVG string) |
1261
+
1262
+ **Slots**
1263
+
1264
+ | Slot | Description |
1265
+ |------|-------------|
1266
+ | `default` | Toolbar content (buttons, filters, etc.) |
1267
+
1268
+ **Usage**
1269
+
1270
+ ```vue
1271
+ <template>
1272
+ <NHeroSection title="User Management" :icon="userIcon">
1273
+ <NButton intent="primary" @click="create">New User</NButton>
1274
+ </NHeroSection>
1275
+ </template>
1276
+
1277
+ <script setup>
1278
+ import { NHeroSection, NButton } from 'nicklabs-ui'
1279
+
1280
+ const userIcon = '<svg>...</svg>'
1281
+ </script>
1282
+ ```
1283
+
1284
+ ---
1285
+
1286
+ #### NSideFilter
1287
+
1288
+ A slide-out side drawer for filter interfaces.
1289
+
1290
+ **Props**
1291
+
1292
+ | Prop | Type | Default | Description |
1293
+ |------|------|---------|-------------|
1294
+ | `open` | `boolean` | `false` | Open state (v-model:open) |
1295
+ | `title` | `string` | — | Drawer title |
1296
+
1297
+ **Events**
1298
+
1299
+ | Event | Description |
1300
+ |-------|-------------|
1301
+ | `update:open` | Open state changed |
1302
+ | `close` | Drawer closed |
1303
+
1304
+ **Usage**
1305
+
1306
+ ```vue
1307
+ <template>
1308
+ <NButton @click="filterOpen = true">Filters</NButton>
1309
+
1310
+ <NSideFilter v-model:open="filterOpen" title="Filter Results">
1311
+ <NSelect v-model="status" :options="statusOptions" title="Status" />
1312
+ <NInput v-model="search" placeholder="Search name..." title="Name" />
1313
+ </NSideFilter>
1314
+ </template>
1315
+
1316
+ <script setup>
1317
+ import { ref } from 'vue'
1318
+ import { NSideFilter, NButton, NSelect, NInput } from 'nicklabs-ui'
1319
+
1320
+ const filterOpen = ref(false)
1321
+ const status = ref(null)
1322
+ const search = ref('')
1323
+ </script>
1324
+ ```
1325
+
1326
+ ---
1327
+
1328
+ ## Composables
1329
+
1330
+ ---
1331
+
1332
+ ### useToast
1333
+
1334
+ Display notification toasts programmatically.
1335
+
1336
+ ```typescript
1337
+ import { useToast } from 'nicklabs-ui'
1338
+
1339
+ const { toasts, toast, removeToast } = useToast()
1340
+ ```
1341
+
1342
+ **Methods**
1343
+
1344
+ | Method | Signature | Description |
1345
+ |--------|-----------|-------------|
1346
+ | `toast` | `(message: string, options?: ToastOptions) => void` | Show a toast |
1347
+ | `toast.success` | `(message: string, options?) => void` | Success toast |
1348
+ | `toast.danger` | `(message: string, options?) => void` | Error toast |
1349
+ | `toast.warning` | `(message: string, options?) => void` | Warning toast |
1350
+ | `toast.info` | `(message: string, options?) => void` | Info toast |
1351
+ | `removeToast` | `(id: string) => void` | Remove a specific toast |
1352
+
1353
+ **ToastOptions**
1354
+
1355
+ ```typescript
1356
+ interface ToastOptions {
1357
+ title?: string // Optional heading
1358
+ duration?: number // Auto-dismiss ms (default: 4000)
1359
+ }
1360
+ ```
1361
+
1362
+ **Usage**
1363
+
1364
+ ```vue
1365
+ <script setup>
1366
+ import { useToast } from 'nicklabs-ui'
1367
+
1368
+ const { toast } = useToast()
1369
+
1370
+ function save() {
1371
+ toast.success('Saved successfully!', { title: 'Done' })
1372
+ }
1373
+
1374
+ function handleError() {
1375
+ toast.danger('Something went wrong.', { duration: 6000 })
1376
+ }
1377
+ </script>
1378
+ ```
1379
+
1380
+ ---
1381
+
1382
+ ### useAlert
1383
+
1384
+ Display programmatic alert and confirm dialogs.
1385
+
1386
+ ```typescript
1387
+ import { useAlert } from 'nicklabs-ui'
1388
+
1389
+ const { alert, confirm, clearAlerts } = useAlert()
1390
+ ```
1391
+
1392
+ **Methods**
1393
+
1394
+ | Method | Returns | Description |
1395
+ |--------|---------|-------------|
1396
+ | `alert(message, options?)` | `Promise<void>` | Show an alert dialog |
1397
+ | `alert.success(message, options?)` | `Promise<void>` | Success alert |
1398
+ | `alert.warning(message, options?)` | `Promise<void>` | Warning alert |
1399
+ | `alert.danger(message, options?)` | `Promise<void>` | Danger alert |
1400
+ | `alert.info(message, options?)` | `Promise<void>` | Info alert |
1401
+ | `confirm(message, options?)` | `Promise<boolean>` | Show a confirm dialog |
1402
+ | `clearAlerts()` | `void` | Dismiss all alerts |
1403
+
1404
+ **AlertOptions**
1405
+
1406
+ ```typescript
1407
+ interface AlertOptions {
1408
+ title?: string
1409
+ confirmText?: string
1410
+ cancelText?: string // confirm() only
1411
+ }
1412
+ ```
1413
+
1414
+ **Usage**
1415
+
1416
+ ```vue
1417
+ <script setup>
1418
+ import { useAlert } from 'nicklabs-ui'
1419
+
1420
+ const { alert, confirm } = useAlert()
1421
+
1422
+ async function deleteUser(id: number) {
1423
+ const confirmed = await confirm(
1424
+ 'This action cannot be undone.',
1425
+ { title: 'Delete user?', confirmText: 'Delete', cancelText: 'Cancel' }
1426
+ )
1427
+
1428
+ if (confirmed) {
1429
+ await api.deleteUser(id)
1430
+ await alert.success('User deleted.')
1431
+ }
1432
+ }
1433
+ </script>
1434
+ ```
1435
+
1436
+ ---
1437
+
1438
+ ### useDisclosure
1439
+
1440
+ Simple boolean state management for modals and drawers.
1441
+
1442
+ ```typescript
1443
+ import { useDisclosure } from 'nicklabs-ui'
1444
+
1445
+ // Array destructuring
1446
+ const [isOpen, open, close] = useDisclosure()
1447
+
1448
+ // Object destructuring
1449
+ const { isOpen, open, close } = useDisclosure()
1450
+ ```
1451
+
1452
+ **Usage**
1453
+
1454
+ ```vue
1455
+ <template>
1456
+ <NButton @click="open">Open Modal</NButton>
1457
+ <NModal v-model:show="isOpen" title="My Modal">
1458
+ <p>Content</p>
1459
+ <template #footer>
1460
+ <NButton @click="close">Close</NButton>
1461
+ </template>
1462
+ </NModal>
1463
+ </template>
1464
+
1465
+ <script setup>
1466
+ import { NModal, NButton, useDisclosure } from 'nicklabs-ui'
1467
+
1468
+ const [isOpen, open, close] = useDisclosure()
1469
+ </script>
1470
+ ```
1471
+
1472
+ ---
1473
+
1474
+ ### useSidebarManager
1475
+
1476
+ Manage sidebar open/close state and expandable menu items, persisted to localStorage.
1477
+
1478
+ ```typescript
1479
+ import { useSidebarManager } from 'nicklabs-ui'
1480
+
1481
+ const {
1482
+ isMenuExpanded,
1483
+ toggleMenu,
1484
+ isSidebarExpanded,
1485
+ toggleSidebar,
1486
+ } = useSidebarManager(sidebarId?)
1487
+ ```
1488
+
1489
+ **Parameters**
1490
+
1491
+ | Parameter | Type | Default | Description |
1492
+ |-----------|------|---------|-------------|
1493
+ | `sidebarId` | `string` | `"default"` | Unique ID for localStorage key |
1494
+
1495
+ **Returns**
1496
+
1497
+ | Property | Type | Description |
1498
+ |----------|------|-------------|
1499
+ | `isMenuExpanded` | `(menuTitle: string) => boolean` | Check if a menu group is expanded |
1500
+ | `toggleMenu` | `(menuTitle: string) => void` | Toggle a menu group |
1501
+ | `isSidebarExpanded` | `() => boolean` | Check if sidebar is open |
1502
+ | `toggleSidebar` | `() => void` | Toggle sidebar open/close |
1503
+
1504
+ ---
1505
+
1506
+ ## Type Reference
1507
+
1508
+ ```typescript
1509
+ // Common
1510
+ interface OptionItem {
1511
+ label: string
1512
+ value: any
1513
+ }
1514
+
1515
+ // Table
1516
+ interface NTableColumn<T = any> {
1517
+ key: string
1518
+ label: string
1519
+ width?: string
1520
+ align?: 'left' | 'center' | 'right'
1521
+ sortable?: boolean
1522
+ formatter?: (value: any, row: T) => string
1523
+ }
1524
+
1525
+ interface NTableSortState {
1526
+ key: string
1527
+ order: 'asc' | 'desc' | null
1528
+ }
1529
+
1530
+ // Layout
1531
+ type Menu = {
1532
+ icon: string
1533
+ title: string
1534
+ children?: { icon: string; title: string; route: string }[]
1535
+ }
1536
+
1537
+ // Sizes / Variants
1538
+ type NSize = 'sm' | 'md' | 'lg'
1539
+ type NButtonVariant = 'none' | 'solid' | 'outline' | 'ghost' | 'mute'
1540
+ type NButtonIntent = 'none' | 'primary' | 'error' | 'success' | 'warning' | 'info'
1541
+ type NTagIntent = 'none' | 'primary' | 'success' | 'warning' | 'error' | 'info'
1542
+ type NTagVariant = 'solid' | 'light' | 'outline'
1543
+ ```
1544
+
1545
+ ---
1546
+
1547
+ ## CSS Variables
1548
+
1549
+ The library reads these CSS custom properties from the host application. Override them to theme the components.
1550
+
1551
+ ```css
1552
+ :root {
1553
+ /* Colors */
1554
+ --primary-color: #4f6ef7;
1555
+ --primary-light: #7b93fa;
1556
+ --primary-dark: #3554e1;
1557
+ --success-color: #22c55e;
1558
+ --error-color: #ef4444;
1559
+ --warning-color: #f59e0b;
1560
+ --info-color: #3b82f6;
1561
+
1562
+ /* Text */
1563
+ --text-main: #1e293b;
1564
+ --text-secondary: #475569;
1565
+ --text-muted: #94a3b8;
1566
+ --text-inverse: #ffffff;
1567
+
1568
+ /* Backgrounds */
1569
+ --bg-body: #f1f5f9;
1570
+ --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1571
+
1572
+ /* Surfaces (glassmorphism) */
1573
+ --surface-solid: #ffffff;
1574
+ --surface-glass: rgba(255, 255, 255, 0.7);
1575
+ --surface-glass-border: rgba(255, 255, 255, 0.3);
1576
+ --surface-glass-hover: rgba(255, 255, 255, 0.85);
1577
+
1578
+ /* Border */
1579
+ --border-color: #e2e8f0;
1580
+
1581
+ /* Radius */
1582
+ --radius-sm: 4px;
1583
+ --radius-md: 8px;
1584
+ --radius-lg: 12px;
1585
+ --radius-xl: 16px;
1586
+
1587
+ /* Layout */
1588
+ --header-height: 56px;
1589
+ --sidebar-width: 60px;
1590
+ --sidebar-width-open: 240px;
1591
+ --sidebar-logo-height: 48px;
1592
+
1593
+ /* Inputs */
1594
+ --input-bg: #ffffff;
1595
+ --input-disabled-bg: #f8fafc;
1596
+ --input-disabled-text: #94a3b8;
1597
+
1598
+ /* Transitions */
1599
+ --transition-fast: 0.15s ease;
1600
+ --transition-normal: 0.25s ease;
1601
+ }
1602
+ ```
1603
+
1604
+ > The `variables.css` export from `nicklabs-ui` provides a default set of these variables. Import it as a starting point and override as needed in your own stylesheet.