v-uixy 1.5.0 → 1.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/package.json +1 -1
- package/templates/components/Alert/Alert.component.vue +53 -11
- package/templates/components/Alert/Alert.types.ts +2 -0
- package/templates/components/Alert/AlertItem.component.vue +21 -2
- package/templates/components/Button/Button.styles.ts +4 -4
- package/templates/components/Checkbox/Checkbox.component.vue +19 -5
- package/templates/components/Checkbox/Checkbox.styles.ts +10 -0
- package/templates/components/Checkbox/Checkbox.types.ts +1 -0
- package/templates/components/DatePicker/DatePicker.component.vue +23 -3
- package/templates/components/DatePicker/DatePicker.types.ts +0 -1
- package/templates/components/DateRangePicker/DateRangePicker.component.vue +29 -4
- package/templates/components/DateRangePicker/DateRangePicker.types.ts +0 -1
- package/templates/components/FileUpload/FileUpload.component.vue +23 -13
- package/templates/components/Input/Input.component.vue +23 -13
- package/templates/components/Input/Input.types.ts +0 -1
- package/templates/components/Popover/Popover.component.vue +1 -1
- package/templates/components/Select/Select.component.vue +23 -14
- package/templates/components/Select/Select.types.ts +0 -1
- package/templates/components/Table/Table.component.vue +507 -0
- package/templates/components/Table/Table.styles.ts +140 -0
- package/templates/components/Table/Table.types.ts +98 -0
- package/templates/components/Table/index.ts +39 -0
- package/templates/components/Textarea/Textarea.component.vue +20 -14
- package/templates/components/Textarea/Textarea.types.ts +0 -1
- package/templates/components.json +6 -0
- package/templates/composables/usePosition.ts +6 -4
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<uixy-card :class="cardClass" v-bind="filteredAttrs">
|
|
3
|
+
<uixy-card class="w-full overflow-hidden p-0">
|
|
4
|
+
<div
|
|
5
|
+
:class="scrollAreaStyles({ scrollY: !!props.maxHeight })"
|
|
6
|
+
:style="props.maxHeight ? { maxHeight: props.maxHeight } : undefined"
|
|
7
|
+
>
|
|
8
|
+
<table :class="tableStyles()">
|
|
9
|
+
<caption v-if="props.caption" :class="captionStyles()">
|
|
10
|
+
{{
|
|
11
|
+
props.caption
|
|
12
|
+
}}
|
|
13
|
+
</caption>
|
|
14
|
+
|
|
15
|
+
<colgroup>
|
|
16
|
+
<col v-if="props.selectable" style="width: 44px" />
|
|
17
|
+
<col
|
|
18
|
+
v-for="column in props.columns"
|
|
19
|
+
:key="column.key"
|
|
20
|
+
:style="{ width: column.width, minWidth: column.minWidth }"
|
|
21
|
+
/>
|
|
22
|
+
</colgroup>
|
|
23
|
+
|
|
24
|
+
<thead>
|
|
25
|
+
<tr>
|
|
26
|
+
<th
|
|
27
|
+
v-if="props.selectable"
|
|
28
|
+
:class="
|
|
29
|
+
thStyles(
|
|
30
|
+
{
|
|
31
|
+
align: 'left',
|
|
32
|
+
density: props.density,
|
|
33
|
+
sticky: !!props.stickyHeader,
|
|
34
|
+
},
|
|
35
|
+
'w-11',
|
|
36
|
+
)
|
|
37
|
+
"
|
|
38
|
+
>
|
|
39
|
+
<uixy-checkbox
|
|
40
|
+
:checked="allSelected"
|
|
41
|
+
:indeterminate="someSelected"
|
|
42
|
+
:disabled="!props.rows.length"
|
|
43
|
+
@update:checked="toggleSelectAll"
|
|
44
|
+
/>
|
|
45
|
+
</th>
|
|
46
|
+
<th
|
|
47
|
+
v-for="column in props.columns"
|
|
48
|
+
:key="column.key"
|
|
49
|
+
scope="col"
|
|
50
|
+
:class="
|
|
51
|
+
thStyles({
|
|
52
|
+
align: column.align ?? 'left',
|
|
53
|
+
density: props.density,
|
|
54
|
+
sticky: !!props.stickyHeader,
|
|
55
|
+
})
|
|
56
|
+
"
|
|
57
|
+
>
|
|
58
|
+
<span :class="thContentStyles()">
|
|
59
|
+
<span
|
|
60
|
+
:class="thLabelStyles({ align: column.align ?? 'left' })"
|
|
61
|
+
>
|
|
62
|
+
<span
|
|
63
|
+
v-if="column.sortable"
|
|
64
|
+
:class="sortButtonStyles()"
|
|
65
|
+
@click="handleSort(column)"
|
|
66
|
+
>
|
|
67
|
+
{{ column.header }}
|
|
68
|
+
<uixy-icon
|
|
69
|
+
:name="sortIconName(column)"
|
|
70
|
+
:class="sortIconStyles({ active: isSorted(column) })"
|
|
71
|
+
/>
|
|
72
|
+
</span>
|
|
73
|
+
<span v-else>{{ column.header }}</span>
|
|
74
|
+
</span>
|
|
75
|
+
<uixy-popover
|
|
76
|
+
v-if="hasFilterSlot(column)"
|
|
77
|
+
:group="filterGroupId"
|
|
78
|
+
>
|
|
79
|
+
<template #trigger="triggerProps">
|
|
80
|
+
<button
|
|
81
|
+
v-bind="triggerProps"
|
|
82
|
+
type="button"
|
|
83
|
+
:class="
|
|
84
|
+
filterTriggerStyles({
|
|
85
|
+
active: isFilterActive(column),
|
|
86
|
+
})
|
|
87
|
+
"
|
|
88
|
+
>
|
|
89
|
+
<uixy-icon name="sliders-2" class="h-3.5 w-3.5" />
|
|
90
|
+
</button>
|
|
91
|
+
</template>
|
|
92
|
+
<template #default="{ close }">
|
|
93
|
+
<uixy-card :class="filterPanelStyles()">
|
|
94
|
+
<slot
|
|
95
|
+
:name="`filter-${column.key}`"
|
|
96
|
+
:column="column"
|
|
97
|
+
:filter="filterFor(column)"
|
|
98
|
+
:close="close"
|
|
99
|
+
/>
|
|
100
|
+
</uixy-card>
|
|
101
|
+
</template>
|
|
102
|
+
</uixy-popover>
|
|
103
|
+
</span>
|
|
104
|
+
</th>
|
|
105
|
+
</tr>
|
|
106
|
+
</thead>
|
|
107
|
+
|
|
108
|
+
<tbody>
|
|
109
|
+
<template v-if="props.loading">
|
|
110
|
+
<tr v-for="n in skeletonRowCount" :key="`skeleton-${n}`">
|
|
111
|
+
<td
|
|
112
|
+
v-if="props.selectable"
|
|
113
|
+
:class="
|
|
114
|
+
tdStyles({
|
|
115
|
+
align: 'left',
|
|
116
|
+
density: props.density,
|
|
117
|
+
last: n === skeletonRowCount,
|
|
118
|
+
})
|
|
119
|
+
"
|
|
120
|
+
>
|
|
121
|
+
<uixy-skeleton class="h-4 w-4 rounded-1" />
|
|
122
|
+
</td>
|
|
123
|
+
<td
|
|
124
|
+
v-for="column in props.columns"
|
|
125
|
+
:key="column.key"
|
|
126
|
+
:class="
|
|
127
|
+
tdStyles({
|
|
128
|
+
align: column.align ?? 'left',
|
|
129
|
+
density: props.density,
|
|
130
|
+
last: n === skeletonRowCount,
|
|
131
|
+
})
|
|
132
|
+
"
|
|
133
|
+
>
|
|
134
|
+
<uixy-skeleton class="h-4 w-full max-w-32 rounded-1" />
|
|
135
|
+
</td>
|
|
136
|
+
</tr>
|
|
137
|
+
</template>
|
|
138
|
+
|
|
139
|
+
<template v-else-if="!props.rows.length">
|
|
140
|
+
<tr>
|
|
141
|
+
<td :colspan="totalColumnCount" class="p-0">
|
|
142
|
+
<div :class="emptyStyles()">
|
|
143
|
+
<slot name="empty">
|
|
144
|
+
<span>{{ props.emptyText ?? "No data available." }}</span>
|
|
145
|
+
</slot>
|
|
146
|
+
</div>
|
|
147
|
+
</td>
|
|
148
|
+
</tr>
|
|
149
|
+
</template>
|
|
150
|
+
|
|
151
|
+
<template v-else>
|
|
152
|
+
<tr
|
|
153
|
+
v-for="(row, index) in props.rows"
|
|
154
|
+
:key="keyOf(row, index)"
|
|
155
|
+
:class="rowStyles({ selected: isSelected(row, index) })"
|
|
156
|
+
>
|
|
157
|
+
<td
|
|
158
|
+
v-if="props.selectable"
|
|
159
|
+
:class="
|
|
160
|
+
tdStyles({
|
|
161
|
+
align: 'left',
|
|
162
|
+
density: props.density,
|
|
163
|
+
last: index === props.rows.length - 1,
|
|
164
|
+
})
|
|
165
|
+
"
|
|
166
|
+
>
|
|
167
|
+
<uixy-checkbox
|
|
168
|
+
:checked="isSelected(row, index)"
|
|
169
|
+
@update:checked="
|
|
170
|
+
(checked) => toggleRow(row, index, checked)
|
|
171
|
+
"
|
|
172
|
+
/>
|
|
173
|
+
</td>
|
|
174
|
+
<td
|
|
175
|
+
v-for="column in props.columns"
|
|
176
|
+
:key="column.key"
|
|
177
|
+
:class="
|
|
178
|
+
tdStyles({
|
|
179
|
+
align: column.align ?? 'left',
|
|
180
|
+
density: props.density,
|
|
181
|
+
last: index === props.rows.length - 1,
|
|
182
|
+
})
|
|
183
|
+
"
|
|
184
|
+
>
|
|
185
|
+
<slot
|
|
186
|
+
:name="`cell-${column.key}`"
|
|
187
|
+
:row="row"
|
|
188
|
+
:value="cellValue(row, column)"
|
|
189
|
+
:column="column"
|
|
190
|
+
:index="index"
|
|
191
|
+
>
|
|
192
|
+
{{ cellValue(row, column) }}
|
|
193
|
+
</slot>
|
|
194
|
+
</td>
|
|
195
|
+
</tr>
|
|
196
|
+
</template>
|
|
197
|
+
</tbody>
|
|
198
|
+
|
|
199
|
+
<tfoot v-if="props.summary">
|
|
200
|
+
<tr>
|
|
201
|
+
<td
|
|
202
|
+
v-if="props.selectable"
|
|
203
|
+
:class="
|
|
204
|
+
footerCellStyles({ align: 'left', density: props.density })
|
|
205
|
+
"
|
|
206
|
+
/>
|
|
207
|
+
<td
|
|
208
|
+
v-for="column in props.columns"
|
|
209
|
+
:key="column.key"
|
|
210
|
+
:class="
|
|
211
|
+
footerCellStyles({
|
|
212
|
+
align: column.align ?? 'left',
|
|
213
|
+
density: props.density,
|
|
214
|
+
})
|
|
215
|
+
"
|
|
216
|
+
>
|
|
217
|
+
<slot
|
|
218
|
+
:name="`summary-${column.key}`"
|
|
219
|
+
:column="column"
|
|
220
|
+
:value="props.summary?.[column.key]"
|
|
221
|
+
>
|
|
222
|
+
{{ props.summary?.[column.key] ?? "" }}
|
|
223
|
+
</slot>
|
|
224
|
+
</td>
|
|
225
|
+
</tr>
|
|
226
|
+
</tfoot>
|
|
227
|
+
</table>
|
|
228
|
+
</div>
|
|
229
|
+
</uixy-card>
|
|
230
|
+
|
|
231
|
+
<div v-if="props.pagination" :class="paginationBarStyles()">
|
|
232
|
+
<slot
|
|
233
|
+
name="pagination"
|
|
234
|
+
:pagination="props.pagination"
|
|
235
|
+
:page-count="pageCount"
|
|
236
|
+
:go-to-page="goToPage"
|
|
237
|
+
>
|
|
238
|
+
<span :class="paginationInfoStyles()">{{ paginationRangeText }}</span>
|
|
239
|
+
<div :class="paginationControlsStyles()">
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
:class="paginationButtonStyles()"
|
|
243
|
+
:disabled="props.pagination.page <= 1"
|
|
244
|
+
@click="goToPage(props.pagination.page - 1)"
|
|
245
|
+
>
|
|
246
|
+
<uixy-icon name="chevron-left" class="h-4 w-4" />
|
|
247
|
+
</button>
|
|
248
|
+
<template v-for="(item, i) in paginationItems" :key="i">
|
|
249
|
+
<span
|
|
250
|
+
v-if="item === 'ellipsis'"
|
|
251
|
+
:class="paginationEllipsisStyles()"
|
|
252
|
+
>
|
|
253
|
+
…
|
|
254
|
+
</span>
|
|
255
|
+
<button
|
|
256
|
+
v-else
|
|
257
|
+
type="button"
|
|
258
|
+
:class="
|
|
259
|
+
paginationButtonStyles({
|
|
260
|
+
active: item === props.pagination.page,
|
|
261
|
+
})
|
|
262
|
+
"
|
|
263
|
+
@click="goToPage(item)"
|
|
264
|
+
>
|
|
265
|
+
{{ item }}
|
|
266
|
+
</button>
|
|
267
|
+
</template>
|
|
268
|
+
<button
|
|
269
|
+
type="button"
|
|
270
|
+
:class="paginationButtonStyles()"
|
|
271
|
+
:disabled="props.pagination.page >= pageCount"
|
|
272
|
+
@click="goToPage(props.pagination.page + 1)"
|
|
273
|
+
>
|
|
274
|
+
<uixy-icon name="chevron-right" class="h-4 w-4" />
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
</slot>
|
|
278
|
+
</div>
|
|
279
|
+
</uixy-card>
|
|
280
|
+
</template>
|
|
281
|
+
|
|
282
|
+
<script
|
|
283
|
+
setup
|
|
284
|
+
lang="ts"
|
|
285
|
+
generic="TRow extends Record<string, any> = Record<string, any>"
|
|
286
|
+
>
|
|
287
|
+
import { UixyCard } from "../Card";
|
|
288
|
+
import { UixyCheckbox } from "../Checkbox";
|
|
289
|
+
import { UixyIcon } from "../Icon";
|
|
290
|
+
import { UixyPopover } from "../Popover";
|
|
291
|
+
import { UixySkeleton } from "../Skeleton";
|
|
292
|
+
import type {
|
|
293
|
+
UixyTableProps,
|
|
294
|
+
UixyTableEmits,
|
|
295
|
+
UixyTableSlots,
|
|
296
|
+
UixyTableColumn,
|
|
297
|
+
UixyTableSort,
|
|
298
|
+
} from "./Table.types";
|
|
299
|
+
import {
|
|
300
|
+
scrollAreaStyles,
|
|
301
|
+
tableStyles,
|
|
302
|
+
captionStyles,
|
|
303
|
+
thStyles,
|
|
304
|
+
thContentStyles,
|
|
305
|
+
thLabelStyles,
|
|
306
|
+
sortButtonStyles,
|
|
307
|
+
sortIconStyles,
|
|
308
|
+
filterTriggerStyles,
|
|
309
|
+
filterPanelStyles,
|
|
310
|
+
rowStyles,
|
|
311
|
+
tdStyles,
|
|
312
|
+
footerCellStyles,
|
|
313
|
+
emptyStyles,
|
|
314
|
+
paginationBarStyles,
|
|
315
|
+
paginationInfoStyles,
|
|
316
|
+
paginationControlsStyles,
|
|
317
|
+
paginationButtonStyles,
|
|
318
|
+
paginationEllipsisStyles,
|
|
319
|
+
} from "./Table.styles";
|
|
320
|
+
|
|
321
|
+
const props = withDefaults(defineProps<UixyTableProps<TRow>>(), {
|
|
322
|
+
rowKey: "id",
|
|
323
|
+
density: "comfortable",
|
|
324
|
+
sort: null,
|
|
325
|
+
selectedRowKeys: () => [],
|
|
326
|
+
loading: false,
|
|
327
|
+
selectable: false,
|
|
328
|
+
stickyHeader: false,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const emit = defineEmits<UixyTableEmits<TRow>>();
|
|
332
|
+
|
|
333
|
+
defineSlots<UixyTableSlots<TRow>>();
|
|
334
|
+
|
|
335
|
+
defineOptions({
|
|
336
|
+
inheritAttrs: false,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const attrs = useAttrs();
|
|
340
|
+
const slots = useSlots();
|
|
341
|
+
const filterGroupId = useId();
|
|
342
|
+
|
|
343
|
+
const filteredAttrs = computed(() => {
|
|
344
|
+
const { class: _cls, ...rest } = attrs;
|
|
345
|
+
return rest;
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const cardClass = computed(() =>
|
|
349
|
+
["w-full p-1 bg-gray-100 dark:bg-black", attrs.class]
|
|
350
|
+
.filter(Boolean)
|
|
351
|
+
.join(" "),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const totalColumnCount = computed(
|
|
355
|
+
() => props.columns.length + (props.selectable ? 1 : 0),
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
function keyOf(row: TRow, index: number): string | number {
|
|
359
|
+
const rk = props.rowKey;
|
|
360
|
+
const value =
|
|
361
|
+
typeof rk === "function"
|
|
362
|
+
? rk(row)
|
|
363
|
+
: (row as Record<string, unknown>)[(rk ?? "id") as string];
|
|
364
|
+
return (value as string | number | undefined) ?? index;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function cellValue(row: TRow, column: UixyTableColumn<TRow>): unknown {
|
|
368
|
+
if (column.accessor) return column.accessor(row);
|
|
369
|
+
return (row as Record<string, unknown>)[column.key];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function filterFor(column: UixyTableColumn<TRow>) {
|
|
373
|
+
return props.filters?.find((filter) => filter.key === column.key);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function hasFilterSlot(column: UixyTableColumn<TRow>) {
|
|
377
|
+
return !!slots[`filter-${column.key}`];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function isFilterActive(column: UixyTableColumn<TRow>) {
|
|
381
|
+
return !!filterFor(column)?.active;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function isSorted(column: UixyTableColumn<TRow>) {
|
|
385
|
+
return props.sort?.key === column.key;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function sortIconName(column: UixyTableColumn<TRow>) {
|
|
389
|
+
if (!isSorted(column)) return "chevrons-vertical" as const;
|
|
390
|
+
return props.sort?.direction === "asc"
|
|
391
|
+
? ("chevron-up" as const)
|
|
392
|
+
: ("chevron-down" as const);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function handleSort(column: UixyTableColumn<TRow>) {
|
|
396
|
+
if (!column.sortable) return;
|
|
397
|
+
|
|
398
|
+
const current = props.sort;
|
|
399
|
+
let next: UixyTableSort<TRow> | null;
|
|
400
|
+
|
|
401
|
+
if (!current || current.key !== column.key) {
|
|
402
|
+
next = { key: column.key, direction: "asc" };
|
|
403
|
+
} else if (current.direction === "asc") {
|
|
404
|
+
next = { key: column.key, direction: "desc" };
|
|
405
|
+
} else {
|
|
406
|
+
next = null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
emit("sortChange", next);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const selectedKeySet = computed(() => new Set(props.selectedRowKeys ?? []));
|
|
413
|
+
|
|
414
|
+
function isSelected(row: TRow, index: number) {
|
|
415
|
+
return selectedKeySet.value.has(keyOf(row, index));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const allSelected = computed(
|
|
419
|
+
() =>
|
|
420
|
+
props.rows.length > 0 &&
|
|
421
|
+
props.rows.every((row, index) => isSelected(row, index)),
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const someSelected = computed(
|
|
425
|
+
() =>
|
|
426
|
+
!allSelected.value &&
|
|
427
|
+
props.rows.some((row, index) => isSelected(row, index)),
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
function toggleRow(row: TRow, index: number, checked: boolean | undefined) {
|
|
431
|
+
const key = keyOf(row, index);
|
|
432
|
+
const next = new Set(selectedKeySet.value);
|
|
433
|
+
|
|
434
|
+
if (checked) next.add(key);
|
|
435
|
+
else next.delete(key);
|
|
436
|
+
|
|
437
|
+
emit("selectionChange", Array.from(next));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function toggleSelectAll(checked: boolean | undefined) {
|
|
441
|
+
const next = new Set(selectedKeySet.value);
|
|
442
|
+
|
|
443
|
+
props.rows.forEach((row, index) => {
|
|
444
|
+
const key = keyOf(row, index);
|
|
445
|
+
if (checked) next.add(key);
|
|
446
|
+
else next.delete(key);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
emit("selectionChange", Array.from(next));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const skeletonRowCount = computed(() =>
|
|
453
|
+
Math.min(props.pagination?.pageSize ?? 8, 10),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const pageCount = computed(() => {
|
|
457
|
+
if (!props.pagination) return 1;
|
|
458
|
+
return Math.max(
|
|
459
|
+
1,
|
|
460
|
+
Math.ceil(props.pagination.total / props.pagination.pageSize),
|
|
461
|
+
);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const paginationRangeText = computed(() => {
|
|
465
|
+
if (!props.pagination) return "";
|
|
466
|
+
|
|
467
|
+
const { page, pageSize, total } = props.pagination;
|
|
468
|
+
if (total === 0) return "0 results";
|
|
469
|
+
|
|
470
|
+
const from = (page - 1) * pageSize + 1;
|
|
471
|
+
const to = Math.min(page * pageSize, total);
|
|
472
|
+
|
|
473
|
+
return `${from}–${to} of ${total}`;
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
function goToPage(page: number) {
|
|
477
|
+
if (!props.pagination) return;
|
|
478
|
+
|
|
479
|
+
const clamped = Math.min(Math.max(page, 1), pageCount.value);
|
|
480
|
+
|
|
481
|
+
if (clamped === props.pagination.page) return;
|
|
482
|
+
|
|
483
|
+
emit("pageChange", clamped);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
type PaginationItem = number | "ellipsis";
|
|
487
|
+
|
|
488
|
+
const paginationItems = computed<PaginationItem[]>(() => {
|
|
489
|
+
const count = pageCount.value;
|
|
490
|
+
const current = props.pagination?.page ?? 1;
|
|
491
|
+
const siblings = 1;
|
|
492
|
+
const items: PaginationItem[] = [1];
|
|
493
|
+
|
|
494
|
+
const start = Math.max(2, current - siblings);
|
|
495
|
+
const end = Math.min(count - 1, current + siblings);
|
|
496
|
+
|
|
497
|
+
if (start > 2) items.push("ellipsis");
|
|
498
|
+
|
|
499
|
+
for (let i = start; i <= end; i++) items.push(i);
|
|
500
|
+
|
|
501
|
+
if (end < count - 1) items.push("ellipsis");
|
|
502
|
+
|
|
503
|
+
if (count > 1) items.push(count);
|
|
504
|
+
|
|
505
|
+
return items;
|
|
506
|
+
});
|
|
507
|
+
</script>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import styles from "~/utils/styles";
|
|
2
|
+
|
|
3
|
+
export const scrollAreaStyles = styles("scrollbar w-full overflow-x-auto", {
|
|
4
|
+
scrollY: {
|
|
5
|
+
true: "overflow-y-auto",
|
|
6
|
+
false: "",
|
|
7
|
+
},
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const tableStyles = styles(
|
|
11
|
+
"w-full caption-bottom border-separate border-spacing-0 text-xs",
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export const captionStyles = styles(
|
|
15
|
+
"px-4 py-3 text-left text-xs text-gray-600 dark:text-gray-500",
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const thStyles = styles(
|
|
19
|
+
"whitespace-nowrap border-b border-gray-300 bg-white align-middle text-xs font-500 text-gray-600 dark:border-gray-900 dark:bg-gray-1000 dark:text-gray-500",
|
|
20
|
+
{
|
|
21
|
+
align: {
|
|
22
|
+
left: "text-left",
|
|
23
|
+
center: "text-center",
|
|
24
|
+
right: "text-right",
|
|
25
|
+
},
|
|
26
|
+
density: {
|
|
27
|
+
compact: "h-9 px-3",
|
|
28
|
+
comfortable: "h-10 px-4",
|
|
29
|
+
},
|
|
30
|
+
sticky: {
|
|
31
|
+
true: "sticky top-0 z-10",
|
|
32
|
+
false: "",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export const thContentStyles = styles("flex w-full items-center gap-2");
|
|
38
|
+
|
|
39
|
+
export const thLabelStyles = styles("flex min-w-0 flex-1 items-center gap-1", {
|
|
40
|
+
align: {
|
|
41
|
+
left: "justify-start",
|
|
42
|
+
center: "justify-center",
|
|
43
|
+
right: "justify-end",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const sortButtonStyles = styles(
|
|
48
|
+
"inline-flex cursor-pointer select-none items-center gap-1 transition-colors hover:text-black dark:hover:text-white",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export const sortIconStyles = styles(
|
|
52
|
+
"h-3.5 w-3.5 shrink-0 text-gray-500 dark:text-gray-600",
|
|
53
|
+
{
|
|
54
|
+
active: {
|
|
55
|
+
true: "text-black dark:text-white",
|
|
56
|
+
false: "",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
export const filterTriggerStyles = styles(
|
|
62
|
+
"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-1 transition-colors hover:bg-gray-200 dark:hover:bg-gray-800",
|
|
63
|
+
{
|
|
64
|
+
active: {
|
|
65
|
+
true: "text-black dark:text-white",
|
|
66
|
+
false: "text-gray-500 dark:text-gray-600",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
export const filterPanelStyles = styles("flex w-56 flex-col gap-2 p-2");
|
|
72
|
+
|
|
73
|
+
export const rowStyles = styles("transition-colors", {
|
|
74
|
+
selected: {
|
|
75
|
+
true: "bg-gray-100 dark:bg-gray-900",
|
|
76
|
+
false: "hover:bg-gray-100 dark:hover:bg-gray-900",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const tdStyles = styles(
|
|
81
|
+
"border-b border-gray-300 align-middle text-gray-900 dark:border-gray-900 dark:text-gray-100",
|
|
82
|
+
{
|
|
83
|
+
align: {
|
|
84
|
+
left: "text-left",
|
|
85
|
+
center: "text-center",
|
|
86
|
+
right: "text-right",
|
|
87
|
+
},
|
|
88
|
+
density: {
|
|
89
|
+
compact: "px-3 py-2",
|
|
90
|
+
comfortable: "px-4 py-3",
|
|
91
|
+
},
|
|
92
|
+
last: {
|
|
93
|
+
true: "border-b-0",
|
|
94
|
+
false: "",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
export const footerCellStyles = styles(
|
|
100
|
+
"border-t border-gray-300 bg-gray-100 align-middle font-500 text-gray-800 dark:border-gray-900 dark:bg-gray-900 dark:text-gray-300",
|
|
101
|
+
{
|
|
102
|
+
align: {
|
|
103
|
+
left: "text-left",
|
|
104
|
+
center: "text-center",
|
|
105
|
+
right: "text-right",
|
|
106
|
+
},
|
|
107
|
+
density: {
|
|
108
|
+
compact: "px-3 py-2",
|
|
109
|
+
comfortable: "px-4 py-3",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
export const emptyStyles = styles(
|
|
115
|
+
"flex flex-col items-center justify-center gap-2 py-16 text-xs text-gray-500 dark:text-gray-500",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
export const paginationBarStyles = styles(
|
|
119
|
+
"flex flex-wrap items-center justify-between gap-3 px-2 pt-1 pb-0 text-xs",
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
export const paginationInfoStyles = styles(
|
|
123
|
+
"text-xs text-gray-600 dark:text-gray-500",
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
export const paginationControlsStyles = styles("flex items-center gap-1");
|
|
127
|
+
|
|
128
|
+
export const paginationButtonStyles = styles(
|
|
129
|
+
"flex h-6 min-w-6 cursor-pointer items-center justify-center rounded-1 px-1.5 text-xs transition-colors hover:bg-gray-200 disabled:pointer-events-none disabled:cursor-default disabled:text-gray-400 dark:hover:bg-gray-800 dark:disabled:text-gray-700",
|
|
130
|
+
{
|
|
131
|
+
active: {
|
|
132
|
+
true: "bg-gray-800 text-gray-100 hover:bg-gray-700 dark:bg-gray-300 dark:text-black dark:hover:bg-gray-200",
|
|
133
|
+
false: "text-gray-800 dark:text-gray-300",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
export const paginationEllipsisStyles = styles(
|
|
139
|
+
"flex h-6 min-w-6 items-center justify-center text-gray-500 dark:text-gray-600",
|
|
140
|
+
);
|