@evinvest/uikit 0.29.4 → 0.31.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 (204) hide show
  1. package/README.md +294 -90
  2. package/dist/{chunk-3IPB4QQJ.js → chunk-35SPIU4U.js} +10 -6
  3. package/dist/chunk-35SPIU4U.js.map +1 -0
  4. package/dist/components/account-list.js +128 -0
  5. package/dist/components/account-list.js.map +1 -0
  6. package/dist/components/account-menu-profile.js +114 -0
  7. package/dist/components/account-menu-profile.js.map +1 -0
  8. package/dist/components/account-menu.js +50 -23
  9. package/dist/components/account-menu.js.map +1 -1
  10. package/dist/components/account-settings-dialog.js +84 -0
  11. package/dist/components/account-settings-dialog.js.map +1 -0
  12. package/dist/components/alert-dialog.js +3 -2
  13. package/dist/components/alert-dialog.js.map +1 -1
  14. package/dist/components/animated-number-columns.js +105 -0
  15. package/dist/components/animated-number-columns.js.map +1 -0
  16. package/dist/components/animated-number.js +157 -0
  17. package/dist/components/animated-number.js.map +1 -0
  18. package/dist/components/app-shell.js +9 -0
  19. package/dist/components/app-shell.js.map +1 -1
  20. package/dist/components/aside.js +140 -0
  21. package/dist/components/aside.js.map +1 -0
  22. package/dist/components/badge.js +1 -1
  23. package/dist/components/badge.js.map +1 -1
  24. package/dist/components/button.js +3 -11
  25. package/dist/components/button.js.map +1 -1
  26. package/dist/components/chat-composer.js +141 -0
  27. package/dist/components/chat-composer.js.map +1 -0
  28. package/dist/components/chat-message.js +195 -0
  29. package/dist/components/chat-message.js.map +1 -0
  30. package/dist/components/chat-panel.js +137 -0
  31. package/dist/components/chat-panel.js.map +1 -0
  32. package/dist/components/chat-stream-text.js +69 -0
  33. package/dist/components/chat-stream-text.js.map +1 -0
  34. package/dist/components/chat-typing.js +26 -0
  35. package/dist/components/chat-typing.js.map +1 -0
  36. package/dist/components/command-palette.js +119 -0
  37. package/dist/components/command-palette.js.map +1 -0
  38. package/dist/components/command.js +3 -1
  39. package/dist/components/command.js.map +1 -1
  40. package/dist/components/confirm-dialog.js +123 -0
  41. package/dist/components/confirm-dialog.js.map +1 -0
  42. package/dist/components/context-menu.js +53 -5
  43. package/dist/components/context-menu.js.map +1 -1
  44. package/dist/components/dialog.js +3 -2
  45. package/dist/components/dialog.js.map +1 -1
  46. package/dist/components/drawer.js +3 -1
  47. package/dist/components/drawer.js.map +1 -1
  48. package/dist/components/dropdown-menu.js +64 -5
  49. package/dist/components/dropdown-menu.js.map +1 -1
  50. package/dist/components/form-row.js +51 -0
  51. package/dist/components/form-row.js.map +1 -0
  52. package/dist/components/header-controls.js +149 -0
  53. package/dist/components/header-controls.js.map +1 -0
  54. package/dist/components/icon-button.js +35 -0
  55. package/dist/components/icon-button.js.map +1 -0
  56. package/dist/components/key-value.js +68 -0
  57. package/dist/components/key-value.js.map +1 -0
  58. package/dist/components/menubar.js +39 -5
  59. package/dist/components/menubar.js.map +1 -1
  60. package/dist/components/native-select.js +3 -4
  61. package/dist/components/native-select.js.map +1 -1
  62. package/dist/components/nav-collapse.js +142 -0
  63. package/dist/components/nav-collapse.js.map +1 -0
  64. package/dist/components/nav-model.js.map +1 -1
  65. package/dist/components/navigation-menu.js +4 -5
  66. package/dist/components/navigation-menu.js.map +1 -1
  67. package/dist/components/page-frame.js +2 -5
  68. package/dist/components/page-frame.js.map +1 -1
  69. package/dist/components/popover-form.js +60 -0
  70. package/dist/components/popover-form.js.map +1 -0
  71. package/dist/components/resource-error.js +1 -1
  72. package/dist/components/resource-error.js.map +1 -1
  73. package/dist/components/section-nav.js +73 -20
  74. package/dist/components/section-nav.js.map +1 -1
  75. package/dist/components/select.js +16 -6
  76. package/dist/components/select.js.map +1 -1
  77. package/dist/components/settings.js +94 -0
  78. package/dist/components/settings.js.map +1 -0
  79. package/dist/components/sheet.js +3 -2
  80. package/dist/components/sheet.js.map +1 -1
  81. package/dist/components/shell-nav.js +80 -38
  82. package/dist/components/shell-nav.js.map +1 -1
  83. package/dist/components/smart-header.js +53 -0
  84. package/dist/components/smart-header.js.map +1 -0
  85. package/dist/components/status-item.js +42 -0
  86. package/dist/components/status-item.js.map +1 -0
  87. package/dist/components/system-banner.js +22 -12
  88. package/dist/components/system-banner.js.map +1 -1
  89. package/dist/components/top-bar.js +4 -3
  90. package/dist/components/top-bar.js.map +1 -1
  91. package/dist/components/ui-icons.js +77 -0
  92. package/dist/components/ui-icons.js.map +1 -0
  93. package/dist/components/use-count-tween.js +42 -0
  94. package/dist/components/use-count-tween.js.map +1 -0
  95. package/dist/components/workspace-tab.js +140 -0
  96. package/dist/components/workspace-tab.js.map +1 -0
  97. package/dist/generated/accent.js +2 -2
  98. package/dist/generated/accent.js.map +1 -1
  99. package/dist/generated/account-list.js +35 -0
  100. package/dist/generated/account-list.js.map +1 -0
  101. package/dist/generated/account-settings-dialog.js +27 -0
  102. package/dist/generated/account-settings-dialog.js.map +1 -0
  103. package/dist/generated/alert-dialog.js +1 -1
  104. package/dist/generated/alert-dialog.js.map +1 -1
  105. package/dist/generated/alert.js +2 -2
  106. package/dist/generated/alert.js.map +1 -1
  107. package/dist/generated/aside.js +36 -0
  108. package/dist/generated/aside.js.map +1 -0
  109. package/dist/generated/badge.js +7 -6
  110. package/dist/generated/badge.js.map +1 -1
  111. package/dist/generated/band.js +1 -1
  112. package/dist/generated/band.js.map +1 -1
  113. package/dist/generated/breadcrumb.js +2 -2
  114. package/dist/generated/breadcrumb.js.map +1 -1
  115. package/dist/generated/button-group.js +2 -2
  116. package/dist/generated/button-group.js.map +1 -1
  117. package/dist/generated/button.js +4 -4
  118. package/dist/generated/button.js.map +1 -1
  119. package/dist/generated/card.js +6 -6
  120. package/dist/generated/card.js.map +1 -1
  121. package/dist/generated/chat-composer.js +21 -0
  122. package/dist/generated/chat-composer.js.map +1 -0
  123. package/dist/generated/chat-message.js +86 -0
  124. package/dist/generated/chat-message.js.map +1 -0
  125. package/dist/generated/chat-typing.js +9 -0
  126. package/dist/generated/chat-typing.js.map +1 -0
  127. package/dist/generated/command-palette.js +17 -0
  128. package/dist/generated/command-palette.js.map +1 -0
  129. package/dist/generated/command.js +3 -3
  130. package/dist/generated/command.js.map +1 -1
  131. package/dist/generated/confirm-dialog.js +28 -0
  132. package/dist/generated/confirm-dialog.js.map +1 -0
  133. package/dist/generated/context-menu.js +1 -1
  134. package/dist/generated/context-menu.js.map +1 -1
  135. package/dist/generated/date-time-picker.js +1 -1
  136. package/dist/generated/date-time-picker.js.map +1 -1
  137. package/dist/generated/dialog.js +1 -1
  138. package/dist/generated/dialog.js.map +1 -1
  139. package/dist/generated/drawer.js +1 -1
  140. package/dist/generated/drawer.js.map +1 -1
  141. package/dist/generated/dropdown-menu.js +1 -1
  142. package/dist/generated/dropdown-menu.js.map +1 -1
  143. package/dist/generated/form-row.js +17 -0
  144. package/dist/generated/form-row.js.map +1 -0
  145. package/dist/generated/header-controls.js +56 -0
  146. package/dist/generated/header-controls.js.map +1 -0
  147. package/dist/generated/icon-button.js +13 -0
  148. package/dist/generated/icon-button.js.map +1 -0
  149. package/dist/generated/key-value.js +15 -0
  150. package/dist/generated/key-value.js.map +1 -0
  151. package/dist/generated/menubar.js +1 -1
  152. package/dist/generated/menubar.js.map +1 -1
  153. package/dist/generated/navigation-menu.js +1 -1
  154. package/dist/generated/navigation-menu.js.map +1 -1
  155. package/dist/generated/popover-form.js +13 -0
  156. package/dist/generated/popover-form.js.map +1 -0
  157. package/dist/generated/section-label.js +10 -0
  158. package/dist/generated/section-label.js.map +1 -0
  159. package/dist/generated/select.js +15 -3
  160. package/dist/generated/select.js.map +1 -1
  161. package/dist/generated/settings.js +36 -0
  162. package/dist/generated/settings.js.map +1 -0
  163. package/dist/generated/sheet.js +1 -1
  164. package/dist/generated/sheet.js.map +1 -1
  165. package/dist/generated/slider.js +1 -1
  166. package/dist/generated/slider.js.map +1 -1
  167. package/dist/generated/smart-header.js +11 -0
  168. package/dist/generated/smart-header.js.map +1 -0
  169. package/dist/generated/sonner.js +4 -4
  170. package/dist/generated/sonner.js.map +1 -1
  171. package/dist/generated/status-item.js +17 -0
  172. package/dist/generated/status-item.js.map +1 -0
  173. package/dist/generated/system-banner.js +33 -0
  174. package/dist/generated/system-banner.js.map +1 -0
  175. package/dist/generated/top-bar.js +9 -0
  176. package/dist/generated/top-bar.js.map +1 -0
  177. package/dist/generated/workspace-tab.js +17 -0
  178. package/dist/generated/workspace-tab.js.map +1 -0
  179. package/dist/index.d.ts +852 -156
  180. package/dist/index.js +21 -1
  181. package/dist/lib/cn.js +8 -1
  182. package/dist/lib/cn.js.map +1 -1
  183. package/dist/lib/number-parts.js +90 -0
  184. package/dist/lib/number-parts.js.map +1 -0
  185. package/dist/palette-cli.js +1 -1
  186. package/dist/palette.d.ts +6 -4
  187. package/dist/palette.js +3 -1
  188. package/dist/primitives/dismissable-layer.js +76 -4
  189. package/dist/primitives/dismissable-layer.js.map +1 -1
  190. package/dist/primitives/focus-scope.js +20 -5
  191. package/dist/primitives/focus-scope.js.map +1 -1
  192. package/dist/primitives/use-floating.js +16 -1
  193. package/dist/primitives/use-floating.js.map +1 -1
  194. package/dist/primitives/use-reduced-motion.js +20 -0
  195. package/dist/primitives/use-reduced-motion.js.map +1 -0
  196. package/package.json +1 -1
  197. package/styles/ev.css +281 -49
  198. package/styles/theme.css +335 -39
  199. package/styles/tokens.css +643 -103
  200. package/dist/chunk-3IPB4QQJ.js.map +0 -1
  201. package/dist/components/terminal.js +0 -206
  202. package/dist/components/terminal.js.map +0 -1
  203. package/dist/generated/terminal.js +0 -70
  204. package/dist/generated/terminal.js.map +0 -1
