@maykonpaulo/maestro-admin 0.3.0 → 0.4.0-next.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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/MaestroAdmin.tsx
2
- import { useMemo as useMemo3, useState as useState10 } from "react";
2
+ import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo7, useState as useState14 } from "react";
3
3
 
4
4
  // src/client/MaestroClient.ts
5
5
  var MaestroApiError = class extends Error {
@@ -110,6 +110,11 @@ function useEntity(entityId) {
110
110
  const entities = useEntities();
111
111
  return entityId ? entities.find((e) => e.id === entityId) : void 0;
112
112
  }
113
+ var RootContext = createContext(null);
114
+ var AdminRootProvider = RootContext.Provider;
115
+ function useAdminRoot() {
116
+ return useContext(RootContext);
117
+ }
113
118
 
114
119
  // src/hooks.ts
115
120
  import { useCallback, useEffect, useState } from "react";
@@ -198,9 +203,6 @@ function formFields(entity, mode) {
198
203
  return entity.fields.filter((f) => mode === "create" ? f.form.creatable : f.form.editable).sort((a, b) => (a.form.order ?? 0) - (b.form.order ?? 0));
199
204
  }
200
205
 
201
- // src/components/Sidebar.tsx
202
- import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
203
-
204
206
  // src/grouping.ts
205
207
  var MIN_PREFIX = 4;
206
208
  function commonPrefix(a, b) {
@@ -257,6 +259,9 @@ function buildMenu(entities, labels) {
257
259
  return { groups, ungrouped };
258
260
  }
259
261
 
262
+ // src/components/Sidebar.tsx
263
+ import { useMemo, useState as useState2 } from "react";
264
+
260
265
  // src/i18n.ts
261
266
  import { createContext as createContext2, useContext as useContext2 } from "react";
262
267
  var EN = {
@@ -264,7 +269,7 @@ var EN = {
264
269
  loadingMetadata: "Loading metadata\u2026",
265
270
  records: (total) => `${total} record${total === 1 ? "" : "s"}`,
266
271
  searchPlaceholder: (plural) => `Search ${plural.toLowerCase()}\u2026`,
267
- newRecord: "+ New",
272
+ newRecord: "New",
268
273
  view: "View",
269
274
  edit: "Edit",
270
275
  clone: "Clone",
@@ -303,14 +308,42 @@ var EN = {
303
308
  searchMenu: "Search menu\u2026",
304
309
  recents: "Recents",
305
310
  menus: "Menus",
306
- noMenuResults: "No matches"
311
+ noMenuResults: "No matches",
312
+ hub: "Hub",
313
+ breadcrumb: "Breadcrumb",
314
+ search: "Search",
315
+ commandKey: "\u2318K",
316
+ commandPalette: "Command palette",
317
+ palettePlaceholder: "Go to an entity, or run an action\u2026",
318
+ goTo: "Go to",
319
+ onThisScreen: "On this screen",
320
+ appearance: "Appearance",
321
+ palette: "Palette",
322
+ density: "Density",
323
+ colorScheme: "Light / dark",
324
+ schemeSystem: "system",
325
+ schemeLight: "light",
326
+ schemeDark: "dark",
327
+ entities: "Entities",
328
+ hubMeta: (entities, sources) => `${entities} ${entities === 1 ? "entity" : "entities"} across ${sources} ${sources === 1 ? "datasource" : "datasources"}.`,
329
+ otherEntities: "Other",
330
+ noEntities: "No entities",
331
+ noEntitiesHint: "The server served an empty metadata document.",
332
+ fieldCount: (count) => `${count} field${count === 1 ? "" : "s"}`,
333
+ capRead: "read",
334
+ capCreate: "create",
335
+ capUpdate: "update",
336
+ capDelete: "delete",
337
+ columns: "Columns",
338
+ columnsCount: (visible, total) => `${visible}/${total}`,
339
+ resetColumns: "Reset columns"
307
340
  };
308
341
  var PT_BR = {
309
342
  loading: "Carregando\u2026",
310
343
  loadingMetadata: "Carregando metadados\u2026",
311
344
  records: (total) => `${total} registro${total === 1 ? "" : "s"}`,
312
345
  searchPlaceholder: (plural) => `Buscar ${plural.toLowerCase()}\u2026`,
313
- newRecord: "+ Novo",
346
+ newRecord: "Novo",
314
347
  view: "Visualizar",
315
348
  edit: "Editar",
316
349
  clone: "Clonar",
@@ -349,7 +382,35 @@ var PT_BR = {
349
382
  searchMenu: "Buscar no menu\u2026",
350
383
  recents: "Recentes",
351
384
  menus: "Menus",
352
- noMenuResults: "Nada encontrado"
385
+ noMenuResults: "Nada encontrado",
386
+ hub: "Hub",
387
+ breadcrumb: "Trilha",
388
+ search: "Buscar",
389
+ commandKey: "\u2318K",
390
+ commandPalette: "Paleta de comandos",
391
+ palettePlaceholder: "Ir para uma entidade, ou executar uma a\xE7\xE3o\u2026",
392
+ goTo: "Ir para",
393
+ onThisScreen: "Nesta tela",
394
+ appearance: "Apar\xEAncia",
395
+ palette: "Paleta",
396
+ density: "Densidade",
397
+ colorScheme: "Claro / escuro",
398
+ schemeSystem: "sistema",
399
+ schemeLight: "claro",
400
+ schemeDark: "escuro",
401
+ entities: "Entidades",
402
+ hubMeta: (entities, sources) => `${entities} ${entities === 1 ? "entidade" : "entidades"} em ${sources} ${sources === 1 ? "origem de dados" : "origens de dados"}.`,
403
+ otherEntities: "Outras",
404
+ noEntities: "Nenhuma entidade",
405
+ noEntitiesHint: "O servidor entregou um documento de metadados vazio.",
406
+ fieldCount: (count) => `${count} campo${count === 1 ? "" : "s"}`,
407
+ capRead: "ler",
408
+ capCreate: "criar",
409
+ capUpdate: "editar",
410
+ capDelete: "excluir",
411
+ columns: "Colunas",
412
+ columnsCount: (visible, total) => `${visible}/${total}`,
413
+ resetColumns: "Restaurar colunas"
353
414
  };
354
415
  var LOCALES = { en: EN, "pt-BR": PT_BR };
355
416
  function stringsFor(locale) {
@@ -361,8 +422,179 @@ function useT() {
361
422
  return useContext2(I18nContext);
362
423
  }
363
424
 
364
- // src/components/Sidebar.tsx
425
+ // src/components/icons.tsx
365
426
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
427
+ var PATHS = {
428
+ hub: /* @__PURE__ */ jsxs(Fragment, { children: [
429
+ /* @__PURE__ */ jsx("rect", { x: "3.5", y: "3.5", width: "7", height: "7", rx: "1.5" }),
430
+ /* @__PURE__ */ jsx("rect", { x: "13.5", y: "3.5", width: "7", height: "7", rx: "1.5" }),
431
+ /* @__PURE__ */ jsx("rect", { x: "3.5", y: "13.5", width: "7", height: "7", rx: "1.5" }),
432
+ /* @__PURE__ */ jsx("rect", { x: "13.5", y: "13.5", width: "7", height: "7", rx: "1.5" })
433
+ ] }),
434
+ baton: /* @__PURE__ */ jsxs(Fragment, { children: [
435
+ /* @__PURE__ */ jsx("path", { d: "M4 20 14 10" }),
436
+ /* @__PURE__ */ jsx("circle", { cx: "17", cy: "7", r: "3.2" })
437
+ ] }),
438
+ search: /* @__PURE__ */ jsxs(Fragment, { children: [
439
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "6.5" }),
440
+ /* @__PURE__ */ jsx("path", { d: "m16 16 4.5 4.5" })
441
+ ] }),
442
+ chevronRight: /* @__PURE__ */ jsx("path", { d: "m9 5 7 7-7 7" }),
443
+ chevronLeft: /* @__PURE__ */ jsx("path", { d: "M15 5 8 12l7 7" }),
444
+ chevronsLeft: /* @__PURE__ */ jsx("path", { d: "M17 5l-7 7 7 7M10 5l-7 7 7 7" }),
445
+ chevronsRight: /* @__PURE__ */ jsx("path", { d: "m7 5 7 7-7 7M14 5l7 7-7 7" }),
446
+ plus: /* @__PURE__ */ jsx("path", { d: "M12 5v14M5 12h14" }),
447
+ close: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6 6 18" }),
448
+ check: /* @__PURE__ */ jsx("path", { d: "m4.5 12.5 5 5 10-11" }),
449
+ filter: /* @__PURE__ */ jsx("path", { d: "M3 5h18l-7 8v6l-4 2v-8L3 5Z" }),
450
+ download: /* @__PURE__ */ jsx("path", { d: "M12 3v12M7.5 10.5 12 15l4.5-4.5M4 20h16" }),
451
+ columns: /* @__PURE__ */ jsxs(Fragment, { children: [
452
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
453
+ /* @__PURE__ */ jsx("path", { d: "M9.5 4v16M15 4v16" })
454
+ ] }),
455
+ rows: /* @__PURE__ */ jsxs(Fragment, { children: [
456
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
457
+ /* @__PURE__ */ jsx("path", { d: "M3 9.3h18M3 14.7h18" })
458
+ ] }),
459
+ sun: /* @__PURE__ */ jsxs(Fragment, { children: [
460
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4.2" }),
461
+ /* @__PURE__ */ jsx("path", { d: "M12 2.5v2.2M12 19.3v2.2M4.2 4.2l1.6 1.6M18.2 18.2l1.6 1.6M2.5 12h2.2M19.3 12h2.2M4.2 19.8l1.6-1.6M18.2 5.8l1.6-1.6" })
462
+ ] }),
463
+ moon: /* @__PURE__ */ jsx("path", { d: "M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5Z" }),
464
+ swatch: /* @__PURE__ */ jsxs(Fragment, { children: [
465
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "4.5" }),
466
+ /* @__PURE__ */ jsx("circle", { cx: "15.5", cy: "8.5", r: "4.5" }),
467
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "15", r: "4.5" })
468
+ ] }),
469
+ dots: /* @__PURE__ */ jsxs(Fragment, { children: [
470
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "5", r: "1.4" }),
471
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1.4" }),
472
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "19", r: "1.4" })
473
+ ] }),
474
+ trash: /* @__PURE__ */ jsx("path", { d: "M4 7h16M9 7V4.5h6V7M6.5 7l1 13h9l1-13" }),
475
+ pencil: /* @__PURE__ */ jsx("path", { d: "M4 20h4L20 8l-4-4L4 16v4Z" }),
476
+ eye: /* @__PURE__ */ jsxs(Fragment, { children: [
477
+ /* @__PURE__ */ jsx("path", { d: "M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z" }),
478
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
479
+ ] }),
480
+ copy: /* @__PURE__ */ jsxs(Fragment, { children: [
481
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "11", height: "11", rx: "2" }),
482
+ /* @__PURE__ */ jsx("path", { d: "M15 6.5V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h.5" })
483
+ ] }),
484
+ clone: /* @__PURE__ */ jsxs(Fragment, { children: [
485
+ /* @__PURE__ */ jsx("rect", { x: "4", y: "4", width: "11", height: "11", rx: "2" }),
486
+ /* @__PURE__ */ jsx("path", { d: "M9 20h9a2 2 0 0 0 2-2V9" })
487
+ ] }),
488
+ arrowUpRight: /* @__PURE__ */ jsx("path", { d: "M7 17 17 7M9 7h8v8" }),
489
+ arrowLeft: /* @__PURE__ */ jsx("path", { d: "M19 12H6M11 6l-5 6 5 6" }),
490
+ sort: /* @__PURE__ */ jsx("path", { d: "M8 9 12 5l4 4M8 15l4 4 4-4" }),
491
+ sortAsc: /* @__PURE__ */ jsx("path", { d: "M12 19V6M7 11l5-5 5 5" }),
492
+ sortDesc: /* @__PURE__ */ jsx("path", { d: "M12 5v13M7 13l5 5 5-5" }),
493
+ database: /* @__PURE__ */ jsxs(Fragment, { children: [
494
+ /* @__PURE__ */ jsx("ellipse", { cx: "12", cy: "6", rx: "8", ry: "3.2" }),
495
+ /* @__PURE__ */ jsx("path", { d: "M4 6v12c0 1.8 3.6 3.2 8 3.2s8-1.4 8-3.2V6M4 12c0 1.8 3.6 3.2 8 3.2s8-1.4 8-3.2" })
496
+ ] }),
497
+ table: /* @__PURE__ */ jsxs(Fragment, { children: [
498
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
499
+ /* @__PURE__ */ jsx("path", { d: "M3 9.5h18M9 9.5V20" })
500
+ ] }),
501
+ users: /* @__PURE__ */ jsxs(Fragment, { children: [
502
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "8", r: "3.2" }),
503
+ /* @__PURE__ */ jsx("path", { d: "M3 19c0-3 2.7-4.8 6-4.8s6 1.8 6 4.8" }),
504
+ /* @__PURE__ */ jsx("path", { d: "M16 5.5a3 3 0 0 1 0 5.6M17.5 14.6c2 .6 3.5 2.1 3.5 4.4" })
505
+ ] }),
506
+ box: /* @__PURE__ */ jsxs(Fragment, { children: [
507
+ /* @__PURE__ */ jsx("path", { d: "M12 3 20.5 7.5v9L12 21l-8.5-4.5v-9L12 3Z" }),
508
+ /* @__PURE__ */ jsx("path", { d: "M3.5 7.5 12 12l8.5-4.5M12 12v9" })
509
+ ] }),
510
+ receipt: /* @__PURE__ */ jsxs(Fragment, { children: [
511
+ /* @__PURE__ */ jsx("path", { d: "M5 3h14v18l-2.3-1.6-2.3 1.6-2.4-1.6L9.6 21l-2.3-1.6L5 21V3Z" }),
512
+ /* @__PURE__ */ jsx("path", { d: "M9 8h6M9 12h6" })
513
+ ] }),
514
+ key: /* @__PURE__ */ jsxs(Fragment, { children: [
515
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "13", r: "4" }),
516
+ /* @__PURE__ */ jsx("path", { d: "m11 11 8-8M17 5l2 2M15 7l2 2" })
517
+ ] }),
518
+ shield: /* @__PURE__ */ jsxs(Fragment, { children: [
519
+ /* @__PURE__ */ jsx("path", { d: "M12 3 20 6v6c0 4.4-3.3 7.9-8 9-4.7-1.1-8-4.6-8-9V6l8-3Z" }),
520
+ /* @__PURE__ */ jsx("path", { d: "m9 12 2.2 2.2L15.5 10" })
521
+ ] }),
522
+ activity: /* @__PURE__ */ jsx("path", { d: "M3 12h4l2.5-6 4 12 2.5-6h5" }),
523
+ tag: /* @__PURE__ */ jsxs(Fragment, { children: [
524
+ /* @__PURE__ */ jsx("path", { d: "M3 11.5V4h7.5l10 10-7.5 7.5-10-10Z" }),
525
+ /* @__PURE__ */ jsx("circle", { cx: "7.5", cy: "7.5", r: "1.3" })
526
+ ] }),
527
+ map: /* @__PURE__ */ jsxs(Fragment, { children: [
528
+ /* @__PURE__ */ jsx("path", { d: "M9 4 3 6.5v13L9 17l6 2.5 6-2.5v-13L15 6.5 9 4Z" }),
529
+ /* @__PURE__ */ jsx("path", { d: "M9 4v13M15 6.5v13" })
530
+ ] }),
531
+ calendar: /* @__PURE__ */ jsxs(Fragment, { children: [
532
+ /* @__PURE__ */ jsx("rect", { x: "3.5", y: "5", width: "17", height: "15", rx: "2" }),
533
+ /* @__PURE__ */ jsx("path", { d: "M3.5 10h17M8 3v4M16 3v4" })
534
+ ] }),
535
+ mail: /* @__PURE__ */ jsxs(Fragment, { children: [
536
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
537
+ /* @__PURE__ */ jsx("path", { d: "m3.5 7 8.5 6 8.5-6" })
538
+ ] }),
539
+ file: /* @__PURE__ */ jsxs(Fragment, { children: [
540
+ /* @__PURE__ */ jsx("path", { d: "M13 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9l-6-6Z" }),
541
+ /* @__PURE__ */ jsx("path", { d: "M13 3v6h6" })
542
+ ] }),
543
+ alert: /* @__PURE__ */ jsxs(Fragment, { children: [
544
+ /* @__PURE__ */ jsx("path", { d: "M12 4 2.5 20h19L12 4Z" }),
545
+ /* @__PURE__ */ jsx("path", { d: "M12 10v4.5M12 17.4v.2" })
546
+ ] })
547
+ };
548
+ function Icon({
549
+ name,
550
+ size = 15,
551
+ className,
552
+ title
553
+ }) {
554
+ return /* @__PURE__ */ jsxs(
555
+ "svg",
556
+ {
557
+ viewBox: "0 0 24 24",
558
+ width: size,
559
+ height: size,
560
+ className: className ? `mst-icon ${className}` : "mst-icon",
561
+ "aria-hidden": title ? void 0 : true,
562
+ role: title ? "img" : void 0,
563
+ focusable: "false",
564
+ children: [
565
+ title && /* @__PURE__ */ jsx("title", { children: title }),
566
+ PATHS[name]
567
+ ]
568
+ }
569
+ );
570
+ }
571
+ var ENTITY_ICON_HINTS = [
572
+ [["user", "usuario", "people", "pessoa", "customer", "cliente", "associado", "member", "contact"], "users"],
573
+ [["invoice", "fatura", "cobranca", "billing", "receipt", "nota"], "receipt"],
574
+ [["payment", "pagamento", "transaction", "transacao", "ledger"], "table"],
575
+ [["product", "produto", "item", "sku", "inventory", "estoque", "catalog"], "box"],
576
+ [["address", "endereco", "location", "local", "region", "city", "cidade"], "map"],
577
+ [["role", "papel", "permission", "permissao", "grant", "token", "credential"], "key"],
578
+ [["audit", "auditoria", "log", "event", "evento", "metric"], "activity"],
579
+ [["policy", "politica", "security", "seguranca", "consent", "consentimento"], "shield"],
580
+ [["tag", "label", "category", "categoria", "plan", "plano", "subscription", "assinatura"], "tag"],
581
+ [["schedule", "agenda", "appointment", "date", "periodo", "calendar"], "calendar"],
582
+ [["message", "mensagem", "email", "notification", "notificacao", "ticket"], "mail"],
583
+ [["document", "documento", "file", "arquivo", "contract", "contrato", "report"], "file"]
584
+ ];
585
+ function fold(text) {
586
+ return text.toLowerCase().normalize("NFD").replace(/\p{M}/gu, "");
587
+ }
588
+ function entityIcon(entity) {
589
+ const haystack = fold(`${entity.id} ${entity.table} ${entity.label.singular} ${entity.label.plural}`);
590
+ for (const [words, icon] of ENTITY_ICON_HINTS) {
591
+ if (words.some((word) => haystack.includes(word))) return icon;
592
+ }
593
+ return "table";
594
+ }
595
+
596
+ // src/components/Sidebar.tsx
597
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
366
598
  function normalize(text) {
367
599
  return text.toLowerCase().normalize("NFD").replace(/\p{M}/gu, "");
368
600
  }
