@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,261 @@
1
+ "use client";
2
+
3
+ // src/components/AppShell.tsx
4
+ import {
5
+ createContext,
6
+ forwardRef,
7
+ useCallback,
8
+ useContext,
9
+ useEffect,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ useSyncExternalStore
14
+ } from "react";
15
+ import { Menu as MenuIcon } from "lucide-react";
16
+ import { recipe } from "../lib/recipe.js";
17
+ import { Button } from "./Button.js";
18
+ import { Drawer } from "./Drawer.js";
19
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
20
+ var WIDE_QUERY = "(min-width: 64rem)";
21
+ function canMatchMedia() {
22
+ return typeof window !== "undefined" && typeof window.matchMedia === "function";
23
+ }
24
+ function subscribeToWidth(onChange) {
25
+ if (!canMatchMedia()) return () => {
26
+ };
27
+ const query = window.matchMedia(WIDE_QUERY);
28
+ query.addEventListener("change", onChange);
29
+ return () => query.removeEventListener("change", onChange);
30
+ }
31
+ function isWide() {
32
+ return canMatchMedia() && window.matchMedia(WIDE_QUERY).matches;
33
+ }
34
+ function isWideOnServer() {
35
+ return false;
36
+ }
37
+ var AppShellContext = createContext(null);
38
+ var shell = recipe({
39
+ slots: {
40
+ root: "flex min-h-screen bg-surface-base font-sans text-content-primary",
41
+ column: "flex min-w-0 flex-1 flex-col"
42
+ }
43
+ });
44
+ var AppShell = forwardRef(function AppShell2({
45
+ sidebar,
46
+ topbar,
47
+ children,
48
+ sidebarOpen,
49
+ defaultSidebarOpen = false,
50
+ onSidebarOpenChange,
51
+ className,
52
+ ...props
53
+ }, ref) {
54
+ const styles = shell();
55
+ const [ownOpen, setOwnOpen] = useState(defaultSidebarOpen);
56
+ const open = sidebarOpen ?? ownOpen;
57
+ const wide = useSyncExternalStore(subscribeToWidth, isWide, isWideOnServer);
58
+ const hasSidebar = sidebar != null && sidebar !== false;
59
+ const drawerOpen = hasSidebar && open && !wide;
60
+ const onChange = useRef(onSidebarOpenChange);
61
+ useEffect(() => {
62
+ onChange.current = onSidebarOpenChange;
63
+ });
64
+ const controlled = sidebarOpen !== void 0;
65
+ const setSidebarOpen = useCallback(
66
+ (next) => {
67
+ if (!controlled) setOwnOpen(next);
68
+ onChange.current?.(next);
69
+ },
70
+ [controlled]
71
+ );
72
+ useEffect(() => {
73
+ if (wide && open) setSidebarOpen(false);
74
+ }, [wide, open, setSidebarOpen]);
75
+ const context = useMemo(
76
+ () => ({ hasSidebar, drawerOpen, setSidebarOpen }),
77
+ [hasSidebar, drawerOpen, setSidebarOpen]
78
+ );
79
+ return /* @__PURE__ */ jsx(AppShellContext.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { ref, "data-slot": "app-shell", className: styles.root({ class: className }), ...props, children: [
80
+ sidebar,
81
+ /* @__PURE__ */ jsxs("div", { "data-slot": "app-shell-column", className: styles.column(), children: [
82
+ topbar,
83
+ children
84
+ ] })
85
+ ] }) });
86
+ });
87
+ var sidebarStyles = recipe({
88
+ slots: {
89
+ aside: "hidden w-72 shrink-0 flex-col border-r-4 border-edge-strong bg-surface-base lg:sticky lg:top-0 lg:flex lg:h-screen",
90
+ header: "border-b-2 border-edge-strong px-6 py-5",
91
+ body: "flex-1 overflow-y-auto px-4 py-5",
92
+ footer: "border-t-2 border-edge-strong px-4 py-4",
93
+ drawerHeader: "mb-5 border-b-2 border-edge-strong pb-4"
94
+ }
95
+ });
96
+ var AppSidebar = forwardRef(function AppSidebar2({ label, header, children, footer, className, ...props }, ref) {
97
+ const styles = sidebarStyles();
98
+ const context = useContext(AppShellContext);
99
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
100
+ /* @__PURE__ */ jsxs(
101
+ "aside",
102
+ {
103
+ ref,
104
+ "aria-label": label,
105
+ "data-slot": "app-sidebar",
106
+ className: styles.aside({ class: className }),
107
+ ...props,
108
+ children: [
109
+ header != null && /* @__PURE__ */ jsx("div", { "data-slot": "app-sidebar-header", className: styles.header(), children: header }),
110
+ /* @__PURE__ */ jsx("div", { "data-slot": "app-sidebar-body", className: styles.body(), children }),
111
+ footer != null && /* @__PURE__ */ jsx("div", { "data-slot": "app-sidebar-footer", className: styles.footer(), children: footer })
112
+ ]
113
+ }
114
+ ),
115
+ context && /* @__PURE__ */ jsxs(
116
+ Drawer,
117
+ {
118
+ isOpen: context.drawerOpen,
119
+ onClose: () => context.setSidebarOpen(false),
120
+ placement: "left",
121
+ title: label,
122
+ footer: footer ?? void 0,
123
+ children: [
124
+ header != null && /* @__PURE__ */ jsx("div", { className: styles.drawerHeader(), children: header }),
125
+ children
126
+ ]
127
+ }
128
+ )
129
+ ] });
130
+ });
131
+ var navStyles = recipe({
132
+ slots: {
133
+ nav: "font-mono text-sm",
134
+ heading: "mb-2 px-3 font-mono text-xs font-bold uppercase tracking-widest text-content-muted",
135
+ list: "flex flex-col gap-1.5",
136
+ nested: "mt-1.5 ml-3 flex flex-col gap-1.5 border-l-2 border-edge-strong pl-3",
137
+ entry: "flex w-full items-center gap-2.5 border-2 px-3 py-2 text-left font-bold uppercase no-underline",
138
+ iconFrame: "inline-flex shrink-0",
139
+ icon: "h-4 w-4",
140
+ label: "min-w-0 flex-1 truncate",
141
+ group: "flex items-center gap-2.5 px-3 py-2 font-bold uppercase text-content-muted"
142
+ },
143
+ variants: {
144
+ active: {
145
+ true: { entry: "border-edge-strong bg-accent-primary text-content-inverse shadow-hard-sm" },
146
+ false: { entry: "border-transparent text-content-primary hover:border-edge-strong hover:bg-surface-raised" }
147
+ }
148
+ }
149
+ });
150
+ var AppSidebarNav = forwardRef(function AppSidebarNav2({ label, items, activeId, onNavigate, heading, className, ...props }, ref) {
151
+ const styles = navStyles();
152
+ const context = useContext(AppShellContext);
153
+ const choose = (id) => {
154
+ onNavigate?.(id);
155
+ context?.setSidebarOpen(false);
156
+ };
157
+ const renderEntry = (item) => {
158
+ const active = item.id === activeId;
159
+ const entry = navStyles({ active });
160
+ const Icon = item.icon;
161
+ const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
162
+ Icon && /* @__PURE__ */ jsx("span", { className: styles.iconFrame(), "aria-hidden": "true", children: /* @__PURE__ */ jsx(Icon, { className: styles.icon() }) }),
163
+ /* @__PURE__ */ jsx("span", { className: styles.label(), children: item.label }),
164
+ item.badge
165
+ ] });
166
+ if (item.href !== void 0) {
167
+ return /* @__PURE__ */ jsx(
168
+ "a",
169
+ {
170
+ href: item.href,
171
+ "aria-current": active ? "page" : void 0,
172
+ className: entry.entry(),
173
+ onClick: () => choose(item.id),
174
+ children: inner
175
+ }
176
+ );
177
+ }
178
+ if (item.items && !onNavigate) {
179
+ return /* @__PURE__ */ jsx("span", { className: styles.group(), children: inner });
180
+ }
181
+ return /* @__PURE__ */ jsx(
182
+ "button",
183
+ {
184
+ type: "button",
185
+ "aria-current": active ? "page" : void 0,
186
+ className: entry.entry(),
187
+ onClick: () => choose(item.id),
188
+ children: inner
189
+ }
190
+ );
191
+ };
192
+ const renderList = (list, depth) => /* @__PURE__ */ jsx("ul", { className: depth === 0 ? styles.list() : styles.nested(), children: list.map((item) => /* @__PURE__ */ jsxs("li", { children: [
193
+ renderEntry(item),
194
+ item.items && item.items.length > 0 && renderList(item.items, depth + 1)
195
+ ] }, item.id)) });
196
+ return /* @__PURE__ */ jsxs("nav", { ref, "aria-label": label, "data-slot": "app-sidebar-nav", className: styles.nav({ class: className }), ...props, children: [
197
+ heading != null && /* @__PURE__ */ jsx("p", { className: styles.heading(), children: heading }),
198
+ renderList(items, 0)
199
+ ] });
200
+ });
201
+ var topbarStyles = recipe({
202
+ slots: {
203
+ header: "flex flex-wrap items-center gap-4 border-b-4 border-edge-strong bg-surface-base px-4 py-3 md:px-8",
204
+ toggle: "shrink-0 px-3 lg:hidden",
205
+ lead: "flex min-w-0 flex-auto flex-wrap items-center gap-3",
206
+ actions: "ml-auto flex shrink-0 flex-wrap items-center gap-3"
207
+ }
208
+ });
209
+ var AppTopbar = forwardRef(function AppTopbar2({
210
+ label = "Application",
211
+ children,
212
+ actions,
213
+ sidebarToggleLabel = "Open navigation",
214
+ className,
215
+ ...props
216
+ }, ref) {
217
+ const styles = topbarStyles();
218
+ const context = useContext(AppShellContext);
219
+ return /* @__PURE__ */ jsxs(
220
+ "header",
221
+ {
222
+ ref,
223
+ "aria-label": label,
224
+ "data-slot": "app-topbar",
225
+ className: styles.header({ class: className }),
226
+ ...props,
227
+ children: [
228
+ context?.hasSidebar && /* @__PURE__ */ jsx(
229
+ Button,
230
+ {
231
+ type: "button",
232
+ size: "sm",
233
+ "aria-label": sidebarToggleLabel,
234
+ "aria-haspopup": "dialog",
235
+ "aria-expanded": context.drawerOpen,
236
+ "data-slot": "app-sidebar-toggle",
237
+ className: styles.toggle(),
238
+ onClick: () => context.setSidebarOpen(true),
239
+ children: /* @__PURE__ */ jsx(MenuIcon, { className: "h-5 w-5", "aria-hidden": "true" })
240
+ }
241
+ ),
242
+ /* @__PURE__ */ jsx("div", { "data-slot": "app-topbar-lead", className: styles.lead(), children }),
243
+ actions != null && /* @__PURE__ */ jsx("div", { "data-slot": "app-topbar-actions", className: styles.actions(), children: actions })
244
+ ]
245
+ }
246
+ );
247
+ });
248
+ var mainStyles = recipe({
249
+ base: "flex min-w-0 flex-1 flex-col gap-6 p-4 md:p-8"
250
+ });
251
+ var AppMain = forwardRef(function AppMain2({ label = "Content", children, className, ...props }, ref) {
252
+ return /* @__PURE__ */ jsx("main", { ref, "aria-label": label, "data-slot": "app-main", className: mainStyles({ class: className }), ...props, children });
253
+ });
254
+ export {
255
+ AppMain,
256
+ AppShell,
257
+ AppSidebar,
258
+ AppSidebarNav,
259
+ AppTopbar
260
+ };
261
+ //# sourceMappingURL=AppShell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/AppShell.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport type { ElementType, HTMLAttributes, ReactNode } from 'react';\nimport { Menu as MenuIcon } from 'lucide-react';\nimport { recipe } from '../lib/recipe';\nimport { Button } from './Button';\nimport { Drawer } from './Drawer';\n\n// The width at which the sidebar stops being off-canvas and stays on the page.\n// Tailwind's `lg` step, written as the same query so the CSS that shows the\n// persistent sidebar and the script that suppresses the drawer agree on one\n// number — a gap between them would leave a band of widths with both, or\n// neither.\nconst WIDE_QUERY = '(min-width: 64rem)';\n\nfunction canMatchMedia(): boolean {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function';\n}\n\nfunction subscribeToWidth(onChange: () => void): () => void {\n if (!canMatchMedia()) return () => {};\n const query = window.matchMedia(WIDE_QUERY);\n query.addEventListener('change', onChange);\n return () => query.removeEventListener('change', onChange);\n}\n\nfunction isWide(): boolean {\n return canMatchMedia() && window.matchMedia(WIDE_QUERY).matches;\n}\n\n// The server renders narrow: the persistent sidebar is shown by CSS alone, so\n// the only thing a narrow first paint decides is that no drawer is open yet.\nfunction isWideOnServer(): boolean {\n return false;\n}\n\ninterface AppShellContextValue {\n /** Whether `AppShell` was given a sidebar, so the topbar knows to offer a toggle. */\n hasSidebar: boolean;\n /** The off-canvas sidebar is open. Always false at desktop width. */\n drawerOpen: boolean;\n setSidebarOpen: (open: boolean) => void;\n}\n\nconst AppShellContext = createContext<AppShellContextValue | null>(null);\n\nconst shell = recipe({\n slots: {\n root: 'flex min-h-screen bg-surface-base font-sans text-content-primary',\n column: 'flex min-w-0 flex-1 flex-col',\n },\n});\n\nexport interface AppShellProps extends Omit<HTMLAttributes<HTMLDivElement>, 'className'> {\n /**\n * The navigation column — an `AppSidebar`. Persistent at desktop width;\n * below it, the same content opens off-canvas in a `Drawer` from the\n * topbar's toggle. Omit it for an application with no navigation, and the\n * toggle goes with it.\n */\n sidebar?: ReactNode;\n /** The bar above the content — an `AppTopbar`. */\n topbar?: ReactNode;\n /**\n * The page — an `AppMain`, which is the one `<main>` landmark. The shell\n * does not render `<main>` itself, so a page never ends up with two.\n */\n children: ReactNode;\n /**\n * Whether the off-canvas sidebar is open, controlled. Only means anything\n * below desktop width, where the sidebar is off-canvas; leave it unset and\n * the shell keeps its own state.\n */\n sidebarOpen?: boolean;\n /** Open the off-canvas sidebar on first render, uncontrolled — for a story or a walkthrough. */\n defaultSidebarOpen?: boolean;\n /**\n * Called with the next open state: the toggle, a dismissal, a navigation\n * from inside the drawer, or the viewport widening past the breakpoint.\n */\n onSidebarOpenChange?: (open: boolean) => void;\n /** Merged into the root's classes. */\n className?: string;\n}\n\n/**\n * The application layout: a navigation column, a bar, and the page.\n *\n * The *Application layout* capability (#247). Five pieces that compose into a\n * working app with no CSS of the consumer's own:\n *\n * - `AppShell` — the frame, and the owner of the sidebar's open state.\n * - `AppSidebar` — a labelled `<aside>` at desktop width; below it, the same\n * content in a left `Drawer`, which brings focus in on open, traps it, and\n * returns it to the toggle on close.\n * - `AppSidebarNav` — a labelled `<nav>` over a navigation tree the consumer\n * supplies, of any depth.\n * - `AppTopbar` — the `<header>`, with the sidebar toggle below desktop width,\n * a leading slot for a title or status, and an `actions` slot.\n * - `AppMain` — the single `<main>`.\n *\n * The account area is a slot — `AppSidebar`'s `footer` — rather than a user\n * prop with a name and an avatar: what an application shows about who is\n * signed in, if anyone is, is the application's business. The shell makes no\n * assumption about the domain it is holding either; the navigation, the\n * status, the actions and the content are all supplied.\n *\n * @example\n * ```tsx\n * <AppShell\n * sidebar={\n * <AppSidebar label=\"Workspace\" header={<strong>ACME</strong>} footer={<AccountMenu />}>\n * <AppSidebarNav label=\"Primary\" items={nav} activeId=\"projects\" />\n * </AppSidebar>\n * }\n * topbar={<AppTopbar actions={<Button size=\"sm\">NEW</Button>}>PROJECTS</AppTopbar>}\n * >\n * <AppMain>…</AppMain>\n * </AppShell>\n * ```\n */\nexport const AppShell = forwardRef<HTMLDivElement, AppShellProps>(function AppShell(\n {\n sidebar,\n topbar,\n children,\n sidebarOpen,\n defaultSidebarOpen = false,\n onSidebarOpenChange,\n className,\n ...props\n },\n ref,\n) {\n const styles = shell();\n const [ownOpen, setOwnOpen] = useState(defaultSidebarOpen);\n const open = sidebarOpen ?? ownOpen;\n const wide = useSyncExternalStore(subscribeToWidth, isWide, isWideOnServer);\n const hasSidebar = sidebar != null && sidebar !== false;\n // One derived value, read everywhere: the drawer, the toggle's\n // `aria-expanded` and the context all ask the same question of the same\n // inputs, so they cannot disagree about whether the drawer is showing.\n const drawerOpen = hasSidebar && open && !wide;\n\n // The latest callback, read at call time, so `setSidebarOpen` keeps one\n // identity for as long as the shell is controlled the same way — a caller\n // passing an inline arrow would otherwise re-run the effect below on every\n // render and report the same close over and over.\n const onChange = useRef(onSidebarOpenChange);\n useEffect(() => {\n onChange.current = onSidebarOpenChange;\n });\n const controlled = sidebarOpen !== undefined;\n\n const setSidebarOpen = useCallback(\n (next: boolean) => {\n if (!controlled) setOwnOpen(next);\n onChange.current?.(next);\n },\n [controlled],\n );\n\n // Widening past the breakpoint with the drawer open closes it, rather than\n // leaving it to reappear the next time the window narrows.\n useEffect(() => {\n if (wide && open) setSidebarOpen(false);\n }, [wide, open, setSidebarOpen]);\n\n const context = useMemo(\n () => ({ hasSidebar, drawerOpen, setSidebarOpen }),\n [hasSidebar, drawerOpen, setSidebarOpen],\n );\n\n return (\n <AppShellContext.Provider value={context}>\n <div ref={ref} data-slot=\"app-shell\" className={styles.root({ class: className })} {...props}>\n {sidebar}\n <div data-slot=\"app-shell-column\" className={styles.column()}>\n {topbar}\n {children}\n </div>\n </div>\n </AppShellContext.Provider>\n );\n});\n\nconst sidebarStyles = recipe({\n slots: {\n aside:\n 'hidden w-72 shrink-0 flex-col border-r-4 border-edge-strong bg-surface-base '\n + 'lg:sticky lg:top-0 lg:flex lg:h-screen',\n header: 'border-b-2 border-edge-strong px-6 py-5',\n body: 'flex-1 overflow-y-auto px-4 py-5',\n footer: 'border-t-2 border-edge-strong px-4 py-4',\n drawerHeader: 'mb-5 border-b-2 border-edge-strong pb-4',\n },\n});\n\nexport interface AppSidebarProps extends Omit<HTMLAttributes<HTMLElement>, 'className' | 'children'> {\n /**\n * The sidebar's accessible name — the `<aside>` landmark's label at\n * desktop width, and the drawer's title below it. Required: an unlabelled\n * complementary region and a dialog announced as \"dialog\" both leave a\n * screen reader user guessing.\n */\n label: string;\n /** Above the navigation: the application's name or mark. */\n header?: ReactNode;\n /** The navigation — normally one or more `AppSidebarNav`. */\n children: ReactNode;\n /**\n * Below the navigation: the account area, as a slot. A `Menu` behind an\n * `Avatar`, a sign-in link, or nothing. In the drawer it takes the place of\n * the default close button; the drawer's own close control stays.\n */\n footer?: ReactNode;\n /** Merged into the `<aside>`'s classes. */\n className?: string;\n}\n\n/**\n * The navigation column: a labelled `<aside>` at desktop width, a left\n * `Drawer` below it.\n *\n * The content is written once and rendered in whichever of the two the\n * viewport calls for. The persistent column is hidden by CSS below `lg`, which\n * takes it out of the accessibility tree too; the drawer only exists while it\n * is open, and it never opens at desktop width. Outside an `AppShell` there is\n * no open state to read, so only the persistent column renders.\n */\nexport const AppSidebar = forwardRef<HTMLElement, AppSidebarProps>(function AppSidebar(\n { label, header, children, footer, className, ...props },\n ref,\n) {\n const styles = sidebarStyles();\n const context = useContext(AppShellContext);\n\n return (\n <>\n <aside\n ref={ref}\n aria-label={label}\n data-slot=\"app-sidebar\"\n className={styles.aside({ class: className })}\n {...props}\n >\n {header != null && <div data-slot=\"app-sidebar-header\" className={styles.header()}>{header}</div>}\n <div data-slot=\"app-sidebar-body\" className={styles.body()}>{children}</div>\n {footer != null && <div data-slot=\"app-sidebar-footer\" className={styles.footer()}>{footer}</div>}\n </aside>\n {context && (\n <Drawer\n isOpen={context.drawerOpen}\n onClose={() => context.setSidebarOpen(false)}\n placement=\"left\"\n title={label}\n footer={footer ?? undefined}\n >\n {header != null && <div className={styles.drawerHeader()}>{header}</div>}\n {children}\n </Drawer>\n )}\n </>\n );\n});\n\n/**\n * One entry in the navigation tree. The tree is the consumer's: any labels,\n * any depth, links or actions.\n */\nexport interface AppNavItem {\n /** Unique within the tree. Matched against `activeId`, and passed to `onNavigate`. */\n id: string;\n /** What the entry reads as. */\n label: ReactNode;\n /**\n * Where it goes. Present, the entry is an `<a>`; absent, it is a button that\n * reports through `onNavigate` — or, with `items` and no `onNavigate`, a\n * plain group heading.\n */\n href?: string;\n /**\n * A component, not a rendered element — `LayoutDashboard`, never\n * `<LayoutDashboard />` — matching `StatCard` and `PageHeader`.\n */\n icon?: ElementType<{ className?: string }>;\n /** Trailing content: a count, a `Badge`. */\n badge?: ReactNode;\n /** Children, rendered as a nested list under this entry. */\n items?: readonly AppNavItem[];\n}\n\nconst navStyles = recipe({\n slots: {\n nav: 'font-mono text-sm',\n heading: 'mb-2 px-3 font-mono text-xs font-bold uppercase tracking-widest text-content-muted',\n list: 'flex flex-col gap-1.5',\n nested: 'mt-1.5 ml-3 flex flex-col gap-1.5 border-l-2 border-edge-strong pl-3',\n entry:\n 'flex w-full items-center gap-2.5 border-2 px-3 py-2 text-left font-bold uppercase no-underline',\n iconFrame: 'inline-flex shrink-0',\n icon: 'h-4 w-4',\n label: 'min-w-0 flex-1 truncate',\n group: 'flex items-center gap-2.5 px-3 py-2 font-bold uppercase text-content-muted',\n },\n variants: {\n active: {\n true: { entry: 'border-edge-strong bg-accent-primary text-content-inverse shadow-hard-sm' },\n false: { entry: 'border-transparent text-content-primary hover:border-edge-strong hover:bg-surface-raised' },\n },\n },\n});\n\nexport interface AppSidebarNavProps\n extends Omit<HTMLAttributes<HTMLElement>, 'className' | 'children'> {\n /**\n * The `<nav>` landmark's accessible name — `Primary`, `Projects`. Required,\n * because a sidebar can hold more than one, and two unnamed navigation\n * landmarks are indistinguishable.\n */\n label: string;\n /** The tree. */\n items: readonly AppNavItem[];\n /** The entry for the current page, marked `aria-current=\"page\"` and filled. */\n activeId?: string;\n /**\n * Called with an entry's `id` when it is chosen, link or button. Inside an\n * `AppShell` a choice also closes the off-canvas sidebar, since the reader\n * has gone where they opened it to go.\n */\n onNavigate?: (id: string) => void;\n /** Shown above the list as a small caps heading. The landmark's name is `label` either way. */\n heading?: ReactNode;\n /** Merged into the `<nav>`'s classes. */\n className?: string;\n}\n\n/**\n * A labelled `<nav>` over a navigation tree the consumer supplies.\n *\n * Entries with an `href` are links; entries without one are buttons that\n * report through `onNavigate`, which is where a client-side router takes\n * over. An entry with `items` nests them, to any depth. The current entry\n * carries `aria-current=\"page\"` — on the element a screen reader lands on, not\n * a wrapper — and the filled, shadowed treatment.\n */\nexport const AppSidebarNav = forwardRef<HTMLElement, AppSidebarNavProps>(function AppSidebarNav(\n { label, items, activeId, onNavigate, heading, className, ...props },\n ref,\n) {\n const styles = navStyles();\n const context = useContext(AppShellContext);\n\n const choose = (id: string) => {\n onNavigate?.(id);\n context?.setSidebarOpen(false);\n };\n\n const renderEntry = (item: AppNavItem) => {\n const active = item.id === activeId;\n const entry = navStyles({ active });\n const Icon = item.icon;\n const inner = (\n <>\n {Icon && (\n <span className={styles.iconFrame()} aria-hidden=\"true\">\n <Icon className={styles.icon()} />\n </span>\n )}\n <span className={styles.label()}>{item.label}</span>\n {item.badge}\n </>\n );\n\n if (item.href !== undefined) {\n return (\n <a\n href={item.href}\n aria-current={active ? 'page' : undefined}\n className={entry.entry()}\n onClick={() => choose(item.id)}\n >\n {inner}\n </a>\n );\n }\n if (item.items && !onNavigate) {\n return <span className={styles.group()}>{inner}</span>;\n }\n return (\n <button\n type=\"button\"\n aria-current={active ? 'page' : undefined}\n className={entry.entry()}\n onClick={() => choose(item.id)}\n >\n {inner}\n </button>\n );\n };\n\n const renderList = (list: readonly AppNavItem[], depth: number) => (\n <ul className={depth === 0 ? styles.list() : styles.nested()}>\n {list.map((item) => (\n <li key={item.id}>\n {renderEntry(item)}\n {item.items && item.items.length > 0 && renderList(item.items, depth + 1)}\n </li>\n ))}\n </ul>\n );\n\n return (\n <nav ref={ref} aria-label={label} data-slot=\"app-sidebar-nav\" className={styles.nav({ class: className })} {...props}>\n {heading != null && <p className={styles.heading()}>{heading}</p>}\n {renderList(items, 0)}\n </nav>\n );\n});\n\nconst topbarStyles = recipe({\n slots: {\n header:\n 'flex flex-wrap items-center gap-4 border-b-4 border-edge-strong bg-surface-base px-4 py-3 md:px-8',\n toggle: 'shrink-0 px-3 lg:hidden',\n lead: 'flex min-w-0 flex-auto flex-wrap items-center gap-3',\n actions: 'ml-auto flex shrink-0 flex-wrap items-center gap-3',\n },\n});\n\nexport interface AppTopbarProps extends Omit<HTMLAttributes<HTMLElement>, 'className'> {\n /**\n * The `<header>` landmark's accessible name. `Application` by default; name\n * it for the application when a page carries more than one header.\n */\n label?: string;\n /** The leading slot: the page's title, a breadcrumb, status badges. */\n children?: ReactNode;\n /** The trailing slot: actions, a search field, a status area. */\n actions?: ReactNode;\n /**\n * The sidebar toggle's accessible name. It is an icon, so this is all a\n * screen reader hears of it; name it for what it opens.\n */\n sidebarToggleLabel?: string;\n /** Merged into the `<header>`'s classes. */\n className?: string;\n}\n\n/**\n * The bar above the page — a labelled `<header>`.\n *\n * Inside an `AppShell` with a sidebar it leads with the sidebar toggle, shown\n * below desktop width only. The toggle is a real button in the tab order,\n * named by `sidebarToggleLabel`, reporting `aria-expanded`; opening it moves\n * focus into the drawer, and closing the drawer returns focus here.\n *\n * When the leading slot and the actions do not fit on one line, the actions\n * wrap onto a second, right-aligned, rather than overlapping the lead.\n */\nexport const AppTopbar = forwardRef<HTMLElement, AppTopbarProps>(function AppTopbar(\n {\n label = 'Application',\n children,\n actions,\n sidebarToggleLabel = 'Open navigation',\n className,\n ...props\n },\n ref,\n) {\n const styles = topbarStyles();\n const context = useContext(AppShellContext);\n\n return (\n <header\n ref={ref}\n aria-label={label}\n data-slot=\"app-topbar\"\n className={styles.header({ class: className })}\n {...props}\n >\n {context?.hasSidebar && (\n <Button\n type=\"button\"\n size=\"sm\"\n aria-label={sidebarToggleLabel}\n aria-haspopup=\"dialog\"\n aria-expanded={context.drawerOpen}\n data-slot=\"app-sidebar-toggle\"\n className={styles.toggle()}\n onClick={() => context.setSidebarOpen(true)}\n >\n <MenuIcon className=\"h-5 w-5\" aria-hidden=\"true\" />\n </Button>\n )}\n <div data-slot=\"app-topbar-lead\" className={styles.lead()}>{children}</div>\n {actions != null && <div data-slot=\"app-topbar-actions\" className={styles.actions()}>{actions}</div>}\n </header>\n );\n});\n\nconst mainStyles = recipe({\n base: 'flex min-w-0 flex-1 flex-col gap-6 p-4 md:p-8',\n});\n\nexport interface AppMainProps extends Omit<HTMLAttributes<HTMLElement>, 'className'> {\n /**\n * The `<main>` landmark's accessible name. `Content` by default; the page's\n * title is the better name when it has one.\n */\n label?: string;\n /** The page. Stacked with the system's gap, so sections need no spacing of their own. */\n children: ReactNode;\n /** Merged into the `<main>`'s classes. */\n className?: string;\n}\n\n/**\n * The page's one `<main>` landmark, labelled, padded and stacking its\n * children with a consistent gap — so a page of `PageHeader`, `Card` and\n * `DataTable` needs no layout CSS of its own.\n */\nexport const AppMain = forwardRef<HTMLElement, AppMainProps>(function AppMain(\n { label = 'Content', children, className, ...props },\n ref,\n) {\n return (\n <main ref={ref} aria-label={label} data-slot=\"app-main\" className={mainStyles({ class: className })} {...props}>\n {children}\n </main>\n );\n});\n"],"mappings":";;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,QAAQ,gBAAgB;AACjC,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,cAAc;AAwKnB,SAgEA,UAhEA,KAGI,YAHJ;AAjKJ,IAAM,aAAa;AAEnB,SAAS,gBAAyB;AAChC,SAAO,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe;AACvE;AAEA,SAAS,iBAAiB,UAAkC;AAC1D,MAAI,CAAC,cAAc,EAAG,QAAO,MAAM;AAAA,EAAC;AACpC,QAAM,QAAQ,OAAO,WAAW,UAAU;AAC1C,QAAM,iBAAiB,UAAU,QAAQ;AACzC,SAAO,MAAM,MAAM,oBAAoB,UAAU,QAAQ;AAC3D;AAEA,SAAS,SAAkB;AACzB,SAAO,cAAc,KAAK,OAAO,WAAW,UAAU,EAAE;AAC1D;AAIA,SAAS,iBAA0B;AACjC,SAAO;AACT;AAUA,IAAM,kBAAkB,cAA2C,IAAI;AAEvE,IAAM,QAAQ,OAAO;AAAA,EACnB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,EACV;AACF,CAAC;AAsEM,IAAM,WAAW,WAA0C,SAASA,UACzE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,SAAS,MAAM;AACrB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,kBAAkB;AACzD,QAAM,OAAO,eAAe;AAC5B,QAAM,OAAO,qBAAqB,kBAAkB,QAAQ,cAAc;AAC1E,QAAM,aAAa,WAAW,QAAQ,YAAY;AAIlD,QAAM,aAAa,cAAc,QAAQ,CAAC;AAM1C,QAAM,WAAW,OAAO,mBAAmB;AAC3C,YAAU,MAAM;AACd,aAAS,UAAU;AAAA,EACrB,CAAC;AACD,QAAM,aAAa,gBAAgB;AAEnC,QAAM,iBAAiB;AAAA,IACrB,CAAC,SAAkB;AACjB,UAAI,CAAC,WAAY,YAAW,IAAI;AAChC,eAAS,UAAU,IAAI;AAAA,IACzB;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAIA,YAAU,MAAM;AACd,QAAI,QAAQ,KAAM,gBAAe,KAAK;AAAA,EACxC,GAAG,CAAC,MAAM,MAAM,cAAc,CAAC;AAE/B,QAAM,UAAU;AAAA,IACd,OAAO,EAAE,YAAY,YAAY,eAAe;AAAA,IAChD,CAAC,YAAY,YAAY,cAAc;AAAA,EACzC;AAEA,SACE,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,SAC/B,+BAAC,SAAI,KAAU,aAAU,aAAY,WAAW,OAAO,KAAK,EAAE,OAAO,UAAU,CAAC,GAAI,GAAG,OACpF;AAAA;AAAA,IACD,qBAAC,SAAI,aAAU,oBAAmB,WAAW,OAAO,OAAO,GACxD;AAAA;AAAA,MACA;AAAA,OACH;AAAA,KACF,GACF;AAEJ,CAAC;AAED,IAAM,gBAAgB,OAAO;AAAA,EAC3B,OAAO;AAAA,IACL,OACE;AAAA,IAEF,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AACF,CAAC;AAkCM,IAAM,aAAa,WAAyC,SAASC,YAC1E,EAAE,OAAO,QAAQ,UAAU,QAAQ,WAAW,GAAG,MAAM,GACvD,KACA;AACA,QAAM,SAAS,cAAc;AAC7B,QAAM,UAAU,WAAW,eAAe;AAE1C,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,cAAY;AAAA,QACZ,aAAU;AAAA,QACV,WAAW,OAAO,MAAM,EAAE,OAAO,UAAU,CAAC;AAAA,QAC3C,GAAG;AAAA,QAEH;AAAA,oBAAU,QAAQ,oBAAC,SAAI,aAAU,sBAAqB,WAAW,OAAO,OAAO,GAAI,kBAAO;AAAA,UAC3F,oBAAC,SAAI,aAAU,oBAAmB,WAAW,OAAO,KAAK,GAAI,UAAS;AAAA,UACrE,UAAU,QAAQ,oBAAC,SAAI,aAAU,sBAAqB,WAAW,OAAO,OAAO,GAAI,kBAAO;AAAA;AAAA;AAAA,IAC7F;AAAA,IACC,WACC;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,QAAQ;AAAA,QAChB,SAAS,MAAM,QAAQ,eAAe,KAAK;AAAA,QAC3C,WAAU;AAAA,QACV,OAAO;AAAA,QACP,QAAQ,UAAU;AAAA,QAEjB;AAAA,oBAAU,QAAQ,oBAAC,SAAI,WAAW,OAAO,aAAa,GAAI,kBAAO;AAAA,UACjE;AAAA;AAAA;AAAA,IACH;AAAA,KAEJ;AAEJ,CAAC;AA4BD,IAAM,YAAY,OAAO;AAAA,EACvB,OAAO;AAAA,IACL,KAAK;AAAA,IACL,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,OACE;AAAA,IACF,WAAW;AAAA,IACX,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAAA,EACA,UAAU;AAAA,IACR,QAAQ;AAAA,MACN,MAAM,EAAE,OAAO,2EAA2E;AAAA,MAC1F,OAAO,EAAE,OAAO,2FAA2F;AAAA,IAC7G;AAAA,EACF;AACF,CAAC;AAmCM,IAAM,gBAAgB,WAA4C,SAASC,eAChF,EAAE,OAAO,OAAO,UAAU,YAAY,SAAS,WAAW,GAAG,MAAM,GACnE,KACA;AACA,QAAM,SAAS,UAAU;AACzB,QAAM,UAAU,WAAW,eAAe;AAE1C,QAAM,SAAS,CAAC,OAAe;AAC7B,iBAAa,EAAE;AACf,aAAS,eAAe,KAAK;AAAA,EAC/B;AAEA,QAAM,cAAc,CAAC,SAAqB;AACxC,UAAM,SAAS,KAAK,OAAO;AAC3B,UAAM,QAAQ,UAAU,EAAE,OAAO,CAAC;AAClC,UAAM,OAAO,KAAK;AAClB,UAAM,QACJ,iCACG;AAAA,cACC,oBAAC,UAAK,WAAW,OAAO,UAAU,GAAG,eAAY,QAC/C,8BAAC,QAAK,WAAW,OAAO,KAAK,GAAG,GAClC;AAAA,MAEF,oBAAC,UAAK,WAAW,OAAO,MAAM,GAAI,eAAK,OAAM;AAAA,MAC5C,KAAK;AAAA,OACR;AAGF,QAAI,KAAK,SAAS,QAAW;AAC3B,aACE;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,KAAK;AAAA,UACX,gBAAc,SAAS,SAAS;AAAA,UAChC,WAAW,MAAM,MAAM;AAAA,UACvB,SAAS,MAAM,OAAO,KAAK,EAAE;AAAA,UAE5B;AAAA;AAAA,MACH;AAAA,IAEJ;AACA,QAAI,KAAK,SAAS,CAAC,YAAY;AAC7B,aAAO,oBAAC,UAAK,WAAW,OAAO,MAAM,GAAI,iBAAM;AAAA,IACjD;AACA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,gBAAc,SAAS,SAAS;AAAA,QAChC,WAAW,MAAM,MAAM;AAAA,QACvB,SAAS,MAAM,OAAO,KAAK,EAAE;AAAA,QAE5B;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,QAAM,aAAa,CAAC,MAA6B,UAC/C,oBAAC,QAAG,WAAW,UAAU,IAAI,OAAO,KAAK,IAAI,OAAO,OAAO,GACxD,eAAK,IAAI,CAAC,SACT,qBAAC,QACE;AAAA,gBAAY,IAAI;AAAA,IAChB,KAAK,SAAS,KAAK,MAAM,SAAS,KAAK,WAAW,KAAK,OAAO,QAAQ,CAAC;AAAA,OAFjE,KAAK,EAGd,CACD,GACH;AAGF,SACE,qBAAC,SAAI,KAAU,cAAY,OAAO,aAAU,mBAAkB,WAAW,OAAO,IAAI,EAAE,OAAO,UAAU,CAAC,GAAI,GAAG,OAC5G;AAAA,eAAW,QAAQ,oBAAC,OAAE,WAAW,OAAO,QAAQ,GAAI,mBAAQ;AAAA,IAC5D,WAAW,OAAO,CAAC;AAAA,KACtB;AAEJ,CAAC;AAED,IAAM,eAAe,OAAO;AAAA,EAC1B,OAAO;AAAA,IACL,QACE;AAAA,IACF,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,EACX;AACF,CAAC;AAgCM,IAAM,YAAY,WAAwC,SAASC,WACxE;AAAA,EACE,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,SAAS,aAAa;AAC5B,QAAM,UAAU,WAAW,eAAe;AAE1C,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,cAAY;AAAA,MACZ,aAAU;AAAA,MACV,WAAW,OAAO,OAAO,EAAE,OAAO,UAAU,CAAC;AAAA,MAC5C,GAAG;AAAA,MAEH;AAAA,iBAAS,cACR;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,MAAK;AAAA,YACL,cAAY;AAAA,YACZ,iBAAc;AAAA,YACd,iBAAe,QAAQ;AAAA,YACvB,aAAU;AAAA,YACV,WAAW,OAAO,OAAO;AAAA,YACzB,SAAS,MAAM,QAAQ,eAAe,IAAI;AAAA,YAE1C,8BAAC,YAAS,WAAU,WAAU,eAAY,QAAO;AAAA;AAAA,QACnD;AAAA,QAEF,oBAAC,SAAI,aAAU,mBAAkB,WAAW,OAAO,KAAK,GAAI,UAAS;AAAA,QACpE,WAAW,QAAQ,oBAAC,SAAI,aAAU,sBAAqB,WAAW,OAAO,QAAQ,GAAI,mBAAQ;AAAA;AAAA;AAAA,EAChG;AAEJ,CAAC;AAED,IAAM,aAAa,OAAO;AAAA,EACxB,MAAM;AACR,CAAC;AAmBM,IAAM,UAAU,WAAsC,SAASC,SACpE,EAAE,QAAQ,WAAW,UAAU,WAAW,GAAG,MAAM,GACnD,KACA;AACA,SACE,oBAAC,UAAK,KAAU,cAAY,OAAO,aAAU,YAAW,WAAW,WAAW,EAAE,OAAO,UAAU,CAAC,GAAI,GAAG,OACtG,UACH;AAEJ,CAAC;","names":["AppShell","AppSidebar","AppSidebarNav","AppTopbar","AppMain"]}
@@ -0,0 +1,8 @@
1
+ // src/components/AsciiDivider.tsx
2
+ import { Divider } from "./Divider.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var AsciiDivider = (props) => /* @__PURE__ */ jsx(Divider, { ...props });
5
+ export {
6
+ AsciiDivider
7
+ };
8
+ //# sourceMappingURL=AsciiDivider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/AsciiDivider.tsx"],"sourcesContent":["import type React from 'react';\nimport { Divider } from './Divider';\nimport type { DividerProps } from './Divider';\n\nexport type AsciiDividerProps = DividerProps;\n\n/**\n * @deprecated Use {@link Divider}. This name described the mark (`//====//`),\n * which is only the mark the *dark* levels draw — the light levels draw a\n * pencil rule. `Divider` names the role instead, and picks the mark from the\n * level's polarity.\n *\n * Kept as a thin alias so existing call sites keep compiling. Note that the\n * bare `.ascii-divider` class is gone: it existed only as a hook for a\n * consumer's `::after`, and that behaviour is now a variant on `Divider`.\n * Anything rendering `<div className=\"ascii-divider\" />` directly must render\n * the component instead.\n */\nexport const AsciiDivider: React.FC<AsciiDividerProps> = (props) => <Divider {...props} />;\n"],"mappings":";AACA,SAAS,eAAe;AAiB4C;AAA7D,IAAM,eAA4C,CAAC,UAAU,oBAAC,WAAS,GAAG,OAAO;","names":[]}
@@ -0,0 +1,130 @@
1
+ // src/components/AsciiGauge.tsx
2
+ import { forwardRef } from "react";
3
+ import { accentTextClass } from "../lib/accentClasses.js";
4
+ import { cn } from "../lib/recipe.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ var GLYPH_SETS = {
7
+ block: { fill: "\u2588", empty: "\u2591", target: "|" },
8
+ shade: { fill: "\u2593", empty: "\u2591", target: "|", shades: ["\u2593", "\u2592", "\u2591"] },
9
+ line: { fill: "=", empty: "-", target: "|" },
10
+ ascii: { fill: "#", empty: "-", target: "|" }
11
+ };
12
+ var AsciiGauge = forwardRef(
13
+ function AsciiGauge2({
14
+ label = "Progress",
15
+ value,
16
+ min = 0,
17
+ max = 100,
18
+ target,
19
+ length = 10,
20
+ brackets = ["[", "]"],
21
+ variant = "block",
22
+ accent = "primary",
23
+ showValue = false,
24
+ valueFormat,
25
+ className,
26
+ ...props
27
+ }, ref) {
28
+ const span = Math.max(1e-4, max - min);
29
+ const clampedValue = Math.min(max, Math.max(min, value));
30
+ const ratio = Math.min(1, Math.max(0, (clampedValue - min) / span));
31
+ const filledCount = Math.round(ratio * length);
32
+ const glyphs = GLYPH_SETS[variant] ?? GLYPH_SETS.block;
33
+ const targetIndex = target !== void 0 && target >= min && target <= max ? Math.min(
34
+ length - 1,
35
+ Math.max(0, Math.round((target - min) / span * length))
36
+ ) : null;
37
+ const chars = [];
38
+ for (let i = 0; i < length; i++) {
39
+ const isTarget = targetIndex !== null && i === targetIndex;
40
+ if (isTarget && i >= filledCount) {
41
+ chars.push({ char: glyphs.target, isFilled: false, isTarget: true });
42
+ } else if (i < filledCount) {
43
+ if (variant === "shade" && glyphs.shades) {
44
+ const shadeIndex = Math.min(
45
+ glyphs.shades.length - 1,
46
+ Math.floor((1 - i / filledCount) * glyphs.shades.length)
47
+ );
48
+ chars.push({
49
+ char: glyphs.shades[shadeIndex],
50
+ isFilled: true,
51
+ isTarget
52
+ });
53
+ } else {
54
+ chars.push({ char: glyphs.fill, isFilled: true, isTarget });
55
+ }
56
+ } else {
57
+ chars.push({ char: glyphs.empty, isFilled: false, isTarget });
58
+ }
59
+ }
60
+ const [openBracket, closeBracket] = brackets ?? ["", ""];
61
+ const formattedValue = valueFormat ? valueFormat(clampedValue, ratio) : `${Math.round(ratio * 100)}%`;
62
+ const accentClass = accentTextClass(accent);
63
+ return /* @__PURE__ */ jsxs(
64
+ "span",
65
+ {
66
+ ref,
67
+ role: "meter",
68
+ "aria-label": label ?? (props["aria-label"] || "Progress"),
69
+ "aria-valuenow": clampedValue,
70
+ "aria-valuemin": min,
71
+ "aria-valuemax": max,
72
+ "data-slot": "ascii-gauge",
73
+ className: cn(
74
+ "inline-flex items-center font-mono text-xs select-none tracking-tight",
75
+ className
76
+ ),
77
+ ...props,
78
+ children: [
79
+ openBracket && /* @__PURE__ */ jsx(
80
+ "span",
81
+ {
82
+ "data-slot": "ascii-gauge-bracket",
83
+ "aria-hidden": "true",
84
+ className: "text-content-muted",
85
+ children: openBracket
86
+ }
87
+ ),
88
+ /* @__PURE__ */ jsx(
89
+ "span",
90
+ {
91
+ "data-slot": "ascii-gauge-bar",
92
+ "aria-hidden": "true",
93
+ className: "inline-flex",
94
+ children: chars.map((item, idx) => /* @__PURE__ */ jsx(
95
+ "span",
96
+ {
97
+ className: item.isTarget ? "text-intent-warning font-bold" : item.isFilled ? accentClass : "text-content-muted",
98
+ children: item.char
99
+ },
100
+ `gauge-char-${idx}`
101
+ ))
102
+ }
103
+ ),
104
+ closeBracket && /* @__PURE__ */ jsx(
105
+ "span",
106
+ {
107
+ "data-slot": "ascii-gauge-bracket",
108
+ "aria-hidden": "true",
109
+ className: "text-content-muted",
110
+ children: closeBracket
111
+ }
112
+ ),
113
+ showValue && /* @__PURE__ */ jsx(
114
+ "span",
115
+ {
116
+ "data-slot": "ascii-gauge-value",
117
+ className: "ml-1.5 font-mono text-xs font-bold text-content-primary",
118
+ children: formattedValue
119
+ }
120
+ )
121
+ ]
122
+ }
123
+ );
124
+ }
125
+ );
126
+ AsciiGauge.displayName = "AsciiGauge";
127
+ export {
128
+ AsciiGauge
129
+ };
130
+ //# sourceMappingURL=AsciiGauge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/AsciiGauge.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes } from 'react';\nimport { accentTextClass } from '../lib/accentClasses';\nimport { cn } from '../lib/recipe';\nimport type { AccentToken } from '../lib/theme';\n\nexport type AsciiGaugeVariant =\n | 'block'\n | 'shade'\n | 'line'\n | 'ascii';\n\nexport interface AsciiGaugeProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {\n /** Accessible label describing what the meter measures (default 'Progress'). */\n label?: string;\n /** Current value between min and max. */\n value: number;\n /** Minimum value range (default 0). */\n min?: number;\n /** Maximum value range (default 100). */\n max?: number;\n /** Optional target or benchmark threshold value. When provided, renders a target pin inside the bar. */\n target?: number;\n /** Total character length of inner bar (excluding brackets, default 10). */\n length?: number;\n /** Enclosing boundary characters (default ['[', ']']). Pass null for bare meter. */\n brackets?: [string, string] | null;\n /** Visual glyph set (default 'block'). */\n variant?: AsciiGaugeVariant;\n /** Accent role token for filled glyphs (default 'primary'). */\n accent?: AccentToken;\n /** Show numeric percentage readout after bar (default false). */\n showValue?: boolean;\n /** Custom formatter for numeric readout. */\n valueFormat?: (value: number, ratio: number) => string;\n}\n\nconst GLYPH_SETS: Record<\n AsciiGaugeVariant,\n { fill: string; empty: string; target: string; shades?: string[] }\n> = {\n block: { fill: '█', empty: '░', target: '|' },\n shade: { fill: '▓', empty: '░', target: '|', shades: ['▓', '▒', '░'] },\n line: { fill: '=', empty: '-', target: '|' },\n ascii: { fill: '#', empty: '-', target: '|' },\n};\n\n/**\n * Foundational zero-dependency monospace meter primitive.\n *\n * Emits pure UTF-8 character progress bars and benchmark comparisons for terminal HUDs,\n * CLI logs, and table cells. Co-exists alongside SVG BulletChart by addressing text-mode\n * environments and compact monospace contexts. Conforms to 0px border radius and\n * accessible role=\"meter\".\n *\n * @example\n * ```tsx\n * <AsciiGauge value={68} target={80} variant=\"block\" accent=\"primary\" showValue />\n * ```\n */\nexport const AsciiGauge = forwardRef<HTMLSpanElement, AsciiGaugeProps>(\n function AsciiGauge(\n {\n label = 'Progress',\n value,\n min = 0,\n max = 100,\n target,\n length = 10,\n brackets = ['[', ']'],\n variant = 'block',\n accent = 'primary',\n showValue = false,\n valueFormat,\n className,\n ...props\n },\n ref,\n ) {\n const span = Math.max(0.0001, max - min);\n const clampedValue = Math.min(max, Math.max(min, value));\n const ratio = Math.min(1, Math.max(0, (clampedValue - min) / span));\n const filledCount = Math.round(ratio * length);\n\n const glyphs = GLYPH_SETS[variant] ?? GLYPH_SETS.block;\n const targetIndex =\n target !== undefined && target >= min && target <= max\n ? Math.min(\n length - 1,\n Math.max(0, Math.round(((target - min) / span) * length)),\n )\n : null;\n\n // Build character array\n const chars: { char: string; isFilled: boolean; isTarget: boolean }[] = [];\n\n for (let i = 0; i < length; i++) {\n const isTarget = targetIndex !== null && i === targetIndex;\n if (isTarget && i >= filledCount) {\n chars.push({ char: glyphs.target, isFilled: false, isTarget: true });\n } else if (i < filledCount) {\n if (variant === 'shade' && glyphs.shades) {\n const shadeIndex = Math.min(\n glyphs.shades.length - 1,\n Math.floor((1 - i / filledCount) * glyphs.shades.length),\n );\n chars.push({\n char: glyphs.shades[shadeIndex],\n isFilled: true,\n isTarget,\n });\n } else {\n chars.push({ char: glyphs.fill, isFilled: true, isTarget });\n }\n } else {\n chars.push({ char: glyphs.empty, isFilled: false, isTarget });\n }\n }\n\n const [openBracket, closeBracket] = brackets ?? ['', ''];\n const formattedValue = valueFormat\n ? valueFormat(clampedValue, ratio)\n : `${Math.round(ratio * 100)}%`;\n\n const accentClass = accentTextClass(accent);\n\n return (\n <span\n ref={ref}\n role=\"meter\"\n aria-label={label ?? (props['aria-label'] || 'Progress')}\n aria-valuenow={clampedValue}\n aria-valuemin={min}\n aria-valuemax={max}\n data-slot=\"ascii-gauge\"\n className={cn(\n 'inline-flex items-center font-mono text-xs select-none tracking-tight',\n className,\n )}\n {...props}\n >\n {openBracket && (\n <span\n data-slot=\"ascii-gauge-bracket\"\n aria-hidden=\"true\"\n className=\"text-content-muted\"\n >\n {openBracket}\n </span>\n )}\n <span\n data-slot=\"ascii-gauge-bar\"\n aria-hidden=\"true\"\n className=\"inline-flex\"\n >\n {chars.map((item, idx) => (\n <span\n key={`gauge-char-${idx}`}\n className={\n item.isTarget\n ? 'text-intent-warning font-bold'\n : item.isFilled\n ? accentClass\n : 'text-content-muted'\n }\n >\n {item.char}\n </span>\n ))}\n </span>\n {closeBracket && (\n <span\n data-slot=\"ascii-gauge-bracket\"\n aria-hidden=\"true\"\n className=\"text-content-muted\"\n >\n {closeBracket}\n </span>\n )}\n {showValue && (\n <span\n data-slot=\"ascii-gauge-value\"\n className=\"ml-1.5 font-mono text-xs font-bold text-content-primary\"\n >\n {formattedValue}\n </span>\n )}\n </span>\n );\n },\n);\n\nAsciiGauge.displayName = 'AsciiGauge';\n"],"mappings":"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","names":["AsciiGauge"]}
@@ -0,0 +1,87 @@
1
+ "use client";
2
+
3
+ // src/components/Avatar.tsx
4
+ import { Avatar as BaseAvatar } from "@base-ui/react/avatar";
5
+ import { forwardRef } from "react";
6
+ import { accentVar } from "../lib/theme.js";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var Avatar = forwardRef(function Avatar2({
9
+ src,
10
+ alt = "Avatar",
11
+ fallback = "RK",
12
+ size = "md",
13
+ accent = "primary",
14
+ className = "",
15
+ style,
16
+ ...props
17
+ }, ref) {
18
+ const getSizePx = () => {
19
+ switch (size) {
20
+ case "sm":
21
+ return "32px";
22
+ case "lg":
23
+ return "56px";
24
+ default:
25
+ return "44px";
26
+ }
27
+ };
28
+ const accentColor = accentVar(accent);
29
+ const sizePx = getSizePx();
30
+ return /* @__PURE__ */ jsxs(
31
+ BaseAvatar.Root,
32
+ {
33
+ render: (rootProps) => /* @__PURE__ */ jsx(
34
+ "div",
35
+ {
36
+ ...rootProps,
37
+ ...props,
38
+ ref,
39
+ "data-slot": "avatar",
40
+ className,
41
+ style: {
42
+ width: sizePx,
43
+ height: sizePx,
44
+ border: "2px solid var(--ds-border-strong)",
45
+ boxShadow: `3px 3px 0px 0px ${accentColor}`,
46
+ backgroundColor: "var(--ds-surface-base)",
47
+ display: "flex",
48
+ alignItems: "center",
49
+ justifyContent: "center",
50
+ overflow: "hidden",
51
+ boxSizing: "border-box",
52
+ ...style
53
+ }
54
+ }
55
+ ),
56
+ children: [
57
+ src && /* @__PURE__ */ jsx(
58
+ BaseAvatar.Image,
59
+ {
60
+ src,
61
+ alt,
62
+ "data-slot": "avatar-image",
63
+ style: { width: "100%", height: "100%", objectFit: "cover" }
64
+ }
65
+ ),
66
+ /* @__PURE__ */ jsx(
67
+ BaseAvatar.Fallback,
68
+ {
69
+ "data-slot": "avatar-fallback",
70
+ style: {
71
+ fontFamily: "var(--ds-font-display)",
72
+ fontWeight: 800,
73
+ fontSize: size === "sm" ? "0.75rem" : size === "lg" ? "1.2rem" : "0.95rem",
74
+ color: accentColor,
75
+ textTransform: "uppercase"
76
+ },
77
+ children: fallback
78
+ }
79
+ )
80
+ ]
81
+ }
82
+ );
83
+ });
84
+ export {
85
+ Avatar
86
+ };
87
+ //# sourceMappingURL=Avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Avatar.tsx"],"sourcesContent":["'use client';\n\nimport { Avatar as BaseAvatar } from '@base-ui/react/avatar';\nimport { forwardRef } from 'react';\nimport { accentVar } from '../lib/theme';\nimport type { AccentToken } from '../lib/theme';\nimport type React from 'react';\n\nexport interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {\n src?: string;\n alt?: string;\n fallback?: string;\n size?: 'sm' | 'md' | 'lg';\n /**\n * Which accent draws the ring and the offset shadow.\n *\n * Roles only: `primary`, `secondary`, `tertiary`, `quiet`, and the four\n * intents. The hue names this used to accept are gone.\n *\n * The hue names also mislead now that `palette` exists. On `sketch`,\n * `accent=\"primary\"` renders `#1450d7` — blue — because it was never asking for\n * cyan, it was asking for the primary accent. Use the role.\n */\n accent?: AccentToken;\n}\n\n/**\n * An avatar, on Base UI's `avatar`.\n *\n * The part that matters is the failure path: this rendered a bare `<img src>`\n * with no `onError`, so a URL that 404s showed the browser's broken-image glyph\n * rather than the initials — the one state a fallback exists for, and the one\n * that was never reached. `Avatar.Image` only renders once the image has\n * actually loaded, and `Avatar.Fallback` covers every other case, including the\n * moment before it does.\n */\nexport const Avatar = forwardRef<HTMLDivElement, AvatarProps>(function Avatar({\n src,\n alt = 'Avatar',\n fallback = 'RK',\n size = 'md',\n accent = 'primary',\n className = '',\n style,\n ...props\n}, ref) {\n const getSizePx = () => {\n switch (size) {\n case 'sm': return '32px';\n case 'lg': return '56px';\n default: return '44px';\n }\n };\n\n // `accentVar` owns the resolution. Duplicating it here as a switch is how the\n // two drifted before: a role added to the token layer rendered as `primary`\n // in this component and nowhere else, silently.\n const accentColor = accentVar(accent);\n\n const sizePx = getSizePx();\n\n return (\n <BaseAvatar.Root\n /*\n * The *function* form of `render`, as `Input` uses for `Field.Control`.\n * Base UI's root is typed to a `span`; `AvatarProps extends\n * HTMLAttributes<HTMLDivElement>` is the published type. Spreading\n * div-typed props through the element form is a type error rather than a\n * cosmetic one, and changing the published type to match a library's\n * default element is the tail wagging the dog.\n */\n render={(rootProps) => (\n <div\n {...rootProps}\n {...props}\n ref={ref}\n data-slot=\"avatar\"\n className={className}\n style={{\n width: sizePx,\n height: sizePx,\n border: '2px solid var(--ds-border-strong)',\n boxShadow: `3px 3px 0px 0px ${accentColor}`,\n backgroundColor: 'var(--ds-surface-base)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n overflow: 'hidden',\n boxSizing: 'border-box',\n ...style,\n }}\n />\n )}\n >\n {src && (\n <BaseAvatar.Image\n src={src}\n alt={alt}\n data-slot=\"avatar-image\"\n style={{ width: '100%', height: '100%', objectFit: 'cover' }}\n />\n )}\n <BaseAvatar.Fallback\n data-slot=\"avatar-fallback\"\n style={{\n fontFamily: 'var(--ds-font-display)',\n fontWeight: 800,\n fontSize: size === 'sm' ? '0.75rem' : size === 'lg' ? '1.2rem' : '0.95rem',\n color: accentColor,\n textTransform: 'uppercase',\n }}\n >\n {fallback}\n </BaseAvatar.Fallback>\n </BaseAvatar.Root>\n );\n});\n"],"mappings":";;;AAEA,SAAS,UAAU,kBAAkB;AACrC,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AA0DtB,SAUI,KAVJ;AA1BG,IAAM,SAAS,WAAwC,SAASA,QAAO;AAAA,EAC5E;AAAA,EACA,MAAM;AAAA,EACN,WAAW;AAAA,EACX,OAAO;AAAA,EACP,SAAS;AAAA,EACT,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,GAAG,KAAK;AACN,QAAM,YAAY,MAAM;AACtB,YAAQ,MAAM;AAAA,MACZ,KAAK;AAAM,eAAO;AAAA,MAClB,KAAK;AAAM,eAAO;AAAA,MAClB;AAAS,eAAO;AAAA,IAClB;AAAA,EACF;AAKA,QAAM,cAAc,UAAU,MAAM;AAEpC,QAAM,SAAS,UAAU;AAEzB,SACE;AAAA,IAAC,WAAW;AAAA,IAAX;AAAA,MASC,QAAQ,CAAC,cACP;AAAA,QAAC;AAAA;AAAA,UACE,GAAG;AAAA,UACH,GAAG;AAAA,UACJ;AAAA,UACA,aAAU;AAAA,UACV;AAAA,UACA,OAAO;AAAA,YACL,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,QAAQ;AAAA,YACR,WAAW,mBAAmB,WAAW;AAAA,YACzC,iBAAiB;AAAA,YACjB,SAAS;AAAA,YACT,YAAY;AAAA,YACZ,gBAAgB;AAAA,YAChB,UAAU;AAAA,YACV,WAAW;AAAA,YACX,GAAG;AAAA,UACL;AAAA;AAAA,MACF;AAAA,MAGD;AAAA,eACC;AAAA,UAAC,WAAW;AAAA,UAAX;AAAA,YACC;AAAA,YACA;AAAA,YACA,aAAU;AAAA,YACV,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,WAAW,QAAQ;AAAA;AAAA,QAC7D;AAAA,QAEF;AAAA,UAAC,WAAW;AAAA,UAAX;AAAA,YACC,aAAU;AAAA,YACV,OAAO;AAAA,cACH,YAAY;AAAA,cACZ,YAAY;AAAA,cACZ,UAAU,SAAS,OAAO,YAAY,SAAS,OAAO,WAAW;AAAA,cACjE,OAAO;AAAA,cACT,eAAe;AAAA,YACjB;AAAA,YAEC;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EACF;AAEJ,CAAC;","names":["Avatar"]}
@@ -0,0 +1,25 @@
1
+ // src/components/Badge.tsx
2
+ import { forwardRef } from "react";
3
+ import { accentVar } from "../lib/theme.js";
4
+ import { cn } from "../lib/recipe.js";
5
+ import { jsx } from "react/jsx-runtime";
6
+ var Badge = forwardRef(function Badge2({ children, accent = "primary", className = "", style, ...props }, ref) {
7
+ return /* @__PURE__ */ jsx(
8
+ "span",
9
+ {
10
+ "data-slot": "badge",
11
+ ref,
12
+ className: cn(
13
+ "inline-flex items-center gap-[0.4rem] border-2 border-edge-strong bg-surface-base px-[0.6rem] py-[0.2rem] font-mono text-[0.75rem] font-bold",
14
+ className
15
+ ),
16
+ style: { color: accentVar(accent), ...style },
17
+ ...props,
18
+ children
19
+ }
20
+ );
21
+ });
22
+ export {
23
+ Badge
24
+ };
25
+ //# sourceMappingURL=Badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Badge.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport type React from 'react';\nimport { accentVar } from '../lib/theme';\nimport type { AccentToken } from '../lib/theme';\nimport { cn } from '../lib/recipe';\n\nexport interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {\n /**\n * The label: a state, a count, a category. Short by construction: the badge\n * does not wrap, so anything long enough to need two lines is a `Tag` or\n * prose, not a badge.\n */\n children: React.ReactNode;\n /**\n * Semantic role. Accepts an `Emphasis` (`primary`…`quiet`) or an `Intent`\n * (`info`/`success`/`warning`/`danger`); the legacy palette names still\n * resolve to the same values.\n */\n accent?: AccentToken;\n}\n\n/**\n * A short, inline status marker — `STABLE`, `3 FAILING`, `DRAFT`.\n *\n * It states what something *is*, and nothing about it is interactive: no\n * click, no focus, no keyboard affordance. A badge that needs to be pressed is\n * a `Button`, and one that needs to be removed is a `Tag`.\n *\n * The accent carries the meaning, so choose it by role rather than by colour —\n * `intent.danger` for a failure, `quiet` for a neutral count. On `midnight`\n * that is pink and grey; on another level it is whatever those roles resolve\n * to, and the badge stays correct because it never names a hue.\n */\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { children, accent = 'primary', className = '', style, ...props },\n ref,\n) {\n return (\n <span data-slot=\"badge\"\n ref={ref}\n className={cn(\n 'inline-flex items-center gap-[0.4rem] border-2 border-edge-strong bg-surface-base px-[0.6rem] py-[0.2rem] font-mono text-[0.75rem] font-bold',\n className\n )}\n // `text-[0.75rem]` rather than `text-xs`: the named size ships a paired\n // line-height, and the class this replaced set font-size alone so the\n // badge inherited the article's unitless 1.5. Pinning any explicit\n // leading changes the line box. The accent is a runtime value, so it\n // stays an inline custom style rather than a build-time utility.\n style={{ color: accentVar(accent), ...style }}\n {...props}\n >\n {children}\n </span>\n );\n});\n"],"mappings":";AAAA,SAAS,kBAAkB;AAE3B,SAAS,iBAAiB;AAE1B,SAAS,UAAU;AAkCf;AALG,IAAM,QAAQ,WAAwC,SAASA,OACpE,EAAE,UAAU,SAAS,WAAW,YAAY,IAAI,OAAO,GAAG,MAAM,GAChE,KACA;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MAAK,aAAU;AAAA,MACd;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAMA,OAAO,EAAE,OAAO,UAAU,MAAM,GAAG,GAAG,MAAM;AAAA,MAC3C,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ,CAAC;","names":["Badge"]}