nicklabs-ui 1.0.25 → 1.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A Vue 3 component library with glassmorphism design, built for modern web applications.
4
4
 
5
- **Version**: 1.0.6 | **Framework**: Vue 3.5+
5
+ **Version**: 1.0.26 | **Framework**: Vue 3.5+
6
6
 
7
7
  ---
8
8
 
@@ -19,6 +19,7 @@ A Vue 3 component library with glassmorphism design, built for modern web applic
19
19
  - [NSelect](#nselect)
20
20
  - [NFileSelect](#nfileselect)
21
21
  - [NSwitch](#nswitch)
22
+ - [NDatePicker](#ndatepicker)
22
23
  - [Data Display](#data-display)
23
24
  - [NTable](#ntable)
24
25
  - [NList](#nlist)
@@ -46,7 +47,9 @@ A Vue 3 component library with glassmorphism design, built for modern web applic
46
47
  - [useToast](#usetoast)
47
48
  - [useAlert](#usealert)
48
49
  - [useDisclosure](#usedisclosure)
50
+ - [useBreadcrumb](#usebreadcrumb)
49
51
  - [useSidebarManager](#usesidebarmanager)
52
+ - [useRouteModal](#useroutemodal)
50
53
  - [Type Reference](#type-reference)
51
54
  - [CSS Variables](#css-variables)
52
55
 
@@ -90,6 +93,22 @@ Import components individually as needed:
90
93
  import { NButton, NInput, NModal, useToast } from 'nicklabs-ui'
91
94
  ```
92
95
 
96
+ ### 3. Register All Components (Optional)
97
+
98
+ ```typescript
99
+ import { createApp } from 'vue'
100
+ import App from './App.vue'
101
+ import router from './router'
102
+ import { NickLabsUI } from 'nicklabs-ui'
103
+
104
+ const app = createApp(App)
105
+ app.use(router)
106
+ app.use(NickLabsUI, { router })
107
+ app.mount('#app')
108
+ ```
109
+
110
+ > Passing `router` enables `useBreadcrumb` and `useRouteModal` without any additional setup.
111
+
93
112
  ---
94
113
 
95
114
  ## Components
@@ -394,10 +413,6 @@ const cities = [
394
413
  { label: 'Tokyo', value: 'tokyo' },
395
414
  { label: 'Seoul', value: 'seoul' },
396
415
  ]
397
- const countries = [
398
- { label: 'Taiwan', value: 'TW' },
399
- { label: 'Japan', value: 'JP' },
400
- ]
401
416
  const tagOptions = [
402
417
  { label: 'Frontend', value: 'frontend' },
403
418
  { label: 'Backend', value: 'backend' },
@@ -500,50 +515,110 @@ const darkMode = ref(false)
500
515
 
501
516
  ---
502
517
 
518
+ #### NDatePicker
519
+
520
+ Date picker with range selection and custom format support.
521
+
522
+ **Props**
523
+
524
+ | Prop | Type | Default | Description |
525
+ |------|------|---------|-------------|
526
+ | `modelValue` | `string \| Date` | — | Bound value (v-model) for single date |
527
+ | `start` | `string` | — | Range start (v-model:start) |
528
+ | `end` | `string` | — | Range end (v-model:end) |
529
+ | `placeholder` | `string` | `"請選擇日期"` | Placeholder text |
530
+ | `disabled` | `boolean` | `false` | Disable picker |
531
+ | `clearable` | `boolean` | `false` | Show clear button |
532
+ | `title` | `string` | — | Label above picker |
533
+ | `format` | `string` | `"YYYY-MM-DD"` | Date format string (supports `YYYY`, `MM`, `DD`, `HH`, `mm`) |
534
+ | `range` | `boolean` | `false` | Enable range selection mode |
535
+
536
+ **Events**
537
+
538
+ | Event | Payload | Description |
539
+ |-------|---------|-------------|
540
+ | `update:modelValue` | `string` | Single date value changed |
541
+ | `change` | `string` | Date changed |
542
+ | `clear` | — | Clear button clicked |
543
+ | `update:start` | `string` | Range start changed |
544
+ | `update:end` | `string` | Range end changed |
545
+
546
+ **Usage**
547
+
548
+ ```vue
549
+ <template>
550
+ <!-- Single date -->
551
+ <NDatePicker v-model="date" title="Select Date" clearable />
552
+
553
+ <!-- With time format -->
554
+ <NDatePicker v-model="datetime" format="YYYY-MM-DD HH:mm" title="Date & Time" />
555
+
556
+ <!-- Date range -->
557
+ <NDatePicker
558
+ range
559
+ v-model:start="startDate"
560
+ v-model:end="endDate"
561
+ title="Date Range"
562
+ />
563
+ </template>
564
+
565
+ <script setup>
566
+ import { ref } from 'vue'
567
+ import { NDatePicker } from 'nicklabs-ui'
568
+
569
+ const date = ref('')
570
+ const datetime = ref('')
571
+ const startDate = ref('')
572
+ const endDate = ref('')
573
+ </script>
574
+ ```
575
+
576
+ ---
577
+
503
578
  ### Data Display
504
579
 
505
580
  ---
506
581
 
507
582
  #### NTable
508
583
 
509
- A type-safe, sortable data table with support for custom formatters and action slots.
584
+ A type-safe, sortable data table with support for batch selection and action slots.
510
585
 
511
586
  **Props**
512
587
 
513
588
  | Prop | Type | Default | Description |
514
589
  |------|------|---------|-------------|
515
- | `columns` | `NTableColumn<T>[]` | `[]` | Column definitions |
516
- | `rows` | `T[]` | `[]` | Row data |
517
- | `striped` | `boolean` | `false` | Alternating row colors |
590
+ | `columns` | `NTableColumn[]` | `[]` | Column definitions |
591
+ | `items` | `T[]` | `[]` | Row data |
518
592
  | `bordered` | `boolean` | `false` | Show borders |
519
593
  | `hoverable` | `boolean` | `true` | Highlight rows on hover |
520
594
  | `loading` | `boolean` | `false` | Show loading state |
521
- | `emptyText` | `string` | | Message when no data |
522
- | `rowKey` | `string` | `"id"` | Unique key field |
595
+ | `emptyTitle` | `string` | `"目前沒有資料"` | Title when no data |
596
+ | `emptyDescription` | `string` | | Description when no data |
597
+ | `itemKey` | `string` | `"id"` | Unique key field |
523
598
 
524
599
  **Events**
525
600
 
526
601
  | Event | Payload | Description |
527
602
  |-------|---------|-------------|
528
603
  | `sort` | `NTableSortState` | Column sort changed |
529
- | `row-click` | `T` | Row was clicked |
604
+ | `click` | `T` | Row was clicked |
530
605
 
531
606
  **Slots**
532
607
 
533
608
  | Slot | Description |
534
609
  |------|-------------|
535
- | `action` | Custom action buttons per row (scoped: `{ row }`) |
610
+ | `batch` | Content in the batch selection header column |
611
+ | `actions-header` | Custom header text for the actions column (default: "操作") |
612
+ | `actions` | Custom action buttons per row (scoped: `{ item, column, index }`) |
613
+ | `empty` | Custom empty state content |
536
614
 
537
615
  **NTableColumn Interface**
538
616
 
539
617
  ```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
618
+ interface NTableColumn {
619
+ key: string // Data field key
620
+ label: string // Column header text
621
+ sortable?: boolean
547
622
  }
548
623
  ```
549
624
 
@@ -553,15 +628,14 @@ interface NTableColumn<T = any> {
553
628
  <template>
554
629
  <NTable
555
630
  :columns="columns"
556
- :rows="users"
557
- striped
631
+ :items="users"
558
632
  hoverable
559
633
  @sort="handleSort"
560
- @row-click="handleRowClick"
634
+ @click="handleItemClick"
561
635
  >
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>
636
+ <template #actions="{ item }">
637
+ <NButton size="sm" variant="ghost" intent="primary" @click="edit(item)">Edit</NButton>
638
+ <NButton size="sm" variant="ghost" intent="error" @click="remove(item)">Delete</NButton>
565
639
  </template>
566
640
  </NTable>
567
641
  </template>
@@ -576,31 +650,25 @@ interface User {
576
650
  name: string
577
651
  email: string
578
652
  role: string
579
- createdAt: string
580
653
  }
581
654
 
582
- const columns: NTableColumn<User>[] = [
655
+ const columns: NTableColumn[] = [
583
656
  { key: 'name', label: 'Name', sortable: true },
584
657
  { 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
- },
658
+ { key: 'role', label: 'Role' },
591
659
  ]
592
660
 
593
661
  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' },
662
+ { id: 1, name: 'Alice', email: 'alice@example.com', role: 'Admin' },
663
+ { id: 2, name: 'Bob', email: 'bob@example.com', role: 'User' },
596
664
  ])
597
665
 
598
666
  function handleSort(state: NTableSortState) {
599
667
  console.log('Sort by:', state.key, state.order)
600
668
  }
601
669
 
602
- function handleRowClick(row: User) {
603
- console.log('Clicked:', row)
670
+ function handleItemClick(item: User) {
671
+ console.log('Clicked:', item)
604
672
  }
605
673
  </script>
606
674
  ```
@@ -615,55 +683,77 @@ A full-featured data management component combining table, pagination, filtering
615
683
 
616
684
  | Prop | Type | Default | Description |
617
685
  |------|------|---------|-------------|
618
- | `items` | `T[]` | `[]` | Data items |
619
- | `columns` | `NTableColumn<T>[]` | `[]` | Column definitions |
686
+ | `title` | `string` | | Section title displayed in hero header |
687
+ | `description` | `string` | | Section description displayed in hero header |
688
+ | `items` | `T[]` | `[]` | Data items (T must extend `{ id: number }`) |
689
+ | `columns` | `NTableColumn[]` | `[]` | Column definitions |
690
+ | `itemKey` | `string` | `"id"` | Unique key field |
620
691
  | `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 |
692
+ | `maxPageButtons` | `number` | `7` | Visible page buttons |
693
+ | `filterable` | `boolean` | `false` | Show filter button |
694
+ | `updatable` | `boolean` | `false` | Show row edit button |
695
+ | `deletable` | `boolean` | `false` | Show row delete button |
625
696
  | `creatable` | `boolean` | `false` | Show create button |
626
- | `batchDeletable` | `boolean` | `false` | Enable batch delete |
697
+ | `batchDeletable` | `boolean` | `false` | Enable batch delete with checkboxes |
627
698
  | `refreshable` | `boolean` | `false` | Show refresh button |
699
+ | `emptyTitle` | `string` | `"目前沒有資料"` | Empty state title |
700
+ | `emptyDescription` | `string` | — | Empty state description |
701
+ | `emptyIcon` | `string` | — | Custom SVG string for empty state |
628
702
 
629
703
  **Events**
630
704
 
631
705
  | Event | Payload | Description |
632
706
  |-------|---------|-------------|
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 |
707
+ | `update` | `T` | Row edit clicked |
708
+ | `delete` | `T` | Row delete clicked |
709
+ | `pageChange` | `number` | Page changed |
710
+ | `create` | — | Create clicked |
711
+ | `batchDelete` | `number[]` | Batch delete with selected IDs |
712
+ | `refresh` | — | Refresh clicked |
713
+ | `filter` | — | Filter clicked |
714
+ | `click` | `T` | Row clicked |
641
715
  | `sort` | `NTableSortState` | Sort changed |
642
716
 
717
+ **Slots**
718
+
719
+ | Slot | Description |
720
+ |------|-------------|
721
+ | `toolbar` | Extra toolbar content (alongside create/refresh buttons) |
722
+ | `item` | Custom cell renderer (scoped: `{ item, column, index }`) |
723
+ | `actions` | Custom action buttons per row (scoped: `{ item, index }`) |
724
+ | `actions-header` | Custom header text for the actions column |
725
+
643
726
  **Usage**
644
727
 
645
728
  ```vue
646
729
  <template>
647
730
  <NList
731
+ title="User Management"
648
732
  :items="users"
649
733
  :columns="columns"
650
734
  :page-size="20"
651
735
  filterable
652
- row-editable
653
- row-deletable
736
+ updatable
737
+ deletable
654
738
  creatable
655
739
  refreshable
656
- @on-row-edit="handleEdit"
657
- @on-row-delete="handleDelete"
658
- @on-create="handleCreate"
659
- @on-refresh="loadUsers"
740
+ @update="handleEdit"
741
+ @delete="handleDelete"
742
+ @create="handleCreate"
743
+ @refresh="loadUsers"
744
+ @filter="openFilter"
660
745
  />
661
746
  </template>
662
747
 
663
748
  <script setup lang="ts">
664
749
  import { NList } from 'nicklabs-ui'
665
750
 
666
- // ... same column/data setup as NTable
751
+ // T must extend { id: number }
752
+ interface User {
753
+ id: number
754
+ name: string
755
+ email: string
756
+ }
667
757
  </script>
668
758
  ```
669
759
 
@@ -829,7 +919,6 @@ const isOpen = ref(false)
829
919
 
830
920
  function open() { isOpen.value = true }
831
921
  function confirm() {
832
- // do something
833
922
  isOpen.value = false
834
923
  }
835
924
  </script>
@@ -960,37 +1049,29 @@ const isSubmitting = ref(false)
960
1049
 
961
1050
  #### NLayout
962
1051
 
963
- Main application shell integrating sidebar and navigation.
1052
+ Main application shell integrating sidebar and main content area.
964
1053
 
965
1054
  **Props**
966
1055
 
967
1056
  | Prop | Type | Default | Description |
968
1057
  |------|------|---------|-------------|
969
- | `menus` | `Menu[]` | `[]` | Sidebar menu items |
1058
+ | `menus` | `Menu[]` | | Sidebar menu items |
970
1059
  | `isShowSidebar` | `boolean` | `true` | Show/hide sidebar |
971
1060
  | `copyright` | `string` | — | Footer copyright text |
972
- | `currentPath` | `string` | | Active route path |
1061
+ | `currentPath` | `string` | `""` | Active route path |
973
1062
 
974
1063
  **Events**
975
1064
 
976
- | Event | Description |
977
- |-------|-------------|
978
- | `logout` | Logout triggered |
979
- | `navigate` | Menu item clicked |
1065
+ | Event | Payload | Description |
1066
+ |-------|---------|-------------|
1067
+ | `logout` | — | Logout triggered |
1068
+ | `navigate` | `MenuChild` | Menu item clicked |
980
1069
 
981
- **Menu Type**
1070
+ **Slots**
982
1071
 
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
- ```
1072
+ | Slot | Description |
1073
+ |------|-------------|
1074
+ | `default` | Main page content |
994
1075
 
995
1076
  **Usage**
996
1077
 
@@ -1048,23 +1129,25 @@ Top navigation bar with sidebar toggle, fullscreen, and user controls.
1048
1129
 
1049
1130
  #### NSidebar
1050
1131
 
1051
- Collapsible side navigation menu.
1132
+ Collapsible side navigation menu with hover-expand behavior.
1052
1133
 
1053
1134
  **Props**
1054
1135
 
1055
1136
  | Prop | Type | Default | Description |
1056
1137
  |------|------|---------|-------------|
1057
- | `isOpen` | `boolean` | `true` | Sidebar open state |
1058
- | `menus` | `Menu[]` | `[]` | Menu items |
1059
- | `currentPath` | `string` | | Active route path |
1138
+ | `isOpen` | `boolean` | `false` | Sidebar open state |
1139
+ | `menus` | `Menu[]` | | Menu items |
1140
+ | `currentPath` | `string` | `""` | Active route path |
1141
+ | `userName` | `string` | — | Display name shown in user area |
1142
+ | `userAvatarUrl` | `string` | — | Avatar image URL |
1060
1143
 
1061
1144
  **Events**
1062
1145
 
1063
- | Event | Description |
1064
- |-------|-------------|
1065
- | `update:isOpen` | Open state changed |
1066
- | `logout` | Logout triggered |
1067
- | `navigate` | Menu item clicked |
1146
+ | Event | Payload | Description |
1147
+ |-------|---------|-------------|
1148
+ | `update:isOpen` | `boolean` | Open state changed |
1149
+ | `logout` | — | Logout triggered |
1150
+ | `navigate` | `MenuChild` | Menu item clicked |
1068
1151
 
1069
1152
  ---
1070
1153
 
@@ -1077,7 +1160,7 @@ Content card with glassmorphism styling.
1077
1160
  | Prop | Type | Default | Description |
1078
1161
  |------|------|---------|-------------|
1079
1162
  | `size` | `"none" \| "sm" \| "md" \| "lg"` | `"md"` | Padding size |
1080
- | `radius` | `"none" \| "sm" \| "md" \| "lg" \| "xl"` | `"lg"` | Border radius |
1163
+ | `radius` | `"none" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | Border radius |
1081
1164
 
1082
1165
  **Usage**
1083
1166
 
@@ -1104,18 +1187,29 @@ Form wrapper with optional tab navigation and hero section header.
1104
1187
 
1105
1188
  | Prop | Type | Default | Description |
1106
1189
  |------|------|---------|-------------|
1107
- | `model` | `object` | | Form data object |
1190
+ | `model` | `object` | `{}` | Form data object |
1108
1191
  | `disabled` | `boolean` | `false` | Disable all inputs |
1109
1192
  | `title` | `string` | — | Form header title |
1193
+ | `description` | `string` | — | Form header description |
1110
1194
  | `icon` | `string` | — | Header icon (SVG string) |
1111
- | `tabs` | `string[]` | | Tab labels for multi-section forms |
1195
+ | `tabs` | `string[]` | `[]` | Tab labels for multi-section forms |
1112
1196
 
1113
1197
  **Events**
1114
1198
 
1115
- | Event | Description |
1116
- |-------|-------------|
1117
- | `submit` | Form submitted |
1118
- | `reset` | Form reset |
1199
+ | Event | Payload | Description |
1200
+ |-------|---------|-------------|
1201
+ | `submit` | `Record<string, any>` | Form submitted with model data |
1202
+ | `reset` | — | Form reset |
1203
+
1204
+ **Slots**
1205
+
1206
+ | Slot | Description |
1207
+ |------|-------------|
1208
+ | `toolbar` | Toolbar area in the hero header (buttons, etc.) |
1209
+ | `description` | Custom description content in hero header |
1210
+ | `tab0` | Content for first tab (or only content when no tabs) |
1211
+ | `tab1`, `tab2`, ... | Content for subsequent tabs |
1212
+ | `footer` | Form footer (submit/cancel buttons) |
1119
1213
 
1120
1214
  **Usage**
1121
1215
 
@@ -1124,22 +1218,37 @@ Form wrapper with optional tab navigation and hero section header.
1124
1218
  <NForm
1125
1219
  :model="formData"
1126
1220
  title="User Profile"
1221
+ description="Manage your account details"
1127
1222
  :tabs="['Basic Info', 'Security', 'Preferences']"
1128
1223
  @submit="handleSubmit"
1129
1224
  >
1130
- <!-- Content renders based on active tab -->
1131
- <NInput v-model="formData.name" title="Name" />
1225
+ <template #toolbar>
1226
+ <NButton variant="ghost" @click="cancel">Cancel</NButton>
1227
+ </template>
1228
+
1229
+ <template #tab0>
1230
+ <NInput v-model="formData.name" title="Name" />
1231
+ <NInput v-model="formData.email" title="Email" />
1232
+ </template>
1233
+
1234
+ <template #tab1>
1235
+ <NInput v-model="formData.password" type="password" title="Password" />
1236
+ </template>
1237
+
1238
+ <template #footer>
1239
+ <NButton type="submit" intent="primary">Save</NButton>
1240
+ </template>
1132
1241
  </NForm>
1133
1242
  </template>
1134
1243
 
1135
1244
  <script setup>
1136
1245
  import { reactive } from 'vue'
1137
- import { NForm, NInput } from 'nicklabs-ui'
1246
+ import { NForm, NInput, NButton } from 'nicklabs-ui'
1138
1247
 
1139
- const formData = reactive({ name: '' })
1248
+ const formData = reactive({ name: '', email: '', password: '' })
1140
1249
 
1141
- function handleSubmit() {
1142
- console.log('Submitted:', formData)
1250
+ function handleSubmit(model) {
1251
+ console.log('Submitted:', model)
1143
1252
  }
1144
1253
  </script>
1145
1254
  ```
@@ -1154,8 +1263,8 @@ Full-screen login page wrapper with animated card.
1154
1263
 
1155
1264
  | Prop | Type | Default | Description |
1156
1265
  |------|------|---------|-------------|
1157
- | `backgroundImage` | `string` | — | Background image URL |
1158
- | `logo` | `string` | — | Logo image URL or SVG |
1266
+ | `backgroundImage` | `string` | — | Background image URL (falls back to `--bg-gradient`) |
1267
+ | `logo` | `string` | — | Logo image URL or SVG string |
1159
1268
  | `title` | `string` | — | Application name |
1160
1269
  | `description` | `string` | — | Subtitle/tagline |
1161
1270
 
@@ -1188,7 +1297,9 @@ const password = ref('')
1188
1297
 
1189
1298
  #### NBreadcrumb
1190
1299
 
1191
- Static breadcrumb navigation display.
1300
+ Breadcrumb navigation driven by `useBreadcrumb`. No props — reads from the composable's state automatically.
1301
+
1302
+ > Configure breadcrumbs via [useBreadcrumb](#usebreadcrumb).
1192
1303
 
1193
1304
  **Usage**
1194
1305
 
@@ -1212,9 +1323,9 @@ Pagination control with smart ellipsis.
1212
1323
 
1213
1324
  | Prop | Type | Default | Description |
1214
1325
  |------|------|---------|-------------|
1215
- | `totalItems` | `number` | `0` | Total number of items |
1216
- | `pageSize` | `number` | `10` | Items per page |
1217
- | `maxPageButtons` | `number` | `5` | Max visible page buttons |
1326
+ | `totalItems` | `number` | | Total number of items |
1327
+ | `pageSize` | `number` | | Items per page |
1328
+ | `maxPageButtons` | `number` | `7` | Max visible page buttons (min: 5) |
1218
1329
 
1219
1330
  **Events**
1220
1331
 
@@ -1250,27 +1361,31 @@ function loadPage(page: number) {
1250
1361
 
1251
1362
  #### NHeroSection
1252
1363
 
1253
- Page header with icon, title, breadcrumb, and toolbar slot.
1364
+ Page header with icon, title, description, breadcrumb, and toolbar slot.
1254
1365
 
1255
1366
  **Props**
1256
1367
 
1257
1368
  | Prop | Type | Default | Description |
1258
1369
  |------|------|---------|-------------|
1259
1370
  | `title` | `string` | — | Section title |
1371
+ | `description` | `string` | — | Section description |
1260
1372
  | `icon` | `string` | — | Icon (SVG string) |
1261
1373
 
1262
1374
  **Slots**
1263
1375
 
1264
1376
  | Slot | Description |
1265
1377
  |------|-------------|
1266
- | `default` | Toolbar content (buttons, filters, etc.) |
1378
+ | `toolbar` | Toolbar content (buttons, filters, etc.) |
1379
+ | `description` | Custom description content |
1267
1380
 
1268
1381
  **Usage**
1269
1382
 
1270
1383
  ```vue
1271
1384
  <template>
1272
- <NHeroSection title="User Management" :icon="userIcon">
1273
- <NButton intent="primary" @click="create">New User</NButton>
1385
+ <NHeroSection title="User Management" description="Manage system users" :icon="userIcon">
1386
+ <template #toolbar>
1387
+ <NButton intent="primary" @click="create">New User</NButton>
1388
+ </template>
1274
1389
  </NHeroSection>
1275
1390
  </template>
1276
1391
 
@@ -1291,7 +1406,7 @@ A slide-out side drawer for filter interfaces.
1291
1406
 
1292
1407
  | Prop | Type | Default | Description |
1293
1408
  |------|------|---------|-------------|
1294
- | `open` | `boolean` | `false` | Open state (v-model:open) |
1409
+ | `open` | `boolean` | | Open state (v-model:open) |
1295
1410
  | `title` | `string` | — | Drawer title |
1296
1411
 
1297
1412
  **Events**
@@ -1471,9 +1586,83 @@ const [isOpen, open, close] = useDisclosure()
1471
1586
 
1472
1587
  ---
1473
1588
 
1589
+ ### useBreadcrumb
1590
+
1591
+ Manage breadcrumb navigation state. `NBreadcrumb` reads from this composable automatically.
1592
+
1593
+ ```typescript
1594
+ import { useBreadcrumb } from 'nicklabs-ui'
1595
+
1596
+ const {
1597
+ breadcrumbs,
1598
+ setLabelResolver,
1599
+ setBreadcrumbSuffix,
1600
+ clearSuffix,
1601
+ navigate,
1602
+ } = useBreadcrumb()
1603
+ ```
1604
+
1605
+ **Methods**
1606
+
1607
+ | Method | Signature | Description |
1608
+ |--------|-----------|-------------|
1609
+ | `setLabelResolver` | `(fn: (route) => string) => void` | Custom function to extract label from a route record |
1610
+ | `setBreadcrumbSuffix` | `(fn: (label: string) => string) => void` | Transform the last breadcrumb label (e.g. append a record name) |
1611
+ | `clearSuffix` | `() => void` | Reset the suffix transform |
1612
+ | `navigate` | `(path: string) => void` | Navigate to a breadcrumb path |
1613
+
1614
+ **BreadcrumbItem**
1615
+
1616
+ ```typescript
1617
+ interface BreadcrumbItem {
1618
+ label: string
1619
+ path?: string // undefined for the last (active) crumb
1620
+ }
1621
+ ```
1622
+
1623
+ **Setup**
1624
+
1625
+ Pass `router` when installing the plugin (see [Setup](#setup)). The label is read from `route.meta.breadcrumb` by default — no additional configuration needed.
1626
+
1627
+ **Route meta**
1628
+
1629
+ ```typescript
1630
+ const routes = [
1631
+ {
1632
+ path: '/users',
1633
+ component: UserList,
1634
+ meta: { breadcrumb: 'Users' },
1635
+ children: [
1636
+ {
1637
+ path: ':id',
1638
+ component: UserDetail,
1639
+ meta: { breadcrumb: 'Detail' },
1640
+ },
1641
+ ],
1642
+ },
1643
+ ]
1644
+ ```
1645
+
1646
+ **Dynamic suffix example**
1647
+
1648
+ ```vue
1649
+ <script setup>
1650
+ import { onMounted, onUnmounted } from 'vue'
1651
+ import { useBreadcrumb } from 'nicklabs-ui'
1652
+
1653
+ const { setBreadcrumbSuffix, clearSuffix } = useBreadcrumb()
1654
+
1655
+ // Show user name as the last breadcrumb label
1656
+ onMounted(() => setBreadcrumbSuffix(() => user.value.name))
1657
+ onUnmounted(() => clearSuffix())
1658
+ </script>
1659
+ ```
1660
+
1661
+ ---
1662
+
1474
1663
  ### useSidebarManager
1475
1664
 
1476
- Manage sidebar open/close state and expandable menu items, persisted to localStorage.
1665
+ Manage sidebar open/close state and expandable menu groups, persisted to localStorage.
1477
1666
 
1478
1667
  ```typescript
1479
1668
  import { useSidebarManager } from 'nicklabs-ui'
@@ -1503,6 +1692,77 @@ const {
1503
1692
 
1504
1693
  ---
1505
1694
 
1695
+ ### useRouteModal
1696
+
1697
+ Open and close route-based modals using Vue Router's nested routes.
1698
+
1699
+ ```typescript
1700
+ import { useRouteModal } from 'nicklabs-ui'
1701
+
1702
+ const { isOpen, open, close, params } = useRouteModal({
1703
+ routeName: 'user-detail',
1704
+ parentRouteName: 'users', // optional fallback when no history
1705
+ })
1706
+ ```
1707
+
1708
+ **Options**
1709
+
1710
+ | Option | Type | Required | Description |
1711
+ |--------|------|----------|-------------|
1712
+ | `routeName` | `string` | Yes | Named route that represents the open modal state |
1713
+ | `parentRouteName` | `string` | No | Fallback route to navigate to on close when there is no browser history |
1714
+
1715
+ **Returns**
1716
+
1717
+ | Property | Type | Description |
1718
+ |----------|------|-------------|
1719
+ | `isOpen` | `ComputedRef<boolean>` | `true` when the named route is in the matched stack |
1720
+ | `open` | `(params?) => void` | Navigate to the modal route |
1721
+ | `close` | `() => void` | Navigate back (uses `router.back()` or parent route) |
1722
+ | `params` | `ComputedRef<RouteParams>` | Current route params |
1723
+
1724
+ **Usage**
1725
+
1726
+ ```typescript
1727
+ // router/index.ts
1728
+ const routes = [
1729
+ {
1730
+ path: '/users',
1731
+ name: 'users',
1732
+ component: UserList,
1733
+ children: [
1734
+ {
1735
+ path: ':id',
1736
+ name: 'user-detail',
1737
+ component: UserDetail,
1738
+ },
1739
+ ],
1740
+ },
1741
+ ]
1742
+ ```
1743
+
1744
+ ```vue
1745
+ <template>
1746
+ <NButton @click="open({ id: user.id })">View</NButton>
1747
+
1748
+ <NModal v-model:show="isOpen" title="User Detail" @close="close">
1749
+ <p>ID: {{ params.id }}</p>
1750
+ </NModal>
1751
+ </template>
1752
+
1753
+ <script setup>
1754
+ import { useRouteModal } from 'nicklabs-ui'
1755
+ import { NModal, NButton } from 'nicklabs-ui'
1756
+
1757
+ const { isOpen, open, close, params } = useRouteModal({
1758
+ routeName: 'user-detail',
1759
+ parentRouteName: 'users',
1760
+ })
1761
+ </script>
1762
+ ```
1763
+
1764
+ ---
1765
+
1506
1766
  ## Type Reference
1507
1767
 
1508
1768
  ```typescript
@@ -1512,34 +1772,50 @@ interface OptionItem {
1512
1772
  value: any
1513
1773
  }
1514
1774
 
1775
+ type NSize = 'sm' | 'md' | 'lg'
1776
+
1515
1777
  // Table
1516
- interface NTableColumn<T = any> {
1778
+ interface NTableColumn {
1517
1779
  key: string
1518
1780
  label: string
1519
- width?: string
1520
- align?: 'left' | 'center' | 'right'
1521
1781
  sortable?: boolean
1522
- formatter?: (value: any, row: T) => string
1523
1782
  }
1524
1783
 
1784
+ type NTableSortOrder = 'asc' | 'desc' | null
1785
+
1525
1786
  interface NTableSortState {
1526
1787
  key: string
1527
- order: 'asc' | 'desc' | null
1788
+ order: NTableSortOrder
1528
1789
  }
1529
1790
 
1530
1791
  // Layout
1792
+ type MenuChild = {
1793
+ icon: string
1794
+ title: string
1795
+ route: string
1796
+ }
1797
+
1531
1798
  type Menu = {
1532
1799
  icon: string
1533
1800
  title: string
1534
- children?: { icon: string; title: string; route: string }[]
1801
+ children?: MenuChild[]
1802
+ }
1803
+
1804
+ // Breadcrumb
1805
+ interface BreadcrumbItem {
1806
+ label: string
1807
+ path?: string
1535
1808
  }
1536
1809
 
1537
1810
  // Sizes / Variants
1538
- type NSize = 'sm' | 'md' | 'lg'
1539
1811
  type NButtonVariant = 'none' | 'solid' | 'outline' | 'ghost' | 'mute'
1540
1812
  type NButtonIntent = 'none' | 'primary' | 'error' | 'success' | 'warning' | 'info'
1813
+ type NButtonRadiusSize = 'sm' | 'md' | 'lg' | 'xl' | 'full'
1814
+ type NButtonSize = 'sm' | 'md' | 'lg'
1541
1815
  type NTagIntent = 'none' | 'primary' | 'success' | 'warning' | 'error' | 'info'
1542
1816
  type NTagVariant = 'solid' | 'light' | 'outline'
1817
+ type PaddingSize = 'none' | 'sm' | 'md' | 'lg'
1818
+ type RadiusSize = 'none' | 'sm' | 'md' | 'lg' | 'xl'
1543
1819
  ```
1544
1820
 
1545
1821
  ---