elysia-rack 0.2.0 → 0.3.0

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