livemark 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/README.md +3 -2
  2. package/build/actions/config/define.d.ts +6 -0
  3. package/build/actions/config/define.unit.js +8 -1
  4. package/build/actions/escape/copy.d.ts +11 -0
  5. package/build/actions/escape/copy.js +29 -0
  6. package/build/actions/escape/copy.unit.d.ts +1 -0
  7. package/build/actions/escape/copy.unit.js +37 -0
  8. package/build/actions/escape/list.d.ts +12 -0
  9. package/build/actions/escape/list.js +27 -0
  10. package/build/actions/escape/list.unit.d.ts +1 -0
  11. package/build/actions/escape/list.unit.js +36 -0
  12. package/build/actions/target/build.d.ts +9 -26
  13. package/build/actions/target/build.js +50 -71
  14. package/build/actions/target/build.unit.d.ts +1 -0
  15. package/build/actions/target/build.unit.js +76 -0
  16. package/build/actions/target/clear.d.ts +4 -0
  17. package/build/actions/target/clear.js +14 -0
  18. package/build/actions/target/clear.unit.d.ts +1 -0
  19. package/build/actions/target/clear.unit.js +23 -0
  20. package/build/commands/build.js +1 -2
  21. package/build/commands/clear.d.ts +5 -0
  22. package/build/commands/clear.js +15 -0
  23. package/build/commands/escape.d.ts +4 -3
  24. package/build/commands/escape.js +45 -48
  25. package/build/commands/preview.js +1 -2
  26. package/build/commands/start.js +1 -2
  27. package/build/components/Abbr.d.ts +1 -1
  28. package/build/components/Abbr.js +2 -4
  29. package/build/components/AnsiCode.d.ts +1 -1
  30. package/build/components/AnsiCode.js +2 -2
  31. package/build/components/Article.d.ts +2 -1
  32. package/build/components/Article.js +7 -14
  33. package/build/components/BackToTop.d.ts +1 -1
  34. package/build/components/Banner.d.ts +2 -2
  35. package/build/components/Banner.js +7 -3
  36. package/build/components/BlogIndex.d.ts +1 -1
  37. package/build/components/BlogIndex.js +2 -11
  38. package/build/components/Breadcrumbs.d.ts +1 -1
  39. package/build/components/Breadcrumbs.js +2 -3
  40. package/build/components/Callout.d.ts +1 -1
  41. package/build/components/Callout.js +5 -9
  42. package/build/components/Card.d.ts +1 -1
  43. package/build/components/Card.js +2 -3
  44. package/build/components/Cards.d.ts +1 -1
  45. package/build/components/ChangelogIndex.d.ts +1 -1
  46. package/build/components/ChangelogIndex.js +2 -11
  47. package/build/components/ChoiceList.d.ts +15 -0
  48. package/build/components/ChoiceList.js +13 -0
  49. package/build/components/CodeBlock.d.ts +1 -1
  50. package/build/components/CodeBlock.js +3 -16
  51. package/build/components/CodeTabs.d.ts +1 -1
  52. package/build/components/CodeTabs.js +2 -3
  53. package/build/components/Column.d.ts +1 -1
  54. package/build/components/Columns.d.ts +1 -1
  55. package/build/components/Content.d.ts +1 -1
  56. package/build/components/ContentTab.d.ts +1 -1
  57. package/build/components/ContentTabs.d.ts +1 -1
  58. package/build/components/ContentTabs.js +2 -3
  59. package/build/components/DefaultCatchBoundary.d.ts +1 -1
  60. package/build/components/DefaultCatchBoundary.js +5 -7
  61. package/build/components/DefinitionList.d.ts +3 -3
  62. package/build/components/Details.d.ts +1 -1
  63. package/build/components/Details.js +2 -5
  64. package/build/components/FileTree.d.ts +1 -1
  65. package/build/components/FileTree.js +3 -5
  66. package/build/components/Footer.d.ts +1 -1
  67. package/build/components/Footer.js +2 -5
  68. package/build/components/FootnoteRef.d.ts +1 -1
  69. package/build/components/FootnoteRef.js +2 -4
  70. package/build/components/Header.d.ts +6 -0
  71. package/build/components/Header.js +26 -0
  72. package/build/components/HeaderMenu.d.ts +2 -0
  73. package/build/components/HeaderMenu.js +27 -0
  74. package/build/components/HeaderNav.d.ts +6 -0
  75. package/build/components/HeaderNav.js +74 -0
  76. package/build/components/HeaderSearch.d.ts +4 -0
  77. package/build/components/HeaderSearch.js +33 -0
  78. package/build/components/Heading.d.ts +6 -6
  79. package/build/components/InlineBadge.d.ts +2 -2
  80. package/build/components/InlineBadge.js +1 -1
  81. package/build/components/InlineIcon.d.ts +1 -1
  82. package/build/components/InlineToc.d.ts +1 -1
  83. package/build/components/InlineToc.js +2 -4
  84. package/build/components/Layout.d.ts +2 -1
  85. package/build/components/Layout.js +5 -58
  86. package/build/components/LinkButton.d.ts +2 -2
  87. package/build/components/LinkButton.js +1 -1
  88. package/build/components/MenuSheet.d.ts +22 -0
  89. package/build/components/MenuSheet.js +27 -0
  90. package/build/components/Mermaid.d.ts +1 -1
  91. package/build/components/MermaidRenderer.d.ts +1 -1
  92. package/build/components/MermaidRenderer.js +20 -8
  93. package/build/components/NotFound.d.ts +1 -1
  94. package/build/components/NotFound.js +3 -6
  95. package/build/components/PackageTabs.d.ts +1 -1
  96. package/build/components/PackageTabs.js +3 -7
  97. package/build/components/PageToolbar.d.ts +1 -1
  98. package/build/components/PageToolbar.js +2 -10
  99. package/build/components/PrevNext.d.ts +1 -1
  100. package/build/components/PrevNext.js +2 -10
  101. package/build/components/Root.d.ts +4 -0
  102. package/build/components/Root.js +8 -0
  103. package/build/components/Search.d.ts +5 -2
  104. package/build/components/Search.js +11 -9
  105. package/build/components/SearchDialog.d.ts +1 -1
  106. package/build/components/SearchDialog.js +10 -133
  107. package/build/components/Section.d.ts +1 -1
  108. package/build/components/SectionHeader.d.ts +1 -1
  109. package/build/components/SheetPanel.d.ts +12 -0
  110. package/build/components/SheetPanel.js +19 -0
  111. package/build/components/Sidebar.d.ts +1 -1
  112. package/build/components/Sidebar.js +12 -14
  113. package/build/components/SidebarArticles.d.ts +4 -1
  114. package/build/components/SidebarArticles.js +5 -9
  115. package/build/components/SidebarLinks.d.ts +4 -1
  116. package/build/components/SidebarLinks.js +6 -9
  117. package/build/components/SidebarPosts.d.ts +4 -1
  118. package/build/components/SidebarPosts.js +7 -23
  119. package/build/components/SidebarReleases.d.ts +4 -1
  120. package/build/components/SidebarReleases.js +5 -15
  121. package/build/components/SidebarSectionPicker.d.ts +8 -0
  122. package/build/components/SidebarSectionPicker.js +31 -0
  123. package/build/components/SidebarSections.d.ts +1 -1
  124. package/build/components/SidebarSections.js +5 -9
  125. package/build/components/SiteLink.d.ts +1 -1
  126. package/build/components/SiteTitle.d.ts +1 -1
  127. package/build/components/SiteTitle.js +2 -6
  128. package/build/components/SortableTable.d.ts +1 -1
  129. package/build/components/SoundCloud.d.ts +1 -1
  130. package/build/components/SoundCloud.js +2 -2
  131. package/build/components/TagIndex.d.ts +1 -1
  132. package/build/components/TagIndex.js +2 -11
  133. package/build/components/Theme.d.ts +2 -1
  134. package/build/components/Theme.js +35 -15
  135. package/build/components/Toc.d.ts +5 -4
  136. package/build/components/Toc.js +35 -35
  137. package/build/components/YouTube.d.ts +1 -1
  138. package/build/components/YouTube.js +2 -2
  139. package/build/components/ZoomImage.d.ts +1 -1
  140. package/build/content-collections.js +5 -1
  141. package/build/elements/accordion.d.ts +4 -4
  142. package/build/elements/accordion.js +2 -3
  143. package/build/elements/alert-dialog.d.ts +12 -12
  144. package/build/elements/alert-dialog.js +2 -4
  145. package/build/elements/alert.d.ts +4 -4
  146. package/build/elements/alert.js +2 -2
  147. package/build/elements/aspect-ratio.d.ts +1 -1
  148. package/build/elements/avatar.d.ts +6 -6
  149. package/build/elements/badge.d.ts +1 -1
  150. package/build/elements/badge.js +3 -1
  151. package/build/elements/breadcrumb.d.ts +6 -6
  152. package/build/elements/breadcrumb.js +2 -4
  153. package/build/elements/button-group.d.ts +2 -2
  154. package/build/elements/button-group.js +5 -5
  155. package/build/elements/button.d.ts +2 -2
  156. package/build/elements/button.js +9 -7
  157. package/build/elements/calendar.d.ts +2 -2
  158. package/build/elements/calendar.js +2 -2
  159. package/build/elements/card.d.ts +7 -7
  160. package/build/elements/card.js +4 -4
  161. package/build/elements/carousel.d.ts +5 -5
  162. package/build/elements/carousel.js +3 -7
  163. package/build/elements/chart.d.ts +6 -6
  164. package/build/elements/chart.js +5 -11
  165. package/build/elements/checkbox.d.ts +1 -1
  166. package/build/elements/collapsible.d.ts +3 -3
  167. package/build/elements/command.d.ts +9 -9
  168. package/build/elements/command.js +5 -11
  169. package/build/elements/context-menu.d.ts +15 -15
  170. package/build/elements/context-menu.js +6 -9
  171. package/build/elements/dialog.d.ts +10 -10
  172. package/build/elements/dialog.js +2 -6
  173. package/build/elements/drawer.d.ts +14 -12
  174. package/build/elements/drawer.js +14 -15
  175. package/build/elements/dropdown-menu.d.ts +15 -15
  176. package/build/elements/dropdown-menu.js +7 -10
  177. package/build/elements/empty.d.ts +6 -6
  178. package/build/elements/empty.js +2 -2
  179. package/build/elements/field.d.ts +10 -10
  180. package/build/elements/field.js +3 -4
  181. package/build/elements/hover-card.d.ts +3 -3
  182. package/build/elements/hover-card.js +2 -2
  183. package/build/elements/input-group.d.ts +7 -7
  184. package/build/elements/input-group.js +5 -5
  185. package/build/elements/input-otp.d.ts +4 -4
  186. package/build/elements/input.d.ts +1 -1
  187. package/build/elements/input.js +2 -2
  188. package/build/elements/item.d.ts +9 -9
  189. package/build/elements/item.js +2 -2
  190. package/build/elements/kbd.d.ts +2 -2
  191. package/build/elements/label.d.ts +1 -1
  192. package/build/elements/menubar.d.ts +16 -16
  193. package/build/elements/menubar.js +9 -11
  194. package/build/elements/native-select.d.ts +3 -3
  195. package/build/elements/native-select.js +2 -4
  196. package/build/elements/navigation-menu.d.ts +8 -8
  197. package/build/elements/navigation-menu.js +3 -5
  198. package/build/elements/pagination.d.ts +7 -7
  199. package/build/elements/pagination.js +4 -10
  200. package/build/elements/popover.d.ts +6 -6
  201. package/build/elements/popover.js +2 -2
  202. package/build/elements/progress.d.ts +5 -5
  203. package/build/elements/progress.js +2 -3
  204. package/build/elements/radio-group.d.ts +2 -2
  205. package/build/elements/resizable.d.ts +3 -3
  206. package/build/elements/scroll-area.d.ts +2 -2
  207. package/build/elements/scroll-area.js +2 -4
  208. package/build/elements/select.d.ts +9 -9
  209. package/build/elements/select.js +4 -9
  210. package/build/elements/separator.d.ts +1 -1
  211. package/build/elements/sheet.d.ts +8 -8
  212. package/build/elements/sheet.js +2 -6
  213. package/build/elements/sidebar.d.ts +19 -19
  214. package/build/elements/sidebar.js +15 -37
  215. package/build/elements/skeleton.d.ts +1 -1
  216. package/build/elements/slider.d.ts +2 -1
  217. package/build/elements/slider.js +2 -3
  218. package/build/elements/sonner.d.ts +1 -1
  219. package/build/elements/spinner.d.ts +1 -1
  220. package/build/elements/switch.d.ts +1 -1
  221. package/build/elements/table.d.ts +8 -8
  222. package/build/elements/tabs.d.ts +4 -4
  223. package/build/elements/tabs.js +3 -3
  224. package/build/elements/textarea.d.ts +1 -1
  225. package/build/elements/textarea.js +2 -2
  226. package/build/elements/toggle-group.d.ts +3 -2
  227. package/build/elements/toggle-group.js +3 -3
  228. package/build/elements/toggle.d.ts +1 -1
  229. package/build/elements/toggle.js +2 -2
  230. package/build/elements/tooltip.d.ts +4 -4
  231. package/build/elements/tooltip.js +2 -3
  232. package/build/head.d.ts +4 -0
  233. package/build/head.js +6 -0
  234. package/build/helpers/article-search.d.ts +19 -0
  235. package/build/helpers/article-search.js +134 -0
  236. package/build/helpers/dependency-dir.d.ts +5 -0
  237. package/build/helpers/dependency-dir.js +20 -0
  238. package/build/helpers/dependency-dir.unit.d.ts +1 -0
  239. package/build/helpers/dependency-dir.unit.js +25 -0
  240. package/build/helpers/dynamic-icon-resolver.d.ts +1 -1
  241. package/build/helpers/dynamic-icon.d.ts +1 -1
  242. package/build/helpers/head.d.ts +53 -0
  243. package/build/helpers/head.js +69 -0
  244. package/build/helpers/head.unit.d.ts +1 -0
  245. package/build/helpers/head.unit.js +108 -0
  246. package/build/helpers/nav-items.d.ts +11 -0
  247. package/build/helpers/nav-items.js +65 -0
  248. package/build/helpers/render-inline-markdown.d.ts +1 -0
  249. package/build/helpers/render-inline-markdown.js +34 -0
  250. package/build/helpers/render-inline-markdown.unit.d.ts +1 -0
  251. package/build/helpers/render-inline-markdown.unit.js +20 -0
  252. package/build/helpers/search-highlight.d.ts +8 -0
  253. package/build/helpers/search-highlight.js +59 -0
  254. package/build/helpers/search-highlight.unit.d.ts +1 -0
  255. package/build/helpers/search-highlight.unit.js +36 -0
  256. package/build/helpers/target-export.d.ts +9 -0
  257. package/build/helpers/target-export.js +24 -0
  258. package/build/helpers/target-export.unit.d.ts +1 -0
  259. package/build/helpers/target-export.unit.js +25 -0
  260. package/build/helpers/theme.d.ts +6 -2
  261. package/build/helpers/theme.js +40 -7
  262. package/build/helpers/theme.unit.d.ts +1 -0
  263. package/build/helpers/theme.unit.js +30 -0
  264. package/build/hooks/hydrated.d.ts +2 -0
  265. package/build/hooks/hydrated.js +15 -0
  266. package/build/index.d.ts +1 -0
  267. package/build/index.js +1 -1
  268. package/build/models/config.d.ts +32 -6
  269. package/build/models/config.js +10 -1
  270. package/build/models/section.d.ts +4 -2
  271. package/build/models/section.js +1 -1
  272. package/build/plugins/vite-livemark.d.ts +4 -5
  273. package/build/plugins/vite-livemark.js +56 -90
  274. package/build/program.js +4 -2
  275. package/build/routes/$.d.ts +4 -1
  276. package/build/routes/$.js +20 -9
  277. package/build/routes/__root.js +29 -63
  278. package/build/settings.js +2 -1
  279. package/package.json +68 -66
  280. package/{.agents/skills → skills}/livemark-authoring/SKILL.md +2 -2
  281. package/{.agents/skills → skills}/livemark-configuration/SKILL.md +12 -5
  282. package/skills/livemark-customization/SKILL.md +145 -0
  283. package/source/actions/config/define.unit.ts +9 -0
  284. package/source/actions/escape/copy.ts +35 -0
  285. package/source/actions/escape/copy.unit.ts +51 -0
  286. package/source/actions/escape/list.ts +30 -0
  287. package/source/actions/escape/list.unit.ts +42 -0
  288. package/source/actions/target/build.ts +49 -69
  289. package/source/actions/target/build.unit.ts +95 -0
  290. package/source/actions/target/clear.ts +14 -0
  291. package/source/actions/target/clear.unit.ts +25 -0
  292. package/source/commands/build.ts +0 -1
  293. package/source/commands/clear.ts +15 -0
  294. package/source/commands/escape.ts +42 -59
  295. package/source/commands/preview.ts +0 -1
  296. package/source/commands/start.ts +0 -1
  297. package/source/components/AnsiCode.tsx +1 -1
  298. package/source/components/Article.tsx +8 -4
  299. package/source/components/Banner.tsx +12 -2
  300. package/source/components/BlogIndex.tsx +1 -1
  301. package/source/components/Callout.tsx +4 -6
  302. package/source/components/ChangelogIndex.tsx +1 -1
  303. package/source/components/ChoiceList.tsx +69 -0
  304. package/source/components/CodeBlock.tsx +6 -6
  305. package/source/components/CodeTabs.tsx +1 -1
  306. package/source/components/ContentTabs.tsx +1 -1
  307. package/source/components/DefaultCatchBoundary.tsx +4 -6
  308. package/source/components/Details.tsx +1 -1
  309. package/source/components/FileTree.tsx +1 -1
  310. package/source/components/Header.tsx +52 -0
  311. package/source/components/HeaderMenu.tsx +41 -0
  312. package/source/components/HeaderNav.tsx +145 -0
  313. package/source/components/HeaderSearch.tsx +77 -0
  314. package/source/components/InlineBadge.tsx +1 -1
  315. package/source/components/InlineToc.tsx +1 -1
  316. package/source/components/Layout.tsx +16 -160
  317. package/source/components/LinkButton.tsx +9 -1
  318. package/source/components/MenuSheet.tsx +92 -0
  319. package/source/components/MermaidRenderer.tsx +19 -8
  320. package/source/components/NotFound.tsx +3 -5
  321. package/source/components/PackageTabs.tsx +2 -2
  322. package/source/components/PageToolbar.tsx +11 -3
  323. package/source/components/PrevNext.tsx +2 -2
  324. package/source/components/Root.tsx +13 -0
  325. package/source/components/Search.tsx +28 -8
  326. package/source/components/SearchDialog.tsx +15 -189
  327. package/source/components/SheetPanel.tsx +69 -0
  328. package/source/components/Sidebar.tsx +27 -14
  329. package/source/components/SidebarArticles.tsx +4 -3
  330. package/source/components/SidebarLinks.tsx +3 -2
  331. package/source/components/SidebarPosts.tsx +3 -2
  332. package/source/components/SidebarReleases.tsx +3 -2
  333. package/source/components/SidebarSectionPicker.tsx +70 -0
  334. package/source/components/SiteTitle.tsx +5 -5
  335. package/source/components/SoundCloud.tsx +1 -1
  336. package/source/components/TagIndex.tsx +1 -1
  337. package/source/components/Theme.tsx +55 -29
  338. package/source/components/Toc.tsx +76 -55
  339. package/source/components/YouTube.tsx +1 -1
  340. package/source/content-collections.ts +4 -0
  341. package/source/elements/alert-dialog.tsx +1 -1
  342. package/source/elements/alert.tsx +1 -1
  343. package/source/elements/badge.tsx +4 -0
  344. package/source/elements/button-group.tsx +4 -4
  345. package/source/elements/button.tsx +10 -6
  346. package/source/elements/calendar.tsx +1 -1
  347. package/source/elements/card.tsx +3 -3
  348. package/source/elements/command.tsx +7 -4
  349. package/source/elements/context-menu.tsx +5 -5
  350. package/source/elements/dialog.tsx +1 -1
  351. package/source/elements/drawer.tsx +59 -43
  352. package/source/elements/dropdown-menu.tsx +6 -6
  353. package/source/elements/empty.tsx +1 -1
  354. package/source/elements/field.tsx +1 -1
  355. package/source/elements/hover-card.tsx +1 -1
  356. package/source/elements/input-group.tsx +4 -4
  357. package/source/elements/input.tsx +1 -1
  358. package/source/elements/item.tsx +1 -1
  359. package/source/elements/menubar.tsx +8 -8
  360. package/source/elements/native-select.tsx +1 -1
  361. package/source/elements/popover.tsx +1 -1
  362. package/source/elements/select.tsx +3 -3
  363. package/source/elements/sidebar.tsx +8 -21
  364. package/source/elements/tabs.tsx +2 -2
  365. package/source/elements/textarea.tsx +1 -1
  366. package/source/elements/toggle-group.tsx +2 -2
  367. package/source/elements/toggle.tsx +1 -1
  368. package/source/env.d.ts +1 -0
  369. package/source/head.ts +7 -0
  370. package/source/helpers/article-search.tsx +192 -0
  371. package/source/helpers/dependency-dir.ts +20 -0
  372. package/source/helpers/dependency-dir.unit.ts +39 -0
  373. package/source/helpers/head.ts +94 -0
  374. package/source/helpers/head.unit.ts +116 -0
  375. package/source/helpers/nav-items.tsx +102 -0
  376. package/source/helpers/render-inline-markdown.ts +44 -0
  377. package/source/helpers/render-inline-markdown.unit.ts +30 -0
  378. package/source/helpers/search-highlight.ts +73 -0
  379. package/source/helpers/search-highlight.unit.ts +42 -0
  380. package/source/helpers/target-export.ts +28 -0
  381. package/source/helpers/target-export.unit.ts +37 -0
  382. package/source/helpers/theme.ts +40 -6
  383. package/source/helpers/theme.unit.ts +32 -0
  384. package/source/hooks/hydrated.ts +18 -0
  385. package/source/index.ts +1 -0
  386. package/source/models/config.ts +13 -0
  387. package/source/models/section.ts +3 -0
  388. package/source/plugins/vite-livemark.ts +54 -95
  389. package/source/program.ts +2 -0
  390. package/source/routes/$.tsx +27 -9
  391. package/source/routes/__root.tsx +51 -71
  392. package/source/settings.ts +1 -0
  393. package/source/styles/general.css +59 -11
  394. package/source/styles/markdown.css +13 -7
  395. package/source/styles/shape.css +14 -0
  396. package/.agents/skills/change-review/SKILL.md +0 -47
  397. package/.agents/skills/livemark-customization/SKILL.md +0 -118
