@urbicon-ui/table 6.1.4
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 +153 -0
- package/dist/cells/ActionButtons.svelte +224 -0
- package/dist/cells/ActionButtons.svelte.d.ts +74 -0
- package/dist/cells/CopyButton.svelte +89 -0
- package/dist/cells/CopyButton.svelte.d.ts +33 -0
- package/dist/cells/CustomCell.svelte +136 -0
- package/dist/cells/CustomCell.svelte.d.ts +44 -0
- package/dist/cells/DateCell.svelte +194 -0
- package/dist/cells/DateCell.svelte.d.ts +39 -0
- package/dist/cells/LinkCell.svelte +240 -0
- package/dist/cells/LinkCell.svelte.d.ts +42 -0
- package/dist/cells/NumberCell.svelte +225 -0
- package/dist/cells/NumberCell.svelte.d.ts +47 -0
- package/dist/cells/StatusBadge.svelte +121 -0
- package/dist/cells/StatusBadge.svelte.d.ts +44 -0
- package/dist/cells/UserAvatar.svelte +71 -0
- package/dist/cells/UserAvatar.svelte.d.ts +37 -0
- package/dist/cells/index.d.ts +8 -0
- package/dist/cells/index.js +9 -0
- package/dist/core/EmptyState.svelte +161 -0
- package/dist/core/EmptyState.svelte.d.ts +16 -0
- package/dist/core/ErrorState.svelte +158 -0
- package/dist/core/ErrorState.svelte.d.ts +15 -0
- package/dist/core/GroupedRow.svelte +239 -0
- package/dist/core/GroupedRow.svelte.d.ts +18 -0
- package/dist/core/LoadingState.svelte +75 -0
- package/dist/core/LoadingState.svelte.d.ts +14 -0
- package/dist/core/MobileCard.svelte +151 -0
- package/dist/core/MobileCard.svelte.d.ts +15 -0
- package/dist/core/TableCell.svelte +105 -0
- package/dist/core/TableCell.svelte.d.ts +14 -0
- package/dist/core/TableDesktop.svelte +480 -0
- package/dist/core/TableDesktop.svelte.d.ts +26 -0
- package/dist/core/TableHead.svelte +314 -0
- package/dist/core/TableHead.svelte.d.ts +7 -0
- package/dist/core/TableMobile.svelte +112 -0
- package/dist/core/TableMobile.svelte.d.ts +13 -0
- package/dist/core/TableProvider.svelte +271 -0
- package/dist/core/TableProvider.svelte.d.ts +40 -0
- package/dist/core/TableRow.svelte +171 -0
- package/dist/core/TableRow.svelte.d.ts +16 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +14 -0
- package/dist/core/sticky-context.svelte.d.ts +48 -0
- package/dist/core/sticky-context.svelte.js +88 -0
- package/dist/core/table/Table.svelte +304 -0
- package/dist/core/table/Table.svelte.d.ts +26 -0
- package/dist/core/table/index.d.ts +448 -0
- package/dist/core/table/index.js +1 -0
- package/dist/core/table-style-context.d.ts +66 -0
- package/dist/core/table-style-context.js +26 -0
- package/dist/factories/ColumnValidation.d.ts +49 -0
- package/dist/factories/ColumnValidation.js +188 -0
- package/dist/factories/TableColumns.d.ts +97 -0
- package/dist/factories/TableColumns.js +262 -0
- package/dist/factories/TypedColumnBuilder.d.ts +41 -0
- package/dist/factories/TypedColumnBuilder.js +72 -0
- package/dist/factories/index.d.ts +12 -0
- package/dist/factories/index.js +13 -0
- package/dist/features/HeaderMenu.svelte +236 -0
- package/dist/features/HeaderMenu.svelte.d.ts +8 -0
- package/dist/features/LiveUpdateBanner.svelte +66 -0
- package/dist/features/LiveUpdateBanner.svelte.d.ts +6 -0
- package/dist/features/SearchHighlight.svelte +21 -0
- package/dist/features/SearchHighlight.svelte.d.ts +8 -0
- package/dist/features/SmartFilterBar/ChipsField.svelte +104 -0
- package/dist/features/SmartFilterBar/ChipsField.svelte.d.ts +5 -0
- package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte +84 -0
- package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/FilterMenu.svelte +367 -0
- package/dist/features/SmartFilterBar/FilterMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/GroupingMenu.svelte +82 -0
- package/dist/features/SmartFilterBar/GroupingMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/SmartFilterBar.svelte +109 -0
- package/dist/features/SmartFilterBar/SmartFilterBar.svelte.d.ts +11 -0
- package/dist/features/SmartFilterBar/SummaryMenu.svelte +118 -0
- package/dist/features/SmartFilterBar/SummaryMenu.svelte.d.ts +3 -0
- package/dist/features/SummaryRow.svelte +97 -0
- package/dist/features/SummaryRow.svelte.d.ts +8 -0
- package/dist/features/index.d.ts +4 -0
- package/dist/features/index.js +4 -0
- package/dist/i18n/index.d.ts +366 -0
- package/dist/i18n/index.js +21 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +41 -0
- package/dist/stores/TableStore.svelte.d.ts +192 -0
- package/dist/stores/TableStore.svelte.js +362 -0
- package/dist/stores/concerns/index.d.ts +15 -0
- package/dist/stores/concerns/index.js +14 -0
- package/dist/stores/concerns/types.d.ts +31 -0
- package/dist/stores/concerns/types.js +1 -0
- package/dist/stores/concerns/useColumnOrder.svelte.d.ts +16 -0
- package/dist/stores/concerns/useColumnOrder.svelte.js +81 -0
- package/dist/stores/concerns/useColumnVisibility.svelte.d.ts +16 -0
- package/dist/stores/concerns/useColumnVisibility.svelte.js +58 -0
- package/dist/stores/concerns/useExpansion.svelte.d.ts +9 -0
- package/dist/stores/concerns/useExpansion.svelte.js +32 -0
- package/dist/stores/concerns/useFiltering.svelte.d.ts +20 -0
- package/dist/stores/concerns/useFiltering.svelte.js +109 -0
- package/dist/stores/concerns/useFocusManagement.svelte.d.ts +15 -0
- package/dist/stores/concerns/useFocusManagement.svelte.js +52 -0
- package/dist/stores/concerns/useGrouping.svelte.d.ts +15 -0
- package/dist/stores/concerns/useGrouping.svelte.js +86 -0
- package/dist/stores/concerns/useLiveUpdates.svelte.d.ts +45 -0
- package/dist/stores/concerns/useLiveUpdates.svelte.js +175 -0
- package/dist/stores/concerns/usePagination.svelte.d.ts +18 -0
- package/dist/stores/concerns/usePagination.svelte.js +54 -0
- package/dist/stores/concerns/usePersistence.svelte.d.ts +36 -0
- package/dist/stores/concerns/usePersistence.svelte.js +167 -0
- package/dist/stores/concerns/useRemoteData.svelte.d.ts +21 -0
- package/dist/stores/concerns/useRemoteData.svelte.js +64 -0
- package/dist/stores/concerns/useSearch.svelte.d.ts +8 -0
- package/dist/stores/concerns/useSearch.svelte.js +16 -0
- package/dist/stores/concerns/useSelection.svelte.d.ts +21 -0
- package/dist/stores/concerns/useSelection.svelte.js +110 -0
- package/dist/stores/concerns/useSorting.svelte.d.ts +11 -0
- package/dist/stores/concerns/useSorting.svelte.js +70 -0
- package/dist/stores/concerns/useSummary.svelte.d.ts +18 -0
- package/dist/stores/concerns/useSummary.svelte.js +96 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.js +1 -0
- package/dist/style/index.css +137 -0
- package/dist/style/index.d.ts +2 -0
- package/dist/style/index.js +2 -0
- package/dist/style/table-theme.css +131 -0
- package/dist/style/themes/comfortable.css +20 -0
- package/dist/style/themes/compact.css +20 -0
- package/dist/translations/de.d.ts +177 -0
- package/dist/translations/de.js +176 -0
- package/dist/translations/en.d.ts +177 -0
- package/dist/translations/en.js +176 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/tableTypes.d.ts +262 -0
- package/dist/types/tableTypes.js +1 -0
- package/dist/utils/index.d.ts +165 -0
- package/dist/utils/index.js +330 -0
- package/dist/utils/sticky-measure.d.ts +54 -0
- package/dist/utils/sticky-measure.js +107 -0
- package/dist/utils/virtualizer.d.ts +43 -0
- package/dist/utils/virtualizer.js +43 -0
- package/dist/variants/index.d.ts +11 -0
- package/dist/variants/index.js +15 -0
- package/dist/variants/table-cells.variants.d.ts +827 -0
- package/dist/variants/table-cells.variants.js +627 -0
- package/dist/variants/table-features.variants.d.ts +547 -0
- package/dist/variants/table-features.variants.js +412 -0
- package/dist/variants/table-states.variants.d.ts +594 -0
- package/dist/variants/table-states.variants.js +394 -0
- package/dist/variants/table.system.d.ts +301 -0
- package/dist/variants/table.system.js +314 -0
- package/dist/variants/table.variants.d.ts +428 -0
- package/dist/variants/table.variants.js +360 -0
- package/package.json +93 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
resolveIcon,
|
|
4
|
+
Checkbox,
|
|
5
|
+
createDraggable,
|
|
6
|
+
findDropTarget,
|
|
7
|
+
ChevronDownIcon as ChevronDownIconDefault,
|
|
8
|
+
ChevronUpIcon as ChevronUpIconDefault
|
|
9
|
+
} from '@urbicon-ui/blocks';
|
|
10
|
+
import { getTableContext } from '../stores/TableStore.svelte';
|
|
11
|
+
import { resolveColumnId } from '../utils';
|
|
12
|
+
|
|
13
|
+
const ChevronDownIcon = resolveIcon('chevronDown', ChevronDownIconDefault);
|
|
14
|
+
const ChevronUpIcon = resolveIcon('chevronUp', ChevronUpIconDefault);
|
|
15
|
+
import HeaderMenu from '../features/HeaderMenu.svelte';
|
|
16
|
+
import { useTableI18n } from '../i18n';
|
|
17
|
+
import { tableHeaderVariants, headerIndicatorVariants } from '../variants';
|
|
18
|
+
import { TABLE_INDICATORS, TABLE_RESPONSIVE } from '../variants/table.system';
|
|
19
|
+
import { getTableStyleConfig, resolveSlotClass } from './table-style-context';
|
|
20
|
+
import { getStickyContext } from './sticky-context.svelte';
|
|
21
|
+
import { measureToCssVar } from '../utils/sticky-measure';
|
|
22
|
+
|
|
23
|
+
const tt = useTableI18n();
|
|
24
|
+
|
|
25
|
+
let { expandable = false, enableColumnReorder = false, size = 'md' as const } = $props();
|
|
26
|
+
|
|
27
|
+
const tableContext = getTableContext();
|
|
28
|
+
const { state: tableState, handleSort, toggleAllGroups } = tableContext;
|
|
29
|
+
const styleConfig = getTableStyleConfig();
|
|
30
|
+
const stickyContext = getStickyContext();
|
|
31
|
+
|
|
32
|
+
let selectable = $derived(tableState.selectionMode !== 'none');
|
|
33
|
+
let multiSelect = $derived(tableState.selectionMode === 'multi');
|
|
34
|
+
|
|
35
|
+
// Column reorder state
|
|
36
|
+
let dragFromIndex = $state<number | null>(null);
|
|
37
|
+
let dropIndicatorIndex = $state<number | null>(null);
|
|
38
|
+
|
|
39
|
+
// Use orderedColumns when column reorder is enabled
|
|
40
|
+
const displayColumns = $derived(
|
|
41
|
+
enableColumnReorder ? tableContext.orderedColumns : tableState.columns
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
function makeDraggable(colIndex: number) {
|
|
45
|
+
if (!enableColumnReorder) {
|
|
46
|
+
// No-op attachment when reorder is disabled.
|
|
47
|
+
return () => {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return createDraggable({
|
|
51
|
+
axis: 'horizontal',
|
|
52
|
+
threshold: 8,
|
|
53
|
+
cursor: 'grabbing',
|
|
54
|
+
disabled: !enableColumnReorder,
|
|
55
|
+
onDragStart: ({ element }) => {
|
|
56
|
+
dragFromIndex = colIndex;
|
|
57
|
+
element.style.opacity = '0.5';
|
|
58
|
+
},
|
|
59
|
+
onDragMove: ({ clientX, clientY }) => {
|
|
60
|
+
const target = findDropTarget(clientX, clientY, 'reorderCol');
|
|
61
|
+
if (target) {
|
|
62
|
+
dropIndicatorIndex = Number(target.dataset.reorderCol);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
onDragEnd: ({ element, didDrag }) => {
|
|
66
|
+
element.style.opacity = '';
|
|
67
|
+
if (
|
|
68
|
+
didDrag &&
|
|
69
|
+
dragFromIndex !== null &&
|
|
70
|
+
dropIndicatorIndex !== null &&
|
|
71
|
+
dragFromIndex !== dropIndicatorIndex
|
|
72
|
+
) {
|
|
73
|
+
tableContext.reorderColumn(dragFromIndex, dropIndicatorIndex);
|
|
74
|
+
}
|
|
75
|
+
dragFromIndex = null;
|
|
76
|
+
dropIndicatorIndex = null;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleHeaderKeyDown(e: KeyboardEvent, colIndex: number) {
|
|
82
|
+
if (!enableColumnReorder) return;
|
|
83
|
+
if (!e.shiftKey) return;
|
|
84
|
+
|
|
85
|
+
if (e.key === 'ArrowLeft' && colIndex > 0) {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
tableContext.reorderColumn(colIndex, colIndex - 1);
|
|
88
|
+
} else if (e.key === 'ArrowRight' && colIndex < displayColumns.length - 1) {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
tableContext.reorderColumn(colIndex, colIndex + 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function hasActiveFilter(columnKey: string): boolean {
|
|
95
|
+
return tableState.activeFilters.some((filter) => filter.column === columnKey);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isGroupedColumn(columnKey: string): boolean {
|
|
99
|
+
return tableState.groupByKey === columnKey;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function hasSummary(columnKey: string): boolean {
|
|
103
|
+
return tableState.summaryConfigs.some((config) => config.column === columnKey);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getSummaryTypes(columnKey: string): string[] {
|
|
107
|
+
return tableState.summaryConfigs
|
|
108
|
+
.filter((config) => config.column === columnKey)
|
|
109
|
+
.map((config) => {
|
|
110
|
+
switch (config.type) {
|
|
111
|
+
case 'sum':
|
|
112
|
+
return '∑';
|
|
113
|
+
case 'avg':
|
|
114
|
+
return '⌀';
|
|
115
|
+
case 'count':
|
|
116
|
+
return '#';
|
|
117
|
+
case 'min':
|
|
118
|
+
return '↓';
|
|
119
|
+
case 'max':
|
|
120
|
+
return '↑';
|
|
121
|
+
default:
|
|
122
|
+
return '?';
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getActionIndicators(columnKey: string) {
|
|
128
|
+
const indicators: Array<{ type: 'filter' | 'group' | 'summary' }> = [];
|
|
129
|
+
if (hasActiveFilter(columnKey)) indicators.push({ type: 'filter' });
|
|
130
|
+
if (isGroupedColumn(columnKey)) indicators.push({ type: 'group' });
|
|
131
|
+
if (hasSummary(columnKey)) indicators.push({ type: 'summary' });
|
|
132
|
+
return indicators;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const INDICATOR_BG: Record<string, string> = {
|
|
136
|
+
filter: TABLE_INDICATORS.dot.intent.filter,
|
|
137
|
+
group: TABLE_INDICATORS.dot.intent.group,
|
|
138
|
+
summary: TABLE_INDICATORS.dot.intent.summary
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const headerStyles = $derived(tableHeaderVariants({ size, sticky: stickyContext.mode.header }));
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<thead
|
|
145
|
+
class={resolveSlotClass(
|
|
146
|
+
headerStyles.header(),
|
|
147
|
+
styleConfig.slotClasses.thead,
|
|
148
|
+
styleConfig.unstyled
|
|
149
|
+
)}
|
|
150
|
+
{@attach stickyContext.mode.header ? measureToCssVar('--blocks-table-thead-h') : () => {}}
|
|
151
|
+
>
|
|
152
|
+
<tr
|
|
153
|
+
class={resolveSlotClass(
|
|
154
|
+
headerStyles.row(),
|
|
155
|
+
styleConfig.slotClasses.headerRow,
|
|
156
|
+
styleConfig.unstyled
|
|
157
|
+
)}
|
|
158
|
+
>
|
|
159
|
+
{#if tableState.groupByKey}
|
|
160
|
+
<th class="{headerStyles.cell()} w-10 text-center">
|
|
161
|
+
<button
|
|
162
|
+
onclick={() => toggleAllGroups()}
|
|
163
|
+
class="text-text-secondary hover:text-text-primary hover:bg-surface-hover rounded-modify flex h-6 w-6 items-center justify-center transition-colors"
|
|
164
|
+
aria-label={tableState.allGroupsExpanded
|
|
165
|
+
? tt('header.collapseAllGroups')
|
|
166
|
+
: tt('header.expandAllGroups')}
|
|
167
|
+
data-testid="toggle-all-groups"
|
|
168
|
+
>
|
|
169
|
+
{#if tableState.allGroupsExpanded}
|
|
170
|
+
<ChevronUpIcon class="h-4 w-4" />
|
|
171
|
+
{:else}
|
|
172
|
+
<ChevronDownIcon class="h-4 w-4" />
|
|
173
|
+
{/if}
|
|
174
|
+
</button>
|
|
175
|
+
</th>
|
|
176
|
+
{/if}
|
|
177
|
+
|
|
178
|
+
{#if selectable}
|
|
179
|
+
<th class="{headerStyles.cell()} w-12 text-center" data-testid="selection-header">
|
|
180
|
+
{#if multiSelect}
|
|
181
|
+
<Checkbox
|
|
182
|
+
checked={tableContext.allSelected}
|
|
183
|
+
indeterminate={tableContext.someSelected}
|
|
184
|
+
onchange={() => tableContext.toggleAll()}
|
|
185
|
+
aria-label={tableContext.allSelected
|
|
186
|
+
? tt('selection.deselectAllRows')
|
|
187
|
+
: tt('selection.selectAllRows')}
|
|
188
|
+
size="sm"
|
|
189
|
+
/>
|
|
190
|
+
{/if}
|
|
191
|
+
</th>
|
|
192
|
+
{/if}
|
|
193
|
+
|
|
194
|
+
{#if expandable}
|
|
195
|
+
<th class="{headerStyles.cell()} w-10 text-center" aria-hidden="true"></th>
|
|
196
|
+
{/if}
|
|
197
|
+
|
|
198
|
+
{#each displayColumns as column, colIdx (resolveColumnId(column))}
|
|
199
|
+
{@const columnId = resolveColumnId(column)}
|
|
200
|
+
{@const hasFilter = hasActiveFilter(columnId)}
|
|
201
|
+
{@const isGrouped = isGroupedColumn(columnId)}
|
|
202
|
+
{@const columnHasSummary = hasSummary(columnId)}
|
|
203
|
+
{@const summaryTypes = getSummaryTypes(columnId)}
|
|
204
|
+
{@const actionIndicators = getActionIndicators(columnId)}
|
|
205
|
+
{@const isActiveSorted = tableState.sortColumn === columnId}
|
|
206
|
+
{@const sortedState = isActiveSorted ? tableState.sortDirection : 'none'}
|
|
207
|
+
{@const isSortable =
|
|
208
|
+
column.accessor !== undefined && (column.sortable === undefined || column.sortable)}
|
|
209
|
+
{@const columnStyles = tableHeaderVariants({
|
|
210
|
+
size,
|
|
211
|
+
sortable: isSortable,
|
|
212
|
+
sorted: sortedState
|
|
213
|
+
})}
|
|
214
|
+
{@const isDragOver =
|
|
215
|
+
dropIndicatorIndex === colIdx && dragFromIndex !== null && dragFromIndex !== colIdx}
|
|
216
|
+
|
|
217
|
+
<th
|
|
218
|
+
{@attach makeDraggable(colIdx)}
|
|
219
|
+
style={column.width
|
|
220
|
+
? `width: ${column.width}; min-width: ${column.minWidth || '4rem'};`
|
|
221
|
+
: ''}
|
|
222
|
+
class="{columnStyles.cell()} whitespace-nowrap {column.flex
|
|
223
|
+
? 'flex-col'
|
|
224
|
+
: ''} {column.priority
|
|
225
|
+
? (TABLE_RESPONSIVE.priority[column.priority as keyof typeof TABLE_RESPONSIVE.priority] ??
|
|
226
|
+
'')
|
|
227
|
+
: ''} {enableColumnReorder ? 'cursor-grab' : ''} {isDragOver
|
|
228
|
+
? 'outline-primary outline outline-2 outline-offset-[-2px]'
|
|
229
|
+
: ''}"
|
|
230
|
+
aria-sort={isActiveSorted
|
|
231
|
+
? tableState.sortDirection === 'asc'
|
|
232
|
+
? 'ascending'
|
|
233
|
+
: 'descending'
|
|
234
|
+
: 'none'}
|
|
235
|
+
onkeydown={(e) => handleHeaderKeyDown(e, colIdx)}
|
|
236
|
+
data-reorder-col={colIdx}
|
|
237
|
+
data-testid={`column-header-${columnId}`}
|
|
238
|
+
>
|
|
239
|
+
<div class={columnStyles.cellContent()}>
|
|
240
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
241
|
+
<div
|
|
242
|
+
class="{columnStyles.titleContainer()} {isSortable ? 'cursor-pointer' : ''}"
|
|
243
|
+
onclick={() => isSortable && handleSort(columnId)}
|
|
244
|
+
role={isSortable ? 'button' : undefined}
|
|
245
|
+
tabindex={isSortable ? 0 : undefined}
|
|
246
|
+
onkeydown={(e) => {
|
|
247
|
+
if (isSortable && (e.key === 'Enter' || e.key === ' ')) {
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
handleSort(columnId);
|
|
250
|
+
}
|
|
251
|
+
}}
|
|
252
|
+
>
|
|
253
|
+
<div class={columnStyles.titleContent()}>
|
|
254
|
+
<span class={columnStyles.title()}>{column.title}</span>
|
|
255
|
+
|
|
256
|
+
<div class={columnStyles.indicators()}>
|
|
257
|
+
{#if hasFilter}
|
|
258
|
+
<div
|
|
259
|
+
class={headerIndicatorVariants({ type: 'filter', state: 'default' })}
|
|
260
|
+
title={tt('header.activeFilter')}
|
|
261
|
+
data-testid={`filter-indicator-${columnId}`}
|
|
262
|
+
></div>
|
|
263
|
+
{/if}
|
|
264
|
+
|
|
265
|
+
{#if isGrouped}
|
|
266
|
+
<div
|
|
267
|
+
class={headerIndicatorVariants({ type: 'group', state: 'default' })}
|
|
268
|
+
title={tt('header.groupedColumn')}
|
|
269
|
+
data-testid={`group-indicator-${columnId}`}
|
|
270
|
+
></div>
|
|
271
|
+
{/if}
|
|
272
|
+
|
|
273
|
+
{#if columnHasSummary}
|
|
274
|
+
<div
|
|
275
|
+
class={headerIndicatorVariants({ type: 'summary', state: 'default' })}
|
|
276
|
+
title={tt('header.summarizedColumn') + ': ' + summaryTypes.join(', ')}
|
|
277
|
+
data-testid={`summary-indicator-${columnId}`}
|
|
278
|
+
></div>
|
|
279
|
+
{/if}
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{#if isActiveSorted}
|
|
284
|
+
<span class={columnStyles.sortIcon()} aria-hidden="true">
|
|
285
|
+
{#if tableState.sortDirection === 'asc'}
|
|
286
|
+
<ChevronUpIcon class="h-4 w-4" />
|
|
287
|
+
{:else}
|
|
288
|
+
<ChevronDownIcon class="h-4 w-4" />
|
|
289
|
+
{/if}
|
|
290
|
+
</span>
|
|
291
|
+
{/if}
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<HeaderMenu {column} isActive={isActiveSorted || isGrouped || columnHasSummary} />
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
{#if actionIndicators.length > 0}
|
|
298
|
+
<div
|
|
299
|
+
class={columnStyles.actionIndicators()}
|
|
300
|
+
data-testid={`action-indicators-${columnId}`}
|
|
301
|
+
>
|
|
302
|
+
{#each actionIndicators as indicator (indicator.type)}
|
|
303
|
+
<div
|
|
304
|
+
class="{columnStyles.actionIndicatorBar()} {INDICATOR_BG[indicator.type]}"
|
|
305
|
+
title={tt('header.activeIndicator', { type: indicator.type })}
|
|
306
|
+
data-action={indicator.type}
|
|
307
|
+
></div>
|
|
308
|
+
{/each}
|
|
309
|
+
</div>
|
|
310
|
+
{/if}
|
|
311
|
+
</th>
|
|
312
|
+
{/each}
|
|
313
|
+
</tr>
|
|
314
|
+
</thead>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getTableContext } from '../stores/TableStore.svelte';
|
|
3
|
+
import { useTableI18n } from '../i18n';
|
|
4
|
+
import MobileCard from './MobileCard.svelte';
|
|
5
|
+
import { resolveColumnId } from '../utils';
|
|
6
|
+
import type { Column, TableItem } from '../types/tableTypes';
|
|
7
|
+
import type { Snippet } from 'svelte';
|
|
8
|
+
|
|
9
|
+
const tt = useTableI18n();
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
size = 'md' as 'sm' | 'md' | 'lg',
|
|
13
|
+
expandable = false,
|
|
14
|
+
expandedRowContent = undefined as Snippet<[item: TableItem]> | undefined,
|
|
15
|
+
cell = undefined as Snippet<[item: TableItem, value: unknown, column: Column]> | undefined,
|
|
16
|
+
empty = undefined as Snippet | undefined,
|
|
17
|
+
noDataText = '',
|
|
18
|
+
onRowClick = undefined as ((item: TableItem) => void) | undefined
|
|
19
|
+
} = $props();
|
|
20
|
+
|
|
21
|
+
const tableContext = getTableContext();
|
|
22
|
+
const { state: tableState, groupedSummaryData } = tableContext;
|
|
23
|
+
const filteredItems = $derived(tableContext.filteredItems);
|
|
24
|
+
const paginatedItems = $derived(tableContext.paginatedItems);
|
|
25
|
+
const grouped = $derived(tableContext.grouped);
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<div class="mobile-only md:hidden" data-testid="mobile-table">
|
|
29
|
+
{#if filteredItems.length === 0}
|
|
30
|
+
{#if empty}
|
|
31
|
+
{@render empty()}
|
|
32
|
+
{:else}
|
|
33
|
+
<div class="text-text-secondary py-6 text-center text-sm" data-testid="empty-state-mobile">
|
|
34
|
+
{noDataText}
|
|
35
|
+
</div>
|
|
36
|
+
{/if}
|
|
37
|
+
{:else if tableState.groupByKey}
|
|
38
|
+
{#each Object.entries(grouped) as [groupName, groupItems] (groupName)}
|
|
39
|
+
<div class="mb-6">
|
|
40
|
+
<h3
|
|
41
|
+
class="text-text-primary border-border-subtle mb-3 flex min-h-11 items-center border-b pb-2 text-base font-semibold"
|
|
42
|
+
>
|
|
43
|
+
{groupName}
|
|
44
|
+
<span class="text-text-tertiary ml-1.5 text-sm font-normal">
|
|
45
|
+
({groupItems.length}
|
|
46
|
+
{groupItems.length === 1 ? tt('group.item') : tt('group.items')})
|
|
47
|
+
</span>
|
|
48
|
+
</h3>
|
|
49
|
+
{#each groupItems as item, i (item.id ?? i)}
|
|
50
|
+
<MobileCard {item} {expandable} {expandedRowContent} {cell} {size} onClick={onRowClick} />
|
|
51
|
+
{/each}
|
|
52
|
+
|
|
53
|
+
{#if tableState.showSummary && tableState.summaryConfigs.length > 0}
|
|
54
|
+
<div class="bg-surface-elevated border-border-subtle rounded-contain mt-3 border p-4">
|
|
55
|
+
<h4 class="text-text-primary mb-2 text-sm font-semibold">
|
|
56
|
+
{tt('group.summaryFor')}
|
|
57
|
+
{groupName}:
|
|
58
|
+
</h4>
|
|
59
|
+
<div class="space-y-1.5 text-sm">
|
|
60
|
+
{#each tableState.summaryConfigs as config (config.column)}
|
|
61
|
+
{#if groupedSummaryData[groupName] && groupedSummaryData[groupName][config.column] !== undefined}
|
|
62
|
+
<div class="flex min-h-8 items-center justify-between">
|
|
63
|
+
<span class="text-text-secondary">
|
|
64
|
+
{tableState.columns.find((c) => resolveColumnId(c) === config.column)
|
|
65
|
+
?.title || config.column}:
|
|
66
|
+
</span>
|
|
67
|
+
<span class="text-text-primary font-medium">
|
|
68
|
+
{tableContext.getFormattedSummaryValue(
|
|
69
|
+
config.column,
|
|
70
|
+
groupedSummaryData[groupName][config.column]
|
|
71
|
+
)}
|
|
72
|
+
</span>
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
75
|
+
{/each}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
{/if}
|
|
79
|
+
</div>
|
|
80
|
+
{/each}
|
|
81
|
+
{:else}
|
|
82
|
+
{#each Array.isArray(paginatedItems) ? paginatedItems : [] as item, i (item.id ?? i)}
|
|
83
|
+
<MobileCard {item} {expandable} {expandedRowContent} {cell} {size} onClick={onRowClick} />
|
|
84
|
+
{/each}
|
|
85
|
+
|
|
86
|
+
{#if tableState.showSummary && tableState.summaryConfigs.length > 0}
|
|
87
|
+
<div class="bg-surface-elevated border-border-subtle rounded-contain mt-4 border p-4">
|
|
88
|
+
<h4 class="text-text-primary mb-2 text-sm font-semibold">
|
|
89
|
+
{tt('table.summary.totalSummary')}
|
|
90
|
+
</h4>
|
|
91
|
+
<div class="space-y-1.5 text-sm">
|
|
92
|
+
{#each tableState.summaryConfigs as config (config.column)}
|
|
93
|
+
{#if tableContext.summaryData[config.column] !== undefined}
|
|
94
|
+
<div class="flex min-h-8 items-center justify-between">
|
|
95
|
+
<span class="text-text-secondary">
|
|
96
|
+
{tableState.columns.find((c) => resolveColumnId(c) === config.column)?.title ||
|
|
97
|
+
config.column}:
|
|
98
|
+
</span>
|
|
99
|
+
<span class="text-text-primary font-medium">
|
|
100
|
+
{tableContext.getFormattedSummaryValue(
|
|
101
|
+
config.column,
|
|
102
|
+
tableContext.summaryData[config.column]
|
|
103
|
+
)}
|
|
104
|
+
</span>
|
|
105
|
+
</div>
|
|
106
|
+
{/if}
|
|
107
|
+
{/each}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
{/if}
|
|
111
|
+
{/if}
|
|
112
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Column, TableItem } from '../types/tableTypes';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
declare const TableMobile: import("svelte").Component<{
|
|
4
|
+
size?: "sm" | "md" | "lg";
|
|
5
|
+
expandable?: boolean;
|
|
6
|
+
expandedRowContent?: Snippet<[item: TableItem]> | undefined;
|
|
7
|
+
cell?: Snippet<[item: TableItem, value: unknown, column: Column]> | undefined;
|
|
8
|
+
empty?: Snippet | undefined;
|
|
9
|
+
noDataText?: string;
|
|
10
|
+
onRowClick?: ((item: TableItem) => void) | undefined;
|
|
11
|
+
}, {}, "">;
|
|
12
|
+
type TableMobile = ReturnType<typeof TableMobile>;
|
|
13
|
+
export default TableMobile;
|