@polymarbot/nuxt-layer-shadcn-ui 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,6 +21,16 @@ const sampleData: User[] = [
21
21
  { id: 5, name: 'Eve', email: 'eve@example.com', role: 'User', status: 'inactive', amount: 3456.89, createdAt: '2024-05-12T11:20:00Z' },
22
22
  ]
23
23
 
24
+ const longData: User[] = Array.from({ length: 30 }, (_, i) => ({
25
+ id: i + 1,
26
+ name: `User ${i + 1}`,
27
+ email: `user${i + 1}@example.com`,
28
+ role: [ 'Admin', 'Editor', 'User' ][i % 3]!,
29
+ status: i % 4 === 0 ? 'inactive' : 'active',
30
+ amount: Math.round(Math.random() * 10000) / 100,
31
+ createdAt: new Date(2024, 0, 1 + i).toISOString(),
32
+ }))
33
+
24
34
  const basicColumns: DataTableColumn[] = [
25
35
  { field: 'name', title: 'Name', width: '120px' },
26
36
  { field: 'email', title: 'Email', minWidth: '200px' },
@@ -67,11 +77,13 @@ const meta = {
67
77
  selectionMode: { control: 'select', options: [ undefined, 'single', 'multiple' ]},
68
78
  loading: { control: 'boolean' },
69
79
  clickable: { control: 'boolean' },
80
+ height: { control: 'text' },
70
81
  },
71
82
  args: {
72
83
  selectionMode: undefined,
73
84
  loading: false,
74
85
  clickable: false,
86
+ height: undefined,
75
87
  },
76
88
  render: args => ({
77
89
  components: { DataTable: DataTable as any },
@@ -91,6 +103,163 @@ const noControls = { controls: { disable: true }} satisfies Story['parameters']
91
103
 
92
104
  export const Default: Story = {}
93
105
 
106
+ export const Sortable: Story = {
107
+ parameters: {
108
+ ...noControls,
109
+ docs: {
110
+ source: {
111
+ code: `
112
+ <template>
113
+ <DataTable
114
+ :data="sortedData"
115
+ :columns="sortableColumns"
116
+ v-model:sortBy="sortBy"
117
+ v-model:sortOrder="sortOrder"
118
+ />
119
+ </template>
120
+ `.trim(),
121
+ },
122
+ },
123
+ },
124
+ render: () => ({
125
+ components: { DataTable: DataTable as any },
126
+ setup () {
127
+ const sortBy = ref<string | null>(null)
128
+ const sortOrder = ref<number | null>(null)
129
+
130
+ const sortedData = computed(() => {
131
+ if (!sortBy.value || !sortOrder.value) return sampleData
132
+ return [ ...sampleData ].sort((a, b) => {
133
+ const av = a[sortBy.value as keyof User]
134
+ const bv = b[sortBy.value as keyof User]
135
+ if (av < bv) return -1 * sortOrder.value!
136
+ if (av > bv) return 1 * sortOrder.value!
137
+ return 0
138
+ })
139
+ })
140
+
141
+ return { sortableColumns, sortBy, sortOrder, sortedData }
142
+ },
143
+ template: `
144
+ <div class="w-full">
145
+ <DataTable
146
+ :data="sortedData"
147
+ :columns="sortableColumns"
148
+ v-model:sortBy="sortBy"
149
+ v-model:sortOrder="sortOrder"
150
+ />
151
+ <div class="mt-2 text-sm text-muted-foreground">
152
+ Sort: {{ sortBy ?? 'none' }} / {{ sortOrder ?? 'none' }}
153
+ </div>
154
+ </div>
155
+ `,
156
+ }),
157
+ }
158
+
159
+ export const ColumnTypes: Story = {
160
+ parameters: {
161
+ ...noControls,
162
+ docs: {
163
+ source: {
164
+ code: `
165
+ <template>
166
+ <DataTable :data="data" :columns="typeColumns" />
167
+ </template>
168
+ `.trim(),
169
+ },
170
+ },
171
+ },
172
+ render: () => ({
173
+ components: { DataTable: DataTable as any },
174
+ setup: () => ({ data: sampleData, typeColumns }),
175
+ template: `
176
+ <div class="w-full">
177
+ <DataTable :data="data" :columns="typeColumns" />
178
+ </div>
179
+ `,
180
+ }),
181
+ }
182
+
183
+ export const RowClick: Story = {
184
+ parameters: {
185
+ ...noControls,
186
+ docs: {
187
+ source: {
188
+ code: `
189
+ <template>
190
+ <DataTable
191
+ :data="data"
192
+ :columns="columns"
193
+ clickable
194
+ @rowClick="row => lastClicked = row"
195
+ />
196
+ </template>
197
+ `.trim(),
198
+ },
199
+ },
200
+ },
201
+ render: () => ({
202
+ components: { DataTable: DataTable as any },
203
+ setup () {
204
+ const lastClicked = ref<User | null>(null)
205
+ return { data: sampleData, basicColumns, lastClicked }
206
+ },
207
+ template: `
208
+ <div class="w-full">
209
+ <DataTable
210
+ :data="data"
211
+ :columns="basicColumns"
212
+ clickable
213
+ @rowClick="row => lastClicked = row"
214
+ />
215
+ <div class="mt-2 text-sm text-muted-foreground">
216
+ Last clicked: {{ lastClicked?.name ?? 'none' }}
217
+ </div>
218
+ </div>
219
+ `,
220
+ }),
221
+ }
222
+
223
+ export const Loading: Story = {
224
+ parameters: {
225
+ ...noControls,
226
+ docs: {
227
+ source: {
228
+ code: '<DataTable :data="data" :columns="columns" loading />',
229
+ },
230
+ },
231
+ },
232
+ render: () => ({
233
+ components: { DataTable: DataTable as any },
234
+ setup: () => ({ data: sampleData, basicColumns }),
235
+ template: `
236
+ <div class="w-full">
237
+ <DataTable :data="data" :columns="basicColumns" loading />
238
+ </div>
239
+ `,
240
+ }),
241
+ }
242
+
243
+ export const EmptyState: Story = {
244
+ parameters: {
245
+ ...noControls,
246
+ docs: {
247
+ source: {
248
+ code: '<DataTable :data="[]" :columns="columns" />',
249
+ },
250
+ },
251
+ },
252
+ render: () => ({
253
+ components: { DataTable: DataTable as any },
254
+ setup: () => ({ basicColumns }),
255
+ template: `
256
+ <div class="w-full">
257
+ <DataTable :data="[]" :columns="basicColumns" />
258
+ </div>
259
+ `,
260
+ }),
261
+ }
262
+
94
263
  export const SingleSelection: Story = {
95
264
  parameters: {
96
265
  ...noControls,
@@ -171,7 +340,7 @@ export const MultipleSelection: Story = {
171
340
  }),
172
341
  }
173
342
 
174
- export const Sortable: Story = {
343
+ export const FrozenColumns: Story = {
175
344
  parameters: {
176
345
  ...noControls,
177
346
  docs: {
@@ -179,11 +348,15 @@ export const Sortable: Story = {
179
348
  code: `
180
349
  <template>
181
350
  <DataTable
182
- :data="sortedData"
183
- :columns="sortableColumns"
184
- v-model:sortBy="sortBy"
185
- v-model:sortOrder="sortOrder"
186
- />
351
+ :data="data"
352
+ :columns="frozenColumns"
353
+ selectionMode="multiple"
354
+ v-model:selection="selection"
355
+ >
356
+ <template #id>
357
+ <button class="text-sm text-primary underline">Edit</button>
358
+ </template>
359
+ </DataTable>
187
360
  </template>
188
361
  `.trim(),
189
362
  },
@@ -192,46 +365,41 @@ export const Sortable: Story = {
192
365
  render: () => ({
193
366
  components: { DataTable: DataTable as any },
194
367
  setup () {
195
- const sortBy = ref<string | null>(null)
196
- const sortOrder = ref<number | null>(null)
197
-
198
- const sortedData = computed(() => {
199
- if (!sortBy.value || !sortOrder.value) return sampleData
200
- return [ ...sampleData ].sort((a, b) => {
201
- const av = a[sortBy.value as keyof User]
202
- const bv = b[sortBy.value as keyof User]
203
- if (av < bv) return -1 * sortOrder.value!
204
- if (av > bv) return 1 * sortOrder.value!
205
- return 0
206
- })
207
- })
208
-
209
- return { sortableColumns, sortBy, sortOrder, sortedData }
368
+ const selection = ref<User[]>([])
369
+ return { data: sampleData, frozenColumns, selection }
210
370
  },
211
371
  template: `
212
- <div class="w-full">
372
+ <div class="max-w-[600px]">
213
373
  <DataTable
214
- :data="sortedData"
215
- :columns="sortableColumns"
216
- v-model:sortBy="sortBy"
217
- v-model:sortOrder="sortOrder"
218
- />
219
- <div class="mt-2 text-sm text-muted-foreground">
220
- Sort: {{ sortBy ?? 'none' }} / {{ sortOrder ?? 'none' }}
221
- </div>
374
+ :data="data"
375
+ :columns="frozenColumns"
376
+ selectionMode="multiple"
377
+ v-model:selection="selection"
378
+ >
379
+ <template #id>
380
+ <button class="text-sm text-primary underline">Edit</button>
381
+ </template>
382
+ </DataTable>
222
383
  </div>
223
384
  `,
224
385
  }),
225
386
  }
226
387
 
227
- export const ColumnTypes: Story = {
388
+ /** Internal vertical scroll: header and footer stay pinned, only tbody scrolls. */
389
+ export const FixedHeight: Story = {
228
390
  parameters: {
229
391
  ...noControls,
230
392
  docs: {
231
393
  source: {
232
394
  code: `
233
395
  <template>
234
- <DataTable :data="data" :columns="typeColumns" />
396
+ <DataTable :data="data" :columns="columns" height="360px">
397
+ <template #footer>
398
+ <div class="text-right text-sm font-medium">
399
+ Total: <span class="font-mono">\${{ total.toFixed(2) }}</span>
400
+ </div>
401
+ </template>
402
+ </DataTable>
235
403
  </template>
236
404
  `.trim(),
237
405
  },
@@ -239,16 +407,28 @@ export const ColumnTypes: Story = {
239
407
  },
240
408
  render: () => ({
241
409
  components: { DataTable: DataTable as any },
242
- setup: () => ({ data: sampleData, typeColumns }),
410
+ setup () {
411
+ const total = computed(() => longData.reduce((sum, r) => sum + r.amount, 0))
412
+ return { data: longData, sortableColumns, total }
413
+ },
243
414
  template: `
244
415
  <div class="w-full">
245
- <DataTable :data="data" :columns="typeColumns" />
416
+ <DataTable :data="data" :columns="sortableColumns" height="360px">
417
+ <template #footer>
418
+ <div class="text-right text-sm font-medium">
419
+ Total: <span class="font-mono">\${{ total.toFixed(2) }}</span>
420
+ </div>
421
+ </template>
422
+ </DataTable>
246
423
  </div>
247
424
  `,
248
425
  }),
249
426
  }
250
427
 
251
- export const CustomSlots: Story = {
428
+ // -- Slot variations -------------------------------------------------------
429
+
430
+ /** Per-column cell slots — slot name matches `column.field`, scope: `{ column, row, value, index }`. */
431
+ export const CellSlots: Story = {
252
432
  parameters: {
253
433
  ...noControls,
254
434
  docs: {
@@ -290,7 +470,8 @@ export const CustomSlots: Story = {
290
470
  }),
291
471
  }
292
472
 
293
- export const SlotEmptyFallback: Story = {
473
+ /** When a per-column cell slot renders nothing, a placeholder dash shows automatically. */
474
+ export const CellSlotEmptyFallback: Story = {
294
475
  parameters: {
295
476
  ...noControls,
296
477
  docs: {
@@ -355,40 +536,20 @@ export const SlotEmptyFallback: Story = {
355
536
  }),
356
537
  }
357
538
 
358
- export const EmptyState: Story = {
359
- parameters: {
360
- ...noControls,
361
- docs: {
362
- source: {
363
- code: '<DataTable :data="[]" :columns="columns" />',
364
- },
365
- },
366
- },
367
- render: () => ({
368
- components: { DataTable: DataTable as any },
369
- setup: () => ({ basicColumns }),
370
- template: `
371
- <div class="w-full">
372
- <DataTable :data="[]" :columns="basicColumns" />
373
- </div>
374
- `,
375
- }),
376
- }
377
-
378
- export const FooterSlot: Story = {
539
+ /** `bodyStart` / `bodyEnd` slots render full-width rows above/below the data. */
540
+ export const BodySlots: Story = {
379
541
  parameters: {
380
542
  ...noControls,
381
543
  docs: {
382
544
  source: {
383
545
  code: `
384
546
  <template>
385
- <DataTable :data="data" :columns="columns">
386
- <template #footer>
387
- <tr class="h-12 border-t text-sm font-medium">
388
- <td class="px-4">Total</td>
389
- <td class="px-4" colspan="2"></td>
390
- <td class="px-4 font-mono">\${{ data.reduce((sum, r) => sum + r.amount, 0).toFixed(2) }}</td>
391
- </tr>
547
+ <DataTable :data="data" :columns="columns" height="360px">
548
+ <template #bodyStart>
549
+ <div class="text-center text-xs text-muted-foreground">Latest entries</div>
550
+ </template>
551
+ <template #bodyEnd>
552
+ <div class="text-center text-xs text-muted-foreground">— end of list —</div>
392
553
  </template>
393
554
  </DataTable>
394
555
  </template>
@@ -397,17 +558,16 @@ export const FooterSlot: Story = {
397
558
  },
398
559
  },
399
560
  render: () => ({
400
- components: { DataTable: DataTable as any, Tag },
401
- setup: () => ({ data: sampleData, slotColumns }),
561
+ components: { DataTable: DataTable as any },
562
+ setup: () => ({ data: longData, sortableColumns }),
402
563
  template: `
403
564
  <div class="w-full">
404
- <DataTable :data="data" :columns="slotColumns">
405
- <template #footer>
406
- <tr class="h-12 border-t text-sm font-medium">
407
- <td class="px-4">Total</td>
408
- <td class="px-4" colspan="2"></td>
409
- <td class="px-4 font-mono">\${{ data.reduce((sum, r) => sum + r.amount, 0).toFixed(2) }}</td>
410
- </tr>
565
+ <DataTable :data="data" :columns="sortableColumns" height="360px">
566
+ <template #bodyStart>
567
+ <div class="text-center text-xs text-muted-foreground">Latest entries</div>
568
+ </template>
569
+ <template #bodyEnd>
570
+ <div class="text-center text-xs text-muted-foreground">— end of list —</div>
411
571
  </template>
412
572
  </DataTable>
413
573
  </div>
@@ -415,21 +575,19 @@ export const FooterSlot: Story = {
415
575
  }),
416
576
  }
417
577
 
418
- export const FrozenColumns: Story = {
578
+ /** Footer slot content is wrapped internally in a full-width row. */
579
+ export const FooterSlot: Story = {
419
580
  parameters: {
420
581
  ...noControls,
421
582
  docs: {
422
583
  source: {
423
584
  code: `
424
585
  <template>
425
- <DataTable
426
- :data="data"
427
- :columns="frozenColumns"
428
- selectionMode="multiple"
429
- v-model:selection="selection"
430
- >
431
- <template #id>
432
- <button class="text-sm text-primary underline">Edit</button>
586
+ <DataTable :data="data" :columns="columns">
587
+ <template #footer>
588
+ <div class="text-right text-sm font-medium">
589
+ Total: <span class="font-mono">\${{ total.toFixed(2) }}</span>
590
+ </div>
433
591
  </template>
434
592
  </DataTable>
435
593
  </template>
@@ -438,84 +596,21 @@ export const FrozenColumns: Story = {
438
596
  },
439
597
  },
440
598
  render: () => ({
441
- components: { DataTable: DataTable as any },
599
+ components: { DataTable: DataTable as any, Tag },
442
600
  setup () {
443
- const selection = ref<User[]>([])
444
- return { data: sampleData, frozenColumns, selection }
601
+ const total = computed(() => sampleData.reduce((sum, r) => sum + r.amount, 0))
602
+ return { data: sampleData, slotColumns, total }
445
603
  },
446
604
  template: `
447
- <div class="max-w-[600px]">
448
- <DataTable
449
- :data="data"
450
- :columns="frozenColumns"
451
- selectionMode="multiple"
452
- v-model:selection="selection"
453
- >
454
- <template #id>
455
- <button class="text-sm text-primary underline">Edit</button>
605
+ <div class="w-full">
606
+ <DataTable :data="data" :columns="slotColumns">
607
+ <template #footer>
608
+ <div class="text-right text-sm font-medium">
609
+ Total: <span class="font-mono">\${{ total.toFixed(2) }}</span>
610
+ </div>
456
611
  </template>
457
612
  </DataTable>
458
613
  </div>
459
614
  `,
460
615
  }),
461
616
  }
462
-
463
- export const Loading: Story = {
464
- parameters: {
465
- ...noControls,
466
- docs: {
467
- source: {
468
- code: '<DataTable :data="data" :columns="columns" loading />',
469
- },
470
- },
471
- },
472
- render: () => ({
473
- components: { DataTable: DataTable as any },
474
- setup: () => ({ data: sampleData, basicColumns }),
475
- template: `
476
- <div class="w-full">
477
- <DataTable :data="data" :columns="basicColumns" loading />
478
- </div>
479
- `,
480
- }),
481
- }
482
-
483
- export const RowClick: Story = {
484
- parameters: {
485
- ...noControls,
486
- docs: {
487
- source: {
488
- code: `
489
- <template>
490
- <DataTable
491
- :data="data"
492
- :columns="columns"
493
- clickable
494
- @rowClick="row => lastClicked = row"
495
- />
496
- </template>
497
- `.trim(),
498
- },
499
- },
500
- },
501
- render: () => ({
502
- components: { DataTable: DataTable as any },
503
- setup () {
504
- const lastClicked = ref<User | null>(null)
505
- return { data: sampleData, basicColumns, lastClicked }
506
- },
507
- template: `
508
- <div class="w-full">
509
- <DataTable
510
- :data="data"
511
- :columns="basicColumns"
512
- clickable
513
- @rowClick="row => lastClicked = row"
514
- />
515
- <div class="mt-2 text-sm text-muted-foreground">
516
- Last clicked: {{ lastClicked?.name ?? 'none' }}
517
- </div>
518
- </div>
519
- `,
520
- }),
521
- }