@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.
Files changed (154) hide show
  1. package/README.md +153 -0
  2. package/dist/cells/ActionButtons.svelte +224 -0
  3. package/dist/cells/ActionButtons.svelte.d.ts +74 -0
  4. package/dist/cells/CopyButton.svelte +89 -0
  5. package/dist/cells/CopyButton.svelte.d.ts +33 -0
  6. package/dist/cells/CustomCell.svelte +136 -0
  7. package/dist/cells/CustomCell.svelte.d.ts +44 -0
  8. package/dist/cells/DateCell.svelte +194 -0
  9. package/dist/cells/DateCell.svelte.d.ts +39 -0
  10. package/dist/cells/LinkCell.svelte +240 -0
  11. package/dist/cells/LinkCell.svelte.d.ts +42 -0
  12. package/dist/cells/NumberCell.svelte +225 -0
  13. package/dist/cells/NumberCell.svelte.d.ts +47 -0
  14. package/dist/cells/StatusBadge.svelte +121 -0
  15. package/dist/cells/StatusBadge.svelte.d.ts +44 -0
  16. package/dist/cells/UserAvatar.svelte +71 -0
  17. package/dist/cells/UserAvatar.svelte.d.ts +37 -0
  18. package/dist/cells/index.d.ts +8 -0
  19. package/dist/cells/index.js +9 -0
  20. package/dist/core/EmptyState.svelte +161 -0
  21. package/dist/core/EmptyState.svelte.d.ts +16 -0
  22. package/dist/core/ErrorState.svelte +158 -0
  23. package/dist/core/ErrorState.svelte.d.ts +15 -0
  24. package/dist/core/GroupedRow.svelte +239 -0
  25. package/dist/core/GroupedRow.svelte.d.ts +18 -0
  26. package/dist/core/LoadingState.svelte +75 -0
  27. package/dist/core/LoadingState.svelte.d.ts +14 -0
  28. package/dist/core/MobileCard.svelte +151 -0
  29. package/dist/core/MobileCard.svelte.d.ts +15 -0
  30. package/dist/core/TableCell.svelte +105 -0
  31. package/dist/core/TableCell.svelte.d.ts +14 -0
  32. package/dist/core/TableDesktop.svelte +480 -0
  33. package/dist/core/TableDesktop.svelte.d.ts +26 -0
  34. package/dist/core/TableHead.svelte +314 -0
  35. package/dist/core/TableHead.svelte.d.ts +7 -0
  36. package/dist/core/TableMobile.svelte +112 -0
  37. package/dist/core/TableMobile.svelte.d.ts +13 -0
  38. package/dist/core/TableProvider.svelte +271 -0
  39. package/dist/core/TableProvider.svelte.d.ts +40 -0
  40. package/dist/core/TableRow.svelte +171 -0
  41. package/dist/core/TableRow.svelte.d.ts +16 -0
  42. package/dist/core/index.d.ts +17 -0
  43. package/dist/core/index.js +14 -0
  44. package/dist/core/sticky-context.svelte.d.ts +48 -0
  45. package/dist/core/sticky-context.svelte.js +88 -0
  46. package/dist/core/table/Table.svelte +304 -0
  47. package/dist/core/table/Table.svelte.d.ts +26 -0
  48. package/dist/core/table/index.d.ts +448 -0
  49. package/dist/core/table/index.js +1 -0
  50. package/dist/core/table-style-context.d.ts +66 -0
  51. package/dist/core/table-style-context.js +26 -0
  52. package/dist/factories/ColumnValidation.d.ts +49 -0
  53. package/dist/factories/ColumnValidation.js +188 -0
  54. package/dist/factories/TableColumns.d.ts +97 -0
  55. package/dist/factories/TableColumns.js +262 -0
  56. package/dist/factories/TypedColumnBuilder.d.ts +41 -0
  57. package/dist/factories/TypedColumnBuilder.js +72 -0
  58. package/dist/factories/index.d.ts +12 -0
  59. package/dist/factories/index.js +13 -0
  60. package/dist/features/HeaderMenu.svelte +236 -0
  61. package/dist/features/HeaderMenu.svelte.d.ts +8 -0
  62. package/dist/features/LiveUpdateBanner.svelte +66 -0
  63. package/dist/features/LiveUpdateBanner.svelte.d.ts +6 -0
  64. package/dist/features/SearchHighlight.svelte +21 -0
  65. package/dist/features/SearchHighlight.svelte.d.ts +8 -0
  66. package/dist/features/SmartFilterBar/ChipsField.svelte +104 -0
  67. package/dist/features/SmartFilterBar/ChipsField.svelte.d.ts +5 -0
  68. package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte +84 -0
  69. package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte.d.ts +3 -0
  70. package/dist/features/SmartFilterBar/FilterMenu.svelte +367 -0
  71. package/dist/features/SmartFilterBar/FilterMenu.svelte.d.ts +3 -0
  72. package/dist/features/SmartFilterBar/GroupingMenu.svelte +82 -0
  73. package/dist/features/SmartFilterBar/GroupingMenu.svelte.d.ts +3 -0
  74. package/dist/features/SmartFilterBar/SmartFilterBar.svelte +109 -0
  75. package/dist/features/SmartFilterBar/SmartFilterBar.svelte.d.ts +11 -0
  76. package/dist/features/SmartFilterBar/SummaryMenu.svelte +118 -0
  77. package/dist/features/SmartFilterBar/SummaryMenu.svelte.d.ts +3 -0
  78. package/dist/features/SummaryRow.svelte +97 -0
  79. package/dist/features/SummaryRow.svelte.d.ts +8 -0
  80. package/dist/features/index.d.ts +4 -0
  81. package/dist/features/index.js +4 -0
  82. package/dist/i18n/index.d.ts +366 -0
  83. package/dist/i18n/index.js +21 -0
  84. package/dist/index.d.ts +28 -0
  85. package/dist/index.js +41 -0
  86. package/dist/stores/TableStore.svelte.d.ts +192 -0
  87. package/dist/stores/TableStore.svelte.js +362 -0
  88. package/dist/stores/concerns/index.d.ts +15 -0
  89. package/dist/stores/concerns/index.js +14 -0
  90. package/dist/stores/concerns/types.d.ts +31 -0
  91. package/dist/stores/concerns/types.js +1 -0
  92. package/dist/stores/concerns/useColumnOrder.svelte.d.ts +16 -0
  93. package/dist/stores/concerns/useColumnOrder.svelte.js +81 -0
  94. package/dist/stores/concerns/useColumnVisibility.svelte.d.ts +16 -0
  95. package/dist/stores/concerns/useColumnVisibility.svelte.js +58 -0
  96. package/dist/stores/concerns/useExpansion.svelte.d.ts +9 -0
  97. package/dist/stores/concerns/useExpansion.svelte.js +32 -0
  98. package/dist/stores/concerns/useFiltering.svelte.d.ts +20 -0
  99. package/dist/stores/concerns/useFiltering.svelte.js +109 -0
  100. package/dist/stores/concerns/useFocusManagement.svelte.d.ts +15 -0
  101. package/dist/stores/concerns/useFocusManagement.svelte.js +52 -0
  102. package/dist/stores/concerns/useGrouping.svelte.d.ts +15 -0
  103. package/dist/stores/concerns/useGrouping.svelte.js +86 -0
  104. package/dist/stores/concerns/useLiveUpdates.svelte.d.ts +45 -0
  105. package/dist/stores/concerns/useLiveUpdates.svelte.js +175 -0
  106. package/dist/stores/concerns/usePagination.svelte.d.ts +18 -0
  107. package/dist/stores/concerns/usePagination.svelte.js +54 -0
  108. package/dist/stores/concerns/usePersistence.svelte.d.ts +36 -0
  109. package/dist/stores/concerns/usePersistence.svelte.js +167 -0
  110. package/dist/stores/concerns/useRemoteData.svelte.d.ts +21 -0
  111. package/dist/stores/concerns/useRemoteData.svelte.js +64 -0
  112. package/dist/stores/concerns/useSearch.svelte.d.ts +8 -0
  113. package/dist/stores/concerns/useSearch.svelte.js +16 -0
  114. package/dist/stores/concerns/useSelection.svelte.d.ts +21 -0
  115. package/dist/stores/concerns/useSelection.svelte.js +110 -0
  116. package/dist/stores/concerns/useSorting.svelte.d.ts +11 -0
  117. package/dist/stores/concerns/useSorting.svelte.js +70 -0
  118. package/dist/stores/concerns/useSummary.svelte.d.ts +18 -0
  119. package/dist/stores/concerns/useSummary.svelte.js +96 -0
  120. package/dist/stores/index.d.ts +1 -0
  121. package/dist/stores/index.js +1 -0
  122. package/dist/style/index.css +137 -0
  123. package/dist/style/index.d.ts +2 -0
  124. package/dist/style/index.js +2 -0
  125. package/dist/style/table-theme.css +131 -0
  126. package/dist/style/themes/comfortable.css +20 -0
  127. package/dist/style/themes/compact.css +20 -0
  128. package/dist/translations/de.d.ts +177 -0
  129. package/dist/translations/de.js +176 -0
  130. package/dist/translations/en.d.ts +177 -0
  131. package/dist/translations/en.js +176 -0
  132. package/dist/types/index.d.ts +1 -0
  133. package/dist/types/index.js +1 -0
  134. package/dist/types/tableTypes.d.ts +262 -0
  135. package/dist/types/tableTypes.js +1 -0
  136. package/dist/utils/index.d.ts +165 -0
  137. package/dist/utils/index.js +330 -0
  138. package/dist/utils/sticky-measure.d.ts +54 -0
  139. package/dist/utils/sticky-measure.js +107 -0
  140. package/dist/utils/virtualizer.d.ts +43 -0
  141. package/dist/utils/virtualizer.js +43 -0
  142. package/dist/variants/index.d.ts +11 -0
  143. package/dist/variants/index.js +15 -0
  144. package/dist/variants/table-cells.variants.d.ts +827 -0
  145. package/dist/variants/table-cells.variants.js +627 -0
  146. package/dist/variants/table-features.variants.d.ts +547 -0
  147. package/dist/variants/table-features.variants.js +412 -0
  148. package/dist/variants/table-states.variants.d.ts +594 -0
  149. package/dist/variants/table-states.variants.js +394 -0
  150. package/dist/variants/table.system.d.ts +301 -0
  151. package/dist/variants/table.system.js +314 -0
  152. package/dist/variants/table.variants.d.ts +428 -0
  153. package/dist/variants/table.variants.js +360 -0
  154. package/package.json +93 -0
