@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,116 @@
1
+ // src/components/blog/BlogPost.tsx
2
+ import { Calendar, Clock, User } from "lucide-react";
3
+ import { PageTitle } from "../PageTitle.js";
4
+ import { Card } from "../Card.js";
5
+ import { Badge } from "../Badge.js";
6
+ import { AsciiDivider } from "../AsciiDivider.js";
7
+ import { SiteLink } from "../LinkProvider.js";
8
+ import { deriveInitials, resolveAuthor } from "./author.js";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ var BlogPost = ({
11
+ title,
12
+ subtitle,
13
+ author: authorProp,
14
+ authorCard,
15
+ date,
16
+ readingTime,
17
+ tags,
18
+ children
19
+ }) => {
20
+ const author = resolveAuthor(authorProp);
21
+ const initials = author.initials ?? deriveInitials(author.name);
22
+ return /* @__PURE__ */ jsxs(
23
+ "article",
24
+ {
25
+ "data-slot": "blog-post",
26
+ style: {
27
+ maxWidth: "840px",
28
+ margin: "0 auto",
29
+ padding: "2rem 1.5rem",
30
+ color: "var(--ds-text-primary)"
31
+ },
32
+ children: [
33
+ /* @__PURE__ */ jsxs("header", { style: { marginBottom: "2.5rem" }, children: [
34
+ /* @__PURE__ */ jsx(PageTitle, { subtitle, bracketed: true, children: title }),
35
+ /* @__PURE__ */ jsxs(
36
+ "div",
37
+ {
38
+ style: {
39
+ display: "flex",
40
+ flexWrap: "wrap",
41
+ gap: "1.5rem",
42
+ padding: "1rem 0",
43
+ borderTop: "2px solid var(--ds-border-strong)",
44
+ borderBottom: "2px solid var(--ds-border-strong)",
45
+ fontFamily: "var(--ds-font-mono)",
46
+ fontSize: "0.85rem",
47
+ color: "var(--ds-accent-primary)",
48
+ alignItems: "center"
49
+ },
50
+ children: [
51
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.4rem" }, children: [
52
+ /* @__PURE__ */ jsx(User, { size: 15 }),
53
+ /* @__PURE__ */ jsx("span", { children: author.name })
54
+ ] }),
55
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.4rem" }, children: [
56
+ /* @__PURE__ */ jsx(Calendar, { size: 15 }),
57
+ /* @__PURE__ */ jsx("span", { children: date })
58
+ ] }),
59
+ readingTime && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.4rem" }, children: [
60
+ /* @__PURE__ */ jsx(Clock, { size: 15 }),
61
+ /* @__PURE__ */ jsx("span", { children: readingTime })
62
+ ] }),
63
+ tags && tags.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "0.5rem", marginLeft: "auto" }, children: tags.map((t) => /* @__PURE__ */ jsxs(Badge, { accent: "primary", children: [
64
+ "#",
65
+ t
66
+ ] }, t)) })
67
+ ]
68
+ }
69
+ )
70
+ ] }),
71
+ /* @__PURE__ */ jsx(
72
+ "div",
73
+ {
74
+ style: {
75
+ fontFamily: "var(--ds-font-body)",
76
+ fontSize: "1.125rem",
77
+ lineHeight: 1.8
78
+ },
79
+ children
80
+ }
81
+ ),
82
+ /* @__PURE__ */ jsx(AsciiDivider, {}),
83
+ authorCard !== null && authorCard !== false && /* @__PURE__ */ jsx("footer", { style: { marginTop: "3rem" }, children: authorCard === void 0 ? /* @__PURE__ */ jsx(Card, { style: { borderColor: "var(--ds-accent-primary)" }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "1rem", marginBottom: "0.5rem" }, children: [
84
+ author.avatar ?? /* @__PURE__ */ jsx(
85
+ "div",
86
+ {
87
+ style: {
88
+ width: "40px",
89
+ height: "40px",
90
+ backgroundColor: "var(--ds-accent-primary)",
91
+ color: "var(--ds-text-inverse)",
92
+ fontWeight: 900,
93
+ display: "flex",
94
+ alignItems: "center",
95
+ justifyContent: "center",
96
+ fontFamily: "var(--ds-font-display)"
97
+ },
98
+ children: initials
99
+ }
100
+ ),
101
+ /* @__PURE__ */ jsxs("div", { children: [
102
+ /* @__PURE__ */ jsxs("h4", { style: { fontFamily: "var(--ds-font-display)", margin: 0, fontSize: "1.1rem", fontWeight: 800, textTransform: "uppercase" }, children: [
103
+ "Written by ",
104
+ author.url ? /* @__PURE__ */ jsx(SiteLink, { href: author.url, className: "text-inherit underline decoration-2 underline-offset-4", children: author.name }) : author.name
105
+ ] }),
106
+ author.description ? /* @__PURE__ */ jsx("span", { style: { fontFamily: "var(--ds-font-mono)", fontSize: "0.8rem", color: "var(--ds-accent-secondary)" }, children: author.description }) : null
107
+ ] })
108
+ ] }) }) : authorCard })
109
+ ]
110
+ }
111
+ );
112
+ };
113
+ export {
114
+ BlogPost
115
+ };
116
+ //# sourceMappingURL=BlogPost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/blog/BlogPost.tsx"],"sourcesContent":["import type React from 'react';\nimport { Calendar, Clock, User, Tag as TagIcon } from 'lucide-react';\nimport { PageTitle } from '../PageTitle';\nimport { Card } from '../Card';\nimport { Badge } from '../Badge';\nimport { AsciiDivider } from '../AsciiDivider';\nimport { SiteLink } from '../LinkProvider';\nimport { deriveInitials, resolveAuthor } from './author';\nimport type { BlogAuthor } from './author';\n\nexport type { BlogAuthor } from './author';\n\nexport interface BlogPostProps {\n /** The post's title, set as a bracketed `PageTitle`. */\n title: string;\n /** A line under the title — the standfirst. Omitted when not supplied. */\n subtitle?: string;\n /**\n * Who wrote it. Omit it for the site's own author — the byline and author\n * card render exactly as they always have. A string is a name: the default\n * author's name gets the default card whole, any other gets just that name,\n * with derived initials and no description. A `BlogAuthor` object sets every\n * field the card shows, and only those it sets are shown.\n */\n author?: string | BlogAuthor;\n /**\n * Replaces the author card under the body — the slot for anything an\n * `BlogAuthor` cannot express, such as a bio with links or several authors. It\n * is rendered inside the post's `<footer>`; `null` or `false` removes the\n * footer. The byline row still reads `author`.\n */\n authorCard?: React.ReactNode;\n /** The publication date, shown in the byline row exactly as given. */\n date: string;\n /**\n * Reading time, e.g. `'9 min read'`. Omitted from the byline row when not\n * supplied — there is no default, because a reading time nobody measured is\n * worse than no reading time at all.\n */\n readingTime?: string;\n /** Topic tags in the header. Omitted entirely when not supplied or empty. */\n tags?: string[];\n /** The article body. Wrap Markdown output in `Prose`; this applies no typography. */\n children: React.ReactNode;\n}\n\n/**\n * The editorial article shell: title, byline row, tags, rule, body.\n *\n * It composes `PageTitle`, `Badge` and `Divider` into the standard post header\n * so every article on the site agrees about where the date sits. The body is\n * yours — it applies no typography of its own, so Markdown output should be\n * wrapped in `Prose` inside it.\n *\n * `readingTime` and `tags` have no defaults, and that is deliberate: they are\n * content, not configuration, so inventing them would publish a claim nobody\n * made — a nine-thousand-word article confidently advertising \"5 min read\".\n * Each is simply omitted from the header when absent.\n *\n * `author` *does* default, to the site's own author. That is defensible where\n * the other two are not: the byline of a personal site is the same on almost\n * every post, where a reading time and a topic list differ on all of them. The\n * default lives outside this file, and every field of it — name, initials,\n * avatar, link, description — is overridable: pass a `BlogAuthor` for a guest\n * post, or `authorCard` for a card a `BlogAuthor` cannot describe.\n *\n * ```tsx\n * <BlogPost title=\"Where a theme stops applying\" date=\"2026-08-11\"\n * readingTime=\"9 min read\" tags={['CSS', 'Design systems']}>\n * <Prose>{content}</Prose>\n * </BlogPost>\n *\n * <BlogPost title=\"A guest post\" date=\"2026-09-01\"\n * author={{ name: 'Ada King Lovelace', url: 'https://example.com',\n * description: 'Analyst, Analytical Engine' }}>\n * <Prose>{content}</Prose>\n * </BlogPost>\n * ```\n */\nexport const BlogPost: React.FC<BlogPostProps> = ({\n title,\n subtitle,\n author: authorProp,\n authorCard,\n date,\n readingTime,\n tags,\n children,\n}) => {\n const author = resolveAuthor(authorProp);\n const initials = author.initials ?? deriveInitials(author.name);\n return (\n <article data-slot=\"blog-post\"\n style={{\n maxWidth: '840px',\n margin: '0 auto',\n padding: '2rem 1.5rem',\n color: 'var(--ds-text-primary)',\n }}\n >\n {/* Blog Post Header */}\n <header style={{ marginBottom: '2.5rem' }}>\n <PageTitle subtitle={subtitle} bracketed>\n {title}\n </PageTitle>\n\n {/* Metadata Bar */}\n <div\n style={{\n display: 'flex',\n flexWrap: 'wrap',\n gap: '1.5rem',\n padding: '1rem 0',\n borderTop: '2px solid var(--ds-border-strong)',\n borderBottom: '2px solid var(--ds-border-strong)',\n fontFamily: 'var(--ds-font-mono)',\n fontSize: '0.85rem',\n color: 'var(--ds-accent-primary)',\n alignItems: 'center',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}>\n <User size={15} />\n <span>{author.name}</span>\n </div>\n\n <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}>\n <Calendar size={15} />\n <span>{date}</span>\n </div>\n\n {readingTime && (\n <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}>\n <Clock size={15} />\n <span>{readingTime}</span>\n </div>\n )}\n\n {tags && tags.length > 0 && (\n <div style={{ display: 'flex', gap: '0.5rem', marginLeft: 'auto' }}>\n {tags.map((t) => (\n <Badge key={t} accent=\"primary\">\n #{t}\n </Badge>\n ))}\n </div>\n )}\n </div>\n </header>\n\n {/* Main Narrative Body */}\n <div\n style={{\n fontFamily: 'var(--ds-font-body)',\n fontSize: '1.125rem',\n lineHeight: 1.8,\n }}\n >\n {children}\n </div>\n\n <AsciiDivider />\n\n {/* Author Bio Footer */}\n {authorCard !== null && authorCard !== false && (\n <footer style={{ marginTop: '3rem' }}>\n {authorCard === undefined ? (\n <Card style={{ borderColor: 'var(--ds-accent-primary)' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: '1rem', marginBottom: '0.5rem' }}>\n {author.avatar ?? (\n <div\n style={{\n width: '40px',\n height: '40px',\n backgroundColor: 'var(--ds-accent-primary)',\n color: 'var(--ds-text-inverse)',\n fontWeight: 900,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontFamily: 'var(--ds-font-display)',\n }}\n >\n {initials}\n </div>\n )}\n <div>\n <h4 style={{ fontFamily: 'var(--ds-font-display)', margin: 0, fontSize: '1.1rem', fontWeight: 800, textTransform: 'uppercase' }}>\n Written by {author.url ? (\n <SiteLink href={author.url} className=\"text-inherit underline decoration-2 underline-offset-4\">\n {author.name}\n </SiteLink>\n ) : (\n author.name\n )}\n </h4>\n {author.description ? (\n <span style={{ fontFamily: 'var(--ds-font-mono)', fontSize: '0.8rem', color: 'var(--ds-accent-secondary)' }}>\n {author.description}\n </span>\n ) : null}\n </div>\n </div>\n </Card>\n ) : (\n authorCard\n )}\n </footer>\n )}\n </article>\n );\n};\n"],"mappings":";AACA,SAAS,UAAU,OAAO,YAA4B;AACtD,SAAS,iBAAiB;AAC1B,SAAS,YAAY;AACrB,SAAS,aAAa;AACtB,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AACzB,SAAS,gBAAgB,qBAAqB;AA+FtC,cAmBE,YAnBF;AAvBD,IAAM,WAAoC,CAAC;AAAA,EAChD;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,SAAS,cAAc,UAAU;AACvC,QAAM,WAAW,OAAO,YAAY,eAAe,OAAO,IAAI;AAC9D,SACE;AAAA,IAAC;AAAA;AAAA,MAAQ,aAAU;AAAA,MACjB,OAAO;AAAA,QACL,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,MAGA;AAAA,6BAAC,YAAO,OAAO,EAAE,cAAc,SAAS,GACtC;AAAA,8BAAC,aAAU,UAAoB,WAAS,MACrC,iBACH;AAAA,UAGA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,gBACL,SAAS;AAAA,gBACT,UAAU;AAAA,gBACV,KAAK;AAAA,gBACL,SAAS;AAAA,gBACT,WAAW;AAAA,gBACX,cAAc;AAAA,gBACd,YAAY;AAAA,gBACZ,UAAU;AAAA,gBACV,OAAO;AAAA,gBACP,YAAY;AAAA,cACd;AAAA,cAEA;AAAA,qCAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,SAAS,GACjE;AAAA,sCAAC,QAAK,MAAM,IAAI;AAAA,kBAChB,oBAAC,UAAM,iBAAO,MAAK;AAAA,mBACrB;AAAA,gBAEA,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,SAAS,GACjE;AAAA,sCAAC,YAAS,MAAM,IAAI;AAAA,kBACpB,oBAAC,UAAM,gBAAK;AAAA,mBACd;AAAA,gBAEC,eACC,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,SAAS,GACjE;AAAA,sCAAC,SAAM,MAAM,IAAI;AAAA,kBACjB,oBAAC,UAAM,uBAAY;AAAA,mBACrB;AAAA,gBAGD,QAAQ,KAAK,SAAS,KACrB,oBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,KAAK,UAAU,YAAY,OAAO,GAC9D,eAAK,IAAI,CAAC,MACT,qBAAC,SAAc,QAAO,WAAU;AAAA;AAAA,kBAC5B;AAAA,qBADQ,CAEZ,CACD,GACH;AAAA;AAAA;AAAA,UAEJ;AAAA,WACF;AAAA,QAGA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,YAAY;AAAA,cACZ,UAAU;AAAA,cACV,YAAY;AAAA,YACd;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QAEA,oBAAC,gBAAa;AAAA,QAGb,eAAe,QAAQ,eAAe,SACrC,oBAAC,YAAO,OAAO,EAAE,WAAW,OAAO,GAChC,yBAAe,SACd,oBAAC,QAAK,OAAO,EAAE,aAAa,2BAA2B,GACrD,+BAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,YAAY,UAAU,KAAK,QAAQ,cAAc,SAAS,GACtF;AAAA,iBAAO,UACN;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,gBACL,OAAO;AAAA,gBACP,QAAQ;AAAA,gBACR,iBAAiB;AAAA,gBACjB,OAAO;AAAA,gBACP,YAAY;AAAA,gBACZ,SAAS;AAAA,gBACT,YAAY;AAAA,gBACZ,gBAAgB;AAAA,gBAChB,YAAY;AAAA,cACd;AAAA,cAEC;AAAA;AAAA,UACH;AAAA,UAEF,qBAAC,SACC;AAAA,iCAAC,QAAG,OAAO,EAAE,YAAY,0BAA0B,QAAQ,GAAG,UAAU,UAAU,YAAY,KAAK,eAAe,YAAY,GAAG;AAAA;AAAA,cACnH,OAAO,MACjB,oBAAC,YAAS,MAAM,OAAO,KAAK,WAAU,0DACnC,iBAAO,MACV,IAEA,OAAO;AAAA,eAEX;AAAA,YACC,OAAO,cACN,oBAAC,UAAK,OAAO,EAAE,YAAY,uBAAuB,UAAU,UAAU,OAAO,6BAA6B,GACvG,iBAAO,aACV,IACE;AAAA,aACN;AAAA,WACF,GACF,IAEA,YAEJ;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
@@ -0,0 +1,62 @@
1
+ // src/components/blog/LoremIpsumPost.tsx
2
+ import { BlogPost } from "./BlogPost.js";
3
+ import { TLDR } from "../TLDR.js";
4
+ import { NoteBlock } from "../NoteBlock.js";
5
+ import { BracketText } from "../BracketText.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ var LoremIpsumPost = () => {
8
+ return /* @__PURE__ */ jsxs(
9
+ BlogPost,
10
+ {
11
+ title: "Architecture of a Brutalist Design Engine",
12
+ subtitle: "Extending terminal aesthetics and dual-mode theme propagation across web surfaces",
13
+ date: "July 26, 2026",
14
+ readingTime: "6 min read",
15
+ tags: ["Design System", "Brutalism", "Architecture"],
16
+ children: [
17
+ /* @__PURE__ */ jsxs(TLDR, { children: [
18
+ "Modern web design systems often default to safe, generic rounded corners and muted colors. By enforcing zero border-radius, hard offset shadows, bracketed display typography, and a unified dual-mode (",
19
+ /* @__PURE__ */ jsx(BracketText, { accent: "primary", children: "DARK" }),
20
+ " / ",
21
+ /* @__PURE__ */ jsx(BracketText, { accent: "secondary", children: "SKETCH" }),
22
+ ") token matrix, we achieve an unmistakable visual identity that scales across every application."
23
+ ] }),
24
+ /* @__PURE__ */ jsx("h2", { style: { fontFamily: "var(--ds-font-display)", fontSize: "1.75rem", fontWeight: 800, textTransform: "uppercase", marginTop: "2.5rem", color: "var(--ds-accent-primary)" }, children: "[ 01. The Problem with Generic Web UI ]" }),
25
+ /* @__PURE__ */ jsx("p", { style: { marginTop: "1rem" }, children: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta." }),
26
+ /* @__PURE__ */ jsxs(NoteBlock, { type: "important", title: "SYSTEM RULE ENFORCEMENT", children: [
27
+ "Never hardcode pixel border radii or hex literals inside downstream component code. All colors must resolve through standard CSS tokens (",
28
+ /* @__PURE__ */ jsx("code", { style: { color: "var(--ds-accent-primary)" }, children: "--ds-accent-primary" }),
29
+ ", ",
30
+ /* @__PURE__ */ jsx("code", { style: { color: "var(--ds-accent-tertiary)" }, children: "--ds-accent-tertiary" }),
31
+ ") to preserve paper-and-ink theme remapping."
32
+ ] }),
33
+ /* @__PURE__ */ jsx("h2", { style: { fontFamily: "var(--ds-font-display)", fontSize: "1.75rem", fontWeight: 800, textTransform: "uppercase", marginTop: "2.5rem", color: "var(--ds-accent-tertiary)" }, children: "[ 02. Executable Design Tokens ]" }),
34
+ /* @__PURE__ */ jsx("p", { style: { marginTop: "1rem" }, children: "Below is an example of code block rendering within the design system:" }),
35
+ /* @__PURE__ */ jsxs("div", { style: { margin: "1.5rem 0", border: "2px solid var(--ds-border-strong)", backgroundColor: "var(--ds-surface-base)" }, children: [
36
+ /* @__PURE__ */ jsx("div", { style: { padding: "0.75rem 1.25rem", backgroundColor: "var(--ds-border-strong)", color: "var(--ds-surface-base)", fontFamily: "var(--ds-font-mono)", fontWeight: 800, fontSize: "0.875rem" }, children: "// tailwind-preset.ts" }),
37
+ /* @__PURE__ */ jsx("pre", { tabIndex: 0, role: "region", "aria-label": "Code sample", style: { padding: "1.25rem", fontFamily: "var(--ds-font-mono)", fontSize: "0.875rem", color: "var(--ds-intent-success)", overflowX: "auto", margin: 0 }, children: `export const brutalistTailwindPreset = {
38
+ theme: {
39
+ extend: {
40
+ borderRadius: { none: '0px' },
41
+ boxShadow: {
42
+ 'hard-md': '4px 4px 0px 0px var(--ds-shadow-color)',
43
+ 'hard-cyan': '4px 4px 0px 0px var(--ds-accent-primary)',
44
+ }
45
+ }
46
+ }
47
+ };` })
48
+ ] }),
49
+ /* @__PURE__ */ jsxs(NoteBlock, { type: "tip", title: "DUAL-MODE THEME SYNCHRONIZATION", children: [
50
+ "When cycling to ",
51
+ /* @__PURE__ */ jsx(BracketText, { accent: "secondary", children: "SKETCH" }),
52
+ " mode, the dark screen transforms into a warm paper sheet, while cyan and pink re-map into rich blue and red pen inks."
53
+ ] }),
54
+ /* @__PURE__ */ jsx("p", { style: { marginTop: "1.5rem" }, children: "In conclusion, adopting this design system as a shared foundation guarantees that every website and project inherits a cohesive, high-performance visual surface." })
55
+ ]
56
+ }
57
+ );
58
+ };
59
+ export {
60
+ LoremIpsumPost
61
+ };
62
+ //# sourceMappingURL=LoremIpsumPost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/blog/LoremIpsumPost.tsx"],"sourcesContent":["import type React from 'react';\nimport { BlogPost } from './BlogPost';\nimport { TLDR } from '../TLDR';\nimport { NoteBlock } from '../NoteBlock';\nimport { BracketText } from '../BracketText';\n\nexport const LoremIpsumPost: React.FC = () => {\n return (\n <BlogPost\n title=\"Architecture of a Brutalist Design Engine\"\n subtitle=\"Extending terminal aesthetics and dual-mode theme propagation across web surfaces\"\n date=\"July 26, 2026\"\n readingTime=\"6 min read\"\n tags={['Design System', 'Brutalism', 'Architecture']}\n >\n <TLDR>\n Modern web design systems often default to safe, generic rounded corners and muted colors.\n By enforcing zero border-radius, hard offset shadows, bracketed display typography, and a unified\n dual-mode (<BracketText accent=\"primary\">DARK</BracketText> / <BracketText accent=\"secondary\">SKETCH</BracketText>) token matrix,\n we achieve an unmistakable visual identity that scales across every application.\n </TLDR>\n\n <h2 style={{ fontFamily: 'var(--ds-font-display)', fontSize: '1.75rem', fontWeight: 800, textTransform: 'uppercase', marginTop: '2.5rem', color: 'var(--ds-accent-primary)' }}>\n [ 01. The Problem with Generic Web UI ]\n </h2>\n <p style={{ marginTop: '1rem' }}>\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam.\n Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta.\n </p>\n\n <NoteBlock type=\"important\" title=\"SYSTEM RULE ENFORCEMENT\">\n Never hardcode pixel border radii or hex literals inside downstream component code.\n All colors must resolve through standard CSS tokens (<code style={{ color: 'var(--ds-accent-primary)' }}>--ds-accent-primary</code>, <code style={{ color: 'var(--ds-accent-tertiary)' }}>--ds-accent-tertiary</code>) to preserve paper-and-ink theme remapping.\n </NoteBlock>\n\n <h2 style={{ fontFamily: 'var(--ds-font-display)', fontSize: '1.75rem', fontWeight: 800, textTransform: 'uppercase', marginTop: '2.5rem', color: 'var(--ds-accent-tertiary)' }}>\n [ 02. Executable Design Tokens ]\n </h2>\n <p style={{ marginTop: '1rem' }}>\n Below is an example of code block rendering within the design system:\n </p>\n\n <div style={{ margin: '1.5rem 0', border: '2px solid var(--ds-border-strong)', backgroundColor: 'var(--ds-surface-base)' }}>\n <div style={{ padding: '0.75rem 1.25rem', backgroundColor: 'var(--ds-border-strong)', color: 'var(--ds-surface-base)', fontFamily: 'var(--ds-font-mono)', fontWeight: 800, fontSize: '0.875rem' }}>\n // tailwind-preset.ts\n </div>\n {/* Scrollable at narrow widths — see `CodeBlock` for why this needs a tabIndex. */}\n <pre tabIndex={0} role=\"region\" aria-label=\"Code sample\" style={{ padding: '1.25rem', fontFamily: 'var(--ds-font-mono)', fontSize: '0.875rem', color: 'var(--ds-intent-success)', overflowX: 'auto', margin: 0 }}>\n{`export const brutalistTailwindPreset = {\n theme: {\n extend: {\n borderRadius: { none: '0px' },\n boxShadow: {\n 'hard-md': '4px 4px 0px 0px var(--ds-shadow-color)',\n 'hard-cyan': '4px 4px 0px 0px var(--ds-accent-primary)',\n }\n }\n }\n};`}\n </pre>\n </div>\n\n <NoteBlock type=\"tip\" title=\"DUAL-MODE THEME SYNCHRONIZATION\">\n When cycling to <BracketText accent=\"secondary\">SKETCH</BracketText> mode, the dark screen transforms into a warm paper sheet,\n while cyan and pink re-map into rich blue and red pen inks.\n </NoteBlock>\n\n <p style={{ marginTop: '1.5rem' }}>\n In conclusion, adopting this design system as a shared foundation guarantees that every website and project\n inherits a cohesive, high-performance visual surface.\n </p>\n </BlogPost>\n );\n};\n"],"mappings":";AACA,SAAS,gBAAgB;AACzB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAWtB,SAGa,KAHb;AATC,IAAM,iBAA2B,MAAM;AAC5C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,UAAS;AAAA,MACT,MAAK;AAAA,MACL,aAAY;AAAA,MACZ,MAAM,CAAC,iBAAiB,aAAa,cAAc;AAAA,MAEnD;AAAA,6BAAC,QAAK;AAAA;AAAA,UAGO,oBAAC,eAAY,QAAO,WAAU,kBAAI;AAAA,UAAc;AAAA,UAAG,oBAAC,eAAY,QAAO,aAAY,oBAAM;AAAA,UAAc;AAAA,WAEpH;AAAA,QAEA,oBAAC,QAAG,OAAO,EAAE,YAAY,0BAA0B,UAAU,WAAW,YAAY,KAAK,eAAe,aAAa,WAAW,UAAU,OAAO,2BAA2B,GAAG,qDAE/K;AAAA,QACA,oBAAC,OAAE,OAAO,EAAE,WAAW,OAAO,GAAG,4QAGjC;AAAA,QAEA,qBAAC,aAAU,MAAK,aAAY,OAAM,2BAA0B;AAAA;AAAA,UAEL,oBAAC,UAAK,OAAO,EAAE,OAAO,2BAA2B,GAAG,iCAAmB;AAAA,UAAO;AAAA,UAAE,oBAAC,UAAK,OAAO,EAAE,OAAO,4BAA4B,GAAG,kCAAoB;AAAA,UAAO;AAAA,WACvN;AAAA,QAEA,oBAAC,QAAG,OAAO,EAAE,YAAY,0BAA0B,UAAU,WAAW,YAAY,KAAK,eAAe,aAAa,WAAW,UAAU,OAAO,4BAA4B,GAAG,8CAEhL;AAAA,QACA,oBAAC,OAAE,OAAO,EAAE,WAAW,OAAO,GAAG,mFAEjC;AAAA,QAEA,qBAAC,SAAI,OAAO,EAAE,QAAQ,YAAY,QAAQ,qCAAqC,iBAAiB,yBAAyB,GACvH;AAAA,8BAAC,SAAI,OAAO,EAAE,SAAS,mBAAmB,iBAAiB,2BAA2B,OAAO,0BAA0B,YAAY,uBAAuB,YAAY,KAAK,UAAU,WAAW,GAAG,mCAEnM;AAAA,UAEA,oBAAC,SAAI,UAAU,GAAG,MAAK,UAAS,cAAW,eAAc,OAAO,EAAE,SAAS,WAAW,YAAY,uBAAuB,UAAU,YAAY,OAAO,4BAA4B,WAAW,QAAQ,QAAQ,EAAE,GACtN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAWO;AAAA,WACF;AAAA,QAEA,qBAAC,aAAU,MAAK,OAAM,OAAM,mCAAkC;AAAA;AAAA,UAC5C,oBAAC,eAAY,QAAO,aAAY,oBAAM;AAAA,UAAc;AAAA,WAEtE;AAAA,QAEA,oBAAC,OAAE,OAAO,EAAE,WAAW,SAAS,GAAG,+KAGnC;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,25 @@
1
+ // src/components/blog/author.ts
2
+ var DEFAULT_AUTHOR = {
3
+ name: "Ryan Kelly",
4
+ description: "ryankelly.dev \u2022 Systems Architecture & Brutalist UI"
5
+ };
6
+ function deriveInitials(name) {
7
+ const words = name.trim().split(/\s+/).filter(Boolean);
8
+ if (words.length === 0) return "";
9
+ const first = (word) => Array.from(word)[0] ?? "";
10
+ const letters = words.length === 1 ? first(words[0]) : first(words[0]) + first(words[words.length - 1]);
11
+ return letters.toUpperCase();
12
+ }
13
+ function resolveAuthor(author) {
14
+ if (author === void 0) return DEFAULT_AUTHOR;
15
+ if (typeof author === "string") {
16
+ return author === DEFAULT_AUTHOR.name ? DEFAULT_AUTHOR : { name: author };
17
+ }
18
+ return author;
19
+ }
20
+ export {
21
+ DEFAULT_AUTHOR,
22
+ deriveInitials,
23
+ resolveAuthor
24
+ };
25
+ //# sourceMappingURL=author.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/blog/author.ts"],"sourcesContent":["import type { ReactNode } from 'react';\n\n/** Who wrote a post, as `BlogPost`'s byline and author card display it. */\nexport interface BlogAuthor {\n /** Shown in the byline row and after \"Written by\" in the author card. */\n name: string;\n /**\n * The letters in the author card's square. Derived from `name` when absent:\n * the first letter of the first word and of the last word, so a one-word\n * name gives one letter and a three-word name skips the middle one.\n */\n initials?: string;\n /**\n * Replaces the initials square when present — normally an `Avatar` with the\n * author's picture. Use `size=\"sm\"` to keep the card's 40px rhythm.\n */\n avatar?: ReactNode;\n /**\n * Where the name in the author card links to. The name is plain text when\n * absent. Internal hrefs go through the `LinkProvider`'s router link.\n */\n url?: string;\n /** The line under the name in the author card. Omitted when absent. */\n description?: string;\n}\n\n// The byline of the site this package was built for. It lives here rather\n// than in `BlogPost.tsx` so the component holds no author of its own: every\n// field below is what a consumer who passes nothing gets, and each one is\n// overridable through `BlogPost`'s `author` prop.\n//\n// `initials` is left to derivation, which gives `RK`, and there is no `url`:\n// the card has never linked the name, and a link would move the default's\n// rendering.\nexport const DEFAULT_AUTHOR: BlogAuthor = {\n name: 'Ryan Kelly',\n description: 'ryankelly.dev • Systems Architecture & Brutalist UI',\n};\n\n// First letter of the first word, plus first letter of the last word when\n// there is more than one: `Ryan Kelly` is `RK`, `Prince` is `P`, `Ada King\n// Lovelace` is `AL`. Words split on whitespace only, so `Jean-Luc Picard` is\n// `JP`. Letters are taken by code point rather than UTF-16 unit, so a name\n// beginning outside the BMP is not cut in half. An empty name gives `''`.\n// Uppercased without a locale, so a server and the browser hydrating its\n// markup agree whatever their locales are: `ipek` is `I`, never Turkish `İ`.\nexport function deriveInitials(name: string): string {\n const words = name.trim().split(/\\s+/).filter(Boolean);\n if (words.length === 0) return '';\n const first = (word: string) => Array.from(word)[0] ?? '';\n const letters =\n words.length === 1 ? first(words[0]) : first(words[0]) + first(words[words.length - 1]);\n return letters.toUpperCase();\n}\n\n// A string is a name. Naming the default author gets the default author\n// whole, so `author=\"Ryan Kelly\"` renders exactly as omitting the prop does;\n// any other name gets only itself, so a guest is never given the default's\n// description.\nexport function resolveAuthor(author: string | BlogAuthor | undefined): BlogAuthor {\n if (author === undefined) return DEFAULT_AUTHOR;\n if (typeof author === 'string') {\n return author === DEFAULT_AUTHOR.name ? DEFAULT_AUTHOR : { name: author };\n }\n return author;\n}\n"],"mappings":";AAkCO,IAAM,iBAA6B;AAAA,EACxC,MAAM;AAAA,EACN,aAAa;AACf;AASO,SAAS,eAAe,MAAsB;AACnD,QAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AACrD,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,QAAQ,CAAC,SAAiB,MAAM,KAAK,IAAI,EAAE,CAAC,KAAK;AACvD,QAAM,UACJ,MAAM,WAAW,IAAI,MAAM,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,MAAM,SAAS,CAAC,CAAC;AACxF,SAAO,QAAQ,YAAY;AAC7B;AAMO,SAAS,cAAc,QAAqD;AACjF,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI,OAAO,WAAW,UAAU;AAC9B,WAAO,WAAW,eAAe,OAAO,iBAAiB,EAAE,MAAM,OAAO;AAAA,EAC1E;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,50 @@
1
+ // src/components/booleanControl.ts
2
+ import { recipe } from "../lib/recipe.js";
3
+ var booleanControl = recipe({
4
+ slots: {
5
+ control: "flex shrink-0 border-2 border-edge-strong bg-surface-base transition-colors duration-quick ease-brutalist data-[disabled]:cursor-not-allowed data-[disabled]:border-edge-subtle data-[disabled]:bg-surface-sunken data-[invalid]:border-intent-danger motion-reduce:transition-none",
6
+ mark: "pointer-events-none"
7
+ },
8
+ variants: {
9
+ shape: {
10
+ // A square box with a mark in it: the checkbox.
11
+ //
12
+ // Disabled and checked are both attribute variants, so which background
13
+ // won used to be CSS source order — and it was the disabled one, leaving
14
+ // `text-content-inverse`, an ink chosen for the accent fill, nearly
15
+ // invisible on `surface-sunken` (#270). The compound selectors below make
16
+ // the disabled surface win on purpose, and the mark drops to
17
+ // `text-content-muted`, the pair `check:contrast` verifies on every
18
+ // surface. That is how `dot` already handles it: the state stays legible,
19
+ // only the emphasis goes.
20
+ box: {
21
+ control: "size-6 cursor-pointer items-center justify-center data-[checked]:bg-[var(--field-accent)] data-[indeterminate]:bg-[var(--field-accent)] data-[checked]:data-[disabled]:bg-surface-sunken data-[indeterminate]:data-[disabled]:bg-surface-sunken",
22
+ mark: "font-mono text-sm font-bold leading-none text-content-inverse data-[disabled]:text-content-muted"
23
+ },
24
+ /**
25
+ * The same box holding a solid square: the radio. One option of a set
26
+ * is a boolean too — selected or not — so it wears this surface rather
27
+ * than a third one.
28
+ *
29
+ * Square, not round, because the radius is zero everywhere (DESIGN.md)
30
+ * and a circle would be the one exception. What tells it from a checkbox
31
+ * is the mark instead: a checkbox *fills* with the accent and carries an
32
+ * `X`; a radio stays on the base surface and carries a smaller solid
33
+ * square, so a set of them never reads as a set of ticks.
34
+ */
35
+ dot: {
36
+ control: "size-6 cursor-pointer items-center justify-center",
37
+ mark: "size-2.5 bg-[var(--field-accent)] data-[disabled]:bg-content-muted"
38
+ },
39
+ /** A track the thumb travels along: the switch. */
40
+ track: {
41
+ control: "h-6 w-12 cursor-pointer items-center p-0.5 data-[checked]:bg-[var(--field-accent)]",
42
+ mark: "size-4 bg-content-primary transition-transform duration-quick ease-brutalist data-[checked]:translate-x-6 data-[checked]:bg-content-inverse data-[disabled]:bg-content-muted motion-reduce:transition-none"
43
+ }
44
+ }
45
+ }
46
+ });
47
+ export {
48
+ booleanControl
49
+ };
50
+ //# sourceMappingURL=booleanControl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/booleanControl.ts"],"sourcesContent":["import { recipe } from '../lib/recipe';\n\n/**\n * The surface a boolean control wears, in its two presentations.\n *\n * `Checkbox` and `Switch` are one capability — a boolean — drawn twice (and\n * `Radio` is one option of a set, drawn a third time), so the\n * border weight, the checked fill, the disabled treatment and the invalid\n * border are declared **once** and selected by `shape`. Two recipes would\n * drift, and the drift would show up as a switch that disagrees with the\n * checkbox beside it about what \"disabled\" looks like.\n *\n * A module of its own for the reason `dialogSurface.ts` gives: `src/index.ts`\n * re-exports the component files wholesale, and a `recipe` in the published\n * surface puts `tailwind-variants`' types back into the `.d.ts`.\n *\n * Every state here is a Base UI data attribute — `data-checked`,\n * `data-indeterminate`, `data-disabled`, `data-invalid` — so nothing in the\n * components computes a class from a prop. `data-invalid` in particular is\n * inherited from the enclosing `Field.Root`, which is what makes an error on\n * the field visible on the control without either being told about the other.\n *\n * No focus class. The global `:focus-visible` outline in `styles.css` is the\n * ring, and a component-level override is exactly the #44 regression that rule\n * exists to prevent.\n */\nexport const booleanControl = recipe({\n slots: {\n control:\n 'flex shrink-0 border-2 border-edge-strong bg-surface-base transition-colors duration-quick ease-brutalist '\n + 'data-[disabled]:cursor-not-allowed data-[disabled]:border-edge-subtle data-[disabled]:bg-surface-sunken '\n + 'data-[invalid]:border-intent-danger motion-reduce:transition-none',\n mark: 'pointer-events-none',\n },\n variants: {\n shape: {\n // A square box with a mark in it: the checkbox.\n //\n // Disabled and checked are both attribute variants, so which background\n // won used to be CSS source order — and it was the disabled one, leaving\n // `text-content-inverse`, an ink chosen for the accent fill, nearly\n // invisible on `surface-sunken` (#270). The compound selectors below make\n // the disabled surface win on purpose, and the mark drops to\n // `text-content-muted`, the pair `check:contrast` verifies on every\n // surface. That is how `dot` already handles it: the state stays legible,\n // only the emphasis goes.\n box: {\n control:\n 'size-6 cursor-pointer items-center justify-center '\n + 'data-[checked]:bg-[var(--field-accent)] data-[indeterminate]:bg-[var(--field-accent)] '\n + 'data-[checked]:data-[disabled]:bg-surface-sunken '\n + 'data-[indeterminate]:data-[disabled]:bg-surface-sunken',\n mark:\n 'font-mono text-sm font-bold leading-none text-content-inverse '\n + 'data-[disabled]:text-content-muted',\n },\n /**\n * The same box holding a solid square: the radio. One option of a set\n * is a boolean too — selected or not — so it wears this surface rather\n * than a third one.\n *\n * Square, not round, because the radius is zero everywhere (DESIGN.md)\n * and a circle would be the one exception. What tells it from a checkbox\n * is the mark instead: a checkbox *fills* with the accent and carries an\n * `X`; a radio stays on the base surface and carries a smaller solid\n * square, so a set of them never reads as a set of ticks.\n */\n dot: {\n control: 'size-6 cursor-pointer items-center justify-center',\n mark: 'size-2.5 bg-[var(--field-accent)] data-[disabled]:bg-content-muted',\n },\n /** A track the thumb travels along: the switch. */\n track: {\n control:\n 'h-6 w-12 cursor-pointer items-center p-0.5 '\n + 'data-[checked]:bg-[var(--field-accent)]',\n mark:\n 'size-4 bg-content-primary transition-transform duration-quick ease-brutalist '\n + 'data-[checked]:translate-x-6 data-[checked]:bg-content-inverse '\n + 'data-[disabled]:bg-content-muted motion-reduce:transition-none',\n },\n },\n },\n});\n"],"mappings":";AAAA,SAAS,cAAc;AA0BhB,IAAM,iBAAiB,OAAO;AAAA,EACnC,OAAO;AAAA,IACL,SACE;AAAA,IAGF,MAAM;AAAA,EACR;AAAA,EACA,UAAU;AAAA,IACR,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWL,KAAK;AAAA,QACH,SACE;AAAA,QAIF,MACE;AAAA,MAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYA,KAAK;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA;AAAA,MAEA,OAAO;AAAA,QACL,SACE;AAAA,QAEF,MACE;AAAA,MAGJ;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":[]}
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ // src/components/dialogPopupRef.ts
4
+ import { useCallback, useRef } from "react";
5
+ function assignRef(ref, node) {
6
+ if (typeof ref === "function") {
7
+ ref(node);
8
+ return;
9
+ }
10
+ if (ref) {
11
+ ref.current = node;
12
+ }
13
+ }
14
+ function usePopupRef(forwarded) {
15
+ const popup = useRef(null);
16
+ const attach = useCallback(
17
+ (node) => {
18
+ popup.current = node;
19
+ assignRef(forwarded, node);
20
+ },
21
+ [forwarded]
22
+ );
23
+ return [popup, attach];
24
+ }
25
+ export {
26
+ usePopupRef
27
+ };
28
+ //# sourceMappingURL=dialogPopupRef.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/dialogPopupRef.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useRef } from 'react';\nimport type { ForwardedRef } from 'react';\n\n/**\n * The ref plumbing every dialog surface needs, in one place.\n *\n * A module of its own for the reason `dialogSurface.ts` is one: `src/index.ts`\n * re-exports `Modal.tsx` and `Drawer.tsx` wholesale, so a helper living in\n * either would be published — and a hook in the `.d.ts` is API, whatever it\n * was written for. Nothing exports this file, so it stays internal.\n *\n * It was `Modal`'s, verbatim, until `Drawer` needed the same thing. Copying it\n * would have meant two `initialFocus` races to fix the day one of them is\n * found.\n */\n\n/**\n * Module scope on purpose: written inline, the assignment reads to the\n * compiler lint as a component mutating a value it captured during render.\n */\nfunction assignRef<T>(ref: ForwardedRef<T>, node: T | null) {\n if (typeof ref === 'function') {\n ref(node);\n return;\n }\n if (ref) {\n ref.current = node;\n }\n}\n\n/**\n * Keeps a local handle on the popup while still honouring a caller's ref.\n *\n * `initialFocus` needs the popup *element*, and the only ref slot Base UI\n * offers is the one the caller may also have asked for. Merging them here is\n * cheaper than making the caller give theirs up.\n */\nexport function usePopupRef(forwarded: ForwardedRef<HTMLDivElement>) {\n const popup = useRef<HTMLDivElement | null>(null);\n\n const attach = useCallback(\n (node: HTMLDivElement | null) => {\n popup.current = node;\n assignRef(forwarded, node);\n },\n [forwarded],\n );\n\n return [popup, attach] as const;\n}\n"],"mappings":";;;AAEA,SAAS,aAAa,cAAc;AAoBpC,SAAS,UAAa,KAAsB,MAAgB;AAC1D,MAAI,OAAO,QAAQ,YAAY;AAC7B,QAAI,IAAI;AACR;AAAA,EACF;AACA,MAAI,KAAK;AACP,QAAI,UAAU;AAAA,EAChB;AACF;AASO,SAAS,YAAY,WAAyC;AACnE,QAAM,QAAQ,OAA8B,IAAI;AAEhD,QAAM,SAAS;AAAA,IACb,CAAC,SAAgC;AAC/B,YAAM,UAAU;AAChB,gBAAU,WAAW,IAAI;AAAA,IAC3B;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,SAAO,CAAC,OAAO,MAAM;AACvB;","names":[]}
@@ -0,0 +1,75 @@
1
+ // src/components/dialogSurface.ts
2
+ import { recipe } from "../lib/recipe.js";
3
+ var dialogSurface = recipe({
4
+ slots: {
5
+ /*
6
+ * Enter and exit are `data-starting-style` / `data-ending-style` rather
7
+ * than keyframes: Base UI sets them for one frame either side of the
8
+ * transition, so an opacity pair and a duration is the whole animation.
9
+ * `motion-reduce` drops it — an overlay appearing is exactly the motion
10
+ * `prefers-reduced-motion` is asking not to see.
11
+ */
12
+ backdrop: "fixed inset-0 z-top bg-surface-overlay transition-opacity duration-quick ease-brutalist data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 motion-reduce:transition-none",
13
+ viewport: "fixed inset-0 z-top flex items-center justify-center p-4",
14
+ /*
15
+ * `focus-visible:outline-none!` on the popup and nowhere else. Focus lands
16
+ * on this element so a screen reader announces the dialog's own title
17
+ * rather than its close button — it is an announcement target, not a
18
+ * keyboard stop, and the global `:focus-visible` rule in styles.css would
19
+ * otherwise draw the accent ring around the entire surface. The
20
+ * hand-rolled container this replaced took focus the same way and painted
21
+ * no ring; Base UI's focus call matches `:focus-visible`, so the ring has
22
+ * to be turned off here instead. Controls inside the dialog keep theirs.
23
+ *
24
+ * The `!` is load-bearing, and the only one in the package. That rule is
25
+ * unlayered on purpose — styles.css says keyboard focus must stay visible
26
+ * even where a component restyles it — so it outranks Tailwind's
27
+ * `@layer utilities` regardless of specificity. Important is the sanctioned
28
+ * way past it, and the narrow scope is what makes it safe: one slot, one
29
+ * element that no keyboard user can Tab to.
30
+ */
31
+ popup: "max-h-[90vh] w-full max-w-lg overflow-y-auto border-4 border-edge-strong bg-surface-raised focus-visible:outline-none! font-mono shadow-hard-lg transition-opacity duration-quick ease-brutalist data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 motion-reduce:transition-none",
32
+ header: "flex items-center justify-between border-b-2 border-edge-strong bg-surface-base px-6 py-4",
33
+ title: "font-display text-xl font-bold uppercase tracking-wider text-content-primary",
34
+ close: "border-2 border-edge-strong bg-surface-raised px-2 font-mono text-lg font-bold text-content-primary transition-colors hover:bg-surface-base hover:text-accent-tertiary focus-visible:ring-2 focus-visible:ring-accent-primary",
35
+ body: "p-6 font-sans text-sm leading-relaxed text-content-primary",
36
+ footer: "flex justify-end gap-3 border-t-2 border-edge-strong bg-surface-base px-6 py-4"
37
+ },
38
+ /*
39
+ * Placement, and nothing else.
40
+ *
41
+ * There is deliberately no `centred` value and no `defaultVariants`. `Modal`
42
+ * and `AlertDialog` call `dialogSurface()` with no argument and get exactly
43
+ * the class strings above, unchanged — a default variant would have appended
44
+ * classes to both and made two components' rendering depend on an edit made
45
+ * for a third.
46
+ *
47
+ * `top` and `bottom` are absent for the reason #241 states: no consumer asks
48
+ * for them, and an edge nobody anchors to is surface that still has to be
49
+ * themed, screenshotted and kept working.
50
+ */
51
+ variants: {
52
+ placement: {
53
+ left: {
54
+ // `items-stretch` and `justify-start` beat the base's centring, and
55
+ // `p-0` its gutter: a drawer meets three edges of the viewport, so the
56
+ // padding that keeps a modal off the edges is exactly wrong here.
57
+ viewport: "items-stretch justify-start p-0",
58
+ popup: "flex h-full max-h-full max-w-sm flex-col overflow-y-hidden border-y-0 border-l-0 border-r-4 transition data-[starting-style]:-translate-x-full data-[ending-style]:-translate-x-full",
59
+ // The panel is as tall as the viewport, so the body takes the slack
60
+ // and scrolls inside it. Scrolling the whole popup instead would take
61
+ // the title and the close control off-screen with the content.
62
+ body: "flex-1 overflow-y-auto"
63
+ },
64
+ right: {
65
+ viewport: "items-stretch justify-end p-0",
66
+ popup: "flex h-full max-h-full max-w-sm flex-col overflow-y-hidden border-y-0 border-r-0 border-l-4 shadow-none transition data-[starting-style]:translate-x-full data-[ending-style]:translate-x-full",
67
+ body: "flex-1 overflow-y-auto"
68
+ }
69
+ }
70
+ }
71
+ });
72
+ export {
73
+ dialogSurface
74
+ };
75
+ //# sourceMappingURL=dialogSurface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/dialogSurface.ts"],"sourcesContent":["import { recipe } from '../lib/recipe';\n\n/**\n * The chrome both dialogs wear.\n *\n * A module of its own, not a shared export from `Modal.tsx`: `src/index.ts`\n * re-exports that file wholesale, and a `recipe` in the published surface puts\n * `tailwind-variants`' types back into the `.d.ts` — which `lib/recipe.ts`\n * exists to prevent, since it would make swapping the engine a breaking\n * change. Nothing exports this one, so the types stay in.\n *\n * `Modal` and `AlertDialog` are the same surface with different dismissal\n * contracts, so the surface is declared once. Two recipes would drift, and the\n * drift would show up as a confirmation dialog that does not look like the\n * dialog it was opened from.\n *\n * `Drawer` is the third caller and the reason `placement` exists. A drawer is\n * a dialog anchored to an edge — the same chrome, a different position and a\n * different transition — so it takes a variant here rather than a recipe of\n * its own. The header, title, close, body and footer slots are shared\n * verbatim, which is the point: a drawer that does not look like the modal it\n * sits beside is the drift a second recipe would have produced.\n *\n * `overlay` and `popup` both sit on `z-top`: the viewport follows the backdrop\n * in DOM order, so it paints above it without a second stacking value. The\n * layer comes from the `--ds-layer-*` scale either way — the `z-50` this\n * component used to hardcode is the drift the token layer exists to prevent.\n */\nexport const dialogSurface = recipe({\n slots: {\n /*\n * Enter and exit are `data-starting-style` / `data-ending-style` rather\n * than keyframes: Base UI sets them for one frame either side of the\n * transition, so an opacity pair and a duration is the whole animation.\n * `motion-reduce` drops it — an overlay appearing is exactly the motion\n * `prefers-reduced-motion` is asking not to see.\n */\n backdrop:\n 'fixed inset-0 z-top bg-surface-overlay transition-opacity duration-quick ease-brutalist '\n + 'data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 motion-reduce:transition-none',\n viewport: 'fixed inset-0 z-top flex items-center justify-center p-4',\n /*\n * `focus-visible:outline-none!` on the popup and nowhere else. Focus lands\n * on this element so a screen reader announces the dialog's own title\n * rather than its close button — it is an announcement target, not a\n * keyboard stop, and the global `:focus-visible` rule in styles.css would\n * otherwise draw the accent ring around the entire surface. The\n * hand-rolled container this replaced took focus the same way and painted\n * no ring; Base UI's focus call matches `:focus-visible`, so the ring has\n * to be turned off here instead. Controls inside the dialog keep theirs.\n *\n * The `!` is load-bearing, and the only one in the package. That rule is\n * unlayered on purpose — styles.css says keyboard focus must stay visible\n * even where a component restyles it — so it outranks Tailwind's\n * `@layer utilities` regardless of specificity. Important is the sanctioned\n * way past it, and the narrow scope is what makes it safe: one slot, one\n * element that no keyboard user can Tab to.\n */\n popup:\n 'max-h-[90vh] w-full max-w-lg overflow-y-auto border-4 border-edge-strong bg-surface-raised '\n + 'focus-visible:outline-none! '\n + 'font-mono shadow-hard-lg transition-opacity duration-quick ease-brutalist '\n + 'data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 motion-reduce:transition-none',\n header:\n 'flex items-center justify-between border-b-2 border-edge-strong bg-surface-base px-6 py-4',\n title:\n 'font-display text-xl font-bold uppercase tracking-wider text-content-primary',\n close:\n 'border-2 border-edge-strong bg-surface-raised px-2 font-mono text-lg 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 body: 'p-6 font-sans text-sm leading-relaxed text-content-primary',\n footer:\n 'flex justify-end gap-3 border-t-2 border-edge-strong bg-surface-base px-6 py-4',\n },\n /*\n * Placement, and nothing else.\n *\n * There is deliberately no `centred` value and no `defaultVariants`. `Modal`\n * and `AlertDialog` call `dialogSurface()` with no argument and get exactly\n * the class strings above, unchanged — a default variant would have appended\n * classes to both and made two components' rendering depend on an edit made\n * for a third.\n *\n * `top` and `bottom` are absent for the reason #241 states: no consumer asks\n * for them, and an edge nobody anchors to is surface that still has to be\n * themed, screenshotted and kept working.\n */\n variants: {\n placement: {\n left: {\n // `items-stretch` and `justify-start` beat the base's centring, and\n // `p-0` its gutter: a drawer meets three edges of the viewport, so the\n // padding that keeps a modal off the edges is exactly wrong here.\n viewport: 'items-stretch justify-start p-0',\n popup:\n 'flex h-full max-h-full max-w-sm flex-col overflow-y-hidden '\n + 'border-y-0 border-l-0 border-r-4 '\n // `transition`, not the base's `transition-opacity`: the drawer moves\n // as well as fades, and a translate with no transition on it would\n // land instantly and leave only the fade visible.\n + 'transition data-[starting-style]:-translate-x-full data-[ending-style]:-translate-x-full',\n // The panel is as tall as the viewport, so the body takes the slack\n // and scrolls inside it. Scrolling the whole popup instead would take\n // the title and the close control off-screen with the content.\n body: 'flex-1 overflow-y-auto',\n },\n right: {\n viewport: 'items-stretch justify-end p-0',\n popup:\n 'flex h-full max-h-full max-w-sm flex-col overflow-y-hidden '\n + 'border-y-0 border-r-0 border-l-4 '\n // The hard shadow falls down and to the right, so on a drawer flush\n // with the right edge it paints off-screen and draws nothing. The\n // 4px inner border is the separation instead. `shadow-none` clears it\n // only because `lib/recipe.ts` declares the hard-shadow scale to the\n // merger — the case its `mergeConfig` comment calls out.\n + 'shadow-none '\n + 'transition data-[starting-style]:translate-x-full data-[ending-style]:translate-x-full',\n body: 'flex-1 overflow-y-auto',\n },\n },\n },\n});\n"],"mappings":";AAAA,SAAS,cAAc;AA4BhB,IAAM,gBAAgB,OAAO;AAAA,EAClC,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQL,UACE;AAAA,IAEF,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAkBV,OACE;AAAA,IAIF,QACE;AAAA,IACF,OACE;AAAA,IACF,OACE;AAAA,IAGF,MAAM;AAAA,IACN,QACE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,UAAU;AAAA,IACR,WAAW;AAAA,MACT,MAAM;AAAA;AAAA;AAAA;AAAA,QAIJ,UAAU;AAAA,QACV,OACE;AAAA;AAAA;AAAA;AAAA,QASF,MAAM;AAAA,MACR;AAAA,MACA,OAAO;AAAA,QACL,UAAU;AAAA,QACV,OACE;AAAA,QASF,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":[]}
@@ -0,0 +1,80 @@
1
+ "use client";
2
+
3
+ // src/components/docs/AnchorHeading.tsx
4
+ import { useCallback, useMemo } from "react";
5
+ import { Check, Link2 } from "lucide-react";
6
+ import { useCopyToClipboard } from "../../hooks/useCopyToClipboard.js";
7
+ import { childrenToText, slugify } from "../../lib/slug.js";
8
+ import { HEADING_EMPHASIS, accentVar } from "../../lib/theme.js";
9
+ import { cn } from "../../lib/recipe.js";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
+ function AnchorHeading({
12
+ level,
13
+ children,
14
+ id,
15
+ anchor = true,
16
+ emphasis,
17
+ className = "",
18
+ ...rest
19
+ }) {
20
+ const Tag = `h${level}`;
21
+ const { copied, copy } = useCopyToClipboard();
22
+ const headingId = useMemo(
23
+ () => id ?? slugify(childrenToText(children)),
24
+ [id, children]
25
+ );
26
+ const onCopyLink = useCallback(
27
+ (event) => {
28
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
29
+ event.preventDefault();
30
+ const url = typeof window === "undefined" ? `#${headingId}` : `${window.location.origin}${window.location.pathname}${window.location.search}#${headingId}`;
31
+ void copy(url);
32
+ if (typeof window !== "undefined") {
33
+ window.history.replaceState(null, "", `#${headingId}`);
34
+ document.getElementById(headingId)?.scrollIntoView({ behavior: "smooth" });
35
+ }
36
+ },
37
+ [copy, headingId]
38
+ );
39
+ const color = accentVar(emphasis, HEADING_EMPHASIS[level]);
40
+ return /* @__PURE__ */ jsxs(
41
+ Tag,
42
+ {
43
+ "data-slot": "anchor-heading",
44
+ id: headingId,
45
+ className: cn("docs-anchor-heading group", className),
46
+ ...rest,
47
+ children: [
48
+ children,
49
+ anchor && /* @__PURE__ */ jsx(
50
+ "a",
51
+ {
52
+ href: `#${headingId}`,
53
+ onClick: onCopyLink,
54
+ className: "docs-anchor-link",
55
+ style: { color },
56
+ "aria-label": copied ? "Link copied" : "Copy link to this section",
57
+ title: copied ? "Copied!" : "Copy link to this section",
58
+ children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
59
+ /* @__PURE__ */ jsx(Check, { size: 14, "aria-hidden": "true" }),
60
+ /* @__PURE__ */ jsx("span", { children: "COPIED" })
61
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
62
+ /* @__PURE__ */ jsx(Link2, { size: 14, "aria-hidden": "true" }),
63
+ /* @__PURE__ */ jsx("span", { children: "COPY LINK" })
64
+ ] })
65
+ }
66
+ )
67
+ ]
68
+ }
69
+ );
70
+ }
71
+ function createAnchorHeading(level) {
72
+ const Heading = ({ children, id, ...rest }) => /* @__PURE__ */ jsx(AnchorHeading, { level, id, ...rest, children });
73
+ Heading.displayName = `AnchorHeading${level}`;
74
+ return Heading;
75
+ }
76
+ export {
77
+ AnchorHeading,
78
+ createAnchorHeading
79
+ };
80
+ //# sourceMappingURL=AnchorHeading.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/docs/AnchorHeading.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useMemo } from 'react';\nimport type { HTMLAttributes, MouseEvent, ReactNode } from 'react';\nimport { Check, Link2 } from 'lucide-react';\nimport { useCopyToClipboard } from '../../hooks/useCopyToClipboard';\nimport { childrenToText, slugify } from '../../lib/slug';\nimport { HEADING_EMPHASIS, accentVar } from '../../lib/theme';\nimport type { AccentToken } from '../../lib/theme';\nimport { cn } from '../../lib/recipe';\n\nexport type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\nexport interface AnchorHeadingProps\n extends Omit<HTMLAttributes<HTMLHeadingElement>, 'id'> {\n level: HeadingLevel;\n children: ReactNode;\n /**\n * Explicit anchor id. Prefer passing this — build-time sluggers\n * (`rehype-slug`) deduplicate repeated headings across the whole document,\n * which a per-heading component cannot see. Omit it only for one-off headings\n * outside a compiled MDX page.\n */\n id?: string;\n /** Hide the copy-link affordance (e.g. a page title that is not a section). */\n anchor?: boolean;\n /**\n * Emphasis of the anchor affordance. Defaults to the level's place in\n * {@link HEADING_EMPHASIS}, so colour hierarchy tracks document hierarchy\n * without every heading restating it.\n */\n emphasis?: AccentToken;\n}\n\n/**\n * A section heading that owns its own anchor.\n *\n * Every heading gets a stable `#slug` id so any section is directly linkable,\n * and the trailing affordance copies the *absolute* url including that hash —\n * the thing people actually want to paste into a ticket or a chat. Clicking\n * also updates `location.hash` via `history.replaceState`, so the address bar\n * agrees with what was copied without pushing a history entry per click.\n *\n * `scroll-margin-top` is applied through the `docs-anchor-heading` class in\n * `prose.css` rather than inline, so a sticky header can retune the offset by\n * setting `--docs-header-height` without this component re-rendering.\n */\nexport function AnchorHeading({\n level,\n children,\n id,\n anchor = true,\n emphasis,\n className = '',\n ...rest\n}: AnchorHeadingProps) {\n const Tag = `h${level}` as const;\n const { copied, copy } = useCopyToClipboard();\n\n const headingId = useMemo(\n () => id ?? slugify(childrenToText(children)),\n [id, children],\n );\n\n const onCopyLink = useCallback(\n (event: MouseEvent<HTMLAnchorElement>) => {\n // Let modified clicks behave like a normal link (new tab, download, …).\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n event.preventDefault();\n\n const url =\n typeof window === 'undefined'\n ? `#${headingId}`\n : `${window.location.origin}${window.location.pathname}${window.location.search}#${headingId}`;\n\n void copy(url);\n\n if (typeof window !== 'undefined') {\n window.history.replaceState(null, '', `#${headingId}`);\n document.getElementById(headingId)?.scrollIntoView({ behavior: 'smooth' });\n }\n },\n [copy, headingId],\n );\n\n const color = accentVar(emphasis, HEADING_EMPHASIS[level]);\n\n return (\n <Tag\n data-slot=\"anchor-heading\"\n id={headingId}\n className={cn('docs-anchor-heading group', className)}\n {...rest}\n >\n {children}\n {anchor && (\n <a\n href={`#${headingId}`}\n onClick={onCopyLink}\n className=\"docs-anchor-link\"\n style={{ color }}\n aria-label={copied ? 'Link copied' : 'Copy link to this section'}\n title={copied ? 'Copied!' : 'Copy link to this section'}\n >\n {copied ? (\n <>\n <Check size={14} aria-hidden=\"true\" />\n <span>COPIED</span>\n </>\n ) : (\n <>\n <Link2 size={14} aria-hidden=\"true\" />\n <span>COPY LINK</span>\n </>\n )}\n </a>\n )}\n </Tag>\n );\n}\n\n/**\n * Factory for the `h1`–`h6` entries of an MDX component map. The compiled MDX\n * supplies `id` (from `rehype-slug`) and `children`; everything else is fixed\n * per level.\n */\nexport function createAnchorHeading(level: HeadingLevel) {\n const Heading = ({ children, id, ...rest }: HTMLAttributes<HTMLHeadingElement>) => (\n <AnchorHeading level={level} id={id} {...rest}>\n {children}\n </AnchorHeading>\n );\n Heading.displayName = `AnchorHeading${level}`;\n return Heading;\n}\n"],"mappings":";;;AAEA,SAAS,aAAa,eAAe;AAErC,SAAS,OAAO,aAAa;AAC7B,SAAS,0BAA0B;AACnC,SAAS,gBAAgB,eAAe;AACxC,SAAS,kBAAkB,iBAAiB;AAE5C,SAAS,UAAU;AAgGP,mBACE,KADF;AA1DL,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA,YAAY;AAAA,EACZ,GAAG;AACL,GAAuB;AACrB,QAAM,MAAM,IAAI,KAAK;AACrB,QAAM,EAAE,QAAQ,KAAK,IAAI,mBAAmB;AAE5C,QAAM,YAAY;AAAA,IAChB,MAAM,MAAM,QAAQ,eAAe,QAAQ,CAAC;AAAA,IAC5C,CAAC,IAAI,QAAQ;AAAA,EACf;AAEA,QAAM,aAAa;AAAA,IACjB,CAAC,UAAyC;AAExC,UAAI,MAAM,WAAW,MAAM,WAAW,MAAM,YAAY,MAAM,OAAQ;AACtE,YAAM,eAAe;AAErB,YAAM,MACJ,OAAO,WAAW,cACd,IAAI,SAAS,KACb,GAAG,OAAO,SAAS,MAAM,GAAG,OAAO,SAAS,QAAQ,GAAG,OAAO,SAAS,MAAM,IAAI,SAAS;AAEhG,WAAK,KAAK,GAAG;AAEb,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,QAAQ,aAAa,MAAM,IAAI,IAAI,SAAS,EAAE;AACrD,iBAAS,eAAe,SAAS,GAAG,eAAe,EAAE,UAAU,SAAS,CAAC;AAAA,MAC3E;AAAA,IACF;AAAA,IACA,CAAC,MAAM,SAAS;AAAA,EAClB;AAEA,QAAM,QAAQ,UAAU,UAAU,iBAAiB,KAAK,CAAC;AAEzD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,IAAI;AAAA,MACJ,WAAW,GAAG,6BAA6B,SAAS;AAAA,MACnD,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,UACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAM,IAAI,SAAS;AAAA,YACnB,SAAS;AAAA,YACT,WAAU;AAAA,YACV,OAAO,EAAE,MAAM;AAAA,YACf,cAAY,SAAS,gBAAgB;AAAA,YACrC,OAAO,SAAS,YAAY;AAAA,YAE3B,mBACC,iCACE;AAAA,kCAAC,SAAM,MAAM,IAAI,eAAY,QAAO;AAAA,cACpC,oBAAC,UAAK,oBAAM;AAAA,eACd,IAEA,iCACE;AAAA,kCAAC,SAAM,MAAM,IAAI,eAAY,QAAO;AAAA,cACpC,oBAAC,UAAK,uBAAS;AAAA,eACjB;AAAA;AAAA,QAEJ;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAOO,SAAS,oBAAoB,OAAqB;AACvD,QAAM,UAAU,CAAC,EAAE,UAAU,IAAI,GAAG,KAAK,MACvC,oBAAC,iBAAc,OAAc,IAAS,GAAG,MACtC,UACH;AAEF,UAAQ,cAAc,gBAAgB,KAAK;AAC3C,SAAO;AACT;","names":[]}
@@ -0,0 +1,26 @@
1
+ // src/components/docs/Breadcrumbs.tsx
2
+ import { Fragment } from "react";
3
+ import { DocsLink } from "./DocsLinkProvider.js";
4
+ import { cn } from "../../lib/recipe.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function Breadcrumbs({ items, separator = "/", className = "" }) {
7
+ if (items.length === 0) return null;
8
+ return /* @__PURE__ */ jsx("nav", { "data-slot": "breadcrumbs", className: cn("not-prose", className), "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "docs-breadcrumbs-list", children: items.map((crumb, index) => {
9
+ const isLast = index === items.length - 1;
10
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
11
+ /* @__PURE__ */ jsx("li", { children: crumb.href && !isLast ? /* @__PURE__ */ jsx(DocsLink, { href: crumb.href, className: "docs-breadcrumbs-link", children: crumb.label }) : /* @__PURE__ */ jsx(
12
+ "span",
13
+ {
14
+ className: "docs-breadcrumbs-current",
15
+ ...isLast ? { "aria-current": "page" } : {},
16
+ children: crumb.label
17
+ }
18
+ ) }),
19
+ !isLast && /* @__PURE__ */ jsx("li", { "aria-hidden": "true", className: "docs-breadcrumbs-sep", children: separator })
20
+ ] }, `${crumb.label}-${index}`);
21
+ }) }) });
22
+ }
23
+ export {
24
+ Breadcrumbs
25
+ };
26
+ //# sourceMappingURL=Breadcrumbs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/docs/Breadcrumbs.tsx"],"sourcesContent":["import { Fragment } from 'react';\nimport { DocsLink } from './DocsLinkProvider';\nimport { cn } from '../../lib/recipe';\n\nexport interface Crumb {\n label: string;\n /** Omit on the final crumb, or for grouping segments with no index page. */\n href?: string;\n}\n\nexport interface BreadcrumbsProps {\n items: readonly Crumb[];\n /** Separator glyph between crumbs. */\n separator?: string;\n className?: string;\n}\n\n/**\n * Path trail for nested documentation.\n *\n * The last crumb is always rendered as plain text and marked `aria-current`\n * even when it carries an `href` — self-links read as navigation to assistive\n * tech but go nowhere.\n */\nexport function Breadcrumbs({ items, separator = '/', className = '' }: BreadcrumbsProps) {\n if (items.length === 0) return null;\n\n return (\n <nav data-slot=\"breadcrumbs\" className={cn('not-prose', className)} aria-label=\"Breadcrumb\">\n <ol className=\"docs-breadcrumbs-list\">\n {items.map((crumb, index) => {\n const isLast = index === items.length - 1;\n\n return (\n <Fragment key={`${crumb.label}-${index}`}>\n <li>\n {crumb.href && !isLast ? (\n <DocsLink href={crumb.href} className=\"docs-breadcrumbs-link\">\n {crumb.label}\n </DocsLink>\n ) : (\n <span\n className=\"docs-breadcrumbs-current\"\n {...(isLast ? { 'aria-current': 'page' as const } : {})}\n >\n {crumb.label}\n </span>\n )}\n </li>\n {!isLast && (\n <li aria-hidden=\"true\" className=\"docs-breadcrumbs-sep\">\n {separator}\n </li>\n )}\n </Fragment>\n );\n })}\n </ol>\n </nav>\n );\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,UAAU;AAgCP,SAGM,KAHN;AAVL,SAAS,YAAY,EAAE,OAAO,YAAY,KAAK,YAAY,GAAG,GAAqB;AACxF,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SACE,oBAAC,SAAI,aAAU,eAAc,WAAW,GAAG,aAAa,SAAS,GAAG,cAAW,cAC7E,8BAAC,QAAG,WAAU,yBACX,gBAAM,IAAI,CAAC,OAAO,UAAU;AAC3B,UAAM,SAAS,UAAU,MAAM,SAAS;AAExC,WACE,qBAAC,YACC;AAAA,0BAAC,QACE,gBAAM,QAAQ,CAAC,SACd,oBAAC,YAAS,MAAM,MAAM,MAAM,WAAU,yBACnC,gBAAM,OACT,IAEA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACT,GAAI,SAAS,EAAE,gBAAgB,OAAgB,IAAI,CAAC;AAAA,UAEpD,gBAAM;AAAA;AAAA,MACT,GAEJ;AAAA,MACC,CAAC,UACA,oBAAC,QAAG,eAAY,QAAO,WAAU,wBAC9B,qBACH;AAAA,SAlBW,GAAG,MAAM,KAAK,IAAI,KAAK,EAoBtC;AAAA,EAEJ,CAAC,GACH,GACF;AAEJ;","names":[]}