elysia-rack 0.4.0 → 0.5.1

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,1159 @@
1
+ import {
2
+ Masthead,
3
+ Sidebar
4
+ } from "./index-9k12apt9.js";
5
+ import {
6
+ getRackTree
7
+ } from "./index-4wxvmd8a.js";
8
+ import {
9
+ RackIcon,
10
+ resolveDonateProp
11
+ } from "./index-vq4gh509.js";
12
+ import"./index-37x76zdn.js";
13
+
14
+ // src/react/page/components/Breadcrumb.tsx
15
+ import { jsxDEV } from "react/jsx-dev-runtime";
16
+ function Breadcrumb({ trail }) {
17
+ return /* @__PURE__ */ jsxDEV("nav", {
18
+ "aria-label": "Breadcrumb",
19
+ className: "pb-3 text-xs",
20
+ children: /* @__PURE__ */ jsxDEV("ol", {
21
+ className: "flex flex-wrap items-center gap-1.5",
22
+ children: trail.map((item, i) => {
23
+ const last = i === trail.length - 1;
24
+ return /* @__PURE__ */ jsxDEV("li", {
25
+ className: "flex items-center gap-1.5",
26
+ children: [
27
+ i > 0 ? /* @__PURE__ */ jsxDEV("span", {
28
+ "aria-hidden": "true",
29
+ className: "text-text-muted",
30
+ children: "/"
31
+ }, undefined, false, undefined, this) : null,
32
+ item.href && !last ? /* @__PURE__ */ jsxDEV("a", {
33
+ href: item.href,
34
+ className: "text-breadcrumb-foreground hover:text-foreground",
35
+ children: item.label
36
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("span", {
37
+ "aria-current": last ? "page" : undefined,
38
+ className: last ? "text-foreground font-medium" : "text-breadcrumb-foreground",
39
+ children: item.label
40
+ }, undefined, false, undefined, this)
41
+ ]
42
+ }, `${item.label}-${i}`, true, undefined, this);
43
+ })
44
+ }, undefined, false, undefined, this)
45
+ }, undefined, false, undefined, this);
46
+ }
47
+
48
+ // src/react/page/components/href.ts
49
+ function href(params, over = {}) {
50
+ const query = new URLSearchParams;
51
+ for (const [key, value] of Object.entries({ ...params, ...over })) {
52
+ if (value === undefined || value === null || value === "")
53
+ continue;
54
+ query.set(key, Array.isArray(value) ? value.join(",") : String(value));
55
+ }
56
+ const str = query.toString();
57
+ return str ? `?${str}` : "?";
58
+ }
59
+ function pageWindow(page, total) {
60
+ const keep = new Set([1, total, page - 1, page, page + 1]);
61
+ const sorted = [...keep].filter((p) => p >= 1 && p <= total).sort((a, b) => a - b);
62
+ const out = [];
63
+ let prev = 0;
64
+ for (const p of sorted) {
65
+ if (p - prev > 1)
66
+ out.push("…");
67
+ out.push(p);
68
+ prev = p;
69
+ }
70
+ return out;
71
+ }
72
+
73
+ // src/react/page/components/panelFields.ts
74
+ function defaultFormat(kind) {
75
+ switch (kind) {
76
+ case "integer":
77
+ case "number":
78
+ return "number";
79
+ case "boolean":
80
+ return "boolean";
81
+ case "date":
82
+ return "datetime";
83
+ case "json":
84
+ return "json";
85
+ default:
86
+ return "text";
87
+ }
88
+ }
89
+ function toDescriptor(name, primaryKey) {
90
+ return {
91
+ name,
92
+ kind: "text",
93
+ primary: name === primaryKey,
94
+ autoIncrement: false,
95
+ nullable: true
96
+ };
97
+ }
98
+ async function buildPanelFields(input) {
99
+ const config = input.config ?? {};
100
+ const hidden = new Set(input.hiddenFields ?? []);
101
+ const base = input.fields?.length ? input.fields : input.columns.map((name) => toDescriptor(name, input.primaryKey));
102
+ const out = [];
103
+ base.forEach((field, index) => {
104
+ if (field.name === input.deletedAtField)
105
+ return;
106
+ const cfg = config[field.name] ?? {};
107
+ const hide = hidden.has(field.name) || cfg.hideInTable === true;
108
+ const hideInForm = hidden.has(field.name) || cfg.hideInForm === true || cfg.readOnly === true;
109
+ out.push({
110
+ name: field.name,
111
+ label: cfg.label ?? field.name,
112
+ kind: field.kind,
113
+ format: cfg.format ?? defaultFormat(field.kind),
114
+ ...cfg.currency !== undefined ? { currency: cfg.currency } : {},
115
+ nullable: field.nullable,
116
+ primary: field.primary,
117
+ autoIncrement: field.autoIncrement,
118
+ readOnly: cfg.readOnly === true,
119
+ hideInTable: hide,
120
+ hideInForm,
121
+ ...field.enumValues !== undefined ? { enumValues: field.enumValues } : {},
122
+ order: cfg.order ?? index
123
+ });
124
+ });
125
+ for (const field of out) {
126
+ const relation = config[field.name]?.relation;
127
+ if (!relation)
128
+ continue;
129
+ const options = relation.options ?? (relation.load ? await relation.load() : undefined) ?? [];
130
+ field.relation = [...options];
131
+ }
132
+ return out.map((field, index) => ({ field, index })).sort((a, b) => a.field.order === b.field.order ? a.index - b.index : a.field.order - b.field.order).map(({ field }) => field);
133
+ }
134
+ function isNumericField(field) {
135
+ return field.format === "number" || field.format === "currency" || field.kind === "integer" || field.kind === "number";
136
+ }
137
+ function formatDate(value, withTime) {
138
+ const date = new Date(String(value));
139
+ if (Number.isNaN(date.getTime()))
140
+ return String(value);
141
+ return withTime ? date.toLocaleString("en-US") : date.toLocaleDateString("en-US");
142
+ }
143
+ function displayValue(field, value) {
144
+ if (value === null || value === undefined)
145
+ return "";
146
+ if (field?.relation) {
147
+ const match = field.relation.find((o) => String(o.value) === String(value));
148
+ if (match)
149
+ return match.label;
150
+ }
151
+ switch (field?.format) {
152
+ case "boolean":
153
+ return value === true || value === "true" || value === 1 ? "Yes" : "No";
154
+ case "date":
155
+ return formatDate(value, false);
156
+ case "datetime":
157
+ return formatDate(value, true);
158
+ case "currency": {
159
+ const amount = Number(value);
160
+ if (Number.isNaN(amount))
161
+ return String(value);
162
+ return new Intl.NumberFormat("en-US", {
163
+ style: "currency",
164
+ currency: field.currency ?? "USD"
165
+ }).format(amount);
166
+ }
167
+ case "number":
168
+ return Number.isNaN(Number(value)) ? String(value) : Number(value).toLocaleString("en-US");
169
+ case "json":
170
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
171
+ default:
172
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
173
+ }
174
+ }
175
+
176
+ // src/react/page/components/DataTable.tsx
177
+ import { jsxDEV as jsxDEV2, Fragment } from "react/jsx-dev-runtime";
178
+ function actionHref(action, id) {
179
+ return action.href.replaceAll(":id", encodeURIComponent(id));
180
+ }
181
+ function badgeClass(value) {
182
+ if (typeof value !== "string")
183
+ return "bg-badge-background text-badge-foreground";
184
+ const text = value.toLowerCase();
185
+ if (text === "active")
186
+ return "bg-success-muted text-success-muted-foreground";
187
+ if (text === "archived")
188
+ return "bg-warning-muted text-warning-muted-foreground";
189
+ return "bg-badge-background text-badge-foreground";
190
+ }
191
+ function RowAction({ action, id }) {
192
+ const className = "text-link hover:text-link-hover ml-3 px-1 text-sm font-medium hover:underline";
193
+ if (action.method) {
194
+ return /* @__PURE__ */ jsxDEV2("button", {
195
+ type: "button",
196
+ "data-rack-href": actionHref(action, id),
197
+ "data-rack-method": action.method,
198
+ "data-rack-confirm": action.confirm,
199
+ className,
200
+ children: action.label
201
+ }, undefined, false, undefined, this);
202
+ }
203
+ return /* @__PURE__ */ jsxDEV2("a", {
204
+ href: actionHref(action, id),
205
+ className,
206
+ children: action.label
207
+ }, undefined, false, undefined, this);
208
+ }
209
+ function DataTable({
210
+ fields,
211
+ rows,
212
+ sortable,
213
+ params,
214
+ selectable,
215
+ editable,
216
+ viewable,
217
+ actions,
218
+ primaryKey,
219
+ softDelete,
220
+ restorable,
221
+ deletedAtField
222
+ }) {
223
+ const activeSort = typeof params.sort === "string" ? params.sort : "";
224
+ const activeOrder = params.order === "desc" ? "desc" : "asc";
225
+ const pk = primaryKey ?? "id";
226
+ const deletedAt = deletedAtField ?? "deletedAt";
227
+ const rowActions = actions ?? [];
228
+ const columns = fields.filter((f) => !f.hideInTable);
229
+ const showActions = Boolean(editable) || Boolean(viewable) || Boolean(softDelete) || rowActions.length > 0;
230
+ const extra = (selectable ? 1 : 0) + (showActions ? 1 : 0);
231
+ const cellClass = (field) => `border-table-border border-t px-3 py-2.5 ${isNumericField(field) ? "text-right tabular-nums" : ""}`;
232
+ const tableAttrs = {
233
+ "data-selectable": selectable ? true : undefined,
234
+ "data-editable": editable ? true : undefined,
235
+ "data-viewable": viewable ? true : undefined,
236
+ "data-soft-delete": softDelete ? true : undefined,
237
+ "data-restorable": restorable ? true : undefined,
238
+ "data-deleted-at": deletedAt,
239
+ "data-rack-fields": JSON.stringify(fields),
240
+ "data-rack-actions": rowActions.length > 0 ? JSON.stringify(rowActions) : undefined,
241
+ "data-pk": pk
242
+ };
243
+ if (rows.length === 0)
244
+ return /* @__PURE__ */ jsxDEV2("div", {
245
+ className: "border-border bg-table overflow-x-auto rounded-lg border",
246
+ children: /* @__PURE__ */ jsxDEV2("table", {
247
+ className: "w-full border-collapse text-left text-sm",
248
+ ...tableAttrs,
249
+ children: /* @__PURE__ */ jsxDEV2("tbody", {
250
+ "data-panel-rows": true,
251
+ children: /* @__PURE__ */ jsxDEV2("tr", {
252
+ children: /* @__PURE__ */ jsxDEV2("td", {
253
+ colSpan: columns.length + extra || 1,
254
+ className: "text-text-muted px-4 py-10 text-center",
255
+ children: "No records found."
256
+ }, undefined, false, undefined, this)
257
+ }, undefined, false, undefined, this)
258
+ }, undefined, false, undefined, this)
259
+ }, undefined, false, undefined, this)
260
+ }, undefined, false, undefined, this);
261
+ return /* @__PURE__ */ jsxDEV2("div", {
262
+ className: "border-border bg-table overflow-x-auto rounded-lg border",
263
+ children: /* @__PURE__ */ jsxDEV2("table", {
264
+ className: "w-full border-collapse text-left text-sm",
265
+ ...tableAttrs,
266
+ children: [
267
+ /* @__PURE__ */ jsxDEV2("thead", {
268
+ children: /* @__PURE__ */ jsxDEV2("tr", {
269
+ children: [
270
+ selectable ? /* @__PURE__ */ jsxDEV2("th", {
271
+ className: "border-border w-10 border-b px-3 py-2",
272
+ children: /* @__PURE__ */ jsxDEV2("input", {
273
+ type: "checkbox",
274
+ "data-select-all": true,
275
+ "aria-label": "Select all",
276
+ className: "accent-primary size-4"
277
+ }, undefined, false, undefined, this)
278
+ }, undefined, false, undefined, this) : null,
279
+ columns.map((field) => /* @__PURE__ */ jsxDEV2("th", {
280
+ "data-col": field.name,
281
+ className: `border-border text-text-muted border-b px-3 py-2 text-xs font-medium ${isNumericField(field) ? "text-right" : ""}`,
282
+ children: sortable?.includes(field.name) ? /* @__PURE__ */ jsxDEV2("a", {
283
+ className: "hover:text-foreground text-link",
284
+ "data-qlink": true,
285
+ "data-sort-link": field.name,
286
+ href: href(params, {
287
+ sort: field.name,
288
+ order: activeSort === field.name && activeOrder === "asc" ? "desc" : "asc"
289
+ }),
290
+ children: [
291
+ field.label,
292
+ /* @__PURE__ */ jsxDEV2("span", {
293
+ "data-sort-ind": true,
294
+ children: activeSort === field.name ? activeOrder === "asc" ? " ▲" : " ▼" : ""
295
+ }, undefined, false, undefined, this)
296
+ ]
297
+ }, undefined, true, undefined, this) : field.label
298
+ }, field.name, false, undefined, this)),
299
+ showActions ? /* @__PURE__ */ jsxDEV2("th", {
300
+ className: "border-border text-text-muted border-b px-3 py-2 text-right text-xs font-medium",
301
+ children: "Actions"
302
+ }, undefined, false, undefined, this) : null
303
+ ]
304
+ }, undefined, true, undefined, this)
305
+ }, undefined, false, undefined, this),
306
+ /* @__PURE__ */ jsxDEV2("tbody", {
307
+ "data-panel-rows": true,
308
+ children: rows.map((row, i) => {
309
+ const id = String(row[pk] ?? "");
310
+ const deleted = Boolean(softDelete && row[deletedAt] != null);
311
+ return /* @__PURE__ */ jsxDEV2("tr", {
312
+ className: "hover:bg-table-row-hover",
313
+ children: [
314
+ selectable ? /* @__PURE__ */ jsxDEV2("td", {
315
+ className: "border-table-border border-t px-3 py-2.5",
316
+ children: /* @__PURE__ */ jsxDEV2("input", {
317
+ type: "checkbox",
318
+ "data-select-row": true,
319
+ value: id,
320
+ "aria-label": `Select row ${id || i}`,
321
+ className: "accent-primary size-4"
322
+ }, undefined, false, undefined, this)
323
+ }, undefined, false, undefined, this) : null,
324
+ columns.map((field) => {
325
+ const value = row[field.name];
326
+ const badge = field.format === "badge" || field.name === "status";
327
+ return /* @__PURE__ */ jsxDEV2("td", {
328
+ className: cellClass(field),
329
+ children: badge ? value === null || value === undefined ? "" : /* @__PURE__ */ jsxDEV2("span", {
330
+ className: `rounded-sm px-1.5 py-0.5 text-xs font-medium ${badgeClass(value)}`,
331
+ children: displayValue(field, value)
332
+ }, undefined, false, undefined, this) : displayValue(field, value)
333
+ }, field.name, false, undefined, this);
334
+ }),
335
+ showActions ? /* @__PURE__ */ jsxDEV2("td", {
336
+ className: "border-table-border whitespace-nowrap border-t px-3 py-2.5 text-right",
337
+ children: deleted ? restorable ? /* @__PURE__ */ jsxDEV2("button", {
338
+ type: "button",
339
+ "data-restore-id": id,
340
+ className: "text-link hover:text-link-hover px-1 text-sm font-medium hover:underline",
341
+ children: "Restore"
342
+ }, undefined, false, undefined, this) : null : /* @__PURE__ */ jsxDEV2(Fragment, {
343
+ children: [
344
+ viewable ? /* @__PURE__ */ jsxDEV2("button", {
345
+ type: "button",
346
+ "data-view-id": id,
347
+ className: "text-link hover:text-link-hover px-1 text-sm font-medium hover:underline",
348
+ children: "View"
349
+ }, undefined, false, undefined, this) : null,
350
+ editable ? /* @__PURE__ */ jsxDEV2(Fragment, {
351
+ children: [
352
+ /* @__PURE__ */ jsxDEV2("button", {
353
+ type: "button",
354
+ "data-edit-id": id,
355
+ className: "text-link hover:text-link-hover ml-3 px-1 text-sm font-medium hover:underline",
356
+ children: "Edit"
357
+ }, undefined, false, undefined, this),
358
+ /* @__PURE__ */ jsxDEV2("button", {
359
+ type: "button",
360
+ "data-delete-id": id,
361
+ className: "text-destructive ml-3 px-1 text-sm font-medium hover:underline",
362
+ children: "Delete"
363
+ }, undefined, false, undefined, this)
364
+ ]
365
+ }, undefined, true, undefined, this) : null,
366
+ rowActions.map((action, a) => /* @__PURE__ */ jsxDEV2(RowAction, {
367
+ action,
368
+ id
369
+ }, a, false, undefined, this))
370
+ ]
371
+ }, undefined, true, undefined, this)
372
+ }, undefined, false, undefined, this) : null
373
+ ]
374
+ }, i, true, undefined, this);
375
+ })
376
+ }, undefined, false, undefined, this)
377
+ ]
378
+ }, undefined, true, undefined, this)
379
+ }, undefined, false, undefined, this);
380
+ }
381
+
382
+ // src/react/page/components/Pagination.tsx
383
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
384
+ var pageClass = "border-border rounded-md border px-2.5 py-1 text-xs hover:bg-navigation-hover";
385
+ var currentClass = "bg-primary text-primary-foreground rounded-md px-2.5 py-1 text-xs font-medium";
386
+ var disabledClass = "border-border text-text-disabled rounded-md border px-2.5 py-1 text-xs";
387
+ function Pagination({ page, totalPages, total, limit, params }) {
388
+ const from = total === 0 ? 0 : (page - 1) * limit + 1;
389
+ const to = Math.min(page * limit, total);
390
+ return /* @__PURE__ */ jsxDEV3("nav", {
391
+ className: "flex flex-wrap items-center justify-between gap-3 py-3 text-sm",
392
+ children: [
393
+ /* @__PURE__ */ jsxDEV3("p", {
394
+ className: "text-text-muted text-xs",
395
+ "data-panel-count": true,
396
+ children: total === 0 ? "0 records" : `${from}–${to} of ${total} records`
397
+ }, undefined, false, undefined, this),
398
+ /* @__PURE__ */ jsxDEV3("div", {
399
+ className: "flex items-center gap-1",
400
+ "data-panel-pages": true,
401
+ children: [
402
+ page > 1 ? /* @__PURE__ */ jsxDEV3("a", {
403
+ className: pageClass,
404
+ "data-qlink": true,
405
+ href: href(params, { page: page - 1 }),
406
+ children: "Previous"
407
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3("span", {
408
+ className: disabledClass,
409
+ children: "Previous"
410
+ }, undefined, false, undefined, this),
411
+ pageWindow(page, totalPages).map((p, i) => p === "…" ? /* @__PURE__ */ jsxDEV3("span", {
412
+ className: "text-text-muted px-1",
413
+ children: "…"
414
+ }, `e${i}`, false, undefined, this) : p === page ? /* @__PURE__ */ jsxDEV3("span", {
415
+ className: currentClass,
416
+ children: p
417
+ }, p, false, undefined, this) : /* @__PURE__ */ jsxDEV3("a", {
418
+ className: pageClass,
419
+ "data-qlink": true,
420
+ href: href(params, { page: p }),
421
+ children: p
422
+ }, p, false, undefined, this)),
423
+ page < totalPages ? /* @__PURE__ */ jsxDEV3("a", {
424
+ className: pageClass,
425
+ "data-qlink": true,
426
+ href: href(params, { page: page + 1 }),
427
+ children: "Next"
428
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3("span", {
429
+ className: disabledClass,
430
+ children: "Next"
431
+ }, undefined, false, undefined, this)
432
+ ]
433
+ }, undefined, true, undefined, this)
434
+ ]
435
+ }, undefined, true, undefined, this);
436
+ }
437
+
438
+ // src/react/page/components/Toolbar.tsx
439
+ import { jsxDEV as jsxDEV4, Fragment as Fragment2 } from "react/jsx-dev-runtime";
440
+ var inputClass = "border-input bg-input-background text-foreground placeholder:text-form-placeholder focus:border-ring h-8 rounded-md border px-2.5 text-sm";
441
+ var labelClass = "flex flex-col gap-1";
442
+ function FilterInput({ field, value }) {
443
+ const current = value === undefined || value === null ? "" : String(value);
444
+ if (field?.relation) {
445
+ return /* @__PURE__ */ jsxDEV4("select", {
446
+ name: field.name,
447
+ defaultValue: current,
448
+ className: `${inputClass} w-40`,
449
+ children: [
450
+ /* @__PURE__ */ jsxDEV4("option", {
451
+ value: "",
452
+ children: "All"
453
+ }, undefined, false, undefined, this),
454
+ field.relation.map((option) => /* @__PURE__ */ jsxDEV4("option", {
455
+ value: String(option.value),
456
+ children: option.label
457
+ }, String(option.value), false, undefined, this))
458
+ ]
459
+ }, undefined, true, undefined, this);
460
+ }
461
+ if (field?.kind === "enum") {
462
+ return /* @__PURE__ */ jsxDEV4("select", {
463
+ name: field.name,
464
+ defaultValue: current,
465
+ className: `${inputClass} w-40`,
466
+ children: [
467
+ /* @__PURE__ */ jsxDEV4("option", {
468
+ value: "",
469
+ children: "All"
470
+ }, undefined, false, undefined, this),
471
+ (field.enumValues ?? []).map((v) => /* @__PURE__ */ jsxDEV4("option", {
472
+ value: v,
473
+ children: v
474
+ }, v, false, undefined, this))
475
+ ]
476
+ }, undefined, true, undefined, this);
477
+ }
478
+ if (field?.kind === "boolean") {
479
+ return /* @__PURE__ */ jsxDEV4("select", {
480
+ name: field.name,
481
+ defaultValue: current,
482
+ className: `${inputClass} w-40`,
483
+ children: [
484
+ /* @__PURE__ */ jsxDEV4("option", {
485
+ value: "",
486
+ children: "All"
487
+ }, undefined, false, undefined, this),
488
+ /* @__PURE__ */ jsxDEV4("option", {
489
+ value: "true",
490
+ children: "Yes"
491
+ }, undefined, false, undefined, this),
492
+ /* @__PURE__ */ jsxDEV4("option", {
493
+ value: "false",
494
+ children: "No"
495
+ }, undefined, false, undefined, this)
496
+ ]
497
+ }, undefined, true, undefined, this);
498
+ }
499
+ if (field?.kind === "date") {
500
+ return /* @__PURE__ */ jsxDEV4("input", {
501
+ type: "date",
502
+ name: field.name,
503
+ defaultValue: current,
504
+ className: `${inputClass} w-40`
505
+ }, undefined, false, undefined, this);
506
+ }
507
+ if (field?.kind === "integer" || field?.kind === "number") {
508
+ return /* @__PURE__ */ jsxDEV4("input", {
509
+ type: "number",
510
+ name: field.name,
511
+ defaultValue: current,
512
+ className: `${inputClass} w-40`
513
+ }, undefined, false, undefined, this);
514
+ }
515
+ return /* @__PURE__ */ jsxDEV4("input", {
516
+ name: field?.name ?? "",
517
+ defaultValue: current,
518
+ className: `${inputClass} w-40`
519
+ }, undefined, false, undefined, this);
520
+ }
521
+ function Toolbar({
522
+ fields,
523
+ searchable,
524
+ filterable,
525
+ sortable,
526
+ params,
527
+ softDelete,
528
+ includeDeleted
529
+ }) {
530
+ const hasFilters = Boolean(searchable?.length) || Boolean(filterable?.length) || Boolean(sortable?.length) || Boolean(softDelete);
531
+ if (!hasFilters)
532
+ return null;
533
+ const byName = new Map((fields ?? []).map((f) => [f.name, f]));
534
+ const label = (name) => byName.get(name)?.label ?? name;
535
+ const searchLabel = (searchable ?? []).map(label).join(", ");
536
+ return /* @__PURE__ */ jsxDEV4("form", {
537
+ method: "get",
538
+ action: "",
539
+ className: "flex flex-wrap items-end gap-3 pb-4",
540
+ children: [
541
+ includeDeleted ? /* @__PURE__ */ jsxDEV4("input", {
542
+ type: "hidden",
543
+ name: "includeDeleted",
544
+ value: "true"
545
+ }, undefined, false, undefined, this) : null,
546
+ searchable && searchable.length > 0 ? /* @__PURE__ */ jsxDEV4("label", {
547
+ className: labelClass,
548
+ children: [
549
+ /* @__PURE__ */ jsxDEV4("span", {
550
+ className: "text-text-muted text-xs font-medium",
551
+ children: "Search"
552
+ }, undefined, false, undefined, this),
553
+ /* @__PURE__ */ jsxDEV4("input", {
554
+ name: "search",
555
+ "data-search-input": true,
556
+ defaultValue: String(params.search ?? ""),
557
+ placeholder: searchLabel,
558
+ autoComplete: "off",
559
+ className: `${inputClass} w-56 max-w-full`
560
+ }, undefined, false, undefined, this)
561
+ ]
562
+ }, undefined, true, undefined, this) : null,
563
+ (filterable ?? []).map((name) => /* @__PURE__ */ jsxDEV4("label", {
564
+ className: labelClass,
565
+ children: [
566
+ /* @__PURE__ */ jsxDEV4("span", {
567
+ className: "text-text-muted text-xs font-medium",
568
+ children: label(name)
569
+ }, undefined, false, undefined, this),
570
+ /* @__PURE__ */ jsxDEV4(FilterInput, {
571
+ field: byName.get(name),
572
+ value: params[name]
573
+ }, undefined, false, undefined, this)
574
+ ]
575
+ }, name, true, undefined, this)),
576
+ sortable && sortable.length > 0 ? /* @__PURE__ */ jsxDEV4(Fragment2, {
577
+ children: [
578
+ /* @__PURE__ */ jsxDEV4("label", {
579
+ className: labelClass,
580
+ children: [
581
+ /* @__PURE__ */ jsxDEV4("span", {
582
+ className: "text-text-muted text-xs font-medium",
583
+ children: "Sort"
584
+ }, undefined, false, undefined, this),
585
+ /* @__PURE__ */ jsxDEV4("select", {
586
+ name: "sort",
587
+ defaultValue: String(params.sort ?? ""),
588
+ className: inputClass,
589
+ children: [
590
+ /* @__PURE__ */ jsxDEV4("option", {
591
+ value: "",
592
+ children: "Default"
593
+ }, undefined, false, undefined, this),
594
+ sortable.map((name) => /* @__PURE__ */ jsxDEV4("option", {
595
+ value: name,
596
+ children: label(name)
597
+ }, name, false, undefined, this))
598
+ ]
599
+ }, undefined, true, undefined, this)
600
+ ]
601
+ }, undefined, true, undefined, this),
602
+ /* @__PURE__ */ jsxDEV4("label", {
603
+ className: labelClass,
604
+ children: [
605
+ /* @__PURE__ */ jsxDEV4("span", {
606
+ className: "text-text-muted text-xs font-medium",
607
+ children: "Order"
608
+ }, undefined, false, undefined, this),
609
+ /* @__PURE__ */ jsxDEV4("select", {
610
+ name: "order",
611
+ defaultValue: String(params.order ?? ""),
612
+ className: inputClass,
613
+ children: [
614
+ /* @__PURE__ */ jsxDEV4("option", {
615
+ value: "",
616
+ children: "Default"
617
+ }, undefined, false, undefined, this),
618
+ /* @__PURE__ */ jsxDEV4("option", {
619
+ value: "asc",
620
+ children: "Ascending"
621
+ }, undefined, false, undefined, this),
622
+ /* @__PURE__ */ jsxDEV4("option", {
623
+ value: "desc",
624
+ children: "Descending"
625
+ }, undefined, false, undefined, this)
626
+ ]
627
+ }, undefined, true, undefined, this)
628
+ ]
629
+ }, undefined, true, undefined, this)
630
+ ]
631
+ }, undefined, true, undefined, this) : null,
632
+ /* @__PURE__ */ jsxDEV4("button", {
633
+ type: "submit",
634
+ className: "border-border bg-background text-foreground hover:bg-navigation-hover h-8 rounded-md border px-3.5 text-sm font-medium",
635
+ children: "Apply"
636
+ }, undefined, false, undefined, this),
637
+ softDelete ? /* @__PURE__ */ jsxDEV4("a", {
638
+ "data-qlink": true,
639
+ href: href(params, {
640
+ includeDeleted: includeDeleted ? undefined : "true",
641
+ page: 1
642
+ }),
643
+ className: "text-text-muted hover:text-foreground h-8 self-end text-sm underline underline-offset-4",
644
+ children: includeDeleted ? "Hide deleted" : "Show deleted"
645
+ }, undefined, false, undefined, this) : null
646
+ ]
647
+ }, undefined, true, undefined, this);
648
+ }
649
+
650
+ // src/react/page/Panel.tsx
651
+ import { jsxDEV as jsxDEV5, Fragment as Fragment3 } from "react/jsx-dev-runtime";
652
+ var inputClass2 = "border-input bg-input-background text-foreground placeholder:text-form-placeholder h-9 rounded-md border px-2.5 text-sm";
653
+ var fieldLabelClass = "flex flex-col gap-1.5 text-sm font-medium text-form-label";
654
+ var secondaryButtonClass = "border-border bg-background text-foreground hover:bg-navigation-hover rounded-md border px-3.5 py-1.5 text-sm font-medium";
655
+ var primaryButtonClass = "bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-3.5 py-1.5 text-sm font-medium";
656
+ function FieldInput({ field, required }) {
657
+ if (field.relation) {
658
+ return /* @__PURE__ */ jsxDEV5("label", {
659
+ className: fieldLabelClass,
660
+ children: [
661
+ field.label,
662
+ /* @__PURE__ */ jsxDEV5("select", {
663
+ name: field.name,
664
+ required,
665
+ defaultValue: "",
666
+ className: inputClass2,
667
+ children: [
668
+ /* @__PURE__ */ jsxDEV5("option", {
669
+ value: "",
670
+ children: "Select"
671
+ }, undefined, false, undefined, this),
672
+ field.relation.map((option) => /* @__PURE__ */ jsxDEV5("option", {
673
+ value: String(option.value),
674
+ children: option.label
675
+ }, String(option.value), false, undefined, this))
676
+ ]
677
+ }, undefined, true, undefined, this)
678
+ ]
679
+ }, undefined, true, undefined, this);
680
+ }
681
+ switch (field.kind) {
682
+ case "enum":
683
+ return /* @__PURE__ */ jsxDEV5("label", {
684
+ className: fieldLabelClass,
685
+ children: [
686
+ field.label,
687
+ /* @__PURE__ */ jsxDEV5("select", {
688
+ name: field.name,
689
+ required,
690
+ defaultValue: "",
691
+ className: inputClass2,
692
+ children: [
693
+ /* @__PURE__ */ jsxDEV5("option", {
694
+ value: "",
695
+ children: "Select"
696
+ }, undefined, false, undefined, this),
697
+ (field.enumValues ?? []).map((v) => /* @__PURE__ */ jsxDEV5("option", {
698
+ value: v,
699
+ children: v
700
+ }, v, false, undefined, this))
701
+ ]
702
+ }, undefined, true, undefined, this)
703
+ ]
704
+ }, undefined, true, undefined, this);
705
+ case "boolean":
706
+ return /* @__PURE__ */ jsxDEV5("label", {
707
+ className: `${fieldLabelClass} flex-row items-center gap-2`,
708
+ children: [
709
+ /* @__PURE__ */ jsxDEV5("input", {
710
+ type: "checkbox",
711
+ name: field.name,
712
+ value: "true",
713
+ className: "accent-primary size-4"
714
+ }, undefined, false, undefined, this),
715
+ field.label
716
+ ]
717
+ }, undefined, true, undefined, this);
718
+ case "integer":
719
+ case "number":
720
+ return /* @__PURE__ */ jsxDEV5("label", {
721
+ className: fieldLabelClass,
722
+ children: [
723
+ field.label,
724
+ /* @__PURE__ */ jsxDEV5("input", {
725
+ type: "number",
726
+ step: field.kind === "number" ? "any" : "1",
727
+ name: field.name,
728
+ required,
729
+ className: inputClass2
730
+ }, undefined, false, undefined, this)
731
+ ]
732
+ }, undefined, true, undefined, this);
733
+ case "date":
734
+ return /* @__PURE__ */ jsxDEV5("label", {
735
+ className: fieldLabelClass,
736
+ children: [
737
+ field.label,
738
+ /* @__PURE__ */ jsxDEV5("input", {
739
+ type: "datetime-local",
740
+ name: field.name,
741
+ required,
742
+ className: inputClass2
743
+ }, undefined, false, undefined, this)
744
+ ]
745
+ }, undefined, true, undefined, this);
746
+ case "json":
747
+ return /* @__PURE__ */ jsxDEV5("label", {
748
+ className: fieldLabelClass,
749
+ children: [
750
+ field.label,
751
+ /* @__PURE__ */ jsxDEV5("textarea", {
752
+ name: field.name,
753
+ "data-json": true,
754
+ rows: 3,
755
+ placeholder: "{}",
756
+ className: `${inputClass2} font-mono`
757
+ }, undefined, false, undefined, this)
758
+ ]
759
+ }, undefined, true, undefined, this);
760
+ default:
761
+ return /* @__PURE__ */ jsxDEV5("label", {
762
+ className: fieldLabelClass,
763
+ children: [
764
+ field.label,
765
+ /* @__PURE__ */ jsxDEV5("input", {
766
+ name: field.name,
767
+ required,
768
+ className: inputClass2
769
+ }, undefined, false, undefined, this)
770
+ ]
771
+ }, undefined, true, undefined, this);
772
+ }
773
+ }
774
+ function itemLabel(r) {
775
+ return r.metadata.pluralLabel ?? r.metadata.label ?? r.metadata.id;
776
+ }
777
+ function toSidebarItem(node, activeId) {
778
+ return {
779
+ id: node.metadata.id,
780
+ label: itemLabel(node),
781
+ icon: node.metadata.icon,
782
+ href: node.path,
783
+ active: node.metadata.id === activeId,
784
+ children: node.children.filter((c) => !c.metadata.hidden).map((c) => toSidebarItem(c, activeId))
785
+ };
786
+ }
787
+ function buildGroupsFromTree(tree, activeId) {
788
+ const byGroup = new Map;
789
+ for (const root of tree) {
790
+ if (root.metadata.hidden)
791
+ continue;
792
+ const name = root.metadata.group ?? "General";
793
+ let group = byGroup.get(name);
794
+ if (!group) {
795
+ group = { name, items: [] };
796
+ byGroup.set(name, group);
797
+ }
798
+ group.items.push(toSidebarItem(root, activeId));
799
+ }
800
+ return [...byGroup.values()].sort((a, b) => a.name.localeCompare(b.name));
801
+ }
802
+ function plural(count, word) {
803
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
804
+ }
805
+ function CustomActionButton({ action }) {
806
+ const content = /* @__PURE__ */ jsxDEV5(Fragment3, {
807
+ children: [
808
+ action.icon ? /* @__PURE__ */ jsxDEV5(RackIcon, {
809
+ name: action.icon,
810
+ size: 14,
811
+ className: "size-3.5"
812
+ }, undefined, false, undefined, this) : null,
813
+ action.label
814
+ ]
815
+ }, undefined, true, undefined, this);
816
+ if (action.method) {
817
+ return /* @__PURE__ */ jsxDEV5("button", {
818
+ type: "button",
819
+ "data-rack-href": action.href,
820
+ "data-rack-method": action.method,
821
+ "data-rack-confirm": action.confirm,
822
+ className: `${secondaryButtonClass} inline-flex items-center gap-1.5`,
823
+ children: content
824
+ }, undefined, false, undefined, this);
825
+ }
826
+ return /* @__PURE__ */ jsxDEV5("a", {
827
+ href: action.href,
828
+ className: `${secondaryButtonClass} inline-flex items-center gap-1.5`,
829
+ children: content
830
+ }, undefined, false, undefined, this);
831
+ }
832
+ async function Panel(props) {
833
+ const params = props.params ?? {};
834
+ const page = Math.max(Number(params.page ?? 1) || 1, 1);
835
+ const limit = Math.max(Number(params.limit ?? props.query?.pagination?.default ?? 20) || 1, 1);
836
+ let rows = [];
837
+ let total = 0;
838
+ if (props.load) {
839
+ try {
840
+ const result = await props.load(params);
841
+ rows = result.data;
842
+ total = result.total;
843
+ } catch {}
844
+ }
845
+ const title = props.metadata?.pluralLabel ?? props.metadata?.label ?? props.resource ?? "Panel";
846
+ const totalPages = Math.max(1, Math.ceil(total / limit));
847
+ const pk = props.primaryKey ?? "id";
848
+ const deletedAt = props.deletedAtField ?? "deletedAt";
849
+ const rawColumns = rows.length > 0 ? Object.keys(rows[0] ?? {}) : [];
850
+ const fields = await buildPanelFields({
851
+ fields: props.fields,
852
+ columns: rawColumns,
853
+ config: props.metadata?.fields,
854
+ hiddenFields: props.metadata?.hiddenFields,
855
+ primaryKey: pk,
856
+ deletedAtField: deletedAt
857
+ });
858
+ const tableFields = fields.filter((f) => !f.hideInTable);
859
+ const formFields = fields.filter((f) => !f.hideInForm && !f.autoIncrement);
860
+ const createFields = formFields;
861
+ const editFields = formFields.filter((f) => f.name !== pk);
862
+ const isRequired = (f) => !f.nullable && f.kind !== "boolean";
863
+ const canCreate = props.operations?.create !== false;
864
+ const canEdit = props.operations?.update !== false || props.operations?.replace !== false;
865
+ const canDelete = props.operations?.delete !== false;
866
+ const canView = props.operations?.detail !== false;
867
+ const softDelete = props.softDelete === true;
868
+ const selectable = canEdit || canDelete;
869
+ const allActions = props.actions ?? [];
870
+ const rowActions = allActions.filter((a) => (a.scope ?? "row") === "row");
871
+ const toolbarActions = allActions.filter((a) => a.scope === "toolbar");
872
+ const includeDeleted = params.includeDeleted === "true" || params.includeDeleted === true;
873
+ const tree = getRackTree().filter((r) => !r.metadata.hidden);
874
+ const groups = buildGroupsFromTree(tree, props.resource);
875
+ const donate = resolveDonateProp(props.donate);
876
+ const trail = [
877
+ { label: "Dashboard", href: "/" },
878
+ ...props.metadata?.group ? [{ label: props.metadata.group }] : [],
879
+ { label: title }
880
+ ];
881
+ return /* @__PURE__ */ jsxDEV5("div", {
882
+ className: "text-foreground flex min-h-screen flex-col lg:flex-row",
883
+ children: [
884
+ /* @__PURE__ */ jsxDEV5(Sidebar, {
885
+ title: "Panel",
886
+ groups
887
+ }, undefined, false, undefined, this),
888
+ /* @__PURE__ */ jsxDEV5("main", {
889
+ className: "min-w-0 flex-1",
890
+ children: /* @__PURE__ */ jsxDEV5("div", {
891
+ className: "mx-auto w-full max-w-5xl px-4 py-4 sm:px-6",
892
+ children: [
893
+ /* @__PURE__ */ jsxDEV5(Breadcrumb, {
894
+ trail
895
+ }, undefined, false, undefined, this),
896
+ /* @__PURE__ */ jsxDEV5("div", {
897
+ "data-query-url": props.queryUrl,
898
+ children: [
899
+ /* @__PURE__ */ jsxDEV5(Masthead, {
900
+ title,
901
+ icon: props.metadata?.icon,
902
+ detail: /* @__PURE__ */ jsxDEV5("span", {
903
+ "data-panel-total": true,
904
+ children: plural(total, "record")
905
+ }, undefined, false, undefined, this),
906
+ actions: toolbarActions.length > 0 || canCreate ? /* @__PURE__ */ jsxDEV5(Fragment3, {
907
+ children: [
908
+ toolbarActions.map((action, i) => /* @__PURE__ */ jsxDEV5(CustomActionButton, {
909
+ action
910
+ }, i, false, undefined, this)),
911
+ canCreate ? /* @__PURE__ */ jsxDEV5("button", {
912
+ type: "button",
913
+ "data-open-create": true,
914
+ className: primaryButtonClass,
915
+ children: "New record"
916
+ }, undefined, false, undefined, this) : null
917
+ ]
918
+ }, undefined, true, undefined, this) : undefined
919
+ }, undefined, false, undefined, this),
920
+ /* @__PURE__ */ jsxDEV5(Toolbar, {
921
+ fields,
922
+ searchable: props.query?.searchable,
923
+ filterable: props.query?.filterable,
924
+ sortable: props.query?.sortable,
925
+ params,
926
+ softDelete,
927
+ includeDeleted
928
+ }, undefined, false, undefined, this),
929
+ /* @__PURE__ */ jsxDEV5("div", {
930
+ "data-bulk-bar": true,
931
+ style: { display: "none" },
932
+ className: "flex flex-wrap items-center gap-2 pb-3",
933
+ children: [
934
+ /* @__PURE__ */ jsxDEV5("span", {
935
+ "data-bulk-count": true,
936
+ className: "text-text-muted text-xs"
937
+ }, undefined, false, undefined, this),
938
+ canDelete ? /* @__PURE__ */ jsxDEV5("button", {
939
+ type: "button",
940
+ "data-bulk-delete": true,
941
+ className: "bg-destructive text-destructive-foreground rounded-md px-3 py-1.5 text-xs font-medium",
942
+ children: "Delete selected"
943
+ }, undefined, false, undefined, this) : null,
944
+ canEdit ? /* @__PURE__ */ jsxDEV5("span", {
945
+ className: "inline-flex items-center gap-2",
946
+ children: [
947
+ /* @__PURE__ */ jsxDEV5("select", {
948
+ "data-bulk-field": true,
949
+ "aria-label": "Bulk field",
950
+ className: "border-input bg-input-background h-8 rounded-md border px-2 text-xs",
951
+ children: editFields.map((f) => /* @__PURE__ */ jsxDEV5("option", {
952
+ value: f.name,
953
+ children: f.label
954
+ }, f.name, false, undefined, this))
955
+ }, undefined, false, undefined, this),
956
+ /* @__PURE__ */ jsxDEV5("input", {
957
+ "data-bulk-value": true,
958
+ placeholder: "New value",
959
+ "aria-label": "Bulk value",
960
+ className: "border-input bg-input-background placeholder:text-form-placeholder h-8 rounded-md border px-2 text-xs"
961
+ }, undefined, false, undefined, this),
962
+ /* @__PURE__ */ jsxDEV5("button", {
963
+ type: "button",
964
+ "data-bulk-apply": true,
965
+ className: "bg-secondary text-secondary-foreground hover:bg-navigation-hover rounded-md px-3 py-1.5 text-xs font-medium",
966
+ children: "Apply"
967
+ }, undefined, false, undefined, this)
968
+ ]
969
+ }, undefined, true, undefined, this) : null,
970
+ /* @__PURE__ */ jsxDEV5("button", {
971
+ type: "button",
972
+ "data-bulk-clear": true,
973
+ className: "text-text-muted hover:text-foreground text-xs underline",
974
+ children: "Clear"
975
+ }, undefined, false, undefined, this)
976
+ ]
977
+ }, undefined, true, undefined, this),
978
+ /* @__PURE__ */ jsxDEV5(DataTable, {
979
+ fields,
980
+ rows,
981
+ sortable: props.query?.sortable,
982
+ params,
983
+ selectable,
984
+ editable: canEdit || canDelete,
985
+ viewable: canView,
986
+ actions: rowActions,
987
+ primaryKey: pk,
988
+ softDelete,
989
+ restorable: canEdit,
990
+ deletedAtField: deletedAt
991
+ }, undefined, false, undefined, this),
992
+ /* @__PURE__ */ jsxDEV5(Pagination, {
993
+ page,
994
+ totalPages,
995
+ total,
996
+ limit,
997
+ params
998
+ }, undefined, false, undefined, this),
999
+ /* @__PURE__ */ jsxDEV5("dialog", {
1000
+ id: "rack-create",
1001
+ className: "bg-card text-card-foreground w-full max-w-md rounded-lg border p-0 shadow-lg",
1002
+ children: /* @__PURE__ */ jsxDEV5("form", {
1003
+ "data-create-form": true,
1004
+ className: "flex flex-col gap-4 p-5",
1005
+ children: [
1006
+ /* @__PURE__ */ jsxDEV5("h2", {
1007
+ className: "font-display text-xl leading-tight",
1008
+ children: "New record"
1009
+ }, undefined, false, undefined, this),
1010
+ createFields.length > 0 ? createFields.map((f) => /* @__PURE__ */ jsxDEV5(FieldInput, {
1011
+ field: f,
1012
+ required: isRequired(f)
1013
+ }, f.name, false, undefined, this)) : /* @__PURE__ */ jsxDEV5("label", {
1014
+ className: "flex flex-col gap-1.5 text-sm font-medium text-form-label",
1015
+ children: [
1016
+ "JSON body",
1017
+ /* @__PURE__ */ jsxDEV5("textarea", {
1018
+ "data-create-json": true,
1019
+ rows: 4,
1020
+ placeholder: '{"name": "..."}',
1021
+ className: `${inputClass2} h-auto font-mono`
1022
+ }, undefined, false, undefined, this)
1023
+ ]
1024
+ }, undefined, true, undefined, this),
1025
+ /* @__PURE__ */ jsxDEV5("p", {
1026
+ "data-form-error": true,
1027
+ className: "text-form-error text-sm"
1028
+ }, undefined, false, undefined, this),
1029
+ /* @__PURE__ */ jsxDEV5("div", {
1030
+ className: "border-border flex justify-end gap-2 border-t pt-4",
1031
+ children: [
1032
+ /* @__PURE__ */ jsxDEV5("button", {
1033
+ type: "button",
1034
+ "data-close": true,
1035
+ className: secondaryButtonClass,
1036
+ children: "Cancel"
1037
+ }, undefined, false, undefined, this),
1038
+ /* @__PURE__ */ jsxDEV5("button", {
1039
+ type: "submit",
1040
+ className: primaryButtonClass,
1041
+ children: "Create"
1042
+ }, undefined, false, undefined, this)
1043
+ ]
1044
+ }, undefined, true, undefined, this)
1045
+ ]
1046
+ }, undefined, true, undefined, this)
1047
+ }, undefined, false, undefined, this),
1048
+ /* @__PURE__ */ jsxDEV5("dialog", {
1049
+ id: "rack-edit",
1050
+ className: "bg-card text-card-foreground w-full max-w-md rounded-lg border p-0 shadow-lg",
1051
+ children: /* @__PURE__ */ jsxDEV5("form", {
1052
+ "data-edit-form": true,
1053
+ className: "flex flex-col gap-4 p-5",
1054
+ children: [
1055
+ /* @__PURE__ */ jsxDEV5("h2", {
1056
+ className: "font-display text-xl leading-tight",
1057
+ children: [
1058
+ "Edit record",
1059
+ " ",
1060
+ /* @__PURE__ */ jsxDEV5("span", {
1061
+ "data-edit-id-label": true,
1062
+ className: "text-text-muted font-mono text-sm"
1063
+ }, undefined, false, undefined, this)
1064
+ ]
1065
+ }, undefined, true, undefined, this),
1066
+ editFields.map((f) => /* @__PURE__ */ jsxDEV5(FieldInput, {
1067
+ field: f
1068
+ }, f.name, false, undefined, this)),
1069
+ /* @__PURE__ */ jsxDEV5("p", {
1070
+ "data-form-error": true,
1071
+ className: "text-form-error text-sm"
1072
+ }, undefined, false, undefined, this),
1073
+ /* @__PURE__ */ jsxDEV5("div", {
1074
+ className: "border-border flex justify-end gap-2 border-t pt-4",
1075
+ children: [
1076
+ /* @__PURE__ */ jsxDEV5("button", {
1077
+ type: "button",
1078
+ "data-close": true,
1079
+ className: secondaryButtonClass,
1080
+ children: "Cancel"
1081
+ }, undefined, false, undefined, this),
1082
+ /* @__PURE__ */ jsxDEV5("button", {
1083
+ type: "submit",
1084
+ className: primaryButtonClass,
1085
+ children: "Save"
1086
+ }, undefined, false, undefined, this)
1087
+ ]
1088
+ }, undefined, true, undefined, this)
1089
+ ]
1090
+ }, undefined, true, undefined, this)
1091
+ }, undefined, false, undefined, this),
1092
+ /* @__PURE__ */ jsxDEV5("dialog", {
1093
+ id: "rack-detail",
1094
+ className: "bg-card text-card-foreground w-full max-w-md rounded-lg border p-0 shadow-lg",
1095
+ children: /* @__PURE__ */ jsxDEV5("div", {
1096
+ className: "flex flex-col gap-4 p-5",
1097
+ children: [
1098
+ /* @__PURE__ */ jsxDEV5("h2", {
1099
+ className: "font-display text-xl leading-tight",
1100
+ children: [
1101
+ "Record",
1102
+ " ",
1103
+ /* @__PURE__ */ jsxDEV5("span", {
1104
+ "data-detail-id-label": true,
1105
+ className: "text-text-muted font-mono text-sm"
1106
+ }, undefined, false, undefined, this)
1107
+ ]
1108
+ }, undefined, true, undefined, this),
1109
+ /* @__PURE__ */ jsxDEV5("dl", {
1110
+ "data-detail-body": true,
1111
+ className: "flex flex-col gap-3 text-sm"
1112
+ }, undefined, false, undefined, this),
1113
+ /* @__PURE__ */ jsxDEV5("div", {
1114
+ className: "border-border flex justify-end border-t pt-4",
1115
+ children: /* @__PURE__ */ jsxDEV5("button", {
1116
+ type: "button",
1117
+ "data-close": true,
1118
+ className: secondaryButtonClass,
1119
+ children: "Close"
1120
+ }, undefined, false, undefined, this)
1121
+ }, undefined, false, undefined, this)
1122
+ ]
1123
+ }, undefined, true, undefined, this)
1124
+ }, undefined, false, undefined, this),
1125
+ /* @__PURE__ */ jsxDEV5("div", {
1126
+ "data-rack-toasts": true,
1127
+ "aria-live": "polite",
1128
+ className: "pointer-events-none fixed right-4 bottom-4 z-50 flex flex-col gap-2"
1129
+ }, undefined, false, undefined, this),
1130
+ /* @__PURE__ */ jsxDEV5("script", {
1131
+ type: "module",
1132
+ src: "/__rack/panel-app.js"
1133
+ }, undefined, false, undefined, this)
1134
+ ]
1135
+ }, undefined, true, undefined, this),
1136
+ /* @__PURE__ */ jsxDEV5("footer", {
1137
+ className: "border-border text-text-muted mt-6 flex flex-wrap items-center justify-between gap-2 border-t pt-3 text-xs",
1138
+ children: [
1139
+ /* @__PURE__ */ jsxDEV5("span", {
1140
+ children: "elysia-rack"
1141
+ }, undefined, false, undefined, this),
1142
+ donate.enabled ? /* @__PURE__ */ jsxDEV5("a", {
1143
+ href: donate.url,
1144
+ target: "_blank",
1145
+ rel: "noopener noreferrer",
1146
+ className: "hover:text-foreground underline-offset-4 hover:underline",
1147
+ children: donate.label
1148
+ }, undefined, false, undefined, this) : null
1149
+ ]
1150
+ }, undefined, true, undefined, this)
1151
+ ]
1152
+ }, undefined, true, undefined, this)
1153
+ }, undefined, false, undefined, this)
1154
+ ]
1155
+ }, undefined, true, undefined, this);
1156
+ }
1157
+ export {
1158
+ Panel as default
1159
+ };