@@ -0,0 +1,142 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { useControllableState } from "../primitives/use-controllable-state.js";
6
+ function useNavGroupOpen(group, containsCurrent) {
7
+ const [open, setOpen] = useControllableState({
8
+ value: group.open,
9
+ defaultValue: group.defaultOpen ?? containsCurrent,
10
+ onChange: group.onOpenChange
11
+ });
12
+ const hadCurrent = React.useRef(containsCurrent);
13
+ React.useEffect(() => {
14
+ if (containsCurrent && !hadCurrent.current && !open) setOpen(true);
15
+ hadCurrent.current = containsCurrent;
16
+ }, [containsCurrent, open, setOpen]);
17
+ return [open, setOpen];
18
+ }
19
+ const MOTION = "duration-(--ev-dur-fast) ease-(--ev-ease-in-out) motion-reduce:transition-none";
20
+ function NavCollapse({
21
+ open,
22
+ id,
23
+ children
24
+ }) {
25
+ const ref = React.useRef(null);
26
+ React.useLayoutEffect(() => {
27
+ ref.current?.toggleAttribute("inert", !open);
28
+ }, [open]);
29
+ return /* @__PURE__ */ jsx(
30
+ "div",
31
+ {
32
+ ref,
33
+ id,
34
+ "data-slot": "nav-collapse",
35
+ "data-state": open ? "open" : "closed",
36
+ className: cn(
37
+ // minmax(0, …): a plain `1fr` column grows to the widest label and the rail overflows.
38
+ "grid grid-cols-[minmax(0,1fr)] transition-[grid-template-rows]",
39
+ MOTION,
40
+ open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
41
+ ),
42
+ children: /* @__PURE__ */ jsx(
43
+ "div",
44
+ {
45
+ className: cn(
46
+ "min-h-0 overflow-y-clip transition-[opacity,visibility]",
47
+ MOTION,
48
+ open ? "visible opacity-100" : "invisible opacity-0"
49
+ ),
50
+ children
51
+ }
52
+ )
53
+ }
54
+ );
55
+ }
56
+ function Chevron({ open }) {
57
+ return /* @__PURE__ */ jsx(
58
+ "svg",
59
+ {
60
+ "aria-hidden": true,
61
+ viewBox: "0 0 24 24",
62
+ fill: "none",
63
+ stroke: "currentColor",
64
+ strokeWidth: 2,
65
+ strokeLinecap: "round",
66
+ strokeLinejoin: "round",
67
+ className: cn("size-4 transition-transform", MOTION, open && "rotate-90"),
68
+ children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
69
+ }
70
+ );
71
+ }
72
+ const ROW = "flex min-h-9 items-center rounded-(--control-radius) text-body font-medium text-ink transition-colors motion-reduce:transition-none";
73
+ const FOCUS = "outline-none focus-visible:ring-2 focus-visible:ring-ring";
74
+ function NavGroupHeader({
75
+ label,
76
+ labelId,
77
+ regionId,
78
+ open,
79
+ onToggle,
80
+ href,
81
+ linkComponent,
82
+ current,
83
+ labels,
84
+ className
85
+ }) {
86
+ const chevronId = React.useId();
87
+ const row = cn(ROW, open ? "bg-active" : "hover:bg-hover", className);
88
+ const chip = cn(
89
+ "grid size-7 shrink-0 place-items-center rounded-md text-ink-mid",
90
+ open && "bg-selected"
91
+ );
92
+ if (href === void 0) {
93
+ return /* @__PURE__ */ jsxs(
94
+ "button",
95
+ {
96
+ type: "button",
97
+ "data-slot": "nav-group-header",
98
+ "aria-expanded": open,
99
+ "aria-controls": regionId,
100
+ onClick: onToggle,
101
+ className: cn(row, FOCUS, "w-full gap-2 py-1 pl-3 pr-1.5 text-left"),
102
+ children: [
103
+ /* @__PURE__ */ jsx("span", { id: labelId, className: "min-w-0 flex-1 truncate", children: label }),
104
+ /* @__PURE__ */ jsx("span", { className: chip, children: /* @__PURE__ */ jsx(Chevron, { open }) })
105
+ ]
106
+ }
107
+ );
108
+ }
109
+ const Link = linkComponent ?? "a";
110
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "nav-group-header", className: cn(row, "gap-1 pr-1.5"), children: [
111
+ /* @__PURE__ */ jsx(
112
+ Link,
113
+ {
114
+ id: labelId,
115
+ href,
116
+ "aria-current": current ? "page" : void 0,
117
+ className: cn("min-w-0 flex-1 truncate rounded-(--control-radius) py-2 pl-3", FOCUS),
118
+ children: label
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx(
122
+ "button",
123
+ {
124
+ type: "button",
125
+ id: chevronId,
126
+ "aria-label": open ? labels?.collapse ?? "Collapse" : labels?.expand ?? "Expand",
127
+ "aria-labelledby": `${chevronId} ${labelId}`,
128
+ "aria-expanded": open,
129
+ "aria-controls": regionId,
130
+ onClick: onToggle,
131
+ className: cn(chip, FOCUS, !open && "hover:bg-hover"),
132
+ children: /* @__PURE__ */ jsx(Chevron, { open })
133
+ }
134
+ )
135
+ ] });
136
+ }
137
+ export {
138
+ NavCollapse,
139
+ NavGroupHeader,
140
+ useNavGroupOpen
141
+ };
142
+ //# sourceMappingURL=nav-collapse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/nav-collapse.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\n\n/** What a nav group takes to become a section that opens and closes. */\nexport interface NavGroupCollapse {\n /**\n * Draws the group's `label` as a header with a chevron and lets the rows fold\n * away. Needs a `label`. Off by default so an existing rail keeps its static\n * eyebrow.\n */\n collapsible?: boolean;\n /** Open on mount. Default: open exactly when the group holds the current item. */\n defaultOpen?: boolean;\n /** Controlled open state; pair with `onOpenChange`. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** A landing page: the label navigates to it and only the chevron toggles. */\n href?: string;\n}\n\nexport interface NavCollapseLabels {\n /** Spoken before the group's name on a closed section's chevron. Default `\"Expand\"`. */\n expand?: string;\n /** Spoken before the group's name on an open section's chevron. Default `\"Collapse\"`. */\n collapse?: string;\n}\n\n/**\n * Open state of one collapsible group. A route change that lands inside a closed\n * group opens it; a user who closes the group holding the current row is not\n * fought, because only the move INTO the group opens it.\n */\nexport function useNavGroupOpen(\n group: NavGroupCollapse,\n containsCurrent: boolean,\n): readonly [open: boolean, setOpen: (next: boolean) => void] {\n const [open, setOpen] = useControllableState<boolean>({\n value: group.open,\n defaultValue: group.defaultOpen ?? containsCurrent,\n onChange: group.onOpenChange,\n });\n const hadCurrent = React.useRef(containsCurrent);\n React.useEffect(() => {\n if (containsCurrent && !hadCurrent.current && !open) setOpen(true);\n hadCurrent.current = containsCurrent;\n }, [containsCurrent, open, setOpen]);\n return [open, setOpen] as const;\n}\n\nconst MOTION =\n \"duration-(--ev-dur-fast) ease-(--ev-ease-in-out) motion-reduce:transition-none\";\n\n/**\n * The folding body. Height runs on a `0fr → 1fr` grid track (no measuring), the\n * content fades, and `visibility` flips at the END of the close so the hidden\n * rows leave the tab order without cutting the fade. `inert` is set at once so\n * a closing region cannot take focus or be read while it fades.\n */\nexport function NavCollapse({\n open,\n id,\n children,\n}: {\n open: boolean;\n id: string;\n children: React.ReactNode;\n}) {\n const ref = React.useRef<HTMLDivElement>(null);\n // `inert` as a React prop is a boolean in 19 and unknown in 18; the DOM\n // attribute works in both.\n React.useLayoutEffect(() => {\n ref.current?.toggleAttribute(\"inert\", !open);\n }, [open]);\n return (\n <div\n ref={ref}\n id={id}\n data-slot=\"nav-collapse\"\n data-state={open ? \"open\" : \"closed\"}\n className={cn(\n // minmax(0, …): a plain `1fr` column grows to the widest label and the rail overflows.\n \"grid grid-cols-[minmax(0,1fr)] transition-[grid-template-rows]\",\n MOTION,\n open ? \"grid-rows-[1fr]\" : \"grid-rows-[0fr]\",\n )}\n >\n {/* `overflow-y-clip` keeps the x axis visible so a focus ring on a row is not cut. */}\n <div\n className={cn(\n \"min-h-0 overflow-y-clip transition-[opacity,visibility]\",\n MOTION,\n open ? \"visible opacity-100\" : \"invisible opacity-0\",\n )}\n >\n {children}\n </div>\n </div>\n );\n}\n\nfunction Chevron({ open }: { open: boolean }) {\n return (\n <svg\n aria-hidden\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn(\"size-4 transition-transform\", MOTION, open && \"rotate-90\")}\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n );\n}\n\nconst ROW = \"flex min-h-9 items-center rounded-(--control-radius) text-body font-medium text-ink transition-colors motion-reduce:transition-none\";\nconst FOCUS = \"outline-none focus-visible:ring-2 focus-visible:ring-ring\";\n\nexport interface NavGroupHeaderProps {\n label: React.ReactNode;\n labelId: string;\n regionId: string;\n open: boolean;\n onToggle: () => void;\n href?: string | undefined;\n linkComponent?: React.ElementType | undefined;\n /** The landing page is the current page. */\n current?: boolean | undefined;\n labels?: NavCollapseLabels | undefined;\n className?: string | undefined;\n}\n\n/**\n * The section's header row. With an `href` the label is a link and the chevron\n * is its own button; without one the whole row is the button and the chevron is\n * a picture of its state.\n */\nexport function NavGroupHeader({\n label,\n labelId,\n regionId,\n open,\n onToggle,\n href,\n linkComponent,\n current,\n labels,\n className,\n}: NavGroupHeaderProps) {\n const chevronId = React.useId();\n const row = cn(ROW, open ? \"bg-active\" : \"hover:bg-hover\", className);\n const chip = cn(\n \"grid size-7 shrink-0 place-items-center rounded-md text-ink-mid\",\n open && \"bg-selected\",\n );\n if (href === undefined) {\n return (\n <button\n type=\"button\"\n data-slot=\"nav-group-header\"\n aria-expanded={open}\n aria-controls={regionId}\n onClick={onToggle}\n className={cn(row, FOCUS, \"w-full gap-2 py-1 pl-3 pr-1.5 text-left\")}\n >\n <span id={labelId} className=\"min-w-0 flex-1 truncate\">\n {label}\n </span>\n <span className={chip}>\n <Chevron open={open} />\n </span>\n </button>\n );\n }\n const Link: React.ElementType = linkComponent ?? \"a\";\n return (\n <div data-slot=\"nav-group-header\" className={cn(row, \"gap-1 pr-1.5\")}>\n <Link\n id={labelId}\n href={href}\n aria-current={current ? \"page\" : undefined}\n className={cn(\"min-w-0 flex-1 truncate rounded-(--control-radius) py-2 pl-3\", FOCUS)}\n >\n {label}\n </Link>\n {/* Named \"Expand Guides\": the button's own aria-label, then the label's text. */}\n <button\n type=\"button\"\n id={chevronId}\n aria-label={open ? (labels?.collapse ?? \"Collapse\") : (labels?.expand ?? \"Expand\")}\n aria-labelledby={`${chevronId} ${labelId}`}\n aria-expanded={open}\n aria-controls={regionId}\n onClick={onToggle}\n className={cn(chip, FOCUS, !open && \"hover:bg-hover\")}\n >\n <Chevron open={open} />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/nav-model.ts"],"sourcesContent":["import type * as React from \"react\";\n\n/**\n * How a {@link NavItem} decides it is the current one.\n *\n * - `\"prefix\"` (default) — the path is the item's own or lies beneath it, on a\n * segment boundary: `/invest` claims `/invest/arb` but not `/investors`. An\n * item at `/` is exact, or it would claim every page.\n * - `\"exact\"` — the path is the item's own and nothing beneath it.\n * - a function — whatever it answers for the path.\n */\nexport type NavMatch = \"exact\" | \"prefix\" | ((pathname: string) => boolean);\n\n/** State a row is rendered in; what `trailing` and `renderItem` are told. */\nexport interface NavItemState {\n active: boolean;\n /** Marked by a click whose navigation has not landed yet. */\n pending: boolean;\n}\n\n/** One destination, shared by the rail ({@link ShellNav}) and the tab bar. */\nexport interface NavItem {\n /** Stable key; also what `onNavigate` / `onItemIntent` hand back. */\n id: string;\n href: string;\n label: React.ReactNode;\n icon?: React.ComponentType<{ className?: string }>;\n /** A count (`NavBadge`, capped at 99+, `0` draws nothing) or your own node. */\n badge?: number | React.ReactNode;\n /** Anything after the label — a status chip; a function is told the row's state. */\n trailing?: React.ReactNode | ((state: NavItemState) => React.ReactNode);\n /**\n * Leaves the app: rendered as a plain `<a>` (never `linkComponent`), never\n * current, never marked pending — a `mailto:`, a docs site.\n */\n external?: boolean;\n /** Passed to the anchor; `_blank` gets `rel=\"noopener noreferrer\"`. */\n target?: React.HTMLAttributeAnchorTarget;\n match?: NavMatch;\n /**\n * More paths this item claims, prefix-matched — the one tab standing in for\n * a whole group of rail rows (`[\"/profile\", \"/notifications\"]`).\n */\n also?: readonly string[];\n /** Rendered, not focusable, never current. */\n disabled?: boolean;\n}\n\n/** A titled run of items; the rail draws one marker per group. */\nexport interface NavGroup {\n id: string;\n /** The eyebrow above the rows; also the list's accessible name. */\n label?: React.ReactNode;\n items: readonly NavItem[];\n}\n\nfunction pathOf(href: string): string {\n const cut = href.search(/[?#]/);\n const path = cut === -1 ? href : href.slice(0, cut);\n return path.length > 1 && path.endsWith(\"/\") ? path.slice(0, -1) : path;\n}\n\nfunction underPrefix(pathname: string, prefix: string): boolean {\n if (prefix === \"/\") return pathname === \"/\";\n return pathname === prefix || pathname.startsWith(`${prefix}/`);\n}\n\n// An exact hit outranks any prefix; among prefixes the longest wins. A function\n// match ranks as a prefix hit on the item's own href.\nconst EXACT = Number.MAX_SAFE_INTEGER;\n\n/**\n * How strongly `item` claims `pathname`, or `null` when it does not. Used to\n * pick ONE current item when several match — the rail and the tab bar each\n * draw a single marker, so two current rows is not a state they can show.\n */\nexport function navItemMatch(item: NavItem, pathname: string): number | null {\n if (item.external || item.disabled) return null;\n const path = pathOf(pathname);\n const own = pathOf(item.href);\n const match = item.match ?? \"prefix\";\n let score: number | null = null;\n if (typeof match === \"function\") score = match(path) ? own.length : null;\n else if (path === own) score = EXACT;\n else if (match === \"prefix\" && underPrefix(path, own)) score = own.length;\n for (const extra of item.also ?? []) {\n const p = pathOf(extra);\n if (underPrefix(path, p) && (score === null || p.length > score)) score = p.length;\n }\n return score;\n}\n\n/** Whether `item` claims `pathname` at all, regardless of its neighbours. */\nexport function isNavItemActive(item: NavItem, pathname: string): boolean {\n return navItemMatch(item, pathname) !== null;\n}\n\n/** The single current item for `pathname`: the strongest claim, ties to the first. */\nexport function resolveActiveNavItem(\n items: Iterable<NavItem>,\n pathname: string,\n): NavItem | undefined {\n let best: NavItem | undefined;\n let bestScore = -1;\n for (const item of items) {\n const score = navItemMatch(item, pathname);\n if (score !== null && score > bestScore) {\n best = item;\n bestScore = score;\n }\n }\n return best;\n}\n\n/**\n * A click the app should handle in place. A modified or non-primary click opens\n * the page elsewhere (a tab, a window, the context menu) and the router lets the\n * browser have it, so the current row stays current; `defaultPrevented` is the\n * same courtesy to anything upstream that claimed the click.\n */\nexport function isPlainLeftClick(e: React.MouseEvent): boolean {\n return !(e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0);\n}\n\n/** Props the rail and the tab bar hand a `linkComponent`; `next/link` takes all of them. */\nexport function anchorTargetProps(\n item: Pick<NavItem, \"target\">,\n): { target?: React.HTMLAttributeAnchorTarget; rel?: string } {\n if (item.target === undefined) return {};\n return item.target === \"_blank\"\n ? { target: item.target, rel: \"noopener noreferrer\" }\n : { target: item.target };\n}\n"],"mappings":"AAwDA,SAAS,OAAO,MAAsB;AACpC,QAAM,MAAM,KAAK,OAAO,MAAM;AAC9B,QAAM,OAAO,QAAQ,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG;AAClD,SAAO,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AACrE;AAEA,SAAS,YAAY,UAAkB,QAAyB;AAC9D,MAAI,WAAW,IAAK,QAAO,aAAa;AACxC,SAAO,aAAa,UAAU,SAAS,WAAW,GAAG,MAAM,GAAG;AAChE;AAIA,MAAM,QAAQ,OAAO;AAOd,SAAS,aAAa,MAAe,UAAiC;AAC3E,MAAI,KAAK,YAAY,KAAK,SAAU,QAAO;AAC3C,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,MAAM,OAAO,KAAK,IAAI;AAC5B,QAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,QAAuB;AAC3B,MAAI,OAAO,UAAU,WAAY,SAAQ,MAAM,IAAI,IAAI,IAAI,SAAS;AAAA,WAC3D,SAAS,IAAK,SAAQ;AAAA,WACtB,UAAU,YAAY,YAAY,MAAM,GAAG,EAAG,SAAQ,IAAI;AACnE,aAAW,SAAS,KAAK,QAAQ,CAAC,GAAG;AACnC,UAAM,IAAI,OAAO,KAAK;AACtB,QAAI,YAAY,MAAM,CAAC,MAAM,UAAU,QAAQ,EAAE,SAAS,OAAQ,SAAQ,EAAE;AAAA,EAC9E;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,MAAe,UAA2B;AACxE,SAAO,aAAa,MAAM,QAAQ,MAAM;AAC1C;AAGO,SAAS,qBACd,OACA,UACqB;AACrB,MAAI;AACJ,MAAI,YAAY;AAChB,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,aAAa,MAAM,QAAQ;AACzC,QAAI,UAAU,QAAQ,QAAQ,WAAW;AACvC,aAAO;AACP,kBAAY;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,iBAAiB,GAA8B;AAC7D,SAAO,EAAE,EAAE,oBAAoB,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW;AAClG;AAGO,SAAS,kBACd,MAC4D;AAC5D,MAAI,KAAK,WAAW,OAAW,QAAO,CAAC;AACvC,SAAO,KAAK,WAAW,WACnB,EAAE,QAAQ,KAAK,QAAQ,KAAK,sBAAsB,IAClD,EAAE,QAAQ,KAAK,OAAO;AAC5B;","names":[]}
1
+ {"version":3,"sources":["../../src/components/nav-model.ts"],"sourcesContent":["import type * as React from \"react\";\nimport type { NavGroupCollapse } from \"./nav-collapse\";\n\n/**\n * How a {@link NavItem} decides it is the current one.\n *\n * - `\"prefix\"` (default) — the path is the item's own or lies beneath it, on a\n * segment boundary: `/invest` claims `/invest/arb` but not `/investors`. An\n * item at `/` is exact, or it would claim every page.\n * - `\"exact\"` — the path is the item's own and nothing beneath it.\n * - a function — whatever it answers for the path.\n */\nexport type NavMatch = \"exact\" | \"prefix\" | ((pathname: string) => boolean);\n\n/** State a row is rendered in; what `trailing` and `renderItem` are told. */\nexport interface NavItemState {\n active: boolean;\n /** Marked by a click whose navigation has not landed yet. */\n pending: boolean;\n}\n\n/** One destination, shared by the rail ({@link ShellNav}) and the tab bar. */\nexport interface NavItem {\n /** Stable key; also what `onNavigate` / `onItemIntent` hand back. */\n id: string;\n href: string;\n label: React.ReactNode;\n icon?: React.ComponentType<{ className?: string }>;\n /** A count (`NavBadge`, capped at 99+, `0` draws nothing) or your own node. */\n badge?: number | React.ReactNode;\n /** Anything after the label — a status chip; a function is told the row's state. */\n trailing?: React.ReactNode | ((state: NavItemState) => React.ReactNode);\n /**\n * Leaves the app: rendered as a plain `<a>` (never `linkComponent`), never\n * current, never marked pending — a `mailto:`, a docs site.\n */\n external?: boolean;\n /** Passed to the anchor; `_blank` gets `rel=\"noopener noreferrer\"`. */\n target?: React.HTMLAttributeAnchorTarget;\n match?: NavMatch;\n /**\n * More paths this item claims, prefix-matched — the one tab standing in for\n * a whole group of rail rows (`[\"/profile\", \"/notifications\"]`).\n */\n also?: readonly string[];\n /** Rendered, not focusable, never current. */\n disabled?: boolean;\n}\n\n/** A titled run of items; the rail draws one marker per group. */\nexport interface NavGroup extends NavGroupCollapse {\n id: string;\n /** The eyebrow above the rows; also the list's accessible name. */\n label?: React.ReactNode;\n items: readonly NavItem[];\n}\n\nfunction pathOf(href: string): string {\n const cut = href.search(/[?#]/);\n const path = cut === -1 ? href : href.slice(0, cut);\n return path.length > 1 && path.endsWith(\"/\") ? path.slice(0, -1) : path;\n}\n\nfunction underPrefix(pathname: string, prefix: string): boolean {\n if (prefix === \"/\") return pathname === \"/\";\n return pathname === prefix || pathname.startsWith(`${prefix}/`);\n}\n\n// An exact hit outranks any prefix; among prefixes the longest wins. A function\n// match ranks as a prefix hit on the item's own href.\nconst EXACT = Number.MAX_SAFE_INTEGER;\n\n/**\n * How strongly `item` claims `pathname`, or `null` when it does not. Used to\n * pick ONE current item when several match — the rail and the tab bar each\n * draw a single marker, so two current rows is not a state they can show.\n */\nexport function navItemMatch(item: NavItem, pathname: string): number | null {\n if (item.external || item.disabled) return null;\n const path = pathOf(pathname);\n const own = pathOf(item.href);\n const match = item.match ?? \"prefix\";\n let score: number | null = null;\n if (typeof match === \"function\") score = match(path) ? own.length : null;\n else if (path === own) score = EXACT;\n else if (match === \"prefix\" && underPrefix(path, own)) score = own.length;\n for (const extra of item.also ?? []) {\n const p = pathOf(extra);\n if (underPrefix(path, p) && (score === null || p.length > score)) score = p.length;\n }\n return score;\n}\n\n/** Whether `item` claims `pathname` at all, regardless of its neighbours. */\nexport function isNavItemActive(item: NavItem, pathname: string): boolean {\n return navItemMatch(item, pathname) !== null;\n}\n\n/** The single current item for `pathname`: the strongest claim, ties to the first. */\nexport function resolveActiveNavItem(\n items: Iterable<NavItem>,\n pathname: string,\n): NavItem | undefined {\n let best: NavItem | undefined;\n let bestScore = -1;\n for (const item of items) {\n const score = navItemMatch(item, pathname);\n if (score !== null && score > bestScore) {\n best = item;\n bestScore = score;\n }\n }\n return best;\n}\n\n/**\n * A click the app should handle in place. A modified or non-primary click opens\n * the page elsewhere (a tab, a window, the context menu) and the router lets the\n * browser have it, so the current row stays current; `defaultPrevented` is the\n * same courtesy to anything upstream that claimed the click.\n */\nexport function isPlainLeftClick(e: React.MouseEvent): boolean {\n return !(e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0);\n}\n\n/** Props the rail and the tab bar hand a `linkComponent`; `next/link` takes all of them. */\nexport function anchorTargetProps(\n item: Pick<NavItem, \"target\">,\n): { target?: React.HTMLAttributeAnchorTarget; rel?: string } {\n if (item.target === undefined) return {};\n return item.target === \"_blank\"\n ? { target: item.target, rel: \"noopener noreferrer\" }\n : { target: item.target };\n}\n"],"mappings":"AAyDA,SAAS,OAAO,MAAsB;AACpC,QAAM,MAAM,KAAK,OAAO,MAAM;AAC9B,QAAM,OAAO,QAAQ,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG;AAClD,SAAO,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AACrE;AAEA,SAAS,YAAY,UAAkB,QAAyB;AAC9D,MAAI,WAAW,IAAK,QAAO,aAAa;AACxC,SAAO,aAAa,UAAU,SAAS,WAAW,GAAG,MAAM,GAAG;AAChE;AAIA,MAAM,QAAQ,OAAO;AAOd,SAAS,aAAa,MAAe,UAAiC;AAC3E,MAAI,KAAK,YAAY,KAAK,SAAU,QAAO;AAC3C,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,MAAM,OAAO,KAAK,IAAI;AAC5B,QAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,QAAuB;AAC3B,MAAI,OAAO,UAAU,WAAY,SAAQ,MAAM,IAAI,IAAI,IAAI,SAAS;AAAA,WAC3D,SAAS,IAAK,SAAQ;AAAA,WACtB,UAAU,YAAY,YAAY,MAAM,GAAG,EAAG,SAAQ,IAAI;AACnE,aAAW,SAAS,KAAK,QAAQ,CAAC,GAAG;AACnC,UAAM,IAAI,OAAO,KAAK;AACtB,QAAI,YAAY,MAAM,CAAC,MAAM,UAAU,QAAQ,EAAE,SAAS,OAAQ,SAAQ,EAAE;AAAA,EAC9E;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,MAAe,UAA2B;AACxE,SAAO,aAAa,MAAM,QAAQ,MAAM;AAC1C;AAGO,SAAS,qBACd,OACA,UACqB;AACrB,MAAI;AACJ,MAAI,YAAY;AAChB,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,aAAa,MAAM,QAAQ;AACzC,QAAI,UAAU,QAAQ,QAAQ,WAAW;AACvC,aAAO;AACP,kBAAY;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,iBAAiB,GAA8B;AAC7D,SAAO,EAAE,EAAE,oBAAoB,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW;AAClG;AAGO,SAAS,kBACd,MAC4D;AAC5D,MAAI,KAAK,WAAW,OAAW,QAAO,CAAC;AACvC,SAAO,KAAK,WAAW,WACnB,EAAE,QAAQ,KAAK,QAAQ,KAAK,sBAAsB,IAClD,EAAE,QAAQ,KAAK,OAAO;AAC5B;","names":[]}
@@ -29,10 +29,12 @@ function useItem() {
29
29
  function NavigationMenu({
30
30
  className,
31
31
  children,
32
+ // Content renders inline under its trigger, so no shared viewport is mounted; the flag
33
+ // stays on the DOM for consumers' selectors.
32
34
  viewport = true,
33
35
  ...props
34
36
  }) {
35
- return /* @__PURE__ */ jsxs(
37
+ return /* @__PURE__ */ jsx(
36
38
  "nav",
37
39
  {
38
40
  "data-slot": "navigation-menu",
@@ -43,10 +45,7 @@ function NavigationMenu({
43
45
  className
44
46
  ),
45
47
  ...props,
46
- children: [
47
- children,
48
- viewport && /* @__PURE__ */ jsx(NavigationMenuViewport, {})
49
- ]
48
+ children
50
49
  }
51
50
  );
52
51
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/navigation-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { DismissableLayerScope, useDismissableLayer } from \"../primitives/dismissable-layer\";\nimport { usePresence } from \"../primitives/presence\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Slot } from \"../primitives/slot\";\nimport {\n NAVIGATION_MENU,\n NAVIGATION_MENU_CONTENT,\n NAVIGATION_MENU_INDICATOR,\n NAVIGATION_MENU_ITEM,\n NAVIGATION_MENU_LINK,\n NAVIGATION_MENU_LIST,\n NAVIGATION_MENU_TRIGGER_STYLE,\n NAVIGATION_MENU_VIEWPORT,\n} from \"../generated/navigation-menu\";\n\n/**\n * Fused canonical trigger class string, the mirror of Rust's\n * `navigation_menu_trigger_style()`. Reuse it to style a plain link the same way\n * a `NavigationMenuTrigger` is styled.\n */\nexport function navigationMenuTriggerStyle(): string {\n return NAVIGATION_MENU_TRIGGER_STYLE;\n}\n\ninterface ItemCtx {\n open: boolean;\n setOpen: (open: boolean) => void;\n triggerRef: React.RefObject<HTMLButtonElement | null>;\n}\nconst NavigationMenuItemContext = React.createContext<ItemCtx | null>(null);\nfunction useItem(): ItemCtx {\n const ctx = React.useContext(NavigationMenuItemContext);\n if (!ctx) throw new Error(\"NavigationMenu parts must be used within <NavigationMenuItem>\");\n return ctx;\n}\n\nexport function NavigationMenu({\n className,\n children,\n viewport = true,\n ...props\n}: React.ComponentProps<\"nav\"> & { viewport?: boolean }) {\n return (\n <nav\n data-slot=\"navigation-menu\"\n data-viewport={viewport}\n role=\"navigation\"\n className={cn(\n NAVIGATION_MENU,\n className,\n )}\n {...props}\n >\n {children}\n {viewport && <NavigationMenuViewport />}\n </nav>\n );\n}\n\nexport function NavigationMenuList({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"navigation-menu-list\"\n role=\"list\"\n className={cn(\n NAVIGATION_MENU_LIST,\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface NavigationMenuItemProps extends React.ComponentProps<\"li\"> {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\nexport function NavigationMenuItem({\n className,\n open,\n defaultOpen = false,\n onOpenChange,\n children,\n ...props\n}: NavigationMenuItemProps) {\n const [isOpen, setOpen] = useControllableState({\n value: open,\n defaultValue: defaultOpen,\n onChange: onOpenChange,\n });\n const triggerRef = React.useRef<HTMLButtonElement | null>(null);\n return (\n <NavigationMenuItemContext.Provider value={{ open: isOpen, setOpen, triggerRef }}>\n <li data-slot=\"navigation-menu-item\" className={cn(NAVIGATION_MENU_ITEM, className)} {...props}>\n {children}\n </li>\n </NavigationMenuItemContext.Provider>\n );\n}\n\nexport function NavigationMenuTrigger({\n className,\n children,\n onClick,\n ...props\n}: React.ComponentProps<\"button\">) {\n const { open, setOpen, triggerRef } = useItem();\n return (\n <button\n ref={triggerRef}\n type=\"button\"\n data-slot=\"navigation-menu-trigger\"\n data-state={open ? \"open\" : \"closed\"}\n className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n onClick={(e) => {\n onClick?.(e);\n setOpen(!open);\n }}\n {...props}\n >\n {children}{\" \"}\n <svg\n className=\"relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n );\n}\n\nexport function NavigationMenuContent({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { open, setOpen, triggerRef } = useItem();\n const { isPresent, ref: presenceRef } = usePresence(open);\n const dismissRef = useDismissableLayer({\n enabled: open,\n onDismiss: () => setOpen(false),\n exclude: [triggerRef],\n });\n if (!isPresent) return null;\n return (\n <div\n ref={mergeRefs(presenceRef, dismissRef)}\n data-slot=\"navigation-menu-content\"\n data-state={open ? \"open\" : \"closed\"}\n className={cn(\n NAVIGATION_MENU_CONTENT,\n className,\n )}\n {...props}\n >\n <DismissableLayerScope layer={dismissRef}>{children}</DismissableLayerScope>\n </div>\n );\n}\n\nexport function NavigationMenuViewport({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div className=\"absolute top-full left-0 isolate z-50 flex justify-center\">\n <div\n data-slot=\"navigation-menu-viewport\"\n className={cn(\n NAVIGATION_MENU_VIEWPORT,\n className,\n )}\n {...props}\n />\n </div>\n );\n}\n\nexport function NavigationMenuLink({\n className,\n asChild = false,\n ...props\n}: React.ComponentProps<\"a\"> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : \"a\";\n return (\n <Comp\n data-slot=\"navigation-menu-link\"\n className={cn(\n NAVIGATION_MENU_LINK,\n className,\n )}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport function NavigationMenuIndicator({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"navigation-menu-indicator\"\n className={cn(\n NAVIGATION_MENU_INDICATOR,\n className,\n )}\n {...props}\n >\n <div className=\"bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md\" />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/navigation-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { DismissableLayerScope, useDismissableLayer } from \"../primitives/dismissable-layer\";\nimport { usePresence } from \"../primitives/presence\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Slot } from \"../primitives/slot\";\nimport {\n NAVIGATION_MENU,\n NAVIGATION_MENU_CONTENT,\n NAVIGATION_MENU_INDICATOR,\n NAVIGATION_MENU_ITEM,\n NAVIGATION_MENU_LINK,\n NAVIGATION_MENU_LIST,\n NAVIGATION_MENU_TRIGGER_STYLE,\n NAVIGATION_MENU_VIEWPORT,\n} from \"../generated/navigation-menu\";\n\n/**\n * Fused canonical trigger class string, the mirror of Rust's\n * `navigation_menu_trigger_style()`. Reuse it to style a plain link the same way\n * a `NavigationMenuTrigger` is styled.\n */\nexport function navigationMenuTriggerStyle(): string {\n return NAVIGATION_MENU_TRIGGER_STYLE;\n}\n\ninterface ItemCtx {\n open: boolean;\n setOpen: (open: boolean) => void;\n triggerRef: React.RefObject<HTMLButtonElement | null>;\n}\nconst NavigationMenuItemContext = React.createContext<ItemCtx | null>(null);\nfunction useItem(): ItemCtx {\n const ctx = React.useContext(NavigationMenuItemContext);\n if (!ctx) throw new Error(\"NavigationMenu parts must be used within <NavigationMenuItem>\");\n return ctx;\n}\n\nexport function NavigationMenu({\n className,\n children,\n // Content renders inline under its trigger, so no shared viewport is mounted; the flag\n // stays on the DOM for consumers' selectors.\n viewport = true,\n ...props\n}: React.ComponentProps<\"nav\"> & { viewport?: boolean }) {\n return (\n <nav\n data-slot=\"navigation-menu\"\n data-viewport={viewport}\n role=\"navigation\"\n className={cn(\n NAVIGATION_MENU,\n className,\n )}\n {...props}\n >\n {children}\n </nav>\n );\n}\n\nexport function NavigationMenuList({ className, ...props }: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"navigation-menu-list\"\n role=\"list\"\n className={cn(\n NAVIGATION_MENU_LIST,\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface NavigationMenuItemProps extends React.ComponentProps<\"li\"> {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\nexport function NavigationMenuItem({\n className,\n open,\n defaultOpen = false,\n onOpenChange,\n children,\n ...props\n}: NavigationMenuItemProps) {\n const [isOpen, setOpen] = useControllableState({\n value: open,\n defaultValue: defaultOpen,\n onChange: onOpenChange,\n });\n const triggerRef = React.useRef<HTMLButtonElement | null>(null);\n return (\n <NavigationMenuItemContext.Provider value={{ open: isOpen, setOpen, triggerRef }}>\n <li data-slot=\"navigation-menu-item\" className={cn(NAVIGATION_MENU_ITEM, className)} {...props}>\n {children}\n </li>\n </NavigationMenuItemContext.Provider>\n );\n}\n\nexport function NavigationMenuTrigger({\n className,\n children,\n onClick,\n ...props\n}: React.ComponentProps<\"button\">) {\n const { open, setOpen, triggerRef } = useItem();\n return (\n <button\n ref={triggerRef}\n type=\"button\"\n data-slot=\"navigation-menu-trigger\"\n data-state={open ? \"open\" : \"closed\"}\n className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n onClick={(e) => {\n onClick?.(e);\n setOpen(!open);\n }}\n {...props}\n >\n {children}{\" \"}\n <svg\n className=\"relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n );\n}\n\nexport function NavigationMenuContent({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { open, setOpen, triggerRef } = useItem();\n const { isPresent, ref: presenceRef } = usePresence(open);\n const dismissRef = useDismissableLayer({\n enabled: open,\n onDismiss: () => setOpen(false),\n exclude: [triggerRef],\n });\n if (!isPresent) return null;\n return (\n <div\n ref={mergeRefs(presenceRef, dismissRef)}\n data-slot=\"navigation-menu-content\"\n data-state={open ? \"open\" : \"closed\"}\n className={cn(\n NAVIGATION_MENU_CONTENT,\n className,\n )}\n {...props}\n >\n <DismissableLayerScope layer={dismissRef}>{children}</DismissableLayerScope>\n </div>\n );\n}\n\nexport function NavigationMenuViewport({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div className=\"absolute top-full left-0 isolate z-50 flex justify-center\">\n <div\n data-slot=\"navigation-menu-viewport\"\n className={cn(\n NAVIGATION_MENU_VIEWPORT,\n className,\n )}\n {...props}\n />\n </div>\n );\n}\n\nexport function NavigationMenuLink({\n className,\n asChild = false,\n ...props\n}: React.ComponentProps<\"a\"> & { asChild?: boolean }) {\n const Comp = asChild ? Slot : \"a\";\n return (\n <Comp\n data-slot=\"navigation-menu-link\"\n className={cn(\n NAVIGATION_MENU_LINK,\n className,\n )}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport function NavigationMenuIndicator({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"navigation-menu-indicator\"\n className={cn(\n NAVIGATION_MENU_INDICATOR,\n className,\n )}\n {...props}\n >\n <div className=\"bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md\" />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,16 +1,13 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { cn } from "../lib/cn.js";
4
+ import { SECTION_LABEL_BASE, sectionLabelToneClasses } from "../generated/section-label.js";
4
5
  function SectionLabel({ as: Comp = "p", tone = "muted", className, ...props }) {
5
6
  return /* @__PURE__ */ jsx(
6
7
  Comp,
7
8
  {
8
9
  "data-slot": "section-label",
9
- className: cn(
10
- "text-xs font-semibold uppercase tracking-widest",
11
- tone === "accent" ? "text-primary-ink" : "text-ink-soft",
12
- className
13
- ),
10
+ className: cn(SECTION_LABEL_BASE, sectionLabelToneClasses[tone], className),
14
11
  ...props
15
12
  }
16
13
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/page-frame.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport type { ShellBreakpoint } from \"./app-shell\";\n\nexport type SectionLabelTone = \"muted\" | \"accent\";\n\nexport interface SectionLabelProps extends React.HTMLAttributes<HTMLElement> {\n as?: React.ElementType;\n /** `muted` labels a group or a section; `accent` announces a hero figure. */\n tone?: SectionLabelTone;\n}\n\n/** The tracked-uppercase label above a figure, a group or a section. */\nexport function SectionLabel({ as: Comp = \"p\", tone = \"muted\", className, ...props }: SectionLabelProps) {\n return (\n <Comp\n data-slot=\"section-label\"\n className={cn(\n \"text-xs font-semibold uppercase tracking-widest\",\n tone === \"accent\" ? \"text-primary-ink\" : \"text-ink-soft\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport interface PageHeadingProps extends Omit<React.ComponentProps<\"header\">, \"title\"> {\n title: React.ReactNode;\n /** What the screen is for, in a sentence. */\n description?: React.ReactNode;\n eyebrow?: React.ReactNode;\n /** The screen's own controls, on the right. */\n actions?: React.ReactNode;\n}\n\n/** The title row: eyebrow → title → description on the left, actions on the right. */\nexport function PageHeading({ title, description, eyebrow, actions, className, ...props }: PageHeadingProps) {\n return (\n <header\n data-slot=\"page-heading\"\n className={cn(\"flex items-center justify-between gap-4\", className)}\n {...props}\n >\n <div className=\"flex min-w-0 flex-col gap-1\">\n {eyebrow !== undefined && <SectionLabel tone=\"accent\">{eyebrow}</SectionLabel>}\n <h1 className=\"text-2xl font-semibold leading-tight text-ink\">{title}</h1>\n {description !== undefined && <p className=\"text-sm text-ink-soft\">{description}</p>}\n </div>\n {actions !== undefined && <div className=\"flex shrink-0 items-center gap-2.5\">{actions}</div>}\n </header>\n );\n}\n\nexport type PageFrameWidth = \"full\" | \"content\";\n\nexport interface PageFrameProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n /** Omit when the screen's heading is its own composition. */\n title?: React.ReactNode;\n description?: React.ReactNode;\n eyebrow?: React.ReactNode;\n actions?: React.ReactNode;\n /**\n * The bar titling the screen on a phone (`MobileAppBar`). With one, the heading\n * shows from `breakpoint` up only and the sections arrive a step after the bar.\n */\n appBar?: React.ReactNode;\n /** Default `lg`. */\n breakpoint?: ShellBreakpoint;\n /** `full` fills the column (the rail already bounds it); `content` caps it at a reading measure. */\n width?: PageFrameWidth;\n headingClassName?: string;\n}\n\nconst HEADING_FROM = { md: \"hidden md:flex\", lg: \"hidden lg:flex\" } as const;\nconst PAD = {\n md: \"gap-4 px-4 pb-6 pt-5 md:gap-6 md:px-8 md:pb-7 md:pt-6\",\n lg: \"gap-4 px-4 pb-6 pt-5 lg:gap-6 lg:px-8 lg:pb-7 lg:pt-6\",\n} as const;\n\n/**\n * The frame every screen sits in: one inset, one section rhythm, one title scale,\n * so moving between screens never reads as moving between products.\n *\n * Its sections arrive in sequence by CSS alone (`data-enter=\"stagger\"`, see\n * motion.css): every direct child fades and rises `--ev-rise`, one\n * `--ev-stagger-section` after the previous, the heading first. Server-safe —\n * no motion library, no hydration needed for the entrance.\n */\nexport function PageFrame({\n title,\n description,\n eyebrow,\n actions,\n appBar,\n breakpoint = \"lg\",\n width = \"full\",\n headingClassName,\n className,\n style,\n children,\n ...props\n}: PageFrameProps) {\n const hasBar = appBar !== undefined && appBar !== null && appBar !== false;\n const enter = {\n \"--enter-step\": \"var(--ev-stagger-section, 50ms)\",\n ...(hasBar ? { \"--enter-delay\": \"var(--ev-stagger-section, 50ms)\" } : {}),\n };\n return (\n <>\n {appBar}\n <div\n data-slot=\"page-frame\"\n data-enter=\"stagger\"\n className={cn(\"flex flex-col\", PAD[breakpoint], width === \"content\" && \"max-w-5xl\", className)}\n style={{ ...enter, ...style }}\n {...props}\n >\n {title !== undefined && (\n <PageHeading\n title={title}\n description={description}\n eyebrow={eyebrow}\n actions={actions}\n className={cn(hasBar && HEADING_FROM[breakpoint], headingClassName)}\n />\n )}\n {children}\n </div>\n </>\n );\n}\n"],"mappings":"AAeI,SA8FA,UA9FA,KA6BE,YA7BF;AAfJ,YAAY,WAAW;AACvB,SAAS,UAAU;AAYZ,SAAS,aAAa,EAAE,IAAI,OAAO,KAAK,OAAO,SAAS,WAAW,GAAG,MAAM,GAAsB;AACvG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA,SAAS,WAAW,qBAAqB;AAAA,QACzC;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAYO,SAAS,YAAY,EAAE,OAAO,aAAa,SAAS,SAAS,WAAW,GAAG,MAAM,GAAqB;AAC3G,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA,MAEJ;AAAA,6BAAC,SAAI,WAAU,+BACZ;AAAA,sBAAY,UAAa,oBAAC,gBAAa,MAAK,UAAU,mBAAQ;AAAA,UAC/D,oBAAC,QAAG,WAAU,iDAAiD,iBAAM;AAAA,UACpE,gBAAgB,UAAa,oBAAC,OAAE,WAAU,yBAAyB,uBAAY;AAAA,WAClF;AAAA,QACC,YAAY,UAAa,oBAAC,SAAI,WAAU,sCAAsC,mBAAQ;AAAA;AAAA;AAAA,EACzF;AAEJ;AAsBA,MAAM,eAAe,EAAE,IAAI,kBAAkB,IAAI,iBAAiB;AAClE,MAAM,MAAM;AAAA,EACV,IAAI;AAAA,EACJ,IAAI;AACN;AAWO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,QAAM,SAAS,WAAW,UAAa,WAAW,QAAQ,WAAW;AACrE,QAAM,QAAQ;AAAA,IACZ,gBAAgB;AAAA,IAChB,GAAI,SAAS,EAAE,iBAAiB,kCAAkC,IAAI,CAAC;AAAA,EACzE;AACA,SACE,iCACG;AAAA;AAAA,IACD;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,cAAW;AAAA,QACX,WAAW,GAAG,iBAAiB,IAAI,UAAU,GAAG,UAAU,aAAa,aAAa,SAAS;AAAA,QAC7F,OAAO,EAAE,GAAG,OAAO,GAAG,MAAM;AAAA,QAC3B,GAAG;AAAA,QAEH;AAAA,oBAAU,UACT;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,WAAW,GAAG,UAAU,aAAa,UAAU,GAAG,gBAAgB;AAAA;AAAA,UACpE;AAAA,UAED;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/page-frame.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { SECTION_LABEL_BASE, sectionLabelToneClasses, type SectionLabelTone } from \"../generated/section-label\";\nimport type { ShellBreakpoint } from \"./app-shell\";\n\nexport type { SectionLabelTone };\n\nexport interface SectionLabelProps extends React.HTMLAttributes<HTMLElement> {\n as?: React.ElementType;\n /** `muted` labels a group or a section; `accent` announces a hero figure. */\n tone?: SectionLabelTone;\n}\n\n/** The tracked-uppercase label above a figure, a group or a section. */\nexport function SectionLabel({ as: Comp = \"p\", tone = \"muted\", className, ...props }: SectionLabelProps) {\n return (\n <Comp\n data-slot=\"section-label\"\n className={cn(SECTION_LABEL_BASE, sectionLabelToneClasses[tone], className)}\n {...props}\n />\n );\n}\n\nexport interface PageHeadingProps extends Omit<React.ComponentProps<\"header\">, \"title\"> {\n title: React.ReactNode;\n /** What the screen is for, in a sentence. */\n description?: React.ReactNode;\n eyebrow?: React.ReactNode;\n /** The screen's own controls, on the right. */\n actions?: React.ReactNode;\n}\n\n/** The title row: eyebrow → title → description on the left, actions on the right. */\nexport function PageHeading({ title, description, eyebrow, actions, className, ...props }: PageHeadingProps) {\n return (\n <header\n data-slot=\"page-heading\"\n className={cn(\"flex items-center justify-between gap-4\", className)}\n {...props}\n >\n <div className=\"flex min-w-0 flex-col gap-1\">\n {eyebrow !== undefined && <SectionLabel tone=\"accent\">{eyebrow}</SectionLabel>}\n <h1 className=\"text-2xl font-semibold leading-tight text-ink\">{title}</h1>\n {description !== undefined && <p className=\"text-sm text-ink-soft\">{description}</p>}\n </div>\n {actions !== undefined && <div className=\"flex shrink-0 items-center gap-2.5\">{actions}</div>}\n </header>\n );\n}\n\nexport type PageFrameWidth = \"full\" | \"content\";\n\nexport interface PageFrameProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n /** Omit when the screen's heading is its own composition. */\n title?: React.ReactNode;\n description?: React.ReactNode;\n eyebrow?: React.ReactNode;\n actions?: React.ReactNode;\n /**\n * The bar titling the screen on a phone (`MobileAppBar`). With one, the heading\n * shows from `breakpoint` up only and the sections arrive a step after the bar.\n */\n appBar?: React.ReactNode;\n /** Default `lg`. */\n breakpoint?: ShellBreakpoint;\n /** `full` fills the column (the rail already bounds it); `content` caps it at a reading measure. */\n width?: PageFrameWidth;\n headingClassName?: string;\n}\n\nconst HEADING_FROM = { md: \"hidden md:flex\", lg: \"hidden lg:flex\" } as const;\nconst PAD = {\n md: \"gap-4 px-4 pb-6 pt-5 md:gap-6 md:px-8 md:pb-7 md:pt-6\",\n lg: \"gap-4 px-4 pb-6 pt-5 lg:gap-6 lg:px-8 lg:pb-7 lg:pt-6\",\n} as const;\n\n/**\n * The frame every screen sits in: one inset, one section rhythm, one title scale,\n * so moving between screens never reads as moving between products.\n *\n * Its sections arrive in sequence by CSS alone (`data-enter=\"stagger\"`, see\n * motion.css): every direct child fades and rises `--ev-rise`, one\n * `--ev-stagger-section` after the previous, the heading first. Server-safe —\n * no motion library, no hydration needed for the entrance.\n */\nexport function PageFrame({\n title,\n description,\n eyebrow,\n actions,\n appBar,\n breakpoint = \"lg\",\n width = \"full\",\n headingClassName,\n className,\n style,\n children,\n ...props\n}: PageFrameProps) {\n const hasBar = appBar !== undefined && appBar !== null && appBar !== false;\n const enter = {\n \"--enter-step\": \"var(--ev-stagger-section, 50ms)\",\n ...(hasBar ? { \"--enter-delay\": \"var(--ev-stagger-section, 50ms)\" } : {}),\n };\n return (\n <>\n {appBar}\n <div\n data-slot=\"page-frame\"\n data-enter=\"stagger\"\n className={cn(\"flex flex-col\", PAD[breakpoint], width === \"content\" && \"max-w-5xl\", className)}\n style={{ ...enter, ...style }}\n {...props}\n >\n {title !== undefined && (\n <PageHeading\n title={title}\n description={description}\n eyebrow={eyebrow}\n actions={actions}\n className={cn(hasBar && HEADING_FROM[breakpoint], headingClassName)}\n />\n )}\n {children}\n </div>\n </>\n );\n}\n"],"mappings":"AAgBI,SA0FA,UA1FA,KAyBE,YAzBF;AAhBJ,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,oBAAoB,+BAAsD;AAY5E,SAAS,aAAa,EAAE,IAAI,OAAO,KAAK,OAAO,SAAS,WAAW,GAAG,MAAM,GAAsB;AACvG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,oBAAoB,wBAAwB,IAAI,GAAG,SAAS;AAAA,MACzE,GAAG;AAAA;AAAA,EACN;AAEJ;AAYO,SAAS,YAAY,EAAE,OAAO,aAAa,SAAS,SAAS,WAAW,GAAG,MAAM,GAAqB;AAC3G,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,2CAA2C,SAAS;AAAA,MACjE,GAAG;AAAA,MAEJ;AAAA,6BAAC,SAAI,WAAU,+BACZ;AAAA,sBAAY,UAAa,oBAAC,gBAAa,MAAK,UAAU,mBAAQ;AAAA,UAC/D,oBAAC,QAAG,WAAU,iDAAiD,iBAAM;AAAA,UACpE,gBAAgB,UAAa,oBAAC,OAAE,WAAU,yBAAyB,uBAAY;AAAA,WAClF;AAAA,QACC,YAAY,UAAa,oBAAC,SAAI,WAAU,sCAAsC,mBAAQ;AAAA;AAAA;AAAA,EACzF;AAEJ;AAsBA,MAAM,eAAe,EAAE,IAAI,kBAAkB,IAAI,iBAAiB;AAClE,MAAM,MAAM;AAAA,EACV,IAAI;AAAA,EACJ,IAAI;AACN;AAWO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,QAAM,SAAS,WAAW,UAAa,WAAW,QAAQ,WAAW;AACrE,QAAM,QAAQ;AAAA,IACZ,gBAAgB;AAAA,IAChB,GAAI,SAAS,EAAE,iBAAiB,kCAAkC,IAAI,CAAC;AAAA,EACzE;AACA,SACE,iCACG;AAAA;AAAA,IACD;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,cAAW;AAAA,QACX,WAAW,GAAG,iBAAiB,IAAI,UAAU,GAAG,UAAU,aAAa,aAAa,SAAS;AAAA,QAC7F,OAAO,EAAE,GAAG,OAAO,GAAG,MAAM;AAAA,QAC3B,GAAG;AAAA,QAEH;AAAA,oBAAU,UACT;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,WAAW,GAAG,UAAU,aAAa,UAAU,GAAG,gBAAgB;AAAA;AAAA,UACpE;AAAA,UAED;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,60 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import {
6
+ POPOVER_FORM_ACTIONS,
7
+ POPOVER_FORM_CONTENT,
8
+ POPOVER_FORM_FIELDS,
9
+ POPOVER_FORM_FORM,
10
+ POPOVER_FORM_TITLE
11
+ } from "../generated/popover-form.js";
12
+ import { useControllableState } from "../primitives/use-controllable-state.js";
13
+ import { Button } from "./button.js";
14
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
15
+ function PopoverForm({
16
+ open,
17
+ defaultOpen = false,
18
+ onOpenChange,
19
+ trigger,
20
+ title,
21
+ children,
22
+ onSubmit,
23
+ onReset,
24
+ labels = {},
25
+ className
26
+ }) {
27
+ const [isOpen, setOpen] = useControllableState({
28
+ ...open !== void 0 ? { value: open } : {},
29
+ defaultValue: defaultOpen,
30
+ ...onOpenChange ? { onChange: onOpenChange } : {}
31
+ });
32
+ const titleId = React.useId();
33
+ return /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: setOpen, children: [
34
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: trigger }),
35
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: cn(POPOVER_FORM_CONTENT, className), "aria-labelledby": titleId, role: "dialog", children: /* @__PURE__ */ jsxs(
36
+ "form",
37
+ {
38
+ className: POPOVER_FORM_FORM,
39
+ onSubmit: (e) => {
40
+ e.preventDefault();
41
+ onSubmit?.(e);
42
+ setOpen(false);
43
+ },
44
+ onReset: () => onReset?.(),
45
+ children: [
46
+ /* @__PURE__ */ jsx("h3", { id: titleId, className: POPOVER_FORM_TITLE, children: title }),
47
+ /* @__PURE__ */ jsx("div", { className: POPOVER_FORM_FIELDS, children }),
48
+ /* @__PURE__ */ jsxs("div", { className: POPOVER_FORM_ACTIONS, children: [
49
+ /* @__PURE__ */ jsx(Button, { type: "reset", variant: "ghost", size: "sm", children: labels.reset ?? "Reset" }),
50
+ /* @__PURE__ */ jsx(Button, { type: "submit", variant: "primary", size: "sm", children: labels.submit ?? "Apply" })
51
+ ] })
52
+ ]
53
+ }
54
+ ) })
55
+ ] });
56
+ }
57
+ export {
58
+ PopoverForm
59
+ };
60
+ //# sourceMappingURL=popover-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/popover-form.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n POPOVER_FORM_ACTIONS,\n POPOVER_FORM_CONTENT,\n POPOVER_FORM_FIELDS,\n POPOVER_FORM_FORM,\n POPOVER_FORM_TITLE,\n} from \"../generated/popover-form\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { Button } from \"./button\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./popover\";\n\nexport interface PopoverFormLabels {\n submit?: string;\n reset?: string;\n}\n\nexport interface PopoverFormProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** The button that opens the form; wrapped with `asChild`, so pass one element. */\n trigger: React.ReactNode;\n title: React.ReactNode;\n /** The fields: `Field`s with an `Input`, `Select` or `Checkbox`. */\n children: React.ReactNode;\n /** Called on submit; the popover closes afterwards. */\n onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;\n /** Called when Reset is pressed; native fields are reset by the browser first. */\n onReset?: () => void;\n labels?: PopoverFormLabels;\n className?: string;\n}\n\n/** A filter-style form in a popover: a title, fields, Reset and Apply. */\nexport function PopoverForm({\n open,\n defaultOpen = false,\n onOpenChange,\n trigger,\n title,\n children,\n onSubmit,\n onReset,\n labels = {},\n className,\n}: PopoverFormProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n const titleId = React.useId();\n return (\n <Popover open={isOpen} onOpenChange={setOpen}>\n <PopoverTrigger asChild>{trigger}</PopoverTrigger>\n <PopoverContent align=\"start\" className={cn(POPOVER_FORM_CONTENT, className)} aria-labelledby={titleId} role=\"dialog\">\n <form\n className={POPOVER_FORM_FORM}\n onSubmit={(e) => {\n e.preventDefault();\n onSubmit?.(e);\n setOpen(false);\n }}\n onReset={() => onReset?.()}\n >\n <h3 id={titleId} className={POPOVER_FORM_TITLE}>\n {title}\n </h3>\n <div className={POPOVER_FORM_FIELDS}>{children}</div>\n <div className={POPOVER_FORM_ACTIONS}>\n <Button type=\"reset\" variant=\"ghost\" size=\"sm\">\n {labels.reset ?? \"Reset\"}\n </Button>\n <Button type=\"submit\" variant=\"primary\" size=\"sm\">\n {labels.submit ?? \"Apply\"}\n </Button>\n </div>\n </form>\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":";AA0DM,cAeI,YAfJ;AAxDN,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,4BAA4B;AACrC,SAAS,cAAc;AACvB,SAAS,SAAS,gBAAgB,sBAAsB;AAyBjD,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AACF,GAAqB;AACnB,QAAM,CAAC,QAAQ,OAAO,IAAI,qBAA8B;AAAA,IACtD,GAAI,SAAS,SAAY,EAAE,OAAO,KAAK,IAAI,CAAC;AAAA,IAC5C,cAAc;AAAA,IACd,GAAI,eAAe,EAAE,UAAU,aAAa,IAAI,CAAC;AAAA,EACnD,CAAC;AACD,QAAM,UAAU,MAAM,MAAM;AAC5B,SACE,qBAAC,WAAQ,MAAM,QAAQ,cAAc,SACnC;AAAA,wBAAC,kBAAe,SAAO,MAAE,mBAAQ;AAAA,IACjC,oBAAC,kBAAe,OAAM,SAAQ,WAAW,GAAG,sBAAsB,SAAS,GAAG,mBAAiB,SAAS,MAAK,UAC3G;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,QACX,UAAU,CAAC,MAAM;AACf,YAAE,eAAe;AACjB,qBAAW,CAAC;AACZ,kBAAQ,KAAK;AAAA,QACf;AAAA,QACA,SAAS,MAAM,UAAU;AAAA,QAEzB;AAAA,8BAAC,QAAG,IAAI,SAAS,WAAW,oBACzB,iBACH;AAAA,UACA,oBAAC,SAAI,WAAW,qBAAsB,UAAS;AAAA,UAC/C,qBAAC,SAAI,WAAW,sBACd;AAAA,gCAAC,UAAO,MAAK,SAAQ,SAAQ,SAAQ,MAAK,MACvC,iBAAO,SAAS,SACnB;AAAA,YACA,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAC1C,iBAAO,UAAU,SACpB;AAAA,aACF;AAAA;AAAA;AAAA,IACF,GACF;AAAA,KACF;AAEJ;","names":[]}
@@ -33,7 +33,7 @@ function ResourceError(props) {
33
33
  ...rest,
34
34
  children: [
35
35
  line,
36
- onRetry && /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: retrying, onClick: onRetry, children: [
36
+ onRetry && /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "xs", disabled: retrying, onClick: onRetry, children: [
37
37
  /* @__PURE__ */ jsx(RefreshIcon, { className: cn("size-4", retrying && "motion-safe:animate-spin") }),
38
38
  labels.retry ?? "Try again"
39
39
  ] })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/resource-error.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { Alert, AlertDescription, AlertTitle } from \"./alert\";\nimport { Button } from \"./button\";\nimport { RefreshIcon, TriangleAlertIcon } from \"./shell-icons\";\n\nexport interface ResourceErrorLabels {\n /** The retry button. Default `\"Try again\"`. */\n retry?: string;\n}\n\ninterface ResourceErrorBase extends Omit<React.ComponentProps<\"div\">, \"title\" | \"children\"> {\n /** What failed, already in the reader's words — the kit formats no errors. */\n message: React.ReactNode;\n labels?: ResourceErrorLabels;\n}\n\n// The two forms take different props and the union says so: an `alert` has no\n// retry and an `inline` line has no heading, so asking for either is a type\n// error rather than a prop that silently does nothing.\nexport type ResourceErrorProps = ResourceErrorBase &\n (\n | {\n /** One line under the page header — for dense screens where a block would push everything down. */\n variant?: \"inline\";\n title?: never;\n /** Re-run the read; boxes the line so the button and the message read as one thing. */\n onRetry?: () => void;\n /** The retry is in flight: the button is disabled and its icon spins. */\n retrying?: boolean;\n }\n | {\n /** The kit's `Alert` with a heading — for screens that address the reader. */\n variant: \"alert\";\n title?: React.ReactNode;\n onRetry?: never;\n retrying?: never;\n }\n );\n\n/**\n * What a screen shows when a read it needed never arrived. Whether there is\n * anything to report stays with the caller — only it knows whether stale data is\n * still on screen, and a failed refresh over figures the reader can see belongs\n * beside them, not in place of them.\n */\nexport function ResourceError(props: ResourceErrorProps) {\n if (props.variant === \"alert\") {\n const { message, title, labels: _labels, variant: _variant, className, ...rest } = props;\n return (\n <div data-slot=\"resource-error\" data-variant=\"alert\" className={className} {...rest}>\n <Alert variant=\"destructive\">\n <TriangleAlertIcon className=\"size-4\" />\n {title !== undefined && <AlertTitle>{title}</AlertTitle>}\n <AlertDescription>{message}</AlertDescription>\n </Alert>\n </div>\n );\n }\n const { message, onRetry, retrying = false, labels = {}, variant: _variant, title: _title, className, ...rest } = props;\n const line = (\n <p className=\"flex min-w-0 items-center gap-2 text-sm text-accent-error\">\n {/* Holds its size: beside the button it is the first thing squeezed, and a\n half-width triangle reads as a rendering fault. */}\n <TriangleAlertIcon className=\"size-4 shrink-0\" /> {message}\n </p>\n );\n return (\n <div\n data-slot=\"resource-error\"\n data-variant=\"inline\"\n role=\"alert\"\n className={cn(\n onRetry &&\n \"flex flex-wrap items-center gap-3 rounded-lg border border-accent-error/40 bg-accent-error/10 px-3 py-2.5\",\n className,\n )}\n {...rest}\n >\n {line}\n {onRetry && (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={retrying} onClick={onRetry}>\n <RefreshIcon className={cn(\"size-4\", retrying && \"motion-safe:animate-spin\")} />\n {labels.retry ?? \"Try again\"}\n </Button>\n )}\n </div>\n );\n}\n"],"mappings":";AAqDQ,SACE,KADF;AAnDR,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,OAAO,kBAAkB,kBAAkB;AACpD,SAAS,cAAc;AACvB,SAAS,aAAa,yBAAyB;AA0CxC,SAAS,cAAc,OAA2B;AACvD,MAAI,MAAM,YAAY,SAAS;AAC7B,UAAM,EAAE,SAAAA,UAAS,OAAO,QAAQ,SAAS,SAASC,WAAU,WAAAC,YAAW,GAAGC,MAAK,IAAI;AACnF,WACE,oBAAC,SAAI,aAAU,kBAAiB,gBAAa,SAAQ,WAAWD,YAAY,GAAGC,OAC7E,+BAAC,SAAM,SAAQ,eACb;AAAA,0BAAC,qBAAkB,WAAU,UAAS;AAAA,MACrC,UAAU,UAAa,oBAAC,cAAY,iBAAM;AAAA,MAC3C,oBAAC,oBAAkB,UAAAH,UAAQ;AAAA,OAC7B,GACF;AAAA,EAEJ;AACA,QAAM,EAAE,SAAS,SAAS,WAAW,OAAO,SAAS,CAAC,GAAG,SAAS,UAAU,OAAO,QAAQ,WAAW,GAAG,KAAK,IAAI;AAClH,QAAM,OACJ,qBAAC,OAAE,WAAU,6DAGX;AAAA,wBAAC,qBAAkB,WAAU,mBAAkB;AAAA,IAAE;AAAA,IAAE;AAAA,KACrD;AAEF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAa;AAAA,MACb,MAAK;AAAA,MACL,WAAW;AAAA,QACT,WACE;AAAA,QACF;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,WACC,qBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,UAAU,UAAU,SAAS,SAC7E;AAAA,8BAAC,eAAY,WAAW,GAAG,UAAU,YAAY,0BAA0B,GAAG;AAAA,UAC7E,OAAO,SAAS;AAAA,WACnB;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":["message","_variant","className","rest"]}
