@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,225 @@
|
|
|
1
|
+
<script lang="ts" generics="Item">
|
|
2
|
+
import { useTableI18n } from '../i18n';
|
|
3
|
+
import { numberCellVariants, type NumberCellVariantProps } from '../variants';
|
|
4
|
+
|
|
5
|
+
const tt = useTableI18n();
|
|
6
|
+
|
|
7
|
+
export type NumberCellProps<Item> = {
|
|
8
|
+
item: Item;
|
|
9
|
+
valueKey?: keyof Item;
|
|
10
|
+
value?: number | ((item: Item) => number);
|
|
11
|
+
format?: 'integer' | 'decimal' | 'currency' | 'percentage' | 'custom';
|
|
12
|
+
currency?: string;
|
|
13
|
+
locale?: string;
|
|
14
|
+
decimals?: number;
|
|
15
|
+
customFormatter?: (value: number) => string;
|
|
16
|
+
prefix?: string;
|
|
17
|
+
suffix?: string;
|
|
18
|
+
fallback?: string;
|
|
19
|
+
colorMode?: 'none' | 'positive-negative' | 'threshold';
|
|
20
|
+
thresholds?: {
|
|
21
|
+
danger?: number;
|
|
22
|
+
warning?: number;
|
|
23
|
+
success?: number;
|
|
24
|
+
};
|
|
25
|
+
onClick?: (item: Item, value: number | null) => void;
|
|
26
|
+
className?: string;
|
|
27
|
+
testId?: string;
|
|
28
|
+
} & NumberCellVariantProps;
|
|
29
|
+
|
|
30
|
+
// Props with sensible defaults
|
|
31
|
+
let {
|
|
32
|
+
item,
|
|
33
|
+
valueKey = undefined,
|
|
34
|
+
value = undefined,
|
|
35
|
+
format = 'decimal',
|
|
36
|
+
currency = 'EUR',
|
|
37
|
+
locale = 'de-DE',
|
|
38
|
+
decimals = 2,
|
|
39
|
+
customFormatter = undefined,
|
|
40
|
+
prefix = '',
|
|
41
|
+
suffix = '',
|
|
42
|
+
fallback = '—',
|
|
43
|
+
colorMode = 'none',
|
|
44
|
+
thresholds = {},
|
|
45
|
+
onClick = undefined,
|
|
46
|
+
className = '',
|
|
47
|
+
testId = undefined,
|
|
48
|
+
size = 'md',
|
|
49
|
+
align = 'right',
|
|
50
|
+
variant = 'default'
|
|
51
|
+
}: NumberCellProps<Item> = $props();
|
|
52
|
+
|
|
53
|
+
// Extract numeric value from item or prop
|
|
54
|
+
const extractValue = (item: Item): number | null => {
|
|
55
|
+
if (typeof value === 'function') {
|
|
56
|
+
return value(item);
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === 'number') {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
if (valueKey) {
|
|
62
|
+
const extractedValue = item[valueKey];
|
|
63
|
+
if (typeof extractedValue === 'number') return extractedValue;
|
|
64
|
+
if (typeof extractedValue === 'string') {
|
|
65
|
+
const parsed = parseFloat(extractedValue);
|
|
66
|
+
return isNaN(parsed) ? null : parsed;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Get the numeric value
|
|
73
|
+
const numericValue = $derived.by(() => extractValue(item));
|
|
74
|
+
|
|
75
|
+
// Check if value is clickable
|
|
76
|
+
const isClickable = $derived(Boolean(onClick && numericValue !== null));
|
|
77
|
+
|
|
78
|
+
// Format the number based on options
|
|
79
|
+
const formatNumber = (num: number | null): string => {
|
|
80
|
+
if (num === null) return fallback;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// Custom formatter takes precedence
|
|
84
|
+
if (customFormatter) {
|
|
85
|
+
return prefix + customFormatter(num) + suffix;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let formatted: string;
|
|
89
|
+
|
|
90
|
+
switch (format) {
|
|
91
|
+
case 'integer':
|
|
92
|
+
formatted = new Intl.NumberFormat(locale, {
|
|
93
|
+
maximumFractionDigits: 0
|
|
94
|
+
}).format(num);
|
|
95
|
+
break;
|
|
96
|
+
|
|
97
|
+
case 'decimal':
|
|
98
|
+
formatted = new Intl.NumberFormat(locale, {
|
|
99
|
+
minimumFractionDigits: decimals,
|
|
100
|
+
maximumFractionDigits: decimals
|
|
101
|
+
}).format(num);
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case 'currency':
|
|
105
|
+
formatted = new Intl.NumberFormat(locale, {
|
|
106
|
+
style: 'currency',
|
|
107
|
+
currency: currency,
|
|
108
|
+
minimumFractionDigits: decimals,
|
|
109
|
+
maximumFractionDigits: decimals
|
|
110
|
+
}).format(num);
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case 'percentage':
|
|
114
|
+
formatted = new Intl.NumberFormat(locale, {
|
|
115
|
+
style: 'percent',
|
|
116
|
+
minimumFractionDigits: decimals,
|
|
117
|
+
maximumFractionDigits: decimals
|
|
118
|
+
}).format(num / 100);
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
default:
|
|
122
|
+
formatted = new Intl.NumberFormat(locale).format(num);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return prefix + formatted + suffix;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.warn('NumberCell: Error formatting number', error);
|
|
128
|
+
return fallback;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Get formatted number string
|
|
133
|
+
const formattedNumber = $derived.by(() => formatNumber(numericValue));
|
|
134
|
+
|
|
135
|
+
// Determine color variant based on value
|
|
136
|
+
type NumberVariant = 'default' | 'neutral' | 'positive' | 'negative' | 'currency';
|
|
137
|
+
const getColorVariant = (num: number | null): NumberVariant => {
|
|
138
|
+
if (num === null || colorMode === 'none') return variant;
|
|
139
|
+
|
|
140
|
+
if (colorMode === 'positive-negative') {
|
|
141
|
+
if (num > 0) return 'positive';
|
|
142
|
+
if (num < 0) return 'negative';
|
|
143
|
+
return 'neutral';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (colorMode === 'threshold' && thresholds) {
|
|
147
|
+
if (thresholds.danger !== undefined && num <= thresholds.danger) {
|
|
148
|
+
return 'negative';
|
|
149
|
+
}
|
|
150
|
+
if (thresholds.warning !== undefined && num <= thresholds.warning) {
|
|
151
|
+
return 'neutral';
|
|
152
|
+
}
|
|
153
|
+
if (thresholds.success !== undefined && num >= thresholds.success) {
|
|
154
|
+
return 'positive';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return variant;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const colorVariant = $derived.by(() => getColorVariant(numericValue));
|
|
162
|
+
|
|
163
|
+
// Generate tooltip with raw value
|
|
164
|
+
const tooltipText = $derived.by(() => {
|
|
165
|
+
const num = numericValue;
|
|
166
|
+
if (num === null) return undefined;
|
|
167
|
+
|
|
168
|
+
// Show raw value if different from formatted
|
|
169
|
+
const raw = num.toString();
|
|
170
|
+
const formatted = formattedNumber;
|
|
171
|
+
|
|
172
|
+
if (raw !== formatted && !formatted.includes(raw)) {
|
|
173
|
+
return tt('number.valueLabel', { value: raw });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return undefined;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const tooltipValue = $derived(tooltipText);
|
|
180
|
+
|
|
181
|
+
// Generate test ID
|
|
182
|
+
const computedTestId = $derived.by(() => {
|
|
183
|
+
if (testId) return testId;
|
|
184
|
+
if (item && typeof item === 'object' && 'id' in item) {
|
|
185
|
+
return `number-cell-${item.id}-${String(valueKey)}`;
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const styles = $derived(numberCellVariants({ size, align, variant: colorVariant }));
|
|
191
|
+
|
|
192
|
+
// Event handlers
|
|
193
|
+
function handleClick(event: MouseEvent) {
|
|
194
|
+
const num = numericValue;
|
|
195
|
+
if (onClick && num !== null) {
|
|
196
|
+
event.stopPropagation();
|
|
197
|
+
onClick(item, num);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
202
|
+
if (isClickable && (event.key === 'Enter' || event.key === ' ')) {
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
const num = numericValue;
|
|
205
|
+
if (onClick && num !== null) {
|
|
206
|
+
onClick(item, num);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
213
|
+
<div
|
|
214
|
+
class="{styles.container()} {className}"
|
|
215
|
+
title={tooltipValue}
|
|
216
|
+
data-testid={computedTestId}
|
|
217
|
+
onclick={handleClick}
|
|
218
|
+
onkeydown={handleKeyDown}
|
|
219
|
+
role={isClickable ? 'button' : undefined}
|
|
220
|
+
tabindex={isClickable ? 0 : undefined}
|
|
221
|
+
>
|
|
222
|
+
<span class={styles.number()}>
|
|
223
|
+
{formattedNumber}
|
|
224
|
+
</span>
|
|
225
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type NumberCellVariantProps } from '../variants';
|
|
2
|
+
export type NumberCellProps<Item> = {
|
|
3
|
+
item: Item;
|
|
4
|
+
valueKey?: keyof Item;
|
|
5
|
+
value?: number | ((item: Item) => number);
|
|
6
|
+
format?: 'integer' | 'decimal' | 'currency' | 'percentage' | 'custom';
|
|
7
|
+
currency?: string;
|
|
8
|
+
locale?: string;
|
|
9
|
+
decimals?: number;
|
|
10
|
+
customFormatter?: (value: number) => string;
|
|
11
|
+
prefix?: string;
|
|
12
|
+
suffix?: string;
|
|
13
|
+
fallback?: string;
|
|
14
|
+
colorMode?: 'none' | 'positive-negative' | 'threshold';
|
|
15
|
+
thresholds?: {
|
|
16
|
+
danger?: number;
|
|
17
|
+
warning?: number;
|
|
18
|
+
success?: number;
|
|
19
|
+
};
|
|
20
|
+
onClick?: (item: Item, value: number | null) => void;
|
|
21
|
+
className?: string;
|
|
22
|
+
testId?: string;
|
|
23
|
+
} & NumberCellVariantProps;
|
|
24
|
+
declare function $$render<Item>(): {
|
|
25
|
+
props: NumberCellProps<Item>;
|
|
26
|
+
exports: {};
|
|
27
|
+
bindings: "";
|
|
28
|
+
slots: {};
|
|
29
|
+
events: {};
|
|
30
|
+
};
|
|
31
|
+
declare class __sveltets_Render<Item> {
|
|
32
|
+
props(): ReturnType<typeof $$render<Item>>['props'];
|
|
33
|
+
events(): ReturnType<typeof $$render<Item>>['events'];
|
|
34
|
+
slots(): ReturnType<typeof $$render<Item>>['slots'];
|
|
35
|
+
bindings(): "";
|
|
36
|
+
exports(): {};
|
|
37
|
+
}
|
|
38
|
+
interface $$IsomorphicComponent {
|
|
39
|
+
new <Item>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Item>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Item>['props']>, ReturnType<__sveltets_Render<Item>['events']>, ReturnType<__sveltets_Render<Item>['slots']>> & {
|
|
40
|
+
$$bindings?: ReturnType<__sveltets_Render<Item>['bindings']>;
|
|
41
|
+
} & ReturnType<__sveltets_Render<Item>['exports']>;
|
|
42
|
+
<Item>(internal: unknown, props: ReturnType<__sveltets_Render<Item>['props']> & {}): ReturnType<__sveltets_Render<Item>['exports']>;
|
|
43
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
44
|
+
}
|
|
45
|
+
declare const NumberCell: $$IsomorphicComponent;
|
|
46
|
+
type NumberCell<Item> = InstanceType<typeof NumberCell<Item>>;
|
|
47
|
+
export default NumberCell;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<script lang="ts" generics="Item">
|
|
2
|
+
import { Badge } from '@urbicon-ui/blocks';
|
|
3
|
+
import type { BadgeProps } from '@urbicon-ui/blocks';
|
|
4
|
+
import { useTableI18n } from '../i18n';
|
|
5
|
+
import { customCellVariants, type CustomCellVariantProps } from '../variants';
|
|
6
|
+
|
|
7
|
+
const tt = useTableI18n();
|
|
8
|
+
|
|
9
|
+
export type StatusConfig = {
|
|
10
|
+
intent: BadgeProps['intent'];
|
|
11
|
+
text: string;
|
|
12
|
+
icon: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type StatusBadgeProps<Item> = {
|
|
16
|
+
item: Item;
|
|
17
|
+
statusKey?: keyof Item;
|
|
18
|
+
statusMap?: Record<string, StatusConfig>;
|
|
19
|
+
size?: BadgeProps['size'];
|
|
20
|
+
/** Visual variant — restricted to label variants. The `dot` arm of Badge forbids children. */
|
|
21
|
+
variant?: Exclude<BadgeProps['variant'], 'dot'>;
|
|
22
|
+
onClick?: (item: Item, status: string) => void;
|
|
23
|
+
className?: string;
|
|
24
|
+
testId?: string;
|
|
25
|
+
align?: CustomCellVariantProps['align'];
|
|
26
|
+
interactive?: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const defaultStatusMap: Record<string, StatusConfig> = $derived({
|
|
30
|
+
active: { intent: 'success', text: tt('status.active'), icon: true },
|
|
31
|
+
inactive: { intent: 'danger', text: tt('status.inactive'), icon: true },
|
|
32
|
+
pending: { intent: 'warning', text: tt('status.pending'), icon: true },
|
|
33
|
+
online: { intent: 'success', text: tt('status.online'), icon: true },
|
|
34
|
+
offline: { intent: 'neutral', text: tt('status.offline'), icon: true },
|
|
35
|
+
processing: { intent: 'primary', text: tt('status.processing'), icon: true },
|
|
36
|
+
completed: { intent: 'success', text: tt('status.completed'), icon: false },
|
|
37
|
+
failed: { intent: 'danger', text: tt('status.failed'), icon: false },
|
|
38
|
+
draft: { intent: 'neutral', text: tt('status.draft'), icon: false },
|
|
39
|
+
published: { intent: 'success', text: tt('status.published'), icon: true },
|
|
40
|
+
archived: { intent: 'neutral', text: tt('status.archived'), icon: false }
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const staticFallbackConfig: StatusConfig = { intent: 'neutral', text: '', icon: false };
|
|
44
|
+
|
|
45
|
+
let {
|
|
46
|
+
item,
|
|
47
|
+
statusKey = 'status' as keyof Item,
|
|
48
|
+
statusMap = undefined as Record<string, StatusConfig> | undefined,
|
|
49
|
+
size = 'sm',
|
|
50
|
+
variant = 'filled',
|
|
51
|
+
onClick = undefined,
|
|
52
|
+
className = '',
|
|
53
|
+
testId = undefined,
|
|
54
|
+
align = 'center',
|
|
55
|
+
interactive = false
|
|
56
|
+
}: StatusBadgeProps<Item> = $props();
|
|
57
|
+
|
|
58
|
+
const mergedStatusMap = $derived({ ...defaultStatusMap, ...statusMap });
|
|
59
|
+
const statusValue = $derived(String(item[statusKey]) || 'default');
|
|
60
|
+
const fallbackConfig: StatusConfig = $derived({
|
|
61
|
+
...staticFallbackConfig,
|
|
62
|
+
text: tt('status.unknown')
|
|
63
|
+
});
|
|
64
|
+
const config = $derived(mergedStatusMap[statusValue] || fallbackConfig);
|
|
65
|
+
const isClickable = $derived(Boolean(onClick && !interactive === false));
|
|
66
|
+
|
|
67
|
+
const computedTestId = $derived(() => {
|
|
68
|
+
if (testId) return testId;
|
|
69
|
+
if (item && typeof item === 'object' && 'id' in item) {
|
|
70
|
+
return `status-badge-${item.id}`;
|
|
71
|
+
}
|
|
72
|
+
return 'status-badge';
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const containerStyles = $derived(
|
|
76
|
+
customCellVariants({
|
|
77
|
+
align,
|
|
78
|
+
interactive: isClickable,
|
|
79
|
+
size: 'md'
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
function handleClick(event: MouseEvent) {
|
|
84
|
+
if (onClick) {
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
onClick(item, statusValue);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
91
|
+
if (isClickable && (event.key === 'Enter' || event.key === ' ')) {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
if (onClick) {
|
|
94
|
+
onClick(item, statusValue);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
101
|
+
<div
|
|
102
|
+
class="{containerStyles.container()} {className}"
|
|
103
|
+
data-testid={computedTestId()}
|
|
104
|
+
onclick={handleClick}
|
|
105
|
+
onkeydown={handleKeyDown}
|
|
106
|
+
role={isClickable ? 'button' : undefined}
|
|
107
|
+
tabindex={isClickable ? 0 : undefined}
|
|
108
|
+
title={`${tt('status.tooltip', { text: config.text })}${isClickable ? ` ${tt('status.clickToChange')}` : ''}`}
|
|
109
|
+
>
|
|
110
|
+
<div class={containerStyles.content()}>
|
|
111
|
+
<!-- Badge with max-width and truncation -->
|
|
112
|
+
<div class="inline-block max-w-32">
|
|
113
|
+
<Badge {size} {variant} intent={config.intent} class="w-full truncate">
|
|
114
|
+
{#if config.icon}
|
|
115
|
+
<div class="mr-1 h-1.5 w-1.5 shrink-0 rounded-full bg-current opacity-75"></div>
|
|
116
|
+
{/if}
|
|
117
|
+
<span class="truncate">{config.text}</span>
|
|
118
|
+
</Badge>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { BadgeProps } from '@urbicon-ui/blocks';
|
|
2
|
+
import { type CustomCellVariantProps } from '../variants';
|
|
3
|
+
export type StatusConfig = {
|
|
4
|
+
intent: BadgeProps['intent'];
|
|
5
|
+
text: string;
|
|
6
|
+
icon: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type StatusBadgeProps<Item> = {
|
|
9
|
+
item: Item;
|
|
10
|
+
statusKey?: keyof Item;
|
|
11
|
+
statusMap?: Record<string, StatusConfig>;
|
|
12
|
+
size?: BadgeProps['size'];
|
|
13
|
+
/** Visual variant — restricted to label variants. The `dot` arm of Badge forbids children. */
|
|
14
|
+
variant?: Exclude<BadgeProps['variant'], 'dot'>;
|
|
15
|
+
onClick?: (item: Item, status: string) => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
testId?: string;
|
|
18
|
+
align?: CustomCellVariantProps['align'];
|
|
19
|
+
interactive?: boolean;
|
|
20
|
+
};
|
|
21
|
+
declare function $$render<Item>(): {
|
|
22
|
+
props: StatusBadgeProps<Item>;
|
|
23
|
+
exports: {};
|
|
24
|
+
bindings: "";
|
|
25
|
+
slots: {};
|
|
26
|
+
events: {};
|
|
27
|
+
};
|
|
28
|
+
declare class __sveltets_Render<Item> {
|
|
29
|
+
props(): ReturnType<typeof $$render<Item>>['props'];
|
|
30
|
+
events(): ReturnType<typeof $$render<Item>>['events'];
|
|
31
|
+
slots(): ReturnType<typeof $$render<Item>>['slots'];
|
|
32
|
+
bindings(): "";
|
|
33
|
+
exports(): {};
|
|
34
|
+
}
|
|
35
|
+
interface $$IsomorphicComponent {
|
|
36
|
+
new <Item>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Item>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Item>['props']>, ReturnType<__sveltets_Render<Item>['events']>, ReturnType<__sveltets_Render<Item>['slots']>> & {
|
|
37
|
+
$$bindings?: ReturnType<__sveltets_Render<Item>['bindings']>;
|
|
38
|
+
} & ReturnType<__sveltets_Render<Item>['exports']>;
|
|
39
|
+
<Item>(internal: unknown, props: ReturnType<__sveltets_Render<Item>['props']> & {}): ReturnType<__sveltets_Render<Item>['exports']>;
|
|
40
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
41
|
+
}
|
|
42
|
+
declare const StatusBadge: $$IsomorphicComponent;
|
|
43
|
+
type StatusBadge<Item> = InstanceType<typeof StatusBadge<Item>>;
|
|
44
|
+
export default StatusBadge;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts" generics="Item">
|
|
2
|
+
// Use our own Avatar component instead of Flowbite
|
|
3
|
+
import { Avatar } from '@urbicon-ui/blocks';
|
|
4
|
+
import SearchHighlight from '../features/SearchHighlight.svelte';
|
|
5
|
+
import { userCellVariants, type UserCellVariantProps } from '../variants';
|
|
6
|
+
|
|
7
|
+
export type UserAvatarProps<Item> = {
|
|
8
|
+
item: Item;
|
|
9
|
+
nameKey?: keyof Item;
|
|
10
|
+
emailKey?: keyof Item;
|
|
11
|
+
avatarKey?: keyof Item;
|
|
12
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
13
|
+
layout?: 'horizontal' | 'vertical' | 'compact';
|
|
14
|
+
showEmail?: boolean;
|
|
15
|
+
clickable?: boolean;
|
|
16
|
+
onClick?: (item: Item) => void;
|
|
17
|
+
mobile?: boolean;
|
|
18
|
+
} & UserCellVariantProps;
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
item,
|
|
22
|
+
nameKey = 'name' as keyof Item,
|
|
23
|
+
emailKey = 'email' as keyof Item,
|
|
24
|
+
avatarKey = 'avatar' as keyof Item,
|
|
25
|
+
size = 'md',
|
|
26
|
+
layout = 'horizontal',
|
|
27
|
+
showEmail = true,
|
|
28
|
+
clickable = false,
|
|
29
|
+
onClick = undefined,
|
|
30
|
+
mobile = false
|
|
31
|
+
}: UserAvatarProps<Item> = $props();
|
|
32
|
+
|
|
33
|
+
// Performance-optimized values via $derived
|
|
34
|
+
const name = $derived.by(() => (item[nameKey] != null ? String(item[nameKey]) : ''));
|
|
35
|
+
const email = $derived.by(() => (item[emailKey] != null ? String(item[emailKey]) : ''));
|
|
36
|
+
const avatarUrl = $derived.by(() => (item[avatarKey] != null ? String(item[avatarKey]) : ''));
|
|
37
|
+
|
|
38
|
+
const avatarSize = $derived(mobile ? (size === 'lg' ? 'md' : 'sm') : size);
|
|
39
|
+
|
|
40
|
+
const styles = $derived(userCellVariants({ layout, size, clickable }));
|
|
41
|
+
|
|
42
|
+
function handleClick() {
|
|
43
|
+
if (clickable && onClick) {
|
|
44
|
+
onClick(item);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
50
|
+
<div
|
|
51
|
+
class={styles.container()}
|
|
52
|
+
onclick={handleClick}
|
|
53
|
+
role={clickable ? 'button' : undefined}
|
|
54
|
+
tabindex={clickable ? 0 : undefined}
|
|
55
|
+
>
|
|
56
|
+
<div class={styles.avatar()}>
|
|
57
|
+
<Avatar size={avatarSize} src={avatarUrl} alt={name} {name} variant="circle" />
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class={styles.content()}>
|
|
61
|
+
<div class={styles.name()} title={name}>
|
|
62
|
+
<SearchHighlight text={name} />
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{#if showEmail && email}
|
|
66
|
+
<div class={styles.email()} title={email}>
|
|
67
|
+
<SearchHighlight text={email} />
|
|
68
|
+
</div>
|
|
69
|
+
{/if}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type UserCellVariantProps } from '../variants';
|
|
2
|
+
export type UserAvatarProps<Item> = {
|
|
3
|
+
item: Item;
|
|
4
|
+
nameKey?: keyof Item;
|
|
5
|
+
emailKey?: keyof Item;
|
|
6
|
+
avatarKey?: keyof Item;
|
|
7
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
8
|
+
layout?: 'horizontal' | 'vertical' | 'compact';
|
|
9
|
+
showEmail?: boolean;
|
|
10
|
+
clickable?: boolean;
|
|
11
|
+
onClick?: (item: Item) => void;
|
|
12
|
+
mobile?: boolean;
|
|
13
|
+
} & UserCellVariantProps;
|
|
14
|
+
declare function $$render<Item>(): {
|
|
15
|
+
props: UserAvatarProps<Item>;
|
|
16
|
+
exports: {};
|
|
17
|
+
bindings: "";
|
|
18
|
+
slots: {};
|
|
19
|
+
events: {};
|
|
20
|
+
};
|
|
21
|
+
declare class __sveltets_Render<Item> {
|
|
22
|
+
props(): ReturnType<typeof $$render<Item>>['props'];
|
|
23
|
+
events(): ReturnType<typeof $$render<Item>>['events'];
|
|
24
|
+
slots(): ReturnType<typeof $$render<Item>>['slots'];
|
|
25
|
+
bindings(): "";
|
|
26
|
+
exports(): {};
|
|
27
|
+
}
|
|
28
|
+
interface $$IsomorphicComponent {
|
|
29
|
+
new <Item>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Item>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Item>['props']>, ReturnType<__sveltets_Render<Item>['events']>, ReturnType<__sveltets_Render<Item>['slots']>> & {
|
|
30
|
+
$$bindings?: ReturnType<__sveltets_Render<Item>['bindings']>;
|
|
31
|
+
} & ReturnType<__sveltets_Render<Item>['exports']>;
|
|
32
|
+
<Item>(internal: unknown, props: ReturnType<__sveltets_Render<Item>['props']> & {}): ReturnType<__sveltets_Render<Item>['exports']>;
|
|
33
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
34
|
+
}
|
|
35
|
+
declare const UserAvatar: $$IsomorphicComponent;
|
|
36
|
+
type UserAvatar<Item> = InstanceType<typeof UserAvatar<Item>>;
|
|
37
|
+
export default UserAvatar;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { type ActionButtonsProps, default as ActionButtons, type ExtraAction } from './ActionButtons.svelte';
|
|
2
|
+
export { type CopyButtonProps, default as CopyButton } from './CopyButton.svelte';
|
|
3
|
+
export { type CustomCellProps, default as CustomCell } from './CustomCell.svelte';
|
|
4
|
+
export { type DateCellProps, default as DateCell } from './DateCell.svelte';
|
|
5
|
+
export { default as LinkCell, type LinkCellProps } from './LinkCell.svelte';
|
|
6
|
+
export { default as NumberCell, type NumberCellProps } from './NumberCell.svelte';
|
|
7
|
+
export { default as StatusBadge, type StatusBadgeProps } from './StatusBadge.svelte';
|
|
8
|
+
export { default as UserAvatar, type UserAvatarProps } from './UserAvatar.svelte';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Cell Components
|
|
2
|
+
export { default as ActionButtons } from './ActionButtons.svelte';
|
|
3
|
+
export { default as CopyButton } from './CopyButton.svelte';
|
|
4
|
+
export { default as CustomCell } from './CustomCell.svelte';
|
|
5
|
+
export { default as DateCell } from './DateCell.svelte';
|
|
6
|
+
export { default as LinkCell } from './LinkCell.svelte';
|
|
7
|
+
export { default as NumberCell } from './NumberCell.svelte';
|
|
8
|
+
export { default as StatusBadge } from './StatusBadge.svelte';
|
|
9
|
+
export { default as UserAvatar } from './UserAvatar.svelte';
|