@@ -0,0 +1,161 @@
1
+ <script lang="ts">
2
+ import { useTableI18n } from '../i18n';
3
+ import { emptyStateVariants, tableRowVariants, type EmptyStateVariantProps } from '../variants';
4
+ import { getTableStyleConfig, resolveSlotClass } from './table-style-context';
5
+
6
+ const tt = useTableI18n();
7
+
8
+ export type EmptyStateProps = {
9
+ message?: string;
10
+ description?: string;
11
+ icon?: string;
12
+ actionText?: string;
13
+ onAction?: () => void;
14
+ colSpan?: number;
15
+ class?: string;
16
+ testId?: string;
17
+ useI18n?: boolean;
18
+ size?: EmptyStateVariantProps['size'];
19
+ };
20
+
21
+ let {
22
+ message = undefined,
23
+ description = undefined,
24
+ icon = undefined,
25
+ actionText = undefined,
26
+ onAction = undefined,
27
+ colSpan = 1,
28
+ class: className = '',
29
+ testId = 'empty-state',
30
+ useI18n = true,
31
+ size = 'md'
32
+ }: EmptyStateProps = $props();
33
+
34
+ const styleConfig = getTableStyleConfig();
35
+
36
+ // Smart message with I18n fallback
37
+ const displayMessage = $derived.by(() => {
38
+ if (message) return message;
39
+
40
+ if (useI18n) {
41
+ return tt('data.empty');
42
+ }
43
+
44
+ return 'No data found';
45
+ });
46
+
47
+ // Smart action text with I18n fallback
48
+ const displayActionText = $derived.by(() => {
49
+ if (actionText) return actionText;
50
+
51
+ if (useI18n && onAction) {
52
+ return tt('data.refresh');
53
+ }
54
+
55
+ return actionText;
56
+ });
57
+
58
+ // TV Styles
59
+ const styles = $derived(emptyStateVariants({ size }));
60
+ const rowStyles = $derived(tableRowVariants({ size }));
61
+
62
+ // Event handlers
63
+ function handleAction() {
64
+ if (onAction) {
65
+ onAction();
66
+ }
67
+ }
68
+
69
+ function handleKeyDown(event: KeyboardEvent) {
70
+ if (onAction && (event.key === 'Enter' || event.key === ' ')) {
71
+ event.preventDefault();
72
+ onAction();
73
+ }
74
+ }
75
+ </script>
76
+
77
+ <tr class={rowStyles.row()} data-testid={testId}>
78
+ <td colspan={colSpan} class={rowStyles.cell()}>
79
+ <div
80
+ class={resolveSlotClass(
81
+ styles.container(),
82
+ styleConfig.slotClasses.emptyState,
83
+ styleConfig.unstyled,
84
+ className
85
+ )}
86
+ >
87
+ {#if icon}
88
+ <div class={styles.icon()}>
89
+ {#if icon === 'search'}
90
+ <svg
91
+ class={styles.iconSvg()}
92
+ viewBox="0 0 24 24"
93
+ fill="none"
94
+ stroke="currentColor"
95
+ stroke-linecap="round"
96
+ stroke-linejoin="round"
97
+ >
98
+ <circle cx="11" cy="11" r="8"></circle>
99
+ <path d="M21 21L16.65 16.65"></path>
100
+ </svg>
101
+ {:else if icon === 'database'}
102
+ <svg
103
+ class={styles.iconSvg()}
104
+ viewBox="0 0 24 24"
105
+ fill="none"
106
+ stroke="currentColor"
107
+ stroke-linecap="round"
108
+ stroke-linejoin="round"
109
+ >
110
+ <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
111
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
112
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
113
+ </svg>
114
+ {:else if icon === 'inbox'}
115
+ <svg
116
+ class={styles.iconSvg()}
117
+ viewBox="0 0 24 24"
118
+ fill="none"
119
+ stroke="currentColor"
120
+ stroke-linecap="round"
121
+ stroke-linejoin="round"
122
+ >
123
+ <polyline points="22,12 18,12 15,21 9,21 6,12 2,12"></polyline>
124
+ <path
125
+ d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"
126
+ ></path>
127
+ </svg>
128
+ {:else}
129
+ <!-- Consumer-provided SVG markup; they are responsible for
130
+ sanitisation (same contract as the other icon slots). -->
131
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
132
+ {@html icon}
133
+ {/if}
134
+ </div>
135
+ {/if}
136
+
137
+ <div class={styles.content()}>
138
+ <h3 class={styles.title()}>
139
+ {displayMessage}
140
+ </h3>
141
+
142
+ {#if description}
143
+ <p class={styles.description()}>
144
+ {description}
145
+ </p>
146
+ {/if}
147
+
148
+ {#if displayActionText && onAction}
149
+ <button
150
+ class={styles.action()}
151
+ onclick={handleAction}
152
+ onkeydown={handleKeyDown}
153
+ type="button"
154
+ >
155
+ {displayActionText}
156
+ </button>
157
+ {/if}
158
+ </div>
159
+ </div>
160
+ </td>
161
+ </tr>
@@ -0,0 +1,16 @@
1
+ import { type EmptyStateVariantProps } from '../variants';
2
+ export type EmptyStateProps = {
3
+ message?: string;
4
+ description?: string;
5
+ icon?: string;
6
+ actionText?: string;
7
+ onAction?: () => void;
8
+ colSpan?: number;
9
+ class?: string;
10
+ testId?: string;
11
+ useI18n?: boolean;
12
+ size?: EmptyStateVariantProps['size'];
13
+ };
14
+ declare const EmptyState: import("svelte").Component<EmptyStateProps, {}, "">;
15
+ type EmptyState = ReturnType<typeof EmptyState>;
16
+ export default EmptyState;
@@ -0,0 +1,158 @@
1
+ <script lang="ts">
2
+ import { useTableI18n } from '../i18n';
3
+ import { errorStateVariants, tableRowVariants, type ErrorStateVariantProps } from '../variants';
4
+ import { getTableStyleConfig, resolveSlotClass } from './table-style-context';
5
+
6
+ const tt = useTableI18n();
7
+
8
+ export type ErrorStateProps = {
9
+ title?: string;
10
+ message?: string;
11
+ details?: string;
12
+ retryText?: string;
13
+ onRetry?: () => void;
14
+ colSpan?: number;
15
+ className?: string;
16
+ testId?: string;
17
+ size?: ErrorStateVariantProps['size'];
18
+ };
19
+
20
+ let {
21
+ title = tt('error.loadingError'),
22
+ message = tt('error.genericMessage'),
23
+ details = undefined,
24
+ retryText = tt('error.retry'),
25
+ onRetry = undefined,
26
+ colSpan = 1,
27
+ className = '',
28
+ testId = 'error-state',
29
+ size = 'md'
30
+ }: ErrorStateProps = $props();
31
+
32
+ const styleConfig = getTableStyleConfig();
33
+
34
+ let showDetails = $state(false);
35
+
36
+ // TV Styles
37
+ const styles = $derived(errorStateVariants({ size, detailsExpanded: showDetails }));
38
+ const rowStyles = $derived(tableRowVariants({ size }));
39
+
40
+ // Event handlers
41
+ function handleRetry() {
42
+ if (onRetry) {
43
+ onRetry();
44
+ }
45
+ }
46
+
47
+ function toggleDetails() {
48
+ showDetails = !showDetails;
49
+ }
50
+
51
+ function handleKeyDown(event: KeyboardEvent) {
52
+ if (event.key === 'Enter' || event.key === ' ') {
53
+ event.preventDefault();
54
+ const target = event.target as HTMLElement;
55
+
56
+ if (target.classList.contains('retry-button')) {
57
+ handleRetry();
58
+ } else if (target.classList.contains('details-toggle')) {
59
+ toggleDetails();
60
+ }
61
+ }
62
+ }
63
+ </script>
64
+
65
+ <tr class={rowStyles.row()} data-testid={testId}>
66
+ <td colspan={colSpan} class={rowStyles.cell()}>
67
+ <div
68
+ class={resolveSlotClass(
69
+ styles.container(),
70
+ styleConfig.slotClasses.errorState,
71
+ styleConfig.unstyled,
72
+ className
73
+ )}
74
+ >
75
+ <div class={styles.icon()}>
76
+ <svg
77
+ class={styles.iconSvg()}
78
+ viewBox="0 0 24 24"
79
+ fill="none"
80
+ stroke="currentColor"
81
+ stroke-linecap="round"
82
+ stroke-linejoin="round"
83
+ >
84
+ <circle cx="12" cy="12" r="10"></circle>
85
+ <line x1="15" y1="9" x2="9" y2="15"></line>
86
+ <line x1="9" y1="9" x2="15" y2="15"></line>
87
+ </svg>
88
+ </div>
89
+
90
+ <div class={styles.content()}>
91
+ <h3 class={styles.title()}>
92
+ {title}
93
+ </h3>
94
+
95
+ <p class={styles.message()}>
96
+ {message}
97
+ </p>
98
+
99
+ {#if details}
100
+ <div class={styles.details()}>
101
+ <button
102
+ class="{styles.detailsToggle()} details-toggle"
103
+ onclick={toggleDetails}
104
+ onkeydown={handleKeyDown}
105
+ type="button"
106
+ aria-expanded={showDetails}
107
+ >
108
+ <span>{tt('actions.showDetails')}</span>
109
+ <svg
110
+ class={styles.detailsIcon()}
111
+ width="16"
112
+ height="16"
113
+ viewBox="0 0 24 24"
114
+ fill="none"
115
+ stroke="currentColor"
116
+ stroke-width="2"
117
+ stroke-linecap="round"
118
+ stroke-linejoin="round"
119
+ >
120
+ <polyline points="6,9 12,15 18,9"></polyline>
121
+ </svg>
122
+ </button>
123
+
124
+ {#if showDetails}
125
+ <div class={styles.detailsContent()}>
126
+ <pre class={styles.detailsText()}>{details}</pre>
127
+ </div>
128
+ {/if}
129
+ </div>
130
+ {/if}
131
+
132
+ {#if onRetry}
133
+ <button
134
+ class="{styles.retryButton()} retry-button"
135
+ onclick={handleRetry}
136
+ onkeydown={handleKeyDown}
137
+ type="button"
138
+ >
139
+ <svg
140
+ class={styles.retryIcon()}
141
+ viewBox="0 0 24 24"
142
+ fill="none"
143
+ stroke="currentColor"
144
+ stroke-width="2"
145
+ stroke-linecap="round"
146
+ stroke-linejoin="round"
147
+ >
148
+ <polyline points="23,4 23,10 17,10"></polyline>
149
+ <polyline points="1,20 1,14 7,14"></polyline>
150
+ <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
151
+ </svg>
152
+ {retryText}
153
+ </button>
154
+ {/if}
155
+ </div>
156
+ </div>
157
+ </td>
158
+ </tr>
@@ -0,0 +1,15 @@
1
+ import { type ErrorStateVariantProps } from '../variants';
2
+ export type ErrorStateProps = {
3
+ title?: string;
4
+ message?: string;
5
+ details?: string;
6
+ retryText?: string;
7
+ onRetry?: () => void;
8
+ colSpan?: number;
9
+ className?: string;
10
+ testId?: string;
11
+ size?: ErrorStateVariantProps['size'];
12
+ };
13
+ declare const ErrorState: import("svelte").Component<ErrorStateProps, {}, "">;
14
+ type ErrorState = ReturnType<typeof ErrorState>;
15
+ export default ErrorState;
@@ -0,0 +1,239 @@
1
+ <script lang="ts">
2
+ import { slide } from 'svelte/transition';
3
+ import {
4
+ resolveIcon,
5
+ Checkbox,
6
+ ChevronDownIcon as ChevronDownIconDefault
7
+ } from '@urbicon-ui/blocks';
8
+ import { getTableContext } from '../stores/TableStore.svelte';
9
+ import { useTableI18n } from '../i18n';
10
+
11
+ const ChevronDownIcon = resolveIcon('chevronDown', ChevronDownIconDefault);
12
+ import { groupHeaderVariants, type GroupHeaderVariantProps } from '../variants';
13
+ import { tableRowVariants } from '../variants';
14
+ import TableCell from './TableCell.svelte';
15
+ import { getTableStyleConfig, resolveSlotClass } from './table-style-context';
16
+ import { getStickyContext } from './sticky-context.svelte';
17
+ import { resolveColumnId } from '../utils';
18
+ import type { Column, TableItem } from '../types/tableTypes';
19
+ import type { Snippet } from 'svelte';
20
+
21
+ const tt = useTableI18n();
22
+
23
+ export type GroupedRowProps = {
24
+ groupName: string;
25
+ items: TableItem[];
26
+ expandable?: boolean;
27
+ expandedRowContent?: Snippet<[item: TableItem]>;
28
+ cell?: Snippet<[item: TableItem, value: unknown, column: Column]>;
29
+ size?: GroupHeaderVariantProps['size'];
30
+ class?: string;
31
+ testId?: string;
32
+ groupHeaderContent?: Snippet<[groupName: string, items: TableItem[], isExpanded: boolean]>;
33
+ onRowClick?: (item: TableItem) => void;
34
+ };
35
+
36
+ let {
37
+ groupName,
38
+ items,
39
+ expandable = false,
40
+ expandedRowContent = undefined,
41
+ cell = undefined,
42
+ size = 'md',
43
+ class: className = '',
44
+ testId = undefined,
45
+ groupHeaderContent,
46
+ onRowClick = undefined
47
+ }: GroupedRowProps = $props();
48
+
49
+ // Table context
50
+ const tableContext = getTableContext();
51
+ const { state: tableState, toggleGroupExpand, toggleExpand, isItemExpanded } = tableContext;
52
+
53
+ // Reactive computations
54
+ const computedTestId = $derived.by(() => {
55
+ if (testId) return testId;
56
+ return `grouped-row-${groupName.replace(/\s+/g, '-').toLowerCase()}`;
57
+ });
58
+
59
+ const isExpanded = $derived.by(() => {
60
+ return !tableState.collapsedGroups.has(groupName);
61
+ });
62
+
63
+ let selectable = $derived(tableState.selectionMode !== 'none');
64
+ const styleConfig = getTableStyleConfig();
65
+ const stickyContext = getStickyContext();
66
+
67
+ const colSpan = $derived.by(() => {
68
+ let count = tableState.columns.length;
69
+ if (expandable) count++; // For expand column
70
+ if (tableState.groupByKey) count++; // For group indentation column
71
+ if (selectable) count++; // For selection checkbox column
72
+ return count;
73
+ });
74
+
75
+ const displayGroupName = $derived.by(() => {
76
+ if (groupName === '' || groupName === null || groupName === undefined) {
77
+ return tt('group.noGroup');
78
+ }
79
+ return String(groupName);
80
+ });
81
+
82
+ const itemCountText = $derived.by(() => {
83
+ const count = items.length;
84
+ return `(${count} ${count === 1 ? tt('group.item') : tt('group.items')})`;
85
+ });
86
+
87
+ // Tailwind-Variants styling
88
+ const styles = $derived(groupHeaderVariants({ size, sticky: stickyContext.mode.group }));
89
+
90
+ // TableRow styles for individual items
91
+ const rowStyles = $derived(
92
+ tableRowVariants({
93
+ state: 'default',
94
+ size
95
+ })
96
+ );
97
+
98
+ // Event handlers
99
+ function handleToggleGroup() {
100
+ toggleGroupExpand(groupName);
101
+ }
102
+
103
+ function handleKeyDown(event: KeyboardEvent) {
104
+ if (event.key === 'Enter' || event.key === ' ') {
105
+ event.preventDefault();
106
+ handleToggleGroup();
107
+ }
108
+ }
109
+
110
+ function handleItemToggleExpand(e: MouseEvent, row: TableItem) {
111
+ e.stopPropagation();
112
+ if (!expandable) return;
113
+ const candidate = row.id ?? row.__index;
114
+ if (typeof candidate === 'string' || typeof candidate === 'number') {
115
+ toggleExpand(candidate);
116
+ }
117
+ }
118
+ </script>
119
+
120
+ <!-- Group Header Row -->
121
+ <tr
122
+ class={resolveSlotClass(
123
+ styles.row(),
124
+ styleConfig.slotClasses.groupHeader,
125
+ styleConfig.unstyled,
126
+ className
127
+ )}
128
+ data-testid={computedTestId}
129
+ id={`grouped-item-${groupName}`}
130
+ >
131
+ <td
132
+ colspan={colSpan}
133
+ class={styles.cell()}
134
+ onclick={handleToggleGroup}
135
+ onkeydown={handleKeyDown}
136
+ role="button"
137
+ tabindex="0"
138
+ aria-expanded={isExpanded}
139
+ aria-label="{displayGroupName} {isExpanded
140
+ ? tt('header.collapseAllGroups')
141
+ : tt('header.expandAllGroups')}"
142
+ >
143
+ <div class={styles.content()}>
144
+ <!-- Expand/Collapse Icon -->
145
+ <div class={styles.chevron()}>
146
+ <ChevronDownIcon class={styles.chevron()} size={16} />
147
+ </div>
148
+
149
+ <!-- Group Name and Count -->
150
+ <div class={styles.content()}>
151
+ <span class={styles.title()}>
152
+ {displayGroupName}
153
+ </span>
154
+ <span class={styles.count()}>
155
+ {itemCountText}
156
+ </span>
157
+ </div>
158
+
159
+ <!-- Custom header content or actions -->
160
+ <div class={styles.actions()}>
161
+ {#if groupHeaderContent}
162
+ {@render groupHeaderContent(groupName, items, isExpanded)}
163
+ {/if}
164
+ </div>
165
+ </div>
166
+ </td>
167
+ </tr>
168
+
169
+ <!-- Group Content Rows -->
170
+ {#if isExpanded}
171
+ {#each items as item, index (item.id ?? index)}
172
+ {@const rowItemId =
173
+ typeof item.id === 'string' || typeof item.id === 'number' ? item.id : index}
174
+ {@const isRowExpanded = isItemExpanded(rowItemId)}
175
+ {@const isRowSelected = selectable && tableContext.isSelected(rowItemId)}
176
+ <tr
177
+ class={rowStyles.row()}
178
+ transition:slide={{ duration: 150 }}
179
+ aria-selected={selectable ? isRowSelected : undefined}
180
+ data-testid={`grouped-item-${rowItemId}`}
181
+ >
182
+ {#if selectable}
183
+ <td class="{rowStyles.cell()} w-12" onclick={(e) => e.stopPropagation()}>
184
+ <div class="flex h-full w-full items-center justify-center">
185
+ <Checkbox
186
+ checked={isRowSelected}
187
+ onchange={() => tableContext.toggleItem(rowItemId)}
188
+ aria-label={isRowSelected ? tt('selection.deselectRow') : tt('selection.selectRow')}
189
+ size="sm"
190
+ />
191
+ </div>
192
+ </td>
193
+ {/if}
194
+
195
+ {#if tableState.groupByKey}
196
+ <td class={rowStyles.cell()} aria-hidden="true"></td>
197
+ {/if}
198
+
199
+ {#if expandable}
200
+ <td class="{rowStyles.cell()} w-10">
201
+ <div class="flex h-full w-full items-center justify-center px-2 py-2">
202
+ <button
203
+ class="table-expand-button rounded-modify flex h-6 w-6 items-center justify-center transition-transform duration-(--blocks-duration-fast) {isRowExpanded
204
+ ? 'rotate-180'
205
+ : ''}"
206
+ onclick={(e) => handleItemToggleExpand(e, item)}
207
+ aria-label={tt('actions.showDetails')}
208
+ data-testid={`expand-button-${rowItemId}`}
209
+ >
210
+ <ChevronDownIcon class="h-4 w-4" />
211
+ </button>
212
+ </div>
213
+ </td>
214
+ {/if}
215
+
216
+ {#each tableContext.orderedColumns as column (resolveColumnId(column))}
217
+ <TableCell
218
+ {item}
219
+ {column}
220
+ {cell}
221
+ {size}
222
+ cellClass={rowStyles.cell()}
223
+ testIdPrefix="grouped-cell"
224
+ />
225
+ {/each}
226
+ </tr>
227
+
228
+ <!-- Expanded row content -->
229
+ {#if isRowExpanded && expandedRowContent}
230
+ <tr data-testid={`expanded-row-${rowItemId}`} class="border-b-0">
231
+ <td colspan={colSpan} class="p-0">
232
+ <div class="bg-surface-elevated/50 px-6 py-4" transition:slide={{ duration: 150 }}>
233
+ {@render expandedRowContent(item)}
234
+ </div>
235
+ </td>
236
+ </tr>
237
+ {/if}
238
+ {/each}
239
+ {/if}
@@ -0,0 +1,18 @@
1
+ import { type GroupHeaderVariantProps } from '../variants';
2
+ import type { Column, TableItem } from '../types/tableTypes';
3
+ import type { Snippet } from 'svelte';
4
+ export type GroupedRowProps = {
5
+ groupName: string;
6
+ items: TableItem[];
7
+ expandable?: boolean;
8
+ expandedRowContent?: Snippet<[item: TableItem]>;
9
+ cell?: Snippet<[item: TableItem, value: unknown, column: Column]>;
10
+ size?: GroupHeaderVariantProps['size'];
11
+ class?: string;
12
+ testId?: string;
13
+ groupHeaderContent?: Snippet<[groupName: string, items: TableItem[], isExpanded: boolean]>;
14
+ onRowClick?: (item: TableItem) => void;
15
+ };
16
+ declare const GroupedRow: import("svelte").Component<GroupedRowProps, {}, "">;
17
+ type GroupedRow = ReturnType<typeof GroupedRow>;
18
+ export default GroupedRow;
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import { useTableI18n } from '../i18n';
3
+ import { Spinner } from '@urbicon-ui/blocks';
4
+ import {
5
+ loadingStateVariants,
6
+ tableRowVariants,
7
+ type LoadingStateVariantProps
8
+ } from '../variants';
9
+ import { getTableStyleConfig, resolveSlotClass } from './table-style-context';
10
+
11
+ const tt = useTableI18n();
12
+
13
+ export type LoadingStateProps = {
14
+ text?: string;
15
+ description?: string;
16
+ showSpinner?: boolean;
17
+ colSpan?: number;
18
+ class?: string;
19
+ testId?: string;
20
+ useI18n?: boolean;
21
+ size?: LoadingStateVariantProps['size'];
22
+ };
23
+
24
+ let {
25
+ text = undefined,
26
+ description = undefined,
27
+ showSpinner = true,
28
+ colSpan = 1,
29
+ class: className = '',
30
+ testId = 'loading-state',
31
+ useI18n = true,
32
+ size = 'md'
33
+ }: LoadingStateProps = $props();
34
+
35
+ const SPINNER_SIZE_MAP = { sm: 'sm', md: 'md', lg: 'lg' } as const;
36
+
37
+ const displayText = $derived.by(() => {
38
+ if (text) return text;
39
+ if (useI18n) return tt('data.loading');
40
+ return 'Loading data...';
41
+ });
42
+
43
+ const styles = $derived(loadingStateVariants({ size }));
44
+ const rowStyles = $derived(tableRowVariants({ size }));
45
+ const styleConfig = getTableStyleConfig();
46
+ </script>
47
+
48
+ <tr class={rowStyles.row()} data-testid={testId}>
49
+ <td colspan={colSpan} class={rowStyles.cell()}>
50
+ <div
51
+ class={resolveSlotClass(
52
+ styles.container(),
53
+ styleConfig.slotClasses.loadingState,
54
+ styleConfig.unstyled,
55
+ className
56
+ )}
57
+ >
58
+ {#if showSpinner}
59
+ <Spinner size={SPINNER_SIZE_MAP[size]} intent="primary" label={displayText} />
60
+ {/if}
61
+
62
+ <div class={styles.content()}>
63
+ <h3 class={styles.text()}>
64
+ {displayText}
65
+ </h3>
66
+
67
+ {#if description}
68
+ <p class={styles.description()}>
69
+ {description}
70
+ </p>
71
+ {/if}
72
+ </div>
73
+ </div>
74
+ </td>
75
+ </tr>
@@ -0,0 +1,14 @@
1
+ import { type LoadingStateVariantProps } from '../variants';
2
+ export type LoadingStateProps = {
3
+ text?: string;
4
+ description?: string;
5
+ showSpinner?: boolean;
6
+ colSpan?: number;
7
+ class?: string;
8
+ testId?: string;
9
+ useI18n?: boolean;
10
+ size?: LoadingStateVariantProps['size'];
11
+ };
12
+ declare const LoadingState: import("svelte").Component<LoadingStateProps, {}, "">;
13
+ type LoadingState = ReturnType<typeof LoadingState>;
14
+ export default LoadingState;