@@ -1,4 +1,5 @@
1
1
  import { config } from "livemark:virtual"
2
+ import { useState } from "react"
2
3
  import { useLocation, useMatch } from "@tanstack/react-router"
3
4
  import { useHotkey } from "@tanstack/react-hotkeys"
4
5
  import { currentSection } from "../content/article.ts"
@@ -6,7 +7,6 @@ import { activeSiteLink } from "../helpers/section.ts"
6
7
  import {
7
8
  Sidebar as SidebarRoot,
8
9
  SidebarContent,
9
- SidebarFooter,
10
10
  SidebarHeader,
11
11
  SidebarMenu,
12
12
  SidebarMenuButton,
@@ -14,15 +14,15 @@ import {
14
14
  SidebarRail,
15
15
  useSidebar,
16
16
  } from "../elements/sidebar.tsx"
17
- import { Search } from "./Search.tsx"
18
17
  import { SidebarArticles } from "./SidebarArticles.tsx"
19
18
  import { SidebarLinks } from "./SidebarLinks.tsx"
20
19
  import { SidebarPosts } from "./SidebarPosts.tsx"
21
20
  import { SidebarReleases } from "./SidebarReleases.tsx"
21
+ import { SidebarSectionPicker } from "./SidebarSectionPicker.tsx"
22
22
  import { SidebarSections } from "./SidebarSections.tsx"
23
23
  import { SiteLink } from "./SiteLink.tsx"
24
24
  import { SiteTitle } from "./SiteTitle.tsx"
25
- import { Theme } from "./Theme.tsx"
25
+ import type { RoutedSection } from "../models/section.ts"
26
26
 
27
27
  /** Application sidebar shell: header, body slot picked by section.type, footer */
28
28
  export function Sidebar(props: { withSidebar?: boolean }) {
@@ -30,13 +30,19 @@ export function Sidebar(props: { withSidebar?: boolean }) {
30
30
  const articleRoute = useMatch({ from: "/$", shouldThrow: false })
31
31
  const { toggleSidebar, isMobile } = useSidebar()
32
32
 
33
- useHotkey("S", () => {
33
+ useHotkey("L", () => {
34
34
  if (props.withSidebar || isMobile) toggleSidebar()
35
35
  })
36
36
 
37
- const section = articleRoute
37
+ const [browsed, setBrowsed] = useState<{ section: RoutedSection; pathname: string }>()
38
+
39
+ const routeSection = articleRoute
38
40
  ? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
39
41
  : undefined
42
+ const picking = isMobile && routeSection !== undefined
43
+ // Browsing is scoped to the page it started on, so navigating resets it
44
+ const section =
45
+ picking && browsed?.pathname === pathname ? browsed.section : routeSection
40
46
  const siteLinkTarget = activeSiteLink(pathname, config.sections ?? [])
41
47
 
42
48
  return (
@@ -51,18 +57,25 @@ export function Sidebar(props: { withSidebar?: boolean }) {
51
57
  </SidebarMenu>
52
58
  </SidebarHeader>
53
59
  <SidebarContent>
54
- <SidebarSections />
55
- {props.withSidebar && section?.type === "blog" && <SidebarPosts />}
56
- {props.withSidebar && section?.type === "changelog" && <SidebarReleases />}
60
+ {picking && section ? (
61
+ <SidebarSectionPicker
62
+ section={section}
63
+ onSelect={picked => setBrowsed({ section: picked, pathname })}
64
+ />
65
+ ) : (
66
+ <SidebarSections />
67
+ )}
68
+ {props.withSidebar && section?.type === "blog" && (
69
+ <SidebarPosts section={section} />
70
+ )}
71
+ {props.withSidebar && section?.type === "changelog" && (
72
+ <SidebarReleases section={section} />
73
+ )}
57
74
  {props.withSidebar &&
58
75
  section?.type !== "blog" &&
59
- section?.type !== "changelog" && <SidebarArticles />}
60
- <SidebarLinks />
76
+ section?.type !== "changelog" && <SidebarArticles section={section} />}
77
+ <SidebarLinks section={section} />
61
78
  </SidebarContent>
62
- <SidebarFooter className="p-4 space-y-2">
63
- <Search />
64
- <Theme />
65
- </SidebarFooter>
66
79
  <SidebarRail />
67
80
  </SidebarRoot>
68
81
  )
@@ -20,13 +20,14 @@ import {
20
20
  } from "../elements/sidebar.tsx"
21
21
  import { resolveArticleIcon } from "../helpers/article-icon.ts"
22
22
  import type { ArticleNode } from "../models/article.ts"
23
+ import type { RoutedSection } from "../models/section.ts"
23
24
 
24
25
  /** Article-tree body slot for the Sidebar shell */
25
- export function SidebarArticles() {
26
+ export function SidebarArticles(props: { section?: RoutedSection }) {
26
27
  const pathname = useLocation({ select: l => l.pathname })
27
28
 
28
29
  const configSections = config.sections
29
- const section = currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
30
+ const section = props.section ?? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
30
31
  const tree = configSections?.length
31
32
  ? (sectionArticleTrees.get(section?.prefix ?? "__default__") ?? articleTree)
32
33
  : articleTree
@@ -88,7 +89,7 @@ function NavNode(props: { node: ArticleNode; currentPath: string; depth?: number
88
89
  </Button>
89
90
  <CollapsibleTrigger
90
91
  aria-label={`Toggle ${node.label ?? node.title} submenu`}
91
- className="absolute right-1 top-1.5 p-1 rounded-md hover:bg-sidebar-accent"
92
+ className="absolute right-1 top-1.5 p-1 rounded-control hover:bg-sidebar-accent"
92
93
  >
93
94
  <ChevronRight className="size-3.5 transition-transform group-data-[open]/collapsible:rotate-90" />
94
95
  </CollapsibleTrigger>
@@ -11,13 +11,14 @@ import {
11
11
  SidebarMenuButton,
12
12
  SidebarMenuItem,
13
13
  } from "../elements/sidebar.tsx"
14
+ import type { RoutedSection } from "../models/section.ts"
14
15
 
15
16
  /** Shared Links group. Renders sidebar-positioned links always, and
16
17
  * header-positioned links only on mobile (mobile collapses all chrome
17
18
  * links into the sidebar since the header strip is hidden below `md`). */
18
- export function SidebarLinks() {
19
+ export function SidebarLinks(props: { section?: RoutedSection }) {
19
20
  const pathname = useLocation({ select: l => l.pathname })
20
- const section = currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
21
+ const section = props.section ?? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
21
22
  const links =
22
23
  config.links?.filter(link => !link.prefix || link.prefix === section?.prefix) ?? []
23
24
 
@@ -17,12 +17,13 @@ import {
17
17
  SidebarMenuSubButton,
18
18
  SidebarMenuSubItem,
19
19
  } from "../elements/sidebar.tsx"
20
+ import type { RoutedSection } from "../models/section.ts"
20
21
 
21
22
  /** Blog body slot for the Sidebar shell: All Posts / Recent Posts / RSS / Tags */
22
- export function SidebarPosts() {
23
+ export function SidebarPosts(props: { section?: RoutedSection }) {
23
24
  const pathname = useLocation({ select: l => l.pathname })
24
25
 
25
- const section = currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
26
+ const section = props.section ?? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
26
27
  const flat = section ? (sectionFlatArticles.get(section.prefix) ?? []) : []
27
28
  const posts = flat
28
29
  .map(p => sortedArticles.find(a => a.path === p))
@@ -16,12 +16,13 @@ import {
16
16
  SidebarMenuSubButton,
17
17
  SidebarMenuSubItem,
18
18
  } from "../elements/sidebar.tsx"
19
+ import type { RoutedSection } from "../models/section.ts"
19
20
 
20
21
  /** Changelog body slot for the Sidebar shell: All Releases / Recent Releases */
21
- export function SidebarReleases() {
22
+ export function SidebarReleases(props: { section?: RoutedSection }) {
22
23
  const pathname = useLocation({ select: l => l.pathname })
23
24
 
24
- const section = currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
25
+ const section = props.section ?? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
25
26
  const flat = section ? (sectionFlatArticles.get(section.prefix) ?? []) : []
26
27
  const entries = flat
27
28
  .map(p => sortedArticles.find(a => a.path === p))
@@ -0,0 +1,70 @@
1
+ import { config } from "livemark:virtual"
2
+ import { ChevronDownIcon } from "lucide-react"
3
+ import { DynamicIcon } from "../helpers/dynamic-icon.tsx"
4
+ import { prefixUrl } from "../helpers/prefix-url.ts"
5
+ import {
6
+ SidebarGroup,
7
+ SidebarGroupContent,
8
+ SidebarGroupLabel,
9
+ } from "../elements/sidebar.tsx"
10
+ import type { RoutedSection, Section } from "../models/section.ts"
11
+ import { MenuSheet } from "./MenuSheet.tsx"
12
+
13
+ /** Mobile Sections group: a select-like trigger opening a bottom sheet that swaps the
14
+ * sidebar body to another section without navigating; custom sections still navigate
15
+ * to their URL. */
16
+ export function SidebarSectionPicker(props: {
17
+ section: RoutedSection
18
+ onSelect: (section: RoutedSection) => void
19
+ }) {
20
+ const sections = config.sections ?? []
21
+
22
+ const selectSection = (picked: Section) => {
23
+ if (picked.type === "custom") {
24
+ window.location.assign(prefixUrl(picked.url, config.base))
25
+ return
26
+ }
27
+ props.onSelect(picked)
28
+ }
29
+
30
+ return (
31
+ <SidebarGroup>
32
+ <SidebarGroupLabel className="uppercase font-mono text-xs tracking-widest">
33
+ Sections
34
+ </SidebarGroupLabel>
35
+ <SidebarGroupContent>
36
+ <MenuSheet
37
+ title="Choose section"
38
+ items={sections.map(s => ({
39
+ id: sectionKey(s),
40
+ label: s.title,
41
+ icon: s.icon ? <DynamicIcon name={s.icon} className="size-4" /> : undefined,
42
+ active: s.type !== "custom" && s.prefix === props.section.prefix,
43
+ onSelect: () => selectSection(s),
44
+ }))}
45
+ trigger={
46
+ <button
47
+ type="button"
48
+ aria-label="Section"
49
+ aria-haspopup="dialog"
50
+ className="flex h-9 w-full min-w-0 cursor-pointer items-center gap-2 rounded-control border border-input bg-transparent px-3 text-left text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50"
51
+ />
52
+ }
53
+ >
54
+ {props.section.icon && (
55
+ <DynamicIcon name={props.section.icon} className="size-4 shrink-0" />
56
+ )}
57
+ <span className="flex-1 truncate">{props.section.title}</span>
58
+ <ChevronDownIcon
59
+ className="size-4 shrink-0 text-muted-foreground"
60
+ aria-hidden="true"
61
+ />
62
+ </MenuSheet>
63
+ </SidebarGroupContent>
64
+ </SidebarGroup>
65
+ )
66
+ }
67
+
68
+ function sectionKey(section: Section) {
69
+ return section.type === "custom" ? `url:${section.url}` : section.prefix
70
+ }
@@ -14,11 +14,11 @@ export function SiteTitle() {
14
14
  const description = section?.siteDescription ?? config.description
15
15
  const logo = prefixUrl(config.logo ?? defaultLogo, config.base)
16
16
  return (
17
- <div className="flex items-end gap-2.5 text-sm">
18
- <img src={logo} alt={title} className="size-6.5 mb-1.25" />
19
- <div className="flex flex-col gap-0.5 leading-none">
20
- <span className="font-semibold">{title}</span>
21
- <span className="text-xs opacity-80">{description}</span>
17
+ <div className="flex min-w-0 items-end gap-2.5 text-sm">
18
+ <img src={logo} alt={title} className="size-6.5 mb-1.25 shrink-0" />
19
+ <div className="flex min-w-0 flex-col gap-0.5 leading-none">
20
+ <span className="truncate font-semibold">{title}</span>
21
+ <span className="truncate text-xs opacity-80">{description}</span>
22
22
  </div>
23
23
  </div>
24
24
  )
@@ -3,7 +3,7 @@ export function SoundCloud(props: { url: string }) {
3
3
  const src = `https://w.soundcloud.com/player/?url=${encodeURIComponent(props.url)}&color=%23ff5500&auto_play=false&hide_related=true&show_comments=false&show_user=true&show_reposts=false&show_teaser=false`
4
4
 
5
5
  return (
6
- <div className="not-prose my-4 overflow-hidden rounded-lg border">
6
+ <div className="not-prose my-4 overflow-hidden rounded-card border">
7
7
  <iframe
8
8
  className="w-full"
9
9
  height={166}
@@ -39,7 +39,7 @@ export function TagIndex(props: { sectionPrefix: string; tag: string }) {
39
39
 
40
40
  return (
41
41
  <TocContext.Provider value={tocItems}>
42
- <MobileToc items={tocItems}>
42
+ <MobileToc items={tocItems} title={title}>
43
43
  <PageToolbar content={content} />
44
44
  </MobileToc>
45
45
  <div className="flex flex-1 gap-10 p-4 pt-8 md:p-10">
@@ -1,39 +1,65 @@
1
- import { Moon, Sun } from "lucide-react"
1
+ import { Moon, Sun, SunMoon } from "lucide-react"
2
2
  import { useHotkey } from "@tanstack/react-hotkeys"
3
+ import { useState } from "react"
3
4
  import { Button } from "../elements/button.tsx"
4
- import { getCurrentTheme, setTheme } from "../helpers/theme.ts"
5
+ import type { ThemePref } from "../helpers/theme.ts"
6
+ import { getCurrentTheme, getThemePref, setTheme } from "../helpers/theme.ts"
7
+ import { MenuSheet } from "./MenuSheet.tsx"
5
8
 
9
+ /** Header menu choosing light, dark or system theme; "T" flips light and dark */
6
10
  export function Theme() {
7
- const handleToggle = () => {
8
- const oldTheme = getCurrentTheme()
9
- const newTheme = oldTheme === "light" ? "dark" : "light"
10
- setTheme(newTheme)
11
- }
11
+ // Read as the menu opens: on the server there is no stored preference to read.
12
+ const [current, setCurrent] = useState<ThemePref>("system")
13
+
14
+ useHotkey("T", () => setTheme(getCurrentTheme() === "light" ? "dark" : "light"))
12
15
 
13
- useHotkey("T", handleToggle)
16
+ const choose = (pref: ThemePref) => {
17
+ setCurrent(pref)
18
+ setTheme(pref)
19
+ }
14
20
 
15
21
  return (
16
- <Button
17
- variant="ghost"
18
- size="sm"
19
- onClick={handleToggle}
20
- title="Change Theme"
21
- className="w-full h-auto rounded-xl cursor-pointer text-xs font-normal justify-start text-muted-foreground bg-sidebar-accent/70 border border-border shadow-xs hover:!bg-background px-3 py-2"
22
+ <MenuSheet
23
+ title="Change theme"
24
+ onOpenChange={open => open && setCurrent(getThemePref())}
25
+ items={[
26
+ {
27
+ id: "light",
28
+ label: "Light",
29
+ icon: <Sun className="size-4 shrink-0" />,
30
+ active: current === "light",
31
+ onSelect: () => choose("light"),
32
+ },
33
+ {
34
+ id: "dark",
35
+ label: "Dark",
36
+ icon: <Moon className="size-4 shrink-0" />,
37
+ active: current === "dark",
38
+ onSelect: () => choose("dark"),
39
+ },
40
+ {
41
+ id: "system",
42
+ label: "System",
43
+ icon: <SunMoon className="size-4 shrink-0" />,
44
+ active: current === "system",
45
+ onSelect: () => choose("system"),
46
+ },
47
+ ]}
48
+ trigger={
49
+ <Button
50
+ variant="ghost"
51
+ size="icon-sm"
52
+ title="Change theme"
53
+ aria-label="Change theme"
54
+ aria-keyshortcuts="T"
55
+ className="cursor-pointer text-muted-foreground"
56
+ />
57
+ }
22
58
  >
23
- <div className="flex flex-1 gap-2 items-center dark:hidden">
24
- <Sun className="size-4" />
25
- <span className="flex-1 text-left">Light Theme</span>
26
- <kbd className="rounded border border-border bg-muted px-1.5 py-0.5 text-[10px] font-mono">
27
- T
28
- </kbd>
29
- </div>
30
- <div className="flex-1 gap-2 items-center hidden dark:flex">
31
- <Moon className="size-4" />
32
- <span className="flex-1 text-left">Dark Theme</span>
33
- <kbd className="rounded border border-border bg-muted px-1.5 py-0.5 text-[10px] font-mono">
34
- T
35
- </kbd>
36
- </div>
37
- </Button>
59
+ {/* Swapped on the `.dark` class rather than on state, so the server render
60
+ matches: no hydration mismatch, no wrong-icon flash. */}
61
+ <Sun className="size-4 dark:hidden" />
62
+ <Moon className="hidden size-4 dark:block" />
63
+ </MenuSheet>
38
64
  )
39
65
  }
@@ -1,6 +1,9 @@
1
- import { ChevronDown } from "lucide-react"
2
- import { useEffect, useRef, useState, type ReactNode } from "react"
1
+ import { ChevronUp } from "lucide-react"
2
+ import { useEffect, useRef, useState, type MouseEvent, type ReactNode } from "react"
3
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../elements/tabs.tsx"
3
4
  import type { TocItem } from "../models/toc.ts"
5
+ import { ChoiceList, type Choice } from "./ChoiceList.tsx"
6
+ import { SheetPanel } from "./SheetPanel.tsx"
4
7
 
5
8
  export function Toc(props: { items: TocItem[]; children?: ReactNode }) {
6
9
  const activeId = useActiveHeading(props.items)
@@ -10,7 +13,12 @@ export function Toc(props: { items: TocItem[]; children?: ReactNode }) {
10
13
  return (
11
14
  <aside className="hidden xl:block w-56 shrink-0">
12
15
  <div className="sticky top-20 max-h-[calc(100vh-5rem)] overflow-auto [scrollbar-width:none]">
13
- {props.children}
16
+ {props.children && (
17
+ <div className="mb-6">
18
+ <p className="text-sm font-medium mb-3">Actions</p>
19
+ {props.children}
20
+ </div>
21
+ )}
14
22
  {props.items.length > 0 && (
15
23
  <nav>
16
24
  <p className="text-sm font-medium mb-3">Contents</p>
@@ -34,74 +42,87 @@ export function Toc(props: { items: TocItem[]; children?: ReactNode }) {
34
42
  )
35
43
  }
36
44
 
37
- /** Mobile-only sticky secondary bar showing the active heading and an
38
- * overlay dropdown with the Contents + page Actions. */
39
- export function MobileToc(props: { items: TocItem[]; children?: ReactNode }) {
45
+ const SHEET_ACTIONS =
46
+ "[&_[data-slot=page-toolbar]]:-mx-2 [&_[data-slot=page-toolbar]]:gap-0.5 [&_[data-slot=page-action]]:h-10 [&_[data-slot=page-action]]:gap-2.5 [&_[data-slot=page-action]]:rounded-control [&_[data-slot=page-action]]:px-2 [&_[data-slot=page-action]]:text-base [&_[data-slot=page-action]]:text-foreground [&_[data-slot=page-action]:hover]:bg-muted [&_[data-slot=page-action]_svg]:size-[18px]"
47
+
48
+ /** Mobile-only sticky secondary bar showing the active heading; it opens a
49
+ * bottom sheet with the Contents and page Actions as two tabs. */
50
+ export function MobileToc(props: {
51
+ items: TocItem[]
52
+ title?: string
53
+ children?: ReactNode
54
+ }) {
40
55
  const activeId = useActiveHeading(props.items)
41
56
  const [open, setOpen] = useState(false)
42
- const wrapperRef = useRef<HTMLDivElement>(null)
43
-
44
- useEffect(() => {
45
- if (!open) return
46
- const onPointerDown = (event: MouseEvent) => {
47
- if (!wrapperRef.current?.contains(event.target as Node)) setOpen(false)
48
- }
49
- const onKey = (event: KeyboardEvent) => {
50
- if (event.key === "Escape") setOpen(false)
51
- }
52
- document.addEventListener("mousedown", onPointerDown)
53
- document.addEventListener("keydown", onKey)
54
- return () => {
55
- document.removeEventListener("mousedown", onPointerDown)
56
- document.removeEventListener("keydown", onKey)
57
- }
58
- }, [open])
57
+ const [tab, setTab] = useState(props.items.length > 0 ? "contents" : "actions")
58
+ const pendingUrl = useRef<string | null>(null)
59
59
 
60
60
  if (props.items.length === 0 && !props.children) return null
61
61
 
62
62
  const active = props.items.find(item => item.url.slice(1) === activeId)
63
63
  const title = active?.title ?? props.items[0]?.title ?? "Contents"
64
+ const choices: Choice[] = props.items.map(item => ({
65
+ id: item.url.slice(1),
66
+ label: item.title,
67
+ href: item.url,
68
+ depth: Math.max(0, item.depth - 2),
69
+ }))
70
+
71
+ // The sheet locks page scroll until it has closed, which swallows a native hash
72
+ // jump made from inside it; navigate once the close has finished.
73
+ const handleSelect = (choice: Choice, event: MouseEvent<HTMLElement>) => {
74
+ event.preventDefault()
75
+ pendingUrl.current = choice.href ?? null
76
+ setOpen(false)
77
+ }
78
+
79
+ const handleOpenChangeComplete = (isOpen: boolean) => {
80
+ const url = pendingUrl.current
81
+ if (isOpen || !url) return
82
+ pendingUrl.current = null
83
+ history.pushState(null, "", url)
84
+ document.getElementById(url.slice(1))?.scrollIntoView()
85
+ }
86
+
87
+ const contents = (
88
+ <ChoiceList choices={choices} selectedId={activeId} onSelect={handleSelect} />
89
+ )
64
90
 
65
91
  return (
66
- <div ref={wrapperRef} className="xl:hidden sticky top-16 z-20 border-b bg-background">
92
+ <div className="xl:hidden sticky top-16 z-20 border-b bg-background">
67
93
  <button
68
94
  type="button"
69
- onClick={() => setOpen(v => !v)}
70
- aria-expanded={open}
95
+ onClick={() => setOpen(true)}
96
+ aria-haspopup="dialog"
71
97
  className="flex w-full items-center gap-3 px-4 py-3 text-sm text-muted-foreground"
72
98
  >
73
99
  <ScrollProgressRing />
74
100
  <span className="truncate flex-1 text-left">{title}</span>
75
- <ChevronDown
76
- className={`size-4 shrink-0 transition-transform ${open ? "rotate-180" : ""}`}
77
- />
101
+ <ChevronUp className="size-4 shrink-0" />
78
102
  </button>
79
- {open && (
80
- <div className="absolute top-full left-0 right-0 mt-px max-h-[calc(100vh-8rem)] overflow-auto border-b bg-background shadow-md animate-in slide-in-from-top-2 fade-in-0 duration-200">
81
- <div className="space-y-4 px-4 pt-4 pb-4">
82
- {props.children}
83
- {props.items.length > 0 && (
84
- <nav>
85
- <p className="mb-2 text-sm font-medium">Contents</p>
86
- <ul className="border-l border-border text-sm">
87
- {props.items.map(item => (
88
- <li key={item.url}>
89
- <a
90
- href={item.url}
91
- data-active={activeId === item.url.slice(1) || undefined}
92
- onClick={() => setOpen(false)}
93
- className={`-ms-px block border-l border-transparent py-1.5 text-muted-foreground transition-colors hover:text-foreground data-[active]:border-primary data-[active]:font-medium data-[active]:text-primary ${item.depth <= 2 ? "ps-3" : item.depth === 3 ? "ps-6" : "ps-8"}`}
94
- >
95
- {item.title}
96
- </a>
97
- </li>
98
- ))}
99
- </ul>
100
- </nav>
101
- )}
102
- </div>
103
- </div>
104
- )}
103
+ <SheetPanel
104
+ open={open}
105
+ onOpenChange={setOpen}
106
+ onOpenChangeComplete={handleOpenChangeComplete}
107
+ title={props.title ?? "On this page"}
108
+ >
109
+ {props.children && props.items.length > 0 ? (
110
+ <Tabs value={tab} onValueChange={value => setTab(String(value))}>
111
+ <TabsList className="mb-2 w-full">
112
+ <TabsTrigger value="contents">Contents</TabsTrigger>
113
+ <TabsTrigger value="actions">Actions</TabsTrigger>
114
+ </TabsList>
115
+ <TabsContent value="contents">{contents}</TabsContent>
116
+ <TabsContent value="actions" className={SHEET_ACTIONS}>
117
+ {props.children}
118
+ </TabsContent>
119
+ </Tabs>
120
+ ) : props.children ? (
121
+ <div className={SHEET_ACTIONS}>{props.children}</div>
122
+ ) : (
123
+ contents
124
+ )}
125
+ </SheetPanel>
105
126
  </div>
106
127
  )
107
128
  }
@@ -1,7 +1,7 @@
1
1
  /** Renders a responsive YouTube iframe embed */
2
2
  export function YouTube(props: { id: string }) {
3
3
  return (
4
- <div className="not-prose my-4 overflow-hidden rounded-lg border">
4
+ <div className="not-prose my-4 overflow-hidden rounded-card border">
5
5
  <div className="aspect-video">
6
6
  <iframe
7
7
  className="h-full w-full"
@@ -183,6 +183,10 @@ const articles = defineCollection({
183
183
  light: config.codeThemeLight,
184
184
  dark: config.codeThemeDark,
185
185
  },
186
+ // A fence with no or an unknown lang is otherwise skipped by Shiki and
187
+ // renders without line padding, theme colours or transformers.
188
+ defaultLanguage: "text",
189
+ fallbackLanguage: "text",
186
190
  // Darken catppuccin-latte's green (the colour used for strings and
187
191
  // some comments) — the theme default #40A02B lands at 3.2:1 on the
188
192
  // code-block background, below the WCAG AA 4.5:1 threshold.
@@ -46,7 +46,7 @@ function AlertDialogContent({
46
46
  data-slot="alert-dialog-content"
47
47
  data-size={size}
48
48
  className={cn(
49
- "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl bg-popover p-6 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
49
+ "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-panel bg-popover p-6 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
50
50
  className,
51
51
  )}
52
52
  {...props}
@@ -2,7 +2,7 @@ import { cva, type VariantProps } from "class-variance-authority"
2
2
  import { cn } from "cn"
3
3
 
4
4
  const alertVariants = cva(
5
- "grid gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert",
5
+ "grid gap-0.5 rounded-card border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert",
6
6
  {
7
7
  variants: {
8
8
  variant: {
@@ -12,6 +12,10 @@ const badgeVariants = cva(
12
12
  secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
13
13
  destructive:
14
14
  "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
15
+ positive:
16
+ "bg-positive/10 [a]:hover:bg-positive/20 focus-visible:ring-positive/20 dark:focus-visible:ring-positive/40 text-positive-tint-foreground dark:bg-positive/20",
17
+ warning:
18
+ "bg-warning/10 [a]:hover:bg-warning/20 focus-visible:ring-warning/20 dark:focus-visible:ring-warning/40 text-warning-tint-foreground dark:bg-warning/20",
15
19
  outline:
16
20
  "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
17
21
  ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
@@ -6,14 +6,14 @@ import { cn } from "cn"
6
6
  import { Separator } from "./separator.tsx"
7
7
 
8
8
  const buttonGroupVariants = cva(
9
- "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
9
+ "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-control [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
10
10
  {
11
11
  variants: {
12
12
  orientation: {
13
13
  horizontal:
14
- "*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",
14
+ "*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-control! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",
15
15
  vertical:
16
- "flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0",
16
+ "flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-control! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0",
17
17
  },
18
18
  },
19
19
  defaultVariants: {
@@ -48,7 +48,7 @@ function ButtonGroupText({
48
48
  props: mergeProps<"div">(
49
49
  {
50
50
  className: cn(
51
- "flex items-center gap-2 rounded-md border bg-muted px-2.5 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
51
+ "flex items-center gap-2 rounded-control border bg-muted px-2.5 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
52
52
  className,
53
53
  ),
54
54
  },