@tabelhadev/tabelhawebui 0.28.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 (175) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +222 -0
  3. package/README.pt-BR.md +217 -0
  4. package/dist/actions/click-outside.d.ts +3 -0
  5. package/dist/actions/click-outside.js +12 -0
  6. package/dist/actions/combobox-keyboard-nav.d.ts +9 -0
  7. package/dist/actions/combobox-keyboard-nav.js +28 -0
  8. package/dist/actions/focus-trap.d.ts +1 -0
  9. package/dist/actions/focus-trap.js +34 -0
  10. package/dist/actions/scroll-lock.d.ts +1 -0
  11. package/dist/actions/scroll-lock.js +17 -0
  12. package/dist/components/actions/Button.svelte +169 -0
  13. package/dist/components/actions/Button.svelte.d.ts +19 -0
  14. package/dist/components/appshell/AppShell.svelte +50 -0
  15. package/dist/components/appshell/AppShell.svelte.d.ts +19 -0
  16. package/dist/components/appshell/AppShellBottomNav.svelte +67 -0
  17. package/dist/components/appshell/AppShellBottomNav.svelte.d.ts +9 -0
  18. package/dist/components/appshell/AppShellContent.svelte +38 -0
  19. package/dist/components/appshell/AppShellContent.svelte.d.ts +10 -0
  20. package/dist/components/appshell/AppShellSidebar.svelte +196 -0
  21. package/dist/components/appshell/AppShellSidebar.svelte.d.ts +15 -0
  22. package/dist/components/appshell/types.d.ts +6 -0
  23. package/dist/components/appshell/types.js +1 -0
  24. package/dist/components/appshell/utils.d.ts +1 -0
  25. package/dist/components/appshell/utils.js +3 -0
  26. package/dist/components/card/Card.svelte +31 -0
  27. package/dist/components/card/Card.svelte.d.ts +9 -0
  28. package/dist/components/card/CardAction.svelte +57 -0
  29. package/dist/components/card/CardAction.svelte.d.ts +10 -0
  30. package/dist/components/card/CardContent.svelte +25 -0
  31. package/dist/components/card/CardContent.svelte.d.ts +8 -0
  32. package/dist/components/card/CardDescription.svelte +24 -0
  33. package/dist/components/card/CardDescription.svelte.d.ts +8 -0
  34. package/dist/components/card/CardFooter.svelte +25 -0
  35. package/dist/components/card/CardFooter.svelte.d.ts +8 -0
  36. package/dist/components/card/CardHeader.svelte +54 -0
  37. package/dist/components/card/CardHeader.svelte.d.ts +10 -0
  38. package/dist/components/card/CardTitle.svelte +25 -0
  39. package/dist/components/card/CardTitle.svelte.d.ts +8 -0
  40. package/dist/components/chat/ChatMessage.svelte +104 -0
  41. package/dist/components/chat/ChatMessage.svelte.d.ts +10 -0
  42. package/dist/components/choice/Listbox.svelte +183 -0
  43. package/dist/components/choice/Listbox.svelte.d.ts +15 -0
  44. package/dist/components/choice/MultiSelect.svelte +278 -0
  45. package/dist/components/choice/MultiSelect.svelte.d.ts +18 -0
  46. package/dist/components/choice/Rating.svelte +91 -0
  47. package/dist/components/choice/Rating.svelte.d.ts +12 -0
  48. package/dist/components/choice/Toggle.svelte +112 -0
  49. package/dist/components/choice/Toggle.svelte.d.ts +12 -0
  50. package/dist/components/feedback/Badge.svelte +66 -0
  51. package/dist/components/feedback/Badge.svelte.d.ts +9 -0
  52. package/dist/components/feedback/ErrorPage.svelte +99 -0
  53. package/dist/components/feedback/ErrorPage.svelte.d.ts +13 -0
  54. package/dist/components/feedback/Message.svelte +130 -0
  55. package/dist/components/feedback/Message.svelte.d.ts +14 -0
  56. package/dist/components/feedback/ProgressBar.svelte +69 -0
  57. package/dist/components/feedback/ProgressBar.svelte.d.ts +8 -0
  58. package/dist/components/feedback/Skeleton.svelte +47 -0
  59. package/dist/components/feedback/Skeleton.svelte.d.ts +9 -0
  60. package/dist/components/feedback/Status.svelte +75 -0
  61. package/dist/components/feedback/Status.svelte.d.ts +10 -0
  62. package/dist/components/feedback/StatusPill.svelte +94 -0
  63. package/dist/components/feedback/StatusPill.svelte.d.ts +13 -0
  64. package/dist/components/feedback/Toaster.svelte +122 -0
  65. package/dist/components/feedback/Toaster.svelte.d.ts +7 -0
  66. package/dist/components/feedback/Tooltip.svelte +126 -0
  67. package/dist/components/feedback/Tooltip.svelte.d.ts +10 -0
  68. package/dist/components/feedback/toast.svelte.d.ts +27 -0
  69. package/dist/components/feedback/toast.svelte.js +23 -0
  70. package/dist/components/forms/Checkbox.svelte +96 -0
  71. package/dist/components/forms/Checkbox.svelte.d.ts +11 -0
  72. package/dist/components/forms/DatePicker.svelte +372 -0
  73. package/dist/components/forms/DatePicker.svelte.d.ts +21 -0
  74. package/dist/components/forms/Field.svelte +47 -0
  75. package/dist/components/forms/Field.svelte.d.ts +11 -0
  76. package/dist/components/forms/FileUpload.svelte +504 -0
  77. package/dist/components/forms/FileUpload.svelte.d.ts +43 -0
  78. package/dist/components/forms/Input.svelte +93 -0
  79. package/dist/components/forms/Input.svelte.d.ts +9 -0
  80. package/dist/components/forms/Label.svelte +30 -0
  81. package/dist/components/forms/Label.svelte.d.ts +10 -0
  82. package/dist/components/forms/Radio.svelte +80 -0
  83. package/dist/components/forms/Radio.svelte.d.ts +11 -0
  84. package/dist/components/forms/Select.svelte +322 -0
  85. package/dist/components/forms/Select.svelte.d.ts +22 -0
  86. package/dist/components/forms/TagInput.svelte +336 -0
  87. package/dist/components/forms/TagInput.svelte.d.ts +16 -0
  88. package/dist/components/forms/Textarea.svelte +64 -0
  89. package/dist/components/forms/Textarea.svelte.d.ts +9 -0
  90. package/dist/components/landing/Landing.svelte +47 -0
  91. package/dist/components/landing/Landing.svelte.d.ts +8 -0
  92. package/dist/components/landing/LandingFeatures.svelte +92 -0
  93. package/dist/components/landing/LandingFeatures.svelte.d.ts +15 -0
  94. package/dist/components/landing/LandingHero.svelte +92 -0
  95. package/dist/components/landing/LandingHero.svelte.d.ts +13 -0
  96. package/dist/components/landing/LandingRoadmap.svelte +42 -0
  97. package/dist/components/landing/LandingRoadmap.svelte.d.ts +11 -0
  98. package/dist/components/landing/LandingSection.svelte +25 -0
  99. package/dist/components/landing/LandingSection.svelte.d.ts +9 -0
  100. package/dist/components/landing/LandingSectionHeading.svelte +19 -0
  101. package/dist/components/landing/LandingSectionHeading.svelte.d.ts +10 -0
  102. package/dist/components/landing/LandingSteps.svelte +100 -0
  103. package/dist/components/landing/LandingSteps.svelte.d.ts +13 -0
  104. package/dist/components/layout/BracketLink.svelte +41 -0
  105. package/dist/components/layout/BracketLink.svelte.d.ts +9 -0
  106. package/dist/components/layout/Divider.svelte +70 -0
  107. package/dist/components/layout/Divider.svelte.d.ts +9 -0
  108. package/dist/components/layout/Eyebrow.svelte +24 -0
  109. package/dist/components/layout/Eyebrow.svelte.d.ts +8 -0
  110. package/dist/components/layout/PageHeader.svelte +106 -0
  111. package/dist/components/layout/PageHeader.svelte.d.ts +15 -0
  112. package/dist/components/layout/PageSeo.svelte +34 -0
  113. package/dist/components/layout/PageSeo.svelte.d.ts +10 -0
  114. package/dist/components/layout/PageShell.svelte +25 -0
  115. package/dist/components/layout/PageShell.svelte.d.ts +8 -0
  116. package/dist/components/layout/Panel.svelte +105 -0
  117. package/dist/components/layout/Panel.svelte.d.ts +12 -0
  118. package/dist/components/layout/Prose.svelte +101 -0
  119. package/dist/components/layout/Prose.svelte.d.ts +7 -0
  120. package/dist/components/layout/RuleCard.svelte +63 -0
  121. package/dist/components/layout/RuleCard.svelte.d.ts +13 -0
  122. package/dist/components/layout/SectionHeading.svelte +57 -0
  123. package/dist/components/layout/SectionHeading.svelte.d.ts +10 -0
  124. package/dist/components/layout/StatTile.svelte +76 -0
  125. package/dist/components/layout/StatTile.svelte.d.ts +12 -0
  126. package/dist/components/layout/TerminalWindow.svelte +81 -0
  127. package/dist/components/layout/TerminalWindow.svelte.d.ts +9 -0
  128. package/dist/components/layout/Wordmark.svelte +26 -0
  129. package/dist/components/layout/Wordmark.svelte.d.ts +8 -0
  130. package/dist/components/list/List.svelte +40 -0
  131. package/dist/components/list/List.svelte.d.ts +13 -0
  132. package/dist/components/list/ListItem.svelte +81 -0
  133. package/dist/components/list/ListItem.svelte.d.ts +13 -0
  134. package/dist/components/marketing/MarketingShell.svelte +170 -0
  135. package/dist/components/marketing/MarketingShell.svelte.d.ts +23 -0
  136. package/dist/components/marketing/MarketingShellFooter.svelte +92 -0
  137. package/dist/components/marketing/MarketingShellFooter.svelte.d.ts +14 -0
  138. package/dist/components/navigation/Accordion.svelte +110 -0
  139. package/dist/components/navigation/Accordion.svelte.d.ts +15 -0
  140. package/dist/components/navigation/Carousel.svelte +115 -0
  141. package/dist/components/navigation/Carousel.svelte.d.ts +12 -0
  142. package/dist/components/navigation/InstructionStep.svelte +115 -0
  143. package/dist/components/navigation/InstructionStep.svelte.d.ts +10 -0
  144. package/dist/components/navigation/InstructionSteps.svelte +39 -0
  145. package/dist/components/navigation/InstructionSteps.svelte.d.ts +8 -0
  146. package/dist/components/navigation/Nav.svelte +102 -0
  147. package/dist/components/navigation/Nav.svelte.d.ts +14 -0
  148. package/dist/components/navigation/Stepper.svelte +112 -0
  149. package/dist/components/navigation/Stepper.svelte.d.ts +12 -0
  150. package/dist/components/navigation/TabCard.svelte +62 -0
  151. package/dist/components/navigation/TabCard.svelte.d.ts +10 -0
  152. package/dist/components/navigation/Tabs.svelte +83 -0
  153. package/dist/components/navigation/Tabs.svelte.d.ts +12 -0
  154. package/dist/components/navigation/ThemeToggle.svelte +118 -0
  155. package/dist/components/navigation/ThemeToggle.svelte.d.ts +20 -0
  156. package/dist/components/overlay/Dialog.svelte +200 -0
  157. package/dist/components/overlay/Dialog.svelte.d.ts +19 -0
  158. package/dist/components/overlay/DialogActions.svelte +50 -0
  159. package/dist/components/overlay/DialogActions.svelte.d.ts +9 -0
  160. package/dist/components/overlay/Dropdown.svelte +110 -0
  161. package/dist/components/overlay/Dropdown.svelte.d.ts +10 -0
  162. package/dist/components/overlay/FloatingActionPill.svelte +96 -0
  163. package/dist/components/overlay/FloatingActionPill.svelte.d.ts +14 -0
  164. package/dist/components/overlay/Sidebar.svelte +258 -0
  165. package/dist/components/overlay/Sidebar.svelte.d.ts +18 -0
  166. package/dist/components/table/Table.svelte +550 -0
  167. package/dist/components/table/Table.svelte.d.ts +42 -0
  168. package/dist/components/timeline/Timeline.svelte +25 -0
  169. package/dist/components/timeline/Timeline.svelte.d.ts +8 -0
  170. package/dist/components/timeline/TimelineItem.svelte +220 -0
  171. package/dist/components/timeline/TimelineItem.svelte.d.ts +18 -0
  172. package/dist/index.d.ts +156 -0
  173. package/dist/index.js +149 -0
  174. package/dist/theme/theme.css +348 -0
  175. package/package.json +52 -0
