elysia-rack 0.2.0 → 0.2.2

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.
@@ -0,0 +1,753 @@
1
+ import {
2
+ Breadcrumb,
3
+ Masthead,
4
+ Sidebar
5
+ } from "./index-xsfqad54.js";
6
+ import {
7
+ getRackTree
8
+ } from "./index-4wxvmd8a.js";
9
+ import {
10
+ resolveDonateProp
11
+ } from "./index-h3zbq796.js";
12
+ import"./index-37x76zdn.js";
13
+
14
+ // src/react/page/components/href.ts
15
+ function href(params, over = {}) {
16
+ const query = new URLSearchParams;
17
+ for (const [key, value] of Object.entries({ ...params, ...over })) {
18
+ if (value === undefined || value === null || value === "")
19
+ continue;
20
+ query.set(key, Array.isArray(value) ? value.join(",") : String(value));
21
+ }
22
+ const str = query.toString();
23
+ return str ? `?${str}` : "?";
24
+ }
25
+ function pageWindow(page, total) {
26
+ const keep = new Set([1, total, page - 1, page, page + 1]);
27
+ const sorted = [...keep].filter((p) => p >= 1 && p <= total).sort((a, b) => a - b);
28
+ const out = [];
29
+ let prev = 0;
30
+ for (const p of sorted) {
31
+ if (p - prev > 1)
32
+ out.push("…");
33
+ out.push(p);
34
+ prev = p;
35
+ }
36
+ return out;
37
+ }
38
+
39
+ // src/react/page/components/DataTable.tsx
40
+ import { jsxDEV } from "react/jsx-dev-runtime";
41
+ function statusBadge(value) {
42
+ if (typeof value !== "string")
43
+ return null;
44
+ const text = value.toLowerCase();
45
+ if (text === "active")
46
+ return "bg-success-muted text-success-muted-foreground";
47
+ if (text === "archived")
48
+ return "bg-warning-muted text-warning-muted-foreground";
49
+ return "bg-badge-background text-badge-foreground";
50
+ }
51
+ function cell(value) {
52
+ if (value === null || value === undefined)
53
+ return "";
54
+ if (value instanceof Date)
55
+ return value.toLocaleString("en-US");
56
+ if (typeof value === "object")
57
+ return JSON.stringify(value);
58
+ return String(value);
59
+ }
60
+ function DataTable({
61
+ columns,
62
+ rows,
63
+ sortable,
64
+ params,
65
+ selectable,
66
+ editable,
67
+ primaryKey
68
+ }) {
69
+ const activeSort = typeof params.sort === "string" ? params.sort : "";
70
+ const activeOrder = params.order === "desc" ? "desc" : "asc";
71
+ const pk = primaryKey ?? "id";
72
+ const extra = (selectable ? 1 : 0) + (editable ? 1 : 0);
73
+ if (rows.length === 0)
74
+ return /* @__PURE__ */ jsxDEV("div", {
75
+ className: "border-table-border overflow-x-auto rounded-md border",
76
+ children: /* @__PURE__ */ jsxDEV("table", {
77
+ className: "bg-table w-full border-collapse text-left text-sm",
78
+ "data-selectable": selectable ? true : undefined,
79
+ "data-editable": editable ? true : undefined,
80
+ "data-pk": primaryKey ?? "id",
81
+ children: /* @__PURE__ */ jsxDEV("tbody", {
82
+ "data-panel-rows": true,
83
+ children: /* @__PURE__ */ jsxDEV("tr", {
84
+ children: /* @__PURE__ */ jsxDEV("td", {
85
+ colSpan: columns.length + extra || 1,
86
+ className: "text-text-muted px-4 py-10 text-center",
87
+ children: "No records found."
88
+ }, undefined, false, undefined, this)
89
+ }, undefined, false, undefined, this)
90
+ }, undefined, false, undefined, this)
91
+ }, undefined, false, undefined, this)
92
+ }, undefined, false, undefined, this);
93
+ return /* @__PURE__ */ jsxDEV("div", {
94
+ className: "border-table-border overflow-x-auto rounded-md border",
95
+ children: /* @__PURE__ */ jsxDEV("table", {
96
+ className: "bg-table w-full border-collapse text-left text-sm",
97
+ "data-selectable": selectable ? true : undefined,
98
+ "data-editable": editable ? true : undefined,
99
+ "data-pk": primaryKey ?? "id",
100
+ children: [
101
+ /* @__PURE__ */ jsxDEV("thead", {
102
+ children: /* @__PURE__ */ jsxDEV("tr", {
103
+ className: "bg-table-header",
104
+ children: [
105
+ selectable ? /* @__PURE__ */ jsxDEV("th", {
106
+ className: "border-table-border border-b px-4 py-2",
107
+ children: /* @__PURE__ */ jsxDEV("input", {
108
+ type: "checkbox",
109
+ "data-select-all": true,
110
+ "aria-label": "Select all"
111
+ }, undefined, false, undefined, this)
112
+ }, undefined, false, undefined, this) : null,
113
+ columns.map((col) => /* @__PURE__ */ jsxDEV("th", {
114
+ "data-col": col,
115
+ className: "border-table-border border-b px-4 py-2 text-xs font-bold tracking-wider uppercase",
116
+ children: sortable?.includes(col) ? /* @__PURE__ */ jsxDEV("a", {
117
+ className: "text-link hover:text-link-hover",
118
+ "data-qlink": true,
119
+ "data-sort-link": col,
120
+ href: href(params, {
121
+ sort: col,
122
+ order: activeSort === col && activeOrder === "asc" ? "desc" : "asc"
123
+ }),
124
+ children: [
125
+ col,
126
+ /* @__PURE__ */ jsxDEV("span", {
127
+ "data-sort-ind": true,
128
+ children: activeSort === col ? activeOrder === "asc" ? " ▲" : " ▼" : ""
129
+ }, undefined, false, undefined, this)
130
+ ]
131
+ }, undefined, true, undefined, this) : col
132
+ }, col, false, undefined, this)),
133
+ editable ? /* @__PURE__ */ jsxDEV("th", {
134
+ className: "border-table-border border-b px-4 py-2 text-xs font-bold tracking-wider uppercase",
135
+ children: "Actions"
136
+ }, undefined, false, undefined, this) : null
137
+ ]
138
+ }, undefined, true, undefined, this)
139
+ }, undefined, false, undefined, this),
140
+ /* @__PURE__ */ jsxDEV("tbody", {
141
+ "data-panel-rows": true,
142
+ children: rows.map((row, i) => /* @__PURE__ */ jsxDEV("tr", {
143
+ className: "hover:bg-table-row-hover",
144
+ children: [
145
+ selectable ? /* @__PURE__ */ jsxDEV("td", {
146
+ className: "border-table-border border-t px-4 py-2",
147
+ children: /* @__PURE__ */ jsxDEV("input", {
148
+ type: "checkbox",
149
+ "data-select-row": true,
150
+ value: String(row[pk] ?? ""),
151
+ "aria-label": `Select row ${String(row[pk] ?? i)}`
152
+ }, undefined, false, undefined, this)
153
+ }, undefined, false, undefined, this) : null,
154
+ columns.map((col) => /* @__PURE__ */ jsxDEV("td", {
155
+ className: "border-table-border border-t px-4 py-2",
156
+ children: col === "status" && statusBadge(row[col]) ? /* @__PURE__ */ jsxDEV("span", {
157
+ className: `rounded-full px-2 py-0.5 text-xs font-semibold ${statusBadge(row[col]) ?? ""}`,
158
+ children: cell(row[col])
159
+ }, undefined, false, undefined, this) : cell(row[col])
160
+ }, col, false, undefined, this)),
161
+ editable ? /* @__PURE__ */ jsxDEV("td", {
162
+ className: "border-table-border border-t px-4 py-2 whitespace-nowrap",
163
+ children: /* @__PURE__ */ jsxDEV("div", {
164
+ className: "border-table-border inline-flex overflow-hidden rounded-md border",
165
+ children: [
166
+ /* @__PURE__ */ jsxDEV("button", {
167
+ type: "button",
168
+ "data-edit-id": String(row[pk] ?? ""),
169
+ className: "text-link hover:bg-table-row-hover px-2 py-1 text-sm font-semibold",
170
+ children: "Edit"
171
+ }, undefined, false, undefined, this),
172
+ /* @__PURE__ */ jsxDEV("button", {
173
+ type: "button",
174
+ "data-delete-id": String(row[pk] ?? ""),
175
+ className: "border-table-border text-destructive hover:bg-table-row-hover border-l px-2 py-1 text-sm font-semibold",
176
+ children: "Delete"
177
+ }, undefined, false, undefined, this)
178
+ ]
179
+ }, undefined, true, undefined, this)
180
+ }, undefined, false, undefined, this) : null
181
+ ]
182
+ }, i, true, undefined, this))
183
+ }, undefined, false, undefined, this)
184
+ ]
185
+ }, undefined, true, undefined, this)
186
+ }, undefined, false, undefined, this);
187
+ }
188
+
189
+ // src/react/page/components/Pagination.tsx
190
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
191
+ var pageClass = "border-border rounded-md border px-3 py-1 text-sm hover:bg-navigation-hover";
192
+ var currentClass = "bg-primary text-primary-foreground rounded-md px-3 py-1 text-sm font-bold";
193
+ var disabledClass = "border-border text-text-disabled rounded-md border px-3 py-1 text-sm";
194
+ function Pagination({ page, totalPages, total, limit, params }) {
195
+ const from = total === 0 ? 0 : (page - 1) * limit + 1;
196
+ const to = Math.min(page * limit, total);
197
+ return /* @__PURE__ */ jsxDEV2("nav", {
198
+ className: "flex flex-wrap items-center justify-between gap-3 py-4 text-sm",
199
+ children: [
200
+ /* @__PURE__ */ jsxDEV2("p", {
201
+ className: "text-text-muted",
202
+ "data-panel-count": true,
203
+ children: [
204
+ "Page ",
205
+ page,
206
+ " of ",
207
+ totalPages,
208
+ " — ",
209
+ from,
210
+ "–",
211
+ to,
212
+ " of ",
213
+ total,
214
+ " records"
215
+ ]
216
+ }, undefined, true, undefined, this),
217
+ /* @__PURE__ */ jsxDEV2("div", {
218
+ className: "flex items-center gap-1",
219
+ "data-panel-pages": true,
220
+ children: [
221
+ page > 1 ? /* @__PURE__ */ jsxDEV2("a", {
222
+ className: pageClass,
223
+ "data-qlink": true,
224
+ href: href(params, { page: page - 1 }),
225
+ children: "← Previous"
226
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("span", {
227
+ className: disabledClass,
228
+ children: "← Previous"
229
+ }, undefined, false, undefined, this),
230
+ pageWindow(page, totalPages).map((p, i) => p === "…" ? /* @__PURE__ */ jsxDEV2("span", {
231
+ className: "text-text-muted px-1",
232
+ children: "…"
233
+ }, `e${i}`, false, undefined, this) : p === page ? /* @__PURE__ */ jsxDEV2("span", {
234
+ className: currentClass,
235
+ children: p
236
+ }, p, false, undefined, this) : /* @__PURE__ */ jsxDEV2("a", {
237
+ className: pageClass,
238
+ "data-qlink": true,
239
+ href: href(params, { page: p }),
240
+ children: p
241
+ }, p, false, undefined, this)),
242
+ page < totalPages ? /* @__PURE__ */ jsxDEV2("a", {
243
+ className: pageClass,
244
+ "data-qlink": true,
245
+ href: href(params, { page: page + 1 }),
246
+ children: "Next →"
247
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("span", {
248
+ className: disabledClass,
249
+ children: "Next →"
250
+ }, undefined, false, undefined, this)
251
+ ]
252
+ }, undefined, true, undefined, this)
253
+ ]
254
+ }, undefined, true, undefined, this);
255
+ }
256
+
257
+ // src/react/page/components/Toolbar.tsx
258
+ import { jsxDEV as jsxDEV3, Fragment } from "react/jsx-dev-runtime";
259
+ var inputClass = "bg-input-background border-input text-foreground placeholder:text-form-placeholder rounded-md border px-3 py-1.5 text-sm";
260
+ var labelClass = "flex flex-col gap-1 text-xs font-semibold tracking-wider text-form-label uppercase";
261
+ function Toolbar({ searchable, filterable, sortable, params }) {
262
+ if (!searchable?.length && !filterable?.length && !sortable?.length)
263
+ return null;
264
+ return /* @__PURE__ */ jsxDEV3("form", {
265
+ method: "get",
266
+ action: "",
267
+ className: "flex flex-wrap items-end gap-3 py-4",
268
+ children: [
269
+ searchable && searchable.length > 0 ? /* @__PURE__ */ jsxDEV3("label", {
270
+ className: labelClass,
271
+ children: [
272
+ "Search",
273
+ /* @__PURE__ */ jsxDEV3("input", {
274
+ name: "search",
275
+ "data-search-input": true,
276
+ defaultValue: String(params.search ?? ""),
277
+ placeholder: searchable.join(", "),
278
+ autoComplete: "off",
279
+ className: inputClass
280
+ }, undefined, false, undefined, this)
281
+ ]
282
+ }, undefined, true, undefined, this) : null,
283
+ (filterable ?? []).map((field) => /* @__PURE__ */ jsxDEV3("label", {
284
+ className: labelClass,
285
+ children: [
286
+ field,
287
+ /* @__PURE__ */ jsxDEV3("input", {
288
+ name: field,
289
+ defaultValue: String(params[field] ?? ""),
290
+ className: inputClass
291
+ }, undefined, false, undefined, this)
292
+ ]
293
+ }, field, true, undefined, this)),
294
+ sortable && sortable.length > 0 ? /* @__PURE__ */ jsxDEV3(Fragment, {
295
+ children: [
296
+ /* @__PURE__ */ jsxDEV3("label", {
297
+ className: labelClass,
298
+ children: [
299
+ "Sort",
300
+ /* @__PURE__ */ jsxDEV3("select", {
301
+ name: "sort",
302
+ defaultValue: String(params.sort ?? ""),
303
+ className: inputClass,
304
+ children: [
305
+ /* @__PURE__ */ jsxDEV3("option", {
306
+ value: "",
307
+ children: "—"
308
+ }, undefined, false, undefined, this),
309
+ sortable.map((field) => /* @__PURE__ */ jsxDEV3("option", {
310
+ value: field,
311
+ children: field
312
+ }, field, false, undefined, this))
313
+ ]
314
+ }, undefined, true, undefined, this)
315
+ ]
316
+ }, undefined, true, undefined, this),
317
+ /* @__PURE__ */ jsxDEV3("label", {
318
+ className: labelClass,
319
+ children: [
320
+ "Order",
321
+ /* @__PURE__ */ jsxDEV3("select", {
322
+ name: "order",
323
+ defaultValue: String(params.order ?? ""),
324
+ className: inputClass,
325
+ children: [
326
+ /* @__PURE__ */ jsxDEV3("option", {
327
+ value: "asc",
328
+ children: "Ascending"
329
+ }, undefined, false, undefined, this),
330
+ /* @__PURE__ */ jsxDEV3("option", {
331
+ value: "desc",
332
+ children: "Descending"
333
+ }, undefined, false, undefined, this)
334
+ ]
335
+ }, undefined, true, undefined, this)
336
+ ]
337
+ }, undefined, true, undefined, this)
338
+ ]
339
+ }, undefined, true, undefined, this) : null,
340
+ /* @__PURE__ */ jsxDEV3("button", {
341
+ type: "submit",
342
+ className: "bg-primary text-primary-foreground rounded-md px-4 py-1.5 text-sm font-semibold",
343
+ children: "Apply"
344
+ }, undefined, false, undefined, this)
345
+ ]
346
+ }, undefined, true, undefined, this);
347
+ }
348
+
349
+ // src/react/page/Panel.tsx
350
+ import { jsxDEV as jsxDEV4, Fragment as Fragment2 } from "react/jsx-dev-runtime";
351
+ var inputClass2 = "bg-input-background border-input text-foreground rounded-md border px-3 py-1.5 text-sm font-normal normal-case";
352
+ var fieldLabelClass = "flex flex-col gap-1 text-xs font-semibold tracking-wider text-form-label uppercase";
353
+ function FieldInput({ field, required }) {
354
+ switch (field.kind) {
355
+ case "enum":
356
+ return /* @__PURE__ */ jsxDEV4("label", {
357
+ className: fieldLabelClass,
358
+ children: [
359
+ field.name,
360
+ /* @__PURE__ */ jsxDEV4("select", {
361
+ name: field.name,
362
+ required,
363
+ defaultValue: "",
364
+ className: inputClass2,
365
+ children: [
366
+ /* @__PURE__ */ jsxDEV4("option", {
367
+ value: "",
368
+ children: "—"
369
+ }, undefined, false, undefined, this),
370
+ (field.enumValues ?? []).map((v) => /* @__PURE__ */ jsxDEV4("option", {
371
+ value: v,
372
+ children: v
373
+ }, v, false, undefined, this))
374
+ ]
375
+ }, undefined, true, undefined, this)
376
+ ]
377
+ }, undefined, true, undefined, this);
378
+ case "boolean":
379
+ return /* @__PURE__ */ jsxDEV4("label", {
380
+ className: fieldLabelClass,
381
+ children: [
382
+ field.name,
383
+ /* @__PURE__ */ jsxDEV4("input", {
384
+ type: "checkbox",
385
+ name: field.name,
386
+ value: "true",
387
+ className: "h-4 w-4"
388
+ }, undefined, false, undefined, this)
389
+ ]
390
+ }, undefined, true, undefined, this);
391
+ case "integer":
392
+ case "number":
393
+ return /* @__PURE__ */ jsxDEV4("label", {
394
+ className: fieldLabelClass,
395
+ children: [
396
+ field.name,
397
+ /* @__PURE__ */ jsxDEV4("input", {
398
+ type: "number",
399
+ step: field.kind === "number" ? "any" : "1",
400
+ name: field.name,
401
+ required,
402
+ className: inputClass2
403
+ }, undefined, false, undefined, this)
404
+ ]
405
+ }, undefined, true, undefined, this);
406
+ case "date":
407
+ return /* @__PURE__ */ jsxDEV4("label", {
408
+ className: fieldLabelClass,
409
+ children: [
410
+ field.name,
411
+ /* @__PURE__ */ jsxDEV4("input", {
412
+ type: "datetime-local",
413
+ name: field.name,
414
+ required,
415
+ className: inputClass2
416
+ }, undefined, false, undefined, this)
417
+ ]
418
+ }, undefined, true, undefined, this);
419
+ case "json":
420
+ return /* @__PURE__ */ jsxDEV4("label", {
421
+ className: fieldLabelClass,
422
+ children: [
423
+ field.name,
424
+ /* @__PURE__ */ jsxDEV4("textarea", {
425
+ name: field.name,
426
+ "data-json": true,
427
+ rows: 3,
428
+ placeholder: "{}",
429
+ className: `${inputClass2} font-mono`
430
+ }, undefined, false, undefined, this)
431
+ ]
432
+ }, undefined, true, undefined, this);
433
+ default:
434
+ return /* @__PURE__ */ jsxDEV4("label", {
435
+ className: fieldLabelClass,
436
+ children: [
437
+ field.name,
438
+ /* @__PURE__ */ jsxDEV4("input", {
439
+ name: field.name,
440
+ required,
441
+ className: inputClass2
442
+ }, undefined, false, undefined, this)
443
+ ]
444
+ }, undefined, true, undefined, this);
445
+ }
446
+ }
447
+ function itemLabel(r) {
448
+ return r.metadata.pluralLabel ?? r.metadata.label ?? r.metadata.id;
449
+ }
450
+ function toSidebarItem(node, activeId) {
451
+ return {
452
+ id: node.metadata.id,
453
+ label: itemLabel(node),
454
+ icon: node.metadata.icon,
455
+ href: node.path,
456
+ active: node.metadata.id === activeId,
457
+ children: node.children.filter((c) => !c.metadata.hidden).map((c) => toSidebarItem(c, activeId))
458
+ };
459
+ }
460
+ function buildGroupsFromTree(tree, activeId) {
461
+ const byGroup = new Map;
462
+ for (const root of tree) {
463
+ if (root.metadata.hidden)
464
+ continue;
465
+ const name = root.metadata.group ?? "General";
466
+ let group = byGroup.get(name);
467
+ if (!group) {
468
+ group = { name, items: [] };
469
+ byGroup.set(name, group);
470
+ }
471
+ group.items.push(toSidebarItem(root, activeId));
472
+ }
473
+ return [...byGroup.values()].sort((a, b) => a.name.localeCompare(b.name));
474
+ }
475
+ async function Panel(props) {
476
+ const params = props.params ?? {};
477
+ const page = Math.max(Number(params.page ?? 1) || 1, 1);
478
+ const limit = Math.max(Number(params.limit ?? props.query?.pagination?.default ?? 20) || 1, 1);
479
+ let rows = [];
480
+ let total = 0;
481
+ if (props.load) {
482
+ try {
483
+ const result = await props.load(params);
484
+ rows = result.data;
485
+ total = result.total;
486
+ } catch {}
487
+ }
488
+ const columns = rows.length > 0 ? Object.keys(rows[0] ?? {}).filter((c) => c !== "deletedAt") : [];
489
+ const title = props.metadata?.pluralLabel ?? props.metadata?.label ?? props.resource ?? "Panel";
490
+ const totalPages = Math.max(1, Math.ceil(total / limit));
491
+ const pk = props.primaryKey ?? "id";
492
+ const deletedAt = props.deletedAtField ?? "deletedAt";
493
+ const fieldDefs = props.fields?.length ? props.fields.filter((f) => !f.autoIncrement && f.name !== deletedAt).map((f) => ({
494
+ name: f.name,
495
+ kind: f.kind,
496
+ nullable: f.nullable,
497
+ enumValues: f.enumValues
498
+ })) : columns.filter((c) => c !== deletedAt).map((name) => ({
499
+ name,
500
+ kind: "text",
501
+ nullable: name === pk
502
+ }));
503
+ const createFields = fieldDefs;
504
+ const editFields = fieldDefs.filter((f) => f.name !== pk);
505
+ const isRequired = (f) => !f.nullable && f.kind !== "boolean";
506
+ const canCreate = props.operations?.create !== false;
507
+ const canEdit = props.operations?.update !== false || props.operations?.replace !== false;
508
+ const canDelete = props.operations?.delete !== false;
509
+ const selectable = canEdit || canDelete;
510
+ const tree = getRackTree().filter((r) => !r.metadata.hidden);
511
+ const groups = buildGroupsFromTree(tree, props.resource);
512
+ const donate = resolveDonateProp(props.donate);
513
+ const trail = [
514
+ { label: "Dashboard", href: "/" },
515
+ ...props.metadata?.group ? [{ label: props.metadata.group }] : [],
516
+ { label: title }
517
+ ];
518
+ return /* @__PURE__ */ jsxDEV4("div", {
519
+ className: "koran-paper font-koran-body text-foreground flex min-h-screen flex-col",
520
+ children: /* @__PURE__ */ jsxDEV4("div", {
521
+ className: "flex flex-1 gap-6 p-4 sm:p-6",
522
+ children: [
523
+ /* @__PURE__ */ jsxDEV4(Sidebar, {
524
+ title: "Panel",
525
+ groups
526
+ }, undefined, false, undefined, this),
527
+ /* @__PURE__ */ jsxDEV4("main", {
528
+ className: "flex min-w-0 flex-1 flex-col",
529
+ children: [
530
+ /* @__PURE__ */ jsxDEV4(Breadcrumb, {
531
+ trail
532
+ }, undefined, false, undefined, this),
533
+ /* @__PURE__ */ jsxDEV4("div", {
534
+ className: "mx-auto w-full max-w-5xl px-4 pb-10 sm:px-6",
535
+ children: [
536
+ /* @__PURE__ */ jsxDEV4(Masthead, {
537
+ title,
538
+ group: props.metadata?.group,
539
+ resource: props.resource,
540
+ total
541
+ }, undefined, false, undefined, this),
542
+ /* @__PURE__ */ jsxDEV4("div", {
543
+ "data-query-url": props.queryUrl,
544
+ children: [
545
+ /* @__PURE__ */ jsxDEV4(Toolbar, {
546
+ searchable: props.query?.searchable,
547
+ filterable: props.query?.filterable,
548
+ sortable: props.query?.sortable,
549
+ params
550
+ }, undefined, false, undefined, this),
551
+ /* @__PURE__ */ jsxDEV4("div", {
552
+ className: "flex items-center justify-between gap-3 pb-3",
553
+ children: [
554
+ /* @__PURE__ */ jsxDEV4("div", {
555
+ "data-bulk-bar": true,
556
+ style: { display: "none" },
557
+ className: "items-center gap-2",
558
+ children: [
559
+ /* @__PURE__ */ jsxDEV4("span", {
560
+ "data-bulk-count": true,
561
+ className: "text-sm text-text-muted"
562
+ }, undefined, false, undefined, this),
563
+ canDelete ? /* @__PURE__ */ jsxDEV4("button", {
564
+ type: "button",
565
+ "data-bulk-delete": true,
566
+ className: "bg-destructive text-destructive-foreground rounded-md px-3 py-1.5 text-sm font-semibold",
567
+ children: "Delete selected"
568
+ }, undefined, false, undefined, this) : null,
569
+ canEdit ? /* @__PURE__ */ jsxDEV4("span", {
570
+ className: "inline-flex items-center gap-2",
571
+ children: [
572
+ /* @__PURE__ */ jsxDEV4("select", {
573
+ "data-bulk-field": true,
574
+ "aria-label": "Bulk field",
575
+ className: "bg-input-background border-input rounded-md border px-2 py-1.5 text-sm",
576
+ children: editFields.map((f) => /* @__PURE__ */ jsxDEV4("option", {
577
+ value: f.name,
578
+ children: f.name
579
+ }, f.name, false, undefined, this))
580
+ }, undefined, false, undefined, this),
581
+ /* @__PURE__ */ jsxDEV4("input", {
582
+ "data-bulk-value": true,
583
+ placeholder: "New value",
584
+ "aria-label": "Bulk value",
585
+ className: "bg-input-background border-input placeholder:text-form-placeholder rounded-md border px-2 py-1.5 text-sm"
586
+ }, undefined, false, undefined, this),
587
+ /* @__PURE__ */ jsxDEV4("button", {
588
+ type: "button",
589
+ "data-bulk-apply": true,
590
+ className: "bg-secondary text-secondary-foreground rounded-md px-3 py-1.5 text-sm font-semibold",
591
+ children: "Apply"
592
+ }, undefined, false, undefined, this)
593
+ ]
594
+ }, undefined, true, undefined, this) : null,
595
+ /* @__PURE__ */ jsxDEV4("button", {
596
+ type: "button",
597
+ "data-bulk-clear": true,
598
+ className: "text-text-muted text-sm underline",
599
+ children: "Clear"
600
+ }, undefined, false, undefined, this)
601
+ ]
602
+ }, undefined, true, undefined, this),
603
+ canCreate ? /* @__PURE__ */ jsxDEV4("button", {
604
+ type: "button",
605
+ "data-open-create": true,
606
+ className: "bg-primary text-primary-foreground ml-auto rounded-md px-4 py-1.5 text-sm font-semibold",
607
+ children: "+ New"
608
+ }, undefined, false, undefined, this) : null
609
+ ]
610
+ }, undefined, true, undefined, this),
611
+ /* @__PURE__ */ jsxDEV4(DataTable, {
612
+ columns,
613
+ rows,
614
+ sortable: props.query?.sortable,
615
+ params,
616
+ selectable,
617
+ editable: canEdit || canDelete,
618
+ primaryKey: pk
619
+ }, undefined, false, undefined, this),
620
+ /* @__PURE__ */ jsxDEV4(Pagination, {
621
+ page,
622
+ totalPages,
623
+ total,
624
+ limit,
625
+ params
626
+ }, undefined, false, undefined, this),
627
+ /* @__PURE__ */ jsxDEV4("dialog", {
628
+ id: "rack-create",
629
+ className: "bg-card text-card-foreground rounded-lg border p-0",
630
+ children: /* @__PURE__ */ jsxDEV4("form", {
631
+ "data-create-form": true,
632
+ className: "flex min-w-80 flex-col gap-3 p-6",
633
+ children: [
634
+ /* @__PURE__ */ jsxDEV4("h2", {
635
+ className: "font-koran text-2xl font-bold",
636
+ children: "New record"
637
+ }, undefined, false, undefined, this),
638
+ createFields.length > 0 ? createFields.map((f) => /* @__PURE__ */ jsxDEV4(FieldInput, {
639
+ field: f,
640
+ required: isRequired(f)
641
+ }, f.name, false, undefined, this)) : /* @__PURE__ */ jsxDEV4("label", {
642
+ className: "flex flex-col gap-1 text-xs font-semibold tracking-wider text-form-label uppercase",
643
+ children: [
644
+ "JSON body",
645
+ /* @__PURE__ */ jsxDEV4("textarea", {
646
+ "data-create-json": true,
647
+ rows: 4,
648
+ placeholder: '{"name": "..."}',
649
+ className: "bg-input-background border-input text-foreground rounded-md border px-3 py-1.5 font-mono text-sm font-normal normal-case"
650
+ }, undefined, false, undefined, this)
651
+ ]
652
+ }, undefined, true, undefined, this),
653
+ /* @__PURE__ */ jsxDEV4("p", {
654
+ "data-form-error": true,
655
+ className: "text-form-error text-sm"
656
+ }, undefined, false, undefined, this),
657
+ /* @__PURE__ */ jsxDEV4("div", {
658
+ className: "flex justify-end gap-2",
659
+ children: [
660
+ /* @__PURE__ */ jsxDEV4("button", {
661
+ type: "button",
662
+ "data-close": true,
663
+ className: "border-border rounded-md border px-4 py-1.5 text-sm",
664
+ children: "Cancel"
665
+ }, undefined, false, undefined, this),
666
+ /* @__PURE__ */ jsxDEV4("button", {
667
+ type: "submit",
668
+ className: "bg-primary text-primary-foreground rounded-md px-4 py-1.5 text-sm font-semibold",
669
+ children: "Create"
670
+ }, undefined, false, undefined, this)
671
+ ]
672
+ }, undefined, true, undefined, this)
673
+ ]
674
+ }, undefined, true, undefined, this)
675
+ }, undefined, false, undefined, this),
676
+ /* @__PURE__ */ jsxDEV4("dialog", {
677
+ id: "rack-edit",
678
+ className: "bg-card text-card-foreground rounded-lg border p-0",
679
+ children: /* @__PURE__ */ jsxDEV4("form", {
680
+ "data-edit-form": true,
681
+ className: "flex min-w-80 flex-col gap-3 p-6",
682
+ children: [
683
+ /* @__PURE__ */ jsxDEV4("h2", {
684
+ className: "font-koran text-2xl font-bold",
685
+ children: [
686
+ "Edit record ",
687
+ /* @__PURE__ */ jsxDEV4("span", {
688
+ "data-edit-id-label": true,
689
+ className: "text-text-muted text-lg"
690
+ }, undefined, false, undefined, this)
691
+ ]
692
+ }, undefined, true, undefined, this),
693
+ editFields.map((f) => /* @__PURE__ */ jsxDEV4(FieldInput, {
694
+ field: f
695
+ }, f.name, false, undefined, this)),
696
+ /* @__PURE__ */ jsxDEV4("p", {
697
+ "data-form-error": true,
698
+ className: "text-form-error text-sm"
699
+ }, undefined, false, undefined, this),
700
+ /* @__PURE__ */ jsxDEV4("div", {
701
+ className: "flex justify-end gap-2",
702
+ children: [
703
+ /* @__PURE__ */ jsxDEV4("button", {
704
+ type: "button",
705
+ "data-close": true,
706
+ className: "border-border rounded-md border px-4 py-1.5 text-sm",
707
+ children: "Cancel"
708
+ }, undefined, false, undefined, this),
709
+ /* @__PURE__ */ jsxDEV4("button", {
710
+ type: "submit",
711
+ className: "bg-primary text-primary-foreground rounded-md px-4 py-1.5 text-sm font-semibold",
712
+ children: "Save"
713
+ }, undefined, false, undefined, this)
714
+ ]
715
+ }, undefined, true, undefined, this)
716
+ ]
717
+ }, undefined, true, undefined, this)
718
+ }, undefined, false, undefined, this),
719
+ /* @__PURE__ */ jsxDEV4("script", {
720
+ type: "module",
721
+ src: "/__rack/panel-app.js"
722
+ }, undefined, false, undefined, this)
723
+ ]
724
+ }, undefined, true, undefined, this),
725
+ /* @__PURE__ */ jsxDEV4("footer", {
726
+ className: "border-border text-text-muted border-t pt-3 text-center text-xs tracking-widest uppercase",
727
+ children: [
728
+ donate.enabled ? /* @__PURE__ */ jsxDEV4(Fragment2, {
729
+ children: [
730
+ /* @__PURE__ */ jsxDEV4("a", {
731
+ href: donate.url,
732
+ target: "_blank",
733
+ rel: "noopener noreferrer",
734
+ className: "hover:text-foreground underline decoration-dotted underline-offset-4",
735
+ children: donate.label
736
+ }, undefined, false, undefined, this),
737
+ " · "
738
+ ]
739
+ }, undefined, true, undefined, this) : null,
740
+ "Printed by elysia-rack"
741
+ ]
742
+ }, undefined, true, undefined, this)
743
+ ]
744
+ }, undefined, true, undefined, this)
745
+ ]
746
+ }, undefined, true, undefined, this)
747
+ ]
748
+ }, undefined, true, undefined, this)
749
+ }, undefined, false, undefined, this);
750
+ }
751
+ export {
752
+ Panel as default
753
+ };