@marktiderman/genesis-ui 2.15.0 → 2.17.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.
Files changed (36) hide show
  1. package/SKILL.md +2 -1
  2. package/dist/{AppShell-HCXpi6nv.d.ts → AppShell-DDyVqNmJ.d.ts} +12 -2
  3. package/dist/{chunk-46TZGMFW.js → chunk-24NUW3QN.js} +22 -22
  4. package/dist/chunk-24NUW3QN.js.map +1 -0
  5. package/dist/{chunk-DTQHAZWN.js → chunk-3FKF2SN4.js} +19 -5
  6. package/dist/chunk-3FKF2SN4.js.map +1 -0
  7. package/dist/{chunk-25XEXYGC.js → chunk-OT6KKSD2.js} +20 -20
  8. package/dist/chunk-OT6KKSD2.js.map +1 -0
  9. package/dist/{chunk-STZVZUYO.js → chunk-TQ7OJDSU.js} +8 -6
  10. package/dist/chunk-TQ7OJDSU.js.map +1 -0
  11. package/dist/chunk-YCQAYWMX.js +192 -0
  12. package/dist/chunk-YCQAYWMX.js.map +1 -0
  13. package/dist/components/data/index.d.ts +6 -6
  14. package/dist/components/data/index.js +170 -123
  15. package/dist/components/data/index.js.map +1 -1
  16. package/dist/components/layout/detail-page.d.ts +1 -1
  17. package/dist/components/layout/index.d.ts +27 -11
  18. package/dist/components/layout/index.js +73 -23
  19. package/dist/components/layout/index.js.map +1 -1
  20. package/dist/components/layout/settings-page.d.ts +1 -1
  21. package/dist/components/patterns/index.js +3 -2
  22. package/dist/components/patterns/sortable-list.js +2 -1
  23. package/dist/hooks/index.d.ts +2 -2
  24. package/dist/hooks/index.js +2 -1
  25. package/dist/hooks/use-resource-page.d.ts +1 -1
  26. package/dist/index.d.ts +176 -2
  27. package/dist/index.js +9 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/{split-Dk9f4Mi0.d.ts → split-D-uYckQ_.d.ts} +1 -1
  30. package/dist/{use-resource-page-DaJzuq3M.d.ts → use-resource-page-BA1P7xea.d.ts} +1 -1
  31. package/dist/{use-resource-table-easmRDQI.d.ts → use-resource-table-WDqz7oQ-.d.ts} +17 -3
  32. package/package.json +2 -2
  33. package/dist/chunk-25XEXYGC.js.map +0 -1
  34. package/dist/chunk-46TZGMFW.js.map +0 -1
  35. package/dist/chunk-DTQHAZWN.js.map +0 -1
  36. package/dist/chunk-STZVZUYO.js.map +0 -1
package/SKILL.md CHANGED
@@ -386,7 +386,8 @@ most specific sibling wins) and an optional `badge` (a count or short status, sh
386
386
  it over.
387
387
  For live data (badge counts) the page calls `useAppShellSubNav(items, { mode? })`, which
388
388
  replaces the node's items while it is mounted, and puts a host chip at the end of the tab row with
389
- `<AppShellSubNavEnd>…</AppShellSubNavEnd>`. The page's resource header (breadcrumb, title,
389
+ `<AppShellSubNavEnd>…</AppShellSubNavEnd>` (on phones, where the bottom bar replaces the row,
390
+ the chip moves to a compact strip under the page header, so it stays reachable). The page's resource header (breadcrumb, title,
390
391
  status, actions) goes in `<AppShellPageHeader>…</AppShellPageHeader>`, which the shell renders
391
392
  above the tab row (on phones too; at the top of the content when there is no row). Both are
392
393
  portals; when a layout and its page both mount one, the page's (innermost) renders, and the same
@@ -31,6 +31,14 @@ type SubNavMode = "section" | "tabs" | "sidebar" | "off";
31
31
  interface SubNavItem extends Omit<NavItem, "icon" | "children"> {
32
32
  icon?: LucideIcon;
33
33
  children?: SubNavItem[];
34
+ /**
35
+ * Leave this item out of the phone bar (it stays in the tab row, the
36
+ * sidebar and the rail). The phone bar fits back plus about four items:
37
+ * fold a builder panel into its parent tab on phones instead of
38
+ * overflowing. If its route sits under another item's (without `end`),
39
+ * that item shows as current while it is open.
40
+ */
41
+ hideOnPhone?: boolean;
34
42
  }
