@placeholderco/placeholder-ui 1.0.3 → 1.0.6

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 (136) hide show
  1. package/LICENSE +26 -26
  2. package/README.md +179 -179
  3. package/dist/display/Alert.svelte +179 -179
  4. package/dist/display/Avatar.svelte +166 -166
  5. package/dist/display/LinkCollection.svelte +161 -161
  6. package/dist/display/Paper.svelte +118 -118
  7. package/dist/form/Autocomplete.svelte +223 -191
  8. package/dist/form/Autocomplete.svelte.d.ts +3 -1
  9. package/dist/form/AutocompleteMulti.svelte +356 -0
  10. package/dist/form/AutocompleteMulti.svelte.d.ts +28 -0
  11. package/dist/form/Checkbox.svelte +201 -201
  12. package/dist/form/Chips.svelte +128 -128
  13. package/dist/form/ComboBox.svelte +158 -158
  14. package/dist/form/ComboBox.svelte.d.ts +1 -1
  15. package/dist/form/ComboBoxItemBuilder.svelte +460 -460
  16. package/dist/form/ComboBoxMulti.svelte +197 -197
  17. package/dist/form/ComboBoxMulti.svelte.d.ts +1 -1
  18. package/dist/form/CronBuilder.svelte +693 -693
  19. package/dist/form/DatePicker.svelte +672 -672
  20. package/dist/form/DateTimePicker.svelte +712 -712
  21. package/dist/form/FileInput.svelte +235 -235
  22. package/dist/form/FormGroup.svelte +68 -68
  23. package/dist/form/Number.svelte +238 -238
  24. package/dist/form/PasswordInput.svelte +252 -252
  25. package/dist/form/RadioGroup.svelte +210 -210
  26. package/dist/form/Rating.svelte +235 -235
  27. package/dist/form/SegmentedControl.svelte +149 -149
  28. package/dist/form/Select.svelte +590 -590
  29. package/dist/form/Select.svelte.d.ts +1 -1
  30. package/dist/form/SelectMulti.svelte +613 -613
  31. package/dist/form/SelectMulti.svelte.d.ts +1 -1
  32. package/dist/form/Slider.svelte +358 -358
  33. package/dist/form/Switch.svelte +147 -147
  34. package/dist/form/TextArea.svelte +148 -148
  35. package/dist/form/Textbox.svelte +228 -228
  36. package/dist/form/TimePicker.svelte +267 -267
  37. package/dist/icon/Icon.svelte +52 -52
  38. package/dist/icon/alert-octagon.svg +5 -5
  39. package/dist/icon/alert-triangle.svg +5 -5
  40. package/dist/icon/archive.svg +1 -1
  41. package/dist/icon/arrow-down.svg +1 -1
  42. package/dist/icon/arrow-left.svg +1 -1
  43. package/dist/icon/arrow-right.svg +1 -1
  44. package/dist/icon/arrow-up.svg +1 -1
  45. package/dist/icon/at.svg +1 -1
  46. package/dist/icon/bell.svg +1 -1
  47. package/dist/icon/bookmark.svg +1 -1
  48. package/dist/icon/calendar.svg +1 -1
  49. package/dist/icon/camera.svg +1 -1
  50. package/dist/icon/chart-bar.svg +1 -1
  51. package/dist/icon/chart-line.svg +1 -1
  52. package/dist/icon/chart-pie.svg +1 -1
  53. package/dist/icon/checkbox.svg +1 -1
  54. package/dist/icon/checklist.svg +1 -1
  55. package/dist/icon/circle-check.svg +1 -1
  56. package/dist/icon/circle-x.svg +1 -1
  57. package/dist/icon/clock.svg +1 -1
  58. package/dist/icon/credit-card.svg +1 -1
  59. package/dist/icon/dots-vertical.svg +1 -1
  60. package/dist/icon/dots.svg +1 -1
  61. package/dist/icon/external-link.svg +1 -1
  62. package/dist/icon/eye-off.svg +1 -1
  63. package/dist/icon/eye.svg +1 -1
  64. package/dist/icon/filter.svg +1 -1
  65. package/dist/icon/fingerprint.svg +1 -1
  66. package/dist/icon/flag.svg +1 -1
  67. package/dist/icon/heart.svg +1 -1
  68. package/dist/icon/home.svg +1 -1
  69. package/dist/icon/key.svg +1 -1
  70. package/dist/icon/list-check.svg +1 -1
  71. package/dist/icon/login.svg +1 -1
  72. package/dist/icon/logout.svg +1 -1
  73. package/dist/icon/map-pin.svg +1 -1
  74. package/dist/icon/maximize.svg +1 -1
  75. package/dist/icon/microphone.svg +1 -1
  76. package/dist/icon/minimize.svg +1 -1
  77. package/dist/icon/note.svg +1 -1
  78. package/dist/icon/player-pause.svg +1 -1
  79. package/dist/icon/printer.svg +1 -1
  80. package/dist/icon/qrcode.svg +1 -1
  81. package/dist/icon/send.svg +1 -1
  82. package/dist/icon/settings.svg +1 -1
  83. package/dist/icon/share.svg +1 -1
  84. package/dist/icon/shopping-cart.svg +1 -1
  85. package/dist/icon/sort-ascending.svg +1 -1
  86. package/dist/icon/sort-descending.svg +1 -1
  87. package/dist/icon/star.svg +1 -1
  88. package/dist/icon/tag.svg +1 -1
  89. package/dist/icon/trending-down.svg +1 -1
  90. package/dist/icon/trending-up.svg +1 -1
  91. package/dist/icon/upload.svg +1 -1
  92. package/dist/icon/volume-off.svg +1 -1
  93. package/dist/icon/volume.svg +1 -1
  94. package/dist/icon/world.svg +1 -1
  95. package/dist/icon/zoom-in.svg +1 -1
  96. package/dist/icon/zoom-out.svg +1 -1
  97. package/dist/index.d.ts +1 -0
  98. package/dist/index.js +1 -0
  99. package/dist/layout/AppShell.svelte +169 -169
  100. package/dist/layout/CustomNavbar.svelte +61 -61
  101. package/dist/layout/Navbar.svelte +206 -206
  102. package/dist/layout/NavbarItemDisplay.svelte +29 -29
  103. package/dist/layout/Sidenav.svelte +712 -712
  104. package/dist/styles/components.css +199 -199
  105. package/dist/styles/dark.css +146 -146
  106. package/dist/styles/index.css +116 -116
  107. package/dist/styles/reset.css +110 -110
  108. package/dist/styles/semantic.css +86 -86
  109. package/dist/styles/tokens.css +203 -197
  110. package/dist/styles/utilities.css +523 -523
  111. package/dist/ui/Accordion.svelte +289 -289
  112. package/dist/ui/ActionIcon.svelte +76 -76
  113. package/dist/ui/Badge.svelte +329 -279
  114. package/dist/ui/Breadcrumbs.svelte +131 -131
  115. package/dist/ui/Button.svelte +432 -370
  116. package/dist/ui/ButtonVariant.d.ts +1 -1
  117. package/dist/ui/Dialog.svelte +307 -307
  118. package/dist/ui/Drawer.svelte +524 -524
  119. package/dist/ui/Dropdown.svelte +97 -97
  120. package/dist/ui/Dropzone.svelte +122 -122
  121. package/dist/ui/Link.svelte +32 -32
  122. package/dist/ui/Loader.svelte +70 -70
  123. package/dist/ui/LoadingOverlay.svelte +53 -53
  124. package/dist/ui/Pagination.svelte +135 -135
  125. package/dist/ui/Popover.svelte +225 -225
  126. package/dist/ui/Progress.svelte +191 -191
  127. package/dist/ui/RingProgress.svelte +141 -141
  128. package/dist/ui/Skeleton.svelte +85 -85
  129. package/dist/ui/Stepper.svelte +355 -355
  130. package/dist/ui/Table.svelte +345 -345
  131. package/dist/ui/Tabs.svelte +146 -146
  132. package/dist/ui/ThemeSwitcher.svelte +39 -39
  133. package/dist/ui/Timeline.svelte +225 -225
  134. package/dist/ui/Toaster.svelte +6 -6
  135. package/dist/ui/Tooltip.svelte +434 -434
  136. package/package.json +14 -14
