@polymarbot/nuxt-layer-shadcn-ui 0.6.3 → 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.
- package/app/components/ui/DataTable/index.stories.ts +249 -154
- package/app/components/ui/DataTable/index.vue +94 -65
- package/app/components/ui/DataTable/types.ts +2 -0
- package/app/components/ui/DatePicker/index.stories.ts +1 -1
- package/app/components/ui/DatePicker/index.vue +1 -1
- package/app/components/ui/DateRangePicker/index.stories.ts +2 -2
- package/app/components/ui/DateRangePicker/index.vue +2 -2
- package/app/components/ui/InfiniteDataTable/en.json +6 -0
- package/app/components/ui/InfiniteDataTable/index.stories.ts +165 -0
- package/app/components/ui/InfiniteDataTable/index.vue +239 -0
- package/app/components/ui/InfiniteDataTable/types.ts +35 -0
- package/app/components/ui/InputRange/index.stories.ts +2 -2
- package/app/components/ui/InputRange/index.vue +2 -2
- package/app/components/ui/SearchSelect/index.vue +4 -4
- package/app/components/ui/Select/index.stories.ts +10 -0
- package/app/components/ui/Select/index.vue +10 -3
- package/app/components/ui/Select/types.ts +2 -0
- package/i18n/messages/ar.json +6 -0
- package/i18n/messages/de.json +6 -0
- package/i18n/messages/en.json +6 -0
- package/i18n/messages/es.json +6 -0
- package/i18n/messages/fr.json +6 -0
- package/i18n/messages/hi.json +6 -0
- package/i18n/messages/id.json +6 -0
- package/i18n/messages/it.json +6 -0
- package/i18n/messages/ja.json +6 -0
- package/i18n/messages/ko.json +6 -0
- package/i18n/messages/nl.json +6 -0
- package/i18n/messages/pl.json +6 -0
- package/i18n/messages/pt.json +6 -0
- package/i18n/messages/ru.json +6 -0
- package/i18n/messages/th.json +6 -0
- package/i18n/messages/tr.json +6 -0
- package/i18n/messages/vi.json +6 -0
- package/i18n/messages/zh-CN.json +6 -0
- package/i18n/messages/zh-TW.json +6 -0
- package/package.json +2 -2
|
@@ -27,6 +27,7 @@ const props = withDefaults(defineProps<DataTableProps<TData>>(), {
|
|
|
27
27
|
sortOrder: undefined,
|
|
28
28
|
loading: false,
|
|
29
29
|
clickable: false,
|
|
30
|
+
height: undefined,
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
const emit = defineEmits<{
|
|
@@ -46,6 +47,8 @@ defineSlots<
|
|
|
46
47
|
}) => any> & {
|
|
47
48
|
empty?: () => any
|
|
48
49
|
footer?: () => any
|
|
50
|
+
bodyStart?: () => any
|
|
51
|
+
bodyEnd?: () => any
|
|
49
52
|
} & Record<`header-${string}`, (_: { column: DataTableColumn }) => any>
|
|
50
53
|
>()
|
|
51
54
|
|
|
@@ -252,6 +255,9 @@ function buildColumnStyle (column: DataTableColumn): Record<string, string> {
|
|
|
252
255
|
return style
|
|
253
256
|
}
|
|
254
257
|
|
|
258
|
+
// CSS var bound on outer wrapper, read by table-container via :deep selector
|
|
259
|
+
const heightStyle = computed(() => props.height ? { '--data-table-height': props.height } : undefined)
|
|
260
|
+
|
|
255
261
|
// Reusable class fragments
|
|
256
262
|
const headerCellClass = 'h-auto bg-border px-4 py-3 text-xs font-normal text-foreground'
|
|
257
263
|
const headerDividerClass = 'relative after:absolute after:top-1/2 after:right-0 after:h-4 after:w-px after:-translate-y-1/2 after:bg-muted-foreground/25'
|
|
@@ -263,13 +269,21 @@ const selectionColumnStyle = { width: '1%' }
|
|
|
263
269
|
const selectionColumnShadowDir = computed<FrozenShadow | undefined>(() =>
|
|
264
270
|
showSelectionColumn.value && !lastLeftFrozenField.value && !atStart.value ? 'left' : undefined,
|
|
265
271
|
)
|
|
272
|
+
|
|
273
|
+
defineExpose({
|
|
274
|
+
/** The shadcn table-container element — useful as IntersectionObserver root. */
|
|
275
|
+
scrollEl,
|
|
276
|
+
})
|
|
266
277
|
</script>
|
|
267
278
|
|
|
268
279
|
<template>
|
|
269
280
|
<div
|
|
270
|
-
:class="cn(
|
|
271
|
-
|
|
272
|
-
|
|
281
|
+
:class="cn(
|
|
282
|
+
'rounded-lg bg-border px-1 text-foreground relative',
|
|
283
|
+
!$slots.footer && 'pb-1',
|
|
284
|
+
height && 'has-sticky-bounds',
|
|
285
|
+
)"
|
|
286
|
+
:style="heightStyle"
|
|
273
287
|
>
|
|
274
288
|
<!-- Loading overlay -->
|
|
275
289
|
<Transition
|
|
@@ -281,7 +295,7 @@ const selectionColumnShadowDir = computed<FrozenShadow | undefined>(() =>
|
|
|
281
295
|
<div
|
|
282
296
|
v-if="loading"
|
|
283
297
|
class="
|
|
284
|
-
inset-0 rounded-lg bg-background/60 absolute z-
|
|
298
|
+
inset-0 rounded-lg bg-background/60 absolute z-30 flex items-center
|
|
285
299
|
justify-center
|
|
286
300
|
"
|
|
287
301
|
>
|
|
@@ -292,7 +306,10 @@ const selectionColumnShadowDir = computed<FrozenShadow | undefined>(() =>
|
|
|
292
306
|
</div>
|
|
293
307
|
</Transition>
|
|
294
308
|
|
|
295
|
-
<Table
|
|
309
|
+
<Table
|
|
310
|
+
ref="tableRef"
|
|
311
|
+
class="h-full"
|
|
312
|
+
>
|
|
296
313
|
<TableHeader>
|
|
297
314
|
<TableRow
|
|
298
315
|
class="hover:bg-transparent"
|
|
@@ -354,6 +371,17 @@ const selectionColumnShadowDir = computed<FrozenShadow | undefined>(() =>
|
|
|
354
371
|
[&_tr]:h-15
|
|
355
372
|
"
|
|
356
373
|
>
|
|
374
|
+
<!-- Top body slot (e.g. infinite-scroll trigger) -->
|
|
375
|
+
<TableRow
|
|
376
|
+
v-if="$slots.bodyStart"
|
|
377
|
+
data-virtual-row
|
|
378
|
+
class="hover:bg-transparent"
|
|
379
|
+
>
|
|
380
|
+
<TableCell :colspan="totalColumns">
|
|
381
|
+
<slot name="bodyStart" />
|
|
382
|
+
</TableCell>
|
|
383
|
+
</TableRow>
|
|
384
|
+
|
|
357
385
|
<template v-if="data?.length">
|
|
358
386
|
<TableRow
|
|
359
387
|
v-for="(row, index) in data"
|
|
@@ -409,89 +437,90 @@ const selectionColumnShadowDir = computed<FrozenShadow | undefined>(() =>
|
|
|
409
437
|
</TableRow>
|
|
410
438
|
</template>
|
|
411
439
|
|
|
412
|
-
<
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
440
|
+
<TableEmpty
|
|
441
|
+
v-else-if="!loading"
|
|
442
|
+
:colspan="totalColumns"
|
|
443
|
+
>
|
|
444
|
+
<slot name="empty">
|
|
445
|
+
<div
|
|
446
|
+
class="gap-2 text-muted-foreground flex flex-col items-center"
|
|
447
|
+
>
|
|
448
|
+
<Icon
|
|
449
|
+
name="inbox"
|
|
450
|
+
class="size-8"
|
|
451
|
+
/>
|
|
452
|
+
<span class="text-sm">
|
|
453
|
+
{{ T('empty') }}
|
|
454
|
+
</span>
|
|
455
|
+
</div>
|
|
456
|
+
</slot>
|
|
457
|
+
</TableEmpty>
|
|
458
|
+
|
|
459
|
+
<!-- Bottom body slot (e.g. infinite-scroll trigger / "all loaded") -->
|
|
460
|
+
<TableRow
|
|
461
|
+
v-if="$slots.bodyEnd"
|
|
462
|
+
data-virtual-row
|
|
463
|
+
class="hover:bg-transparent"
|
|
464
|
+
>
|
|
465
|
+
<TableCell :colspan="totalColumns">
|
|
466
|
+
<slot name="bodyEnd" />
|
|
467
|
+
</TableCell>
|
|
468
|
+
</TableRow>
|
|
429
469
|
</TableBody>
|
|
430
470
|
|
|
431
471
|
<TableFooter
|
|
432
472
|
v-if="$slots.footer"
|
|
433
|
-
class="
|
|
473
|
+
class="
|
|
474
|
+
[&_td]:px-4 [&_td]:py-2
|
|
475
|
+
border-t-0 bg-transparent
|
|
476
|
+
"
|
|
434
477
|
>
|
|
435
|
-
<
|
|
478
|
+
<TableRow>
|
|
479
|
+
<TableCell
|
|
480
|
+
:colspan="totalColumns"
|
|
481
|
+
class="bg-border"
|
|
482
|
+
>
|
|
483
|
+
<slot name="footer" />
|
|
484
|
+
</TableCell>
|
|
485
|
+
</TableRow>
|
|
436
486
|
</TableFooter>
|
|
437
487
|
</Table>
|
|
438
488
|
</div>
|
|
439
489
|
</template>
|
|
440
490
|
|
|
441
491
|
<style scoped>
|
|
442
|
-
|
|
443
|
-
:
|
|
444
|
-
--cell-bg: var(--color-card);
|
|
445
|
-
--corner-r: 8px;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
:deep(tbody tr:hover) {
|
|
449
|
-
--cell-bg: var(--color-muted);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
:deep(tbody td) {
|
|
453
|
-
background: var(--cell-bg);
|
|
492
|
+
:deep([data-slot="table-container"]) {
|
|
493
|
+
height: var(--data-table-height, auto);
|
|
454
494
|
}
|
|
455
495
|
|
|
456
|
-
/*
|
|
457
|
-
|
|
458
|
-
:
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
var(--cell-bg);
|
|
496
|
+
/* sticky on <th>/<td>, not <tr> — row-level sticky lacks browser support */
|
|
497
|
+
.has-sticky-bounds :deep(thead th) {
|
|
498
|
+
position: sticky;
|
|
499
|
+
top: 0;
|
|
500
|
+
z-index: 20;
|
|
462
501
|
}
|
|
463
502
|
|
|
464
|
-
:deep(
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
503
|
+
.has-sticky-bounds :deep(tfoot td) {
|
|
504
|
+
position: sticky;
|
|
505
|
+
bottom: 0;
|
|
506
|
+
z-index: 20;
|
|
468
507
|
}
|
|
469
508
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
var(--cell-bg);
|
|
509
|
+
/* clip-path is reliable on table-row-group, unlike overflow:hidden */
|
|
510
|
+
:deep(tbody) {
|
|
511
|
+
clip-path: inset(0 round 8px);
|
|
474
512
|
}
|
|
475
513
|
|
|
476
|
-
:deep(tbody tr
|
|
477
|
-
|
|
478
|
-
radial-gradient(circle at 0 0, transparent var(--corner-r), var(--color-accent) var(--corner-r)) 100% 100% / var(--corner-r) var(--corner-r) no-repeat,
|
|
479
|
-
var(--cell-bg);
|
|
514
|
+
:deep(tbody tr) {
|
|
515
|
+
--cell-bg: var(--color-card);
|
|
480
516
|
}
|
|
481
517
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
background:
|
|
485
|
-
radial-gradient(circle at var(--corner-r) var(--corner-r), transparent var(--corner-r), var(--color-accent) var(--corner-r)) 0 0 / var(--corner-r) var(--corner-r) no-repeat,
|
|
486
|
-
radial-gradient(circle at var(--corner-r) 0, transparent var(--corner-r), var(--color-accent) var(--corner-r)) 0 100% / var(--corner-r) var(--corner-r) no-repeat,
|
|
487
|
-
var(--cell-bg);
|
|
518
|
+
:deep(tbody tr:hover) {
|
|
519
|
+
--cell-bg: var(--color-muted);
|
|
488
520
|
}
|
|
489
521
|
|
|
490
|
-
:deep(tbody
|
|
491
|
-
background:
|
|
492
|
-
radial-gradient(circle at 0 var(--corner-r), transparent var(--corner-r), var(--color-accent) var(--corner-r)) 100% 0 / var(--corner-r) var(--corner-r) no-repeat,
|
|
493
|
-
radial-gradient(circle at 0 0, transparent var(--corner-r), var(--color-accent) var(--corner-r)) 100% 100% / var(--corner-r) var(--corner-r) no-repeat,
|
|
494
|
-
var(--cell-bg);
|
|
522
|
+
:deep(tbody td) {
|
|
523
|
+
background: var(--cell-bg);
|
|
495
524
|
}
|
|
496
525
|
|
|
497
526
|
/* Frozen column shadow via ::before — ::after is reserved for header divider */
|
|
@@ -41,4 +41,6 @@ export interface DataTableProps<T = Record<string, any>> {
|
|
|
41
41
|
loading?: boolean
|
|
42
42
|
/** Whether rows are clickable (shows pointer cursor and pairs with `@rowClick`) */
|
|
43
43
|
clickable?: boolean
|
|
44
|
+
/** Fixed height for the inner scroll container (e.g. '400px'). Enables internal vertical scroll, with sticky header and footer. */
|
|
45
|
+
height?: string
|
|
44
46
|
}
|
|
@@ -86,7 +86,7 @@ const timeConfig = computed(() => {
|
|
|
86
86
|
<div @click.stop>
|
|
87
87
|
<Input
|
|
88
88
|
:modelValue="value"
|
|
89
|
-
:placeholder="placeholder
|
|
89
|
+
:placeholder="placeholder || T('placeholder')"
|
|
90
90
|
:disabled="disabled"
|
|
91
91
|
:readonly="readonly"
|
|
92
92
|
@update:modelValue="(v: string | undefined) => onInput(v ?? '')"
|
|
@@ -27,8 +27,8 @@ const meta = {
|
|
|
27
27
|
showTime: false,
|
|
28
28
|
disabled: false,
|
|
29
29
|
readonly: false,
|
|
30
|
-
startPlaceholder:
|
|
31
|
-
endPlaceholder:
|
|
30
|
+
startPlaceholder: '',
|
|
31
|
+
endPlaceholder: '',
|
|
32
32
|
maxSpanDays: undefined,
|
|
33
33
|
valueFormat: undefined,
|
|
34
34
|
autoApply: true,
|
|
@@ -97,7 +97,7 @@ const endMaxDate = computed(() => {
|
|
|
97
97
|
:showTime="showTime"
|
|
98
98
|
:disabled="disabled"
|
|
99
99
|
:readonly="readonly"
|
|
100
|
-
:placeholder="startPlaceholder
|
|
100
|
+
:placeholder="startPlaceholder || T('startPlaceholder')"
|
|
101
101
|
:minDate="startMinDate"
|
|
102
102
|
:maxDate="startMaxDate"
|
|
103
103
|
:valueFormat="valueFormat"
|
|
@@ -112,7 +112,7 @@ const endMaxDate = computed(() => {
|
|
|
112
112
|
:showTime="showTime"
|
|
113
113
|
:disabled="disabled"
|
|
114
114
|
:readonly="readonly"
|
|
115
|
-
:placeholder="endPlaceholder
|
|
115
|
+
:placeholder="endPlaceholder || T('endPlaceholder')"
|
|
116
116
|
:minDate="endMinDate"
|
|
117
117
|
:maxDate="endMaxDate"
|
|
118
118
|
:valueFormat="valueFormat"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import type { DataTableColumn } from '../DataTable/types'
|
|
3
|
+
import type { InfiniteDataTableFetchParams, InfiniteDataTableFetchResult } from './types'
|
|
4
|
+
import InfiniteDataTable from './index.vue'
|
|
5
|
+
|
|
6
|
+
interface User {
|
|
7
|
+
id: number
|
|
8
|
+
name: string
|
|
9
|
+
email: string
|
|
10
|
+
role: string
|
|
11
|
+
status: string
|
|
12
|
+
amount: number
|
|
13
|
+
createdAt: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const allUsers: User[] = Array.from({ length: 120 }, (_, i) => ({
|
|
17
|
+
id: i + 1,
|
|
18
|
+
name: `User ${i + 1}`,
|
|
19
|
+
email: `user${i + 1}@example.com`,
|
|
20
|
+
role: [ 'Admin', 'Editor', 'User' ][i % 3]!,
|
|
21
|
+
status: i % 4 === 0 ? 'inactive' : 'active',
|
|
22
|
+
amount: Math.round(Math.random() * 10000) / 100,
|
|
23
|
+
createdAt: new Date(2024, 0, 1 + i).toISOString(),
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
const columns: DataTableColumn[] = [
|
|
27
|
+
{ field: 'name', title: 'Name', width: '120px', sortable: true },
|
|
28
|
+
{ field: 'email', title: 'Email', minWidth: '200px' },
|
|
29
|
+
{ field: 'role', title: 'Role', width: '100px', sortable: true },
|
|
30
|
+
{ field: 'status', title: 'Status', width: '100px' },
|
|
31
|
+
{ field: 'amount', title: 'Amount', width: '120px', type: 'currency', sortable: true },
|
|
32
|
+
{ field: 'createdAt', title: 'Created', width: '140px', type: 'date' },
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
/** Mock fetch using offset as the opaque `next` token */
|
|
36
|
+
function mockFetch (params: InfiniteDataTableFetchParams): Promise<InfiniteDataTableFetchResult<User>> {
|
|
37
|
+
return new Promise(resolve => {
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
const data = [ ...allUsers ]
|
|
40
|
+
|
|
41
|
+
if (params.role) {
|
|
42
|
+
data.splice(0, data.length, ...data.filter(u => u.role === params.role))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (params.sortBy) {
|
|
46
|
+
const order = params.sortOrder ?? 1
|
|
47
|
+
data.sort((a, b) => {
|
|
48
|
+
const av = a[params.sortBy as keyof User]
|
|
49
|
+
const bv = b[params.sortBy as keyof User]
|
|
50
|
+
if (av! < bv!) return -1 * order
|
|
51
|
+
if (av! > bv!) return 1 * order
|
|
52
|
+
return 0
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const offset = params.next ? Number(params.next) : 0
|
|
57
|
+
const items = data.slice(offset, offset + params.limit)
|
|
58
|
+
const nextOffset = offset + items.length
|
|
59
|
+
resolve({
|
|
60
|
+
items,
|
|
61
|
+
next: nextOffset < data.length ? String(nextOffset) : undefined,
|
|
62
|
+
total: data.length,
|
|
63
|
+
})
|
|
64
|
+
}, 400)
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const meta = {
|
|
69
|
+
title: 'UI/InfiniteDataTable',
|
|
70
|
+
component: InfiniteDataTable as any,
|
|
71
|
+
argTypes: {
|
|
72
|
+
columns: { control: 'object' },
|
|
73
|
+
fetchMethod: { control: false },
|
|
74
|
+
autoFetch: { control: 'boolean' },
|
|
75
|
+
filters: { control: 'object' },
|
|
76
|
+
pageSize: { control: 'number' },
|
|
77
|
+
height: { control: 'text' },
|
|
78
|
+
clickable: { control: 'boolean' },
|
|
79
|
+
},
|
|
80
|
+
args: {
|
|
81
|
+
columns,
|
|
82
|
+
fetchMethod: mockFetch,
|
|
83
|
+
autoFetch: true,
|
|
84
|
+
filters: undefined,
|
|
85
|
+
pageSize: 30,
|
|
86
|
+
height: '360px',
|
|
87
|
+
clickable: false,
|
|
88
|
+
},
|
|
89
|
+
render: args => ({
|
|
90
|
+
components: { InfiniteDataTable: InfiniteDataTable as any },
|
|
91
|
+
setup: () => ({ args }),
|
|
92
|
+
template: '<InfiniteDataTable v-bind="args" />',
|
|
93
|
+
}),
|
|
94
|
+
} satisfies Meta<typeof InfiniteDataTable>
|
|
95
|
+
|
|
96
|
+
export default meta
|
|
97
|
+
type Story = StoryObj<typeof meta>
|
|
98
|
+
|
|
99
|
+
const noControls = { controls: { disable: true }} satisfies Story['parameters']
|
|
100
|
+
|
|
101
|
+
/** Internal scroll container — scrolling inside the table fetches the next page */
|
|
102
|
+
export const Default: Story = {}
|
|
103
|
+
|
|
104
|
+
/** External `filters` changes also reset pagination — try toggling the role filter */
|
|
105
|
+
export const WithFilters: Story = {
|
|
106
|
+
parameters: {
|
|
107
|
+
...noControls,
|
|
108
|
+
docs: {
|
|
109
|
+
source: {
|
|
110
|
+
code: `
|
|
111
|
+
<template>
|
|
112
|
+
<select v-model="role">
|
|
113
|
+
<option value="">All</option>
|
|
114
|
+
<option value="Admin">Admin</option>
|
|
115
|
+
<option value="Editor">Editor</option>
|
|
116
|
+
<option value="User">User</option>
|
|
117
|
+
</select>
|
|
118
|
+
<InfiniteDataTable
|
|
119
|
+
:columns="columns"
|
|
120
|
+
:fetchMethod="mockFetch"
|
|
121
|
+
:filters="{ role: role || undefined }"
|
|
122
|
+
height="360px"
|
|
123
|
+
/>
|
|
124
|
+
</template>
|
|
125
|
+
`.trim(),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
render: () => ({
|
|
130
|
+
components: { InfiniteDataTable: InfiniteDataTable as any },
|
|
131
|
+
setup () {
|
|
132
|
+
const role = ref('')
|
|
133
|
+
const filters = computed(() => ({ role: role.value || undefined }))
|
|
134
|
+
return { columns, mockFetch, role, filters }
|
|
135
|
+
},
|
|
136
|
+
template: `
|
|
137
|
+
<div class="space-y-3">
|
|
138
|
+
<label class="gap-2 text-sm flex items-center">
|
|
139
|
+
Role:
|
|
140
|
+
<select
|
|
141
|
+
v-model="role"
|
|
142
|
+
class="px-2 py-1 border rounded"
|
|
143
|
+
>
|
|
144
|
+
<option value="">All</option>
|
|
145
|
+
<option value="Admin">Admin</option>
|
|
146
|
+
<option value="Editor">Editor</option>
|
|
147
|
+
<option value="User">User</option>
|
|
148
|
+
</select>
|
|
149
|
+
</label>
|
|
150
|
+
<InfiniteDataTable
|
|
151
|
+
:columns="columns"
|
|
152
|
+
:fetchMethod="mockFetch"
|
|
153
|
+
:filters="filters"
|
|
154
|
+
height="360px"
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
`,
|
|
158
|
+
}),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** No fixed height — the page scrolls and triggers loading at the bottom */
|
|
162
|
+
export const PageScroll: Story = {
|
|
163
|
+
parameters: noControls,
|
|
164
|
+
args: { height: undefined },
|
|
165
|
+
}
|