35
43
  /**
36
44
  * Props for {@link SubNavTabs}.
@@ -93,8 +101,10 @@ declare function useAppShellSubNav(items: SubNavItem[] | null | undefined, optio
93
101
  /**
94
102
  * Render `children` in the right-hand slot of the shell's sub-navigation
95
103
  * tab row, e.g. a "Ready to publish" status chip. Rendered through a
96
- * portal, so it keeps the page's context and state. Renders nothing when
97
- * the shell shows no tab row (other modes, phones, no children). When
104
+ * portal, so it keeps the page's context and state. On phones, where the
105
+ * bottom bar replaces the tab row, it renders in a compact strip under the
106
+ * page header instead, so the chip stays reachable. Renders nothing when
107
+ * the shell has no tab row (other modes, a page without children). When
98
108
  * several are mounted (a layout's and a page's), the innermost renders.
99
109
  *
100
110
  * @stability Beta
@@ -1,17 +1,19 @@
1
1
  import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from './chunk-FK42ZGGV.js';
2
2
  import { Popover, PopoverTrigger, PopoverContent } from './chunk-22XB3Q6H.js';
3
3
  import { Button } from './chunk-NWSCYLPT.js';
4
+ import { useUiMessages, fillMessage } from './chunk-YCQAYWMX.js';
4
5
  import { LayoutGrid, Table2, List, Settings2 } from 'lucide-react';
5
6
  import { jsxs, jsx } from 'react/jsx-runtime';
6
7
  import { useId } from 'react';
7
8
 
8
9
  function ViewToggle({ viewMode, onViewModeChange }) {
10
+ const m = useUiMessages();
9
11
  return /* @__PURE__ */ jsxs(
10
12
  "div",
11
13
  {
12
14
  className: "flex items-center border rounded-lg overflow-hidden",
13
15
  role: "group",
14
- "aria-label": "View mode",
16
+ "aria-label": m.view.mode,
15
17
  children: [
16
18
  /* @__PURE__ */ jsx(
17
19
  Button,
@@ -20,8 +22,8 @@ function ViewToggle({ viewMode, onViewModeChange }) {
20
22
  size: "icon",
21
23
  className: "h-8 w-8 rounded-none",
22
24
  onClick: () => onViewModeChange("grid"),
23
- title: "Grid view",
24
- "aria-label": "Grid view",
25
+ title: m.view.grid,
26
+ "aria-label": m.view.grid,
25
27
  "aria-pressed": viewMode === "grid",
26
28
  testID: "view-toggle-grid",
27
29
  children: /* @__PURE__ */ jsx(LayoutGrid, { className: "h-4 w-4", "aria-hidden": "true" })
@@ -34,8 +36,8 @@ function ViewToggle({ viewMode, onViewModeChange }) {
34
36
  size: "icon",
35
37
  className: "h-8 w-8 rounded-none",
36
38
  onClick: () => onViewModeChange("table"),
37
- title: "Table view",
38
- "aria-label": "Table view",
39
+ title: m.view.table,
40
+ "aria-label": m.view.table,
39
41
  "aria-pressed": viewMode === "table",
40
42
  testID: "view-toggle-table",
41
43
  children: /* @__PURE__ */ jsx(Table2, { className: "h-4 w-4", "aria-hidden": "true" })
@@ -48,8 +50,8 @@ function ViewToggle({ viewMode, onViewModeChange }) {
48
50
  size: "icon",
49
51
  className: "h-8 w-8 rounded-none",
50
52
  onClick: () => onViewModeChange("list"),
51
- title: "List view",
52
- "aria-label": "List view",
53
+ title: m.view.list,
54
+ "aria-label": m.view.list,
53
55
  "aria-pressed": viewMode === "list",
54
56
  testID: "view-toggle-list",
55
57
  children: /* @__PURE__ */ jsx(List, { className: "h-4 w-4", "aria-hidden": "true" })
@@ -61,9 +63,9 @@ function ViewToggle({ viewMode, onViewModeChange }) {
61
63
  }
62
64
  var PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
63
65
  var DENSITY_OPTIONS = [
64
- { value: "compact", label: "Compact" },
65
- { value: "comfortable", label: "Comfortable" },
66
- { value: "spacious", label: "Spacious" }
66
+ { value: "compact", label: "compact" },
67
+ { value: "comfortable", label: "comfortable" },
68
+ { value: "spacious", label: "spacious" }
67
69
  ];
68
70
  function ViewSettings({
69
71
  pageSize,
@@ -71,6 +73,7 @@ function ViewSettings({
71
73
  density,
72
74
  onDensityChange
73
75
  }) {
76
+ const m = useUiMessages();
74
77
  const baseId = useId();
75
78
  const pageSizeLabelId = `${baseId}-page-size`;
76
79
  const densityLabelId = `${baseId}-density`;
@@ -81,21 +84,21 @@ function ViewSettings({
81
84
  variant: "ghost",
82
85
  size: "icon",
83
86
  className: "h-8 w-8",
84
- title: "View settings",
85
- "aria-label": "View settings",
87
+ title: m.view.settings,
88
+ "aria-label": m.view.settings,
86
89
  testID: "view-settings-trigger",
87
90
  children: /* @__PURE__ */ jsx(Settings2, { className: "h-4 w-4", "aria-hidden": "true" })
88
91
  }
89
92
  ) }),
90
93
  /* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: "w-56 space-y-4", children: [
91
- /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Display Settings" }),
94
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: m.view.settingsTitle }),
92
95
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
93
96
  /* @__PURE__ */ jsx(
94
97
  "span",
95
98
  {
96
99
  id: pageSizeLabelId,
97
100
  className: "block text-xs text-muted-foreground",
98
- children: "Page size"
101
+ children: m.view.pageSize
99
102
  }
100
103
  ),
101
104
  /* @__PURE__ */ jsxs(
@@ -113,10 +116,7 @@ function ViewSettings({
113
116
  children: /* @__PURE__ */ jsx(SelectValue, {})
114
117
  }
115
118
  ),
116
- /* @__PURE__ */ jsx(SelectContent, { className: "bg-popover z-50", children: PAGE_SIZE_OPTIONS.map((n) => /* @__PURE__ */ jsxs(SelectItem, { value: String(n), children: [
117
- n,
118
- " items"
119
- ] }, n)) })
119
+ /* @__PURE__ */ jsx(SelectContent, { className: "bg-popover z-50", children: PAGE_SIZE_OPTIONS.map((n) => /* @__PURE__ */ jsx(SelectItem, { value: String(n), children: fillMessage(m.view.pageSizeOption, { n }) }, n)) })
120
120
  ]
121
121
  }
122
122
  )
@@ -127,7 +127,7 @@ function ViewSettings({
127
127
  {
128
128
  id: densityLabelId,
129
129
  className: "block text-xs text-muted-foreground",
130
- children: "Density"
130
+ children: m.view.density
131
131
  }
132
132
  ),
133
133
  /* @__PURE__ */ jsxs(
@@ -145,7 +145,7 @@ function ViewSettings({
145
145
  children: /* @__PURE__ */ jsx(SelectValue, {})
146
146
  }
147
147
  ),
148
- /* @__PURE__ */ jsx(SelectContent, { className: "bg-popover z-50", children: DENSITY_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
148
+ /* @__PURE__ */ jsx(SelectContent, { className: "bg-popover z-50", children: DENSITY_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: m.view[opt.label] }, opt.value)) })
149
149
  ]
150
150
  }
151
151
  )
@@ -155,5 +155,5 @@ function ViewSettings({
155
155
  }
156
156
 
157
157
  export { ViewSettings, ViewToggle };
158
- //# sourceMappingURL=chunk-46TZGMFW.js.map
159
- //# sourceMappingURL=chunk-46TZGMFW.js.map
158
+ //# sourceMappingURL=chunk-24NUW3QN.js.map
159
+ //# sourceMappingURL=chunk-24NUW3QN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/patterns/view-toggle.tsx","../src/components/patterns/view-settings.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"chunk-24NUW3QN.js","sourcesContent":["import { Button } from \"../ui/button\";\nimport { LayoutGrid, Table2, List } from \"lucide-react\";\nimport { useUiMessages } from \"../../messages\";\n\nexport type ViewMode = \"grid\" | \"table\" | \"list\";\n\ninterface ViewToggleProps {\n viewMode: ViewMode;\n onViewModeChange: (mode: ViewMode) => void;\n}\n\nexport function ViewToggle({ viewMode, onViewModeChange }: ViewToggleProps) {\n const m = useUiMessages();\n // Each button is icon-only, so `aria-label` supplies the accessible name\n // (`title` alone is not a reliable one) and `aria-pressed` reports which of\n // the three is current — the `default`/`ghost` variant swap conveys that to\n // sighted users only.\n return (\n <div\n className=\"flex items-center border rounded-lg overflow-hidden\"\n role=\"group\"\n aria-label={m.view.mode}\n >\n <Button\n variant={viewMode === \"grid\" ? \"default\" : \"ghost\"}\n size=\"icon\"\n className=\"h-8 w-8 rounded-none\"\n onClick={() => onViewModeChange(\"grid\")}\n title={m.view.grid}\n aria-label={m.view.grid}\n aria-pressed={viewMode === \"grid\"}\n testID=\"view-toggle-grid\"\n >\n <LayoutGrid className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n <Button\n variant={viewMode === \"table\" ? \"default\" : \"ghost\"}\n size=\"icon\"\n className=\"h-8 w-8 rounded-none\"\n onClick={() => onViewModeChange(\"table\")}\n title={m.view.table}\n aria-label={m.view.table}\n aria-pressed={viewMode === \"table\"}\n testID=\"view-toggle-table\"\n >\n <Table2 className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n <Button\n variant={viewMode === \"list\" ? \"default\" : \"ghost\"}\n size=\"icon\"\n className=\"h-8 w-8 rounded-none\"\n onClick={() => onViewModeChange(\"list\")}\n title={m.view.list}\n aria-label={m.view.list}\n aria-pressed={viewMode === \"list\"}\n testID=\"view-toggle-list\"\n >\n <List className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </div>\n );\n}\n","import { useId } from \"react\";\nimport { Settings2 } from \"lucide-react\";\nimport { fillMessage, useUiMessages } from \"../../messages\";\nimport { Button } from \"../ui/button\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../ui/popover\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../ui/select\";\nimport type { Density } from \"../../hooks/use-view-settings\";\n\ninterface ViewSettingsProps {\n pageSize: number;\n onPageSizeChange: (size: number) => void;\n density: Density;\n onDensityChange: (density: Density) => void;\n}\n\nconst PAGE_SIZE_OPTIONS = [10, 25, 50, 100];\nconst DENSITY_OPTIONS: { value: Density; label: \"compact\" | \"comfortable\" | \"spacious\" }[] = [\n { value: \"compact\", label: \"compact\" },\n { value: \"comfortable\", label: \"comfortable\" },\n { value: \"spacious\", label: \"spacious\" },\n];\n\nexport function ViewSettings({\n pageSize,\n onPageSizeChange,\n density,\n onDensityChange,\n}: ViewSettingsProps) {\n const m = useUiMessages();\n // Radix renders each `SelectTrigger` as a <button>, and a <label> cannot be\n // associated with a button via `htmlFor`. `aria-labelledby` pointing at the\n // visible label is the association that actually reaches assistive tech.\n // `useId` keeps the ids unique when more than one ViewSettings is mounted.\n const baseId = useId();\n const pageSizeLabelId = `${baseId}-page-size`;\n const densityLabelId = `${baseId}-density`;\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n title={m.view.settings}\n // Icon-only trigger: `title` is not a dependable accessible name.\n aria-label={m.view.settings}\n testID=\"view-settings-trigger\"\n >\n <Settings2 className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-56 space-y-4\">\n <h4 className=\"text-sm font-semibold\">{m.view.settingsTitle}</h4>\n\n <div className=\"space-y-2\">\n <span\n id={pageSizeLabelId}\n className=\"block text-xs text-muted-foreground\"\n >\n {m.view.pageSize}\n </span>\n <Select\n value={String(pageSize)}\n onValueChange={(v) => onPageSizeChange(Number(v))}\n >\n <SelectTrigger\n className=\"h-8 text-xs\"\n aria-labelledby={pageSizeLabelId}\n data-testid=\"view-settings-page-size\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent className=\"bg-popover z-50\">\n {PAGE_SIZE_OPTIONS.map((n) => (\n <SelectItem key={n} value={String(n)}>\n {fillMessage(m.view.pageSizeOption, { n })}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-2\">\n <span\n id={densityLabelId}\n className=\"block text-xs text-muted-foreground\"\n >\n {m.view.density}\n </span>\n <Select\n value={density}\n onValueChange={(v) => onDensityChange(v as Density)}\n >\n <SelectTrigger\n className=\"h-8 text-xs\"\n aria-labelledby={densityLabelId}\n data-testid=\"view-settings-density\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent className=\"bg-popover z-50\">\n {DENSITY_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {m.view[opt.label]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { ViewToggle, ViewSettings } from './chunk-46TZGMFW.js';
2
+ import { ViewToggle, ViewSettings } from './chunk-24NUW3QN.js';
3
3
  import { Collapsible, CollapsibleContent } from './chunk-B2GIBUQD.js';
4
4
  import { Badge } from './chunk-NPNADZHM.js';
5
5
  import { Tooltip } from './chunk-G5YKBPBF.js';
@@ -10,6 +10,7 @@ import { Switch } from './chunk-27ELYTGE.js';
10
10
  import { Label } from './chunk-F47U5KNW.js';
11
11
  import { cn } from './chunk-NZ5WBA4R.js';
12
12
  import { isPathActive, resolveNavigation, DefaultLink } from './chunk-J45FHL7D.js';
13
+ import { useIsMobile } from './chunk-VFO3I7PV.js';
13
14
  import { createContext, useId, useState, useEffect, useMemo, useCallback, useContext, useRef, useLayoutEffect, useSyncExternalStore } from 'react';
14
15
  import { ListFilter, MoreHorizontal, SlidersHorizontal, Menu, ArrowLeft, X, Sun, Moon, PanelLeft, PanelLeftClose, ChevronRight, ChevronDown } from 'lucide-react';
15
16
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -1416,7 +1417,7 @@ function AppShell({
1416
1417
  const subActive = contextualNavItems.some((i) => itemIsActive(i, activePath, isActive));
1417
1418
  const subNavOn = subNavMode === "tabs" || subNavMode === "sidebar";
1418
1419
  const leaves = (items) => items.filter((i) => !i.children || i.children.length === 0);
1419
- const contextualLeaves = subNavMode === "off" ? [] : leaves(contextualNavItems);
1420
+ const contextualLeaves = subNavMode === "off" ? [] : leaves(contextualNavItems).filter((i) => !i.hideOnPhone);
1420
1421
  const subBottom = subNavOn && contextualLeaves.length > 0;
1421
1422
  const legacyBottom = !subNavOn && !mobileNavItems && contextualLeaves.length > 0;
1422
1423
  const backTarget = subBottom ? parentNavigation ?? derived?.backItem ?? (() => {
@@ -1454,6 +1455,8 @@ function AppShell({
1454
1455
  const showUser = hasUser && resolved.showUserSlot !== false;
1455
1456
  const showMobileBottom = bottomItems.length > 0 && resolved.showMobileBottomNav !== false;
1456
1457
  const showTabs = subNavMode === "tabs" && contextualNavItems.length > 0;
1458
+ const isPhone = useIsMobile();
1459
+ const endOnPhone = showTabs && showMobileBottom && isPhone && subNavChannel.hasEnd;
1457
1460
  const optionsControl = resolved.configurable && toggles.length > 0 ? /* @__PURE__ */ jsxs(Popover, { children: [
1458
1461
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1459
1462
  Button,
@@ -1758,6 +1761,17 @@ function AppShell({
1758
1761
  "data-testid": "app-shell-page-header"
1759
1762
  }
1760
1763
  ) : null,
1764
+ endOnPhone ? /* @__PURE__ */ jsx(
1765
+ "div",
1766
+ {
1767
+ ref: subNavChannel.setEndSlot,
1768
+ className: cn(
1769
+ "flex min-w-0 flex-wrap items-center justify-end gap-2 px-4 sm:px-6",
1770
+ parentNavigation || hasPageHeader ? "pt-2 sm:pt-3" : "pt-4 sm:pt-6"
1771
+ ),
1772
+ "data-testid": "app-shell-sub-nav-end-phone"
1773
+ }
1774
+ ) : null,
1761
1775
  showTabs ? /* @__PURE__ */ jsx(
1762
1776
  "div",
1763
1777
  {
@@ -1775,7 +1789,7 @@ function AppShell({
1775
1789
  isActive,
1776
1790
  linkComponent,
1777
1791
  label: subNavLabel ?? derived?.current.label ?? "Page sections",
1778
- end: subNavChannel.hasEnd ? /* @__PURE__ */ jsx("div", { ref: subNavChannel.setEndSlot, className: "flex items-center" }) : void 0
1792
+ end: subNavChannel.hasEnd && !endOnPhone ? /* @__PURE__ */ jsx("div", { ref: subNavChannel.setEndSlot, className: "flex items-center" }) : void 0
1779
1793
  }
1780
1794
  )
1781
1795
  }
@@ -1855,5 +1869,5 @@ function AppShell({
1855
1869
  }
1856
1870
 
1857
1871
  export { AppShell, AppShellPageHeader, AppShellSubNavEnd, PageHeader, SubNavTabs, useAppShellSubNav };
1858
- //# sourceMappingURL=chunk-DTQHAZWN.js.map
1859
- //# sourceMappingURL=chunk-DTQHAZWN.js.map
1872
+ //# sourceMappingURL=chunk-3FKF2SN4.js.map
1873
+ //# sourceMappingURL=chunk-3FKF2SN4.js.map