@@ -0,0 +1,550 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ // Column: simple string (key == label) or { key, label, width, sortable }.
5
+ type Column = string | { key: string; label?: string; width?: string; sortable?: boolean };
6
+
7
+ function colKey(col: Column): string {
8
+ return typeof col === 'string' ? col : col.key;
9
+ }
10
+ function colLabel(col: Column): string {
11
+ return typeof col === 'string' ? col : (col.label ?? col.key);
12
+ }
13
+ function colWidth(col: Column): string | undefined {
14
+ return typeof col === 'string' ? undefined : col.width;
15
+ }
16
+ function colSortable(col: Column, sortable: boolean): boolean {
17
+ return typeof col === 'string' ? sortable : (col.sortable ?? sortable);
18
+ }
19
+
20
+ let {
21
+ columns,
22
+ rows = [],
23
+ cell,
24
+ children,
25
+ widths,
26
+ // Sorting
27
+ sortable = false,
28
+ // Global filter
29
+ filterable = false,
30
+ filterFields = [],
31
+ // Row selection
32
+ selection,
33
+ selected = $bindable([]),
34
+ rowKey,
35
+ // States
36
+ loading = false,
37
+ skeletonRows = 5,
38
+ empty,
39
+ // Pagination
40
+ pageSize = $bindable(10),
41
+ pageSizeOptions,
42
+ pageReport = '{currentPage} de {totalPages}',
43
+ showFirstLast = true,
44
+ labels = {
45
+ records: 'registros',
46
+ page: 'página',
47
+ prev: 'Anterior',
48
+ next: 'Próxima',
49
+ first: 'Primeira página',
50
+ last: 'Última página',
51
+ rowsPerPage: 'Linhas por página',
52
+ search: 'Buscar...',
53
+ empty: 'Nenhum registro'
54
+ },
55
+ class: className = ''
56
+ }: {
57
+ columns?: Column[];
58
+ rows?: Array<Record<string, unknown>>;
59
+ cell?: Snippet<[row: Record<string, unknown>, key: string]>;
60
+ children?: Snippet;
61
+ // Relative width proportions per column, e.g. [4, 1, 2, 3].
62
+ widths?: number[];
63
+ // Sorting: clicking the header cycles none → asc → desc.
64
+ sortable?: boolean;
65
+ // Global filter: search box above the table.
66
+ filterable?: boolean;
67
+ filterFields?: string[];
68
+ // Row selection: 'single' | 'multiple'.
69
+ selection?: 'single' | 'multiple';
70
+ selected?: Array<Record<string, unknown>>;
71
+ rowKey?: string;
72
+ loading?: boolean;
73
+ skeletonRows?: number;
74
+ empty?: Snippet;
75
+ pageSize?: number;
76
+ pageSizeOptions?: number[];
77
+ pageReport?: string;
78
+ showFirstLast?: boolean;
79
+ labels?: {
80
+ records?: string;
81
+ page?: string;
82
+ prev?: string;
83
+ next?: string;
84
+ first?: string;
85
+ last?: string;
86
+ rowsPerPage?: string;
87
+ search?: string;
88
+ empty?: string;
89
+ };
90
+ class?: string;
91
+ } = $props();
92
+
93
+ // --- column widths ---
94
+ const total = $derived(widths?.reduce((a, b) => a + b, 0) ?? 0);
95
+ function colWidthPercent(i: number): string | undefined {
96
+ if (!widths || total === 0) return undefined;
97
+ return `${(widths[i] / total) * 100}%`;
98
+ }
99
+ function colStyle(col: Column, i: number): string | undefined {
100
+ const w = colWidth(col) ?? colWidthPercent(i);
101
+ return w ? `width: ${w}` : undefined;
102
+ }
103
+
104
+ // --- ordenação ---
105
+ let sortField = $state<string | undefined>(undefined);
106
+ let sortOrder = $state<'asc' | 'desc'>('asc');
107
+
108
+ function cycleSort(key: string) {
109
+ if (sortField !== key) {
110
+ sortField = key;
111
+ sortOrder = 'asc';
112
+ } else if (sortOrder === 'asc') {
113
+ sortOrder = 'desc';
114
+ } else {
115
+ sortField = undefined;
116
+ }
117
+ }
118
+
119
+ function compare(a: unknown, b: unknown): number {
120
+ if (typeof a === 'number' && typeof b === 'number') return a - b;
121
+ return String(a ?? '').localeCompare(String(b ?? ''));
122
+ }
123
+
124
+ // --- global filter ---
125
+ let query = $state('');
126
+ const filterableKeys = $derived(
127
+ filterable && filterFields.length > 0 ? filterFields : columns?.map(colKey) ?? []
128
+ );
129
+ const filteredRows = $derived(
130
+ filterable && query.trim() && filterableKeys.length > 0
131
+ ? rows.filter((row) =>
132
+ filterableKeys.some((k) =>
133
+ String(row[k] ?? '').toLowerCase().includes(query.trim().toLowerCase())
134
+ )
135
+ )
136
+ : rows
137
+ );
138
+ const sortedRows = $derived(
139
+ sortField ? [...filteredRows].sort((a, b) => compare(a[sortField!], b[sortField!]) * (sortOrder === 'asc' ? 1 : -1)) : filteredRows
140
+ );
141
+
142
+ // --- pagination ---
143
+ // pageSize=0 means "no pagination, show all rows". Avoids
144
+ // slice((p-1)*0, p*0) = slice(0,0) = [] and division by zero in totalPages.
145
+ const noPagination = $derived(!pageSize || pageSize <= 0);
146
+ const totalRecords = $derived(sortedRows.length);
147
+ const totalPages = $derived(
148
+ noPagination ? 1 : Math.max(1, Math.ceil(totalRecords / pageSize))
149
+ );
150
+ let currentPage = $state(1);
151
+ const pageRows = $derived(
152
+ noPagination ? sortedRows : sortedRows.slice((currentPage - 1) * pageSize, currentPage * pageSize)
153
+ );
154
+
155
+ $effect(() => {
156
+ if (currentPage > totalPages) currentPage = Math.max(1, totalPages);
157
+ });
158
+ $effect(() => {
159
+ if (query) currentPage = 1;
160
+ });
161
+ $effect(() => {
162
+ if (pageSize) currentPage = 1;
163
+ });
164
+
165
+ function pageRange(): number[] {
166
+ const start = Math.max(1, Math.min(currentPage - 2, totalPages - 4));
167
+ const end = Math.min(totalPages, start + 4);
168
+ const range: number[] = [];
169
+ for (let i = start; i <= end; i++) range.push(i);
170
+ return range;
171
+ }
172
+ function setPage(p: number) {
173
+ currentPage = Math.min(Math.max(1, p), totalPages);
174
+ }
175
+ function renderReport(): string {
176
+ const first = totalRecords === 0 ? 0 : (currentPage - 1) * pageSize + 1;
177
+ const last = Math.min(currentPage * pageSize, totalRecords);
178
+ return pageReport
179
+ .replace('{first}', String(first))
180
+ .replace('{last}', String(last))
181
+ .replace('{totalRecords}', String(totalRecords))
182
+ .replace('{currentPage}', String(currentPage))
183
+ .replace('{totalPages}', String(totalPages));
184
+ }
185
+
186
+ // --- selection ---
187
+ function isSelected(row: Record<string, unknown>): boolean {
188
+ return selected.some((s) => (rowKey ? s[rowKey] === row[rowKey] : s === row));
189
+ }
190
+ function toggleRow(row: Record<string, unknown>) {
191
+ if (!selection) return;
192
+ if (selection === 'single') {
193
+ selected = isSelected(row) ? [] : [row];
194
+ return;
195
+ }
196
+ selected = isSelected(row) ? selected.filter((s) => (rowKey ? s[rowKey] !== row[rowKey] : s !== row)) : [...selected, row];
197
+ }
198
+ </script>
199
+
200
+ {#if filterable}
201
+ <input
202
+ type="text"
203
+ class="twui-table-filter"
204
+ placeholder={labels.search ?? 'Buscar...'}
205
+ bind:value={query}
206
+ aria-label={labels.search ?? 'Buscar...'}
207
+ />
208
+ {/if}
209
+
210
+ <table class="twui-table {widths ? 'twui-table-fixed' : ''} {className}">
211
+ {#if columns}
212
+ <thead>
213
+ <tr>
214
+ {#each columns as col, i (colKey(col))}
215
+ <th
216
+ style={colStyle(col, i)}
217
+ class:twui-table-sortable={colSortable(col, sortable)}
218
+ aria-sort={colKey(col) === sortField ? (sortOrder === 'asc' ? 'ascending' : 'descending') : undefined}
219
+ >
220
+ {#if colSortable(col, sortable)}
221
+ <button type="button" class="twui-table-sort-btn" onclick={() => cycleSort(colKey(col))}>
222
+ {colLabel(col)}
223
+ {#if colKey(col) === sortField}
224
+ <span class="twui-table-sort-ind" aria-hidden="true">{sortOrder === 'asc' ? '▲' : '▼'}</span>
225
+ {/if}
226
+ </button>
227
+ {:else}
228
+ {colLabel(col)}
229
+ {/if}
230
+ </th>
231
+ {/each}
232
+ </tr>
233
+ </thead>
234
+ {/if}
235
+ {#if loading}
236
+ <tbody aria-hidden="true">
237
+ {#each Array(skeletonRows) as _, i (i)}
238
+ <tr>
239
+ {#each columns ?? [] as col, j (colKey(col))}
240
+ <td class="twui-table-skeleton-cell">
241
+ <span class="twui-table-skeleton"></span>
242
+ </td>
243
+ {/each}
244
+ </tr>
245
+ {/each}
246
+ </tbody>
247
+ {:else if pageRows.length > 0 && columns}
248
+ <tbody>
249
+ {#each pageRows as row, i (i)}
250
+ <tr
251
+ class:twui-table-row-selected={isSelected(row)}
252
+ class:twui-table-row-clickable={!!selection}
253
+ aria-selected={selection ? isSelected(row) : undefined}
254
+ onclick={selection ? () => toggleRow(row) : undefined}
255
+ >
256
+ {#each columns as col, j (colKey(col))}
257
+ <td style={colStyle(col, j)}>
258
+ {#if cell}
259
+ {@render cell(row, colKey(col))}
260
+ {:else}
261
+ {String(row[colKey(col)] ?? '')}
262
+ {/if}
263
+ </td>
264
+ {/each}
265
+ </tr>
266
+ {/each}
267
+ </tbody>
268
+ {:else if empty}
269
+ <tbody>
270
+ <tr>
271
+ <td colspan={columns?.length ?? 1} class="twui-table-empty">
272
+ {@render empty()}
273
+ </td>
274
+ </tr>
275
+ </tbody>
276
+ {:else if columns && !children}
277
+ <tbody>
278
+ <tr>
279
+ <td colspan={columns.length} class="twui-table-empty">{labels.empty ?? 'Nenhum registro'}</td>
280
+ </tr>
281
+ </tbody>
282
+ {:else if children}
283
+ {@render children()}
284
+ {/if}
285
+ </table>
286
+
287
+ {#if pageSize && totalRecords > pageSize}
288
+ <div class="twui-table-pagination">
289
+ <div class="twui-table-pagination-left">
290
+ {#if pageSizeOptions && pageSizeOptions.length > 0}
291
+ <label class="twui-table-rows-label">
292
+ {labels.rowsPerPage ?? 'Linhas por página'}
293
+ <select class="twui-table-rows-select" bind:value={pageSize}>
294
+ {#each pageSizeOptions as size (size)}
295
+ <option value={size}>{size}</option>
296
+ {/each}
297
+ </select>
298
+ </label>
299
+ {/if}
300
+ <span class="twui-table-pagination-info" aria-live="polite">{renderReport()}</span>
301
+ </div>
302
+ <div class="twui-table-pagination-buttons">
303
+ {#if showFirstLast}
304
+ <button
305
+ type="button"
306
+ class="twui-table-page-btn"
307
+ disabled={currentPage <= 1}
308
+ onclick={() => setPage(1)}
309
+ aria-label={labels.first ?? 'Primeira página'}
310
+ >«</button>
311
+ {/if}
312
+ <button
313
+ type="button"
314
+ class="twui-table-page-btn"
315
+ disabled={currentPage <= 1}
316
+ onclick={() => setPage(currentPage - 1)}
317
+ aria-label={labels.prev ?? 'Anterior'}
318
+ >‹</button>
319
+ {#each pageRange() as p (p)}
320
+ <button
321
+ type="button"
322
+ class="twui-table-page-btn {p === currentPage ? 'twui-table-page-btn-active' : ''}"
323
+ aria-current={p === currentPage ? 'page' : undefined}
324
+ onclick={() => setPage(p)}
325
+ >{p}</button>
326
+ {/each}
327
+ <button
328
+ type="button"
329
+ class="twui-table-page-btn"
330
+ disabled={currentPage >= totalPages}
331
+ onclick={() => setPage(currentPage + 1)}
332
+ aria-label={labels.next ?? 'Próxima'}
333
+ >›</button>
334
+ {#if showFirstLast}
335
+ <button
336
+ type="button"
337
+ class="twui-table-page-btn"
338
+ disabled={currentPage >= totalPages}
339
+ onclick={() => setPage(totalPages)}
340
+ aria-label={labels.last ?? 'Última página'}
341
+ >»</button>
342
+ {/if}
343
+ </div>
344
+ </div>
345
+ {/if}
346
+
347
+ <style>
348
+ .twui-table {
349
+ width: 100%;
350
+ border-collapse: collapse;
351
+ font-family: var(--twui-font-mono);
352
+ font-size: 14px;
353
+ color: var(--twui-ink);
354
+ }
355
+
356
+ .twui-table-fixed {
357
+ table-layout: fixed;
358
+ }
359
+
360
+ .twui-table th {
361
+ padding: 8px 16px;
362
+ text-align: left;
363
+ font-size: 12px;
364
+ font-weight: 500;
365
+ color: var(--twui-ink-faint);
366
+ border-bottom: 1px solid var(--twui-rule);
367
+ }
368
+
369
+ .twui-table th:last-child,
370
+ .twui-table td:last-child {
371
+ text-align: right;
372
+ }
373
+
374
+ .twui-table td {
375
+ padding: 8px 16px;
376
+ border-bottom: 1px solid var(--twui-rule);
377
+ vertical-align: middle;
378
+ }
379
+
380
+ .twui-table tbody tr:last-child td {
381
+ border-bottom: none;
382
+ }
383
+
384
+ .twui-table tbody tr:hover td {
385
+ background: var(--twui-paper);
386
+ }
387
+
388
+ .twui-table-sort-btn {
389
+ display: inline-flex;
390
+ align-items: center;
391
+ gap: 4px;
392
+ padding: 0;
393
+ border: none;
394
+ background: transparent;
395
+ font-family: inherit;
396
+ font-size: inherit;
397
+ font-weight: inherit;
398
+ color: inherit;
399
+ cursor: pointer;
400
+ }
401
+
402
+ .twui-table-sort-btn:hover {
403
+ color: var(--twui-accent);
404
+ }
405
+
406
+ .twui-table-sort-ind {
407
+ font-size: 9px;
408
+ color: var(--twui-accent);
409
+ }
410
+
411
+ .twui-table-row-clickable {
412
+ cursor: pointer;
413
+ }
414
+
415
+ .twui-table-row-selected td {
416
+ background: var(--twui-accent-soft);
417
+ }
418
+
419
+ .twui-table td.twui-table-empty {
420
+ padding: 24px 16px;
421
+ text-align: center;
422
+ font-size: 13px;
423
+ color: var(--twui-ink-faint);
424
+ }
425
+
426
+ .twui-table-skeleton-cell {
427
+ padding-top: 10px;
428
+ padding-bottom: 10px;
429
+ }
430
+
431
+ .twui-table-skeleton {
432
+ display: block;
433
+ height: 12px;
434
+ border-radius: 2px;
435
+ background: var(--twui-paper-raised);
436
+ background-image: linear-gradient(
437
+ 90deg,
438
+ var(--twui-paper-raised) 0%,
439
+ var(--twui-rule) 50%,
440
+ var(--twui-paper-raised) 100%
441
+ );
442
+ background-size: 200% 100%;
443
+ animation: twui-table-shimmer 1.2s ease-in-out infinite;
444
+ }
445
+
446
+ @keyframes twui-table-shimmer {
447
+ from {
448
+ background-position: 200% 0;
449
+ }
450
+ to {
451
+ background-position: -200% 0;
452
+ }
453
+ }
454
+
455
+ .twui-table-filter {
456
+ width: 100%;
457
+ max-width: 240px;
458
+ margin-bottom: 12px;
459
+ padding: 8px 12px;
460
+ border: 1px solid var(--twui-rule);
461
+ background: var(--twui-paper);
462
+ font-family: var(--twui-font-mono);
463
+ font-size: 14px;
464
+ color: var(--twui-ink);
465
+ outline: none;
466
+ }
467
+
468
+ .twui-table-filter::placeholder {
469
+ color: var(--twui-ink-faint);
470
+ }
471
+
472
+ .twui-table-filter:focus-visible {
473
+ border-color: var(--twui-accent);
474
+ }
475
+
476
+ .twui-table-pagination {
477
+ display: flex;
478
+ align-items: center;
479
+ justify-content: space-between;
480
+ flex-wrap: wrap;
481
+ gap: 8px 16px;
482
+ padding: 10px 16px;
483
+ border-top: 1px solid var(--twui-rule);
484
+ }
485
+
486
+ .twui-table-pagination-left {
487
+ display: flex;
488
+ align-items: center;
489
+ flex-wrap: wrap;
490
+ gap: 8px 16px;
491
+ }
492
+
493
+ .twui-table-rows-label {
494
+ display: inline-flex;
495
+ align-items: center;
496
+ gap: 6px;
497
+ font-size: 12px;
498
+ color: var(--twui-ink-soft);
499
+ }
500
+
501
+ .twui-table-rows-select {
502
+ padding: 4px 8px;
503
+ border: 1px solid var(--twui-rule);
504
+ background: var(--twui-paper);
505
+ font-family: var(--twui-font-mono);
506
+ font-size: 12px;
507
+ color: var(--twui-ink);
508
+ cursor: pointer;
509
+ }
510
+
511
+ .twui-table-pagination-info {
512
+ font-size: 12px;
513
+ color: var(--twui-ink-soft);
514
+ }
515
+
516
+ .twui-table-pagination-buttons {
517
+ display: flex;
518
+ align-items: center;
519
+ gap: 4px;
520
+ }
521
+
522
+ .twui-table-page-btn {
523
+ min-width: 28px;
524
+ height: 28px;
525
+ padding: 0 6px;
526
+ border: 1px solid var(--twui-rule);
527
+ background: transparent;
528
+ font-family: var(--twui-font-mono);
529
+ font-size: 12px;
530
+ color: var(--twui-ink);
531
+ cursor: pointer;
532
+ }
533
+
534
+ .twui-table-page-btn:hover:not(:disabled):not(.twui-table-page-btn-active) {
535
+ border-color: var(--twui-accent);
536
+ color: var(--twui-accent);
537
+ }
538
+
539
+ .twui-table-page-btn-active {
540
+ border-color: var(--twui-accent);
541
+ background: var(--twui-accent-soft);
542
+ color: var(--twui-accent);
543
+ font-weight: 500;
544
+ }
545
+
546
+ .twui-table-page-btn:disabled {
547
+ opacity: 0.4;
548
+ cursor: not-allowed;
549
+ }
550
+ </style>
@@ -0,0 +1,42 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Column = string | {
3
+ key: string;
4
+ label?: string;
5
+ width?: string;
6
+ sortable?: boolean;
7
+ };
8
+ type $$ComponentProps = {
9
+ columns?: Column[];
10
+ rows?: Array<Record<string, unknown>>;
11
+ cell?: Snippet<[row: Record<string, unknown>, key: string]>;
12
+ children?: Snippet;
13
+ widths?: number[];
14
+ sortable?: boolean;
15
+ filterable?: boolean;
16
+ filterFields?: string[];
17
+ selection?: 'single' | 'multiple';
18
+ selected?: Array<Record<string, unknown>>;
19
+ rowKey?: string;
20
+ loading?: boolean;
21
+ skeletonRows?: number;
22
+ empty?: Snippet;
23
+ pageSize?: number;
24
+ pageSizeOptions?: number[];
25
+ pageReport?: string;
26
+ showFirstLast?: boolean;
27
+ labels?: {
28
+ records?: string;
29
+ page?: string;
30
+ prev?: string;
31
+ next?: string;
32
+ first?: string;
33
+ last?: string;
34
+ rowsPerPage?: string;
35
+ search?: string;
36
+ empty?: string;
37
+ };
38
+ class?: string;
39
+ };
40
+ declare const Table: import("svelte").Component<$$ComponentProps, {}, "selected" | "pageSize">;
41
+ type Table = ReturnType<typeof Table>;
42
+ export default Table;
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import TimelineItem, { type TimelineItemProps } from './TimelineItem.svelte';
3
+
4
+ let {
5
+ items,
6
+ class: className = ''
7
+ }: {
8
+ items: TimelineItemProps[];
9
+ class?: string;
10
+ } = $props();
11
+ </script>
12
+
13
+ <ul class="twui-timeline {className}">
14
+ {#each items as item, i (item.key ?? i)}
15
+ <TimelineItem {...item} isLast={i === items.length - 1} />
16
+ {/each}
17
+ </ul>
18
+
19
+ <style>
20
+ .twui-timeline {
21
+ margin: 0;
22
+ padding: 0;
23
+ list-style: none;
24
+ }
25
+ </style>
@@ -0,0 +1,8 @@
1
+ import { type TimelineItemProps } from './TimelineItem.svelte';
2
+ type $$ComponentProps = {
3
+ items: TimelineItemProps[];
4
+ class?: string;
5
+ };
6
+ declare const Timeline: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type Timeline = ReturnType<typeof Timeline>;
8
+ export default Timeline;