@rtkelly-labs/design-system 0.0.0-bootstrap.0 → 0.16.0-dev.0.37dedac

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 (284) hide show
  1. package/CHANGELOG.md +347 -0
  2. package/LICENSE +21 -0
  3. package/README.md +317 -2
  4. package/dist/components/AlertDialog.js +95 -0
  5. package/dist/components/AlertDialog.js.map +1 -0
  6. package/dist/components/AppShell.js +261 -0
  7. package/dist/components/AppShell.js.map +1 -0
  8. package/dist/components/AsciiDivider.js +8 -0
  9. package/dist/components/AsciiDivider.js.map +1 -0
  10. package/dist/components/AsciiGauge.js +130 -0
  11. package/dist/components/AsciiGauge.js.map +1 -0
  12. package/dist/components/Avatar.js +87 -0
  13. package/dist/components/Avatar.js.map +1 -0
  14. package/dist/components/Badge.js +25 -0
  15. package/dist/components/Badge.js.map +1 -0
  16. package/dist/components/BarChart.js +288 -0
  17. package/dist/components/BarChart.js.map +1 -0
  18. package/dist/components/BracketText.js +23 -0
  19. package/dist/components/BracketText.js.map +1 -0
  20. package/dist/components/BulletChart.js +50 -0
  21. package/dist/components/BulletChart.js.map +1 -0
  22. package/dist/components/Button.js +130 -0
  23. package/dist/components/Button.js.map +1 -0
  24. package/dist/components/Card.js +181 -0
  25. package/dist/components/Card.js.map +1 -0
  26. package/dist/components/ChartTooltip.js +54 -0
  27. package/dist/components/ChartTooltip.js.map +1 -0
  28. package/dist/components/Checkbox.js +55 -0
  29. package/dist/components/Checkbox.js.map +1 -0
  30. package/dist/components/ColophonCard.js +122 -0
  31. package/dist/components/ColophonCard.js.map +1 -0
  32. package/dist/components/DataTable.js +299 -0
  33. package/dist/components/DataTable.js.map +1 -0
  34. package/dist/components/Divider.js +42 -0
  35. package/dist/components/Divider.js.map +1 -0
  36. package/dist/components/Drawer.js +81 -0
  37. package/dist/components/Drawer.js.map +1 -0
  38. package/dist/components/EmptyState.js +39 -0
  39. package/dist/components/EmptyState.js.map +1 -0
  40. package/dist/components/ErrorSummary.js +122 -0
  41. package/dist/components/ErrorSummary.js.map +1 -0
  42. package/dist/components/Fieldset.js +37 -0
  43. package/dist/components/Fieldset.js.map +1 -0
  44. package/dist/components/Glyph.js +60 -0
  45. package/dist/components/Glyph.js.map +1 -0
  46. package/dist/components/Input.js +111 -0
  47. package/dist/components/Input.js.map +1 -0
  48. package/dist/components/LinkProvider.js +76 -0
  49. package/dist/components/LinkProvider.js.map +1 -0
  50. package/dist/components/Menu.js +144 -0
  51. package/dist/components/Menu.js.map +1 -0
  52. package/dist/components/MetadataGrid.js +184 -0
  53. package/dist/components/MetadataGrid.js.map +1 -0
  54. package/dist/components/MobileNav.js +120 -0
  55. package/dist/components/MobileNav.js.map +1 -0
  56. package/dist/components/Modal.js +79 -0
  57. package/dist/components/Modal.js.map +1 -0
  58. package/dist/components/NerdIcon.js +177 -0
  59. package/dist/components/NerdIcon.js.map +1 -0
  60. package/dist/components/NoteBlock.js +84 -0
  61. package/dist/components/NoteBlock.js.map +1 -0
  62. package/dist/components/PageHeader.js +34 -0
  63. package/dist/components/PageHeader.js.map +1 -0
  64. package/dist/components/PageTitle.js +21 -0
  65. package/dist/components/PageTitle.js.map +1 -0
  66. package/dist/components/Pagination.js +96 -0
  67. package/dist/components/Pagination.js.map +1 -0
  68. package/dist/components/Popover.js +87 -0
  69. package/dist/components/Popover.js.map +1 -0
  70. package/dist/components/Progress.js +83 -0
  71. package/dist/components/Progress.js.map +1 -0
  72. package/dist/components/RadioGroup.js +74 -0
  73. package/dist/components/RadioGroup.js.map +1 -0
  74. package/dist/components/ReportDocument.js +75 -0
  75. package/dist/components/ReportDocument.js.map +1 -0
  76. package/dist/components/SectionContainer.js +18 -0
  77. package/dist/components/SectionContainer.js.map +1 -0
  78. package/dist/components/Select.js +199 -0
  79. package/dist/components/Select.js.map +1 -0
  80. package/dist/components/SiteFooter.js +29 -0
  81. package/dist/components/SiteFooter.js.map +1 -0
  82. package/dist/components/SiteHeader.js +59 -0
  83. package/dist/components/SiteHeader.js.map +1 -0
  84. package/dist/components/SiteNav.js +141 -0
  85. package/dist/components/SiteNav.js.map +1 -0
  86. package/dist/components/Skeleton.js +51 -0
  87. package/dist/components/Skeleton.js.map +1 -0
  88. package/dist/components/SocialIcon.js +77 -0
  89. package/dist/components/SocialIcon.js.map +1 -0
  90. package/dist/components/Sparkline.js +104 -0
  91. package/dist/components/Sparkline.js.map +1 -0
  92. package/dist/components/Spinner.js +65 -0
  93. package/dist/components/Spinner.js.map +1 -0
  94. package/dist/components/StatCard.js +53 -0
  95. package/dist/components/StatCard.js.map +1 -0
  96. package/dist/components/StatusPage.js +99 -0
  97. package/dist/components/StatusPage.js.map +1 -0
  98. package/dist/components/Swatch.js +55 -0
  99. package/dist/components/Swatch.js.map +1 -0
  100. package/dist/components/Switch.js +53 -0
  101. package/dist/components/Switch.js.map +1 -0
  102. package/dist/components/TLDR.js +53 -0
  103. package/dist/components/TLDR.js.map +1 -0
  104. package/dist/components/Table.js +155 -0
  105. package/dist/components/Table.js.map +1 -0
  106. package/dist/components/Tabs.js +325 -0
  107. package/dist/components/Tabs.js.map +1 -0
  108. package/dist/components/Tag.js +19 -0
  109. package/dist/components/Tag.js.map +1 -0
  110. package/dist/components/TelemetryGauge.js +112 -0
  111. package/dist/components/TelemetryGauge.js.map +1 -0
  112. package/dist/components/ThemeProvider.js +87 -0
  113. package/dist/components/ThemeProvider.js.map +1 -0
  114. package/dist/components/TickerTape.js +196 -0
  115. package/dist/components/TickerTape.js.map +1 -0
  116. package/dist/components/Toast.js +200 -0
  117. package/dist/components/Toast.js.map +1 -0
  118. package/dist/components/Tooltip.js +69 -0
  119. package/dist/components/Tooltip.js.map +1 -0
  120. package/dist/components/TrackCard.js +206 -0
  121. package/dist/components/TrackCard.js.map +1 -0
  122. package/dist/components/blog/BlogPost.js +116 -0
  123. package/dist/components/blog/BlogPost.js.map +1 -0
  124. package/dist/components/blog/LoremIpsumPost.js +62 -0
  125. package/dist/components/blog/LoremIpsumPost.js.map +1 -0
  126. package/dist/components/blog/author.js +25 -0
  127. package/dist/components/blog/author.js.map +1 -0
  128. package/dist/components/booleanControl.js +50 -0
  129. package/dist/components/booleanControl.js.map +1 -0
  130. package/dist/components/dialogPopupRef.js +28 -0
  131. package/dist/components/dialogPopupRef.js.map +1 -0
  132. package/dist/components/dialogSurface.js +75 -0
  133. package/dist/components/dialogSurface.js.map +1 -0
  134. package/dist/components/docs/AnchorHeading.js +80 -0
  135. package/dist/components/docs/AnchorHeading.js.map +1 -0
  136. package/dist/components/docs/Breadcrumbs.js +26 -0
  137. package/dist/components/docs/Breadcrumbs.js.map +1 -0
  138. package/dist/components/docs/CodeBlock.js +63 -0
  139. package/dist/components/docs/CodeBlock.js.map +1 -0
  140. package/dist/components/docs/CodeTabs.js +71 -0
  141. package/dist/components/docs/CodeTabs.js.map +1 -0
  142. package/dist/components/docs/DocPager.js +28 -0
  143. package/dist/components/docs/DocPager.js.map +1 -0
  144. package/dist/components/docs/DocsHeader.js +124 -0
  145. package/dist/components/docs/DocsHeader.js.map +1 -0
  146. package/dist/components/docs/DocsLayout.js +61 -0
  147. package/dist/components/docs/DocsLayout.js.map +1 -0
  148. package/dist/components/docs/DocsLinkProvider.js +25 -0
  149. package/dist/components/docs/DocsLinkProvider.js.map +1 -0
  150. package/dist/components/docs/DocsSidebar.js +147 -0
  151. package/dist/components/docs/DocsSidebar.js.map +1 -0
  152. package/dist/components/docs/Prose.js +56 -0
  153. package/dist/components/docs/Prose.js.map +1 -0
  154. package/dist/components/docs/TableOfContents.js +71 -0
  155. package/dist/components/docs/TableOfContents.js.map +1 -0
  156. package/dist/components/docs/codeTabsStore.js +75 -0
  157. package/dist/components/docs/codeTabsStore.js.map +1 -0
  158. package/dist/components/docs/figures/ActivityGrid.js +116 -0
  159. package/dist/components/docs/figures/ActivityGrid.js.map +1 -0
  160. package/dist/components/docs/figures/BeforeAfter.js +64 -0
  161. package/dist/components/docs/figures/BeforeAfter.js.map +1 -0
  162. package/dist/components/docs/figures/ChangeSummary.js +74 -0
  163. package/dist/components/docs/figures/ChangeSummary.js.map +1 -0
  164. package/dist/components/docs/figures/Decision.js +34 -0
  165. package/dist/components/docs/figures/Decision.js.map +1 -0
  166. package/dist/components/docs/figures/DrawingSheet.js +37 -0
  167. package/dist/components/docs/figures/DrawingSheet.js.map +1 -0
  168. package/dist/components/docs/figures/FigureFrame.js +226 -0
  169. package/dist/components/docs/figures/FigureFrame.js.map +1 -0
  170. package/dist/components/docs/figures/FileChangeTree.js +134 -0
  171. package/dist/components/docs/figures/FileChangeTree.js.map +1 -0
  172. package/dist/components/docs/figures/FlowDiagram.js +82 -0
  173. package/dist/components/docs/figures/FlowDiagram.js.map +1 -0
  174. package/dist/components/docs/figures/GanttChart.js +79 -0
  175. package/dist/components/docs/figures/GanttChart.js.map +1 -0
  176. package/dist/components/docs/figures/IndexedSection.js +45 -0
  177. package/dist/components/docs/figures/IndexedSection.js.map +1 -0
  178. package/dist/components/docs/figures/MetricStrip.js +23 -0
  179. package/dist/components/docs/figures/MetricStrip.js.map +1 -0
  180. package/dist/components/docs/figures/Steps.js +58 -0
  181. package/dist/components/docs/figures/Steps.js.map +1 -0
  182. package/dist/components/docs/figures/SwatchCompare.js +42 -0
  183. package/dist/components/docs/figures/SwatchCompare.js.map +1 -0
  184. package/dist/components/docs/figures/TermGrid.js +22 -0
  185. package/dist/components/docs/figures/TermGrid.js.map +1 -0
  186. package/dist/components/docs/figures/Terminal.js +37 -0
  187. package/dist/components/docs/figures/Terminal.js.map +1 -0
  188. package/dist/components/docs/figures/Timeline.js +61 -0
  189. package/dist/components/docs/figures/Timeline.js.map +1 -0
  190. package/dist/components/docs/figures/TokenTable.js +42 -0
  191. package/dist/components/docs/figures/TokenTable.js.map +1 -0
  192. package/dist/components/docs/figures/TreeDiagram.js +76 -0
  193. package/dist/components/docs/figures/TreeDiagram.js.map +1 -0
  194. package/dist/components/docs/figures/UptimeStrip.js +68 -0
  195. package/dist/components/docs/figures/UptimeStrip.js.map +1 -0
  196. package/dist/components/docs/figures/index.js +24 -0
  197. package/dist/components/docs/figures/index.js.map +1 -0
  198. package/dist/components/docs/index.js +15 -0
  199. package/dist/components/docs/index.js.map +1 -0
  200. package/dist/components/docs/mdxComponents.js +108 -0
  201. package/dist/components/docs/mdxComponents.js.map +1 -0
  202. package/dist/components/experiments/DesignSandbox.js +206 -0
  203. package/dist/components/experiments/DesignSandbox.js.map +1 -0
  204. package/dist/components/experiments/ExperimentsView.js +138 -0
  205. package/dist/components/experiments/ExperimentsView.js.map +1 -0
  206. package/dist/components/fieldFrame.js +200 -0
  207. package/dist/components/fieldFrame.js.map +1 -0
  208. package/dist/components/floatingSurface.js +35 -0
  209. package/dist/components/floatingSurface.js.map +1 -0
  210. package/dist/components/marketing/CTASection.js +47 -0
  211. package/dist/components/marketing/CTASection.js.map +1 -0
  212. package/dist/components/marketing/FeatureGrid.js +49 -0
  213. package/dist/components/marketing/FeatureGrid.js.map +1 -0
  214. package/dist/components/marketing/Hero.js +44 -0
  215. package/dist/components/marketing/Hero.js.map +1 -0
  216. package/dist/components/marketing/PricingGrid.js +65 -0
  217. package/dist/components/marketing/PricingGrid.js.map +1 -0
  218. package/dist/components/marketing/SectionGrid.js +51 -0
  219. package/dist/components/marketing/SectionGrid.js.map +1 -0
  220. package/dist/components/paginationRange.js +28 -0
  221. package/dist/components/paginationRange.js.map +1 -0
  222. package/dist/components/portalTheme.js +14 -0
  223. package/dist/components/portalTheme.js.map +1 -0
  224. package/dist/components/saas/SaasLandingPage.js +201 -0
  225. package/dist/components/saas/SaasLandingPage.js.map +1 -0
  226. package/dist/components/siteNavContext.js +24 -0
  227. package/dist/components/siteNavContext.js.map +1 -0
  228. package/dist/components/slides/Slide.js +84 -0
  229. package/dist/components/slides/Slide.js.map +1 -0
  230. package/dist/components/slides/SlideDeck.js +228 -0
  231. package/dist/components/slides/SlideDeck.js.map +1 -0
  232. package/dist/components/themeInitScript.js +16 -0
  233. package/dist/components/themeInitScript.js.map +1 -0
  234. package/dist/components/themeScope.js +9 -0
  235. package/dist/components/themeScope.js.map +1 -0
  236. package/dist/components/toastLifetime.js +21 -0
  237. package/dist/components/toastLifetime.js.map +1 -0
  238. package/dist/fonts/DSSymbols-OFL.txt +98 -0
  239. package/dist/fonts/DSSymbols-Regular.woff2 +0 -0
  240. package/dist/fonts/SymbolsNerdFontMono-Regular.woff2 +0 -0
  241. package/dist/hooks/useActiveHeading.js +59 -0
  242. package/dist/hooks/useActiveHeading.js.map +1 -0
  243. package/dist/hooks/useCopyToClipboard.js +58 -0
  244. package/dist/hooks/useCopyToClipboard.js.map +1 -0
  245. package/dist/index.d.ts +6394 -0
  246. package/dist/index.js +117 -0
  247. package/dist/index.js.map +1 -0
  248. package/dist/lib/accentClasses.js +87 -0
  249. package/dist/lib/accentClasses.js.map +1 -0
  250. package/dist/lib/recipe.js +33 -0
  251. package/dist/lib/recipe.js.map +1 -0
  252. package/dist/lib/slug.js +39 -0
  253. package/dist/lib/slug.js.map +1 -0
  254. package/dist/lib/theme.js +76 -0
  255. package/dist/lib/theme.js.map +1 -0
  256. package/dist/lib/tokenRules.js +51 -0
  257. package/dist/lib/tokenRules.js.map +1 -0
  258. package/dist/prose.css +766 -0
  259. package/dist/styles.css +203 -0
  260. package/dist/theme/ansi.js +77 -0
  261. package/dist/theme/ansi.js.map +1 -0
  262. package/dist/theme/contrast.js +342 -0
  263. package/dist/theme/contrast.js.map +1 -0
  264. package/dist/theme/levels.js +234 -0
  265. package/dist/theme/levels.js.map +1 -0
  266. package/dist/theme/media.js +115 -0
  267. package/dist/theme/media.js.map +1 -0
  268. package/dist/theme/recommended.js +105 -0
  269. package/dist/theme/recommended.js.map +1 -0
  270. package/dist/theme.css +538 -0
  271. package/dist/tokens.js +1 -0
  272. package/dist/tokens.js.map +1 -0
  273. package/package.json +209 -6
  274. package/skills/design-system-usage/SKILL.md +75 -0
  275. package/terminal/rtk-midnight.alacritty.toml +33 -0
  276. package/terminal/rtk-midnight.ghostty.conf +24 -0
  277. package/terminal/rtk-midnight.itermcolors +167 -0
  278. package/terminal/rtk-midnight.windows-terminal.json +23 -0
  279. package/terminal/rtk-sketch.alacritty.toml +33 -0
  280. package/terminal/rtk-sketch.ghostty.conf +24 -0
  281. package/terminal/rtk-sketch.itermcolors +167 -0
  282. package/terminal/rtk-sketch.windows-terminal.json +23 -0
  283. package/tokens/palette.midnight.tokens.json +477 -0
  284. package/tokens/palette.sketch.tokens.json +477 -0