1
+ {"version":3,"sources":["../../src/components/resource-error.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { Alert, AlertDescription, AlertTitle } from \"./alert\";\nimport { Button } from \"./button\";\nimport { RefreshIcon, TriangleAlertIcon } from \"./shell-icons\";\n\nexport interface ResourceErrorLabels {\n /** The retry button. Default `\"Try again\"`. */\n retry?: string;\n}\n\ninterface ResourceErrorBase extends Omit<React.ComponentProps<\"div\">, \"title\" | \"children\"> {\n /** What failed, already in the reader's words — the kit formats no errors. */\n message: React.ReactNode;\n labels?: ResourceErrorLabels;\n}\n\n// The two forms take different props and the union says so: an `alert` has no\n// retry and an `inline` line has no heading, so asking for either is a type\n// error rather than a prop that silently does nothing.\nexport type ResourceErrorProps = ResourceErrorBase &\n (\n | {\n /** One line under the page header — for dense screens where a block would push everything down. */\n variant?: \"inline\";\n title?: never;\n /** Re-run the read; boxes the line so the button and the message read as one thing. */\n onRetry?: () => void;\n /** The retry is in flight: the button is disabled and its icon spins. */\n retrying?: boolean;\n }\n | {\n /** The kit's `Alert` with a heading — for screens that address the reader. */\n variant: \"alert\";\n title?: React.ReactNode;\n onRetry?: never;\n retrying?: never;\n }\n );\n\n/**\n * What a screen shows when a read it needed never arrived. Whether there is\n * anything to report stays with the caller — only it knows whether stale data is\n * still on screen, and a failed refresh over figures the reader can see belongs\n * beside them, not in place of them.\n */\nexport function ResourceError(props: ResourceErrorProps) {\n if (props.variant === \"alert\") {\n const { message, title, labels: _labels, variant: _variant, className, ...rest } = props;\n return (\n <div data-slot=\"resource-error\" data-variant=\"alert\" className={className} {...rest}>\n <Alert variant=\"destructive\">\n <TriangleAlertIcon className=\"size-4\" />\n {title !== undefined && <AlertTitle>{title}</AlertTitle>}\n <AlertDescription>{message}</AlertDescription>\n </Alert>\n </div>\n );\n }\n const { message, onRetry, retrying = false, labels = {}, variant: _variant, title: _title, className, ...rest } = props;\n const line = (\n <p className=\"flex min-w-0 items-center gap-2 text-sm text-accent-error\">\n {/* Holds its size: beside the button it is the first thing squeezed, and a\n half-width triangle reads as a rendering fault. */}\n <TriangleAlertIcon className=\"size-4 shrink-0\" /> {message}\n </p>\n );\n return (\n <div\n data-slot=\"resource-error\"\n data-variant=\"inline\"\n role=\"alert\"\n className={cn(\n onRetry &&\n \"flex flex-wrap items-center gap-3 rounded-lg border border-accent-error/40 bg-accent-error/10 px-3 py-2.5\",\n className,\n )}\n {...rest}\n >\n {line}\n {onRetry && (\n <Button type=\"button\" variant=\"outline\" size=\"xs\" disabled={retrying} onClick={onRetry}>\n <RefreshIcon className={cn(\"size-4\", retrying && \"motion-safe:animate-spin\")} />\n {labels.retry ?? \"Try again\"}\n </Button>\n )}\n </div>\n );\n}\n"],"mappings":";AAqDQ,SACE,KADF;AAnDR,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,OAAO,kBAAkB,kBAAkB;AACpD,SAAS,cAAc;AACvB,SAAS,aAAa,yBAAyB;AA0CxC,SAAS,cAAc,OAA2B;AACvD,MAAI,MAAM,YAAY,SAAS;AAC7B,UAAM,EAAE,SAAAA,UAAS,OAAO,QAAQ,SAAS,SAASC,WAAU,WAAAC,YAAW,GAAGC,MAAK,IAAI;AACnF,WACE,oBAAC,SAAI,aAAU,kBAAiB,gBAAa,SAAQ,WAAWD,YAAY,GAAGC,OAC7E,+BAAC,SAAM,SAAQ,eACb;AAAA,0BAAC,qBAAkB,WAAU,UAAS;AAAA,MACrC,UAAU,UAAa,oBAAC,cAAY,iBAAM;AAAA,MAC3C,oBAAC,oBAAkB,UAAAH,UAAQ;AAAA,OAC7B,GACF;AAAA,EAEJ;AACA,QAAM,EAAE,SAAS,SAAS,WAAW,OAAO,SAAS,CAAC,GAAG,SAAS,UAAU,OAAO,QAAQ,WAAW,GAAG,KAAK,IAAI;AAClH,QAAM,OACJ,qBAAC,OAAE,WAAU,6DAGX;AAAA,wBAAC,qBAAkB,WAAU,mBAAkB;AAAA,IAAE;AAAA,IAAE;AAAA,KACrD;AAEF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAa;AAAA,MACb,MAAK;AAAA,MACL,WAAW;AAAA,QACT,WACE;AAAA,QACF;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,WACC,qBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,UAAU,UAAU,SAAS,SAC7E;AAAA,8BAAC,eAAY,WAAW,GAAG,UAAU,YAAY,0BAA0B,GAAG;AAAA,UAC7E,OAAO,SAAS;AAAA,WACnB;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":["message","_variant","className","rest"]}
@@ -2,14 +2,20 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "../lib/cn.js";
5
+ import {
6
+ NavCollapse,
7
+ NavGroupHeader,
8
+ useNavGroupOpen
9
+ } from "./nav-collapse.js";
5
10
  import { SectionLabel } from "./page-frame.js";