@@ -381,9 +613,9 @@ function Highlight({ label, query }) {
381
613
  if (idx === -1) return label;
382
614
  const start = originalIndex[idx];
383
615
  const end = (originalIndex[idx + foldedQuery.length - 1] ?? label.length - 1) + 1;
384
- return /* @__PURE__ */ jsxs(Fragment, { children: [
616
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
385
617
  label.slice(0, start),
386
- /* @__PURE__ */ jsx("mark", { className: "rounded-sm bg-amber-200 text-inherit", children: label.slice(start, end) }),
618
+ /* @__PURE__ */ jsx2("mark", { children: label.slice(start, end) }),
387
619
  label.slice(end)
388
620
  ] });
389
621
  }
@@ -394,33 +626,40 @@ function EntityButton({
394
626
  subtitle,
395
627
  query
396
628
  }) {
397
- return /* @__PURE__ */ jsxs(
629
+ return /* @__PURE__ */ jsxs2(
398
630
  "button",
399
631
  {
632
+ type: "button",
633
+ "aria-current": active,
400
634
  onClick: () => onSelect(entity.id),
401
- className: `flex w-full flex-col rounded-md px-3 py-1.5 text-left text-sm transition-colors ${active ? "bg-indigo-100 font-medium text-indigo-700" : "text-slate-700 hover:bg-slate-100"}`,
635
+ className: "mst-navitem",
636
+ title: entity.label.plural,
402
637
  children: [
403
- /* @__PURE__ */ jsx("span", { className: "truncate", children: query ? /* @__PURE__ */ jsx(Highlight, { label: entity.label.plural, query }) : entity.label.plural }),
404
- subtitle && /* @__PURE__ */ jsx("span", { className: "truncate text-xs font-normal text-slate-400", children: subtitle })
638
+ /* @__PURE__ */ jsx2(Icon, { name: entityIcon(entity), size: 14 }),
639
+ /* @__PURE__ */ jsxs2("span", { className: "mst-navitem__label", children: [
640
+ query ? /* @__PURE__ */ jsx2(Highlight, { label: entity.label.plural, query }) : entity.label.plural,
641
+ subtitle && /* @__PURE__ */ jsxs2("span", { className: "mst-navitem__sub", children: [
642
+ " \xB7 ",
643
+ subtitle
644
+ ] })
645
+ ] })
405
646
  ]
406
647
  }
407
648
  );
408
649
  }
409
- function SectionTitle({ children }) {
410
- return /* @__PURE__ */ jsx("div", { className: "px-3 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-slate-400", children });
411
- }
412
650
  function Sidebar({
413
651
  entities,
414
652
  activeId,
415
653
  onSelect,
416
654
  title,
417
655
  labels,
418
- recents = []
656
+ recents = [],
657
+ onHome,
658
+ homeActive = false
419
659
  }) {
420
660
  const t = useT();
421
661
  const [query, setQuery] = useState2("");
422
662
  const [expanded, setExpanded] = useState2({});
423
- const searchRef = useRef(null);
424
663
  const menu = useMemo(() => buildMenu(entities, labels), [entities, labels]);
425
664
  const groupOf = useMemo(() => {
426
665
  const map = /* @__PURE__ */ new Map();
@@ -433,27 +672,19 @@ function Sidebar({
433
672
  const q = normalize(query);
434
673
  return entities.filter((e) => normalize(`${e.label.plural} ${e.label.singular} ${e.table}`).includes(q)).sort((a, b) => a.label.plural.localeCompare(b.label.plural));
435
674
  }, [entities, query]);
436
- useEffect2(() => {
437
- const onKey = (e) => {
438
- if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
439
- e.preventDefault();
440
- searchRef.current?.focus();
441
- }
442
- };
443
- document.addEventListener("keydown", onKey);
444
- return () => document.removeEventListener("keydown", onKey);
445
- }, []);
446
675
  const select = (entityId) => {
447
676
  setQuery("");
448
677
  onSelect(entityId);
449
678
  };
450
679
  const isExpanded = (groupId, hasActive) => expanded[groupId] ?? hasActive;
451
- return /* @__PURE__ */ jsxs("aside", { className: "flex w-64 shrink-0 flex-col border-r border-slate-200 bg-slate-50", children: [
452
- /* @__PURE__ */ jsx("div", { className: "border-b border-slate-200 px-4 py-4", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tracking-tight text-slate-900", children: title }) }),
453
- /* @__PURE__ */ jsx("div", { className: "px-2 pt-2", children: /* @__PURE__ */ jsx(
680
+ return /* @__PURE__ */ jsxs2("aside", { className: "mst-side", children: [
681
+ /* @__PURE__ */ jsxs2("div", { className: "mst-side__brand", children: [
682
+ /* @__PURE__ */ jsx2("span", { className: "mst-mark", children: /* @__PURE__ */ jsx2(Icon, { name: "baton", size: 15 }) }),
683
+ /* @__PURE__ */ jsx2("span", { className: "mst-side__title", children: title })
684
+ ] }),
685
+ /* @__PURE__ */ jsx2("div", { className: "mst-side__search", children: /* @__PURE__ */ jsx2(
454
686
  "input",
455
687
  {
456
- ref: searchRef,
457
688
  type: "search",
458
689
  value: query,
459
690
  onChange: (e) => setQuery(e.target.value),
@@ -461,11 +692,11 @@ function Sidebar({
461
692
  if (e.key === "Escape") setQuery("");
462
693
  if (e.key === "Enter" && results[0]) select(results[0].id);
463
694
  },
464
- placeholder: `${t.searchMenu} (Ctrl+K)`,
465
- className: "w-full rounded-md border border-slate-300 bg-white px-3 py-1.5 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
695
+ placeholder: t.searchMenu,
696
+ "aria-label": t.searchMenu
466
697
  }
467
698
  ) }),
468
- /* @__PURE__ */ jsx("nav", { className: "flex-1 overflow-y-auto p-2", children: query ? results.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-2 text-sm text-slate-400", children: t.noMenuResults }) : results.map((e) => /* @__PURE__ */ jsx(
699
+ /* @__PURE__ */ jsx2("nav", { className: "mst-side__nav", children: query ? results.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "mst-side__empty", children: t.noMenuResults }) : results.map((e) => /* @__PURE__ */ jsx2(
469
700
  EntityButton,
470
701
  {
471
702
  entity: e,
@@ -475,90 +706,599 @@ function Sidebar({
475
706
  query
476
707
  },
477
708
  e.id
478
- )) : /* @__PURE__ */ jsxs(Fragment, { children: [
479
- recentEntities.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
480
- /* @__PURE__ */ jsx(SectionTitle, { children: t.recents }),
481
- recentEntities.map((e) => /* @__PURE__ */ jsx(EntityButton, { entity: e, active: e.id === activeId, onSelect: select }, e.id))
709
+ )) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
710
+ onHome && /* @__PURE__ */ jsxs2("button", { type: "button", className: "mst-navitem", "aria-current": homeActive, onClick: onHome, title: t.hub, children: [
711
+ /* @__PURE__ */ jsx2(Icon, { name: "hub", size: 14 }),
712
+ /* @__PURE__ */ jsx2("span", { className: "mst-navitem__label", children: t.hub })
482
713
  ] }),
483
- /* @__PURE__ */ jsx(SectionTitle, { children: t.menus }),
714
+ recentEntities.length > 0 && /* @__PURE__ */ jsxs2(Fragment2, { children: [
715
+ /* @__PURE__ */ jsx2("p", { className: "mst-side__section", children: t.recents }),
716
+ recentEntities.map((e) => /* @__PURE__ */ jsx2(EntityButton, { entity: e, active: e.id === activeId, onSelect: select }, e.id))
717
+ ] }),
718
+ /* @__PURE__ */ jsx2("p", { className: "mst-side__section", children: t.menus }),
484
719
  menu.groups.map((group) => {
485
720
  const hasActive = group.entities.some((e) => e.id === activeId);
486
721
  const open = isExpanded(group.id, hasActive);
487
- return /* @__PURE__ */ jsxs("div", { children: [
488
- /* @__PURE__ */ jsxs(
722
+ return /* @__PURE__ */ jsxs2("div", { children: [
723
+ /* @__PURE__ */ jsxs2(
489
724
  "button",
490
725
  {
726
+ type: "button",
727
+ "aria-expanded": open,
491
728
  onClick: () => setExpanded((prev) => ({ ...prev, [group.id]: !open })),
492
- className: "flex w-full items-center gap-1.5 rounded-md px-3 py-1.5 text-left text-sm font-medium text-slate-600 hover:bg-slate-100",
729
+ className: "mst-navitem mst-navitem--folder",
493
730
  children: [
494
- /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-400", children: open ? "\u25BE" : "\u25B8" }),
495
- /* @__PURE__ */ jsx("span", { className: "truncate", children: group.label }),
496
- /* @__PURE__ */ jsx("span", { className: "ml-auto text-xs text-slate-400", children: group.entities.length })
731
+ /* @__PURE__ */ jsx2(
732
+ Icon,
733
+ {
734
+ name: "chevronRight",
735
+ size: 12,
736
+ className: open ? "mst-navitem__chev mst-navitem__chev--open" : "mst-navitem__chev"
737
+ }
738
+ ),
739
+ /* @__PURE__ */ jsx2("span", { className: "mst-navitem__label", children: group.label }),
740
+ /* @__PURE__ */ jsx2("span", { className: "mst-navitem__count", children: group.entities.length })
497
741
  ]
498
742
  }
499
743
  ),
500
- open && /* @__PURE__ */ jsx("div", { className: "ml-3 border-l border-slate-200 pl-1", children: group.entities.map((e) => /* @__PURE__ */ jsx(EntityButton, { entity: e, active: e.id === activeId, onSelect: select }, e.id)) })
744
+ open && /* @__PURE__ */ jsx2("div", { className: "mst-side__children", children: group.entities.map((e) => /* @__PURE__ */ jsx2(EntityButton, { entity: e, active: e.id === activeId, onSelect: select }, e.id)) })
501
745
  ] }, group.id);
502
746
  }),
503
- menu.ungrouped.map((e) => /* @__PURE__ */ jsx(EntityButton, { entity: e, active: e.id === activeId, onSelect: select }, e.id))
747
+ menu.ungrouped.map((e) => /* @__PURE__ */ jsx2(EntityButton, { entity: e, active: e.id === activeId, onSelect: select }, e.id))
504
748
  ] }) }),
505
- /* @__PURE__ */ jsx("div", { className: "border-t border-slate-200 px-4 py-3 text-xs text-slate-400", children: t.entitiesFooter(entities.length) })
749
+ /* @__PURE__ */ jsxs2("div", { className: "mst-side__foot", children: [
750
+ /* @__PURE__ */ jsx2("span", { className: "mst-pulse", "aria-hidden": true }),
751
+ /* @__PURE__ */ jsx2("span", { children: t.entitiesFooter(entities.length) })
752
+ ] })
506
753
  ] });
507
754
  }
508
755
 
509
- // src/components/EntityList.tsx
510
- import { useMemo as useMemo2, useState as useState5 } from "react";
756
+ // src/components/Header.tsx
757
+ import { useState as useState5 } from "react";
511
758
 
512
- // src/components/ui.tsx
513
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
514
- var VARIANTS = {
515
- primary: "bg-indigo-600 text-white hover:bg-indigo-500 disabled:bg-indigo-300",
516
- secondary: "bg-white text-slate-700 ring-1 ring-inset ring-slate-300 hover:bg-slate-50",
517
- danger: "bg-red-600 text-white hover:bg-red-500 disabled:bg-red-300",
518
- ghost: "text-slate-600 hover:bg-slate-100"
759
+ // src/theme.tsx
760
+ import { createContext as createContext3, useCallback as useCallback2, useContext as useContext3, useMemo as useMemo2, useState as useState3 } from "react";
761
+ import { jsx as jsx3 } from "react/jsx-runtime";
762
+ var MAESTRO_THEMES = [
763
+ { id: "cobalto", label: "Grafite & Cobalto", swatch: "#3557e8" },
764
+ { id: "latao", label: "Ard\xF3sia & Lat\xE3o", swatch: "#9a6a22" },
765
+ { id: "teal", label: "Tinta & Teal", swatch: "#0e7c74" }
766
+ ];
767
+ var STORAGE_KEY = "maestro-admin:appearance";
768
+ var DEFAULTS = {
769
+ preset: "cobalto",
770
+ scheme: "system",
771
+ density: "comfortable"
772
+ };
773
+ function normalizeConfig(input) {
774
+ if (!input) return {};
775
+ return typeof input === "string" ? { preset: input } : input;
776
+ }
777
+ function readStored() {
778
+ try {
779
+ const raw = globalThis.localStorage?.getItem(STORAGE_KEY);
780
+ const parsed = raw ? JSON.parse(raw) : null;
781
+ return parsed && typeof parsed === "object" ? parsed : {};
782
+ } catch {
783
+ return {};
784
+ }
785
+ }
786
+ function writeStored(value) {
787
+ try {
788
+ globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(value));
789
+ } catch {
790
+ }
791
+ }
792
+ var ThemeContext = createContext3({
793
+ ...DEFAULTS,
794
+ picker: true,
795
+ setPreset: () => void 0,
796
+ setScheme: () => void 0,
797
+ setDensity: () => void 0
798
+ });
799
+ function useTheme() {
800
+ return useContext3(ThemeContext);
801
+ }
802
+ function MaestroThemeProvider({
803
+ theme,
804
+ className,
805
+ children,
806
+ rootRef
807
+ }) {
808
+ const config = useMemo2(() => normalizeConfig(theme), [theme]);
809
+ const remember = config.remember !== false;
810
+ const [state, setState] = useState3(() => {
811
+ const stored = remember ? readStored() : {};
812
+ return {
813
+ preset: stored.preset ?? config.preset ?? DEFAULTS.preset,
814
+ scheme: stored.scheme ?? config.scheme ?? DEFAULTS.scheme,
815
+ density: stored.density ?? config.density ?? DEFAULTS.density
816
+ };
817
+ });
818
+ const update = useCallback2(
819
+ (patch) => {
820
+ setState((prev) => {
821
+ const next = { ...prev, ...patch };
822
+ if (remember) writeStored(next);
823
+ return next;
824
+ });
825
+ },
826
+ [remember]
827
+ );
828
+ const value = useMemo2(
829
+ () => ({
830
+ preset: state.preset ?? DEFAULTS.preset,
831
+ scheme: state.scheme ?? DEFAULTS.scheme,
832
+ density: state.density ?? DEFAULTS.density,
833
+ picker: config.picker !== false,
834
+ setPreset: (preset) => update({ preset }),
835
+ setScheme: (scheme) => update({ scheme }),
836
+ setDensity: (density) => update({ density })
837
+ }),
838
+ [state, config.picker, update]
839
+ );
840
+ return /* @__PURE__ */ jsx3(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx3(
841
+ "div",
842
+ {
843
+ ref: rootRef,
844
+ className,
845
+ "data-maestro-theme": value.preset,
846
+ "data-maestro-scheme": value.scheme,
847
+ "data-maestro-density": value.density,
848
+ style: config.tokens,
849
+ children
850
+ }
851
+ ) });
852
+ }
853
+
854
+ // src/components/menu.tsx
855
+ import { useEffect as useEffect2, useLayoutEffect, useRef, useState as useState4 } from "react";
856
+ import { createPortal } from "react-dom";
857
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
858
+ function PopMenu({ anchor, items, onClose }) {
859
+ const ref = useRef(null);
860
+ const [pos, setPos] = useState4(anchor);
861
+ const root = useAdminRoot();
862
+ useLayoutEffect(() => {
863
+ const el = ref.current;
864
+ if (!el) return;
865
+ const rect = el.getBoundingClientRect();
866
+ setPos({
867
+ x: Math.max(4, Math.min(anchor.x, window.innerWidth - rect.width - 8)),
868
+ y: Math.max(4, Math.min(anchor.y, window.innerHeight - rect.height - 8))
869
+ });
870
+ }, [anchor]);
871
+ useEffect2(() => {
872
+ const onDown = (e) => {
873
+ if (ref.current && !ref.current.contains(e.target)) onClose();
874
+ };
875
+ const onKey = (e) => {
876
+ if (e.key === "Escape") onClose();
877
+ };
878
+ document.addEventListener("mousedown", onDown);
879
+ document.addEventListener("keydown", onKey);
880
+ window.addEventListener("scroll", onClose, true);
881
+ window.addEventListener("resize", onClose);
882
+ return () => {
883
+ document.removeEventListener("mousedown", onDown);
884
+ document.removeEventListener("keydown", onKey);
885
+ window.removeEventListener("scroll", onClose, true);
886
+ window.removeEventListener("resize", onClose);
887
+ };
888
+ }, [onClose]);
889
+ return createPortal(
890
+ /* @__PURE__ */ jsx4("div", { ref, role: "menu", style: { left: pos.x, top: pos.y }, className: "mst-menu", children: items.map((item) => /* @__PURE__ */ jsxs3("div", { children: [
891
+ item.separated && /* @__PURE__ */ jsx4("div", { className: "mst-menu__sep" }),
892
+ /* @__PURE__ */ jsxs3(
893
+ "button",
894
+ {
895
+ type: "button",
896
+ role: "menuitem",
897
+ onClick: () => {
898
+ onClose();
899
+ item.onSelect();
900
+ },
901
+ className: `mst-menu__item${item.danger ? " mst-menu__item--danger" : ""}`,
902
+ children: [
903
+ item.icon && /* @__PURE__ */ jsx4(Icon, { name: item.icon, size: 14 }),
904
+ item.label
905
+ ]
906
+ }
907
+ )
908
+ ] }, item.id)) }),
909
+ root ?? document.body
910
+ );
911
+ }
912
+ function MenuButton({ items, label }) {
913
+ const [anchor, setAnchor] = useState4(void 0);
914
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
915
+ /* @__PURE__ */ jsx4(
916
+ "button",
917
+ {
918
+ type: "button",
919
+ "aria-label": label,
920
+ title: label,
921
+ onClick: (e) => {
922
+ const rect = e.currentTarget.getBoundingClientRect();
923
+ setAnchor({ x: rect.right - 184, y: rect.bottom + 4 });
924
+ },
925
+ className: "mst-iconbtn",
926
+ children: /* @__PURE__ */ jsx4(Icon, { name: "dots", size: 14 })
927
+ }
928
+ ),
929
+ anchor && /* @__PURE__ */ jsx4(PopMenu, { anchor, items, onClose: () => setAnchor(void 0) })
930
+ ] });
931
+ }
932
+
933
+ // src/components/Header.tsx
934
+ import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
935
+ var NEXT_SCHEME = { system: "light", light: "dark", dark: "system" };
936
+ var SCHEME_ICON = {
937
+ system: "swatch",
938
+ light: "sun",
939
+ dark: "moon"
519
940
  };
941
+ function Header({ crumbs, onOpenPalette }) {
942
+ const t = useT();
943
+ const theme = useTheme();
944
+ const [anchor, setAnchor] = useState5(void 0);
945
+ const themeItems = MAESTRO_THEMES.map((option) => ({
946
+ id: option.id,
947
+ label: /* @__PURE__ */ jsxs4(Fragment4, { children: [
948
+ /* @__PURE__ */ jsx5("span", { className: "mst-swatch", style: { background: option.swatch } }),
949
+ option.label,
950
+ theme.preset === option.id && /* @__PURE__ */ jsx5(Icon, { name: "check", size: 13 })
951
+ ] }),
952
+ onSelect: () => theme.setPreset(option.id)
953
+ }));
954
+ const schemeLabel = theme.scheme === "system" ? t.schemeSystem : theme.scheme === "light" ? t.schemeLight : t.schemeDark;
955
+ return /* @__PURE__ */ jsxs4("header", { className: "mst-header", children: [
956
+ /* @__PURE__ */ jsx5("nav", { className: "mst-crumbs", "aria-label": t.breadcrumb, children: crumbs.map((crumb, index) => /* @__PURE__ */ jsxs4("span", { className: "mst-crumbs__item", children: [
957
+ index > 0 && /* @__PURE__ */ jsx5(Icon, { name: "chevronRight", size: 12, className: "mst-crumbs__sep" }),
958
+ crumb.onClick ? /* @__PURE__ */ jsxs4("button", { type: "button", className: "mst-crumbs__link", onClick: crumb.onClick, children: [
959
+ crumb.icon && /* @__PURE__ */ jsx5(Icon, { name: crumb.icon, size: 13 }),
960
+ crumb.label
961
+ ] }) : /* @__PURE__ */ jsxs4("span", { className: "mst-crumbs__now", children: [
962
+ crumb.icon && /* @__PURE__ */ jsx5(Icon, { name: crumb.icon, size: 13 }),
963
+ crumb.label
964
+ ] })
965
+ ] }, `${crumb.label}-${index}`)) }),
966
+ /* @__PURE__ */ jsxs4("button", { type: "button", className: "mst-searchchip", onClick: onOpenPalette, children: [
967
+ /* @__PURE__ */ jsx5(Icon, { name: "search", size: 13 }),
968
+ /* @__PURE__ */ jsx5("span", { children: t.search }),
969
+ /* @__PURE__ */ jsx5("kbd", { className: "mst-kbd", children: t.commandKey })
970
+ ] }),
971
+ theme.picker && /* @__PURE__ */ jsxs4(Fragment4, { children: [
972
+ /* @__PURE__ */ jsx5(
973
+ "button",
974
+ {
975
+ type: "button",
976
+ "aria-label": t.density,
977
+ title: t.density,
978
+ "aria-pressed": theme.density === "compact",
979
+ className: "mst-iconbtn",
980
+ onClick: () => theme.setDensity(theme.density === "compact" ? "comfortable" : "compact"),
981
+ children: /* @__PURE__ */ jsx5(Icon, { name: "rows", size: 14 })
982
+ }
983
+ ),
984
+ /* @__PURE__ */ jsx5(
985
+ "button",
986
+ {
987
+ type: "button",
988
+ "aria-label": t.palette,
989
+ title: t.palette,
990
+ className: "mst-iconbtn",
991
+ onClick: (e) => {
992
+ const rect = e.currentTarget.getBoundingClientRect();
993
+ setAnchor({ x: rect.right - 200, y: rect.bottom + 6 });
994
+ },
995
+ children: /* @__PURE__ */ jsx5(Icon, { name: "swatch", size: 14 })
996
+ }
997
+ ),
998
+ /* @__PURE__ */ jsx5(
999
+ "button",
1000
+ {
1001
+ type: "button",
1002
+ "aria-label": `${t.colorScheme}: ${schemeLabel}`,
1003
+ title: `${t.colorScheme}: ${schemeLabel}`,
1004
+ className: "mst-iconbtn",
1005
+ onClick: () => theme.setScheme(NEXT_SCHEME[theme.scheme]),
1006
+ children: /* @__PURE__ */ jsx5(Icon, { name: SCHEME_ICON[theme.scheme], size: 14 })
1007
+ }
1008
+ )
1009
+ ] }),
1010
+ anchor && /* @__PURE__ */ jsx5(PopMenu, { anchor, items: themeItems, onClose: () => setAnchor(void 0) })
1011
+ ] });
1012
+ }
1013
+
1014
+ // src/components/Hub.tsx
1015
+ import { useMemo as useMemo3 } from "react";
1016
+
1017
+ // src/components/ui.tsx
1018
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
520
1019
  function Button({
521
1020
  variant = "secondary",
1021
+ icon,
522
1022
  className = "",
1023
+ children,
523
1024
  ...props
524
1025
  }) {
525
- return /* @__PURE__ */ jsx2(
526
- "button",
527
- {
528
- className: `inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed ${VARIANTS[variant]} ${className}`,
529
- ...props
530
- }
531
- );
1026
+ return /* @__PURE__ */ jsxs5("button", { type: "button", className: `mst-btn mst-btn--${variant} ${className}`.trim(), ...props, children: [
1027
+ icon && /* @__PURE__ */ jsx6(Icon, { name: icon, size: 14 }),
1028
+ children
1029
+ ] });
532
1030
  }
533
- function Badge({ children, tone = "slate" }) {
534
- const tones = {
535
- slate: "bg-slate-100 text-slate-700",
536
- green: "bg-green-100 text-green-700",
537
- red: "bg-red-100 text-red-700",
538
- amber: "bg-amber-100 text-amber-700",
539
- indigo: "bg-indigo-100 text-indigo-700"
540
- };
541
- return /* @__PURE__ */ jsx2("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${tones[tone]}`, children });
1031
+ function IconButton({
1032
+ icon,
1033
+ label,
1034
+ className = "",
1035
+ ...props
1036
+ }) {
1037
+ return /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": label, title: label, className: `mst-iconbtn ${className}`.trim(), ...props, children: /* @__PURE__ */ jsx6(Icon, { name: icon, size: 14 }) });
1038
+ }
1039
+ function Badge({ children, tone = "neutral", dot = false }) {
1040
+ const toneClass = tone === "neutral" ? "" : ` mst-badge--${tone}`;
1041
+ return /* @__PURE__ */ jsx6("span", { className: `mst-badge${toneClass}${dot ? " mst-badge--dot" : ""}`, children });
542
1042
  }
543
1043
  function Spinner({ label }) {
544
- return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-sm text-slate-500", role: "status", children: [
545
- /* @__PURE__ */ jsx2("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-slate-300 border-t-indigo-600" }),
546
- label ?? "Loading\u2026"
547
- ] });
1044
+ return /* @__PURE__ */ jsx6("div", { className: "mst-spinner", role: "status", children: label ?? "Loading\u2026" });
548
1045
  }
549
1046
  function EmptyState({ title, hint }) {
550
- return /* @__PURE__ */ jsxs2("div", { className: "rounded-lg border border-dashed border-slate-300 p-8 text-center", children: [
551
- /* @__PURE__ */ jsx2("p", { className: "text-sm font-medium text-slate-700", children: title }),
552
- hint && /* @__PURE__ */ jsx2("p", { className: "mt-1 text-sm text-slate-500", children: hint })
1047
+ return /* @__PURE__ */ jsxs5("div", { className: "mst-empty", children: [
1048
+ /* @__PURE__ */ jsx6("p", { className: "mst-empty__title", children: title }),
1049
+ hint && /* @__PURE__ */ jsx6("p", { className: "mst-empty__hint", children: hint })
553
1050
  ] });
554
1051
  }
555
1052
  function ErrorBanner({ error }) {
556
- return /* @__PURE__ */ jsx2("div", { className: "rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700", role: "alert", children: error.message });
1053
+ return /* @__PURE__ */ jsxs5("div", { className: "mst-error", role: "alert", children: [
1054
+ /* @__PURE__ */ jsx6(Icon, { name: "alert", size: 15 }),
1055
+ /* @__PURE__ */ jsx6("span", { children: error.message })
1056
+ ] });
1057
+ }
1058
+ function Toast({ children }) {
1059
+ return /* @__PURE__ */ jsx6("div", { className: "mst-toast", role: "status", children });
1060
+ }
1061
+
1062
+ // src/components/Hub.tsx
1063
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1064
+ var CAPS = ["list", "create", "update", "delete"];
1065
+ function EntityCard({ entity, onOpen }) {
1066
+ const t = useT();
1067
+ const capLabel = { list: t.capRead, create: t.capCreate, update: t.capUpdate, delete: t.capDelete };
1068
+ return /* @__PURE__ */ jsxs6("button", { type: "button", className: "mst-ecard", onClick: () => onOpen(entity.id), children: [
1069
+ /* @__PURE__ */ jsx7("span", { className: "mst-ecard__tile", children: /* @__PURE__ */ jsx7(Icon, { name: entityIcon(entity), size: 16 }) }),
1070
+ /* @__PURE__ */ jsxs6("span", { className: "mst-ecard__body", children: [
1071
+ /* @__PURE__ */ jsx7("span", { className: "mst-ecard__name", children: entity.label.plural }),
1072
+ /* @__PURE__ */ jsx7("span", { className: "mst-ecard__desc", children: entity.description ?? t.fieldCount(entity.fields.length) }),
1073
+ /* @__PURE__ */ jsx7("span", { className: "mst-caps", children: CAPS.map((cap) => /* @__PURE__ */ jsx7("span", { className: "mst-cap", "data-on": String(entity.capabilities[cap]), children: capLabel[cap] }, cap)) }),
1074
+ /* @__PURE__ */ jsxs6("span", { className: "mst-source", children: [
1075
+ /* @__PURE__ */ jsx7(Icon, { name: "database", size: 11 }),
1076
+ /* @__PURE__ */ jsxs6("span", { children: [
1077
+ entity.datasource,
1078
+ " \xB7 ",
1079
+ entity.table
1080
+ ] })
1081
+ ] })
1082
+ ] })
1083
+ ] });
1084
+ }
1085
+ function Hub({
1086
+ entities,
1087
+ labels,
1088
+ onOpen
1089
+ }) {
1090
+ const t = useT();
1091
+ const menu = useMemo3(() => buildMenu(entities, labels), [entities, labels]);
1092
+ const sources = useMemo3(() => new Set(entities.map((e) => e.datasource)).size, [entities]);
1093
+ if (entities.length === 0) {
1094
+ return /* @__PURE__ */ jsx7(EmptyState, { title: t.noEntities, hint: t.noEntitiesHint });
1095
+ }
1096
+ return /* @__PURE__ */ jsxs6("section", { className: "mst-page", children: [
1097
+ /* @__PURE__ */ jsx7("div", { className: "mst-pagehead", children: /* @__PURE__ */ jsxs6("div", { children: [
1098
+ /* @__PURE__ */ jsx7("h1", { className: "mst-pagehead__title", children: t.entities }),
1099
+ /* @__PURE__ */ jsx7("p", { className: "mst-pagehead__meta", children: t.hubMeta(entities.length, sources) })
1100
+ ] }) }),
1101
+ /* @__PURE__ */ jsxs6("div", { className: "mst-hub", children: [
1102
+ menu.groups.map((group) => /* @__PURE__ */ jsxs6("div", { children: [
1103
+ /* @__PURE__ */ jsx7("p", { className: "mst-grouplabel", children: group.label }),
1104
+ /* @__PURE__ */ jsx7("div", { className: "mst-cards", children: group.entities.map((entity) => /* @__PURE__ */ jsx7(EntityCard, { entity, onOpen }, entity.id)) })
1105
+ ] }, group.id)),
1106
+ menu.ungrouped.length > 0 && /* @__PURE__ */ jsxs6("div", { children: [
1107
+ menu.groups.length > 0 && /* @__PURE__ */ jsx7("p", { className: "mst-grouplabel", children: t.otherEntities }),
1108
+ /* @__PURE__ */ jsx7("div", { className: "mst-cards", children: menu.ungrouped.map((entity) => /* @__PURE__ */ jsx7(EntityCard, { entity, onOpen }, entity.id)) })
1109
+ ] })
1110
+ ] })
1111
+ ] });
1112
+ }
1113
+
1114
+ // src/components/CommandPalette.tsx
1115
+ import { useEffect as useEffect3, useMemo as useMemo4, useRef as useRef2, useState as useState6 } from "react";
1116
+ import { createPortal as createPortal2 } from "react-dom";
1117
+ import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1118
+ function normalize2(text) {
1119
+ return text.toLowerCase().normalize("NFD").replace(/\p{M}/gu, "");
1120
+ }
1121
+ function Highlight2({ label, query }) {
1122
+ if (!query) return label;
1123
+ const folded = normalize2(label);
1124
+ const idx = folded.indexOf(normalize2(query));
1125
+ if (idx === -1) return label;
1126
+ return /* @__PURE__ */ jsxs7(Fragment5, { children: [
1127
+ label.slice(0, idx),
1128
+ /* @__PURE__ */ jsx8("mark", { children: label.slice(idx, idx + query.length) }),
1129
+ label.slice(idx + query.length)
1130
+ ] });
1131
+ }
1132
+ function entityCommands(entities, groupOf, onSelect) {
1133
+ return entities.map((entity) => ({
1134
+ id: `entity:${entity.id}`,
1135
+ label: entity.label.plural,
1136
+ icon: entityIcon(entity),
1137
+ hint: groupOf.get(entity.id),
1138
+ keywords: `${entity.label.singular} ${entity.table} ${entity.id}`,
1139
+ run: () => onSelect(entity.id)
1140
+ }));
1141
+ }
1142
+ function CommandPalette({ sections, onClose }) {
1143
+ const t = useT();
1144
+ const root = useAdminRoot();
1145
+ const inputRef = useRef2(null);
1146
+ const listRef = useRef2(null);
1147
+ const [query, setQuery] = useState6("");
1148
+ const [cursor, setCursor] = useState6(0);
1149
+ const visible = useMemo4(() => {
1150
+ if (!query) return sections;
1151
+ const q = normalize2(query);
1152
+ return sections.map((section) => ({
1153
+ ...section,
1154
+ commands: section.commands.filter((c) => normalize2(`${c.label} ${c.keywords ?? ""}`).includes(q))
1155
+ })).filter((section) => section.commands.length > 0);
1156
+ }, [sections, query]);
1157
+ const flat = useMemo4(() => visible.flatMap((section) => section.commands), [visible]);
1158
+ const active = flat[Math.min(cursor, flat.length - 1)];
1159
+ useEffect3(() => setCursor(0), [query]);
1160
+ useEffect3(() => inputRef.current?.focus(), []);
1161
+ useEffect3(() => {
1162
+ listRef.current?.querySelector('[data-active="true"]')?.scrollIntoView({ block: "nearest" });
1163
+ }, [cursor, query]);
1164
+ const onKeyDown = (e) => {
1165
+ if (e.key === "Escape") {
1166
+ e.preventDefault();
1167
+ onClose();
1168
+ return;
1169
+ }
1170
+ if (e.key === "ArrowDown" || e.key === "n" && e.ctrlKey) {
1171
+ e.preventDefault();
1172
+ setCursor((c) => flat.length === 0 ? 0 : (Math.min(c, flat.length - 1) + 1) % flat.length);
1173
+ return;
1174
+ }
1175
+ if (e.key === "ArrowUp" || e.key === "p" && e.ctrlKey) {
1176
+ e.preventDefault();
1177
+ setCursor((c) => flat.length === 0 ? 0 : (Math.min(c, flat.length - 1) + flat.length - 1) % flat.length);
1178
+ return;
1179
+ }
1180
+ if (e.key === "Enter" && active) {
1181
+ e.preventDefault();
1182
+ onClose();
1183
+ active.run();
1184
+ }
1185
+ };
1186
+ return createPortal2(
1187
+ /* @__PURE__ */ jsx8(
1188
+ "div",
1189
+ {
1190
+ className: "mst-overlay",
1191
+ role: "presentation",
1192
+ onMouseDown: (e) => {
1193
+ if (e.target === e.currentTarget) onClose();
1194
+ },
1195
+ children: /* @__PURE__ */ jsxs7("div", { className: "mst-palette", role: "dialog", "aria-modal": "true", "aria-label": t.commandPalette, children: [
1196
+ /* @__PURE__ */ jsxs7("div", { className: "mst-palette__input", children: [
1197
+ /* @__PURE__ */ jsx8(Icon, { name: "search", size: 15 }),
1198
+ /* @__PURE__ */ jsx8(
1199
+ "input",
1200
+ {
1201
+ ref: inputRef,
1202
+ type: "text",
1203
+ value: query,
1204
+ placeholder: t.palettePlaceholder,
1205
+ "aria-label": t.commandPalette,
1206
+ onChange: (e) => setQuery(e.target.value),
1207
+ onKeyDown
1208
+ }
1209
+ ),
1210
+ /* @__PURE__ */ jsx8("kbd", { className: "mst-kbd", children: "esc" })
1211
+ ] }),
1212
+ /* @__PURE__ */ jsx8("div", { className: "mst-palette__list", ref: listRef, children: flat.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "mst-palette__empty", children: t.noMenuResults }) : visible.map((section) => /* @__PURE__ */ jsxs7("div", { children: [
1213
+ /* @__PURE__ */ jsx8("p", { className: "mst-palette__section", children: section.label }),
1214
+ section.commands.map((command) => /* @__PURE__ */ jsxs7(
1215
+ "button",
1216
+ {
1217
+ type: "button",
1218
+ className: "mst-palette__row",
1219
+ "data-active": String(command.id === active?.id),
1220
+ onMouseEnter: () => setCursor(flat.findIndex((c) => c.id === command.id)),
1221
+ onClick: () => {
1222
+ onClose();
1223
+ command.run();
1224
+ },
1225
+ children: [
1226
+ command.icon && /* @__PURE__ */ jsx8(Icon, { name: command.icon, size: 14 }),
1227
+ /* @__PURE__ */ jsx8("span", { className: "mst-palette__label", children: /* @__PURE__ */ jsx8(Highlight2, { label: command.label, query }) }),
1228
+ command.hint && /* @__PURE__ */ jsx8("span", { className: "mst-palette__hint", children: command.hint })
1229
+ ]
1230
+ },
1231
+ command.id
1232
+ ))
1233
+ ] }, section.id)) })
1234
+ ] })
1235
+ }
1236
+ ),
1237
+ root ?? document.body
1238
+ );
1239
+ }
1240
+
1241
+ // src/components/EntityList.tsx
1242
+ import { useMemo as useMemo6, useState as useState9 } from "react";
1243
+
1244
+ // src/columns.ts
1245
+ import { useCallback as useCallback3, useMemo as useMemo5, useState as useState7 } from "react";
1246
+ var STORAGE_PREFIX = "maestro-admin:columns";
1247
+ function read(key) {
1248
+ try {
1249
+ const raw = globalThis.localStorage?.getItem(key);
1250
+ const parsed = raw ? JSON.parse(raw) : void 0;
1251
+ return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : void 0;
1252
+ } catch {
1253
+ return void 0;
1254
+ }
1255
+ }
1256
+ function write(key, names) {
1257
+ try {
1258
+ if (names) globalThis.localStorage?.setItem(key, JSON.stringify(names));
1259
+ else globalThis.localStorage?.removeItem(key);
1260
+ } catch {
1261
+ }
1262
+ }
1263
+ function useColumnPrefs(entity, max = 6) {
1264
+ const key = `${STORAGE_PREFIX}:${entity.id}`;
1265
+ const [selection, setSelection] = useState7(() => read(key));
1266
+ const available = useMemo5(() => listFields(entity), [entity]);
1267
+ const fallback = useMemo5(() => primaryListFields(entity, max), [entity, max]);
1268
+ const columns = useMemo5(() => {
1269
+ if (!selection) return fallback;
1270
+ const chosen = available.filter((f) => selection.includes(f.name));
1271
+ return chosen.length > 0 ? chosen : fallback;
1272
+ }, [selection, available, fallback]);
1273
+ const toggle = useCallback3(
1274
+ (name) => {
1275
+ setSelection((prev) => {
1276
+ const current = prev ?? columns.map((f) => f.name);
1277
+ const next = current.includes(name) ? current.filter((n) => n !== name) : [...current, name];
1278
+ write(key, next);
1279
+ return next;
1280
+ });
1281
+ },
1282
+ [key, columns]
1283
+ );
1284
+ const reset = useCallback3(() => {
1285
+ write(key, void 0);
1286
+ setSelection(void 0);
1287
+ }, [key]);
1288
+ const visibleNames = useMemo5(() => new Set(columns.map((f) => f.name)), [columns]);
1289
+ return {
1290
+ columns,
1291
+ available,
1292
+ visible: (name) => visibleNames.has(name),
1293
+ toggle,
1294
+ reset,
1295
+ customized: selection !== void 0
1296
+ };
557
1297
  }
558
1298
 
559
1299
  // src/components/RelationValue.tsx
560
- import { useEffect as useEffect3, useState as useState3 } from "react";
561
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1300
+ import { useEffect as useEffect4, useState as useState8 } from "react";
1301
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
562
1302
  var displayCache = /* @__PURE__ */ new Map();
563
1303
  function resolveDisplay(client, entityId, displayField, id) {
564
1304
  const key = `${entityId}/${id}`;
@@ -579,8 +1319,8 @@ function RelationValue({ field, value }) {
579
1319
  const client = useClient();
580
1320
  const target = useEntity(field.relationEntity);
581
1321
  const id = String(value);
582
- const [display, setDisplay] = useState3(void 0);
583
- useEffect3(() => {
1322
+ const [display, setDisplay] = useState8(void 0);
1323
+ useEffect4(() => {
584
1324
  let cancelled = false;
585
1325
  setDisplay(void 0);
586
1326
  if (!target || target.displayField === target.primaryKey) return;
@@ -591,24 +1331,17 @@ function RelationValue({ field, value }) {
591
1331
  cancelled = true;
592
1332
  };
593
1333
  }, [client, target, id]);
594
- return /* @__PURE__ */ jsxs3(
595
- "span",
596
- {
597
- title: id,
598
- className: "inline-flex max-w-56 items-center gap-1 truncate rounded border border-slate-200 bg-slate-50 px-1.5 py-0.5 text-xs font-medium text-slate-700",
599
- children: [
600
- target && /* @__PURE__ */ jsxs3("span", { className: "text-slate-400", children: [
601
- target.label.singular,
602
- ":"
603
- ] }),
604
- /* @__PURE__ */ jsx3("span", { className: "truncate", children: display ?? shortId(id) })
605
- ]
606
- }
607
- );
1334
+ return /* @__PURE__ */ jsxs8("span", { title: id, className: "mst-relation", children: [
1335
+ target && /* @__PURE__ */ jsxs8("span", { className: "mst-subtle", children: [
1336
+ target.label.singular,
1337
+ ":"
1338
+ ] }),
1339
+ /* @__PURE__ */ jsx9("span", { className: "mst-relation__label", children: display ?? shortId(id) })
1340
+ ] });
608
1341
  }
609
1342
 
610
1343
  // src/components/ObjectValue.tsx
611
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1344
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
612
1345
  var ISO_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
613
1346
  var MAX_DEPTH = 4;
614
1347
  function isPlainObject(value) {
@@ -616,49 +1349,40 @@ function isPlainObject(value) {
616
1349
  }
617
1350
  function Primitive({ value }) {
618
1351
  if (value === null || value === void 0) {
619
- return /* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: "\u2014" });
1352
+ return /* @__PURE__ */ jsx10("span", { className: "mst-subtle", children: "\u2014" });
620
1353
  }
621
1354
  if (typeof value === "boolean" || typeof value === "number") {
622
- return /* @__PURE__ */ jsx4("code", { className: "text-xs font-medium text-indigo-700", children: String(value) });
1355
+ return /* @__PURE__ */ jsx10("code", { className: "mst-mono", style: { color: "var(--maestro-brand)" }, children: String(value) });
623
1356
  }
624
1357
  const text = String(value);
625
1358
  if (ISO_DATETIME.test(text)) {
626
1359
  const d = new Date(text);
627
1360
  if (!Number.isNaN(d.getTime())) {
628
- return /* @__PURE__ */ jsx4("span", { className: "whitespace-nowrap text-slate-700", children: d.toLocaleString() });
1361
+ return /* @__PURE__ */ jsx10("span", { className: "mst-nums", children: d.toLocaleString() });
629
1362
  }
630
1363
  }
631
- return /* @__PURE__ */ jsx4("span", { className: "break-all text-slate-700", children: text });
1364
+ return /* @__PURE__ */ jsx10("span", { style: { overflowWrap: "anywhere" }, children: text });
632
1365
  }
633
1366
  function ObjectValue({ value, depth = 0 }) {
634
1367
  const t = useT();
635
1368
  if (!isPlainObject(value) && !Array.isArray(value)) {
636
- return /* @__PURE__ */ jsx4(Primitive, { value });
1369
+ return /* @__PURE__ */ jsx10(Primitive, { value });
637
1370
  }
638
1371
  if (depth >= MAX_DEPTH) {
639
- return /* @__PURE__ */ jsx4("code", { className: "block max-w-md overflow-x-auto rounded bg-slate-50 px-1.5 py-0.5 text-xs text-slate-700", children: JSON.stringify(value) });
1372
+ return /* @__PURE__ */ jsx10("code", { className: "mst-code mst-code--block", children: JSON.stringify(value) });
640
1373
  }
641
1374
  const entries = Array.isArray(value) ? value.map((v, i) => [String(i), v]) : Object.entries(value);
642
1375
  if (entries.length === 0) {
643
- return /* @__PURE__ */ jsx4("span", { className: "text-slate-400", children: t.emptyObject });
1376
+ return /* @__PURE__ */ jsx10("span", { className: "mst-subtle", children: t.emptyObject });
644
1377
  }
645
- return /* @__PURE__ */ jsx4(
646
- "dl",
647
- {
648
- className: depth === 0 ? "w-full divide-y divide-slate-100 rounded-md border border-slate-200 bg-slate-50/50" : "w-full divide-y divide-slate-100 border-l-2 border-slate-200 pl-2",
649
- children: entries.map(([key, entry]) => (
650
- // flex-wrap + basis: deeply nested values drop to their own full-width line instead of squeezing.
651
- /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-start gap-x-3 gap-y-1 px-3 py-1.5", children: [
652
- /* @__PURE__ */ jsx4("dt", { className: "w-36 shrink-0 truncate pt-px text-xs font-medium text-slate-500", title: key, children: key }),
653
- /* @__PURE__ */ jsx4("dd", { className: "min-w-0 flex-1 basis-56 text-sm", children: isPlainObject(entry) || Array.isArray(entry) ? /* @__PURE__ */ jsx4(ObjectValue, { value: entry, depth: depth + 1 }) : /* @__PURE__ */ jsx4(Primitive, { value: entry }) })
654
- ] }, key)
655
- ))
656
- }
657
- );
1378
+ return /* @__PURE__ */ jsx10("dl", { className: depth === 0 ? "mst-objectvalue mst-objectvalue--root" : "mst-objectvalue mst-objectvalue--nested", children: entries.map(([key, entry]) => /* @__PURE__ */ jsxs9("div", { className: "mst-objectvalue__row", children: [
1379
+ /* @__PURE__ */ jsx10("dt", { className: "mst-objectvalue__key", title: key, children: key }),
1380
+ /* @__PURE__ */ jsx10("dd", { className: "mst-objectvalue__val", children: isPlainObject(entry) || Array.isArray(entry) ? /* @__PURE__ */ jsx10(ObjectValue, { value: entry, depth: depth + 1 }) : /* @__PURE__ */ jsx10(Primitive, { value: entry }) })
1381
+ ] }, key)) });
658
1382
  }
659
1383
 
660
1384
  // src/components/FieldValue.tsx
661
- import { jsx as jsx5 } from "react/jsx-runtime";
1385
+ import { jsx as jsx11 } from "react/jsx-runtime";
662
1386
  function enumLabel(field, value) {
663
1387
  const opt = field.enumOptions?.find((o) => o.value === value);
664
1388
  return opt ? opt.label : String(value);
@@ -666,117 +1390,42 @@ function enumLabel(field, value) {
666
1390
  function FieldValue({ field, value, compact = false }) {
667
1391
  const t = useT();
668
1392
  if (value === null || value === void 0 || value === "") {
669
- return /* @__PURE__ */ jsx5("span", { className: "text-slate-400", children: "\u2014" });
1393
+ return /* @__PURE__ */ jsx11("span", { className: "mst-subtle", children: "\u2014" });
670
1394
  }
671
1395
  if (field.sensitive) {
672
- return /* @__PURE__ */ jsx5("span", { className: "text-slate-400", children: "\u2022\u2022\u2022\u2022\u2022\u2022" });
1396
+ return /* @__PURE__ */ jsx11("span", { className: "mst-subtle", children: "\u2022\u2022\u2022\u2022\u2022\u2022" });
673
1397
  }
674
1398
  switch (field.type) {
675
1399
  case "boolean":
676
- return value ? /* @__PURE__ */ jsx5(Badge, { tone: "green", children: t.yes }) : /* @__PURE__ */ jsx5(Badge, { tone: "slate", children: t.no });
1400
+ return value ? /* @__PURE__ */ jsx11(Badge, { tone: "ok", dot: true, children: t.yes }) : /* @__PURE__ */ jsx11(Badge, { dot: true, children: t.no });
677
1401
  case "enum":
678
- return /* @__PURE__ */ jsx5(Badge, { tone: "indigo", children: enumLabel(field, value) });
1402
+ return /* @__PURE__ */ jsx11(Badge, { tone: "brand", children: enumLabel(field, value) });
679
1403
  case "relation":
680
- return field.relationEntity ? /* @__PURE__ */ jsx5(RelationValue, { field, value }) : /* @__PURE__ */ jsx5("span", { className: "text-slate-700", children: String(value) });
1404
+ return field.relationEntity ? /* @__PURE__ */ jsx11(RelationValue, { field, value }) : /* @__PURE__ */ jsx11("span", { children: String(value) });
681
1405
  case "date":
682
1406
  case "datetime": {
683
1407
  const d = new Date(String(value));
684
- return /* @__PURE__ */ jsx5("span", { className: "whitespace-nowrap", children: Number.isNaN(d.getTime()) ? String(value) : d.toLocaleString() });
1408
+ return /* @__PURE__ */ jsx11("span", { className: "mst-nums", children: Number.isNaN(d.getTime()) ? String(value) : d.toLocaleString() });
685
1409
  }
1410
+ case "integer":
1411
+ case "float":
1412
+ case "number":
1413
+ case "decimal":
1414
+ case "currency":
1415
+ return /* @__PURE__ */ jsx11("span", { className: "mst-nums", children: String(value) });
686
1416
  case "json":
687
1417
  if (compact) {
688
- return /* @__PURE__ */ jsx5("code", { className: "block max-w-xs truncate overflow-x-auto rounded bg-slate-50 px-1.5 py-0.5 text-xs text-slate-700", children: JSON.stringify(value) });
1418
+ return /* @__PURE__ */ jsx11("code", { className: "mst-code", children: JSON.stringify(value) });
689
1419
  }
690
1420
  if (typeof value === "object") {
691
- return /* @__PURE__ */ jsx5(ObjectValue, { value });
1421
+ return /* @__PURE__ */ jsx11(ObjectValue, { value });
692
1422
  }
693
- return /* @__PURE__ */ jsx5("code", { className: "block max-w-md overflow-x-auto rounded bg-slate-50 px-1.5 py-0.5 text-xs text-slate-700", children: JSON.stringify(value) });
1423
+ return /* @__PURE__ */ jsx11("code", { className: "mst-code mst-code--block", children: JSON.stringify(value) });
694
1424
  default:
695
- return /* @__PURE__ */ jsx5("span", { className: `text-slate-700 ${compact ? "block max-w-64 truncate" : ""}`, children: String(value) });
1425
+ return /* @__PURE__ */ jsx11("span", { children: String(value) });
696
1426
  }
697
1427
  }
698
1428
 
699
- // src/components/menu.tsx
700
- import { useEffect as useEffect4, useLayoutEffect, useRef as useRef2, useState as useState4 } from "react";
701
- import { createPortal } from "react-dom";
702
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
703
- function PopMenu({ anchor, items, onClose }) {
704
- const ref = useRef2(null);
705
- const [pos, setPos] = useState4(anchor);
706
- useLayoutEffect(() => {
707
- const el = ref.current;
708
- if (!el) return;
709
- const rect = el.getBoundingClientRect();
710
- setPos({
711
- x: Math.max(4, Math.min(anchor.x, window.innerWidth - rect.width - 8)),
712
- y: Math.max(4, Math.min(anchor.y, window.innerHeight - rect.height - 8))
713
- });
714
- }, [anchor]);
715
- useEffect4(() => {
716
- const onDown = (e) => {
717
- if (ref.current && !ref.current.contains(e.target)) onClose();
718
- };
719
- const onKey = (e) => {
720
- if (e.key === "Escape") onClose();
721
- };
722
- document.addEventListener("mousedown", onDown);
723
- document.addEventListener("keydown", onKey);
724
- window.addEventListener("scroll", onClose, true);
725
- window.addEventListener("resize", onClose);
726
- return () => {
727
- document.removeEventListener("mousedown", onDown);
728
- document.removeEventListener("keydown", onKey);
729
- window.removeEventListener("scroll", onClose, true);
730
- window.removeEventListener("resize", onClose);
731
- };
732
- }, [onClose]);
733
- return createPortal(
734
- /* @__PURE__ */ jsx6(
735
- "div",
736
- {
737
- ref,
738
- role: "menu",
739
- style: { position: "fixed", left: pos.x, top: pos.y, zIndex: 50 },
740
- className: "min-w-44 rounded-md border border-slate-200 bg-white py-1 shadow-lg",
741
- children: items.map((item) => /* @__PURE__ */ jsx6(
742
- "button",
743
- {
744
- role: "menuitem",
745
- onClick: () => {
746
- onClose();
747
- item.onSelect();
748
- },
749
- className: `block w-full px-3 py-1.5 text-left text-sm ${item.danger ? "text-red-600 hover:bg-red-50" : "text-slate-700 hover:bg-slate-100"}`,
750
- children: item.label
751
- },
752
- item.id
753
- ))
754
- }
755
- ),
756
- document.body
757
- );
758
- }
759
- function MenuButton({ items, label }) {
760
- const [anchor, setAnchor] = useState4(void 0);
761
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
762
- /* @__PURE__ */ jsx6(
763
- "button",
764
- {
765
- type: "button",
766
- "aria-label": label,
767
- title: label,
768
- onClick: (e) => {
769
- const rect = e.currentTarget.getBoundingClientRect();
770
- setAnchor({ x: rect.right - 176, y: rect.bottom + 4 });
771
- },
772
- className: "rounded-md px-2 py-1 text-slate-500 hover:bg-slate-100 hover:text-slate-900",
773
- children: "\u22EE"
774
- }
775
- ),
776
- anchor && /* @__PURE__ */ jsx6(PopMenu, { anchor, items, onClose: () => setAnchor(void 0) })
777
- ] });
778
- }
779
-
780
1429
  // src/clipboard.ts
781
1430
  async function copyText(text) {
782
1431
  if (navigator.clipboard?.writeText) {
@@ -797,8 +1446,9 @@ async function copyText(text) {
797
1446
  }
798
1447
 
799
1448
  // src/components/EntityList.tsx
800
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1449
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
801
1450
  var PAGE_SIZE = 20;
1451
+ var ACTIONS_COLUMN_WIDTH = 44;
802
1452
  function filterFor(field, value) {
803
1453
  if (value === null || value === void 0) return { field: field.name, operator: "isNull" };
804
1454
  if (field.type === "boolean") return { field: field.name, operator: value ? "isTrue" : "isFalse" };
@@ -813,6 +1463,11 @@ function filterChipText(entity, filter) {
813
1463
  const value = filter.value ?? "";
814
1464
  return `${label} = ${value.length > 24 ? `${value.slice(0, 24)}\u2026` : value}`;
815
1465
  }
1466
+ function pageWindow(page, totalPages) {
1467
+ const size = Math.min(5, totalPages);
1468
+ const start = Math.max(1, Math.min(page - 2, totalPages - size + 1));
1469
+ return Array.from({ length: size }, (_, i) => start + i);
1470
+ }
816
1471
  function EntityList({
817
1472
  entity,
818
1473
  onOpen,
@@ -826,14 +1481,16 @@ function EntityList({
826
1481
  const t = useT();
827
1482
  const entities = useEntities();
828
1483
  const relatedLabel = (entityId) => entities.find((e) => e.id === entityId)?.label.singular ?? entityId;
829
- const columns = useMemo2(() => primaryListFields(entity), [entity]);
830
- const searchFields = useMemo2(() => entity.fields.filter((f) => f.searchable).map((f) => f.name), [entity]);
831
- const [page, setPage] = useState5(1);
832
- const [search, setSearch] = useState5("");
833
- const [sort, setSort] = useState5(void 0);
834
- const [filters, setFilters] = useState5(initialFilters ?? []);
835
- const [cellMenu, setCellMenu] = useState5(void 0);
836
- const [copied, setCopied] = useState5(false);
1484
+ const columnPrefs = useColumnPrefs(entity);
1485
+ const columns = columnPrefs.columns;
1486
+ const searchFields = useMemo6(() => entity.fields.filter((f) => f.searchable).map((f) => f.name), [entity]);
1487
+ const [page, setPage] = useState9(1);
1488
+ const [search, setSearch] = useState9("");
1489
+ const [sort, setSort] = useState9(void 0);
1490
+ const [filters, setFilters] = useState9(initialFilters ?? []);
1491
+ const [cellMenu, setCellMenu] = useState9(void 0);
1492
+ const [columnMenu, setColumnMenu] = useState9(void 0);
1493
+ const [copied, setCopied] = useState9(false);
837
1494
  const query = {
838
1495
  page,
839
1496
  pageSize: PAGE_SIZE,
@@ -870,17 +1527,31 @@ function EntityList({
870
1527
  };
871
1528
  const rowMenuItems = (record) => {
872
1529
  const id = String(record[entity.primaryKey]);
873
- const items = [{ id: "view", label: `\u{1F441} ${t.view}`, onSelect: () => onOpen(id) }];
874
- if (entity.capabilities.update) items.push({ id: "edit", label: `\u270F\uFE0F ${t.edit}`, onSelect: () => onEdit(id) });
1530
+ const items = [{ id: "view", label: t.view, icon: "eye", onSelect: () => onOpen(id) }];
1531
+ if (entity.capabilities.update) {
1532
+ items.push({ id: "edit", label: t.edit, icon: "pencil", onSelect: () => onEdit(id) });
1533
+ }
875
1534
  if (entity.capabilities.clone && entity.capabilities.create) {
876
- items.push({ id: "clone", label: `\u29C9 ${t.clone}`, onSelect: () => onClone(id) });
1535
+ items.push({ id: "clone", label: t.clone, icon: "clone", onSelect: () => onClone(id) });
877
1536
  }
878
1537
  items.push(
879
- { id: "copy-id", label: `\u2398 ${t.copyId}`, onSelect: () => void copy(id) },
880
- { id: "copy-object", label: `{} ${t.copyObject}`, onSelect: () => void copy(JSON.stringify(record, null, 2)) }
1538
+ { id: "copy-id", label: t.copyId, icon: "copy", separated: true, onSelect: () => void copy(id) },
1539
+ {
1540
+ id: "copy-object",
1541
+ label: t.copyObject,
1542
+ icon: "copy",
1543
+ onSelect: () => void copy(JSON.stringify(record, null, 2))
1544
+ }
881
1545
  );
882
1546
  if (entity.capabilities.delete) {
883
- items.push({ id: "remove", label: `\u{1F5D1} ${t.remove}`, danger: true, onSelect: () => void onDelete(id) });
1547
+ items.push({
1548
+ id: "remove",
1549
+ label: t.remove,
1550
+ icon: "trash",
1551
+ danger: true,
1552
+ separated: true,
1553
+ onSelect: () => void onDelete(id)
1554
+ });
884
1555
  }
885
1556
  return items;
886
1557
  };
@@ -890,130 +1561,195 @@ function EntityList({
890
1561
  const value = record[field.name];
891
1562
  const items = [];
892
1563
  const filter = filterFor(field, value);
893
- if (filter) items.push({ id: "filter", label: `\u25BC ${t.addToFilter}`, onSelect: () => addFilter(filter) });
894
- items.push({ id: "view", label: `\u{1F441} ${t.view}`, onSelect: () => onOpen(id) });
895
- if (entity.capabilities.update) items.push({ id: "edit", label: `\u270F\uFE0F ${t.edit}`, onSelect: () => onEdit(id) });
1564
+ if (filter) items.push({ id: "filter", label: t.addToFilter, icon: "filter", onSelect: () => addFilter(filter) });
1565
+ items.push({ id: "view", label: t.view, icon: "eye", onSelect: () => onOpen(id) });
1566
+ if (entity.capabilities.update) items.push({ id: "edit", label: t.edit, icon: "pencil", onSelect: () => onEdit(id) });
896
1567
  if (field.type === "relation" && field.relationEntity && value !== null && value !== void 0) {
897
1568
  const targetId = field.relationEntity;
898
1569
  items.push({
899
1570
  id: "open-related",
900
- label: `\u2197 ${t.openRelated(relatedLabel(targetId))}`,
1571
+ label: t.openRelated(relatedLabel(targetId)),
1572
+ icon: "arrowUpRight",
901
1573
  onSelect: () => onNavigate(targetId, String(value))
902
1574
  });
903
1575
  }
904
1576
  if (value !== null && value !== void 0) {
905
1577
  items.push({
906
1578
  id: "copy-value",
907
- label: `\u2398 ${t.copyValue}`,
1579
+ label: t.copyValue,
1580
+ icon: "copy",
1581
+ separated: true,
908
1582
  onSelect: () => void copy(typeof value === "object" ? JSON.stringify(value) : String(value))
909
1583
  });
910
1584
  }
911
1585
  return items;
912
1586
  };
1587
+ const columnMenuItems = [
1588
+ ...columnPrefs.available.map((field) => ({
1589
+ id: `col:${field.name}`,
1590
+ label: field.label,
1591
+ icon: columnPrefs.visible(field.name) ? "check" : void 0,
1592
+ onSelect: () => columnPrefs.toggle(field.name)
1593
+ })),
1594
+ ...columnPrefs.customized ? [{ id: "reset", label: t.resetColumns, icon: "rows", separated: true, onSelect: columnPrefs.reset }] : []
1595
+ ];
913
1596
  const totalPages = data?.totalPages ?? (data ? Math.max(1, Math.ceil(data.total / PAGE_SIZE)) : 1);
914
- return /* @__PURE__ */ jsxs6("section", { className: "space-y-4", children: [
915
- /* @__PURE__ */ jsxs6("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [
916
- /* @__PURE__ */ jsxs6("div", { children: [
917
- /* @__PURE__ */ jsx7("h1", { className: "text-lg font-semibold text-slate-900", children: entity.label.plural }),
918
- data && /* @__PURE__ */ jsx7("p", { className: "text-sm text-slate-500", children: t.records(data.total) })
1597
+ const firstRow = data && data.total > 0 ? (page - 1) * PAGE_SIZE + 1 : 0;
1598
+ const lastRow = data ? Math.min(page * PAGE_SIZE, data.total) : 0;
1599
+ return /* @__PURE__ */ jsxs10("section", { className: "mst-page mst-page--fill", children: [
1600
+ /* @__PURE__ */ jsxs10("div", { className: "mst-pagehead", children: [
1601
+ /* @__PURE__ */ jsxs10("div", { children: [
1602
+ /* @__PURE__ */ jsx12("h1", { className: "mst-pagehead__title", children: entity.label.plural }),
1603
+ /* @__PURE__ */ jsxs10("p", { className: "mst-pagehead__meta", children: [
1604
+ data && /* @__PURE__ */ jsx12("span", { className: "mst-nums", children: t.records(data.total) }),
1605
+ /* @__PURE__ */ jsxs10("span", { className: "mst-source", children: [
1606
+ /* @__PURE__ */ jsx12(Icon, { name: "database", size: 11 }),
1607
+ /* @__PURE__ */ jsxs10("span", { children: [
1608
+ entity.datasource,
1609
+ " \xB7 ",
1610
+ entity.table
1611
+ ] })
1612
+ ] })
1613
+ ] })
919
1614
  ] }),
920
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
921
- searchFields.length > 0 && /* @__PURE__ */ jsx7(
1615
+ /* @__PURE__ */ jsxs10("div", { className: "mst-pagehead__actions", children: [
1616
+ searchFields.length > 0 && /* @__PURE__ */ jsx12(
922
1617
  "input",
923
1618
  {
924
1619
  type: "search",
1620
+ className: "mst-input mst-input--inline",
925
1621
  placeholder: t.searchPlaceholder(entity.label.plural),
926
1622
  value: search,
927
1623
  onChange: (e) => {
928
1624
  setSearch(e.target.value);
929
1625
  setPage(1);
1626
+ }
1627
+ }
1628
+ ),
1629
+ /* @__PURE__ */ jsxs10(
1630
+ Button,
1631
+ {
1632
+ icon: "columns",
1633
+ onClick: (e) => {
1634
+ const rect = e.currentTarget.getBoundingClientRect();
1635
+ setColumnMenu({ x: rect.right - 200, y: rect.bottom + 6 });
930
1636
  },
931
- className: "rounded-md border border-slate-300 px-3 py-1.5 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
1637
+ children: [
1638
+ t.columns,
1639
+ " ",
1640
+ /* @__PURE__ */ jsx12("span", { className: "mst-subtle mst-nums", children: t.columnsCount(columns.length, columnPrefs.available.length) })
1641
+ ]
932
1642
  }
933
1643
  ),
934
- entity.capabilities.create && /* @__PURE__ */ jsx7(Button, { variant: "primary", onClick: onCreate, children: t.newRecord })
1644
+ entity.capabilities.create && /* @__PURE__ */ jsx12(Button, { variant: "primary", icon: "plus", onClick: onCreate, children: t.newRecord })
935
1645
  ] })
936
1646
  ] }),
937
- filters.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
938
- /* @__PURE__ */ jsxs6("span", { className: "text-slate-500", children: [
939
- t.filters,
940
- ":"
941
- ] }),
942
- filters.map((f) => /* @__PURE__ */ jsxs6(
1647
+ filters.length > 0 && /* @__PURE__ */ jsxs10("div", { className: "mst-chips", children: [
1648
+ filters.map((f) => /* @__PURE__ */ jsxs10(
943
1649
  "button",
944
1650
  {
1651
+ type: "button",
1652
+ className: "mst-chip",
1653
+ title: f.value,
945
1654
  onClick: () => {
946
1655
  setFilters((prev) => prev.filter((p) => p.field !== f.field));
947
1656
  setPage(1);
948
1657
  },
949
- title: f.value,
950
- className: "inline-flex items-center gap-1 rounded-full bg-indigo-50 px-2.5 py-0.5 text-xs font-medium text-indigo-700 hover:bg-indigo-100",
951
1658
  children: [
952
1659
  filterChipText(entity, f),
953
- /* @__PURE__ */ jsx7("span", { "aria-hidden": true, children: "\xD7" })
1660
+ /* @__PURE__ */ jsx12(Icon, { name: "close", size: 11 })
954
1661
  ]
955
1662
  },
956
1663
  f.field
957
1664
  )),
958
- /* @__PURE__ */ jsx7(
1665
+ /* @__PURE__ */ jsx12(
959
1666
  "button",
960
1667
  {
1668
+ type: "button",
1669
+ className: "mst-btn mst-btn--ghost",
961
1670
  onClick: () => {
962
1671
  setFilters([]);
963
1672
  setPage(1);
964
1673
  },
965
- className: "text-xs text-slate-500 underline hover:text-slate-700",
966
1674
  children: t.clearFilters
967
1675
  }
968
1676
  )
969
1677
  ] }),
970
- error && /* @__PURE__ */ jsx7(ErrorBanner, { error }),
971
- loading && !data ? /* @__PURE__ */ jsx7(Spinner, { label: t.loading }) : data && data.records.length === 0 ? /* @__PURE__ */ jsx7(EmptyState, { title: t.noRecords, hint: search || filters.length ? t.tryDifferentSearch : void 0 }) : /* @__PURE__ */ jsx7("div", { className: "overflow-x-auto rounded-lg border border-slate-200", children: /* @__PURE__ */ jsxs6("table", { className: "min-w-full divide-y divide-slate-200 text-sm", children: [
972
- /* @__PURE__ */ jsx7("thead", { className: "bg-slate-50", children: /* @__PURE__ */ jsxs6("tr", { children: [
973
- columns.map((f) => /* @__PURE__ */ jsxs6(
974
- "th",
975
- {
976
- onClick: () => f.sortable && toggleSort(f.name),
977
- className: `px-3 py-2 text-left font-medium text-slate-600 ${f.sortable ? "cursor-pointer select-none hover:text-slate-900" : ""}`,
978
- children: [
979
- f.label,
980
- sort?.field === f.name && (sort.direction === "asc" ? " \u25B2" : " \u25BC")
981
- ]
982
- },
983
- f.name
984
- )),
985
- /* @__PURE__ */ jsx7("th", { className: "px-3 py-2 text-right font-medium text-slate-600", children: t.actions })
986
- ] }) }),
987
- /* @__PURE__ */ jsx7("tbody", { className: "divide-y divide-slate-100 bg-white", children: data?.records.map((record, i) => {
988
- const id = String(record[entity.primaryKey]);
989
- return /* @__PURE__ */ jsxs6("tr", { className: "hover:bg-slate-50", children: [
990
- columns.map((f) => /* @__PURE__ */ jsx7(
991
- "td",
1678
+ error && /* @__PURE__ */ jsx12(ErrorBanner, { error }),
1679
+ loading && !data ? /* @__PURE__ */ jsx12(Spinner, { label: t.loading }) : data && data.records.length === 0 ? /* @__PURE__ */ jsx12(EmptyState, { title: t.noRecords, hint: search || filters.length ? t.tryDifferentSearch : void 0 }) : /* @__PURE__ */ jsxs10("div", { className: "mst-card mst-tablewrap", children: [
1680
+ /* @__PURE__ */ jsx12("div", { className: "mst-tablescroll", children: /* @__PURE__ */ jsxs10("table", { className: "mst-table", children: [
1681
+ /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsxs10("tr", { children: [
1682
+ columns.map((f) => /* @__PURE__ */ jsx12(
1683
+ "th",
992
1684
  {
993
- className: "cursor-pointer px-3 py-2 align-top",
994
- onClick: (e) => setCellMenu({ anchor: { x: e.clientX, y: e.clientY }, field: f, record }),
995
- onDoubleClick: () => onOpen(id),
996
- children: /* @__PURE__ */ jsx7(FieldValue, { field: f, value: record[f.name], compact: true })
1685
+ style: { width: f.list.width, textAlign: f.list.align },
1686
+ "aria-sort": sort?.field === f.name ? sort.direction === "asc" ? "ascending" : "descending" : "none",
1687
+ children: f.sortable ? /* @__PURE__ */ jsxs10("button", { type: "button", className: "mst-th__button", onClick: () => toggleSort(f.name), children: [
1688
+ /* @__PURE__ */ jsx12("span", { className: "mst-th__label", children: f.label }),
1689
+ /* @__PURE__ */ jsx12(
1690
+ Icon,
1691
+ {
1692
+ name: sort?.field === f.name ? sort.direction === "asc" ? "sortAsc" : "sortDesc" : "sort",
1693
+ size: 11
1694
+ }
1695
+ )
1696
+ ] }) : /* @__PURE__ */ jsx12("span", { className: "mst-th__label", children: f.label })
997
1697
  },
998
1698
  f.name
999
1699
  )),
1000
- /* @__PURE__ */ jsx7("td", { className: "whitespace-nowrap px-3 py-2 text-right", children: /* @__PURE__ */ jsx7(MenuButton, { label: t.actions, items: rowMenuItems(record) }) })
1001
- ] }, id || i);
1002
- }) })
1003
- ] }) }),
1004
- cellMenu && /* @__PURE__ */ jsx7(PopMenu, { anchor: cellMenu.anchor, items: cellMenuItems(cellMenu), onClose: () => setCellMenu(void 0) }),
1005
- copied && /* @__PURE__ */ jsx7("div", { className: "fixed bottom-4 right-4 z-50 rounded-md bg-slate-900 px-3 py-1.5 text-sm text-white shadow-lg", children: t.copied }),
1006
- data && totalPages > 1 && /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-2 text-sm", children: [
1007
- /* @__PURE__ */ jsx7(Button, { variant: "secondary", disabled: page <= 1, onClick: () => setPage((p) => p - 1), children: t.previous }),
1008
- /* @__PURE__ */ jsx7("span", { className: "text-slate-500", children: t.page(page, totalPages) }),
1009
- /* @__PURE__ */ jsx7(Button, { variant: "secondary", disabled: page >= totalPages, onClick: () => setPage((p) => p + 1), children: t.next })
1010
- ] })
1700
+ /* @__PURE__ */ jsx12("th", { style: { width: ACTIONS_COLUMN_WIDTH }, children: /* @__PURE__ */ jsx12("span", { className: "mst-sr-only", children: t.actions }) })
1701
+ ] }) }),
1702
+ /* @__PURE__ */ jsx12("tbody", { children: data?.records.map((record, i) => {
1703
+ const id = String(record[entity.primaryKey]);
1704
+ return /* @__PURE__ */ jsxs10("tr", { children: [
1705
+ columns.map((f) => /* @__PURE__ */ jsx12(
1706
+ "td",
1707
+ {
1708
+ className: "mst-table__cell",
1709
+ style: { textAlign: f.list.align },
1710
+ title: f.label,
1711
+ onClick: (e) => setCellMenu({ anchor: { x: e.clientX, y: e.clientY }, field: f, record }),
1712
+ onDoubleClick: () => onOpen(id),
1713
+ children: /* @__PURE__ */ jsx12(FieldValue, { field: f, value: record[f.name], compact: true })
1714
+ },
1715
+ f.name
1716
+ )),
1717
+ /* @__PURE__ */ jsx12("td", { className: "mst-td--actions", children: /* @__PURE__ */ jsx12(MenuButton, { label: t.actions, items: rowMenuItems(record) }) })
1718
+ ] }, id || i);
1719
+ }) })
1720
+ ] }) }),
1721
+ data && /* @__PURE__ */ jsxs10("div", { className: "mst-pagination", children: [
1722
+ /* @__PURE__ */ jsxs10("span", { className: "mst-nums", children: [
1723
+ firstRow,
1724
+ "\u2013",
1725
+ lastRow
1726
+ ] }),
1727
+ /* @__PURE__ */ jsx12("span", { children: t.records(data.total) }),
1728
+ totalPages > 1 && /* @__PURE__ */ jsxs10("span", { className: "mst-pagination__nums", children: [
1729
+ /* @__PURE__ */ jsx12("button", { type: "button", "aria-label": t.previous, disabled: page <= 1, onClick: () => setPage((p) => p - 1), children: /* @__PURE__ */ jsx12(Icon, { name: "chevronLeft", size: 13 }) }),
1730
+ pageWindow(page, totalPages).map((p) => /* @__PURE__ */ jsx12("button", { type: "button", "aria-current": p === page, onClick: () => setPage(p), children: p }, p)),
1731
+ /* @__PURE__ */ jsx12(
1732
+ "button",
1733
+ {
1734
+ type: "button",
1735
+ "aria-label": t.next,
1736
+ disabled: page >= totalPages,
1737
+ onClick: () => setPage((p) => p + 1),
1738
+ children: /* @__PURE__ */ jsx12(Icon, { name: "chevronRight", size: 13 })
1739
+ }
1740
+ )
1741
+ ] })
1742
+ ] })
1743
+ ] }),
1744
+ cellMenu && /* @__PURE__ */ jsx12(PopMenu, { anchor: cellMenu.anchor, items: cellMenuItems(cellMenu), onClose: () => setCellMenu(void 0) }),
1745
+ columnMenu && /* @__PURE__ */ jsx12(PopMenu, { anchor: columnMenu, items: columnMenuItems, onClose: () => setColumnMenu(void 0) }),
1746
+ copied && /* @__PURE__ */ jsx12(Toast, { children: t.copied })
1011
1747
  ] });
1012
1748
  }
1013
1749
 
1014
1750
  // src/components/EntityDetail.tsx
1015
- import { useState as useState6 } from "react";
1016
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1751
+ import { useState as useState10 } from "react";
1752
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1017
1753
  function EntityDetail({
1018
1754
  entity,
1019
1755
  id,
@@ -1028,7 +1764,7 @@ function EntityDetail({
1028
1764
  const entities = useEntities();
1029
1765
  const { data, error, loading } = useEntityRecord(entity.id, id);
1030
1766
  const fields = detailFields(entity);
1031
- const [copied, setCopied] = useState6(false);
1767
+ const [copied, setCopied] = useState10(false);
1032
1768
  const copy = async (text) => {
1033
1769
  await copyText(text);
1034
1770
  setCopied(true);
@@ -1041,71 +1777,72 @@ function EntityDetail({
1041
1777
  };
1042
1778
  const menuItems = [];
1043
1779
  if (entity.capabilities.clone && entity.capabilities.create) {
1044
- menuItems.push({ id: "clone", label: `\u29C9 ${t.clone}`, onSelect: onClone });
1780
+ menuItems.push({ id: "clone", label: t.clone, icon: "clone", onSelect: onClone });
1045
1781
  }
1046
- menuItems.push({ id: "copy-id", label: `\u2398 ${t.copyId}`, onSelect: () => void copy(id) });
1782
+ menuItems.push({ id: "copy-id", label: t.copyId, icon: "copy", onSelect: () => void copy(id) });
1047
1783
  if (data) {
1048
1784
  menuItems.push({
1049
1785
  id: "copy-object",
1050
- label: `{} ${t.copyObject}`,
1786
+ label: t.copyObject,
1787
+ icon: "copy",
1051
1788
  onSelect: () => void copy(JSON.stringify(data, null, 2))
1052
1789
  });
1053
1790
  }
1054
1791
  if (entity.capabilities.delete) {
1055
- menuItems.push({ id: "remove", label: `\u{1F5D1} ${t.remove}`, danger: true, onSelect: () => void onDelete() });
1792
+ menuItems.push({
1793
+ id: "remove",
1794
+ label: t.remove,
1795
+ icon: "trash",
1796
+ danger: true,
1797
+ separated: true,
1798
+ onSelect: () => void onDelete()
1799
+ });
1056
1800
  }
1057
- return /* @__PURE__ */ jsxs7("section", { className: "space-y-4", children: [
1058
- /* @__PURE__ */ jsxs7("header", { className: "flex items-center justify-between gap-3", children: [
1059
- /* @__PURE__ */ jsxs7("div", { children: [
1060
- /* @__PURE__ */ jsxs7("button", { onClick: onBack, className: "text-sm text-indigo-600 hover:underline", children: [
1061
- "\u2190 ",
1801
+ return /* @__PURE__ */ jsxs11("section", { className: "mst-page", children: [
1802
+ /* @__PURE__ */ jsxs11("div", { className: "mst-pagehead", children: [
1803
+ /* @__PURE__ */ jsxs11("div", { children: [
1804
+ /* @__PURE__ */ jsxs11("button", { type: "button", className: "mst-backlink", onClick: onBack, children: [
1805
+ /* @__PURE__ */ jsx13(Icon, { name: "arrowLeft", size: 12 }),
1062
1806
  entity.label.plural
1063
1807
  ] }),
1064
- /* @__PURE__ */ jsxs7("h1", { className: "text-lg font-semibold text-slate-900", children: [
1808
+ /* @__PURE__ */ jsxs11("h1", { className: "mst-pagehead__title", children: [
1065
1809
  entity.label.singular,
1066
1810
  " ",
1067
1811
  data ? String(data[entity.displayField] ?? id) : ""
1068
1812
  ] })
1069
1813
  ] }),
1070
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
1071
- entity.capabilities.update && /* @__PURE__ */ jsx8(Button, { variant: "primary", onClick: onEdit, children: t.edit }),
1072
- /* @__PURE__ */ jsx8(MenuButton, { label: t.actions, items: menuItems })
1814
+ /* @__PURE__ */ jsxs11("div", { className: "mst-pagehead__actions", children: [
1815
+ entity.capabilities.update && /* @__PURE__ */ jsx13(Button, { variant: "primary", icon: "pencil", onClick: onEdit, children: t.edit }),
1816
+ /* @__PURE__ */ jsx13(MenuButton, { label: t.actions, items: menuItems })
1073
1817
  ] })
1074
1818
  ] }),
1075
- error && /* @__PURE__ */ jsx8(ErrorBanner, { error }),
1076
- loading && !data ? /* @__PURE__ */ jsx8(Spinner, {}) : data && /* @__PURE__ */ jsx8("dl", { className: "divide-y divide-slate-100 rounded-lg border border-slate-200 bg-white", children: fields.map((f) => {
1819
+ error && /* @__PURE__ */ jsx13(ErrorBanner, { error }),
1820
+ loading && !data ? /* @__PURE__ */ jsx13(Spinner, {}) : data && /* @__PURE__ */ jsx13("dl", { className: "mst-fields", children: fields.map((f) => {
1077
1821
  const value = data[f.name];
1078
1822
  const isRelation = f.type === "relation" && f.relationEntity && value !== null && value !== void 0;
1079
1823
  const target = isRelation ? entities.find((e) => e.id === f.relationEntity) : void 0;
1080
- return /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-3 gap-4 px-4 py-3", children: [
1081
- /* @__PURE__ */ jsx8("dt", { className: "text-sm font-medium text-slate-500", children: f.label }),
1082
- /* @__PURE__ */ jsxs7("dd", { className: "col-span-2 flex items-center gap-2 text-sm", children: [
1083
- /* @__PURE__ */ jsx8(FieldValue, { field: f, value }),
1084
- isRelation && /* @__PURE__ */ jsxs7(
1085
- "button",
1086
- {
1087
- onClick: () => onNavigate(f.relationEntity, String(value)),
1088
- className: "text-xs text-indigo-600 hover:underline",
1089
- children: [
1090
- t.openRelated(target?.label.singular ?? f.relationEntity),
1091
- " \u2197"
1092
- ]
1093
- }
1094
- )
1824
+ return /* @__PURE__ */ jsxs11("div", { className: "mst-fields__row", children: [
1825
+ /* @__PURE__ */ jsx13("dt", { className: "mst-fields__key", children: f.label }),
1826
+ /* @__PURE__ */ jsxs11("dd", { className: "mst-fields__value", children: [
1827
+ /* @__PURE__ */ jsx13(FieldValue, { field: f, value }),
1828
+ isRelation && /* @__PURE__ */ jsxs11("button", { type: "button", className: "mst-backlink", onClick: () => onNavigate(f.relationEntity, String(value)), children: [
1829
+ t.openRelated(target?.label.singular ?? f.relationEntity),
1830
+ /* @__PURE__ */ jsx13(Icon, { name: "arrowUpRight", size: 11 })
1831
+ ] })
1095
1832
  ] })
1096
1833
  ] }, f.name);
1097
1834
  }) }),
1098
- copied && /* @__PURE__ */ jsx8("div", { className: "fixed bottom-4 right-4 z-50 rounded-md bg-slate-900 px-3 py-1.5 text-sm text-white shadow-lg", children: t.copied })
1835
+ copied && /* @__PURE__ */ jsx13(Toast, { children: t.copied })
1099
1836
  ] });
1100
1837
  }
1101
1838
 
1102
1839
  // src/components/EntityForm.tsx
1103
- import { useEffect as useEffect6, useState as useState8 } from "react";
1840
+ import { useEffect as useEffect6, useState as useState12 } from "react";
1104
1841
 
1105
1842
  // src/components/JsonInput.tsx
1106
- import { useEffect as useEffect5, useRef as useRef3, useState as useState7 } from "react";
1107
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1108
- var leafInputClass = "w-full rounded-md border border-slate-300 px-2.5 py-1 text-sm text-slate-800 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:bg-slate-100";
1843
+ import { useEffect as useEffect5, useRef as useRef3, useState as useState11 } from "react";
1844
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1845
+ var leafInputClass = "mst-input";
1109
1846
  var MAX_DEPTH2 = 3;
1110
1847
  function RawJsonArea({
1111
1848
  id,
@@ -1116,8 +1853,8 @@ function RawJsonArea({
1116
1853
  placeholder
1117
1854
  }) {
1118
1855
  const t = useT();
1119
- const [text, setText] = useState7(() => value === null || value === void 0 ? "" : JSON.stringify(value, null, 2));
1120
- const [invalid, setInvalid] = useState7(false);
1856
+ const [text, setText] = useState11(() => value === null || value === void 0 ? "" : JSON.stringify(value, null, 2));
1857
+ const [invalid, setInvalid] = useState11(false);
1121
1858
  const lastEmitted = useRef3(value);
1122
1859
  useEffect5(() => {
1123
1860
  if (value !== lastEmitted.current) {
@@ -1143,8 +1880,8 @@ function RawJsonArea({
1143
1880
  setInvalid(true);
1144
1881
  }
1145
1882
  };
1146
- return /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
1147
- /* @__PURE__ */ jsx9(
1883
+ return /* @__PURE__ */ jsxs12("div", { className: "mst-field", children: [
1884
+ /* @__PURE__ */ jsx14(
1148
1885
  "textarea",
1149
1886
  {
1150
1887
  id,
@@ -1154,10 +1891,10 @@ function RawJsonArea({
1154
1891
  disabled,
1155
1892
  onChange: (e) => onText(e.target.value),
1156
1893
  "aria-invalid": invalid || void 0,
1157
- className: `${leafInputClass} font-mono ${invalid ? "border-amber-400 focus:border-amber-500 focus:ring-amber-500" : ""}`
1894
+ className: `mst-textarea mst-mono${invalid ? " mst-input--invalid" : ""}`
1158
1895
  }
1159
1896
  ),
1160
- invalid && /* @__PURE__ */ jsx9("p", { role: "alert", className: "text-xs text-amber-600", children: t.jsonInvalid })
1897
+ invalid && /* @__PURE__ */ jsx14("p", { role: "alert", className: "mst-help mst-help--warn", children: t.jsonInvalid })
1161
1898
  ] });
1162
1899
  }
1163
1900
  function LeafEditor({
@@ -1168,7 +1905,7 @@ function LeafEditor({
1168
1905
  depth
1169
1906
  }) {
1170
1907
  if (typeof value === "boolean") {
1171
- return /* @__PURE__ */ jsx9(
1908
+ return /* @__PURE__ */ jsx14(
1172
1909
  "input",
1173
1910
  {
1174
1911
  id,
@@ -1176,12 +1913,12 @@ function LeafEditor({
1176
1913
  checked: value,
1177
1914
  disabled,
1178
1915
  onChange: (e) => onChange(e.target.checked),
1179
- className: "mt-1 h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500"
1916
+ className: "mst-checkbox"
1180
1917
  }
1181
1918
  );
1182
1919
  }
1183
1920
  if (typeof value === "number") {
1184
- return /* @__PURE__ */ jsx9(
1921
+ return /* @__PURE__ */ jsx14(
1185
1922
  "input",
1186
1923
  {
1187
1924
  id,
@@ -1195,7 +1932,7 @@ function LeafEditor({
1195
1932
  );
1196
1933
  }
1197
1934
  if (typeof value === "string") {
1198
- return /* @__PURE__ */ jsx9(
1935
+ return /* @__PURE__ */ jsx14(
1199
1936
  "input",
1200
1937
  {
1201
1938
  id,
@@ -1208,7 +1945,7 @@ function LeafEditor({
1208
1945
  );
1209
1946
  }
1210
1947
  if (value === null || value === void 0) {
1211
- return /* @__PURE__ */ jsx9(
1948
+ return /* @__PURE__ */ jsx14(
1212
1949
  "input",
1213
1950
  {
1214
1951
  id,
@@ -1222,12 +1959,12 @@ function LeafEditor({
1222
1959
  );
1223
1960
  }
1224
1961
  if (isPlainObject(value) && depth < MAX_DEPTH2) {
1225
- return /* @__PURE__ */ jsx9("div", { className: "rounded-md border border-slate-200", children: /* @__PURE__ */ jsx9(ObjectEditor, { idPrefix: id, value, onChange, disabled, depth: depth + 1 }) });
1962
+ return /* @__PURE__ */ jsx14("div", { className: "mst-json__nest", children: /* @__PURE__ */ jsx14(ObjectEditor, { idPrefix: id, value, onChange, disabled, depth: depth + 1 }) });
1226
1963
  }
1227
1964
  if (Array.isArray(value) && depth < MAX_DEPTH2) {
1228
- return /* @__PURE__ */ jsx9("div", { className: "rounded-md border border-slate-200", children: /* @__PURE__ */ jsx9(ArrayEditor, { idPrefix: id, value, onChange, disabled, depth: depth + 1 }) });
1965
+ return /* @__PURE__ */ jsx14("div", { className: "mst-json__nest", children: /* @__PURE__ */ jsx14(ArrayEditor, { idPrefix: id, value, onChange, disabled, depth: depth + 1 }) });
1229
1966
  }
1230
- return /* @__PURE__ */ jsx9(RawJsonArea, { id, value, onChange, disabled, rows: 3 });
1967
+ return /* @__PURE__ */ jsx14(RawJsonArea, { id, value, onChange, disabled, rows: 3 });
1231
1968
  }
1232
1969
  function ArrayEditor({
1233
1970
  idPrefix,
@@ -1244,27 +1981,27 @@ function ArrayEditor({
1244
1981
  const template = last === void 0 || last === null ? null : JSON.parse(JSON.stringify(last));
1245
1982
  onChange([...value, template]);
1246
1983
  };
1247
- return /* @__PURE__ */ jsxs8("div", { className: "divide-y divide-slate-100", children: [
1984
+ return /* @__PURE__ */ jsxs12("div", { className: "mst-json__rows", children: [
1248
1985
  value.map((item, i) => {
1249
1986
  const id = `${idPrefix}-${i}`;
1250
- return /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-start gap-x-3 gap-y-1.5 px-3 py-2", children: [
1251
- /* @__PURE__ */ jsx9("label", { htmlFor: id, className: "w-8 shrink-0 pt-1.5 text-right text-xs font-medium text-slate-400", children: i }),
1252
- !disabled && /* @__PURE__ */ jsx9(
1987
+ return /* @__PURE__ */ jsxs12("div", { className: "mst-json__row", children: [
1988
+ /* @__PURE__ */ jsx14("label", { htmlFor: id, className: "mst-json__index", children: i }),
1989
+ !disabled && /* @__PURE__ */ jsx14(
1253
1990
  "button",
1254
1991
  {
1255
1992
  type: "button",
1256
1993
  title: t.removeItem,
1257
1994
  "aria-label": `${t.removeItem} ${i}`,
1258
1995
  onClick: () => removeAt(i),
1259
- className: "shrink-0 pt-1.5 text-xs text-slate-400 transition-colors hover:text-red-600",
1996
+ className: "mst-json__remove",
1260
1997
  children: "\u2715"
1261
1998
  }
1262
1999
  ),
1263
- /* @__PURE__ */ jsx9("div", { className: "min-w-0 flex-1 basis-64", children: /* @__PURE__ */ jsx9(LeafEditor, { id, value: item, onChange: (next) => setAt(i, next), disabled, depth }) })
2000
+ /* @__PURE__ */ jsx14("div", { className: "mst-json__field", children: /* @__PURE__ */ jsx14(LeafEditor, { id, value: item, onChange: (next) => setAt(i, next), disabled, depth }) })
1264
2001
  ] }, i);
1265
2002
  }),
1266
- value.length === 0 && /* @__PURE__ */ jsx9("p", { className: "px-3 py-2 text-xs text-slate-400", children: t.emptyObject }),
1267
- !disabled && /* @__PURE__ */ jsx9("div", { className: "px-3 py-1.5", children: /* @__PURE__ */ jsxs8("button", { type: "button", onClick: append, className: "text-xs font-medium text-indigo-600 hover:underline", children: [
2003
+ value.length === 0 && /* @__PURE__ */ jsx14("p", { className: "mst-json__empty", children: t.emptyObject }),
2004
+ !disabled && /* @__PURE__ */ jsx14("div", { className: "mst-json__addrow", children: /* @__PURE__ */ jsxs12("button", { type: "button", onClick: append, className: "mst-backlink", children: [
1268
2005
  "+ ",
1269
2006
  t.addItem
1270
2007
  ] }) })
@@ -1280,16 +2017,16 @@ function ObjectEditor({
1280
2017
  const t = useT();
1281
2018
  const entries = Object.entries(value);
1282
2019
  if (entries.length === 0) {
1283
- return /* @__PURE__ */ jsx9("p", { className: "px-3 py-2 text-xs text-slate-400", children: t.emptyObject });
2020
+ return /* @__PURE__ */ jsx14("p", { className: "mst-json__empty", children: t.emptyObject });
1284
2021
  }
1285
- return /* @__PURE__ */ jsx9("div", { className: "divide-y divide-slate-100", children: entries.map(([key, entry]) => {
2022
+ return /* @__PURE__ */ jsx14("div", { className: "mst-json__rows", children: entries.map(([key, entry]) => {
1286
2023
  const id = `${idPrefix}-${key}`;
1287
2024
  return (
1288
2025
  // flex-wrap + basis: when the label column leaves less than ~16rem for the control (deeply
1289
2026
  // nested groups), the control wraps to its own full-width line instead of squeezing.
1290
- /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-start gap-x-3 gap-y-1.5 px-3 py-2", children: [
1291
- /* @__PURE__ */ jsx9("label", { htmlFor: id, className: "w-36 shrink-0 truncate pt-1.5 text-xs font-medium text-slate-500", title: key, children: key }),
1292
- /* @__PURE__ */ jsx9("div", { className: "min-w-0 flex-1 basis-64", children: /* @__PURE__ */ jsx9(
2027
+ /* @__PURE__ */ jsxs12("div", { className: "mst-json__row", children: [
2028
+ /* @__PURE__ */ jsx14("label", { htmlFor: id, className: "mst-json__key", title: key, children: key }),
2029
+ /* @__PURE__ */ jsx14("div", { className: "mst-json__field", children: /* @__PURE__ */ jsx14(
1293
2030
  LeafEditor,
1294
2031
  {
1295
2032
  id,
@@ -1310,8 +2047,8 @@ function JsonInput({
1310
2047
  disabled
1311
2048
  }) {
1312
2049
  const t = useT();
1313
- const [mode, setMode] = useState7(() => isPlainObject(value) ? "form" : "raw");
1314
- const [modeTouched, setModeTouched] = useState7(false);
2050
+ const [mode, setMode] = useState11(() => isPlainObject(value) ? "form" : "raw");
2051
+ const [modeTouched, setModeTouched] = useState11(false);
1315
2052
  const structured = isPlainObject(value) || Array.isArray(value);
1316
2053
  useEffect5(() => {
1317
2054
  if (!modeTouched && structured) setMode("form");
@@ -1321,10 +2058,10 @@ function JsonInput({
1321
2058
  setMode(next);
1322
2059
  };
1323
2060
  const showForm = mode === "form" && structured;
1324
- const toggleClass = (active) => `rounded px-2 py-0.5 text-xs font-medium transition-colors ${active ? "bg-white text-slate-800 shadow-sm ring-1 ring-slate-300" : "text-slate-500 hover:text-slate-700"}`;
1325
- return /* @__PURE__ */ jsxs8("div", { className: "rounded-md border border-slate-300 shadow-sm", children: [
1326
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-end gap-1 rounded-t-md border-b border-slate-200 bg-slate-50 px-2 py-1", children: [
1327
- /* @__PURE__ */ jsx9(
2061
+ const toggleClass = (active) => `mst-json__tab${active ? " mst-json__tab--active" : ""}`;
2062
+ return /* @__PURE__ */ jsxs12("div", { className: "mst-json", children: [
2063
+ /* @__PURE__ */ jsxs12("div", { className: "mst-json__head", children: [
2064
+ /* @__PURE__ */ jsx14(
1328
2065
  "button",
1329
2066
  {
1330
2067
  type: "button",
@@ -1334,9 +2071,9 @@ function JsonInput({
1334
2071
  children: t.jsonFormMode
1335
2072
  }
1336
2073
  ),
1337
- /* @__PURE__ */ jsx9("button", { type: "button", className: toggleClass(!showForm), onClick: () => pick("raw"), children: t.jsonRawMode })
2074
+ /* @__PURE__ */ jsx14("button", { type: "button", className: toggleClass(!showForm), onClick: () => pick("raw"), children: t.jsonRawMode })
1338
2075
  ] }),
1339
- /* @__PURE__ */ jsx9("div", { className: "p-1", children: showForm ? Array.isArray(value) ? /* @__PURE__ */ jsx9(ArrayEditor, { idPrefix: `field-${field.name}`, value, onChange, disabled }) : /* @__PURE__ */ jsx9(
2076
+ /* @__PURE__ */ jsx14("div", { className: "mst-json__body", children: showForm ? Array.isArray(value) ? /* @__PURE__ */ jsx14(ArrayEditor, { idPrefix: `field-${field.name}`, value, onChange, disabled }) : /* @__PURE__ */ jsx14(
1340
2077
  ObjectEditor,
1341
2078
  {
1342
2079
  idPrefix: `field-${field.name}`,
@@ -1344,7 +2081,7 @@ function JsonInput({
1344
2081
  onChange,
1345
2082
  disabled
1346
2083
  }
1347
- ) : /* @__PURE__ */ jsx9(
2084
+ ) : /* @__PURE__ */ jsx14(
1348
2085
  RawJsonArea,
1349
2086
  {
1350
2087
  id: `field-${field.name}`,
@@ -1358,8 +2095,8 @@ function JsonInput({
1358
2095
  }
1359
2096
 
1360
2097
  // src/components/FieldInput.tsx
1361
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1362
- var inputClass = "w-full rounded-md border border-slate-300 px-3 py-1.5 text-sm text-slate-800 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:bg-slate-100";
2098
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2099
+ var inputClass = "mst-input";
1363
2100
  function FieldInput({
1364
2101
  field,
1365
2102
  value,
@@ -1369,7 +2106,7 @@ function FieldInput({
1369
2106
  const id = `field-${field.name}`;
1370
2107
  switch (field.type) {
1371
2108
  case "boolean":
1372
- return /* @__PURE__ */ jsx10(
2109
+ return /* @__PURE__ */ jsx15(
1373
2110
  "input",
1374
2111
  {
1375
2112
  id,
@@ -1377,7 +2114,7 @@ function FieldInput({
1377
2114
  checked: Boolean(value),
1378
2115
  disabled,
1379
2116
  onChange: (e) => onChange(e.target.checked),
1380
- className: "h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500"
2117
+ className: "mst-checkbox"
1381
2118
  }
1382
2119
  );
1383
2120
  case "integer":
@@ -1385,7 +2122,7 @@ function FieldInput({
1385
2122
  case "float":
1386
2123
  case "decimal":
1387
2124
  case "currency":
1388
- return /* @__PURE__ */ jsx10(
2125
+ return /* @__PURE__ */ jsx15(
1389
2126
  "input",
1390
2127
  {
1391
2128
  id,
@@ -1399,22 +2136,22 @@ function FieldInput({
1399
2136
  }
1400
2137
  );
1401
2138
  case "enum":
1402
- return /* @__PURE__ */ jsxs9(
2139
+ return /* @__PURE__ */ jsxs13(
1403
2140
  "select",
1404
2141
  {
1405
2142
  id,
1406
2143
  value: value === null || value === void 0 ? "" : String(value),
1407
2144
  disabled,
1408
2145
  onChange: (e) => onChange(e.target.value),
1409
- className: inputClass,
2146
+ className: "mst-select",
1410
2147
  children: [
1411
- /* @__PURE__ */ jsx10("option", { value: "", children: "\u2014" }),
1412
- field.enumOptions?.map((o) => /* @__PURE__ */ jsx10("option", { value: String(o.value), children: o.label }, String(o.value)))
2148
+ /* @__PURE__ */ jsx15("option", { value: "", children: "\u2014" }),
2149
+ field.enumOptions?.map((o) => /* @__PURE__ */ jsx15("option", { value: String(o.value), children: o.label }, String(o.value)))
1413
2150
  ]
1414
2151
  }
1415
2152
  );
1416
2153
  case "date":
1417
- return /* @__PURE__ */ jsx10(
2154
+ return /* @__PURE__ */ jsx15(
1418
2155
  "input",
1419
2156
  {
1420
2157
  id,
@@ -1426,9 +2163,9 @@ function FieldInput({
1426
2163
  }
1427
2164
  );
1428
2165
  case "json":
1429
- return /* @__PURE__ */ jsx10(JsonInput, { field, value, onChange, disabled });
2166
+ return /* @__PURE__ */ jsx15(JsonInput, { field, value, onChange, disabled });
1430
2167
  case "text":
1431
- return /* @__PURE__ */ jsx10(
2168
+ return /* @__PURE__ */ jsx15(
1432
2169
  "textarea",
1433
2170
  {
1434
2171
  id,
@@ -1437,11 +2174,11 @@ function FieldInput({
1437
2174
  placeholder: field.form.placeholder,
1438
2175
  disabled,
1439
2176
  onChange: (e) => onChange(e.target.value),
1440
- className: inputClass
2177
+ className: "mst-textarea"
1441
2178
  }
1442
2179
  );
1443
2180
  default:
1444
- return /* @__PURE__ */ jsx10(
2181
+ return /* @__PURE__ */ jsx15(
1445
2182
  "input",
1446
2183
  {
1447
2184
  id,
@@ -1457,7 +2194,7 @@ function FieldInput({
1457
2194
  }
1458
2195
 
1459
2196
  // src/components/EntityForm.tsx
1460
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2197
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1461
2198
  function EntityForm({
1462
2199
  entity,
1463
2200
  mode,
@@ -1471,9 +2208,9 @@ function EntityForm({
1471
2208
  const fields = formFields(entity, mode);
1472
2209
  const sourceId = mode === "edit" ? id : cloneFromId;
1473
2210
  const existing = useEntityRecord(entity.id, sourceId);
1474
- const [values, setValues] = useState8({});
1475
- const [saving, setSaving] = useState8(false);
1476
- const [error, setError] = useState8(void 0);
2211
+ const [values, setValues] = useState12({});
2212
+ const [saving, setSaving] = useState12(false);
2213
+ const [error, setError] = useState12(void 0);
1477
2214
  useEffect6(() => {
1478
2215
  if (!existing.data || !sourceId) return;
1479
2216
  if (mode === "edit") {
@@ -1506,25 +2243,25 @@ function EntityForm({
1506
2243
  }
1507
2244
  };
1508
2245
  if (sourceId && existing.loading && !existing.data) {
1509
- return /* @__PURE__ */ jsx11(Spinner, {});
2246
+ return /* @__PURE__ */ jsx16(Spinner, {});
1510
2247
  }
1511
2248
  const title = mode === "edit" ? t.editTitle(entity.label.singular) : cloneFromId ? t.cloneTitle(entity.label.singular) : t.newTitle(entity.label.singular);
1512
- return /* @__PURE__ */ jsxs10("section", { className: "space-y-4", children: [
1513
- /* @__PURE__ */ jsxs10("header", { children: [
1514
- /* @__PURE__ */ jsxs10("button", { onClick: onCancel, className: "text-sm text-indigo-600 hover:underline", children: [
1515
- "\u2190 ",
2249
+ return /* @__PURE__ */ jsxs14("section", { className: "mst-page", children: [
2250
+ /* @__PURE__ */ jsx16("div", { className: "mst-pagehead", children: /* @__PURE__ */ jsxs14("div", { children: [
2251
+ /* @__PURE__ */ jsxs14("button", { type: "button", className: "mst-backlink", onClick: onCancel, children: [
2252
+ /* @__PURE__ */ jsx16(Icon, { name: "arrowLeft", size: 12 }),
1516
2253
  entity.label.plural
1517
2254
  ] }),
1518
- /* @__PURE__ */ jsx11("h1", { className: "text-lg font-semibold text-slate-900", children: title })
1519
- ] }),
1520
- error && /* @__PURE__ */ jsx11(ErrorBanner, { error }),
1521
- /* @__PURE__ */ jsxs10("form", { onSubmit: submit, className: "space-y-4 rounded-lg border border-slate-200 bg-white p-5", children: [
1522
- fields.map((f) => /* @__PURE__ */ jsxs10("div", { className: "space-y-1", children: [
1523
- /* @__PURE__ */ jsxs10("label", { htmlFor: `field-${f.name}`, className: "block text-sm font-medium text-slate-700", children: [
2255
+ /* @__PURE__ */ jsx16("h1", { className: "mst-pagehead__title", children: title })
2256
+ ] }) }),
2257
+ error && /* @__PURE__ */ jsx16(ErrorBanner, { error }),
2258
+ /* @__PURE__ */ jsxs14("form", { onSubmit: submit, className: "mst-card mst-form", children: [
2259
+ fields.map((f) => /* @__PURE__ */ jsxs14("div", { className: "mst-field", children: [
2260
+ /* @__PURE__ */ jsxs14("label", { htmlFor: `field-${f.name}`, className: "mst-label", children: [
1524
2261
  f.label,
1525
- f.required && /* @__PURE__ */ jsx11("span", { className: "ml-0.5 text-red-500", children: "*" })
2262
+ f.required && /* @__PURE__ */ jsx16("span", { className: "mst-label__req", children: "*" })
1526
2263
  ] }),
1527
- /* @__PURE__ */ jsx11(
2264
+ /* @__PURE__ */ jsx16(
1528
2265
  FieldInput,
1529
2266
  {
1530
2267
  field: f,
@@ -1533,11 +2270,11 @@ function EntityForm({
1533
2270
  onChange: (next) => setValues((v) => ({ ...v, [f.name]: next }))
1534
2271
  }
1535
2272
  ),
1536
- f.form.helpText && /* @__PURE__ */ jsx11("p", { className: "text-xs text-slate-500", children: f.form.helpText })
2273
+ f.form.helpText && /* @__PURE__ */ jsx16("p", { className: "mst-help", children: f.form.helpText })
1537
2274
  ] }, f.name)),
1538
- /* @__PURE__ */ jsxs10("div", { className: "flex justify-end gap-2 border-t border-slate-100 pt-4", children: [
1539
- /* @__PURE__ */ jsx11(Button, { type: "button", variant: "secondary", onClick: onCancel, disabled: saving, children: t.cancel }),
1540
- /* @__PURE__ */ jsx11(Button, { type: "submit", variant: "primary", disabled: saving, children: saving ? t.saving : mode === "create" ? t.create : t.save })
2275
+ /* @__PURE__ */ jsxs14("div", { className: "mst-form__foot", children: [
2276
+ /* @__PURE__ */ jsx16(Button, { type: "button", variant: "secondary", onClick: onCancel, disabled: saving, children: t.cancel }),
2277
+ /* @__PURE__ */ jsx16(Button, { type: "submit", variant: "primary", disabled: saving, children: saving ? t.saving : mode === "create" ? t.create : t.save })
1541
2278
  ] })
1542
2279
  ] })
1543
2280
  ] });
@@ -1566,10 +2303,10 @@ function applyLabels(metadata, labels) {
1566
2303
  }
1567
2304
 
1568
2305
  // src/recents.ts
1569
- import { useCallback as useCallback2, useState as useState9 } from "react";
1570
- var STORAGE_KEY = "maestro-admin:recents";
2306
+ import { useCallback as useCallback4, useState as useState13 } from "react";
2307
+ var STORAGE_KEY2 = "maestro-admin:recents";
1571
2308
  var MAX_RECENTS = 8;
1572
- function read(key) {
2309
+ function read2(key) {
1573
2310
  try {
1574
2311
  const raw = globalThis.localStorage?.getItem(key);
1575
2312
  const parsed = raw ? JSON.parse(raw) : [];
@@ -1578,20 +2315,20 @@ function read(key) {
1578
2315
  return [];
1579
2316
  }
1580
2317
  }
1581
- function write(key, ids) {
2318
+ function write2(key, ids) {
1582
2319
  try {
1583
2320
  globalThis.localStorage?.setItem(key, JSON.stringify(ids));
1584
2321
  } catch {
1585
2322
  }
1586
2323
  }
1587
2324
  function useRecents(scope) {
1588
- const key = scope ? `${STORAGE_KEY}:${scope}` : STORAGE_KEY;
1589
- const [recents, setRecents] = useState9(() => read(key));
1590
- const push = useCallback2(
2325
+ const key = scope ? `${STORAGE_KEY2}:${scope}` : STORAGE_KEY2;
2326
+ const [recents, setRecents] = useState13(() => read2(key));
2327
+ const push = useCallback4(
1591
2328
  (entityId) => {
1592
2329
  setRecents((prev) => {
1593
2330
  const next = [entityId, ...prev.filter((id) => id !== entityId)].slice(0, MAX_RECENTS);
1594
- write(key, next);
2331
+ write2(key, next);
1595
2332
  return next;
1596
2333
  });
1597
2334
  },
@@ -1601,30 +2338,81 @@ function useRecents(scope) {
1601
2338
  }
1602
2339
 
1603
2340
  // src/components/MaestroAdmin.tsx
1604
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2341
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1605
2342
  function AdminShell({ title, labels }) {
1606
2343
  const { data, error, loading } = useMetadata();
1607
2344
  const t = useT();
1608
- const [view, setView] = useState10(void 0);
2345
+ const [view, setView] = useState14({ kind: "hub" });
2346
+ const [paletteOpen, setPaletteOpen] = useState14(false);
1609
2347
  const { recents, push: pushRecent } = useRecents();
1610
- const metadata = useMemo3(() => data ? applyLabels(data, labels) : void 0, [data, labels]);
1611
- const entities = metadata?.entities ?? [];
1612
- const activeId = view?.entityId ?? entities[0]?.id;
2348
+ const metadata = useMemo7(() => data ? applyLabels(data, labels) : void 0, [data, labels]);
2349
+ const entities = useMemo7(() => metadata?.entities ?? [], [metadata]);
2350
+ const activeId = view.kind === "hub" ? void 0 : view.entityId;
1613
2351
  const activeEntity = entities.find((e) => e.id === activeId);
1614
- const effectiveView = view ?? (activeId ? { kind: "list", entityId: activeId } : void 0);
1615
- const openEntity = (entityId) => {
1616
- pushRecent(entityId);
1617
- setView({ kind: "list", entityId });
1618
- };
1619
- const navigateToRecord = (entityId, id) => {
1620
- pushRecent(entityId);
1621
- setView({ kind: "detail", entityId, id });
1622
- };
2352
+ const groupOf = useMemo7(() => {
2353
+ const map = /* @__PURE__ */ new Map();
2354
+ for (const group of buildMenu(entities, labels).groups) {
2355
+ for (const entity of group.entities) map.set(entity.id, group.label);
2356
+ }
2357
+ return map;
2358
+ }, [entities, labels]);
2359
+ const openEntity = useCallback5(
2360
+ (entityId) => {
2361
+ pushRecent(entityId);
2362
+ setView({ kind: "list", entityId });
2363
+ },
2364
+ [pushRecent]
2365
+ );
2366
+ const navigateToRecord = useCallback5(
2367
+ (entityId, id) => {
2368
+ pushRecent(entityId);
2369
+ setView({ kind: "detail", entityId, id });
2370
+ },
2371
+ [pushRecent]
2372
+ );
2373
+ useEffect7(() => {
2374
+ const onKey = (e) => {
2375
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
2376
+ e.preventDefault();
2377
+ setPaletteOpen((open) => !open);
2378
+ }
2379
+ };
2380
+ document.addEventListener("keydown", onKey);
2381
+ return () => document.removeEventListener("keydown", onKey);
2382
+ }, []);
2383
+ const crumbs = useMemo7(() => {
2384
+ const home = { label: t.hub, icon: "hub", onClick: view.kind === "hub" ? void 0 : () => setView({ kind: "hub" }) };
2385
+ if (view.kind === "hub" || !activeEntity) return [home];
2386
+ const entityCrumb = {
2387
+ label: activeEntity.label.plural,
2388
+ icon: entityIcon(activeEntity),
2389
+ onClick: view.kind === "list" ? void 0 : () => setView({ kind: "list", entityId: activeEntity.id })
2390
+ };
2391
+ if (view.kind === "list") return [home, entityCrumb];
2392
+ const leaf = view.kind === "detail" ? { label: view.id } : view.kind === "edit" ? { label: t.editTitle(activeEntity.label.singular) } : { label: view.cloneFromId ? t.cloneTitle(activeEntity.label.singular) : t.newTitle(activeEntity.label.singular) };
2393
+ return [home, entityCrumb, leaf];
2394
+ }, [view, activeEntity, t]);
2395
+ const paletteSections = useMemo7(() => {
2396
+ const goTo = entityCommands(entities, groupOf, openEntity);
2397
+ goTo.unshift({ id: "hub", label: t.hub, icon: "hub", run: () => setView({ kind: "hub" }) });
2398
+ const screen = [];
2399
+ if (activeEntity?.capabilities.create) {
2400
+ screen.push({
2401
+ id: "create",
2402
+ label: t.newTitle(activeEntity.label.singular),
2403
+ icon: "plus",
2404
+ run: () => setView({ kind: "create", entityId: activeEntity.id })
2405
+ });
2406
+ }
2407
+ const sections = [{ id: "goto", label: t.goTo, commands: goTo }];
2408
+ if (screen.length > 0) sections.unshift({ id: "screen", label: t.onThisScreen, commands: screen });
2409
+ return sections;
2410
+ }, [entities, groupOf, openEntity, activeEntity, t]);
1623
2411
  if (loading && !data) {
1624
- return /* @__PURE__ */ jsx12("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx12(Spinner, { label: t.loadingMetadata }) });
2412
+ return /* @__PURE__ */ jsx17("div", { className: "mst-boot", children: /* @__PURE__ */ jsx17(Spinner, { label: t.loadingMetadata }) });
1625
2413
  }
1626
- return /* @__PURE__ */ jsx12(AdminEntitiesProvider, { value: entities, children: /* @__PURE__ */ jsxs11("div", { className: "flex h-screen overflow-hidden bg-white text-slate-800", children: [
1627
- /* @__PURE__ */ jsx12(
2414
+ return /* @__PURE__ */ jsxs15(AdminEntitiesProvider, { value: entities, children: [
2415
+ /* @__PURE__ */ jsx17(
1628
2416
  Sidebar,
1629
2417
  {
1630
2418
  entities,
@@ -1632,83 +2420,111 @@ function AdminShell({ title, labels }) {
1632
2420
  title,
1633
2421
  labels,
1634
2422
  recents,
1635
- onSelect: openEntity
2423
+ onSelect: openEntity,
2424
+ onHome: () => setView({ kind: "hub" }),
2425
+ homeActive: view.kind === "hub"
1636
2426
  }
1637
2427
  ),
1638
- /* @__PURE__ */ jsxs11("main", { className: "flex-1 overflow-y-auto p-6", children: [
1639
- error && /* @__PURE__ */ jsx12(ErrorBanner, { error }),
1640
- activeEntity && effectiveView && effectiveView.kind === "list" && /* @__PURE__ */ jsx12(
1641
- EntityList,
1642
- {
1643
- entity: activeEntity,
1644
- onOpen: (id) => setView({ kind: "detail", entityId: activeEntity.id, id }),
1645
- onCreate: () => setView({ kind: "create", entityId: activeEntity.id }),
1646
- onEdit: (id) => setView({ kind: "edit", entityId: activeEntity.id, id }),
1647
- onClone: (id) => setView({ kind: "create", entityId: activeEntity.id, cloneFromId: id }),
1648
- onNavigate: navigateToRecord
1649
- }
1650
- ),
1651
- activeEntity && effectiveView?.kind === "detail" && /* @__PURE__ */ jsx12(
1652
- EntityDetail,
1653
- {
1654
- entity: activeEntity,
1655
- id: effectiveView.id,
1656
- onBack: () => setView({ kind: "list", entityId: activeEntity.id }),
1657
- onEdit: () => setView({ kind: "edit", entityId: activeEntity.id, id: effectiveView.id }),
1658
- onClone: () => setView({ kind: "create", entityId: activeEntity.id, cloneFromId: effectiveView.id }),
1659
- onDeleted: () => setView({ kind: "list", entityId: activeEntity.id }),
1660
- onNavigate: navigateToRecord
1661
- }
1662
- ),
1663
- activeEntity && (effectiveView?.kind === "create" || effectiveView?.kind === "edit") && /* @__PURE__ */ jsx12(
1664
- EntityForm,
1665
- {
1666
- entity: activeEntity,
1667
- mode: effectiveView.kind,
1668
- id: effectiveView.kind === "edit" ? effectiveView.id : void 0,
1669
- cloneFromId: effectiveView.kind === "create" ? effectiveView.cloneFromId : void 0,
1670
- onCancel: () => setView({ kind: "list", entityId: activeEntity.id }),
1671
- onSaved: () => setView({ kind: "list", entityId: activeEntity.id })
1672
- }
1673
- )
1674
- ] })
1675
- ] }) });
2428
+ /* @__PURE__ */ jsxs15("div", { className: "mst-main", children: [
2429
+ /* @__PURE__ */ jsx17(Header, { crumbs, onOpenPalette: () => setPaletteOpen(true) }),
2430
+ /* @__PURE__ */ jsxs15("div", { className: "mst-content", children: [
2431
+ error && /* @__PURE__ */ jsx17(ErrorBanner, { error }),
2432
+ view.kind === "hub" && /* @__PURE__ */ jsx17(Hub, { entities, labels, onOpen: openEntity }),
2433
+ activeEntity && view.kind === "list" && /* @__PURE__ */ jsx17(
2434
+ EntityList,
2435
+ {
2436
+ entity: activeEntity,
2437
+ onOpen: (id) => setView({ kind: "detail", entityId: activeEntity.id, id }),
2438
+ onCreate: () => setView({ kind: "create", entityId: activeEntity.id }),
2439
+ onEdit: (id) => setView({ kind: "edit", entityId: activeEntity.id, id }),
2440
+ onClone: (id) => setView({ kind: "create", entityId: activeEntity.id, cloneFromId: id }),
2441
+ onNavigate: navigateToRecord
2442
+ }
2443
+ ),
2444
+ activeEntity && view.kind === "detail" && /* @__PURE__ */ jsx17(
2445
+ EntityDetail,
2446
+ {
2447
+ entity: activeEntity,
2448
+ id: view.id,
2449
+ onBack: () => setView({ kind: "list", entityId: activeEntity.id }),
2450
+ onEdit: () => setView({ kind: "edit", entityId: activeEntity.id, id: view.id }),
2451
+ onClone: () => setView({ kind: "create", entityId: activeEntity.id, cloneFromId: view.id }),
2452
+ onDeleted: () => setView({ kind: "list", entityId: activeEntity.id }),
2453
+ onNavigate: navigateToRecord
2454
+ }
2455
+ ),
2456
+ activeEntity && (view.kind === "create" || view.kind === "edit") && /* @__PURE__ */ jsx17(
2457
+ EntityForm,
2458
+ {
2459
+ entity: activeEntity,
2460
+ mode: view.kind,
2461
+ id: view.kind === "edit" ? view.id : void 0,
2462
+ cloneFromId: view.kind === "create" ? view.cloneFromId : void 0,
2463
+ onCancel: () => setView({ kind: "list", entityId: activeEntity.id }),
2464
+ onSaved: () => setView({ kind: "list", entityId: activeEntity.id })
2465
+ }
2466
+ )
2467
+ ] })
2468
+ ] }),
2469
+ paletteOpen && /* @__PURE__ */ jsx17(CommandPalette, { sections: paletteSections, onClose: () => setPaletteOpen(false) })
2470
+ ] });
1676
2471
  }
1677
2472
  function MaestroAdmin(props) {
1678
- const client = useMemo3(
2473
+ const client = useMemo7(
1679
2474
  () => props.client ?? new MaestroClient({ apiUrl: props.apiUrl ?? "", headers: props.headers }),
1680
2475
  [props.client, props.apiUrl, props.headers]
1681
2476
  );
1682
- const strings = useMemo3(() => stringsFor(props.locale ?? "en"), [props.locale]);
1683
- return /* @__PURE__ */ jsx12(AdminClientProvider, { value: client, children: /* @__PURE__ */ jsx12(I18nProvider, { value: strings, children: /* @__PURE__ */ jsx12(AdminShell, { title: props.title ?? "Maestro Admin", labels: props.labels }) }) });
2477
+ const strings = useMemo7(() => stringsFor(props.locale ?? "en"), [props.locale]);
2478
+ const [root, setRoot] = useState14(null);
2479
+ return /* @__PURE__ */ jsx17(AdminClientProvider, { value: client, children: /* @__PURE__ */ jsx17(I18nProvider, { value: strings, children: /* @__PURE__ */ jsx17(MaestroThemeProvider, { theme: props.theme, className: "mst-root", rootRef: setRoot, children: /* @__PURE__ */ jsx17(AdminRootProvider, { value: root, children: /* @__PURE__ */ jsx17(AdminShell, { title: props.title ?? "Maestro Admin", labels: props.labels }) }) }) }) });
1684
2480
  }
1685
2481
  export {
1686
2482
  AdminClientProvider,
2483
+ Badge,
2484
+ Button,
2485
+ CommandPalette,
2486
+ EmptyState,
1687
2487
  EntityDetail,
1688
2488
  EntityForm,
1689
2489
  EntityList,
2490
+ ErrorBanner,
1690
2491
  FieldInput,
1691
2492
  FieldValue,
2493
+ Header,
2494
+ Hub,
1692
2495
  I18nProvider,
2496
+ Icon,
2497
+ IconButton,
1693
2498
  JsonInput,
2499
+ MAESTRO_THEMES,
1694
2500
  MaestroAdmin,
1695
2501
  MaestroApiError,
1696
2502
  MaestroClient,
2503
+ MaestroThemeProvider,
1697
2504
  ObjectValue,
1698
2505
  Sidebar,
2506
+ Spinner,
2507
+ Toast,
1699
2508
  applyLabels,
1700
2509
  buildMenu,
1701
2510
  detailFields,
2511
+ entityCommands,
2512
+ entityIcon,
1702
2513
  formFields,
1703
2514
  listFields,
1704
2515
  primaryListFields,
1705
2516
  stringsFor,
2517
+ useAdminRoot,
1706
2518
  useAsync,
1707
2519
  useClient,
2520
+ useColumnPrefs,
2521
+ useEntities,
2522
+ useEntity,
1708
2523
  useEntityList,
1709
2524
  useEntityRecord,
1710
2525
  useMetadata,
1711
2526
  useRecents,
1712
- useT
2527
+ useT,
2528
+ useTheme
1713
2529
  };
1714
2530
  //# sourceMappingURL=index.js.map