@torch-ui/solid 0.1.3
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 +166 -0
- package/package.json +67 -0
- package/src/components/actions/Button.tsx +612 -0
- package/src/components/actions/ButtonGroup.tsx +728 -0
- package/src/components/actions/Copy.tsx +98 -0
- package/src/components/actions/DarkModeToggle.tsx +80 -0
- package/src/components/actions/Link.tsx +37 -0
- package/src/components/actions/index.ts +19 -0
- package/src/components/actions/useCopyToClipboard.ts +90 -0
- package/src/components/charts/Chart.tsx +331 -0
- package/src/components/charts/Sparkline.tsx +156 -0
- package/src/components/charts/index.ts +13 -0
- package/src/components/data-display/Avatar.tsx +208 -0
- package/src/components/data-display/AvatarGroup.tsx +228 -0
- package/src/components/data-display/Badge.tsx +70 -0
- package/src/components/data-display/Carousel.tsx +214 -0
- package/src/components/data-display/ColorSwatch.tsx +56 -0
- package/src/components/data-display/DataTable.tsx +886 -0
- package/src/components/data-display/EmptyState.tsx +61 -0
- package/src/components/data-display/Image.tsx +277 -0
- package/src/components/data-display/Kbd.tsx +114 -0
- package/src/components/data-display/Persona.tsx +78 -0
- package/src/components/data-display/StatCard.tsx +338 -0
- package/src/components/data-display/Table.tsx +147 -0
- package/src/components/data-display/Tag.tsx +91 -0
- package/src/components/data-display/Timeline.tsx +200 -0
- package/src/components/data-display/TreeView.tsx +172 -0
- package/src/components/data-display/Video.tsx +95 -0
- package/src/components/data-display/avatar-utils.ts +32 -0
- package/src/components/data-display/index.ts +81 -0
- package/src/components/feedback/Loading.tsx +159 -0
- package/src/components/feedback/Progress.tsx +321 -0
- package/src/components/feedback/Skeleton.tsx +62 -0
- package/src/components/feedback/SkeletonBlocks.tsx +222 -0
- package/src/components/feedback/Toast.tsx +648 -0
- package/src/components/feedback/index.ts +44 -0
- package/src/components/feedback/password/PasswordStrengthIndicator.tsx +232 -0
- package/src/components/feedback/password/password-strength.ts +115 -0
- package/src/components/feedback/password/password-validation-data.ts +66 -0
- package/src/components/feedback/password/password-validation.ts +93 -0
- package/src/components/forms/Autocomplete.tsx +268 -0
- package/src/components/forms/Checkbox.tsx +155 -0
- package/src/components/forms/CodeInput.tsx +237 -0
- package/src/components/forms/ColorPicker/ColorPicker.tsx +469 -0
- package/src/components/forms/ColorPicker/color-utils.ts +75 -0
- package/src/components/forms/ColorPicker/index.ts +2 -0
- package/src/components/forms/DatePicker.tsx +516 -0
- package/src/components/forms/DateRangePicker.tsx +464 -0
- package/src/components/forms/FieldPicker.tsx +64 -0
- package/src/components/forms/FileUpload.tsx +614 -0
- package/src/components/forms/FilterBuilder/FilterGroupBlock.ts +6 -0
- package/src/components/forms/FilterBuilder.tsx +16 -0
- package/src/components/forms/FilterRuleRow.tsx +68 -0
- package/src/components/forms/Input.tsx +200 -0
- package/src/components/forms/MultiSelect.tsx +361 -0
- package/src/components/forms/NumberField.tsx +145 -0
- package/src/components/forms/RadioGroup.tsx +135 -0
- package/src/components/forms/RelativeDateDefaultInput.tsx +62 -0
- package/src/components/forms/ReorderableList.tsx +163 -0
- package/src/components/forms/Select.tsx +268 -0
- package/src/components/forms/Slider.tsx +260 -0
- package/src/components/forms/Switch.tsx +135 -0
- package/src/components/forms/TextArea.tsx +202 -0
- package/src/components/forms/ViewCustomizer.tsx +44 -0
- package/src/components/forms/index.ts +43 -0
- package/src/components/layout/Accordion.tsx +110 -0
- package/src/components/layout/Alert.tsx +156 -0
- package/src/components/layout/BlockQuote.tsx +70 -0
- package/src/components/layout/Card.tsx +166 -0
- package/src/components/layout/CodeBlock/CodeBlock.tsx +477 -0
- package/src/components/layout/CodeBlock/code-block-tokens.css +104 -0
- package/src/components/layout/CodeBlock/prism.ts +81 -0
- package/src/components/layout/Collapsible.tsx +84 -0
- package/src/components/layout/Container.tsx +55 -0
- package/src/components/layout/Divider.tsx +64 -0
- package/src/components/layout/Form.tsx +39 -0
- package/src/components/layout/FormActions.tsx +50 -0
- package/src/components/layout/Grid.tsx +53 -0
- package/src/components/layout/PageHeading.tsx +46 -0
- package/src/components/layout/PromptWithAction.tsx +49 -0
- package/src/components/layout/Section.tsx +60 -0
- package/src/components/layout/TablePanel.tsx +24 -0
- package/src/components/layout/TableView/TableView.tsx +1018 -0
- package/src/components/layout/TableView/index.ts +3 -0
- package/src/components/layout/TableView/types.ts +51 -0
- package/src/components/layout/WizardStep.tsx +40 -0
- package/src/components/layout/WizardStepper.tsx +173 -0
- package/src/components/layout/index.ts +96 -0
- package/src/components/navigation/Breadcrumbs.tsx +66 -0
- package/src/components/navigation/DropdownMenu.tsx +86 -0
- package/src/components/navigation/MegaMenu.tsx +480 -0
- package/src/components/navigation/NavigationMenu.tsx +305 -0
- package/src/components/navigation/Pagination.tsx +298 -0
- package/src/components/navigation/Sidebar.tsx +280 -0
- package/src/components/navigation/Tabs.tsx +122 -0
- package/src/components/navigation/ViewSwitcher.tsx +314 -0
- package/src/components/navigation/index.ts +66 -0
- package/src/components/overlays/AlertDialog.tsx +174 -0
- package/src/components/overlays/ContextMenu.tsx +65 -0
- package/src/components/overlays/Dialog.tsx +279 -0
- package/src/components/overlays/Drawer.tsx +370 -0
- package/src/components/overlays/HoverCard.tsx +107 -0
- package/src/components/overlays/Popover.tsx +73 -0
- package/src/components/overlays/Tooltip.tsx +31 -0
- package/src/components/overlays/index.ts +71 -0
- package/src/components/typography/Code.tsx +72 -0
- package/src/components/typography/Icon.tsx +36 -0
- package/src/components/typography/index.ts +10 -0
- package/src/env.d.ts +9 -0
- package/src/index.ts +13 -0
- package/src/styles/theme.css +226 -0
- package/src/types/avatar-types.ts +11 -0
- package/src/types/filter-types.ts +35 -0
- package/src/utilities/classNames.ts +6 -0
- package/src/utilities/componentSize.ts +46 -0
- package/src/utilities/i18n.tsx +60 -0
- package/src/utilities/mergeRefs.ts +12 -0
- package/src/utilities/relativeDateDefault.ts +14 -0
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
import { type JSX, Show, For, createMemo, splitProps } from 'solid-js'
|
|
2
|
+
|
|
3
|
+
import { Plus } from 'lucide-solid'
|
|
4
|
+
|
|
5
|
+
import { Button } from '../actions'
|
|
6
|
+
|
|
7
|
+
import { Input } from '../forms'
|
|
8
|
+
|
|
9
|
+
import { Dialog, AlertDialog } from '../layout'
|
|
10
|
+
|
|
11
|
+
import { EmptyState } from './EmptyState'
|
|
12
|
+
|
|
13
|
+
import { cn } from '../../utilities/classNames'
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
|
|
17
|
+
Table,
|
|
18
|
+
|
|
19
|
+
TableHeader,
|
|
20
|
+
|
|
21
|
+
TableBody,
|
|
22
|
+
|
|
23
|
+
TableRow,
|
|
24
|
+
|
|
25
|
+
TableHead,
|
|
26
|
+
|
|
27
|
+
TableCell,
|
|
28
|
+
|
|
29
|
+
} from './Table'
|
|
30
|
+
|
|
31
|
+
import { Pagination } from '../navigation/Pagination'
|
|
32
|
+
|
|
33
|
+
import type { PaginationProps } from '../navigation/Pagination'
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/** Shared table container styling: white background, border, rounded. Use with overflow-x-auto or overflow-hidden. */
|
|
38
|
+
|
|
39
|
+
export const TABLE_CONTAINER_CLASS =
|
|
40
|
+
|
|
41
|
+
'rounded-xl border border-surface-border bg-surface-raised'
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export interface DataTableSearchProps {
|
|
46
|
+
|
|
47
|
+
value: string
|
|
48
|
+
|
|
49
|
+
onChange: (value: string) => void
|
|
50
|
+
|
|
51
|
+
placeholder: string
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
export interface DataTableButtonProps {
|
|
58
|
+
|
|
59
|
+
label: string
|
|
60
|
+
|
|
61
|
+
onClick: () => void
|
|
62
|
+
|
|
63
|
+
startIcon?: JSX.Element
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export interface DataTableAddRowProps {
|
|
70
|
+
|
|
71
|
+
showAddForm: boolean
|
|
72
|
+
|
|
73
|
+
onToggleAddForm: () => void
|
|
74
|
+
|
|
75
|
+
addButtonLabel: string
|
|
76
|
+
|
|
77
|
+
renderAddCells: () => JSX.Element
|
|
78
|
+
|
|
79
|
+
addError?: string
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
export interface DataTableEditModalProps {
|
|
86
|
+
|
|
87
|
+
open: boolean
|
|
88
|
+
|
|
89
|
+
title: string
|
|
90
|
+
|
|
91
|
+
onClose: () => void
|
|
92
|
+
|
|
93
|
+
children: JSX.Element
|
|
94
|
+
|
|
95
|
+
editError?: string
|
|
96
|
+
|
|
97
|
+
onSave: () => void
|
|
98
|
+
|
|
99
|
+
saving?: boolean
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
export interface DataTableDeleteDialogProps {
|
|
106
|
+
|
|
107
|
+
open: boolean
|
|
108
|
+
|
|
109
|
+
title: string
|
|
110
|
+
|
|
111
|
+
description: string
|
|
112
|
+
|
|
113
|
+
onClose: () => void
|
|
114
|
+
|
|
115
|
+
onConfirm: () => void
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
export interface ColumnDef<T> {
|
|
122
|
+
|
|
123
|
+
id: string
|
|
124
|
+
|
|
125
|
+
/** Column header label or JSX. */
|
|
126
|
+
|
|
127
|
+
header: JSX.Element | string
|
|
128
|
+
|
|
129
|
+
/** Class applied to the <TableHead>. */
|
|
130
|
+
|
|
131
|
+
headClass?: string
|
|
132
|
+
|
|
133
|
+
/** Class applied to each <TableCell> in this column (data rows and skeleton). */
|
|
134
|
+
|
|
135
|
+
cellClass?: string
|
|
136
|
+
|
|
137
|
+
/** Return cell content; DataTable wraps it in <TableCell class={cellClass}>. */
|
|
138
|
+
|
|
139
|
+
cell: (item: T) => JSX.Element | string | number | null
|
|
140
|
+
|
|
141
|
+
/** Custom skeleton element for loading state. Defaults to a pulse bar. */
|
|
142
|
+
|
|
143
|
+
skeleton?: JSX.Element
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
export interface DataTableGroupByProps<T> {
|
|
150
|
+
|
|
151
|
+
/** Return group key for each item; null = uncategorized. When set, rows are rendered in groups with a header row per group. */
|
|
152
|
+
|
|
153
|
+
groupBy: (item: T) => string | null
|
|
154
|
+
|
|
155
|
+
/** Render the group header row (one cell with colSpan). Required when groupBy is set. */
|
|
156
|
+
|
|
157
|
+
renderGroupHeader: (groupKey: string | null) => JSX.Element
|
|
158
|
+
|
|
159
|
+
/** Sort group keys: null (uncategorized) first, then others by key. Override for custom order. */
|
|
160
|
+
|
|
161
|
+
groupOrder?: (a: string | null, b: string | null) => number
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
export type DataTablePagination = Pick<
|
|
168
|
+
|
|
169
|
+
PaginationProps,
|
|
170
|
+
|
|
171
|
+
'totalItems' | 'page' | 'totalPages' | 'pageSize' | 'onPageChange' | 'onPageSizeChange' | 'pageSizeOptions' | 'maxPages' | 'showFirstLast'
|
|
172
|
+
|
|
173
|
+
>
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
export type DataTablePagingProps =
|
|
178
|
+
|
|
179
|
+
| { pagination: DataTablePagination; loadMore?: never }
|
|
180
|
+
|
|
181
|
+
| { loadMore: { hasMore: boolean; onLoadMore: () => void; loading?: boolean }; pagination?: never }
|
|
182
|
+
|
|
183
|
+
| { pagination?: undefined; loadMore?: undefined }
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
export type DataTableProps<T> = JSX.HTMLAttributes<HTMLDivElement> & DataTablePagingProps & {
|
|
188
|
+
|
|
189
|
+
description?: JSX.Element
|
|
190
|
+
|
|
191
|
+
search?: DataTableSearchProps
|
|
192
|
+
|
|
193
|
+
toolbarContent?: JSX.Element
|
|
194
|
+
|
|
195
|
+
toolbarActions?: JSX.Element
|
|
196
|
+
|
|
197
|
+
primaryButton?: DataTableButtonProps
|
|
198
|
+
|
|
199
|
+
secondaryButton?: DataTableButtonProps
|
|
200
|
+
|
|
201
|
+
addRow?: DataTableAddRowProps
|
|
202
|
+
|
|
203
|
+
editModal?: DataTableEditModalProps
|
|
204
|
+
|
|
205
|
+
deleteDialog?: DataTableDeleteDialogProps
|
|
206
|
+
|
|
207
|
+
groupBy?: DataTableGroupByProps<T>
|
|
208
|
+
|
|
209
|
+
/** When true, the table header row is not rendered. */
|
|
210
|
+
|
|
211
|
+
hideHeader?: boolean
|
|
212
|
+
|
|
213
|
+
emptyState?: {
|
|
214
|
+
|
|
215
|
+
title: string
|
|
216
|
+
|
|
217
|
+
description?: string
|
|
218
|
+
|
|
219
|
+
icon?: JSX.Element
|
|
220
|
+
|
|
221
|
+
actions?: JSX.Element
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
loading?: boolean
|
|
226
|
+
|
|
227
|
+
error?: Error | unknown
|
|
228
|
+
|
|
229
|
+
items: T[]
|
|
230
|
+
|
|
231
|
+
/** Column definitions. Drives header, body cells, skeleton, and colSpan automatically. */
|
|
232
|
+
|
|
233
|
+
columns: ColumnDef<T>[]
|
|
234
|
+
|
|
235
|
+
/** Escape hatch: return a complete <TableRow> to override, or null/undefined for column-based default. Returning anything else (fragment, bare text, false) produces invalid table markup. */
|
|
236
|
+
|
|
237
|
+
renderRowOverride?: (item: T) => JSX.Element | null | undefined
|
|
238
|
+
|
|
239
|
+
emptyMessage: string
|
|
240
|
+
|
|
241
|
+
/** Number of skeleton rows to show while loading. Default: 5. */
|
|
242
|
+
|
|
243
|
+
skeletonRows?: number
|
|
244
|
+
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
/** Default group order: null (uncategorized) first, then keys by localeCompare. */
|
|
250
|
+
|
|
251
|
+
function defaultGroupOrder(a: string | null, b: string | null): number {
|
|
252
|
+
|
|
253
|
+
if (a === b) return 0
|
|
254
|
+
|
|
255
|
+
if (a == null) return -1
|
|
256
|
+
|
|
257
|
+
if (b == null) return 1
|
|
258
|
+
|
|
259
|
+
return a.localeCompare(b, undefined, { sensitivity: 'base' })
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
|
|
267
|
+
* Data table with optional toolbar, inline add row, edit modal, delete dialog, and row grouping.
|
|
268
|
+
|
|
269
|
+
* Use groupBy to render rows in groups with a header row per group. All action buttons use type="button" so the table can be used inside a form without causing submit.
|
|
270
|
+
|
|
271
|
+
*/
|
|
272
|
+
|
|
273
|
+
export function DataTable<T>(props: DataTableProps<T>) {
|
|
274
|
+
|
|
275
|
+
const [local, others] = splitProps(props, [
|
|
276
|
+
|
|
277
|
+
'description', 'search', 'toolbarContent', 'toolbarActions',
|
|
278
|
+
|
|
279
|
+
'primaryButton', 'secondaryButton', 'addRow', 'editModal',
|
|
280
|
+
|
|
281
|
+
'deleteDialog', 'groupBy', 'pagination', 'hideHeader',
|
|
282
|
+
|
|
283
|
+
'emptyState', 'loadMore', 'loading', 'error', 'items',
|
|
284
|
+
|
|
285
|
+
'columns', 'renderRowOverride', 'emptyMessage',
|
|
286
|
+
|
|
287
|
+
'skeletonRows', 'class',
|
|
288
|
+
|
|
289
|
+
])
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
if (import.meta.env.DEV && local.columns.length === 0) {
|
|
294
|
+
|
|
295
|
+
console.warn('DataTable: columns must not be empty')
|
|
296
|
+
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
const colSpan = () => local.columns.length
|
|
302
|
+
|
|
303
|
+
const skeletonRowCount = createMemo(() => Array.from({ length: local.skeletonRows ?? 5 }))
|
|
304
|
+
|
|
305
|
+
const hasToolbar = () =>
|
|
306
|
+
|
|
307
|
+
local.description != null ||
|
|
308
|
+
|
|
309
|
+
local.search != null ||
|
|
310
|
+
|
|
311
|
+
local.toolbarContent != null ||
|
|
312
|
+
|
|
313
|
+
local.toolbarActions != null ||
|
|
314
|
+
|
|
315
|
+
local.primaryButton != null ||
|
|
316
|
+
|
|
317
|
+
local.secondaryButton != null ||
|
|
318
|
+
|
|
319
|
+
local.addRow != null
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
const groupedRows = createMemo((): { key: string | null; items: T[] }[] | null => {
|
|
324
|
+
|
|
325
|
+
const g = local.groupBy
|
|
326
|
+
|
|
327
|
+
if (!g) return null
|
|
328
|
+
|
|
329
|
+
if (local.items.length === 0) return []
|
|
330
|
+
|
|
331
|
+
const order = g.groupOrder ?? defaultGroupOrder
|
|
332
|
+
|
|
333
|
+
const map = new Map<string | null, T[]>()
|
|
334
|
+
|
|
335
|
+
for (const item of local.items) {
|
|
336
|
+
|
|
337
|
+
const key = g.groupBy(item)
|
|
338
|
+
|
|
339
|
+
const list = map.get(key)
|
|
340
|
+
|
|
341
|
+
if (list) list.push(item)
|
|
342
|
+
|
|
343
|
+
else map.set(key, [item])
|
|
344
|
+
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const keys = [...map.keys()].sort(order)
|
|
348
|
+
|
|
349
|
+
return keys.map((key) => ({ key, items: map.get(key)! }))
|
|
350
|
+
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
function renderEmptyRow() {
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
|
|
359
|
+
<Show when={!local.addRow?.showAddForm}>
|
|
360
|
+
|
|
361
|
+
<TableRow hover={false}>
|
|
362
|
+
|
|
363
|
+
<TableCell colSpan={colSpan()} class="p-0 align-top">
|
|
364
|
+
|
|
365
|
+
{local.emptyState ? (
|
|
366
|
+
|
|
367
|
+
<EmptyState
|
|
368
|
+
|
|
369
|
+
title={local.emptyState.title}
|
|
370
|
+
|
|
371
|
+
description={local.emptyState.description}
|
|
372
|
+
|
|
373
|
+
icon={local.emptyState.icon}
|
|
374
|
+
|
|
375
|
+
actions={local.emptyState.actions}
|
|
376
|
+
|
|
377
|
+
/>
|
|
378
|
+
|
|
379
|
+
) : (
|
|
380
|
+
|
|
381
|
+
<div class="py-8 text-center text-sm text-ink-500">
|
|
382
|
+
|
|
383
|
+
{local.emptyMessage}
|
|
384
|
+
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
)}
|
|
388
|
+
|
|
389
|
+
</TableCell>
|
|
390
|
+
|
|
391
|
+
</TableRow>
|
|
392
|
+
|
|
393
|
+
</Show>
|
|
394
|
+
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
function renderItem(item: T) {
|
|
402
|
+
|
|
403
|
+
const overridden = local.renderRowOverride?.(item)
|
|
404
|
+
|
|
405
|
+
if (import.meta.env.DEV && overridden != null && (typeof overridden !== 'object' || Array.isArray(overridden))) {
|
|
406
|
+
|
|
407
|
+
console.warn('DataTable: renderRowOverride must return a single <TableRow> element or null/undefined, not an array or fragment')
|
|
408
|
+
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return overridden ?? (
|
|
412
|
+
|
|
413
|
+
<TableRow>
|
|
414
|
+
|
|
415
|
+
<For each={local.columns}>
|
|
416
|
+
|
|
417
|
+
{(col) => <TableCell class={col.cellClass}>{col.cell(item)}</TableCell>}
|
|
418
|
+
|
|
419
|
+
</For>
|
|
420
|
+
|
|
421
|
+
</TableRow>
|
|
422
|
+
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
function FlatRows() {
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
|
|
433
|
+
<Show when={local.items.length > 0} fallback={renderEmptyRow()}>
|
|
434
|
+
|
|
435
|
+
<For each={local.items}>{(item) => renderItem(item)}</For>
|
|
436
|
+
|
|
437
|
+
</Show>
|
|
438
|
+
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
return (
|
|
446
|
+
|
|
447
|
+
<div {...others} class={cn('space-y-4', local.class)}>
|
|
448
|
+
|
|
449
|
+
{local.description}
|
|
450
|
+
|
|
451
|
+
<Show when={hasToolbar()}>
|
|
452
|
+
|
|
453
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
454
|
+
|
|
455
|
+
<Show when={local.search}>
|
|
456
|
+
|
|
457
|
+
{(search) => (
|
|
458
|
+
|
|
459
|
+
<div class="min-w-[200px] flex-1">
|
|
460
|
+
|
|
461
|
+
<Input
|
|
462
|
+
|
|
463
|
+
bare
|
|
464
|
+
|
|
465
|
+
compact
|
|
466
|
+
|
|
467
|
+
value={search().value}
|
|
468
|
+
|
|
469
|
+
onValueChange={search().onChange}
|
|
470
|
+
|
|
471
|
+
placeholder={search().placeholder}
|
|
472
|
+
|
|
473
|
+
class="w-full"
|
|
474
|
+
|
|
475
|
+
inputClass="rounded-lg"
|
|
476
|
+
|
|
477
|
+
/>
|
|
478
|
+
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
)}
|
|
482
|
+
|
|
483
|
+
</Show>
|
|
484
|
+
|
|
485
|
+
<Show when={local.toolbarContent}>
|
|
486
|
+
|
|
487
|
+
{local.toolbarContent}
|
|
488
|
+
|
|
489
|
+
</Show>
|
|
490
|
+
|
|
491
|
+
<Show when={local.toolbarActions}>
|
|
492
|
+
|
|
493
|
+
<div class="flex items-center gap-2 shrink-0">
|
|
494
|
+
|
|
495
|
+
{local.toolbarActions}
|
|
496
|
+
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
</Show>
|
|
500
|
+
|
|
501
|
+
<Show when={local.addRow || local.primaryButton || local.secondaryButton}>
|
|
502
|
+
|
|
503
|
+
<div class="ml-auto flex items-center gap-2 shrink-0">
|
|
504
|
+
|
|
505
|
+
<Show when={local.addRow}>
|
|
506
|
+
|
|
507
|
+
{(addRow) => (
|
|
508
|
+
|
|
509
|
+
<Button
|
|
510
|
+
|
|
511
|
+
type="button"
|
|
512
|
+
|
|
513
|
+
variant="primary"
|
|
514
|
+
|
|
515
|
+
size="sm"
|
|
516
|
+
|
|
517
|
+
class="shrink-0 rounded-lg"
|
|
518
|
+
|
|
519
|
+
label={addRow().addButtonLabel}
|
|
520
|
+
|
|
521
|
+
startIcon={<Plus size={16} />}
|
|
522
|
+
|
|
523
|
+
onClick={() => addRow().onToggleAddForm()}
|
|
524
|
+
|
|
525
|
+
/>
|
|
526
|
+
|
|
527
|
+
)}
|
|
528
|
+
|
|
529
|
+
</Show>
|
|
530
|
+
|
|
531
|
+
<Show when={!local.addRow && local.primaryButton}>
|
|
532
|
+
|
|
533
|
+
{(btn) => (
|
|
534
|
+
|
|
535
|
+
<Button
|
|
536
|
+
|
|
537
|
+
type="button"
|
|
538
|
+
|
|
539
|
+
variant="primary"
|
|
540
|
+
|
|
541
|
+
size="sm"
|
|
542
|
+
|
|
543
|
+
class="shrink-0 rounded-lg"
|
|
544
|
+
|
|
545
|
+
label={btn().label}
|
|
546
|
+
|
|
547
|
+
startIcon={btn().startIcon}
|
|
548
|
+
|
|
549
|
+
onClick={() => btn().onClick()}
|
|
550
|
+
|
|
551
|
+
/>
|
|
552
|
+
|
|
553
|
+
)}
|
|
554
|
+
|
|
555
|
+
</Show>
|
|
556
|
+
|
|
557
|
+
<Show when={local.secondaryButton}>
|
|
558
|
+
|
|
559
|
+
{(btn) => (
|
|
560
|
+
|
|
561
|
+
<Button
|
|
562
|
+
|
|
563
|
+
type="button"
|
|
564
|
+
|
|
565
|
+
variant="outlined"
|
|
566
|
+
|
|
567
|
+
size="sm"
|
|
568
|
+
|
|
569
|
+
class="shrink-0 rounded-lg"
|
|
570
|
+
|
|
571
|
+
label={btn().label}
|
|
572
|
+
|
|
573
|
+
startIcon={btn().startIcon}
|
|
574
|
+
|
|
575
|
+
onClick={() => btn().onClick()}
|
|
576
|
+
|
|
577
|
+
/>
|
|
578
|
+
|
|
579
|
+
)}
|
|
580
|
+
|
|
581
|
+
</Show>
|
|
582
|
+
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
</Show>
|
|
586
|
+
|
|
587
|
+
</div>
|
|
588
|
+
|
|
589
|
+
</Show>
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
<Show when={local.error}>
|
|
594
|
+
|
|
595
|
+
<p class="text-sm text-red-600 dark:text-red-400">
|
|
596
|
+
|
|
597
|
+
{local.error instanceof Error ? local.error.message : 'Failed to load'}
|
|
598
|
+
|
|
599
|
+
</p>
|
|
600
|
+
|
|
601
|
+
</Show>
|
|
602
|
+
|
|
603
|
+
<Show when={local.loading}>
|
|
604
|
+
|
|
605
|
+
<div role="status" aria-live="polite" class="sr-only">Loading</div>
|
|
606
|
+
|
|
607
|
+
</Show>
|
|
608
|
+
|
|
609
|
+
<div class={cn('overflow-x-auto', TABLE_CONTAINER_CLASS)}>
|
|
610
|
+
|
|
611
|
+
<Table class="min-w-full">
|
|
612
|
+
|
|
613
|
+
<Show when={!local.hideHeader}>
|
|
614
|
+
|
|
615
|
+
<TableHeader>
|
|
616
|
+
|
|
617
|
+
<TableRow>
|
|
618
|
+
|
|
619
|
+
<For each={local.columns}>
|
|
620
|
+
|
|
621
|
+
{(col) => (
|
|
622
|
+
|
|
623
|
+
<TableHead class={col.headClass}>
|
|
624
|
+
|
|
625
|
+
{typeof col.header === 'string' ? col.header : col.header}
|
|
626
|
+
|
|
627
|
+
</TableHead>
|
|
628
|
+
|
|
629
|
+
)}
|
|
630
|
+
|
|
631
|
+
</For>
|
|
632
|
+
|
|
633
|
+
</TableRow>
|
|
634
|
+
|
|
635
|
+
</TableHeader>
|
|
636
|
+
|
|
637
|
+
</Show>
|
|
638
|
+
|
|
639
|
+
<TableBody aria-busy={local.loading ? true : undefined}>
|
|
640
|
+
|
|
641
|
+
<Show
|
|
642
|
+
|
|
643
|
+
when={!local.loading}
|
|
644
|
+
|
|
645
|
+
fallback={
|
|
646
|
+
|
|
647
|
+
<For each={skeletonRowCount()}>
|
|
648
|
+
|
|
649
|
+
{() => (
|
|
650
|
+
|
|
651
|
+
<TableRow hover={false}>
|
|
652
|
+
|
|
653
|
+
<For each={local.columns}>
|
|
654
|
+
|
|
655
|
+
{(col) => (
|
|
656
|
+
|
|
657
|
+
<TableCell class={cn('py-3', col.cellClass)}>
|
|
658
|
+
|
|
659
|
+
{col.skeleton ?? (
|
|
660
|
+
|
|
661
|
+
<div class="h-4 w-full max-w-48 animate-pulse rounded bg-ink-200" />
|
|
662
|
+
|
|
663
|
+
)}
|
|
664
|
+
|
|
665
|
+
</TableCell>
|
|
666
|
+
|
|
667
|
+
)}
|
|
668
|
+
|
|
669
|
+
</For>
|
|
670
|
+
|
|
671
|
+
</TableRow>
|
|
672
|
+
|
|
673
|
+
)}
|
|
674
|
+
|
|
675
|
+
</For>
|
|
676
|
+
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
>
|
|
680
|
+
|
|
681
|
+
<Show when={local.addRow?.showAddForm}>
|
|
682
|
+
|
|
683
|
+
<TableRow class="bg-primary-50/50 dark:bg-primary-950/20" hover={false}>
|
|
684
|
+
|
|
685
|
+
{local.addRow!.renderAddCells()}
|
|
686
|
+
|
|
687
|
+
</TableRow>
|
|
688
|
+
|
|
689
|
+
</Show>
|
|
690
|
+
|
|
691
|
+
<Show when={groupedRows()} fallback={<FlatRows />}>
|
|
692
|
+
|
|
693
|
+
{(groups) => (
|
|
694
|
+
|
|
695
|
+
<Show when={groups().length > 0} fallback={renderEmptyRow()}>
|
|
696
|
+
|
|
697
|
+
<For each={groups()}>
|
|
698
|
+
|
|
699
|
+
{({ key, items: groupItems }) => [
|
|
700
|
+
|
|
701
|
+
<TableRow class="bg-ink-50/50 font-medium text-ink-700" hover={false}>
|
|
702
|
+
|
|
703
|
+
<TableCell colSpan={colSpan()} class="py-2 pl-4 text-sm font-medium" role="rowheader">
|
|
704
|
+
|
|
705
|
+
{local.groupBy!.renderGroupHeader(key)}
|
|
706
|
+
|
|
707
|
+
</TableCell>
|
|
708
|
+
|
|
709
|
+
</TableRow>,
|
|
710
|
+
|
|
711
|
+
...groupItems.map((item) => renderItem(item)),
|
|
712
|
+
|
|
713
|
+
]}
|
|
714
|
+
|
|
715
|
+
</For>
|
|
716
|
+
|
|
717
|
+
</Show>
|
|
718
|
+
|
|
719
|
+
)}
|
|
720
|
+
|
|
721
|
+
</Show>
|
|
722
|
+
|
|
723
|
+
<Show when={local.addRow?.addError && local.addRow?.showAddForm}>
|
|
724
|
+
|
|
725
|
+
<TableRow hover={false}>
|
|
726
|
+
|
|
727
|
+
<TableCell colSpan={colSpan()} class="bg-red-50/80 dark:bg-red-950/30 text-sm text-red-600 dark:text-red-400">
|
|
728
|
+
|
|
729
|
+
{local.addRow!.addError}
|
|
730
|
+
|
|
731
|
+
</TableCell>
|
|
732
|
+
|
|
733
|
+
</TableRow>
|
|
734
|
+
|
|
735
|
+
</Show>
|
|
736
|
+
|
|
737
|
+
</Show>
|
|
738
|
+
|
|
739
|
+
</TableBody>
|
|
740
|
+
|
|
741
|
+
</Table>
|
|
742
|
+
|
|
743
|
+
<Show when={local.loadMore?.hasMore}>
|
|
744
|
+
|
|
745
|
+
<div class="flex justify-center border-t border-surface-border bg-surface-raised px-6 py-4">
|
|
746
|
+
|
|
747
|
+
<Button
|
|
748
|
+
|
|
749
|
+
type="button"
|
|
750
|
+
|
|
751
|
+
variant="outlined"
|
|
752
|
+
|
|
753
|
+
size="sm"
|
|
754
|
+
|
|
755
|
+
class="rounded-lg"
|
|
756
|
+
|
|
757
|
+
label="Load more"
|
|
758
|
+
|
|
759
|
+
loading={local.loadMore!.loading}
|
|
760
|
+
|
|
761
|
+
onClick={() => local.loadMore!.onLoadMore()}
|
|
762
|
+
|
|
763
|
+
/>
|
|
764
|
+
|
|
765
|
+
</div>
|
|
766
|
+
|
|
767
|
+
</Show>
|
|
768
|
+
|
|
769
|
+
<Show when={local.pagination}>
|
|
770
|
+
|
|
771
|
+
{(pagination) => (
|
|
772
|
+
|
|
773
|
+
<Pagination
|
|
774
|
+
|
|
775
|
+
{...pagination()}
|
|
776
|
+
|
|
777
|
+
class="border-t border-surface-border bg-surface-raised px-6 py-3"
|
|
778
|
+
|
|
779
|
+
/>
|
|
780
|
+
|
|
781
|
+
)}
|
|
782
|
+
|
|
783
|
+
</Show>
|
|
784
|
+
|
|
785
|
+
</div>
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
<Show when={local.editModal}>
|
|
790
|
+
|
|
791
|
+
{(modal) => (
|
|
792
|
+
|
|
793
|
+
<Dialog
|
|
794
|
+
|
|
795
|
+
open={modal().open}
|
|
796
|
+
|
|
797
|
+
onClose={modal().onClose}
|
|
798
|
+
|
|
799
|
+
size="md"
|
|
800
|
+
|
|
801
|
+
header={<h3 class="text-lg font-semibold text-ink-900">{modal().title}</h3>}
|
|
802
|
+
|
|
803
|
+
footer={
|
|
804
|
+
|
|
805
|
+
<div class="flex justify-end gap-2">
|
|
806
|
+
|
|
807
|
+
<Button type="button" variant="link" size="sm" onClick={modal().onClose} class="rounded-lg">
|
|
808
|
+
|
|
809
|
+
Cancel
|
|
810
|
+
|
|
811
|
+
</Button>
|
|
812
|
+
|
|
813
|
+
<Button
|
|
814
|
+
|
|
815
|
+
type="button"
|
|
816
|
+
|
|
817
|
+
variant="primary"
|
|
818
|
+
|
|
819
|
+
size="sm"
|
|
820
|
+
|
|
821
|
+
onClick={modal().onSave}
|
|
822
|
+
|
|
823
|
+
loading={modal().saving}
|
|
824
|
+
|
|
825
|
+
class="rounded-lg"
|
|
826
|
+
|
|
827
|
+
>
|
|
828
|
+
|
|
829
|
+
Save
|
|
830
|
+
|
|
831
|
+
</Button>
|
|
832
|
+
|
|
833
|
+
</div>
|
|
834
|
+
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
>
|
|
838
|
+
|
|
839
|
+
<div class="space-y-4">{modal().children}</div>
|
|
840
|
+
|
|
841
|
+
<Show when={modal().editError}>
|
|
842
|
+
|
|
843
|
+
<p class="mt-3 text-sm text-red-600 dark:text-red-400">{modal().editError}</p>
|
|
844
|
+
|
|
845
|
+
</Show>
|
|
846
|
+
|
|
847
|
+
</Dialog>
|
|
848
|
+
|
|
849
|
+
)}
|
|
850
|
+
|
|
851
|
+
</Show>
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
<Show when={local.deleteDialog}>
|
|
856
|
+
|
|
857
|
+
{(dialog) => (
|
|
858
|
+
|
|
859
|
+
<AlertDialog
|
|
860
|
+
|
|
861
|
+
open={dialog().open}
|
|
862
|
+
|
|
863
|
+
onOpenChange={(open) => !open && dialog().onClose()}
|
|
864
|
+
|
|
865
|
+
title={dialog().title}
|
|
866
|
+
|
|
867
|
+
description={dialog().description}
|
|
868
|
+
|
|
869
|
+
confirmLabel="Delete"
|
|
870
|
+
|
|
871
|
+
destructive
|
|
872
|
+
|
|
873
|
+
onConfirm={dialog().onConfirm}
|
|
874
|
+
|
|
875
|
+
/>
|
|
876
|
+
|
|
877
|
+
)}
|
|
878
|
+
|
|
879
|
+
</Show>
|
|
880
|
+
|
|
881
|
+
</div>
|
|
882
|
+
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
}
|
|
886
|
+
|