6
- const ITEM = "flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50";
11
+ const ITEM = "flex h-8 w-full items-center gap-2.5 rounded-(--control-radius) px-2.5 text-left text-body outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-40";
7
12
  function SectionNav({
8
13
  groups,
9
14
  value,
10
15
  onValueChange,
11
16
  linkComponent,
12
17
  labels = {},
18
+ collapsible = false,
13
19
  className,
14
20
  ...props
15
21
  }) {
@@ -18,40 +24,87 @@ function SectionNav({
18
24
  {
19
25
  "data-slot": "section-nav",
20
26
  "aria-label": labels.nav ?? "Sections",
21
- className: cn("flex w-60 shrink-0 flex-col gap-5", className),
27
+ className: cn("flex w-64 shrink-0 flex-col gap-0.5", className),
22
28
  ...props,
23
- children: groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
24
- (group.label !== void 0 || group.description !== void 0) && /* @__PURE__ */ jsxs("div", { className: "mb-1 flex flex-col gap-0.5 px-3", children: [
25
- group.label !== void 0 && /* @__PURE__ */ jsx(SectionLabel, { children: group.label }),
26
- group.description !== void 0 && /* @__PURE__ */ jsx("p", { className: "text-xs leading-snug text-ink-soft", children: group.description })
27
- ] }),
28
- group.items.map((item) => /* @__PURE__ */ jsx(
29
- SectionNavRow,
30
- {
31
- item,
32
- active: item.id === value,
33
- onSelect: onValueChange,
34
- linkComponent
35
- },
36
- item.id
37
- ))
38
- ] }, group.id))
29
+ children: groups.map((group) => /* @__PURE__ */ jsx(
30
+ SectionNavGroupView,
31
+ {
32
+ group,
33
+ value,
34
+ onSelect: onValueChange,
35
+ linkComponent,
36
+ labels,
37
+ collapsible
38
+ },
39
+ group.id
40
+ ))
39
41
  }