@@ -1,346 +1,346 @@
1
- <script lang="ts">
2
- import { type Snippet } from 'svelte';
3
- import Textbox from '../form/Textbox.svelte';
4
- import Icon from '../icon/Icon.svelte';
5
- import { iconArrowsSort, iconChevronDown, iconChevronUp, iconSearch } from '../icon/index.js';
6
-
7
- export interface Column<T = any> {
8
- key: string;
9
- label: string;
10
- sortable?: boolean;
11
- width?: string;
12
- align?: 'left' | 'center' | 'right';
13
- render?: (value: any, row: T) => any;
14
- }
15
-
16
- interface Props<T = any> {
17
- columns: Column<T>[];
18
- rows: T[];
19
- searchable?: boolean;
20
- searchPlaceholder?: string;
21
- class?: string;
22
- striped?: boolean;
23
- notRounded?: boolean;
24
- hover?: boolean;
25
- emptyMessage?: string;
26
- loading?: boolean;
27
- children?: Snippet;
28
- buttons?: Snippet<[T, number]>;
29
- onrowclick?: (row: T, index: number) => void;
30
- }
31
-
32
- let {
33
- columns = [],
34
- rows = [],
35
- searchable = false,
36
- searchPlaceholder = 'Search...',
37
- class: tableClass = '',
38
- striped = false,
39
- notRounded = false,
40
- hover = true,
41
- emptyMessage = 'No data available',
42
- loading = false,
43
- children,
44
- buttons,
45
- onrowclick
46
- }: Props = $props();
47
-
48
- let searchQuery = $state('');
49
- let sortColumn = $state<string | null>(null);
50
- let sortDirection = $state<'asc' | 'desc'>('asc');
51
-
52
- function handleSort(column: Column) {
53
- if (!column.sortable) return;
54
-
55
- if (sortColumn === column.key) {
56
- sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
57
- } else {
58
- sortColumn = column.key;
59
- sortDirection = 'asc';
60
- }
61
- }
62
-
63
- function getValue(row: any, key: string): any {
64
- const keys = key.split('.');
65
- let value = row;
66
- for (const k of keys) {
67
- value = value?.[k];
68
- }
69
- return value;
70
- }
71
-
72
- const filteredRows = $derived.by(() => {
73
- let filtered = [...rows];
74
-
75
- // Apply search filter
76
- if (searchable && searchQuery) {
77
- filtered = filtered.filter(row => {
78
- return columns.some(column => {
79
- const value = getValue(row, column.key);
80
- if (value === null || value === undefined) return false;
81
- return String(value).toLowerCase().includes(searchQuery.toLowerCase());
82
- });
83
- });
84
- }
85
-
86
- // Apply sorting
87
- if (sortColumn) {
88
- filtered.sort((a, b) => {
89
- const aValue = getValue(a, sortColumn!);
90
- const bValue = getValue(b, sortColumn!);
91
-
92
- if (aValue === null || aValue === undefined) return 1;
93
- if (bValue === null || bValue === undefined) return -1;
94
-
95
- let comparison = 0;
96
- if (typeof aValue === 'number' && typeof bValue === 'number') {
97
- comparison = aValue - bValue;
98
- } else {
99
- comparison = String(aValue).localeCompare(String(bValue));
100
- }
101
-
102
- return sortDirection === 'asc' ? comparison : -comparison;
103
- });
104
- }
105
-
106
- return filtered;
107
- });
108
-
109
- const getSortIcon = (column: Column) => {
110
- if (!column.sortable) return '';
111
- if (sortColumn !== column.key) return iconArrowsSort;
112
- return sortDirection === 'asc' ? iconChevronUp : iconChevronDown;
113
- };
114
-
115
- const tableClasses = $derived(
116
- `pui-table ${tableClass} ${striped ? 'striped' : ''} ${hover ? 'hover' : ''}`
117
- );
118
- </script>
119
-
120
- <div class="table-container">
121
- {#if searchable}
122
- <div class="table-search">
123
- <Textbox
124
- placeholder={searchPlaceholder}
125
- bind:value={searchQuery}
126
- leftIconSvg={iconSearch}
127
- class="search-input"
128
- />
129
- </div>
130
- {/if}
131
-
132
- <div class="table-wrapper {notRounded ? '' : 'rounded'}">
133
- <table class={tableClasses}>
134
- <thead>
135
- <tr>
136
- {#each columns as column}
137
- <th
138
- class="table-header {column.sortable ? 'sortable' : ''} {column.align || 'left'}"
139
- style={column.width ? `width: ${column.width}` : ''}
140
- onclick={() => handleSort(column)}
141
- >
142
- <div class="header-content">
143
- <span>{column.label}</span>
144
- {#if column.sortable}
145
- <div class="sort-icon">
146
- <Icon
147
- svg={getSortIcon(column)}
148
- size="14px"
149
- />
150
- </div>
151
- {/if}
152
- </div>
153
- </th>
154
- {/each}
155
- {#if buttons}
156
- <th class="table-header center">Actions</th>
157
- {/if}
158
- </tr>
159
- </thead>
160
- <tbody>
161
- {#if loading}
162
- <tr>
163
- <td colspan={columns.length + (buttons ? 1 : 0)} class="loading-cell">
164
- <div class="loading-content">Loading...</div>
165
- </td>
166
- </tr>
167
- {:else if filteredRows.length === 0}
168
- <tr>
169
- <td colspan={columns.length + (buttons ? 1 : 0)} class="empty-cell">
170
- <div class="empty-content">{emptyMessage}</div>
171
- </td>
172
- </tr>
173
- {:else}
174
- {#each filteredRows as row, index}
175
- <tr
176
- class={onrowclick ? 'clickable-row' : ''}
177
- onclick={() => onrowclick?.(row, index)}
178
- >
179
- {#each columns as column}
180
- {@const value = getValue(row, column.key)}
181
- <td class="table-cell {column.align || 'left'}">
182
- {#if column.render}
183
- {@html column.render(value, row)}
184
- {:else if typeof value === 'function'}
185
- {@render value()}
186
- {:else}
187
- {value ?? ''}
188
- {/if}
189
- </td>
190
- {/each}
191
- {#if buttons}
192
- <td class="table-cell center">
193
- {@render buttons(row, index)}
194
- </td>
195
- {/if}
196
- </tr>
197
- {/each}
198
- {/if}
199
- </tbody>
200
- </table>
201
- </div>
202
-
203
- {@render children?.()}
204
- </div>
205
-
206
- <style>
207
- .table-container {
208
- width: 100%;
209
- }
210
-
211
- .table-search {
212
- margin-bottom: var(--pui-spacing-4);
213
- max-width: 400px;
214
- }
215
-
216
- .table-wrapper {
217
- width: 100%;
218
- overflow-x: auto;
219
- border: 1px solid var(--pui-border-default);
220
- background-color: var(--pui-input-bg);
221
- }
222
-
223
- .table-wrapper.rounded {
224
- border-radius: var(--pui-radius-lg);
225
- }
226
-
227
- .pui-table {
228
- width: 100%;
229
- border-collapse: collapse;
230
- font-size: var(--pui-font-size-base);
231
- }
232
-
233
- .table-header {
234
- padding: var(--pui-spacing-2);
235
- text-align: left;
236
- font-weight: var(--pui-font-weight-semibold);
237
- color: var(--pui-text-primary);
238
- background-color: var(--pui-bg-surface-raised);
239
- border-bottom: 2px solid var(--pui-border-default);
240
- user-select: none;
241
- white-space: nowrap;
242
- }
243
-
244
- .table-header.sortable {
245
- cursor: pointer;
246
- transition: background-color var(--pui-transition-fast) var(--pui-ease-out);
247
- }
248
-
249
- .table-header.sortable:hover {
250
- background-color: var(--pui-bg-hover);
251
- }
252
-
253
- .table-header.center {
254
- text-align: center;
255
- }
256
-
257
- .table-header.right {
258
- text-align: right;
259
- }
260
-
261
- .header-content {
262
- display: flex;
263
- align-items: center;
264
- gap: var(--pui-spacing-2);
265
- justify-content: space-between;
266
- }
267
-
268
- .sort-icon {
269
- display: inline-flex;
270
- align-items: center;
271
- opacity: 0.5;
272
- transition: opacity var(--pui-transition-fast) var(--pui-ease-out);
273
- }
274
-
275
- .table-header.sortable:hover .sort-icon {
276
- opacity: 1;
277
- }
278
-
279
- .table-cell {
280
- padding: var(--pui-spacing-2);
281
- text-align: left;
282
- color: var(--pui-text-primary);
283
- border-bottom: 1px solid var(--pui-border-default);
284
- }
285
-
286
- .table-cell.center {
287
- text-align: center;
288
- }
289
-
290
- .table-cell.right {
291
- text-align: right;
292
- }
293
-
294
- .pui-table.striped tbody tr:nth-child(even) {
295
- background-color: var(--pui-bg-hover);
296
- }
297
-
298
- .pui-table.hover tbody tr:hover {
299
- background-color: var(--pui-bg-hover);
300
- transition: background-color var(--pui-transition-fast) var(--pui-ease-out);
301
- }
302
-
303
- .clickable-row {
304
- cursor: pointer;
305
- }
306
-
307
- .clickable-row:hover {
308
- background-color: var(--pui-bg-hover);
309
- transition: background-color var(--pui-transition-fast) var(--pui-ease-out);
310
- }
311
-
312
- tbody tr:last-child td {
313
- border-bottom: none;
314
- }
315
-
316
- .empty-cell,
317
- .loading-cell {
318
- padding: var(--pui-spacing-8);
319
- text-align: center;
320
- color: var(--pui-text-muted);
321
- border-bottom: none;
322
- }
323
-
324
- .empty-content,
325
- .loading-content {
326
- font-style: italic;
327
- }
328
-
329
- /* Icon styles for sort indicators */
330
- :global(.pui-table .sort-icon svg) {
331
- width: 14px;
332
- height: 14px;
333
- }
334
-
335
- /* Responsive adjustments */
336
- @media (max-width: 640px) {
337
- .table-header,
338
- .table-cell {
339
- padding: var(--pui-spacing-2) var(--pui-spacing-3);
340
- }
341
-
342
- .pui-table {
343
- font-size: var(--pui-font-size-xs);
344
- }
345
- }
1
+ <script lang="ts">
2
+ import { type Snippet } from 'svelte';
3
+ import Textbox from '../form/Textbox.svelte';
4
+ import Icon from '../icon/Icon.svelte';
5
+ import { iconArrowsSort, iconChevronDown, iconChevronUp, iconSearch } from '../icon/index.js';
6
+
7
+ export interface Column<T = any> {
8
+ key: string;
9
+ label: string;
10
+ sortable?: boolean;
11
+ width?: string;
12
+ align?: 'left' | 'center' | 'right';
13
+ render?: (value: any, row: T) => any;
14
+ }
15
+
16
+ interface Props<T = any> {
17
+ columns: Column<T>[];
18
+ rows: T[];
19
+ searchable?: boolean;
20
+ searchPlaceholder?: string;
21
+ class?: string;
22
+ striped?: boolean;
23
+ notRounded?: boolean;
24
+ hover?: boolean;
25
+ emptyMessage?: string;
26
+ loading?: boolean;
27
+ children?: Snippet;
28
+ buttons?: Snippet<[T, number]>;
29
+ onrowclick?: (row: T, index: number) => void;
30
+ }
31
+
32
+ let {
33
+ columns = [],
34
+ rows = [],
35
+ searchable = false,
36
+ searchPlaceholder = 'Search...',
37
+ class: tableClass = '',
38
+ striped = false,
39
+ notRounded = false,
40
+ hover = true,
41
+ emptyMessage = 'No data available',
42
+ loading = false,
43
+ children,
44
+ buttons,
45
+ onrowclick
46
+ }: Props = $props();
47
+
48
+ let searchQuery = $state('');
49
+ let sortColumn = $state<string | null>(null);
50
+ let sortDirection = $state<'asc' | 'desc'>('asc');
51
+
52
+ function handleSort(column: Column) {
53
+ if (!column.sortable) return;
54
+
55
+ if (sortColumn === column.key) {
56
+ sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
57
+ } else {
58
+ sortColumn = column.key;
59
+ sortDirection = 'asc';
60
+ }
61
+ }
62
+
63
+ function getValue(row: any, key: string): any {
64
+ const keys = key.split('.');
65
+ let value = row;
66
+ for (const k of keys) {
67
+ value = value?.[k];
68
+ }
69
+ return value;
70
+ }
71
+
72
+ const filteredRows = $derived.by(() => {
73
+ let filtered = [...rows];
74
+
75
+ // Apply search filter
76
+ if (searchable && searchQuery) {
77
+ filtered = filtered.filter(row => {
78
+ return columns.some(column => {
79
+ const value = getValue(row, column.key);
80
+ if (value === null || value === undefined) return false;
81
+ return String(value).toLowerCase().includes(searchQuery.toLowerCase());
82
+ });
83
+ });
84
+ }
85
+
86
+ // Apply sorting
87
+ if (sortColumn) {
88
+ filtered.sort((a, b) => {
89
+ const aValue = getValue(a, sortColumn!);
90
+ const bValue = getValue(b, sortColumn!);
91
+
92
+ if (aValue === null || aValue === undefined) return 1;
93
+ if (bValue === null || bValue === undefined) return -1;
94
+
95
+ let comparison = 0;
96
+ if (typeof aValue === 'number' && typeof bValue === 'number') {
97
+ comparison = aValue - bValue;
98
+ } else {
99
+ comparison = String(aValue).localeCompare(String(bValue));
100
+ }
101
+
102
+ return sortDirection === 'asc' ? comparison : -comparison;
103
+ });
104
+ }
105
+
106
+ return filtered;
107
+ });
108
+
109
+ const getSortIcon = (column: Column) => {
110
+ if (!column.sortable) return '';
111
+ if (sortColumn !== column.key) return iconArrowsSort;
112
+ return sortDirection === 'asc' ? iconChevronUp : iconChevronDown;
113
+ };
114
+
115
+ const tableClasses = $derived(
116
+ `pui-table ${tableClass} ${striped ? 'striped' : ''} ${hover ? 'hover' : ''}`
117
+ );
118
+ </script>
119
+
120
+ <div class="table-container">
121
+ {#if searchable}
122
+ <div class="table-search">
123
+ <Textbox
124
+ placeholder={searchPlaceholder}
125
+ bind:value={searchQuery}
126
+ leftIconSvg={iconSearch}
127
+ class="search-input"
128
+ />
129
+ </div>
130
+ {/if}
131
+
132
+ <div class="table-wrapper {notRounded ? '' : 'rounded'}">
133
+ <table class={tableClasses}>
134
+ <thead>
135
+ <tr>
136
+ {#each columns as column}
137
+ <th
138
+ class="table-header {column.sortable ? 'sortable' : ''} {column.align || 'left'}"
139
+ style={column.width ? `width: ${column.width}` : ''}
140
+ onclick={() => handleSort(column)}
141
+ >
142
+ <div class="header-content">
143
+ <span>{column.label}</span>
144
+ {#if column.sortable}
145
+ <div class="sort-icon">
146
+ <Icon
147
+ svg={getSortIcon(column)}
148
+ size="14px"
149
+ />
150
+ </div>
151
+ {/if}
152
+ </div>
153
+ </th>
154
+ {/each}
155
+ {#if buttons}
156
+ <th class="table-header center">Actions</th>
157
+ {/if}
158
+ </tr>
159
+ </thead>
160
+ <tbody>
161
+ {#if loading}
162
+ <tr>
163
+ <td colspan={columns.length + (buttons ? 1 : 0)} class="loading-cell">
164
+ <div class="loading-content">Loading...</div>
165
+ </td>
166
+ </tr>
167
+ {:else if filteredRows.length === 0}
168
+ <tr>
169
+ <td colspan={columns.length + (buttons ? 1 : 0)} class="empty-cell">
170
+ <div class="empty-content">{emptyMessage}</div>
171
+ </td>
172
+ </tr>
173
+ {:else}
174
+ {#each filteredRows as row, index}
175
+ <tr
176
+ class={onrowclick ? 'clickable-row' : ''}
177
+ onclick={() => onrowclick?.(row, index)}
178
+ >
179
+ {#each columns as column}
180
+ {@const value = getValue(row, column.key)}
181
+ <td class="table-cell {column.align || 'left'}">
182
+ {#if column.render}
183
+ {@html column.render(value, row)}
184
+ {:else if typeof value === 'function'}
185
+ {@render value()}
186
+ {:else}
187
+ {value ?? ''}
188
+ {/if}
189
+ </td>
190
+ {/each}
191
+ {#if buttons}
192
+ <td class="table-cell center">
193
+ {@render buttons(row, index)}
194
+ </td>
195
+ {/if}
196
+ </tr>
197
+ {/each}
198
+ {/if}
199
+ </tbody>
200
+ </table>
201
+ </div>
202
+
203
+ {@render children?.()}
204
+ </div>
205
+
206
+ <style>
207
+ .table-container {
208
+ width: 100%;
209
+ }
210
+
211
+ .table-search {
212
+ margin-bottom: var(--pui-spacing-4);
213
+ max-width: 400px;
214
+ }
215
+
216
+ .table-wrapper {
217
+ width: 100%;
218
+ overflow-x: auto;
219
+ border: 1px solid var(--pui-border-default);
220
+ background-color: var(--pui-input-bg);
221
+ }
222
+
223
+ .table-wrapper.rounded {
224
+ border-radius: var(--pui-radius-lg);
225
+ }
226
+
227
+ .pui-table {
228
+ width: 100%;
229
+ border-collapse: collapse;
230
+ font-size: var(--pui-font-size-base);
231
+ }
232
+
233
+ .table-header {
234
+ padding: var(--pui-spacing-2);
235
+ text-align: left;
236
+ font-weight: var(--pui-font-weight-semibold);
237
+ color: var(--pui-text-primary);
238
+ background-color: var(--pui-bg-surface-raised);
239
+ border-bottom: 2px solid var(--pui-border-default);
240
+ user-select: none;
241
+ white-space: nowrap;
242
+ }
243
+
244
+ .table-header.sortable {
245
+ cursor: pointer;
246
+ transition: background-color var(--pui-transition-fast) var(--pui-ease-out);
247
+ }
248
+
249
+ .table-header.sortable:hover {
250
+ background-color: var(--pui-bg-hover);
251
+ }
252
+
253
+ .table-header.center {
254
+ text-align: center;
255
+ }
256
+
257
+ .table-header.right {
258
+ text-align: right;
259
+ }
260
+
261
+ .header-content {
262
+ display: flex;
263
+ align-items: center;
264
+ gap: var(--pui-spacing-2);
265
+ justify-content: space-between;
266
+ }
267
+
268
+ .sort-icon {
269
+ display: inline-flex;
270
+ align-items: center;
271
+ opacity: 0.5;
272
+ transition: opacity var(--pui-transition-fast) var(--pui-ease-out);
273
+ }
274
+
275
+ .table-header.sortable:hover .sort-icon {
276
+ opacity: 1;
277
+ }
278
+
279
+ .table-cell {
280
+ padding: var(--pui-spacing-2);
281
+ text-align: left;
282
+ color: var(--pui-text-primary);
283
+ border-bottom: 1px solid var(--pui-border-default);
284
+ }
285
+
286
+ .table-cell.center {
287
+ text-align: center;
288
+ }
289
+
290
+ .table-cell.right {
291
+ text-align: right;
292
+ }
293
+
294
+ .pui-table.striped tbody tr:nth-child(even) {
295
+ background-color: var(--pui-bg-hover);
296
+ }
297
+
298
+ .pui-table.hover tbody tr:hover {
299
+ background-color: var(--pui-bg-hover);
300
+ transition: background-color var(--pui-transition-fast) var(--pui-ease-out);
301
+ }
302
+
303
+ .clickable-row {
304
+ cursor: pointer;
305
+ }
306
+
307
+ .clickable-row:hover {
308
+ background-color: var(--pui-bg-hover);
309
+ transition: background-color var(--pui-transition-fast) var(--pui-ease-out);
310
+ }
311
+
312
+ tbody tr:last-child td {
313
+ border-bottom: none;
314
+ }
315
+
316
+ .empty-cell,
317
+ .loading-cell {
318
+ padding: var(--pui-spacing-8);
319
+ text-align: center;
320
+ color: var(--pui-text-muted);
321
+ border-bottom: none;
322
+ }
323
+
324
+ .empty-content,
325
+ .loading-content {
326
+ font-style: italic;
327
+ }
328
+
329
+ /* Icon styles for sort indicators */
330
+ :global(.pui-table .sort-icon svg) {
331
+ width: 14px;
332
+ height: 14px;
333
+ }
334
+
335
+ /* Responsive adjustments */
336
+ @media (max-width: 640px) {
337
+ .table-header,
338
+ .table-cell {
339
+ padding: var(--pui-spacing-2) var(--pui-spacing-3);
340
+ }
341
+
342
+ .pui-table {
343
+ font-size: var(--pui-font-size-xs);
344
+ }
345
+ }
346
346
  </style>