@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,200 @@
1
+ "use client";
2
+
3
+ // src/components/Toast.tsx
4
+ import { createContext, forwardRef, useContext, useMemo } from "react";
5
+ import { Toast as BaseToast } from "@base-ui/react/toast";
6
+ import { AlertCircle, AlertTriangle, CheckCircle2, Info } from "lucide-react";
7
+ import { recipe } from "../lib/recipe.js";
8
+ import { usePortalThemeAttribute } from "./portalTheme.js";
9
+ import { Button } from "./Button.js";
10
+ import { DEFAULT_TIMEOUT, resolveLifetime } from "./toastLifetime.js";
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ var toast = recipe({
13
+ slots: {
14
+ /*
15
+ * Bottom-right from `sm` up, full-width with a 16px gutter below it.
16
+ * `z-top` from the `--ds-layer-*` scale — the layer the dialogs use, and
17
+ * the top of the scale — never a literal `z-` value.
18
+ *
19
+ * The stack is a plain column, newest first, and it grows *upward* from
20
+ * the bottom edge: a new toast appears above the ones already there, so
21
+ * nothing a reader is part-way through moves when another arrives. DOM
22
+ * order is visual order, so Tab walks them in the order they are seen.
23
+ */
24
+ viewport: "fixed right-4 bottom-4 left-4 z-top flex flex-col gap-3 sm:left-auto sm:w-full sm:max-w-sm",
25
+ /*
26
+ * Enter and exit are `data-starting-style` / `data-ending-style`, as the
27
+ * dialogs' are: a short rise in, a short slide out, over `--duration-quick`
28
+ * and `--ease-brutalist`. The `motion-reduce` clause drops both — the
29
+ * toast appears and disappears in place, and is announced exactly the same,
30
+ * because the announcement belongs to the live region and not to the
31
+ * motion.
32
+ *
33
+ * `data-[limited]:hidden`: past the provider's `limit` Base UI marks the
34
+ * oldest toasts limited and inert rather than removing them. Hidden is the
35
+ * other half of that — they are queued, not visible, and come back as the
36
+ * ones ahead of them close.
37
+ */
38
+ root: "flex items-start gap-3 border-2 bg-surface-base p-4 font-mono transition duration-quick ease-brutalist data-[starting-style]:translate-y-2 data-[starting-style]:opacity-0 data-[ending-style]:translate-x-4 data-[ending-style]:opacity-0 data-[limited]:hidden motion-reduce:transition-none",
39
+ icon: "mt-0.5 h-4 w-4 shrink-0",
40
+ content: "flex min-w-0 flex-1 flex-col gap-1",
41
+ title: "font-display text-sm font-extrabold uppercase tracking-wider",
42
+ description: "font-mono text-sm leading-relaxed text-content-primary",
43
+ actions: "mt-2 flex",
44
+ // The dialogs' close control, at the toast's scale.
45
+ close: "shrink-0 border-2 border-edge-strong bg-surface-raised px-2 font-mono text-base font-bold text-content-primary transition-colors hover:bg-surface-base hover:text-accent-tertiary focus-visible:ring-2 focus-visible:ring-accent-primary"
46
+ },
47
+ variants: {
48
+ intent: {
49
+ info: { root: "border-intent-info shadow-hard-intent-info", icon: "text-intent-info", title: "text-intent-info" },
50
+ success: { root: "border-intent-success shadow-hard-intent-success", icon: "text-intent-success", title: "text-intent-success" },
51
+ warning: { root: "border-intent-warning shadow-hard-intent-warning", icon: "text-intent-warning", title: "text-intent-warning" },
52
+ danger: { root: "border-intent-danger shadow-hard-intent-danger", icon: "text-intent-danger", title: "text-intent-danger" }
53
+ }
54
+ },
55
+ defaultVariants: { intent: "info" }
56
+ });
57
+ var ICON = {
58
+ info: Info,
59
+ success: CheckCircle2,
60
+ warning: AlertCircle,
61
+ danger: AlertTriangle
62
+ };
63
+ var ToastContext = createContext(null);
64
+ var counter = 0;
65
+ function nextId() {
66
+ counter += 1;
67
+ return `ds-toast-${counter}`;
68
+ }
69
+ function ToastItem({ item }) {
70
+ const intent = item.data?.intent ?? "info";
71
+ const action = item.data?.action;
72
+ const styles = toast({ intent });
73
+ const Icon = ICON[intent];
74
+ const textHidden = intent === "danger" ? true : void 0;
75
+ return /* @__PURE__ */ jsxs(
76
+ BaseToast.Root,
77
+ {
78
+ toast: item,
79
+ "aria-hidden": intent === "danger" ? false : void 0,
80
+ "data-slot": "toast",
81
+ "data-intent": intent,
82
+ className: styles.root(),
83
+ children: [
84
+ /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true", "data-slot": "toast-icon", className: styles.icon() }),
85
+ /* @__PURE__ */ jsxs("div", { "data-slot": "toast-content", className: styles.content(), children: [
86
+ /* @__PURE__ */ jsxs(
87
+ BaseToast.Title,
88
+ {
89
+ "data-slot": "toast-title",
90
+ "aria-hidden": textHidden,
91
+ className: styles.title(),
92
+ render: /* @__PURE__ */ jsx("p", {}),
93
+ children: [
94
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "[ " }),
95
+ item.title,
96
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " ]" })
97
+ ]
98
+ }
99
+ ),
100
+ item.description ? /* @__PURE__ */ jsx(
101
+ BaseToast.Description,
102
+ {
103
+ "data-slot": "toast-description",
104
+ "aria-hidden": textHidden,
105
+ className: styles.description()
106
+ }
107
+ ) : null,
108
+ action ? /* @__PURE__ */ jsx("div", { "data-slot": "toast-actions", className: styles.actions(), children: /* @__PURE__ */ jsx(
109
+ BaseToast.Action,
110
+ {
111
+ "data-slot": "toast-action",
112
+ render: /* @__PURE__ */ jsx(Button, { size: "sm", bracketed: true, children: action.label })
113
+ }
114
+ ) }) : null
115
+ ] }),
116
+ /* @__PURE__ */ jsx(
117
+ BaseToast.Close,
118
+ {
119
+ "data-slot": "toast-close",
120
+ "aria-hidden": false,
121
+ "aria-label": "Dismiss notification",
122
+ className: styles.close(),
123
+ children: "\xD7"
124
+ }
125
+ )
126
+ ]
127
+ }
128
+ );
129
+ }
130
+ function ToastList() {
131
+ const { toasts } = BaseToast.useToastManager();
132
+ return toasts.map((item) => /* @__PURE__ */ jsx(ToastItem, { item }, item.id));
133
+ }
134
+ var ToastProvider = forwardRef(function ToastProvider2({ children, timeout = DEFAULT_TIMEOUT, limit = 3, label = "Notifications", className, ...props }, ref) {
135
+ const styles = toast();
136
+ const portalTheme = usePortalThemeAttribute();
137
+ return /* @__PURE__ */ jsxs(BaseToast.Provider, { timeout, limit, children: [
138
+ /* @__PURE__ */ jsx(ToastBridge, { timeout, children }),
139
+ /* @__PURE__ */ jsx(BaseToast.Portal, { ...portalTheme, children: /* @__PURE__ */ jsx(
140
+ BaseToast.Viewport,
141
+ {
142
+ ref,
143
+ "aria-label": label,
144
+ "data-slot": "toast-viewport",
145
+ className: styles.viewport({ class: className }),
146
+ ...props,
147
+ children: /* @__PURE__ */ jsx(ToastList, {})
148
+ }
149
+ ) })
150
+ ] });
151
+ });
152
+ function ToastBridge({ timeout, children }) {
153
+ const { add, close } = BaseToast.useToastManager();
154
+ const api = useMemo(
155
+ () => ({
156
+ show(options) {
157
+ const id = options.id ?? nextId();
158
+ const intent = options.intent ?? "info";
159
+ const { action } = options;
160
+ add({
161
+ id,
162
+ title: options.title,
163
+ description: options.description,
164
+ type: intent,
165
+ // Base UI's two priorities are the two politeness levels.
166
+ priority: intent === "danger" ? "high" : "low",
167
+ timeout: resolveLifetime(options, timeout),
168
+ onClose: options.onClose,
169
+ data: { intent, action },
170
+ actionProps: action ? {
171
+ onClick: () => {
172
+ action.onClick();
173
+ close(id);
174
+ }
175
+ } : void 0
176
+ });
177
+ return id;
178
+ },
179
+ dismiss(id) {
180
+ close(id);
181
+ }
182
+ }),
183
+ [add, close, timeout]
184
+ );
185
+ return /* @__PURE__ */ jsx(ToastContext.Provider, { value: api, children });
186
+ }
187
+ function useToast() {
188
+ const api = useContext(ToastContext);
189
+ if (!api) throw new Error("useToast must be called inside a <ToastProvider>.");
190
+ return api;
191
+ }
192
+ function useOptionalToast() {
193
+ return useContext(ToastContext);
194
+ }
195
+ export {
196
+ ToastProvider,
197
+ useOptionalToast,
198
+ useToast
199
+ };
200
+ //# sourceMappingURL=Toast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Toast.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, forwardRef, useContext, useMemo } from 'react';\nimport type { HTMLAttributes, ReactNode } from 'react';\nimport { Toast as BaseToast } from '@base-ui/react/toast';\nimport { AlertCircle, AlertTriangle, CheckCircle2, Info } from 'lucide-react';\nimport { recipe } from '../lib/recipe';\nimport { usePortalThemeAttribute } from './portalTheme';\nimport { Button } from './Button';\nimport { DEFAULT_TIMEOUT, resolveLifetime } from './toastLifetime';\n\n/**\n * What a toast is about — the four intents, and nothing else.\n *\n * The same four `semanticTokens.intent` names every other component uses, so a\n * toast is never coloured by hue: `danger` is whatever `--ds-intent-danger`\n * resolves to on the current Level. There is no emphasis accent here on\n * purpose — a notification that is \"the pink one\" has told nobody anything.\n *\n * A plain string union rather than anything Base UI names, so the primitive\n * stays out of the published `.d.ts`.\n */\nexport type ToastIntent = 'info' | 'success' | 'warning' | 'danger';\n\n/** The one follow-up a toast may offer — `UNDO`, `RETRY`, `VIEW`. */\nexport interface ToastActionOptions {\n /** The button's label, written in caps at the call site as `Button`'s is. */\n label: string;\n /**\n * What the action does. The toast closes itself after this runs: an action\n * that has been taken leaves nothing to decide, and a toast still offering\n * `UNDO` after the undo is a second chance to press it.\n */\n onClick: () => void;\n}\n\n/** One notification, as a caller describes it to {@link ToastApi.show}. */\nexport interface ToastOptions {\n /**\n * The headline, and the toast's accessible name — what a screen reader says\n * first. Required as text, because a toast with no title announces a\n * description with nothing to say what it is about.\n */\n title: string;\n /** The detail under the title. Optional; a short toast is a better toast. */\n description?: ReactNode;\n /**\n * Colour, icon and — for `danger` — how urgently it is announced. Defaults\n * to `info`. `info`, `success` and `warning` are polite; `danger` is\n * assertive and interrupts, which is why it is reserved for failures.\n */\n intent?: ToastIntent;\n /**\n * An optional follow-up. A toast with an action does not expire on its own\n * unless `timeout` says so: a control that can vanish before a keyboard or\n * screen-reader user reaches it is a control only a mouse user has.\n */\n action?: ToastActionOptions;\n /**\n * Milliseconds before the toast dismisses itself; `0` keeps it until it is\n * dismissed. Left unset, it is the provider's `timeout` — lengthened for\n * long text, so a toast is on screen for at least as long as it takes to\n * read — or `0` when there is an action.\n */\n timeout?: number;\n /**\n * A stable id. Showing a toast with an id that is already on screen updates\n * it in place and restarts its lifetime instead of stacking a duplicate —\n * the right behaviour for \"Saving…\" becoming \"Saved\".\n */\n id?: string;\n /** Called once when the toast closes, whatever closed it. */\n onClose?: () => void;\n}\n\n/** What {@link useToast} returns. */\nexport interface ToastApi {\n /** Show a toast. Returns its id, for `dismiss`. */\n show: (options: ToastOptions) => string;\n /** Dismiss one toast by id, or every toast when called with no id. */\n dismiss: (id?: string) => void;\n}\n\nexport interface ToastProviderProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'className'> {\n /** The application. Anything below may call {@link useToast}. */\n children?: ReactNode;\n /**\n * The default lifetime in milliseconds. Six seconds, rather than Base UI's\n * five, and a floor rather than a fixed value: longer text gets longer. `0`\n * makes every toast persist until it is dismissed.\n */\n timeout?: number;\n /**\n * How many toasts are visible at once. Past it the oldest are queued rather\n * than dropped — hidden and inert until one ahead of them closes. Three by\n * default: a fourth simultaneous notification is usually a sign that they\n * should have been one.\n */\n limit?: number;\n /**\n * The region's accessible name — what a screen reader calls the landmark,\n * and what a user pressing F6 lands on. Defaults to `Notifications`.\n */\n label?: string;\n /** Merged into the region's classes, so a caller can move the stack. */\n className?: string;\n}\n\n/**\n * The toast's own data, carried on Base UI's `data` field. Only the parts the\n * item needs to render that Base UI does not already model.\n */\ninterface ToastData {\n intent: ToastIntent;\n action?: ToastActionOptions;\n}\n\n/**\n * The surface. A recipe in this file rather than a shared module because\n * nothing else wears it — and nothing exports it, so `tailwind-variants`' types\n * stay out of the `.d.ts`.\n *\n * The intent classes are the same `--ds-intent-*` custom properties\n * `semanticTokens.intent` names and `NoteBlock` paints inline: a toast and the\n * note block beside it cannot disagree about what `danger` looks like.\n */\nconst toast = recipe({\n slots: {\n /*\n * Bottom-right from `sm` up, full-width with a 16px gutter below it.\n * `z-top` from the `--ds-layer-*` scale — the layer the dialogs use, and\n * the top of the scale — never a literal `z-` value.\n *\n * The stack is a plain column, newest first, and it grows *upward* from\n * the bottom edge: a new toast appears above the ones already there, so\n * nothing a reader is part-way through moves when another arrives. DOM\n * order is visual order, so Tab walks them in the order they are seen.\n */\n viewport:\n 'fixed right-4 bottom-4 left-4 z-top flex flex-col gap-3 sm:left-auto sm:w-full sm:max-w-sm',\n /*\n * Enter and exit are `data-starting-style` / `data-ending-style`, as the\n * dialogs' are: a short rise in, a short slide out, over `--duration-quick`\n * and `--ease-brutalist`. The `motion-reduce` clause drops both — the\n * toast appears and disappears in place, and is announced exactly the same,\n * because the announcement belongs to the live region and not to the\n * motion.\n *\n * `data-[limited]:hidden`: past the provider's `limit` Base UI marks the\n * oldest toasts limited and inert rather than removing them. Hidden is the\n * other half of that — they are queued, not visible, and come back as the\n * ones ahead of them close.\n */\n root:\n 'flex items-start gap-3 border-2 bg-surface-base p-4 font-mono '\n + 'transition duration-quick ease-brutalist '\n + 'data-[starting-style]:translate-y-2 data-[starting-style]:opacity-0 '\n + 'data-[ending-style]:translate-x-4 data-[ending-style]:opacity-0 '\n + 'data-[limited]:hidden motion-reduce:transition-none',\n icon: 'mt-0.5 h-4 w-4 shrink-0',\n content: 'flex min-w-0 flex-1 flex-col gap-1',\n title: 'font-display text-sm font-extrabold uppercase tracking-wider',\n description: 'font-mono text-sm leading-relaxed text-content-primary',\n actions: 'mt-2 flex',\n // The dialogs' close control, at the toast's scale.\n close:\n 'shrink-0 border-2 border-edge-strong bg-surface-raised px-2 font-mono text-base font-bold '\n + 'text-content-primary transition-colors hover:bg-surface-base hover:text-accent-tertiary '\n + 'focus-visible:ring-2 focus-visible:ring-accent-primary',\n },\n variants: {\n intent: {\n info: { root: 'border-intent-info shadow-hard-intent-info', icon: 'text-intent-info', title: 'text-intent-info' },\n success: { root: 'border-intent-success shadow-hard-intent-success', icon: 'text-intent-success', title: 'text-intent-success' },\n warning: { root: 'border-intent-warning shadow-hard-intent-warning', icon: 'text-intent-warning', title: 'text-intent-warning' },\n danger: { root: 'border-intent-danger shadow-hard-intent-danger', icon: 'text-intent-danger', title: 'text-intent-danger' },\n },\n },\n defaultVariants: { intent: 'info' },\n});\n\n/**\n * The icon per intent, paired with colour exactly as `NoteBlock` pairs them —\n * so the same colour never wears two different icons across the two\n * components. The icon is what carries the intent for a reader who cannot\n * tell the colours apart.\n */\nconst ICON = {\n info: Info,\n success: CheckCircle2,\n warning: AlertCircle,\n danger: AlertTriangle,\n} as const;\n\nconst ToastContext = createContext<ToastApi | null>(null);\n\nlet counter = 0;\nfunction nextId(): string {\n counter += 1;\n return `ds-toast-${counter}`;\n}\n\n/** One toast. Unexported: toasts are created with `useToast().show`, not rendered. */\nfunction ToastItem({ item }: { item: BaseToast.Root.ToastObject<ToastData> }) {\n const intent = item.data?.intent ?? 'info';\n const action = item.data?.action;\n const styles = toast({ intent });\n const Icon = ICON[intent];\n // A `danger` toast is announced by the `role=\"alert\"` copy Base UI renders\n // beside the region, so its visible text must not *also* be read by the\n // polite region it sits in. Base UI solves that by hiding the whole root\n // until it is focused — which hides the action and the dismiss control\n // with it, and no `aria-hidden={false}` on a descendant can undo an\n // ancestor's. The result is tabbable buttons the tree says are not there.\n //\n // So the root stays exposed, and only the danger toast's text is hidden.\n // The buttons are reachable; the words are announced once, assertively;\n // and focusing the toast still reads its title and description, because\n // `aria-labelledby` and `aria-describedby` name from hidden content.\n const textHidden = intent === 'danger' ? true : undefined;\n\n return (\n <BaseToast.Root\n toast={item}\n aria-hidden={intent === 'danger' ? false : undefined}\n data-slot=\"toast\"\n // What the toast *is*, on the element, for a test or a consumer's\n // selector — the classes are a rendering detail.\n data-intent={intent}\n className={styles.root()}\n >\n <Icon aria-hidden=\"true\" data-slot=\"toast-icon\" className={styles.icon()} />\n <div data-slot=\"toast-content\" className={styles.content()}>\n {/*\n * `<p>`, not Base UI's default `<h2>`: a notification is not a section\n * of the page, and a heading here would drop into the document outline\n * wherever the toast happened to be read. The brackets are the\n * system's typographic cue and nothing a screen reader should spell.\n */}\n <BaseToast.Title\n data-slot=\"toast-title\"\n aria-hidden={textHidden}\n className={styles.title()}\n render={<p />}\n >\n <span aria-hidden=\"true\">[ </span>\n {item.title}\n <span aria-hidden=\"true\"> ]</span>\n </BaseToast.Title>\n {item.description ? (\n <BaseToast.Description\n data-slot=\"toast-description\"\n aria-hidden={textHidden}\n className={styles.description()}\n />\n ) : null}\n {/*\n * The action sits under the text rather than beside it: beside it, a\n * `Button` takes a third of a 384px toast and wraps the description\n * into a column of three words a line. Below it, it is also where Tab\n * lands next after the toast itself, before the dismiss control.\n */}\n {action ? (\n <div data-slot=\"toast-actions\" className={styles.actions()}>\n <BaseToast.Action\n data-slot=\"toast-action\"\n render={\n <Button size=\"sm\" bracketed>\n {action.label}\n </Button>\n }\n />\n </div>\n ) : null}\n </div>\n {/*\n * `aria-hidden={false}` overrides Base UI, which hides the close\n * control from assistive technology until the stack is hovered or\n * focused, to keep \"Dismiss\" out of the announcement. That leaves a\n * tabbable button hidden from the tree — axe's `aria-hidden-focus` —\n * and unreachable to a screen reader's virtual cursor, which is how\n * most screen-reader users would look for it. Four extra words in a\n * polite announcement is the cheaper of the two costs.\n */}\n <BaseToast.Close\n data-slot=\"toast-close\"\n aria-hidden={false}\n aria-label=\"Dismiss notification\"\n className={styles.close()}\n >\n &times;\n </BaseToast.Close>\n </BaseToast.Root>\n );\n}\n\nfunction ToastList() {\n const { toasts } = BaseToast.useToastManager<ToastData>();\n return toasts.map((item) => <ToastItem key={item.id} item={item} />);\n}\n\n/**\n * Transient notifications — a queue, a live region and a lifetime.\n *\n * Mount one near the root of the application. Anything below it calls\n * {@link useToast} and `show()`s a toast; nothing renders a toast directly,\n * because a toast is not a place in the layout. It is not a `Card` with\n * `position: absolute` either, and the difference is the whole component:\n *\n * ## The live region exists before anything is in it\n *\n * The provider renders its region on mount, empty. A live region inserted at\n * the same moment as its content announces nothing, so a toast system that\n * creates its container when the first toast fires has a first toast nobody\n * hears. `info`, `success` and `warning` are announced politely from that\n * region. `danger` is assertive: it is copied into a `role=\"alert\"` — the one\n * role defined to announce on insertion — so it interrupts, and it is marked\n * hidden in the polite region so it is not read twice.\n *\n * ## The lifetime yields to the reader\n *\n * Every timer pauses while the pointer is over the stack or focus is inside\n * it, and resumes when both leave — so a toast cannot expire while it is\n * being read, or out from under the pointer on its way to the action. The\n * default lifetime is a floor that long text raises, and a toast with an action\n * does not expire at all unless the caller says it should. F6 moves focus\n * into the stack from anywhere on the page, and Tab moves through the toasts\n * and their controls; Escape dismisses the focused one.\n *\n * ## The queue\n *\n * Several toasts at once stack newest first. Past `limit` the oldest are held\n * back — hidden and inert, not discarded — and return as the ones ahead of\n * them close.\n *\n * All of the above is `@base-ui/react/toast`, and this file is its only\n * wrapper: the surface, the intents and the lifetime policy are this\n * package's, the queue, timers and announcements are the primitive's.\n *\n * @example\n * ```tsx\n * <ToastProvider>\n * <App />\n * </ToastProvider>\n *\n * const toast = useToast();\n * toast.show({ intent: 'success', title: 'Saved', description: 'Draft saved 12:04.' });\n * ```\n */\nexport const ToastProvider = forwardRef<HTMLDivElement, ToastProviderProps>(function ToastProvider(\n { children, timeout = DEFAULT_TIMEOUT, limit = 3, label = 'Notifications', className, ...props },\n ref,\n) {\n const styles = toast();\n\n const portalTheme = usePortalThemeAttribute();\n return (\n <BaseToast.Provider timeout={timeout} limit={limit}>\n <ToastBridge timeout={timeout}>{children}</ToastBridge>\n <BaseToast.Portal {...portalTheme}>\n <BaseToast.Viewport\n ref={ref}\n aria-label={label}\n data-slot=\"toast-viewport\"\n className={styles.viewport({ class: className })}\n {...props}\n >\n <ToastList />\n </BaseToast.Viewport>\n </BaseToast.Portal>\n </BaseToast.Provider>\n );\n});\n\n/**\n * Publishes {@link ToastApi} to everything below the provider.\n *\n * The API talks to the provider's store directly, through Base UI's\n * `useToastManager`, rather than through a `createToastManager()` handed to\n * the provider. That manager is an event emitter the provider subscribes to in\n * an effect — and a child's effects run before its parent's, so a component\n * that shows a toast on mount (\"Signed in\", \"Draft restored\") would emit into\n * a manager nobody was listening to yet, and the toast would silently never\n * appear. The store exists from the provider's first render, so this path has\n * no such window.\n *\n * `add` and `close` are stable store methods, so the API object only changes\n * when the provider's `timeout` does.\n */\nfunction ToastBridge({ timeout, children }: { timeout: number; children?: ReactNode }) {\n const { add, close } = BaseToast.useToastManager<ToastData>();\n\n const api = useMemo<ToastApi>(\n () => ({\n show(options) {\n const id = options.id ?? nextId();\n const intent = options.intent ?? 'info';\n const { action } = options;\n add({\n id,\n title: options.title,\n description: options.description,\n type: intent,\n // Base UI's two priorities are the two politeness levels.\n priority: intent === 'danger' ? 'high' : 'low',\n timeout: resolveLifetime(options, timeout),\n onClose: options.onClose,\n data: { intent, action },\n actionProps: action\n ? {\n onClick: () => {\n action.onClick();\n close(id);\n },\n }\n : undefined,\n });\n return id;\n },\n dismiss(id) {\n close(id);\n },\n }),\n [add, close, timeout],\n );\n\n return <ToastContext.Provider value={api}>{children}</ToastContext.Provider>;\n}\n\n/**\n * Show and dismiss toasts. Throws outside a {@link ToastProvider}: a\n * notification with nowhere to be announced is a bug to find at the call site,\n * not a silent no-op to find in production.\n */\nexport function useToast(): ToastApi {\n const api = useContext(ToastContext);\n if (!api) throw new Error('useToast must be called inside a <ToastProvider>.');\n return api;\n}\n\n/**\n * The same API, or `null` when there is no provider — for a component that\n * notifies when it can and renders regardless, like the finance console's\n * sync button in `SaaS/AdminDashboard`. Matches `useOptionalTheme`.\n */\nexport function useOptionalToast(): ToastApi | null {\n return useContext(ToastContext);\n}\n"],"mappings":"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","names":["ToastProvider"]}
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ // src/components/Tooltip.tsx
4
+ import { forwardRef } from "react";
5
+ import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
6
+ import { recipe } from "../lib/recipe.js";
7
+ import { usePortalThemeAttribute } from "./portalTheme.js";
8
+ import { floatingSurface } from "./floatingSurface.js";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ var tooltip = recipe({
11
+ extend: floatingSurface,
12
+ slots: {
13
+ popup: "max-w-[min(20rem,var(--available-width))] px-2 py-1 text-xs font-bold uppercase tracking-wider shadow-hard-sm data-[instant]:transition-none"
14
+ }
15
+ });
16
+ var Tooltip = forwardRef(function Tooltip2({
17
+ content,
18
+ children,
19
+ side = "top",
20
+ align = "center",
21
+ delay = 400,
22
+ open,
23
+ defaultOpen,
24
+ onOpenChange,
25
+ disabled,
26
+ className,
27
+ ...props
28
+ }, ref) {
29
+ const styles = tooltip();
30
+ const portalTheme = usePortalThemeAttribute();
31
+ return /* @__PURE__ */ jsxs(
32
+ BaseTooltip.Root,
33
+ {
34
+ open,
35
+ defaultOpen,
36
+ onOpenChange: onOpenChange ? (next) => onOpenChange(next) : void 0,
37
+ disabled,
38
+ children: [
39
+ /* @__PURE__ */ jsx(BaseTooltip.Trigger, { delay, render: children }),
40
+ /* @__PURE__ */ jsx(BaseTooltip.Portal, { children: /* @__PURE__ */ jsx(
41
+ BaseTooltip.Positioner,
42
+ {
43
+ ...portalTheme,
44
+ side,
45
+ align,
46
+ sideOffset: 8,
47
+ collisionPadding: 8,
48
+ "data-slot": "tooltip-positioner",
49
+ className: styles.positioner(),
50
+ children: /* @__PURE__ */ jsx(
51
+ BaseTooltip.Popup,
52
+ {
53
+ ref,
54
+ "data-slot": "tooltip",
55
+ className: styles.popup({ class: className }),
56
+ ...props,
57
+ children: content
58
+ }
59
+ )
60
+ }
61
+ ) })
62
+ ]
63
+ }
64
+ );
65
+ });
66
+ export {
67
+ Tooltip
68
+ };
69
+ //# sourceMappingURL=Tooltip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Tooltip.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef } from 'react';\nimport type { HTMLAttributes, ReactElement, ReactNode } from 'react';\nimport { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';\nimport { recipe } from '../lib/recipe';\nimport { usePortalThemeAttribute } from './portalTheme';\nimport { floatingSurface } from './floatingSurface';\n\n/**\n * Which side of its trigger a floating element prefers. A preference, not a\n * promise: when that side has no room the element flips to the opposite one.\n *\n * Shared by `Tooltip`, `Popover` and `Menu`. A plain string union rather than\n * Base UI's own `Side`, so the primitive stays out of the published `.d.ts`.\n */\nexport type OverlaySide = 'top' | 'bottom' | 'left' | 'right';\n\n/** How a floating element lines up along its trigger's edge. */\nexport type OverlayAlign = 'start' | 'center' | 'end';\n\nexport interface TooltipProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'content' | 'children' | 'className'> {\n /**\n * What the tooltip says. Short, and never the only place the information\n * lives: a tooltip is not exposed to assistive technology and never opens\n * on touch, so it restates the trigger's accessible name (and may add a\n * shortcut) rather than replacing it.\n */\n content: ReactNode;\n /**\n * The trigger — one element that forwards its ref, such as `Button`. It\n * keeps its own tag and its own accessible name; give an icon-only control\n * an `aria-label`, which the tooltip then mirrors for sighted users.\n */\n children: ReactElement;\n /** The preferred side. `top` by default; flips when there is no room. */\n side?: OverlaySide;\n /** Alignment along the trigger's edge. `center` by default. */\n align?: OverlayAlign;\n /**\n * Milliseconds of hover before it opens. Keyboard focus opens it at once,\n * whatever this says.\n */\n delay?: number;\n /** Controlled open state. Leave unset and the tooltip manages its own. */\n open?: boolean;\n /** Open on first render, uncontrolled — for a story or a walkthrough. */\n defaultOpen?: boolean;\n /** Called with the next open state, whatever caused the change. */\n onOpenChange?: (open: boolean) => void;\n /** Suppress the tooltip without changing the trigger. */\n disabled?: boolean;\n /** Merged into the popup's classes. */\n className?: string;\n}\n\n// A label, not a panel: the smallest shadow and the tightest box, capped at\n// 20rem or the room the positioner measured, whichever is less. `data-instant`\n// is Base UI's mark for a tooltip that replaced an adjacent one without the\n// delay — fading that one in would flash.\nconst tooltip = recipe({\n extend: floatingSurface,\n slots: {\n popup:\n 'max-w-[min(20rem,var(--available-width))] px-2 py-1 text-xs font-bold uppercase tracking-wider '\n + 'shadow-hard-sm data-[instant]:transition-none',\n },\n});\n\n/**\n * A short visual label for a control, on hover and on keyboard focus.\n *\n * The replacement for `title=\"\"`, which a keyboard cannot reach and a theme\n * cannot style. Built on `@base-ui/react/tooltip`: the delay, the hover\n * bridge, collision flipping and the dismissal stack are the primitive's.\n *\n * A tooltip is **supplementary**. It is not announced — the trigger's own\n * accessible name is — and Base UI does not open it on touch, so an icon-only\n * control still needs an `aria-label`, and nothing a user must know may live\n * only here. Anything with content worth reading, or that a touch user\n * needs, is a {@link Popover}.\n *\n * Escape closes it, and only it: an open tooltip is the topmost surface, so a\n * `Popover` or `Modal` underneath stays open — the stack #162 established.\n *\n * @example\n * ```tsx\n * <Tooltip content=\"Speaker notes (N)\">\n * <Button aria-label=\"Show speaker notes\"><FileText /></Button>\n * </Tooltip>\n * ```\n */\nexport const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(function Tooltip(\n {\n content,\n children,\n side = 'top',\n align = 'center',\n delay = 400,\n open,\n defaultOpen,\n onOpenChange,\n disabled,\n className,\n ...props\n },\n ref,\n) {\n const styles = tooltip();\n\n const portalTheme = usePortalThemeAttribute();\n return (\n <BaseTooltip.Root\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange ? (next) => onOpenChange(next) : undefined}\n disabled={disabled}\n >\n <BaseTooltip.Trigger delay={delay} render={children} />\n <BaseTooltip.Portal>\n <BaseTooltip.Positioner\n {...portalTheme}\n side={side}\n align={align}\n sideOffset={8}\n collisionPadding={8}\n data-slot=\"tooltip-positioner\"\n className={styles.positioner()}\n >\n <BaseTooltip.Popup\n ref={ref}\n data-slot=\"tooltip\"\n className={styles.popup({ class: className })}\n {...props}\n >\n {content}\n </BaseTooltip.Popup>\n </BaseTooltip.Positioner>\n </BaseTooltip.Portal>\n </BaseTooltip.Root>\n );\n});\n"],"mappings":";;;AAEA,SAAS,kBAAkB;AAE3B,SAAS,WAAW,mBAAmB;AACvC,SAAS,cAAc;AACvB,SAAS,+BAA+B;AACxC,SAAS,uBAAuB;AA0G5B,SAME,KANF;AApDJ,IAAM,UAAU,OAAO;AAAA,EACrB,QAAQ;AAAA,EACR,OAAO;AAAA,IACL,OACE;AAAA,EAEJ;AACF,CAAC;AAyBM,IAAM,UAAU,WAAyC,SAASA,SACvE;AAAA,EACE;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,SAAS,QAAQ;AAEvB,QAAM,cAAc,wBAAwB;AAC5C,SACE;AAAA,IAAC,YAAY;AAAA,IAAZ;AAAA,MACC;AAAA,MACA;AAAA,MACA,cAAc,eAAe,CAAC,SAAS,aAAa,IAAI,IAAI;AAAA,MAC5D;AAAA,MAEA;AAAA,4BAAC,YAAY,SAAZ,EAAoB,OAAc,QAAQ,UAAU;AAAA,QACrD,oBAAC,YAAY,QAAZ,EACC;AAAA,UAAC,YAAY;AAAA,UAAZ;AAAA,YACE,GAAG;AAAA,YACJ;AAAA,YACA;AAAA,YACA,YAAY;AAAA,YACZ,kBAAkB;AAAA,YAClB,aAAU;AAAA,YACV,WAAW,OAAO,WAAW;AAAA,YAE7B;AAAA,cAAC,YAAY;AAAA,cAAZ;AAAA,gBACC;AAAA,gBACA,aAAU;AAAA,gBACV,WAAW,OAAO,MAAM,EAAE,OAAO,UAAU,CAAC;AAAA,gBAC3C,GAAG;AAAA,gBAEH;AAAA;AAAA,YACH;AAAA;AAAA,QACF,GACF;AAAA;AAAA;AAAA,EACF;AAEJ,CAAC;","names":["Tooltip"]}
@@ -0,0 +1,206 @@
1
+ // src/components/TrackCard.tsx
2
+ import { ArrowRight, Check, Lock } from "lucide-react";
3
+ import { forwardRef } from "react";
4
+ import { cn } from "../lib/recipe.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ var TrackCard = forwardRef(
7
+ function TrackCard2({
8
+ trackNumber,
9
+ title,
10
+ description,
11
+ items,
12
+ className,
13
+ ...props
14
+ }, ref) {
15
+ const completedCount = items.filter((i) => i.status === "completed").length;
16
+ const totalCount = items.length;
17
+ const progressPct = totalCount > 0 ? Math.round(completedCount / totalCount * 100) : 0;
18
+ const progressStyle = { width: `${progressPct}%` };
19
+ return /* @__PURE__ */ jsxs(
20
+ "div",
21
+ {
22
+ ref,
23
+ "data-slot": "track-card",
24
+ className: cn(
25
+ "border-2 border-edge-strong bg-surface-base font-mono text-content-primary shadow-hard-lg",
26
+ className
27
+ ),
28
+ ...props,
29
+ children: [
30
+ /* @__PURE__ */ jsxs(
31
+ "div",
32
+ {
33
+ "data-slot": "track-card-header",
34
+ className: "flex flex-wrap items-center justify-between gap-3 border-b-2 border-edge-strong bg-surface-raised px-4 py-3",
35
+ children: [
36
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
37
+ /* @__PURE__ */ jsxs(
38
+ "span",
39
+ {
40
+ "data-slot": "track-card-badge",
41
+ className: "px-2 py-0.5 text-xs font-black bg-accent-primary text-content-inverse",
42
+ children: [
43
+ "TRACK ",
44
+ trackNumber
45
+ ]
46
+ }
47
+ ),
48
+ /* @__PURE__ */ jsxs(
49
+ "span",
50
+ {
51
+ "data-slot": "track-card-title",
52
+ className: "text-xs font-bold text-content-secondary tracking-wider",
53
+ children: [
54
+ "// ",
55
+ title
56
+ ]
57
+ }
58
+ )
59
+ ] }),
60
+ /* @__PURE__ */ jsxs(
61
+ "div",
62
+ {
63
+ "data-slot": "track-card-stats",
64
+ className: "text-xs font-bold text-content-muted",
65
+ children: [
66
+ completedCount,
67
+ "/",
68
+ totalCount,
69
+ " COMPLETED (",
70
+ progressPct,
71
+ "%)"
72
+ ]
73
+ }
74
+ )
75
+ ]
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsxs(
79
+ "div",
80
+ {
81
+ "data-slot": "track-card-summary",
82
+ className: "border-b-2 border-edge-strong bg-surface-raised/40 p-4 space-y-3",
83
+ children: [
84
+ description && /* @__PURE__ */ jsx(
85
+ "p",
86
+ {
87
+ "data-slot": "track-card-description",
88
+ className: "font-sans text-sm text-content-secondary leading-relaxed",
89
+ children: description
90
+ }
91
+ ),
92
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
93
+ /* @__PURE__ */ jsx(
94
+ "div",
95
+ {
96
+ "data-slot": "track-card-progress-bar",
97
+ className: "h-3 w-full border border-edge-strong bg-surface-base p-0.5",
98
+ children: /* @__PURE__ */ jsx(
99
+ "div",
100
+ {
101
+ "data-slot": "track-card-progress-fill",
102
+ className: "h-full bg-intent-success transition-all duration-500 ease-out",
103
+ style: progressStyle
104
+ }
105
+ )
106
+ }
107
+ ),
108
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-[10px] text-content-muted font-bold uppercase", children: [
109
+ /* @__PURE__ */ jsx("span", { children: "0% START" }),
110
+ /* @__PURE__ */ jsxs("span", { children: [
111
+ "TARGET: COMPLETE ",
112
+ totalCount,
113
+ " UNITS"
114
+ ] }),
115
+ /* @__PURE__ */ jsx("span", { children: "100% READY" })
116
+ ] })
117
+ ] })
118
+ ]
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx(
122
+ "div",
123
+ {
124
+ "data-slot": "track-card-items",
125
+ className: "divide-y divide-edge-subtle p-2 space-y-2",
126
+ children: items.map((item) => {
127
+ const isCompleted = item.status === "completed";
128
+ const isCurrent = item.status === "current";
129
+ const isUpcoming = item.status === "upcoming";
130
+ const content = /* @__PURE__ */ jsxs(
131
+ "div",
132
+ {
133
+ "data-slot": "track-card-item-row",
134
+ className: cn(
135
+ "group flex items-center justify-between gap-4 p-3 border border-edge-subtle transition-all duration-150",
136
+ isCurrent ? "bg-surface-raised border-accent-primary shadow-hard-sm" : isCompleted ? "bg-surface-raised/80 hover:bg-surface-raised" : "bg-surface-base border-dashed hover:bg-surface-raised/40",
137
+ "hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-hard-sm"
138
+ ),
139
+ children: [
140
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
141
+ /* @__PURE__ */ jsx(
142
+ "div",
143
+ {
144
+ "data-slot": "track-card-item-icon",
145
+ className: cn(
146
+ "w-6 h-6 shrink-0 flex items-center justify-center font-bold text-xs border",
147
+ isCompleted ? "bg-intent-success border-intent-success text-content-inverse" : isCurrent ? "bg-accent-primary border-accent-primary text-content-inverse" : "border-edge-default text-content-muted"
148
+ ),
149
+ children: isCompleted ? /* @__PURE__ */ jsx(Check, { size: 14, strokeWidth: 3 }) : isUpcoming ? /* @__PURE__ */ jsx(Lock, { size: 12, strokeWidth: 2 }) : item.number
150
+ }
151
+ ),
152
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
153
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
154
+ /* @__PURE__ */ jsx(
155
+ "span",
156
+ {
157
+ className: cn(
158
+ "text-xs font-bold truncate",
159
+ isCurrent ? "text-content-primary" : isCompleted ? "text-content-secondary" : "text-content-muted"
160
+ ),
161
+ children: item.title
162
+ }
163
+ ),
164
+ isCurrent && /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 text-[9px] font-black bg-accent-secondary text-content-inverse uppercase tracking-wider", children: "ACTIVE" })
165
+ ] }),
166
+ item.summary && /* @__PURE__ */ jsx("p", { className: "font-sans text-xs text-content-muted line-clamp-1 mt-0.5", children: item.summary })
167
+ ] })
168
+ ] }),
169
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 shrink-0", children: [
170
+ item.duration && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-content-muted font-bold", children: item.duration }),
171
+ item.href ? /* @__PURE__ */ jsx(
172
+ ArrowRight,
173
+ {
174
+ size: 14,
175
+ className: "text-content-muted group-hover:text-accent-primary group-hover:translate-x-0.5 transition-all"
176
+ }
177
+ ) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-content-muted font-bold", children: "COMING SOON" })
178
+ ] })
179
+ ]
180
+ }
181
+ );
182
+ if (item.href) {
183
+ return /* @__PURE__ */ jsx(
184
+ "a",
185
+ {
186
+ href: item.href,
187
+ className: "block no-underline",
188
+ children: content
189
+ },
190
+ item.id
191
+ );
192
+ }
193
+ return /* @__PURE__ */ jsx("div", { children: content }, item.id);
194
+ })
195
+ }
196
+ )
197
+ ]
198
+ }
199
+ );
200
+ }
201
+ );
202
+ TrackCard.displayName = "TrackCard";
203
+ export {
204
+ TrackCard
205
+ };
206
+ //# sourceMappingURL=TrackCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/TrackCard.tsx"],"sourcesContent":["import { ArrowRight, Check, Lock } from 'lucide-react';\nimport { type CSSProperties, forwardRef, type HTMLAttributes } from 'react';\nimport { cn } from '../lib/recipe';\n\nexport type TrackItemStatus = 'completed' | 'current' | 'upcoming';\n\nexport interface TrackItem {\n /** Unique milestone identifier. */\n id: string;\n /** Formatted numerical index (e.g. \"01\", \"02\"). */\n number: string;\n /** Milestone title. */\n title: string;\n /** Estimated duration or word count (e.g. \"15 min\"). */\n duration?: string;\n /** Current completion status. */\n status: TrackItemStatus;\n /** Destination link for available milestone articles. */\n href?: string;\n /** Optional summary or key topic description. */\n summary?: string;\n}\n\nexport interface TrackCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Track sequence identifier (e.g. \"01\" or \"02\"). */\n trackNumber: string | number;\n /** Curriculum or series title (e.g. \"DISTRIBUTED LOG ARCHITECTURES\"). */\n title: string;\n /** Short curriculum or learning path summary. */\n description?: string;\n /** Ordered list of milestones in the series. */\n items: TrackItem[];\n}\n\n/**\n * Multi-part curriculum and learning track card.\n *\n * Implements structured learning navigation with brutalist segmented progress bars,\n * numbered milestones, and hover-elevated task item rows with hard offset shadows.\n *\n * @example\n * ```tsx\n * <TrackCard\n * trackNumber=\"01\"\n * title=\"MINI-KAFKA ENGINE\"\n * description=\"A comprehensive deep-dive into append-only commit logs.\"\n * items={[\n * { id: '1', number: '01', title: 'Segment Storage', status: 'completed' },\n * { id: '2', number: '02', title: 'Index Memory Maps', status: 'current' },\n * ]}\n * />\n * ```\n */\nexport const TrackCard = forwardRef<HTMLDivElement, TrackCardProps>(\n function TrackCard(\n {\n trackNumber,\n title,\n description,\n items,\n className,\n ...props\n },\n ref,\n ) {\n const completedCount = items.filter((i) => i.status === 'completed').length;\n const totalCount = items.length;\n const progressPct =\n totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;\n\n const progressStyle: CSSProperties = { width: `${progressPct}%` };\n\n return (\n <div\n ref={ref}\n data-slot=\"track-card\"\n className={cn(\n 'border-2 border-edge-strong bg-surface-base font-mono text-content-primary shadow-hard-lg',\n className,\n )}\n {...props}\n >\n {/* Top Track Header Bar */}\n <div\n data-slot=\"track-card-header\"\n className=\"flex flex-wrap items-center justify-between gap-3 border-b-2 border-edge-strong bg-surface-raised px-4 py-3\"\n >\n <div className=\"flex items-center gap-2.5\">\n <span\n data-slot=\"track-card-badge\"\n className=\"px-2 py-0.5 text-xs font-black bg-accent-primary text-content-inverse\"\n >\n TRACK {trackNumber}\n </span>\n <span\n data-slot=\"track-card-title\"\n className=\"text-xs font-bold text-content-secondary tracking-wider\"\n >\n {'// '}\n {title}\n </span>\n </div>\n <div\n data-slot=\"track-card-stats\"\n className=\"text-xs font-bold text-content-muted\"\n >\n {completedCount}/{totalCount} COMPLETED ({progressPct}%)\n </div>\n </div>\n\n {/* Description & Progress Section */}\n <div\n data-slot=\"track-card-summary\"\n className=\"border-b-2 border-edge-strong bg-surface-raised/40 p-4 space-y-3\"\n >\n {description && (\n <p\n data-slot=\"track-card-description\"\n className=\"font-sans text-sm text-content-secondary leading-relaxed\"\n >\n {description}\n </p>\n )}\n\n {/* Brutalist Progress Bar */}\n <div className=\"space-y-1\">\n <div\n data-slot=\"track-card-progress-bar\"\n className=\"h-3 w-full border border-edge-strong bg-surface-base p-0.5\"\n >\n <div\n data-slot=\"track-card-progress-fill\"\n className=\"h-full bg-intent-success transition-all duration-500 ease-out\"\n style={progressStyle}\n />\n </div>\n <div className=\"flex justify-between text-[10px] text-content-muted font-bold uppercase\">\n <span>0% START</span>\n <span>TARGET: COMPLETE {totalCount} UNITS</span>\n <span>100% READY</span>\n </div>\n </div>\n </div>\n\n {/* Track Milestones / Item Rows */}\n <div\n data-slot=\"track-card-items\"\n className=\"divide-y divide-edge-subtle p-2 space-y-2\"\n >\n {items.map((item) => {\n const isCompleted = item.status === 'completed';\n const isCurrent = item.status === 'current';\n const isUpcoming = item.status === 'upcoming';\n\n const content = (\n <div\n data-slot=\"track-card-item-row\"\n className={cn(\n 'group flex items-center justify-between gap-4 p-3 border border-edge-subtle transition-all duration-150',\n isCurrent\n ? 'bg-surface-raised border-accent-primary shadow-hard-sm'\n : isCompleted\n ? 'bg-surface-raised/80 hover:bg-surface-raised'\n : 'bg-surface-base border-dashed hover:bg-surface-raised/40',\n 'hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-hard-sm',\n )}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n {/* Index / Status Icon */}\n <div\n data-slot=\"track-card-item-icon\"\n className={cn(\n 'w-6 h-6 shrink-0 flex items-center justify-center font-bold text-xs border',\n isCompleted\n ? 'bg-intent-success border-intent-success text-content-inverse'\n : isCurrent\n ? 'bg-accent-primary border-accent-primary text-content-inverse'\n : 'border-edge-default text-content-muted',\n )}\n >\n {isCompleted ? (\n <Check size={14} strokeWidth={3} />\n ) : isUpcoming ? (\n <Lock size={12} strokeWidth={2} />\n ) : (\n item.number\n )}\n </div>\n\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span\n className={cn(\n 'text-xs font-bold truncate',\n isCurrent\n ? 'text-content-primary'\n : isCompleted\n ? 'text-content-secondary'\n : 'text-content-muted',\n )}\n >\n {item.title}\n </span>\n {isCurrent && (\n <span className=\"px-1.5 py-0.5 text-[9px] font-black bg-accent-secondary text-content-inverse uppercase tracking-wider\">\n ACTIVE\n </span>\n )}\n </div>\n {item.summary && (\n <p className=\"font-sans text-xs text-content-muted line-clamp-1 mt-0.5\">\n {item.summary}\n </p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center gap-3 shrink-0\">\n {item.duration && (\n <span className=\"text-[11px] text-content-muted font-bold\">\n {item.duration}\n </span>\n )}\n {item.href ? (\n <ArrowRight\n size={14}\n className=\"text-content-muted group-hover:text-accent-primary group-hover:translate-x-0.5 transition-all\"\n />\n ) : (\n <span className=\"text-[10px] text-content-muted font-bold\">\n COMING SOON\n </span>\n )}\n </div>\n </div>\n );\n\n if (item.href) {\n return (\n <a\n key={item.id}\n href={item.href}\n className=\"block no-underline\"\n >\n {content}\n </a>\n );\n }\n\n return <div key={item.id}>{content}</div>;\n })}\n </div>\n </div>\n );\n },\n);\n\nTrackCard.displayName = 'TrackCard';\n"],"mappings":"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","names":["TrackCard"]}