40
42
  );
41
43
  }
44
+ function SectionNavGroupView({
45
+ group,
46
+ value,
47
+ onSelect,
48
+ linkComponent,
49
+ labels,
50
+ collapsible
51
+ }) {
52
+ const labelId = React.useId();
53
+ const regionId = React.useId();
54
+ const [open, setOpen] = useNavGroupOpen(group, group.items.some((i) => i.id === value));
55
+ const folds = (group.collapsible ?? collapsible) && group.label !== void 0;
56
+ const rows = group.items.map((item) => /* @__PURE__ */ jsx(
57
+ SectionNavRow,
58
+ {
59
+ item,
60
+ active: item.id === value,
61
+ nested: folds,
62
+ onSelect,
63
+ linkComponent
64
+ },
65
+ item.id
66
+ ));
67
+ if (folds) {
68
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col pt-1.5 first:pt-0", children: [
69
+ /* @__PURE__ */ jsx(
70
+ NavGroupHeader,
71
+ {
72
+ label: group.label,
73
+ labelId,
74
+ regionId,
75
+ open,
76
+ onToggle: () => setOpen(!open),
77
+ href: group.href,
78
+ linkComponent,
79
+ labels
80
+ }
81
+ ),
82
+ /* @__PURE__ */ jsx(NavCollapse, { open, id: regionId, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5 pt-0.5 pl-3", children: rows }) })
83
+ ] });
84
+ }
85
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5 pt-3.5 first:pt-0", children: [
86
+ (group.label !== void 0 || group.description !== void 0) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
87
+ group.label !== void 0 && /* @__PURE__ */ jsx(SectionLabel, { className: "px-2 py-1", children: group.label }),
88
+ group.description !== void 0 && /* @__PURE__ */ jsx("p", { className: "px-2 text-caption text-ink-soft", children: group.description })
89
+ ] }),
90
+ rows
91
+ ] });
92
+ }
42
93
  function SectionNavRow({
43
94
  item,
44
95
  active,
96
+ nested,
45
97
  onSelect,
46
98
  linkComponent
47
99
  }) {
48
100
  const Icon = item.icon;
49
101
  const className = cn(
50
102
  ITEM,
51
- active ? "bg-primary-ink/15 font-semibold text-primary-ink" : "text-ink hover:bg-hover"
103
+ active ? "bg-active text-ink" : "text-ink-mid hover:bg-hover",
104
+ nested && "h-9"
52
105
  );
53
106
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
54
- Icon && /* @__PURE__ */ jsx(Icon, { className: "size-4.5 shrink-0" }),
107
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0 text-ink-soft" }),
55
108
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }),
56
109
  item.trailing