@@ -0,0 +1,122 @@
1
+ "use client";
2
+
3
+ // src/components/ErrorSummary.tsx
4
+ import { forwardRef, useCallback, useEffect, useId, useRef } from "react";
5
+ import { recipe } from "../lib/recipe.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ var errorSummary = recipe({
8
+ slots: {
9
+ root: "flex flex-col gap-3 border-2 border-intent-danger bg-surface-base p-5 font-mono shadow-hard-intent-danger",
10
+ title: "m-0 font-display text-lg font-bold uppercase tracking-wider text-content-primary",
11
+ description: "max-w-prose font-sans text-sm leading-relaxed text-content-secondary",
12
+ list: "m-0 flex list-none flex-col gap-2 p-0",
13
+ item: "flex gap-2 text-sm",
14
+ marker: "font-bold text-intent-danger",
15
+ link: "font-bold text-intent-danger underline underline-offset-4 hover:no-underline"
16
+ }
17
+ });
18
+ var FIELD = '[data-slot="field"], [data-slot="fieldset-field"]';
19
+ var FOCUSABLE = [
20
+ 'input:not([type="hidden"]):not([aria-hidden="true"])',
21
+ "select",
22
+ "textarea",
23
+ "button",
24
+ "a[href]",
25
+ "[tabindex]"
26
+ ].join(", ");
27
+ function isFocusable(element) {
28
+ return element.matches(FOCUSABLE) && element.tabIndex >= 0 && !element.matches(":disabled");
29
+ }
30
+ function focusTargetFor(element) {
31
+ if (isFocusable(element)) return element;
32
+ const scopes = [element, element.closest(FIELD)];
33
+ for (const scope of scopes) {
34
+ if (!scope) continue;
35
+ const found = Array.from(scope.querySelectorAll(FOCUSABLE)).find(isFocusable);
36
+ if (found) return found;
37
+ }
38
+ return null;
39
+ }
40
+ function assignRef(ref, node) {
41
+ if (typeof ref === "function") {
42
+ ref(node);
43
+ return;
44
+ }
45
+ if (ref) {
46
+ ref.current = node;
47
+ }
48
+ }
49
+ function followError(event, id) {
50
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
51
+ return;
52
+ }
53
+ const element = event.currentTarget.ownerDocument.getElementById(id);
54
+ const target = element && focusTargetFor(element);
55
+ if (!element || !target) return;
56
+ event.preventDefault();
57
+ target.focus({ preventScroll: true });
58
+ (element.closest(FIELD) ?? target).scrollIntoView?.({ block: "start" });
59
+ }
60
+ var ErrorSummary = forwardRef(function ErrorSummary2({
61
+ errors,
62
+ title = "There is a problem",
63
+ headingLevel = 2,
64
+ focusOnAppear = true,
65
+ children,
66
+ className,
67
+ ...props
68
+ }, ref) {
69
+ const styles = errorSummary();
70
+ const titleId = useId();
71
+ const root = useRef(null);
72
+ const hasErrors = errors.length > 0;
73
+ const attach = useCallback(
74
+ (node) => {
75
+ root.current = node;
76
+ assignRef(ref, node);
77
+ },
78
+ [ref]
79
+ );
80
+ useEffect(() => {
81
+ if (hasErrors && focusOnAppear) {
82
+ root.current?.focus();
83
+ }
84
+ }, [hasErrors, focusOnAppear]);
85
+ if (!hasErrors) return null;
86
+ const Heading = `h${headingLevel}`;
87
+ return /* @__PURE__ */ jsxs(
88
+ "section",
89
+ {
90
+ tabIndex: -1,
91
+ "aria-labelledby": titleId,
92
+ "data-slot": "error-summary",
93
+ ...props,
94
+ ref: attach,
95
+ className: styles.root({ class: className }),
96
+ children: [
97
+ /* @__PURE__ */ jsx(Heading, { id: titleId, "data-slot": "error-summary-title", className: styles.title(), children: title }),
98
+ children && // A `div`, not a `p`, for the reason `EmptyState` gives: the slot takes
99
+ // arbitrary nodes, and a block inside a paragraph is repaired by the
100
+ // parser into markup that is not what was written.
101
+ /* @__PURE__ */ jsx("div", { "data-slot": "error-summary-description", className: styles.description(), children }),
102
+ /* @__PURE__ */ jsx("ul", { "data-slot": "error-summary-list", className: styles.list(), children: errors.map((error, index) => /* @__PURE__ */ jsxs("li", { "data-slot": "error-summary-item", className: styles.item(), children: [
103
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: styles.marker(), children: ">" }),
104
+ /* @__PURE__ */ jsx(
105
+ "a",
106
+ {
107
+ href: `#${error.id}`,
108
+ "data-slot": "error-summary-link",
109
+ className: styles.link(),
110
+ onClick: (event) => followError(event, error.id),
111
+ children: error.message
112
+ }
113
+ )
114
+ ] }, `${error.id}-${index}`)) })
115
+ ]
116
+ }
117
+ );
118
+ });
119
+ export {
120
+ ErrorSummary
121
+ };
122
+ //# sourceMappingURL=ErrorSummary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/ErrorSummary.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, useCallback, useEffect, useId, useRef } from 'react';\nimport type { ForwardedRef, HTMLAttributes, MouseEvent, ReactNode } from 'react';\nimport { recipe } from '../lib/recipe';\n\n// The danger edge and its hard shadow are the whole signal that this box is\n// about a failure; the heading stays in the primary ink so it reads as a\n// heading, and the links carry the danger role, as a field's own error does.\nconst errorSummary = recipe({\n slots: {\n root:\n 'flex flex-col gap-3 border-2 border-intent-danger bg-surface-base p-5 font-mono ' +\n 'shadow-hard-intent-danger',\n title: 'm-0 font-display text-lg font-bold uppercase tracking-wider text-content-primary',\n description: 'max-w-prose font-sans text-sm leading-relaxed text-content-secondary',\n list: 'm-0 flex list-none flex-col gap-2 p-0',\n item: 'flex gap-2 text-sm',\n marker: 'font-bold text-intent-danger',\n link: 'font-bold text-intent-danger underline underline-offset-4 hover:no-underline',\n },\n});\n\n/** One entry in the summary: which control is wrong, and what is wrong with it. */\nexport interface ErrorSummaryError {\n /**\n * The `id` of the invalid control, without the `#`. The entry links to\n * `#id`, and following the link focuses that control. Give the field the\n * same `id` you list here — `Input`, `TextArea`, `Select`, `Checkbox`,\n * `Switch` and `RadioGroup` all take one.\n */\n id: string;\n /**\n * What is wrong and how to put it right, as the link text. Usually the same\n * words as the field's own `error`, so the reader recognises the field when\n * they arrive at it.\n */\n message: string;\n}\n\nexport interface ErrorSummaryProps extends Omit<\n HTMLAttributes<HTMLElement>,\n 'title' | 'className' | 'children'\n> {\n /**\n * The form's current errors, in the order the fields appear. Empty renders\n * nothing at all — there is no \"all clear\" state to announce.\n */\n errors: readonly ErrorSummaryError[];\n /**\n * The heading, which is also the region's accessible name. Say that\n * something is wrong, not what: the list says what.\n */\n title?: string;\n /**\n * The heading's level. `2` suits the usual place — the top of a form under\n * the page's `h1`. Set it where the form sits deeper in the outline.\n */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /**\n * Move focus to the summary when it appears — when `errors` goes from empty\n * to non-empty, including on mount. On by default, because focus is how the\n * summary is announced (see the component notes). Turn it off only where\n * errors appear while the reader is still typing, and focus it from the\n * submit handler through the ref instead. Not `autoFocus`: that is the\n * HTML attribute, which fires once on insertion, and this is a different\n * rule.\n */\n focusOnAppear?: boolean;\n /**\n * Optional prose between the heading and the list — a sentence about the\n * errors as a whole. The list is still rendered from `errors`.\n */\n children?: ReactNode;\n /** Merged onto the root. For placement — margins, width — not colour. */\n className?: string;\n}\n\n// The wrapper each field frame renders. A control found inside it is the one\n// the link is for, and the wrapper — label included — is what scrolls into\n// view, so the reader lands on the question and not just the box.\nconst FIELD = '[data-slot=\"field\"], [data-slot=\"fieldset-field\"]';\n\nconst FOCUSABLE = [\n 'input:not([type=\"hidden\"]):not([aria-hidden=\"true\"])',\n 'select',\n 'textarea',\n 'button',\n 'a[href]',\n '[tabindex]',\n].join(', ');\n\nfunction isFocusable(element: HTMLElement): boolean {\n return element.matches(FOCUSABLE) && element.tabIndex >= 0 && !element.matches(':disabled');\n}\n\n// The element an id names is not always the one that takes focus. A\n// `Checkbox` or `Switch` puts its id on Base UI's hidden input, which is\n// `aria-hidden` and out of the tab order, so the visible control beside it is\n// the target; a `RadioGroup`'s id is on the group, and the target is the radio\n// its roving focus would give Tab.\nfunction focusTargetFor(element: HTMLElement): HTMLElement | null {\n if (isFocusable(element)) return element;\n const scopes: (Element | null)[] = [element, element.closest(FIELD)];\n for (const scope of scopes) {\n if (!scope) continue;\n const found = Array.from(scope.querySelectorAll<HTMLElement>(FOCUSABLE)).find(isFocusable);\n if (found) return found;\n }\n return null;\n}\n\n// Module scope, as in `dialogPopupRef.ts`: written inline, the assignment\n// reads to the compiler lint as a component mutating a captured value.\nfunction assignRef<T>(ref: ForwardedRef<T>, node: T | null) {\n if (typeof ref === 'function') {\n ref(node);\n return;\n }\n if (ref) {\n ref.current = node;\n }\n}\n\nfunction followError(event: MouseEvent<HTMLAnchorElement>, id: string) {\n // A modified or secondary click is the reader asking the browser for\n // something else; leave it to the browser.\n if (\n event.defaultPrevented ||\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return;\n }\n const element = event.currentTarget.ownerDocument.getElementById(id);\n const target = element && focusTargetFor(element);\n // Nothing focusable under that id: the plain fragment jump still scrolls\n // to it, which is better than a link that does nothing.\n if (!element || !target) return;\n\n event.preventDefault();\n target.focus({ preventScroll: true });\n // Optional because not every DOM implements it; jsdom does not.\n (element.closest(FIELD) ?? target).scrollIntoView?.({ block: 'start' });\n}\n\n/**\n * The form's errors, gathered in one place at the top of the form, each a\n * link to the field it is about.\n *\n * Field-level `error` says what is wrong with *this* field, where the reader\n * already is. After a failed submit the reader is at the button, and a long\n * form's errors are scattered above them, some off-screen. This is the list\n * they start from: one heading saying that something is wrong, one line per\n * error, and each line takes them to the control — focus moves to it, and its\n * label scrolls into view.\n *\n * ## How it is announced: focus, not `role=\"alert\"`\n *\n * When the summary appears it moves focus to itself (`tabIndex={-1}`), and it\n * is a `section` named by its heading, so a screen reader announces \"There is\n * a problem, region\" and reads on into the list. That is one deliberate\n * choice, made instead of a live region:\n *\n * - Focus puts the reader *where the fixes are*. The next Tab is the first\n * error's link. An alert is read out and leaves the reader at the submit\n * button with the list somewhere above them.\n * - Doing both double-announces: the alert fires and the focus change reads\n * the same content again.\n *\n * The fields keep to the same rule. A field's `error` is not a live region\n * either: it describes its control through `aria-describedby`, so it is read\n * when a link here moves focus to the field, and not at the moment the\n * summary appears. Before #299 every field error was `role=\"alert\"`, and a\n * failed submit read each one on top of the summary.\n *\n * So there is no `role=\"alert\"` here, and `focusOnAppear` is on by default. It\n * fires when `errors` goes from empty to non-empty. A second failed submit\n * while errors are still listed does not re-fire it; remount the summary with\n * a `key` that changes per submit attempt, or call `focus()` on its ref.\n *\n * It holds no copy of its own beyond the default heading, and renders nothing\n * when `errors` is empty.\n *\n * ```tsx\n * <ErrorSummary\n * key={attempt}\n * errors={[\n * { id: 'display-name', message: 'Enter a display name' },\n * { id: 'terms', message: 'Accept the terms to continue' },\n * ]}\n * />\n * ```\n */\nexport const ErrorSummary = forwardRef<HTMLElement, ErrorSummaryProps>(function ErrorSummary(\n {\n errors,\n title = 'There is a problem',\n headingLevel = 2,\n focusOnAppear = true,\n children,\n className,\n ...props\n },\n ref,\n) {\n const styles = errorSummary();\n const titleId = useId();\n const root = useRef<HTMLElement | null>(null);\n const hasErrors = errors.length > 0;\n\n const attach = useCallback(\n (node: HTMLElement | null) => {\n root.current = node;\n assignRef(ref, node);\n },\n [ref],\n );\n\n useEffect(() => {\n if (hasErrors && focusOnAppear) {\n root.current?.focus();\n }\n }, [hasErrors, focusOnAppear]);\n\n if (!hasErrors) return null;\n\n const Heading = `h${headingLevel}` as const;\n\n return (\n <section\n tabIndex={-1}\n aria-labelledby={titleId}\n data-slot=\"error-summary\"\n {...props}\n ref={attach}\n className={styles.root({ class: className })}\n >\n <Heading id={titleId} data-slot=\"error-summary-title\" className={styles.title()}>\n {title}\n </Heading>\n {children && (\n // A `div`, not a `p`, for the reason `EmptyState` gives: the slot takes\n // arbitrary nodes, and a block inside a paragraph is repaired by the\n // parser into markup that is not what was written.\n <div data-slot=\"error-summary-description\" className={styles.description()}>\n {children}\n </div>\n )}\n <ul data-slot=\"error-summary-list\" className={styles.list()}>\n {errors.map((error, index) => (\n <li key={`${error.id}-${index}`} data-slot=\"error-summary-item\" className={styles.item()}>\n <span aria-hidden=\"true\" className={styles.marker()}>\n &gt;\n </span>\n <a\n href={`#${error.id}`}\n data-slot=\"error-summary-link\"\n className={styles.link()}\n onClick={(event) => followError(event, error.id)}\n >\n {error.message}\n </a>\n </li>\n ))}\n </ul>\n </section>\n );\n});\n"],"mappings":";;;AAEA,SAAS,YAAY,aAAa,WAAW,OAAO,cAAc;AAElE,SAAS,cAAc;AA6OjB,cAaI,YAbJ;AAxON,IAAM,eAAe,OAAO;AAAA,EAC1B,OAAO;AAAA,IACL,MACE;AAAA,IAEF,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,EACR;AACF,CAAC;AA4DD,IAAM,QAAQ;AAEd,IAAM,YAAY;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAEX,SAAS,YAAY,SAA+B;AAClD,SAAO,QAAQ,QAAQ,SAAS,KAAK,QAAQ,YAAY,KAAK,CAAC,QAAQ,QAAQ,WAAW;AAC5F;AAOA,SAAS,eAAe,SAA0C;AAChE,MAAI,YAAY,OAAO,EAAG,QAAO;AACjC,QAAM,SAA6B,CAAC,SAAS,QAAQ,QAAQ,KAAK,CAAC;AACnE,aAAW,SAAS,QAAQ;AAC1B,QAAI,CAAC,MAAO;AACZ,UAAM,QAAQ,MAAM,KAAK,MAAM,iBAA8B,SAAS,CAAC,EAAE,KAAK,WAAW;AACzF,QAAI,MAAO,QAAO;AAAA,EACpB;AACA,SAAO;AACT;AAIA,SAAS,UAAa,KAAsB,MAAgB;AAC1D,MAAI,OAAO,QAAQ,YAAY;AAC7B,QAAI,IAAI;AACR;AAAA,EACF;AACA,MAAI,KAAK;AACP,QAAI,UAAU;AAAA,EAChB;AACF;AAEA,SAAS,YAAY,OAAsC,IAAY;AAGrE,MACE,MAAM,oBACN,MAAM,WAAW,KACjB,MAAM,WACN,MAAM,WACN,MAAM,YACN,MAAM,QACN;AACA;AAAA,EACF;AACA,QAAM,UAAU,MAAM,cAAc,cAAc,eAAe,EAAE;AACnE,QAAM,SAAS,WAAW,eAAe,OAAO;AAGhD,MAAI,CAAC,WAAW,CAAC,OAAQ;AAEzB,QAAM,eAAe;AACrB,SAAO,MAAM,EAAE,eAAe,KAAK,CAAC;AAEpC,GAAC,QAAQ,QAAQ,KAAK,KAAK,QAAQ,iBAAiB,EAAE,OAAO,QAAQ,CAAC;AACxE;AAkDO,IAAM,eAAe,WAA2C,SAASA,cAC9E;AAAA,EACE;AAAA,EACA,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,SAAS,aAAa;AAC5B,QAAM,UAAU,MAAM;AACtB,QAAM,OAAO,OAA2B,IAAI;AAC5C,QAAM,YAAY,OAAO,SAAS;AAElC,QAAM,SAAS;AAAA,IACb,CAAC,SAA6B;AAC5B,WAAK,UAAU;AACf,gBAAU,KAAK,IAAI;AAAA,IACrB;AAAA,IACA,CAAC,GAAG;AAAA,EACN;AAEA,YAAU,MAAM;AACd,QAAI,aAAa,eAAe;AAC9B,WAAK,SAAS,MAAM;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,WAAW,aAAa,CAAC;AAE7B,MAAI,CAAC,UAAW,QAAO;AAEvB,QAAM,UAAU,IAAI,YAAY;AAEhC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU;AAAA,MACV,mBAAiB;AAAA,MACjB,aAAU;AAAA,MACT,GAAG;AAAA,MACJ,KAAK;AAAA,MACL,WAAW,OAAO,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,MAE3C;AAAA,4BAAC,WAAQ,IAAI,SAAS,aAAU,uBAAsB,WAAW,OAAO,MAAM,GAC3E,iBACH;AAAA,QACC;AAAA;AAAA;AAAA,QAIC,oBAAC,SAAI,aAAU,6BAA4B,WAAW,OAAO,YAAY,GACtE,UACH;AAAA,QAEF,oBAAC,QAAG,aAAU,sBAAqB,WAAW,OAAO,KAAK,GACvD,iBAAO,IAAI,CAAC,OAAO,UAClB,qBAAC,QAAgC,aAAU,sBAAqB,WAAW,OAAO,KAAK,GACrF;AAAA,8BAAC,UAAK,eAAY,QAAO,WAAW,OAAO,OAAO,GAAG,eAErD;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAM,IAAI,MAAM,EAAE;AAAA,cAClB,aAAU;AAAA,cACV,WAAW,OAAO,KAAK;AAAA,cACvB,SAAS,CAAC,UAAU,YAAY,OAAO,MAAM,EAAE;AAAA,cAE9C,gBAAM;AAAA;AAAA,UACT;AAAA,aAXO,GAAG,MAAM,EAAE,IAAI,KAAK,EAY7B,CACD,GACH;AAAA;AAAA;AAAA,EACF;AAEJ,CAAC;","names":["ErrorSummary"]}
@@ -0,0 +1,37 @@
1
+ // src/components/Fieldset.tsx
2
+ import { forwardRef } from "react";
3
+ import { Fieldset as BaseFieldset } from "@base-ui/react/fieldset";
4
+ import { cn } from "../lib/recipe.js";
5
+ import { GroupFrame, labelClassName } from "./fieldFrame.js";
6
+ import { jsx } from "react/jsx-runtime";
7
+ var Legend = forwardRef(function Legend2({ className, ...props }, ref) {
8
+ return /* @__PURE__ */ jsx(
9
+ BaseFieldset.Legend,
10
+ {
11
+ ref,
12
+ "data-slot": "fieldset-legend",
13
+ className: cn(labelClassName(), className),
14
+ ...props
15
+ }
16
+ );
17
+ });
18
+ var Fieldset = forwardRef(function Fieldset2({ legend, error, helperText, disabled, className, children, ...props }, ref) {
19
+ return /* @__PURE__ */ jsx(
20
+ GroupFrame,
21
+ {
22
+ ref,
23
+ legend: legend ? /* @__PURE__ */ jsx(Legend, { children: legend }) : void 0,
24
+ error,
25
+ helperText,
26
+ disabled,
27
+ className,
28
+ ...props,
29
+ children
30
+ }
31
+ );
32
+ });
33
+ export {
34
+ Fieldset,
35
+ Legend
36
+ };
37
+ //# sourceMappingURL=Fieldset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Fieldset.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport type { HTMLAttributes, ReactNode, Ref } from 'react';\nimport { Fieldset as BaseFieldset } from '@base-ui/react/fieldset';\nimport { cn } from '../lib/recipe';\nimport { GroupFrame, labelClassName } from './fieldFrame';\n\nexport interface LegendProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {\n /** Merged onto the legend, after the label typography it shares with every field label. */\n className?: string;\n /** The group's name. Plain text in almost every case; it is announced as the group's label. */\n children?: ReactNode;\n}\n\n/**\n * The name of a group — the one accessible name a `Fieldset` or a\n * `RadioGroup` is announced by.\n *\n * `Fieldset` and `RadioGroup` render one for you from their `legend` prop, so\n * reach for this directly only when the name is more than a string. It must\n * sit inside one of them: Base UI's `Fieldset.Legend` registers its id with\n * the enclosing fieldset, and that registration is the whole association.\n *\n * A `div`, not a native `<legend>`. The group it names is sometimes a\n * `<fieldset>` and sometimes a `radiogroup`, and a `<legend>` is only valid as\n * the first child of the former; `aria-labelledby` works for both.\n *\n * ```tsx\n * <Fieldset>\n * <Legend>Notify me about <code>main</code></Legend>\n * …\n * </Fieldset>\n * ```\n */\nexport const Legend = forwardRef<HTMLDivElement, LegendProps>(function Legend(\n { className, ...props },\n ref,\n) {\n return (\n <BaseFieldset.Legend\n ref={ref}\n data-slot=\"fieldset-legend\"\n // `cn` for the reason every control here gives: a caller's utility has to\n // replace the shared typography, not race it in CSS source order.\n className={cn(labelClassName(), className)}\n {...props}\n />\n );\n});\n\nexport interface FieldsetProps\n extends Omit<HTMLAttributes<HTMLFieldSetElement>, 'className' | 'children'> {\n /**\n * The group's visible name, rendered as a `Legend` and wired to the group by\n * `aria-labelledby`. Omit it only to place a `Legend` of your own among the\n * children; a group with no name is one a screen reader announces as\n * \"group\" and nothing else.\n */\n legend?: string;\n /**\n * The group's validation message — an error about the set, not any one\n * control in it (\"choose at least one region\"). It describes the fieldset\n * and every control inside it, so it is read when focus enters the group.\n * It is not a live region; `ErrorSummary` announces a failed submit.\n * Per-control errors belong on the controls.\n */\n error?: string;\n /**\n * Standing guidance for the whole group, shown under it and replaced by\n * `error` while one is set — the same rule a single field follows.\n */\n helperText?: string;\n /**\n * Disables every control in the group. It is the native fieldset attribute\n * *and* the fieldset context Base UI's fields read, so `Checkbox`, `Switch`\n * and `Input` inside grey out and refuse input without each being told.\n */\n disabled?: boolean;\n /** Merged onto the `<fieldset>`, not the wrapper around it and its message. */\n className?: string;\n /** The controls the group gathers — typically `Checkbox`es, or a pair of `Input`s. */\n children: ReactNode;\n}\n\n/**\n * A set of controls that answer one question together, with a label, a\n * description and an error for the set.\n *\n * `Input`, `Checkbox` and `Switch` each carry one label and one message for\n * one control. A set of checkboxes, a start-and-end date pair or an address\n * block needs the same three things one level up, and this is where they\n * live: it is the same field contract — `error` is the message *and* the\n * invalid state, `helperText` gives way to it — applied to a group. It is not\n * radio-specific; `RadioGroup` is built on the same frame.\n *\n * What it gets right that a `<div>` with a heading does not: the legend is\n * the group's accessible name, the group's error is in the fieldset's\n * `aria-describedby` rather than hanging off whichever control it was placed\n * next to, and `disabled` reaches every control inside.\n *\n * ```tsx\n * <Fieldset legend=\"Regions\" helperText=\"Where the replicas run\">\n * <Checkbox name=\"region\" value=\"eu\" label=\"Europe\" />\n * <Checkbox name=\"region\" value=\"us\" label=\"North America\" />\n * </Fieldset>\n * ```\n */\nexport const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(function Fieldset(\n { legend, error, helperText, disabled, className, children, ...props },\n ref,\n) {\n return (\n <GroupFrame\n ref={ref as Ref<HTMLElement>}\n legend={legend ? <Legend>{legend}</Legend> : undefined}\n error={error}\n helperText={helperText}\n disabled={disabled}\n className={className}\n {...props}\n >\n {children}\n </GroupFrame>\n );\n});\n"],"mappings":";AAAA,SAAS,kBAAkB;AAE3B,SAAS,YAAY,oBAAoB;AACzC,SAAS,UAAU;AACnB,SAAS,YAAY,sBAAsB;AAkCvC;AALG,IAAM,SAAS,WAAwC,SAASA,QACrE,EAAE,WAAW,GAAG,MAAM,GACtB,KACA;AACA,SACE;AAAA,IAAC,aAAa;AAAA,IAAb;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MAGV,WAAW,GAAG,eAAe,GAAG,SAAS;AAAA,MACxC,GAAG;AAAA;AAAA,EACN;AAEJ,CAAC;AA2DM,IAAM,WAAW,WAA+C,SAASC,UAC9E,EAAE,QAAQ,OAAO,YAAY,UAAU,WAAW,UAAU,GAAG,MAAM,GACrE,KACA;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,QAAQ,SAAS,oBAAC,UAAQ,kBAAO,IAAY;AAAA,MAC7C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ,CAAC;","names":["Legend","Fieldset"]}
@@ -0,0 +1,60 @@
1
+ // src/components/Glyph.tsx
2
+ import { cn } from "../lib/recipe.js";
3
+ import { NerdIcon } from "./NerdIcon.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ function Glyph({
6
+ name,
7
+ accent,
8
+ size = "md",
9
+ bracketed = false,
10
+ children,
11
+ label,
12
+ className,
13
+ ...props
14
+ }) {
15
+ if (name) {
16
+ return /* @__PURE__ */ jsx(
17
+ NerdIcon,
18
+ {
19
+ name,
20
+ accent,
21
+ size,
22
+ bracketed,
23
+ label,
24
+ className,
25
+ ...props
26
+ }
27
+ );
28
+ }
29
+ return /* @__PURE__ */ jsxs(
30
+ "span",
31
+ {
32
+ "data-slot": "glyph",
33
+ className: cn(
34
+ "inline-flex items-center font-mono select-none",
35
+ accent === "primary" && "text-accent-primary",
36
+ accent === "secondary" && "text-accent-secondary",
37
+ accent === "tertiary" && "text-accent-tertiary",
38
+ accent === "success" && "text-intent-success",
39
+ accent === "warning" && "text-intent-warning",
40
+ accent === "danger" && "text-intent-danger",
41
+ accent === "info" && "text-intent-info",
42
+ accent === "muted" && "text-content-muted",
43
+ className
44
+ ),
45
+ role: label ? "img" : void 0,
46
+ "aria-label": label,
47
+ "aria-hidden": !label,
48
+ ...props,
49
+ children: [
50
+ bracketed && /* @__PURE__ */ jsx("span", { className: "text-content-muted font-bold mr-0.5", children: "[" }),
51
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-center inline-block leading-none", children }),
52
+ bracketed && /* @__PURE__ */ jsx("span", { className: "text-content-muted font-bold ml-0.5", children: "]" })
53
+ ]
54
+ }
55
+ );
56
+ }
57
+ export {
58
+ Glyph
59
+ };
60
+ //# sourceMappingURL=Glyph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Glyph.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../lib/recipe';\nimport { NerdIcon, type NerdIconAccent, type NerdIconName } from './NerdIcon';\n\nexport interface GlyphProps extends HTMLAttributes<HTMLSpanElement> {\n /** Named Nerd Font glyph or custom ASCII character */\n name?: NerdIconName;\n /** Semantic role accent color */\n accent?: NerdIconAccent;\n /** Size scale */\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n /** Wrap with brutalist brackets: `[  ]` */\n bracketed?: boolean;\n /** Custom character or text child */\n children?: ReactNode;\n /** Screen-reader accessible label */\n label?: string;\n}\n\n/**\n * Unified Glyph primitive for typographic and Nerd Font icons.\n *\n * Supports both named developer icons (`name=\"git-branch\"`) and pure ASCII symbols (`children=\"->\"`).\n *\n * @example\n * ```tsx\n * // Named Nerd Font\n * <Glyph name=\"git-branch\" accent=\"primary\" />\n *\n * // Typographic ASCII\n * <Glyph bracketed accent=\"secondary\">-></Glyph>\n * ```\n */\nexport function Glyph({\n name,\n accent,\n size = 'md',\n bracketed = false,\n children,\n label,\n className,\n ...props\n}: GlyphProps) {\n if (name) {\n return (\n <NerdIcon\n name={name}\n accent={accent}\n size={size}\n bracketed={bracketed}\n label={label}\n className={className}\n {...props}\n />\n );\n }\n\n return (\n <span data-slot=\"glyph\"\n className={cn(\n 'inline-flex items-center font-mono select-none',\n accent === 'primary' && 'text-accent-primary',\n accent === 'secondary' && 'text-accent-secondary',\n accent === 'tertiary' && 'text-accent-tertiary',\n accent === 'success' && 'text-intent-success',\n accent === 'warning' && 'text-intent-warning',\n accent === 'danger' && 'text-intent-danger',\n accent === 'info' && 'text-intent-info',\n accent === 'muted' && 'text-content-muted',\n className,\n )}\n role={label ? 'img' : undefined}\n aria-label={label}\n aria-hidden={!label}\n {...props}\n >\n {bracketed && <span className=\"text-content-muted font-bold mr-0.5\">[</span>}\n <span className=\"font-mono text-center inline-block leading-none\">{children}</span>\n {bracketed && <span className=\"text-content-muted font-bold ml-0.5\">]</span>}\n </span>\n );\n}\n"],"mappings":";AACA,SAAS,UAAU;AACnB,SAAS,gBAAwD;AA2C3D,cAaF,YAbE;AAZC,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAe;AACb,MAAI,MAAM;AACR,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACC,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MAAK,aAAU;AAAA,MACd,WAAW;AAAA,QACT;AAAA,QACA,WAAW,aAAa;AAAA,QACxB,WAAW,eAAe;AAAA,QAC1B,WAAW,cAAc;AAAA,QACzB,WAAW,aAAa;AAAA,QACxB,WAAW,aAAa;AAAA,QACxB,WAAW,YAAY;AAAA,QACvB,WAAW,UAAU;AAAA,QACrB,WAAW,WAAW;AAAA,QACtB;AAAA,MACF;AAAA,MACA,MAAM,QAAQ,QAAQ;AAAA,MACtB,cAAY;AAAA,MACZ,eAAa,CAAC;AAAA,MACb,GAAG;AAAA,MAEH;AAAA,qBAAa,oBAAC,UAAK,WAAU,uCAAsC,eAAC;AAAA,QACrE,oBAAC,UAAK,WAAU,mDAAmD,UAAS;AAAA,QAC3E,aAAa,oBAAC,UAAK,WAAU,uCAAsC,eAAC;AAAA;AAAA;AAAA,EACvE;AAEJ;","names":[]}
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ // src/components/Input.tsx
4
+ import { recipe } from "../lib/recipe.js";
5
+ import { Field as BaseField } from "@base-ui/react/field";
6
+ import { FieldFrame, accentStyle } from "./fieldFrame.js";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var fieldControl = recipe({
9
+ slots: {
10
+ /**
11
+ * No `outline-none` here, deliberately. The focus accent is a *border*
12
+ * swap, and a border swap is invisible under forced-colors — which is why
13
+ * `styles.css` declares a global `:focus-visible` outline. Suppressing the
14
+ * outline from this slot defeats that rule: the utility carries a class and
15
+ * a pseudo-class, so it outranks the bare `:focus-visible` selector and
16
+ * wins on exactly the keyboard focus the rule exists to cover.
17
+ *
18
+ * The `disabled:` half is the treatment `Select`'s listbox trigger already
19
+ * wore: a sunken ground, the subtle edge and muted ink. Without it a
20
+ * disabled `Input` rendered exactly like an editable one — the username
21
+ * in the account-settings proof read as a field you could change (#252).
22
+ */
23
+ control: "w-full border-2 border-edge-strong bg-surface-base font-mono text-sm text-content-primary transition-colors placeholder:text-content-muted focus:border-[var(--field-accent)] disabled:cursor-not-allowed disabled:border-edge-subtle disabled:bg-surface-sunken disabled:text-content-muted"
24
+ },
25
+ variants: {
26
+ /** Padding differs by control: a textarea is a box, the others are lines. */
27
+ shape: {
28
+ line: { control: "px-4 py-2.5" },
29
+ box: { control: "p-4" }
30
+ },
31
+ invalid: {
32
+ true: {
33
+ control: "border-intent-danger"
34
+ }
35
+ },
36
+ interactive: {
37
+ true: { control: "cursor-pointer" }
38
+ }
39
+ },
40
+ defaultVariants: {
41
+ shape: "line",
42
+ invalid: false
43
+ }
44
+ });
45
+ function Input({
46
+ label,
47
+ error,
48
+ helperText,
49
+ /**
50
+ * Semantic accent for the focus border, as on every control here. Declared
51
+ * on `FieldProps` in `./fieldFrame`, and named again at the default so the
52
+ * generated props table has a description rather than a blank cell.
53
+ */
54
+ accent = "primary",
55
+ /** Merged onto the control itself, not the label-and-error group. */
56
+ className = "",
57
+ id,
58
+ style,
59
+ ...props
60
+ }) {
61
+ const styles = fieldControl({ invalid: Boolean(error) });
62
+ return /* @__PURE__ */ jsx(FieldFrame, { label, error, helperText, children: /* @__PURE__ */ jsx(
63
+ BaseField.Control,
64
+ {
65
+ id,
66
+ "data-slot": "field-control",
67
+ style: accentStyle(accent, style),
68
+ className: styles.control({ class: className }),
69
+ ...props
70
+ }
71
+ ) });
72
+ }
73
+ function TextArea({
74
+ label,
75
+ error,
76
+ helperText,
77
+ /**
78
+ * Semantic accent for the focus border, as on every control here. Declared
79
+ * on `FieldProps` in `./fieldFrame`, and named again at the default so the
80
+ * generated props table has a description rather than a blank cell.
81
+ */
82
+ accent = "primary",
83
+ /** Merged onto the control itself, not the label-and-error group. */
84
+ className = "",
85
+ id,
86
+ style,
87
+ ...props
88
+ }) {
89
+ const styles = fieldControl({ shape: "box", invalid: Boolean(error) });
90
+ return /* @__PURE__ */ jsx(FieldFrame, { label, error, helperText, children: /* @__PURE__ */ jsx(
91
+ BaseField.Control,
92
+ {
93
+ id,
94
+ render: (controlProps) => /* @__PURE__ */ jsx(
95
+ "textarea",
96
+ {
97
+ ...controlProps,
98
+ ...props,
99
+ style: accentStyle(accent, style),
100
+ className: styles.control({ class: className })
101
+ }
102
+ )
103
+ }
104
+ ) });
105
+ }
106
+ export {
107
+ Input,
108
+ TextArea,
109
+ fieldControl
110
+ };
111
+ //# sourceMappingURL=Input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Input.tsx"],"sourcesContent":["'use client';\n\nimport type { InputHTMLAttributes, TextareaHTMLAttributes } from 'react';\nimport { recipe } from '../lib/recipe';\nimport { Field as BaseField } from '@base-ui/react/field';\nimport { FieldFrame, accentStyle } from './fieldFrame';\nimport type { FieldProps } from './fieldFrame';\n\n/**\n * Form controls — the reference for how a component in this system is built.\n *\n * Three things to copy from here:\n *\n * 1. **Every colour addresses a role.** `bg-surface-base`,\n * `text-content-primary`, `border-edge-strong`, `text-intent-danger`. Every\n * level is styled by the same classes, and a new one would be too.\n * 2. **Styling is a `recipe`, not a template string.** The recipe has one\n * slot per element, so a consumer can reach any of them, and conflicts\n * resolve — a caller's `bg-surface-raised` genuinely replaces the base's\n * `bg-surface-base` rather than racing it in CSS source order.\n * 3. **Runtime values stay out of the class string.** The focus accent comes\n * from a prop, so it travels as a custom property; a utility cannot be\n * assembled at build time from a value known only at runtime.\n *\n * The label, message and ARIA wiring are not here: they are `FieldFrame` in\n * `./fieldFrame`, shared with `Checkbox` and `Switch` so that one field\n * implementation serves every control.\n */\n\n/**\n * The control every text-shaped field wears: `Input`, `TextArea`, and both of\n * `Select`'s presentations — the native `<select>` and the listbox trigger.\n *\n * Exported for `./Select` (#164), whose closed trigger has to look like the\n * native control it replaced — one recipe is what makes that true rather than\n * approximately true. `src/index.ts` names this module's exports rather than\n * re-exporting it wholesale, so the recipe, and the `tailwind-variants` type\n * it carries, stays out of the published `.d.ts`.\n */\nexport const fieldControl = recipe({\n slots: {\n /**\n * No `outline-none` here, deliberately. The focus accent is a *border*\n * swap, and a border swap is invisible under forced-colors — which is why\n * `styles.css` declares a global `:focus-visible` outline. Suppressing the\n * outline from this slot defeats that rule: the utility carries a class and\n * a pseudo-class, so it outranks the bare `:focus-visible` selector and\n * wins on exactly the keyboard focus the rule exists to cover.\n *\n * The `disabled:` half is the treatment `Select`'s listbox trigger already\n * wore: a sunken ground, the subtle edge and muted ink. Without it a\n * disabled `Input` rendered exactly like an editable one — the username\n * in the account-settings proof read as a field you could change (#252).\n */\n control:\n 'w-full border-2 border-edge-strong bg-surface-base font-mono text-sm text-content-primary transition-colors placeholder:text-content-muted focus:border-[var(--field-accent)] ' +\n 'disabled:cursor-not-allowed disabled:border-edge-subtle disabled:bg-surface-sunken disabled:text-content-muted',\n },\n variants: {\n /** Padding differs by control: a textarea is a box, the others are lines. */\n shape: {\n line: { control: 'px-4 py-2.5' },\n box: { control: 'p-4' },\n },\n invalid: {\n true: {\n control: 'border-intent-danger',\n },\n },\n interactive: {\n true: { control: 'cursor-pointer' },\n },\n },\n defaultVariants: {\n shape: 'line',\n invalid: false,\n },\n});\n\nexport interface InputProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'>,\n FieldProps {}\n\n/**\n * A single-line text field, with its label, helper text and error as one unit.\n *\n * `Input`, `TextArea` and `Select` share one contract — `label`, `error`,\n * `helperText`, `accent` — and one recipe. The first two share a page; `Select`\n * has its own, for the list it opens. Pick by the shape of the answer, not by\n * styling: one line, several, or one of a fixed set.\n *\n * The field owns its own labelling. Supplying `label` associates it with a\n * generated `id` when the caller gives none, and `error` does three things at\n * once — the message, `aria-invalid`, and the danger border — so an invalid\n * field cannot end up looking wrong while announcing nothing.\n *\n * The message joins the control's `aria-describedby` and is read when the\n * field takes focus. It is not a live region. Pair a form with `ErrorSummary`,\n * which is what announces a failed submit.\n */\nexport function Input({\n label,\n error,\n helperText,\n /**\n * Semantic accent for the focus border, as on every control here. Declared\n * on `FieldProps` in `./fieldFrame`, and named again at the default so the\n * generated props table has a description rather than a blank cell.\n */\n accent = 'primary',\n /** Merged onto the control itself, not the label-and-error group. */\n className = '',\n id,\n style,\n ...props\n}: InputProps) {\n const styles = fieldControl({ invalid: Boolean(error) });\n\n return (\n <FieldFrame label={label} error={error} helperText={helperText}>\n <BaseField.Control\n id={id}\n data-slot=\"field-control\"\n style={accentStyle(accent, style)}\n // `class` is the recipe's override slot: it merges in rather than being\n // appended after, so a caller's utility actually wins.\n className={styles.control({ class: className })}\n {...props}\n />\n </FieldFrame>\n );\n}\n\nexport interface TextAreaProps\n extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'className'>,\n FieldProps {}\n\nexport function TextArea({\n label,\n error,\n helperText,\n /**\n * Semantic accent for the focus border, as on every control here. Declared\n * on `FieldProps` in `./fieldFrame`, and named again at the default so the\n * generated props table has a description rather than a blank cell.\n */\n accent = 'primary',\n /** Merged onto the control itself, not the label-and-error group. */\n className = '',\n id,\n style,\n ...props\n}: TextAreaProps) {\n const styles = fieldControl({ shape: 'box', invalid: Boolean(error) });\n\n // `id` goes to `Field.Control`, not onto the element `render` returns. Set\n // there, it overwrote the id the label's `for` points at — with the\n // caller's, or with `undefined` when none was given — so a click on the\n // words focused nothing. The name survived only through `aria-labelledby`.\n return (\n <FieldFrame label={label} error={error} helperText={helperText}>\n {/*\n The function form of `render`, not the element form, because\n `Field.Control` is typed to `HTMLInputElement` — spreading a\n textarea's own props through it is a type error rather than a\n cosmetic one. This takes the wired props and puts them on an element\n of the right type.\n */}\n <BaseField.Control\n id={id}\n render={(controlProps) => (\n <textarea\n {...controlProps}\n {...props}\n style={accentStyle(accent, style)}\n className={styles.control({ class: className })}\n />\n )}\n />\n </FieldFrame>\n );\n}\n"],"mappings":";;;AAGA,SAAS,cAAc;AACvB,SAAS,SAAS,iBAAiB;AACnC,SAAS,YAAY,mBAAmB;AAmHlC;AAjFC,IAAM,eAAe,OAAO;AAAA,EACjC,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcL,SACE;AAAA,EAEJ;AAAA,EACA,UAAU;AAAA;AAAA,IAER,OAAO;AAAA,MACL,MAAM,EAAE,SAAS,cAAc;AAAA,MAC/B,KAAK,EAAE,SAAS,MAAM;AAAA,IACxB;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,QACJ,SAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,MAAM,EAAE,SAAS,iBAAiB;AAAA,IACpC;AAAA,EACF;AAAA,EACA,iBAAiB;AAAA,IACf,OAAO;AAAA,IACP,SAAS;AAAA,EACX;AACF,CAAC;AAuBM,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS;AAAA;AAAA,EAET,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAe;AACb,QAAM,SAAS,aAAa,EAAE,SAAS,QAAQ,KAAK,EAAE,CAAC;AAEvD,SACE,oBAAC,cAAW,OAAc,OAAc,YACtC;AAAA,IAAC,UAAU;AAAA,IAAV;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,OAAO,YAAY,QAAQ,KAAK;AAAA,MAGhC,WAAW,OAAO,QAAQ,EAAE,OAAO,UAAU,CAAC;AAAA,MAC7C,GAAG;AAAA;AAAA,EACN,GACF;AAEJ;AAMO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS;AAAA;AAAA,EAET,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,SAAS,aAAa,EAAE,OAAO,OAAO,SAAS,QAAQ,KAAK,EAAE,CAAC;AAMrE,SACE,oBAAC,cAAW,OAAc,OAAc,YAQtC;AAAA,IAAC,UAAU;AAAA,IAAV;AAAA,MACC;AAAA,MACA,QAAQ,CAAC,iBACP;AAAA,QAAC;AAAA;AAAA,UACE,GAAG;AAAA,UACH,GAAG;AAAA,UACJ,OAAO,YAAY,QAAQ,KAAK;AAAA,UAChC,WAAW,OAAO,QAAQ,EAAE,OAAO,UAAU,CAAC;AAAA;AAAA,MAChD;AAAA;AAAA,EAEJ,GACF;AAEJ;","names":[]}
@@ -0,0 +1,76 @@
1
+ "use client";
2
+
3
+ // src/components/LinkProvider.tsx
4
+ import { createContext, forwardRef, useContext, useMemo } from "react";
5
+ import { cn } from "../lib/recipe.js";
6
+ import { jsx } from "react/jsx-runtime";
7
+ var LinkContext = createContext({ component: "a" });
8
+ function LinkProvider({ component, isCurrent, children }) {
9
+ const parent = useContext(LinkContext);
10
+ const resolvedComponent = component ?? parent.component;
11
+ const resolvedIsCurrent = isCurrent ?? parent.isCurrent;
12
+ const value = useMemo(
13
+ () => ({ component: resolvedComponent, isCurrent: resolvedIsCurrent }),
14
+ [resolvedComponent, resolvedIsCurrent]
15
+ );
16
+ return /* @__PURE__ */ jsx(LinkContext.Provider, { value, children });
17
+ }
18
+ function useLinkComponent() {
19
+ return useContext(LinkContext).component;
20
+ }
21
+ var EXTERNAL = /^([a-z][a-z0-9+.-]*:)?\/\//i;
22
+ var NON_ROUTED = /^(mailto:|tel:|#)/i;
23
+ function isExternalHref(href) {
24
+ return EXTERNAL.test(href) || NON_ROUTED.test(href);
25
+ }
26
+ function useIsCurrentHref(href) {
27
+ const { isCurrent } = useContext(LinkContext);
28
+ if (!isCurrent || isExternalHref(href)) return false;
29
+ return isCurrent(href);
30
+ }
31
+ var ProvidedAnchor = forwardRef(function ProvidedAnchor2({ component: Component, href, children, ...rest }, ref) {
32
+ if (isExternalHref(href)) {
33
+ const external = EXTERNAL.test(href);
34
+ return /* @__PURE__ */ jsx(
35
+ "a",
36
+ {
37
+ ref,
38
+ href,
39
+ ...external ? { target: "_blank", rel: "noopener noreferrer" } : {},
40
+ ...rest,
41
+ children
42
+ }
43
+ );
44
+ }
45
+ return /* @__PURE__ */ jsx(Component, { ref, href, ...rest, children });
46
+ });
47
+ function renderProvidedLink(component, props) {
48
+ return /* @__PURE__ */ jsx(ProvidedAnchor, { component, ...props });
49
+ }
50
+ var SiteLink = forwardRef(function SiteLink2({ href, current, className, "aria-current": ariaCurrent, ...props }, ref) {
51
+ const Component = useLinkComponent();
52
+ const matched = useIsCurrentHref(href);
53
+ const isCurrent = current ?? matched;
54
+ const merged = cn(className) || void 0;
55
+ return /* @__PURE__ */ jsx(
56
+ ProvidedAnchor,
57
+ {
58
+ ref,
59
+ component: Component,
60
+ href,
61
+ "data-slot": "site-link",
62
+ ...props,
63
+ className: merged,
64
+ "aria-current": isCurrent ? "page" : ariaCurrent
65
+ }
66
+ );
67
+ });
68
+ export {
69
+ LinkProvider,
70
+ SiteLink,
71
+ isExternalHref,
72
+ renderProvidedLink,
73
+ useIsCurrentHref,
74
+ useLinkComponent
75
+ };
76
+ //# sourceMappingURL=LinkProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/LinkProvider.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, forwardRef, useContext, useMemo } from 'react';\nimport type { AnchorHTMLAttributes, ElementType, ReactNode } from 'react';\nimport { cn } from '../lib/recipe';\n\n/** What a consumer's link component is handed: an `href` and the usual anchor props. */\nexport type LinkComponentProps = AnchorHTMLAttributes<HTMLAnchorElement> & {\n href: string;\n};\n\ninterface LinkContextValue {\n component: ElementType<LinkComponentProps>;\n isCurrent?: (href: string) => boolean;\n}\n\n// One context for every chrome in the package. `DocsLinkProvider` writes to it\n// too, so a site that mounts both the docs chrome and the site chrome injects\n// its router once rather than once per family (issue 246).\nconst LinkContext = createContext<LinkContextValue>({ component: 'a' });\n\nexport interface LinkProviderProps {\n /**\n * Any component accepting `href` and the usual anchor props — `next/link`,\n * `react-router`'s `Link`, a TanStack Router `Link`. Omit it to keep the\n * enclosing provider's, or the plain `<a>` when there is none.\n */\n component?: ElementType<LinkComponentProps>;\n /**\n * Whether `href` is the page being shown — the consumer's route, asked\n * rather than guessed. `SiteNavItem` and `SiteLink` mark the matching link\n * `aria-current=\"page\"`. Omit it to keep the enclosing provider's; with none\n * anywhere, nothing is current unless an item says so itself.\n */\n isCurrent?: (href: string) => boolean;\n children: ReactNode;\n}\n\n/**\n * The link adapter: how this package's chrome renders an internal link, and\n * how it learns which page is current, without depending on a router.\n *\n * The default is a plain `<a>` and no current page, which is right for\n * Storybook, static output and a server-rendered page that marks its own\n * items. An app with a client-side router supplies its `Link` so navigation\n * stops being a full page load, and an `isCurrent` built from its own\n * location so the active item follows the route. Nothing in the package reads\n * `window.location`: the route is the router's to know.\n *\n * Nested providers inherit what they do not set, so a subtree can swap the\n * link component and keep the current-page test, or the reverse.\n *\n * ```tsx\n * <LinkProvider component={Link} isCurrent={(href) => href === pathname}>\n * <SiteHeader … />\n * </LinkProvider>\n * ```\n */\nexport function LinkProvider({ component, isCurrent, children }: LinkProviderProps) {\n const parent = useContext(LinkContext);\n const resolvedComponent = component ?? parent.component;\n const resolvedIsCurrent = isCurrent ?? parent.isCurrent;\n const value = useMemo(\n () => ({ component: resolvedComponent, isCurrent: resolvedIsCurrent }),\n [resolvedComponent, resolvedIsCurrent],\n );\n return <LinkContext.Provider value={value}>{children}</LinkContext.Provider>;\n}\n\n/** The injected link component, or `'a'` when nothing was injected. */\nexport function useLinkComponent(): ElementType<LinkComponentProps> {\n return useContext(LinkContext).component;\n}\n\nconst EXTERNAL = /^([a-z][a-z0-9+.-]*:)?\\/\\//i;\nconst NON_ROUTED = /^(mailto:|tel:|#)/i;\n\n/** True for links that must stay plain anchors regardless of the injected router. */\nexport function isExternalHref(href: string): boolean {\n return EXTERNAL.test(href) || NON_ROUTED.test(href);\n}\n\n/**\n * Whether `href` is the current page, by the provider's `isCurrent`.\n *\n * An external or in-page href is never current: it is not a route, so there is\n * nothing for the consumer's router to match it against.\n */\nexport function useIsCurrentHref(href: string): boolean {\n const { isCurrent } = useContext(LinkContext);\n if (!isCurrent || isExternalHref(href)) return false;\n return isCurrent(href);\n}\n\n// The shared body of `SiteLink` and `DocsLink`: an internal link through the\n// injected component, an external one through a plain `<a>` opening a new tab\n// with `rel=\"noopener noreferrer\"`. Pure in-page hashes stay plain too —\n// routers tend to treat `#section` as a route change and scroll to the top\n// instead of to the anchor. The caller's own props are spread last, so an\n// explicit `target` or `rel` wins.\n//\n// A component rather than a helper taking a ref, so the ref is forwarded the\n// way React expects rather than handed to a function during render.\nconst ProvidedAnchor = forwardRef<\n HTMLAnchorElement,\n LinkComponentProps & { component: ElementType<LinkComponentProps> }\n>(function ProvidedAnchor({ component: Component, href, children, ...rest }, ref) {\n if (isExternalHref(href)) {\n const external = EXTERNAL.test(href);\n return (\n <a\n ref={ref}\n href={href}\n {...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...rest}\n >\n {children}\n </a>\n );\n }\n\n return (\n <Component ref={ref} href={href} {...rest}>\n {children}\n </Component>\n );\n});\n\n// `DocsLink`'s entry point. Not exported from the package: `src/index.ts`\n// names this file's exports one by one.\nexport function renderProvidedLink(\n component: ElementType<LinkComponentProps>,\n props: LinkComponentProps,\n) {\n return <ProvidedAnchor component={component} {...props} />;\n}\n\nexport interface SiteLinkProps extends LinkComponentProps {\n /**\n * Marks the link as the current page. Leave it unset and the provider's\n * `isCurrent` decides; set it to override that for this one link.\n */\n current?: boolean;\n /** Merged into the anchor's classes. */\n className?: string;\n}\n\n/**\n * A link rendered through the {@link LinkProvider} — the router's `Link` for an\n * internal href, a plain `<a>` for an external one — carrying\n * `aria-current=\"page\"` when it is the current page.\n *\n * The attribute is the whole of the current state: `SiteNavItem` styles it with\n * an `aria-[current=page]:` variant rather than a second flag, so what a screen\n * reader is told and what a sighted reader sees cannot disagree.\n */\nexport const SiteLink = forwardRef<HTMLAnchorElement, SiteLinkProps>(function SiteLink(\n { href, current, className, 'aria-current': ariaCurrent, ...props },\n ref,\n) {\n const Component = useLinkComponent();\n const matched = useIsCurrentHref(href);\n const isCurrent = current ?? matched;\n // `cn` so a caller's conflicting utilities resolve rather than both\n // emitting; `undefined` rather than an empty `class=\"\"` when there are none.\n const merged = cn(className) || undefined;\n return (\n <ProvidedAnchor\n ref={ref}\n component={Component}\n href={href}\n data-slot=\"site-link\"\n {...props}\n className={merged}\n aria-current={isCurrent ? 'page' : ariaCurrent}\n />\n );\n});\n"],"mappings":";;;AAEA,SAAS,eAAe,YAAY,YAAY,eAAe;AAE/D,SAAS,UAAU;AA8DV;AA/CT,IAAM,cAAc,cAAgC,EAAE,WAAW,IAAI,CAAC;AAuC/D,SAAS,aAAa,EAAE,WAAW,WAAW,SAAS,GAAsB;AAClF,QAAM,SAAS,WAAW,WAAW;AACrC,QAAM,oBAAoB,aAAa,OAAO;AAC9C,QAAM,oBAAoB,aAAa,OAAO;AAC9C,QAAM,QAAQ;AAAA,IACZ,OAAO,EAAE,WAAW,mBAAmB,WAAW,kBAAkB;AAAA,IACpE,CAAC,mBAAmB,iBAAiB;AAAA,EACvC;AACA,SAAO,oBAAC,YAAY,UAAZ,EAAqB,OAAe,UAAS;AACvD;AAGO,SAAS,mBAAoD;AAClE,SAAO,WAAW,WAAW,EAAE;AACjC;AAEA,IAAM,WAAW;AACjB,IAAM,aAAa;AAGZ,SAAS,eAAe,MAAuB;AACpD,SAAO,SAAS,KAAK,IAAI,KAAK,WAAW,KAAK,IAAI;AACpD;AAQO,SAAS,iBAAiB,MAAuB;AACtD,QAAM,EAAE,UAAU,IAAI,WAAW,WAAW;AAC5C,MAAI,CAAC,aAAa,eAAe,IAAI,EAAG,QAAO;AAC/C,SAAO,UAAU,IAAI;AACvB;AAWA,IAAM,iBAAiB,WAGrB,SAASA,gBAAe,EAAE,WAAW,WAAW,MAAM,UAAU,GAAG,KAAK,GAAG,KAAK;AAChF,MAAI,eAAe,IAAI,GAAG;AACxB,UAAM,WAAW,SAAS,KAAK,IAAI;AACnC,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACC,GAAI,WAAW,EAAE,QAAQ,UAAU,KAAK,sBAAsB,IAAI,CAAC;AAAA,QACnE,GAAG;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,SACE,oBAAC,aAAU,KAAU,MAAa,GAAG,MAClC,UACH;AAEJ,CAAC;AAIM,SAAS,mBACd,WACA,OACA;AACA,SAAO,oBAAC,kBAAe,WAAuB,GAAG,OAAO;AAC1D;AAqBO,IAAM,WAAW,WAA6C,SAASC,UAC5E,EAAE,MAAM,SAAS,WAAW,gBAAgB,aAAa,GAAG,MAAM,GAClE,KACA;AACA,QAAM,YAAY,iBAAiB;AACnC,QAAM,UAAU,iBAAiB,IAAI;AACrC,QAAM,YAAY,WAAW;AAG7B,QAAM,SAAS,GAAG,SAAS,KAAK;AAChC,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,aAAU;AAAA,MACT,GAAG;AAAA,MACJ,WAAW;AAAA,MACX,gBAAc,YAAY,SAAS;AAAA;AAAA,EACrC;AAEJ,CAAC;","names":["ProvidedAnchor","SiteLink"]}
@@ -0,0 +1,144 @@
1
+ "use client";
2
+
3
+ // src/components/Menu.tsx
4
+ import { forwardRef } from "react";
5
+ import { Menu as BaseMenu } from "@base-ui/react/menu";
6
+ import { Check } from "lucide-react";
7
+ import { cn, recipe } from "../lib/recipe.js";
8
+ import { usePortalThemeAttribute } from "./portalTheme.js";
9
+ import { floatingSurface, floatingParts } from "./floatingSurface.js";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ var menu = recipe({
12
+ extend: floatingSurface,
13
+ slots: { popup: "min-w-48 max-h-(--available-height) overflow-y-auto py-1" }
14
+ });
15
+ var Menu = forwardRef(function Menu2({
16
+ trigger,
17
+ children,
18
+ side = "bottom",
19
+ align = "start",
20
+ open,
21
+ defaultOpen,
22
+ onOpenChange,
23
+ className,
24
+ ...props
25
+ }, ref) {
26
+ const styles = menu();
27
+ const portalTheme = usePortalThemeAttribute();
28
+ return /* @__PURE__ */ jsxs(
29
+ BaseMenu.Root,
30
+ {
31
+ open,
32
+ defaultOpen,
33
+ onOpenChange: onOpenChange ? (next) => onOpenChange(next) : void 0,
34
+ children: [
35
+ /* @__PURE__ */ jsx(BaseMenu.Trigger, { render: trigger }),
36
+ /* @__PURE__ */ jsx(BaseMenu.Portal, { children: /* @__PURE__ */ jsx(
37
+ BaseMenu.Positioner,
38
+ {
39
+ ...portalTheme,
40
+ side,
41
+ align,
42
+ sideOffset: 6,
43
+ collisionPadding: 8,
44
+ "data-slot": "menu-positioner",
45
+ className: styles.positioner(),
46
+ children: /* @__PURE__ */ jsx(
47
+ BaseMenu.Popup,
48
+ {
49
+ ref,
50
+ "data-slot": "menu",
51
+ className: styles.popup({ class: className }),
52
+ ...props,
53
+ children
54
+ }
55
+ )
56
+ }
57
+ ) })
58
+ ]
59
+ }
60
+ );
61
+ });
62
+ var MenuItem = forwardRef(function MenuItem2({ children, intent, disabled = false, closeOnClick = true, className, ...props }, ref) {
63
+ const parts = floatingParts();
64
+ return /* @__PURE__ */ jsx(
65
+ BaseMenu.Item,
66
+ {
67
+ ref,
68
+ disabled,
69
+ closeOnClick,
70
+ "data-slot": "menu-item",
71
+ className: cn(
72
+ parts.item(),
73
+ intent === "danger" && parts.danger(),
74
+ disabled && parts.disabled(),
75
+ className
76
+ ),
77
+ ...props,
78
+ children
79
+ }
80
+ );
81
+ });
82
+ var MenuRadioGroup = forwardRef(
83
+ function MenuRadioGroup2({ label, value, defaultValue, onValueChange, children, className, ...props }, ref) {
84
+ const parts = floatingParts();
85
+ return /* @__PURE__ */ jsxs(
86
+ BaseMenu.RadioGroup,
87
+ {
88
+ ref,
89
+ value,
90
+ defaultValue,
91
+ onValueChange: onValueChange ? (next) => onValueChange(next) : void 0,
92
+ "data-slot": "menu-radio-group",
93
+ className,
94
+ ...props,
95
+ children: [
96
+ label ? /* @__PURE__ */ jsx(BaseMenu.GroupLabel, { "data-slot": "menu-group-label", className: parts.groupLabel(), children: label }) : null,
97
+ children
98
+ ]
99
+ }
100
+ );
101
+ }
102
+ );
103
+ var MenuRadioItem = forwardRef(
104
+ function MenuRadioItem2({ value, children, closeOnClick = false, className, ...props }, ref) {
105
+ const parts = floatingParts();
106
+ return /* @__PURE__ */ jsxs(
107
+ BaseMenu.RadioItem,
108
+ {
109
+ ref,
110
+ value,
111
+ closeOnClick,
112
+ "data-slot": "menu-radio-item",
113
+ className: cn(parts.item(), className),
114
+ ...props,
115
+ children: [
116
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: parts.indicator(), children: /* @__PURE__ */ jsx(BaseMenu.RadioItemIndicator, { "data-slot": "menu-radio-indicator", children: /* @__PURE__ */ jsx(Check, { size: 14, strokeWidth: 3 }) }) }),
117
+ children
118
+ ]
119
+ }
120
+ );
121
+ }
122
+ );
123
+ var MenuSeparator = forwardRef(
124
+ function MenuSeparator2({ className, ...props }, ref) {
125
+ const parts = floatingParts();
126
+ return /* @__PURE__ */ jsx(
127
+ BaseMenu.Separator,
128
+ {
129
+ ref,
130
+ "data-slot": "menu-separator",
131
+ className: cn(parts.separator(), className),
132
+ ...props
133
+ }
134
+ );
135
+ }
136
+ );
137
+ export {
138
+ Menu,
139
+ MenuItem,
140
+ MenuRadioGroup,
141
+ MenuRadioItem,
142
+ MenuSeparator
143
+ };
144
+ //# sourceMappingURL=Menu.js.map