57
110
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/section-nav.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { SectionLabel } from \"./page-frame\";\n\nexport interface SectionNavItem {\n id: string;\n label: React.ReactNode;\n icon?: React.ComponentType<{ className?: string }>;\n /** A link (`?section=…`, a sub-route) instead of a button calling `onValueChange`. */\n href?: string;\n /** After the label — a count, a status chip. */\n trailing?: React.ReactNode;\n disabled?: boolean;\n}\n\nexport interface SectionNavGroup {\n id: string;\n label?: React.ReactNode;\n /** One line under the label saying what kind of thing lives here. */\n description?: React.ReactNode;\n items: readonly SectionNavItem[];\n}\n\nexport interface SectionNavLabels {\n /** Accessible name of the nav. Default `\"Sections\"`. */\n nav?: string;\n}\n\nexport interface SectionNavProps extends Omit<React.ComponentProps<\"nav\">, \"children\"> {\n groups: readonly SectionNavGroup[];\n /** The current section's id. */\n value: string;\n /** Called by button items (those without `href`), and by link items on click. */\n onValueChange?: (id: string) => void;\n linkComponent?: React.ElementType;\n labels?: SectionNavLabels;\n}\n\nconst ITEM =\n \"flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\";\n\n/**\n * A screen's own section rail — settings panes, a document's chapters: grouped\n * rows, one current (`aria-current`), each a button or a link. The shell's rail\n * is `ShellNav`; this one lives inside a page, so it carries a tint rather than\n * the rail's solid marker and does not slide.\n */\nexport function SectionNav({\n groups,\n value,\n onValueChange,\n linkComponent,\n labels = {},\n className,\n ...props\n}: SectionNavProps) {\n return (\n <nav\n data-slot=\"section-nav\"\n aria-label={labels.nav ?? \"Sections\"}\n className={cn(\"flex w-60 shrink-0 flex-col gap-5\", className)}\n {...props}\n >\n {groups.map((group) => (\n <div key={group.id} className=\"flex flex-col gap-1\">\n {(group.label !== undefined || group.description !== undefined) && (\n <div className=\"mb-1 flex flex-col gap-0.5 px-3\">\n {group.label !== undefined && <SectionLabel>{group.label}</SectionLabel>}\n {group.description !== undefined && (\n <p className=\"text-xs leading-snug text-ink-soft\">{group.description}</p>\n )}\n </div>\n )}\n {group.items.map((item) => (\n <SectionNavRow\n key={item.id}\n item={item}\n active={item.id === value}\n onSelect={onValueChange}\n linkComponent={linkComponent}\n />\n ))}\n </div>\n ))}\n </nav>\n );\n}\n\nfunction SectionNavRow({\n item,\n active,\n onSelect,\n linkComponent,\n}: {\n item: SectionNavItem;\n active: boolean;\n onSelect: ((id: string) => void) | undefined;\n linkComponent: React.ElementType | undefined;\n}) {\n const Icon = item.icon;\n const className = cn(\n ITEM,\n active ? \"bg-primary-ink/15 font-semibold text-primary-ink\" : \"text-ink hover:bg-hover\",\n );\n const content = (\n <>\n {Icon && <Icon className=\"size-4.5 shrink-0\" />}\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.trailing}\n </>\n );\n if (item.href !== undefined && !item.disabled) {\n const Link = linkComponent ?? \"a\";\n return (\n <Link\n href={item.href}\n aria-current={active ? \"page\" : undefined}\n className={className}\n onClick={() => onSelect?.(item.id)}\n >\n {content}\n </Link>\n );\n }\n return (\n <button\n type=\"button\"\n disabled={item.disabled}\n aria-current={active ? \"true\" : undefined}\n className={className}\n onClick={() => onSelect?.(item.id)}\n >\n {content}\n </button>\n );\n}\n"],"mappings":";AAoEY,SAuCR,UAtCwC,KADhC;AAlEZ,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAoC7B,MAAM,OACJ;AAQK,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAY,OAAO,OAAO;AAAA,MAC1B,WAAW,GAAG,qCAAqC,SAAS;AAAA,MAC3D,GAAG;AAAA,MAEH,iBAAO,IAAI,CAAC,UACX,qBAAC,SAAmB,WAAU,uBAC1B;AAAA,eAAM,UAAU,UAAa,MAAM,gBAAgB,WACnD,qBAAC,SAAI,WAAU,mCACZ;AAAA,gBAAM,UAAU,UAAa,oBAAC,gBAAc,gBAAM,OAAM;AAAA,UACxD,MAAM,gBAAgB,UACrB,oBAAC,OAAE,WAAU,sCAAsC,gBAAM,aAAY;AAAA,WAEzE;AAAA,QAED,MAAM,MAAM,IAAI,CAAC,SAChB;AAAA,UAAC;AAAA;AAAA,YAEC;AAAA,YACA,QAAQ,KAAK,OAAO;AAAA,YACpB,UAAU;AAAA,YACV;AAAA;AAAA,UAJK,KAAK;AAAA,QAKZ,CACD;AAAA,WAjBO,MAAM,EAkBhB,CACD;AAAA;AAAA,EACH;AAEJ;AAEA,SAAS,cAAc;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,OAAO,KAAK;AAClB,QAAM,YAAY;AAAA,IAChB;AAAA,IACA,SAAS,qDAAqD;AAAA,EAChE;AACA,QAAM,UACJ,iCACG;AAAA,YAAQ,oBAAC,QAAK,WAAU,qBAAoB;AAAA,IAC7C,oBAAC,UAAK,WAAU,2BAA2B,eAAK,OAAM;AAAA,IACrD,KAAK;AAAA,KACR;AAEF,MAAI,KAAK,SAAS,UAAa,CAAC,KAAK,UAAU;AAC7C,UAAM,OAAO,iBAAiB;AAC9B,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,KAAK;AAAA,QACX,gBAAc,SAAS,SAAS;AAAA,QAChC;AAAA,QACA,SAAS,MAAM,WAAW,KAAK,EAAE;AAAA,QAEhC;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,UAAU,KAAK;AAAA,MACf,gBAAc,SAAS,SAAS;AAAA,MAChC;AAAA,MACA,SAAS,MAAM,WAAW,KAAK,EAAE;AAAA,MAEhC;AAAA;AAAA,EACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/section-nav.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n NavCollapse,\n NavGroupHeader,\n useNavGroupOpen,\n type NavCollapseLabels,\n type NavGroupCollapse,\n} from \"./nav-collapse\";\nimport { SectionLabel } from \"./page-frame\";\n\nexport interface SectionNavItem {\n id: string;\n label: React.ReactNode;\n icon?: React.ComponentType<{ className?: string }>;\n /** A link (`?section=…`, a sub-route) instead of a button calling `onValueChange`. */\n href?: string;\n /** After the label — a count, a status chip. */\n trailing?: React.ReactNode;\n disabled?: boolean;\n}\n\nexport interface SectionNavGroup extends NavGroupCollapse {\n id: string;\n label?: React.ReactNode;\n /** One line under the label saying what kind of thing lives here. */\n description?: React.ReactNode;\n items: readonly SectionNavItem[];\n}\n\nexport interface SectionNavLabels extends NavCollapseLabels {\n /** Accessible name of the nav. Default `\"Sections\"`. */\n nav?: string;\n}\n\nexport interface SectionNavProps extends Omit<React.ComponentProps<\"nav\">, \"children\"> {\n groups: readonly SectionNavGroup[];\n /** The current section's id. */\n value: string;\n /** Called by button items (those without `href`), and by link items on click. */\n onValueChange?: (id: string) => void;\n linkComponent?: React.ElementType;\n labels?: SectionNavLabels;\n /** Makes every labelled group collapsible; a group's own `collapsible` wins. Default `false`. */\n collapsible?: boolean;\n}\n\nconst ITEM =\n \"flex h-8 w-full items-center gap-2.5 rounded-(--control-radius) px-2.5 text-left text-body outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-40\";\n\n/**\n * A screen's own section rail — settings panes, a document's chapters: grouped\n * rows, one current (`aria-current`), each a button or a link. The shell's rail\n * is `ShellNav`; this one lives inside a page, so it carries a tint rather than\n * the rail's solid marker and does not slide.\n */\nexport function SectionNav({\n groups,\n value,\n onValueChange,\n linkComponent,\n labels = {},\n collapsible = false,\n className,\n ...props\n}: SectionNavProps) {\n return (\n <nav\n data-slot=\"section-nav\"\n aria-label={labels.nav ?? \"Sections\"}\n className={cn(\"flex w-64 shrink-0 flex-col gap-0.5\", className)}\n {...props}\n >\n {groups.map((group) => (\n <SectionNavGroupView\n key={group.id}\n group={group}\n value={value}\n onSelect={onValueChange}\n linkComponent={linkComponent}\n labels={labels}\n collapsible={collapsible}\n />\n ))}\n </nav>\n );\n}\n\nfunction SectionNavGroupView({\n group,\n value,\n onSelect,\n linkComponent,\n labels,\n collapsible,\n}: {\n group: SectionNavGroup;\n value: string;\n onSelect: ((id: string) => void) | undefined;\n linkComponent: React.ElementType | undefined;\n labels: SectionNavLabels;\n collapsible: boolean;\n}) {\n const labelId = React.useId();\n const regionId = React.useId();\n const [open, setOpen] = useNavGroupOpen(group, group.items.some((i) => i.id === value));\n const folds = (group.collapsible ?? collapsible) && group.label !== undefined;\n const rows = group.items.map((item) => (\n <SectionNavRow\n key={item.id}\n item={item}\n active={item.id === value}\n nested={folds}\n onSelect={onSelect}\n linkComponent={linkComponent}\n />\n ));\n if (folds) {\n return (\n <div className=\"flex flex-col pt-1.5 first:pt-0\">\n <NavGroupHeader\n label={group.label}\n labelId={labelId}\n regionId={regionId}\n open={open}\n onToggle={() => setOpen(!open)}\n href={group.href}\n linkComponent={linkComponent}\n labels={labels}\n />\n <NavCollapse open={open} id={regionId}>\n <div className=\"flex flex-col gap-0.5 pt-0.5 pl-3\">{rows}</div>\n </NavCollapse>\n </div>\n );\n }\n return (\n <div className=\"flex flex-col gap-0.5 pt-3.5 first:pt-0\">\n {(group.label !== undefined || group.description !== undefined) && (\n <div className=\"flex flex-col\">\n {group.label !== undefined && <SectionLabel className=\"px-2 py-1\">{group.label}</SectionLabel>}\n {group.description !== undefined && (\n <p className=\"px-2 text-caption text-ink-soft\">{group.description}</p>\n )}\n </div>\n )}\n {rows}\n </div>\n );\n}\n\nfunction SectionNavRow({\n item,\n active,\n nested,\n onSelect,\n linkComponent,\n}: {\n item: SectionNavItem;\n active: boolean;\n nested: boolean;\n onSelect: ((id: string) => void) | undefined;\n linkComponent: React.ElementType | undefined;\n}) {\n const Icon = item.icon;\n const className = cn(\n ITEM,\n active ? \"bg-active text-ink\" : \"text-ink-mid hover:bg-hover\",\n nested && \"h-9\",\n );\n const content = (\n <>\n {Icon && <Icon className=\"size-4 shrink-0 text-ink-soft\" />}\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.trailing}\n </>\n );\n if (item.href !== undefined && !item.disabled) {\n const Link = linkComponent ?? \"a\";\n return (\n <Link\n href={item.href}\n aria-current={active ? \"page\" : undefined}\n className={className}\n onClick={() => onSelect?.(item.id)}\n >\n {content}\n </Link>\n );\n }\n return (\n <button\n type=\"button\"\n disabled={item.disabled}\n aria-current={active ? \"true\" : undefined}\n className={className}\n onClick={() => onSelect?.(item.id)}\n >\n {content}\n </button>\n );\n}\n"],"mappings":"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","names":[]}