@nanisoft/prism-ui 0.10.1 → 0.13.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 (334) hide show
  1. package/LICENSE +21 -21
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/blocks/app-shell-01/app-shell.js +4 -4
  4. package/dist/blocks/app-shell-01/index.d.ts +2 -2
  5. package/dist/blocks/app-shell-01/index.js +1 -1
  6. package/dist/blocks/auth-form-01/auth-form.d.ts +11 -1
  7. package/dist/blocks/auth-form-01/auth-form.d.ts.map +1 -1
  8. package/dist/blocks/auth-form-01/auth-form.js +11 -8
  9. package/dist/blocks/auth-form-01/index.d.ts +2 -2
  10. package/dist/blocks/auth-form-01/index.js +1 -1
  11. package/dist/blocks/cta-01/cta.d.ts +26 -21
  12. package/dist/blocks/cta-01/cta.d.ts.map +1 -1
  13. package/dist/blocks/cta-01/cta.js +2 -2
  14. package/dist/blocks/cta-01/index.d.ts +2 -2
  15. package/dist/blocks/cta-01/index.js +1 -1
  16. package/dist/blocks/data-table-01/data-table.js +7 -7
  17. package/dist/blocks/data-table-01/index.d.ts +2 -2
  18. package/dist/blocks/data-table-01/index.js +1 -1
  19. package/dist/blocks/empty-state-01/empty-state.d.ts +143 -0
  20. package/dist/blocks/empty-state-01/empty-state.d.ts.map +1 -0
  21. package/dist/blocks/empty-state-01/empty-state.js +104 -0
  22. package/dist/blocks/empty-state-01/index.d.ts +3 -0
  23. package/dist/blocks/empty-state-01/index.d.ts.map +1 -0
  24. package/dist/blocks/empty-state-01/index.js +1 -0
  25. package/dist/blocks/feature-grid-01/feature-grid.d.ts +28 -1
  26. package/dist/blocks/feature-grid-01/feature-grid.d.ts.map +1 -1
  27. package/dist/blocks/feature-grid-01/feature-grid.js +34 -3
  28. package/dist/blocks/feature-grid-01/index.d.ts +2 -2
  29. package/dist/blocks/feature-grid-01/index.js +1 -1
  30. package/dist/blocks/hero-01/hero.d.ts +49 -17
  31. package/dist/blocks/hero-01/hero.d.ts.map +1 -1
  32. package/dist/blocks/hero-01/hero.js +14 -9
  33. package/dist/blocks/hero-01/index.d.ts +2 -1
  34. package/dist/blocks/hero-01/index.d.ts.map +1 -1
  35. package/dist/blocks/hero-01/index.js +1 -1
  36. package/dist/blocks/index.d.ts +20 -20
  37. package/dist/blocks/index.js +19 -19
  38. package/dist/blocks/instrument-panel-01/index.d.ts +2 -2
  39. package/dist/blocks/instrument-panel-01/index.js +1 -1
  40. package/dist/blocks/instrument-panel-01/instrument-panel.js +1 -1
  41. package/dist/blocks/logo-strip-01/index.d.ts +2 -2
  42. package/dist/blocks/logo-strip-01/index.js +1 -1
  43. package/dist/blocks/logo-strip-01/logo-strip.d.ts +1 -1
  44. package/dist/blocks/logo-strip-01/logo-strip.js +2 -2
  45. package/dist/blocks/note-grid-01/index.d.ts +2 -2
  46. package/dist/blocks/note-grid-01/index.js +1 -1
  47. package/dist/blocks/note-grid-01/note-grid.d.ts +1 -1
  48. package/dist/blocks/note-grid-01/note-grid.js +1 -1
  49. package/dist/blocks/page-header-01/index.d.ts +2 -2
  50. package/dist/blocks/page-header-01/index.js +1 -1
  51. package/dist/blocks/page-header-01/page-header.d.ts +1 -1
  52. package/dist/blocks/page-header-01/page-header.js +4 -4
  53. package/dist/blocks/pricing-01/index.d.ts +1 -1
  54. package/dist/blocks/pricing-01/index.js +1 -1
  55. package/dist/blocks/pricing-01/pricing.d.ts +8 -1
  56. package/dist/blocks/pricing-01/pricing.d.ts.map +1 -1
  57. package/dist/blocks/pricing-01/pricing.js +15 -5
  58. package/dist/blocks/process-flow-01/index.d.ts +3 -0
  59. package/dist/blocks/process-flow-01/index.d.ts.map +1 -0
  60. package/dist/blocks/process-flow-01/index.js +1 -0
  61. package/dist/blocks/process-flow-01/process-flow.d.ts +118 -0
  62. package/dist/blocks/process-flow-01/process-flow.d.ts.map +1 -0
  63. package/dist/blocks/process-flow-01/process-flow.js +71 -0
  64. package/dist/blocks/process-rail-01/index.d.ts +2 -2
  65. package/dist/blocks/process-rail-01/index.js +1 -1
  66. package/dist/blocks/process-rail-01/process-rail.d.ts +1 -1
  67. package/dist/blocks/process-rail-01/process-rail.js +2 -2
  68. package/dist/blocks/product-grid-01/index.d.ts +2 -2
  69. package/dist/blocks/product-grid-01/index.js +1 -1
  70. package/dist/blocks/product-grid-01/product-grid.d.ts +37 -5
  71. package/dist/blocks/product-grid-01/product-grid.d.ts.map +1 -1
  72. package/dist/blocks/product-grid-01/product-grid.js +20 -6
  73. package/dist/blocks/run-console-01/index.d.ts +3 -0
  74. package/dist/blocks/run-console-01/index.d.ts.map +1 -0
  75. package/dist/blocks/run-console-01/index.js +1 -0
  76. package/dist/blocks/run-console-01/run-console.d.ts +111 -0
  77. package/dist/blocks/run-console-01/run-console.d.ts.map +1 -0
  78. package/dist/blocks/run-console-01/run-console.js +48 -0
  79. package/dist/blocks/settings-panel-01/index.d.ts +2 -2
  80. package/dist/blocks/settings-panel-01/index.js +1 -1
  81. package/dist/blocks/settings-panel-01/settings-panel.d.ts +11 -1
  82. package/dist/blocks/settings-panel-01/settings-panel.d.ts.map +1 -1
  83. package/dist/blocks/settings-panel-01/settings-panel.js +15 -10
  84. package/dist/blocks/site-footer/index.d.ts +2 -2
  85. package/dist/blocks/site-footer/index.js +1 -1
  86. package/dist/blocks/site-footer/site-footer.d.ts +1 -1
  87. package/dist/blocks/site-footer/site-footer.js +2 -2
  88. package/dist/blocks/site-header/index.d.ts +2 -2
  89. package/dist/blocks/site-header/index.js +1 -1
  90. package/dist/blocks/site-header/site-header.d.ts +2 -2
  91. package/dist/blocks/site-header/site-header.js +3 -3
  92. package/dist/blocks/site-navbar/index.d.ts +3 -0
  93. package/dist/blocks/site-navbar/index.d.ts.map +1 -0
  94. package/dist/blocks/site-navbar/index.js +1 -0
  95. package/dist/blocks/site-navbar/mobile-nav.d.ts +48 -0
  96. package/dist/blocks/site-navbar/mobile-nav.d.ts.map +1 -0
  97. package/dist/blocks/site-navbar/mobile-nav.js +35 -0
  98. package/dist/blocks/site-navbar/mode-toggle.d.ts +39 -0
  99. package/dist/blocks/site-navbar/mode-toggle.d.ts.map +1 -0
  100. package/dist/blocks/site-navbar/mode-toggle.js +39 -0
  101. package/dist/blocks/site-navbar/navbar-controls.d.ts +79 -0
  102. package/dist/blocks/site-navbar/navbar-controls.d.ts.map +1 -0
  103. package/dist/blocks/site-navbar/navbar-controls.js +40 -0
  104. package/dist/blocks/site-navbar/search-entry.d.ts +29 -0
  105. package/dist/blocks/site-navbar/search-entry.d.ts.map +1 -0
  106. package/dist/blocks/site-navbar/search-entry.js +52 -0
  107. package/dist/blocks/site-navbar/site-navbar.d.ts +212 -0
  108. package/dist/blocks/site-navbar/site-navbar.d.ts.map +1 -0
  109. package/dist/blocks/site-navbar/site-navbar.js +61 -0
  110. package/dist/blocks/site-navbar/sites-menu.d.ts +40 -0
  111. package/dist/blocks/site-navbar/sites-menu.d.ts.map +1 -0
  112. package/dist/blocks/site-navbar/sites-menu.js +48 -0
  113. package/dist/blocks/site-navbar/theme-menu.d.ts +47 -0
  114. package/dist/blocks/site-navbar/theme-menu.d.ts.map +1 -0
  115. package/dist/blocks/site-navbar/theme-menu.js +55 -0
  116. package/dist/blocks/stack-grid-01/index.d.ts +2 -2
  117. package/dist/blocks/stack-grid-01/index.js +1 -1
  118. package/dist/blocks/stack-grid-01/stack-grid.d.ts +42 -11
  119. package/dist/blocks/stack-grid-01/stack-grid.d.ts.map +1 -1
  120. package/dist/blocks/stack-grid-01/stack-grid.js +15 -7
  121. package/dist/blocks/stats-01/index.d.ts +9 -1
  122. package/dist/blocks/stats-01/index.d.ts.map +1 -1
  123. package/dist/blocks/stats-01/index.js +8 -1
  124. package/dist/blocks/stats-01/stats.d.ts +1 -1
  125. package/dist/blocks/stats-01/stats.js +2 -2
  126. package/dist/blocks/status-ledger-01/index.d.ts +2 -2
  127. package/dist/blocks/status-ledger-01/index.js +1 -1
  128. package/dist/blocks/status-ledger-01/status-ledger.d.ts +1 -1
  129. package/dist/blocks/status-ledger-01/status-ledger.js +2 -2
  130. package/dist/catalog.d.ts +1 -1
  131. package/dist/catalog.d.ts.map +1 -1
  132. package/dist/catalog.js +473 -1
  133. package/dist/components/index.d.ts +104 -66
  134. package/dist/components/index.d.ts.map +1 -1
  135. package/dist/components/index.js +74 -40
  136. package/dist/components/ui/accordion.js +1 -1
  137. package/dist/components/ui/alert-dialog.d.ts +119 -0
  138. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  139. package/dist/components/ui/alert-dialog.js +82 -0
  140. package/dist/components/ui/alert.js +1 -1
  141. package/dist/components/ui/aspect-ratio.d.ts +60 -0
  142. package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
  143. package/dist/components/ui/aspect-ratio.js +61 -0
  144. package/dist/components/ui/avatar.js +1 -1
  145. package/dist/components/ui/badge.js +1 -1
  146. package/dist/components/ui/breadcrumb.js +1 -1
  147. package/dist/components/ui/button-group.d.ts +68 -0
  148. package/dist/components/ui/button-group.d.ts.map +1 -0
  149. package/dist/components/ui/button-group.js +54 -0
  150. package/dist/components/ui/button.js +1 -1
  151. package/dist/components/ui/calendar.d.ts +158 -0
  152. package/dist/components/ui/calendar.d.ts.map +1 -0
  153. package/dist/components/ui/calendar.js +216 -0
  154. package/dist/components/ui/card.d.ts +18 -6
  155. package/dist/components/ui/card.d.ts.map +1 -1
  156. package/dist/components/ui/card.js +23 -8
  157. package/dist/components/ui/carousel.d.ts +118 -0
  158. package/dist/components/ui/carousel.d.ts.map +1 -0
  159. package/dist/components/ui/carousel.js +152 -0
  160. package/dist/components/ui/chart-frame.d.ts +216 -0
  161. package/dist/components/ui/chart-frame.d.ts.map +1 -0
  162. package/dist/components/ui/chart-frame.js +286 -0
  163. package/dist/components/ui/checkbox.js +1 -1
  164. package/dist/components/ui/collapsible.d.ts +97 -0
  165. package/dist/components/ui/collapsible.d.ts.map +1 -0
  166. package/dist/components/ui/collapsible.js +54 -0
  167. package/dist/components/ui/combobox.d.ts +141 -0
  168. package/dist/components/ui/combobox.d.ts.map +1 -0
  169. package/dist/components/ui/combobox.js +243 -0
  170. package/dist/components/ui/command-palette.d.ts +106 -0
  171. package/dist/components/ui/command-palette.d.ts.map +1 -0
  172. package/dist/components/ui/command-palette.js +176 -0
  173. package/dist/components/ui/command.d.ts +82 -0
  174. package/dist/components/ui/command.d.ts.map +1 -0
  175. package/dist/components/ui/command.js +37 -0
  176. package/dist/components/ui/context-menu.d.ts +151 -0
  177. package/dist/components/ui/context-menu.d.ts.map +1 -0
  178. package/dist/components/ui/context-menu.js +97 -0
  179. package/dist/components/ui/cta-link.js +1 -1
  180. package/dist/components/ui/date-picker.d.ts +111 -0
  181. package/dist/components/ui/date-picker.d.ts.map +1 -0
  182. package/dist/components/ui/date-picker.js +74 -0
  183. package/dist/components/ui/diagram.d.ts +84 -15
  184. package/dist/components/ui/diagram.d.ts.map +1 -1
  185. package/dist/components/ui/diagram.js +77 -7
  186. package/dist/components/ui/dialog.js +1 -1
  187. package/dist/components/ui/diff.d.ts +105 -0
  188. package/dist/components/ui/diff.d.ts.map +1 -0
  189. package/dist/components/ui/diff.js +113 -0
  190. package/dist/components/ui/dropdown-menu.d.ts +18 -0
  191. package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
  192. package/dist/components/ui/dropdown-menu.js +1 -1
  193. package/dist/components/ui/fact-list.js +1 -1
  194. package/dist/components/ui/field.js +1 -1
  195. package/dist/components/ui/form.d.ts +151 -0
  196. package/dist/components/ui/form.d.ts.map +1 -0
  197. package/dist/components/ui/form.js +73 -0
  198. package/dist/components/ui/hover-card.d.ts +105 -0
  199. package/dist/components/ui/hover-card.d.ts.map +1 -0
  200. package/dist/components/ui/hover-card.js +49 -0
  201. package/dist/components/ui/input-group.d.ts +99 -0
  202. package/dist/components/ui/input-group.d.ts.map +1 -0
  203. package/dist/components/ui/input-group.js +83 -0
  204. package/dist/components/ui/input.js +1 -1
  205. package/dist/components/ui/item.d.ts +113 -0
  206. package/dist/components/ui/item.d.ts.map +1 -0
  207. package/dist/components/ui/item.js +61 -0
  208. package/dist/components/ui/kbd.js +1 -1
  209. package/dist/components/ui/label.d.ts +58 -0
  210. package/dist/components/ui/label.d.ts.map +1 -0
  211. package/dist/components/ui/label.js +29 -0
  212. package/dist/components/ui/live-region.d.ts +9 -0
  213. package/dist/components/ui/live-region.d.ts.map +1 -1
  214. package/dist/components/ui/live-region.js +10 -2
  215. package/dist/components/ui/mark.js +1 -1
  216. package/dist/components/ui/menubar.d.ts +197 -0
  217. package/dist/components/ui/menubar.d.ts.map +1 -0
  218. package/dist/components/ui/menubar.js +109 -0
  219. package/dist/components/ui/meter.d.ts +121 -0
  220. package/dist/components/ui/meter.d.ts.map +1 -0
  221. package/dist/components/ui/meter.js +82 -0
  222. package/dist/components/ui/native-select.d.ts +72 -0
  223. package/dist/components/ui/native-select.d.ts.map +1 -0
  224. package/dist/components/ui/native-select.js +55 -0
  225. package/dist/components/ui/navigation-menu.d.ts +186 -0
  226. package/dist/components/ui/navigation-menu.d.ts.map +1 -0
  227. package/dist/components/ui/navigation-menu.js +94 -0
  228. package/dist/components/ui/number-field.d.ts +111 -0
  229. package/dist/components/ui/number-field.d.ts.map +1 -0
  230. package/dist/components/ui/number-field.js +84 -0
  231. package/dist/components/ui/one-time-code.d.ts +127 -0
  232. package/dist/components/ui/one-time-code.d.ts.map +1 -0
  233. package/dist/components/ui/one-time-code.js +57 -0
  234. package/dist/components/ui/pagination.js +1 -1
  235. package/dist/components/ui/popover.js +1 -1
  236. package/dist/components/ui/product-mark.d.ts +1 -1
  237. package/dist/components/ui/product-mark.js +1 -1
  238. package/dist/components/ui/product-switcher.d.ts +1 -1
  239. package/dist/components/ui/product-switcher.js +2 -2
  240. package/dist/components/ui/progress.js +1 -1
  241. package/dist/components/ui/prose.js +1 -1
  242. package/dist/components/ui/pulse-graph.js +1 -1
  243. package/dist/components/ui/pulse-series.js +1 -1
  244. package/dist/components/ui/radio-group.js +1 -1
  245. package/dist/components/ui/resizable.d.ts +129 -0
  246. package/dist/components/ui/resizable.d.ts.map +1 -0
  247. package/dist/components/ui/resizable.js +205 -0
  248. package/dist/components/ui/scroll-area.d.ts +97 -0
  249. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  250. package/dist/components/ui/scroll-area.js +68 -0
  251. package/dist/components/ui/search-dialog.d.ts +139 -0
  252. package/dist/components/ui/search-dialog.d.ts.map +1 -0
  253. package/dist/components/ui/search-dialog.js +178 -0
  254. package/dist/components/ui/section.d.ts +26 -0
  255. package/dist/components/ui/section.d.ts.map +1 -1
  256. package/dist/components/ui/section.js +32 -1
  257. package/dist/components/ui/select.js +1 -1
  258. package/dist/components/ui/separator.js +1 -1
  259. package/dist/components/ui/sheet.d.ts +103 -0
  260. package/dist/components/ui/sheet.d.ts.map +1 -0
  261. package/dist/components/ui/sheet.js +58 -0
  262. package/dist/components/ui/sidebar.d.ts +228 -0
  263. package/dist/components/ui/sidebar.d.ts.map +1 -0
  264. package/dist/components/ui/sidebar.js +172 -0
  265. package/dist/components/ui/signal-field.js +1 -1
  266. package/dist/components/ui/skeleton.js +1 -1
  267. package/dist/components/ui/slider.js +1 -1
  268. package/dist/components/ui/spinner.d.ts +79 -0
  269. package/dist/components/ui/spinner.d.ts.map +1 -0
  270. package/dist/components/ui/spinner.js +79 -0
  271. package/dist/components/ui/switch.js +1 -1
  272. package/dist/components/ui/table-sort.d.ts +97 -0
  273. package/dist/components/ui/table-sort.d.ts.map +1 -0
  274. package/dist/components/ui/table-sort.js +102 -0
  275. package/dist/components/ui/table.js +1 -1
  276. package/dist/components/ui/tabs.js +1 -1
  277. package/dist/components/ui/textarea.js +1 -1
  278. package/dist/components/ui/timeline.d.ts +106 -0
  279. package/dist/components/ui/timeline.d.ts.map +1 -0
  280. package/dist/components/ui/timeline.js +111 -0
  281. package/dist/components/ui/toast.d.ts +219 -0
  282. package/dist/components/ui/toast.d.ts.map +1 -0
  283. package/dist/components/ui/toast.js +270 -0
  284. package/dist/components/ui/toggle-group.d.ts +118 -0
  285. package/dist/components/ui/toggle-group.d.ts.map +1 -0
  286. package/dist/components/ui/toggle-group.js +194 -0
  287. package/dist/components/ui/toggle.d.ts +73 -0
  288. package/dist/components/ui/toggle.d.ts.map +1 -0
  289. package/dist/components/ui/toggle.js +44 -0
  290. package/dist/components/ui/tooltip.js +1 -1
  291. package/dist/components/ui/tree.js +1 -1
  292. package/dist/components/ui/typography.js +1 -1
  293. package/dist/fonts/Inter-OFL.txt +93 -93
  294. package/dist/index.d.ts +72 -70
  295. package/dist/index.d.ts.map +1 -1
  296. package/dist/index.js +61 -60
  297. package/dist/lib/rank.d.ts +69 -0
  298. package/dist/lib/rank.d.ts.map +1 -0
  299. package/dist/lib/rank.js +77 -0
  300. package/dist/live/index.d.ts +22 -0
  301. package/dist/live/index.d.ts.map +1 -0
  302. package/dist/live/index.js +20 -0
  303. package/dist/live/run-stream-01/index.d.ts +3 -0
  304. package/dist/live/run-stream-01/index.d.ts.map +1 -0
  305. package/dist/live/run-stream-01/index.js +1 -0
  306. package/dist/live/run-stream-01/run-stream.d.ts +125 -0
  307. package/dist/live/run-stream-01/run-stream.d.ts.map +1 -0
  308. package/dist/live/run-stream-01/run-stream.js +111 -0
  309. package/dist/pages/auth-page/index.d.ts +15 -2
  310. package/dist/pages/auth-page/index.d.ts.map +1 -1
  311. package/dist/pages/auth-page/index.js +8 -4
  312. package/dist/pages/blog-post-page/index.js +5 -5
  313. package/dist/pages/dashboard-page/index.d.ts +4 -4
  314. package/dist/pages/dashboard-page/index.js +4 -4
  315. package/dist/pages/docs-shell/docs-shell.d.ts +37 -4
  316. package/dist/pages/docs-shell/docs-shell.d.ts.map +1 -1
  317. package/dist/pages/docs-shell/docs-shell.js +107 -16
  318. package/dist/pages/docs-shell/index.d.ts +2 -2
  319. package/dist/pages/docs-shell/index.js +1 -1
  320. package/dist/pages/index.d.ts +14 -14
  321. package/dist/pages/index.js +7 -7
  322. package/dist/pages/marketing-page/index.d.ts +6 -6
  323. package/dist/pages/marketing-page/index.js +5 -5
  324. package/dist/pages/not-found-page/index.js +4 -4
  325. package/dist/pages/settings-page/index.d.ts +3 -3
  326. package/dist/pages/settings-page/index.js +4 -4
  327. package/dist/provider/index.d.ts +4 -4
  328. package/dist/provider/index.js +2 -2
  329. package/dist/provider/provider.d.ts +1 -1
  330. package/dist/provider/provider.js +1 -1
  331. package/dist/provider/theme-script.d.ts +1 -1
  332. package/dist/provider/theme-script.js +1 -1
  333. package/dist/styles.css +689 -0
  334. package/package.json +9 -1
@@ -0,0 +1,97 @@
1
+ import type { ComponentProps, ReactNode } from 'react';
2
+ /** Which axis the region is expected to scroll on. */
3
+ export type ScrollAreaOrientation = 'vertical' | 'horizontal' | 'both';
4
+ /** The props the ScrollArea accepts. */
5
+ export interface ScrollAreaProps extends Omit<ComponentProps<'div'>, 'children'> {
6
+ /**
7
+ * The content the region scrolls through.
8
+ *
9
+ * Ordinary children, not a render prop and not a measured height. A consumer
10
+ * sizes the region from the outside with `className` and puts whatever they like
11
+ * inside, which is the arrangement that lets a region hold a table, a list of
12
+ * anything, or a form.
13
+ */
14
+ children?: ReactNode;
15
+ /**
16
+ * Which scrollbars to draw.
17
+ *
18
+ * `both` is the default rather than `vertical` because a region that overflows
19
+ * on an axis nobody asked about is still a region a reader has to be able to
20
+ * scroll on that axis, and a scrollbar that is missing is a scrollbar that has
21
+ * to be found in the browser's own chrome, which is the thing this Component
22
+ * exists to replace. Pass `vertical` when the content is known never to scroll
23
+ * sideways, and the horizontal bar is not drawn at all.
24
+ */
25
+ orientation?: ScrollAreaOrientation;
26
+ /**
27
+ * Keeps the scrollbar in the DOM when the region has nothing to scroll.
28
+ *
29
+ * Off by default, and that default is the decision: a region with nothing to
30
+ * scroll has no scroll position, so a bar drawn beside it is a claim that there
31
+ * is something past the edge. Turn it on only where a region is expected to
32
+ * grow and the reader should see the affordance before the content arrives.
33
+ */
34
+ keepMounted?: boolean;
35
+ /**
36
+ * The accessible name of the region.
37
+ *
38
+ * Required rather than defaulted. A scroll region with no name is announced as
39
+ * a group of focusable nothing, and a page with three of them is a page where a
40
+ * keyboard reader tabs into a region and cannot tell what is in it or what is
41
+ * past the edge. The name is the caller's word.
42
+ */
43
+ label: string;
44
+ /** Layout only. */
45
+ className?: string;
46
+ }
47
+ /**
48
+ * A scrollable region whose scrollbar belongs to this design system.
49
+ *
50
+ * **The browser's scrollbar is the one piece of a page that belongs to no design
51
+ * system at all.** Every other surface a consumer draws is a token from here, and
52
+ * the scrollbar beside it is a rectangle the operating system drew, in a colour
53
+ * chosen for the OS's own background rather than for the card the content is on.
54
+ * It is also the only control on a page that is resized by dragging, and the only
55
+ * one that appears and disappears on a timer, so the browser's version is the one
56
+ * control a design system cannot restyle at all. This is where the package takes
57
+ * it over, and it is worth saying plainly that the native scrollbar is not
58
+ * available to restyle: `::-webkit-scrollbar` is one engine, `scrollbar-width` is
59
+ * two values, and neither can express a track and a thumb in tokens.
60
+ *
61
+ * **The scrolling is still the browser's.** This is the decision that matters most
62
+ * and the one worth arguing with, so here is the argument. The tempting version of
63
+ * this Component is a `div` with `overflow: hidden` whose content is moved by a
64
+ * transform on a wheel handler, with a `div` above it pretending to be a
65
+ * scrollbar. That version has no scroll position at all, so there is nothing for
66
+ * a keyboard to move, nothing for a screen reader to announce, no momentum, no
67
+ * scroll-into-view, and no way for a reader to know how far through they are. A
68
+ * custom scrollbar that is a `div` is worse than the browser's, because the browser
69
+ * gives a reader all of that for free and the `div` version takes it away.
70
+ *
71
+ * So the region here is a real scrollable element in the document: the browser
72
+ * scrolls it, the browser gives it keyboard focus when it overflows, the arrow
73
+ * keys, Page Up, Page Down, Home and End all work because it is a scroll
74
+ * container and not a picture of one. Only the appearance of the bar is taken
75
+ * over, and the bar is a real control layered over the native one rather than a
76
+ * replacement for the scrolling.
77
+ *
78
+ * **A native scrollbar cannot be styled, so this one is a sibling of the scroll
79
+ * container rather than a part of it.** That is the whole reason a consumer cannot
80
+ * assemble this from `overflow-auto` and a coloured box, and it is why the region
81
+ * is a named landmark: a scrollable region that a keyboard reader can tab into and
82
+ * that has no name is a tab stop that says nothing. The name is a prop.
83
+ *
84
+ * **A region with nothing to scroll draws no scrollbar.** Not a disabled one and
85
+ * not a faint one: a bar beside a region with no overflow is a claim that there is
86
+ * something past the edge, and a reader who is told that twice loses trust in the
87
+ * third bar they meet. The bar is absent, and a caller who wants it present before
88
+ * the content arrives says so with `keepMounted`.
89
+ *
90
+ * It is a client Component, because measuring overflow and positioning a thumb is
91
+ * a measurement and a measurement is not something a server can do. Everything it
92
+ * draws is a semantic utility, so a scoped pack boundary restyles the thumb and the
93
+ * track through the cascade exactly as it restyles the text beside them.
94
+ */
95
+ declare function ScrollArea({ className, children, orientation, keepMounted, label, ...props }: ScrollAreaProps): import("react").JSX.Element;
96
+ export { ScrollArea };
97
+ //# sourceMappingURL=scroll-area.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll-area.d.ts","sourceRoot":"","sources":["../../../src/components/ui/scroll-area.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItD,sDAAsD;AACtD,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAA;AAEtE,wCAAwC;AACxC,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAC9E;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,QAAQ,EACR,WAAoB,EACpB,WAAmB,EACnB,KAAK,EACL,GAAG,KAAK,EACT,EAAE,eAAe,+BAiEjB;AAED,OAAO,EAAE,UAAU,EAAE,CAAA"}
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ScrollArea as ScrollAreaPrimitive } from '@base-ui/react/scroll-area';
4
+ import { cn } from '../../lib/utils.js';
5
+ /**
6
+ * A scrollable region whose scrollbar belongs to this design system.
7
+ *
8
+ * **The browser's scrollbar is the one piece of a page that belongs to no design
9
+ * system at all.** Every other surface a consumer draws is a token from here, and
10
+ * the scrollbar beside it is a rectangle the operating system drew, in a colour
11
+ * chosen for the OS's own background rather than for the card the content is on.
12
+ * It is also the only control on a page that is resized by dragging, and the only
13
+ * one that appears and disappears on a timer, so the browser's version is the one
14
+ * control a design system cannot restyle at all. This is where the package takes
15
+ * it over, and it is worth saying plainly that the native scrollbar is not
16
+ * available to restyle: `::-webkit-scrollbar` is one engine, `scrollbar-width` is
17
+ * two values, and neither can express a track and a thumb in tokens.
18
+ *
19
+ * **The scrolling is still the browser's.** This is the decision that matters most
20
+ * and the one worth arguing with, so here is the argument. The tempting version of
21
+ * this Component is a `div` with `overflow: hidden` whose content is moved by a
22
+ * transform on a wheel handler, with a `div` above it pretending to be a
23
+ * scrollbar. That version has no scroll position at all, so there is nothing for
24
+ * a keyboard to move, nothing for a screen reader to announce, no momentum, no
25
+ * scroll-into-view, and no way for a reader to know how far through they are. A
26
+ * custom scrollbar that is a `div` is worse than the browser's, because the browser
27
+ * gives a reader all of that for free and the `div` version takes it away.
28
+ *
29
+ * So the region here is a real scrollable element in the document: the browser
30
+ * scrolls it, the browser gives it keyboard focus when it overflows, the arrow
31
+ * keys, Page Up, Page Down, Home and End all work because it is a scroll
32
+ * container and not a picture of one. Only the appearance of the bar is taken
33
+ * over, and the bar is a real control layered over the native one rather than a
34
+ * replacement for the scrolling.
35
+ *
36
+ * **A native scrollbar cannot be styled, so this one is a sibling of the scroll
37
+ * container rather than a part of it.** That is the whole reason a consumer cannot
38
+ * assemble this from `overflow-auto` and a coloured box, and it is why the region
39
+ * is a named landmark: a scrollable region that a keyboard reader can tab into and
40
+ * that has no name is a tab stop that says nothing. The name is a prop.
41
+ *
42
+ * **A region with nothing to scroll draws no scrollbar.** Not a disabled one and
43
+ * not a faint one: a bar beside a region with no overflow is a claim that there is
44
+ * something past the edge, and a reader who is told that twice loses trust in the
45
+ * third bar they meet. The bar is absent, and a caller who wants it present before
46
+ * the content arrives says so with `keepMounted`.
47
+ *
48
+ * It is a client Component, because measuring overflow and positioning a thumb is
49
+ * a measurement and a measurement is not something a server can do. Everything it
50
+ * draws is a semantic utility, so a scoped pack boundary restyles the thumb and the
51
+ * track through the cascade exactly as it restyles the text beside them.
52
+ */
53
+ function ScrollArea({ className, children, orientation = 'both', keepMounted = false, label, ...props }) {
54
+ return (_jsxs(ScrollAreaPrimitive.Root, { "data-slot": "scroll-area", className: cn('relative', className), ...props, children: [_jsx(ScrollAreaPrimitive.Viewport, { "data-slot": "scroll-area-viewport", role: "group", "aria-label": label, className: cn('size-full overscroll-contain rounded-[inherit] focus-visible:ring-ring outline-none focus-visible:ring-[3px]'), children: children }), orientation === 'horizontal' ? null : (_jsx(ScrollAreaPrimitive.Scrollbar, { "data-slot": "scroll-area-scrollbar", orientation: "vertical", keepMounted: keepMounted,
55
+ /*
56
+ * Transparent until there is something to scroll, then visible on its
57
+ * own, while the pointer is over the region, and while it is being
58
+ * scrolled. The `bg-transparent` is a declared absence rather than a
59
+ * missing class: the track is the surface the content is already on, so
60
+ * drawing one would put a second surface between the reader and the
61
+ * content for no information. The thumb is the only part that carries
62
+ * ink, and it is `bg-border` at rest and the full muted ink on hover and
63
+ * during a scroll, so it is visible against a light card and a dark one
64
+ * without a value that belongs to neither.
65
+ */
66
+ className: "flex w-2 touch-none select-none p-px opacity-0 transition-opacity duration-fast ease-out data-[has-overflow-y]:opacity-100 data-[hovering]:opacity-100 data-[scrolling]:opacity-100", children: _jsx(ScrollAreaPrimitive.Thumb, { "data-slot": "scroll-area-thumb", className: "bg-border relative flex-1 rounded-full transition-colors duration-fast ease-out data-[hovering]:bg-muted-foreground data-[scrolling]:bg-muted-foreground" }) })), orientation === 'vertical' ? null : (_jsx(ScrollAreaPrimitive.Scrollbar, { "data-slot": "scroll-area-scrollbar", orientation: "horizontal", keepMounted: keepMounted, className: "flex h-2 touch-none select-none flex-col p-px opacity-0 transition-opacity duration-fast ease-out data-[has-overflow-x]:opacity-100 data-[hovering]:opacity-100 data-[scrolling]:opacity-100", children: _jsx(ScrollAreaPrimitive.Thumb, { "data-slot": "scroll-area-thumb", className: "bg-border relative flex-1 rounded-full transition-colors duration-fast ease-out data-[hovering]:bg-muted-foreground data-[scrolling]:bg-muted-foreground" }) }))] }));
67
+ }
68
+ export { ScrollArea };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * One page in a search index.
3
+ *
4
+ * The four fields a result is drawn from, and nothing else. A consumer's index is
5
+ * a JSON array of these served from a static route, so the shape is a contract
6
+ * between a build step Prism does not own and a dialog it does, and it is
7
+ * declared here rather than inferred: an index that grew a field this Component
8
+ * ignored would be a field no reader could be shown and no build would report.
9
+ *
10
+ * `description` and `content` are both optional because the two halves of a site
11
+ * differ. A landing page has a standfirst and a body; a section index has a title
12
+ * and a route and nothing else, and an index entry that had to invent an empty
13
+ * string to satisfy a required field would be an index carrying a fact nobody
14
+ * wrote.
15
+ */
16
+ export type SearchIndexEntry = {
17
+ /** A stable key for the entry. The route is the usual choice and needs no coordination. */
18
+ id: string;
19
+ /** The result's own name, which is what the dialog puts on the first line. */
20
+ title: string;
21
+ /** Where the result goes. */
22
+ url: string;
23
+ /** One line of context, shown under the title when the index carries it. */
24
+ description?: string;
25
+ /** The body text the query is matched against. */
26
+ content?: string;
27
+ /** Where the result sits in the site's own hierarchy, shown as a trail. */
28
+ breadcrumbs?: string[];
29
+ };
30
+ /**
31
+ * Every sentence the dialog can say, as props.
32
+ *
33
+ * A Component ships no copy and no accessible name it did not receive, because a
34
+ * hardcoded string here is a claim every consumer of the package inherits and the
35
+ * corpus publishes as the design system's own voice. It is a prop per string
36
+ * rather than one `messages` object with defaults, so that overriding any of them
37
+ * is a decision a consumer makes visibly rather than a partial merge of a shipped
38
+ * object.
39
+ *
40
+ * The two result labels are a pair rather than one label with a number appended,
41
+ * and the reason is that this is the only place a count is spoken. English puts
42
+ * the number first and inflects the noun; most other written languages inflect the
43
+ * noun and put the number where their own grammar puts it. A Component that chose
44
+ * between "1 result" and "3 results" for a consumer would be choosing a language's
45
+ * word order on their behalf, and the two strings are the seam where that choice
46
+ * belongs to the site.
47
+ */
48
+ export type SearchDialogMessages = {
49
+ /** The accessible name of the control that dismisses the dialog. */
50
+ close: string;
51
+ /** What is shown while the index is being fetched. */
52
+ loading: string;
53
+ /** What is shown when the index could not be fetched. */
54
+ failed: string;
55
+ /** What is shown when the query matched nothing. */
56
+ empty: string;
57
+ /** The count announced when exactly one result matched. */
58
+ one: string;
59
+ /** The count announced when any other number of results matched. */
60
+ other: string;
61
+ };
62
+ /**
63
+ * The props SearchDialog takes.
64
+ *
65
+ * `indexUrl` is where the index is fetched from and it is required, because a
66
+ * dialog with nothing to search is a text field. It is a URL rather than an
67
+ * index object for the reason the whole search design turns on: the index is
68
+ * large, it is the same bytes on every page of a site, and a prop would serialize
69
+ * it into the document of every route. Fetched on the first keystroke instead, it
70
+ * is on the wire once and only for the reader who searched.
71
+ */
72
+ export type SearchDialogProps = {
73
+ /**
74
+ * Where the JSON index is served from. Fetched once, when the dialog opens.
75
+ *
76
+ * Opening rather than the first keystroke, because a reader who has opened a
77
+ * search box is already searching and the round trip is the difference between
78
+ * results on the first character and results a moment after it. The dialog is
79
+ * itself only mounted after a deliberate act, so "when the dialog opens" is
80
+ * still well after the page has painted and never on a page nobody searched.
81
+ */
82
+ indexUrl: string;
83
+ /** The accessible name of the dialog, and the placeholder in its field. */
84
+ label: string;
85
+ /** Every sentence the dialog can say. */
86
+ messages: SearchDialogMessages;
87
+ /** The text shown before the reader has typed anything. */
88
+ hint?: string;
89
+ /** Called when the reader dismisses the dialog, by Escape or by the backdrop. */
90
+ onClose: () => void;
91
+ /** The most results to draw. Further matches are dropped rather than paged. */
92
+ limit?: number;
93
+ /** Layout only. Changing a Prism-owned visual property from here is prohibited. */
94
+ className?: string;
95
+ };
96
+ /**
97
+ * A search dialog over a static index, computed in the browser.
98
+ *
99
+ * It is a Component and not a Block because it is a control that appears in a
100
+ * region rather than a region of a page, and because it is the one place in the
101
+ * search design that touches the network: the index is fetched here rather than
102
+ * passed in, so a site ships one index file and every page of that site reads it
103
+ * from the same bytes. `SiteNavbar` composes it behind a trigger, and a consumer
104
+ * that wants a different ranking can render this directly or pass its own control
105
+ * through the navbar's `actions` slot.
106
+ *
107
+ * **The ranking is a scorer, not a search engine, and the difference is the
108
+ * trade.** There is no stemming, no fuzzy matching and no typo tolerance: a query
109
+ * token matches a document when the token is a substring of one of its fields.
110
+ * That is a real downgrade against the inverted index a documentation site would
111
+ * otherwise use, and it is deliberate, because the alternative is a Component in
112
+ * a package whose entire value is that it depends on a design system and a token
113
+ * pipeline and nothing else. A fuzzy engine is a large dependency whose ranking a
114
+ * consumer cannot restyle, cannot audit against this package's gates and cannot
115
+ * replace without forking the dialog. The scorer is thirty lines, its behaviour
116
+ * is stated above, and a consumer whose content needs typo tolerance passes its
117
+ * own control instead.
118
+ *
119
+ * **Every query token has to match somewhere.** That is AND rather than OR, and
120
+ * it is why a two-word query narrows instead of widening: with OR, a reader
121
+ * searching for "docs search" is shown every page that mentions either word,
122
+ * which on a documentation site is most of them, and the list stops being a
123
+ * ranking and becomes a filter they have to work through themselves.
124
+ *
125
+ * The weights are per field and stated rather than tuned: a match in a title is
126
+ * worth more than one in a body, and a match at the start of a title is worth more
127
+ * than one at its end, because a reader who types the first word of a page's name
128
+ * is usually right about which page they meant. `FIELD_WEIGHTS` is the whole of
129
+ * the ranking and is a named constant so a reader can see the numbers rather than
130
+ * infer them.
131
+ *
132
+ * The field is focused on mount and Escape closes, and the backdrop closes, so
133
+ * every exit is a control rather than a gesture. The list is marked as a list of
134
+ * results and the count is announced through a live region, so a screen reader
135
+ * user learns that a search ran rather than hearing the list change under them.
136
+ */
137
+ export declare function SearchDialog({ indexUrl, label, messages, hint, onClose, limit, className, }: SearchDialogProps): import("react").JSX.Element;
138
+ export default SearchDialog;
139
+ //# sourceMappingURL=search-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-dialog.d.ts","sourceRoot":"","sources":["../../../src/components/ui/search-dialog.tsx"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,2FAA2F;IAC3F,EAAE,EAAE,MAAM,CAAA;IACV,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAA;IACb,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAA;IACf,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAA;IACd,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAA;IACb,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;OAQG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAA;IACb,yCAAyC;IACzC,QAAQ,EAAE,oBAAoB,CAAA;IAC9B,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iFAAiF;IACjF,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,KAAU,EACV,SAAS,GACV,EAAE,iBAAiB,+BA6JnB;AA+ED,eAAe,YAAY,CAAA"}
@@ -0,0 +1,178 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { Search, X } from 'lucide-react';
5
+ /**
6
+ * A search dialog over a static index, computed in the browser.
7
+ *
8
+ * It is a Component and not a Block because it is a control that appears in a
9
+ * region rather than a region of a page, and because it is the one place in the
10
+ * search design that touches the network: the index is fetched here rather than
11
+ * passed in, so a site ships one index file and every page of that site reads it
12
+ * from the same bytes. `SiteNavbar` composes it behind a trigger, and a consumer
13
+ * that wants a different ranking can render this directly or pass its own control
14
+ * through the navbar's `actions` slot.
15
+ *
16
+ * **The ranking is a scorer, not a search engine, and the difference is the
17
+ * trade.** There is no stemming, no fuzzy matching and no typo tolerance: a query
18
+ * token matches a document when the token is a substring of one of its fields.
19
+ * That is a real downgrade against the inverted index a documentation site would
20
+ * otherwise use, and it is deliberate, because the alternative is a Component in
21
+ * a package whose entire value is that it depends on a design system and a token
22
+ * pipeline and nothing else. A fuzzy engine is a large dependency whose ranking a
23
+ * consumer cannot restyle, cannot audit against this package's gates and cannot
24
+ * replace without forking the dialog. The scorer is thirty lines, its behaviour
25
+ * is stated above, and a consumer whose content needs typo tolerance passes its
26
+ * own control instead.
27
+ *
28
+ * **Every query token has to match somewhere.** That is AND rather than OR, and
29
+ * it is why a two-word query narrows instead of widening: with OR, a reader
30
+ * searching for "docs search" is shown every page that mentions either word,
31
+ * which on a documentation site is most of them, and the list stops being a
32
+ * ranking and becomes a filter they have to work through themselves.
33
+ *
34
+ * The weights are per field and stated rather than tuned: a match in a title is
35
+ * worth more than one in a body, and a match at the start of a title is worth more
36
+ * than one at its end, because a reader who types the first word of a page's name
37
+ * is usually right about which page they meant. `FIELD_WEIGHTS` is the whole of
38
+ * the ranking and is a named constant so a reader can see the numbers rather than
39
+ * infer them.
40
+ *
41
+ * The field is focused on mount and Escape closes, and the backdrop closes, so
42
+ * every exit is a control rather than a gesture. The list is marked as a list of
43
+ * results and the count is announced through a live region, so a screen reader
44
+ * user learns that a search ran rather than hearing the list change under them.
45
+ */
46
+ export function SearchDialog({ indexUrl, label, messages, hint, onClose, limit = 12, className, }) {
47
+ const inputRef = useRef(null);
48
+ const [query, setQuery] = useState('');
49
+ const [entries, setEntries] = useState(null);
50
+ const [failed, setFailed] = useState(false);
51
+ useEffect(() => {
52
+ inputRef.current?.focus();
53
+ const onKeyDown = (event) => {
54
+ if (event.key === 'Escape')
55
+ onClose();
56
+ };
57
+ document.addEventListener('keydown', onKeyDown);
58
+ return () => document.removeEventListener('keydown', onKeyDown);
59
+ }, [onClose]);
60
+ /**
61
+ * The index, fetched once per dialog.
62
+ *
63
+ * The dialog is mounted only after a reader asks for search, so this runs after
64
+ * a deliberate act rather than on every page load, and the result is held for
65
+ * the life of the mount. A second fetch would be a second download of the same
66
+ * bytes for a reader who reopened the dialog, so `entries` is the cache and the
67
+ * `null` is what "not fetched yet" means.
68
+ *
69
+ * A failed fetch is a state rather than a throw. A reader whose network dropped
70
+ * is told the search is unavailable and still has the close control, which is
71
+ * the whole of what can be done here; an unhandled rejection would take the
72
+ * dialog's event handlers with it and leave a panel with no way out.
73
+ */
74
+ useEffect(() => {
75
+ if (entries !== null || failed)
76
+ return;
77
+ let live = true;
78
+ fetch(indexUrl)
79
+ .then((response) => {
80
+ if (!response.ok)
81
+ throw new Error(`search index responded ${response.status}`);
82
+ return response.json();
83
+ })
84
+ .then((value) => {
85
+ if (live)
86
+ setEntries(Array.isArray(value) ? value : []);
87
+ })
88
+ .catch(() => {
89
+ if (live)
90
+ setFailed(true);
91
+ });
92
+ return () => {
93
+ live = false;
94
+ };
95
+ }, [indexUrl, entries, failed]);
96
+ const results = useMemo(() => rank(entries, query, limit), [entries, query, limit]);
97
+ const typed = query.trim().length > 0;
98
+ const status = (() => {
99
+ if (!typed)
100
+ return hint ?? null;
101
+ if (failed)
102
+ return messages.failed;
103
+ if (entries === null)
104
+ return messages.loading;
105
+ if (results.length === 0)
106
+ return messages.empty;
107
+ return null;
108
+ })();
109
+ return (_jsx("div", { role: "presentation", onClick: onClose, className: "bg-foreground/40 fixed inset-0 z-50 flex items-start justify-center p-4 pt-[15vh]", children: _jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": label, onClick: (event) => event.stopPropagation(), className: 'border-border bg-popover text-popover-foreground flex max-h-[70vh] w-full max-w-xl flex-col overflow-hidden rounded-xl border shadow-lg ' +
110
+ (className ?? ''), children: [_jsxs("div", { className: "border-border flex items-center gap-2 border-b px-3", children: [_jsx(Search, { "aria-hidden": true, className: "text-muted-foreground size-4 shrink-0" }), _jsx("input", { ref: inputRef, value: query, onChange: (event) => setQuery(event.target.value), placeholder: label, "aria-label": label, className: "text-foreground h-12 flex-1 bg-transparent text-sm outline-none" }), _jsx("button", { type: "button", onClick: onClose, "aria-label": messages.close, className: "text-muted-foreground hover:text-foreground focus-visible:border-ring focus-visible:ring-ring rounded-md p-1 transition-colors focus-visible:ring-[3px] focus-visible:outline-none", children: _jsx(X, { "aria-hidden": true, className: "size-4" }) })] }), _jsxs("div", { className: "overflow-y-auto p-2", children: [_jsx("p", { "aria-live": "polite", className: "sr-only", children: typed ? (_jsxs(_Fragment, { children: [_jsx("span", { children: results.length }), ' ', _jsx("span", { children: results.length === 1 ? messages.one : messages.other })] })) : null }), status ? _jsx("p", { className: "text-muted-foreground px-3 py-6 text-sm", children: status }) : null, _jsx("ul", { className: "flex flex-col", children: results.map((result) => (_jsx("li", { children: _jsxs("a", { href: result.url, onClick: onClose, className: "hover:bg-accent focus-visible:border-ring focus-visible:ring-ring flex flex-col gap-0.5 rounded-lg px-3 py-2 transition-colors focus-visible:ring-[3px] focus-visible:outline-none", children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: result.title }), result.description ? (_jsx("span", { className: "text-muted-foreground line-clamp-2 text-xs", children: result.description })) : null, result.breadcrumbs?.length ? (_jsx("span", { className: "text-muted-foreground text-xs", children: result.breadcrumbs.join(' / ') })) : null] }) }, result.id))) })] })] }) }));
111
+ }
112
+ /**
113
+ * What a match in each field is worth, and the whole of the ranking.
114
+ *
115
+ * A title match outranks a description match, which outranks a body match, because
116
+ * a page's name is the claim it makes about itself and its body is the evidence.
117
+ * The route carries a small weight of its own because a site whose routes are
118
+ * words, which is most of them, is searchable by them.
119
+ */
120
+ const FIELD_WEIGHTS = { title: 40, url: 15, description: 10, content: 4 };
121
+ /** What a match at the very start of a field adds on top of its weight. */
122
+ const PREFIX_BONUS = 20;
123
+ function fold(value) {
124
+ return (value ?? '').toLowerCase();
125
+ }
126
+ /**
127
+ * One entry's score for one query, or `null` when a token matched nothing.
128
+ *
129
+ * `null` rather than zero is what makes the conjunction work: a caller summing
130
+ * per-token scores cannot tell "matched with a low score" from "did not match",
131
+ * so the miss is returned as its own value and the entry is dropped there.
132
+ */
133
+ function scoreEntry(entry, tokens) {
134
+ const fields = {
135
+ title: fold(entry.title),
136
+ url: fold(entry.url),
137
+ description: fold(entry.description),
138
+ content: fold(entry.content),
139
+ };
140
+ let total = 0;
141
+ for (const token of tokens) {
142
+ let best = 0;
143
+ for (const [field, value] of Object.entries(fields)) {
144
+ const at = value.indexOf(token);
145
+ if (at === -1)
146
+ continue;
147
+ const weight = FIELD_WEIGHTS[field];
148
+ best = Math.max(best, at === 0 ? weight + PREFIX_BONUS : weight);
149
+ }
150
+ if (best === 0)
151
+ return null;
152
+ total += best;
153
+ }
154
+ return total;
155
+ }
156
+ /**
157
+ * The index, narrowed by the query and cut to `limit`.
158
+ *
159
+ * The sort is by score and then by title, so two entries that match equally are
160
+ * in a stable order rather than in the order the index happened to be written.
161
+ * Without the second key a reordering of the build's page list would silently
162
+ * reorder equal-scoring results, which is the kind of change no test catches and
163
+ * every reader notices.
164
+ */
165
+ function rank(entries, query, limit) {
166
+ const tokens = fold(query).split(/\s+/).filter(Boolean);
167
+ if (entries === null || tokens.length === 0)
168
+ return [];
169
+ const scored = [];
170
+ for (const entry of entries) {
171
+ const score = scoreEntry(entry, tokens);
172
+ if (score !== null)
173
+ scored.push({ entry, score });
174
+ }
175
+ scored.sort((a, b) => b.score - a.score || a.entry.title.localeCompare(b.entry.title));
176
+ return scored.slice(0, limit).map((row) => row.entry);
177
+ }
178
+ export default SearchDialog;
@@ -20,6 +20,32 @@ export declare function Section({ className, children, ...props }: React.Compone
20
20
  * placed, not about the block, which is why it is a prop and not a default.
21
21
  */
22
22
  export type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
23
+ /**
24
+ * The level one step below a section's own heading, which is the level a title
25
+ * *inside* that section is composed at.
26
+ *
27
+ * **Why a function and not a second prop.** A block that draws a set of titled
28
+ * cards has two headings in it: the section's own, and one per card. Asking the
29
+ * caller for both is asking them to keep two levels in step by hand, and the two
30
+ * drift the moment a block is embedded one level deeper than it was written for.
31
+ * Deriving the child from the parent means a block moved from an `h2` section to an
32
+ * `h3` one carries its card titles with it, which is the whole reason
33
+ * `headingLevel` is a prop.
34
+ *
35
+ * This is what answers the question for every block that draws a titled card, so
36
+ * the answer is one function rather than six independent guesses. A block that
37
+ * hardcodes `h3` is right exactly once, at the nesting depth it was written for.
38
+ *
39
+ * **The clamp at `h6`, and why it is a clamp rather than a wrap.** A section
40
+ * already at `h6` has no child level, so a card title inside it is asked to be
41
+ * something it cannot be. Wrapping to `h1` would put a card's title above the
42
+ * section that introduces it, which is worse than a sibling of it: a reader
43
+ * navigating by heading would meet the card before the section it belongs to.
44
+ * Holding at `h6` costs a repeated level, which a screen reader announces as the
45
+ * same depth rather than as a break in the outline, and a `h6` section holding
46
+ * cards is already a document that has run out of levels.
47
+ */
48
+ export declare function childLevel(level: HeadingLevel): HeadingLevel;
23
49
  /**
24
50
  * The heading a section opens with: an optional eyebrow, the title, an optional
25
51
  * supporting line, and the level of the one heading it renders.
@@ -1 +1 @@
1
- {"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../../src/components/ui/section.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC;;;GAGG;AACH,wBAAgB,OAAO,CAAC,EACtB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,+BAWjC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAElE;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,EACP,KAAK,EACL,EAAE,EAAE,OAAc,EAClB,WAAW,EACX,KAAgB,EAChB,SAAS,GACV,EAAE;IACD;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,KAAK,EAAE,SAAS,CAAA;IAChB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IACzB;;;;;OAKG;IACH,EAAE,CAAC,EAAE,YAAY,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,+BA6BA"}
1
+ {"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../../src/components/ui/section.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC;;;GAGG;AACH,wBAAgB,OAAO,CAAC,EACtB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,+BAWjC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAKlE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,CAG5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,EACP,KAAK,EACL,EAAE,EAAE,OAAc,EAClB,WAAW,EACX,KAAgB,EAChB,SAAS,GACV,EAAE;IACD;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,KAAK,EAAE,SAAS,CAAA;IAChB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IACzB;;;;;OAKG;IACH,EAAE,CAAC,EAAE,YAAY,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,+BA6BA"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cn } from '../../lib/utils';
2
+ import { cn } from '../../lib/utils.js';
3
3
  /**
4
4
  * Container primitive. Every block composes its own section from this so vertical
5
5
  * rhythm stays consistent across the catalog instead of being re-invented per block.
@@ -7,6 +7,37 @@ import { cn } from '../../lib/utils';
7
7
  export function Section({ className, children, ...props }) {
8
8
  return (_jsx("section", { className: cn('py-16 sm:py-24', className), ...props, children: _jsx("div", { className: "mx-auto w-full max-w-6xl px-6 lg:px-8", children: children }) }));
9
9
  }
10
+ /** The six levels in outline order, so a level can be stepped rather than compared. */
11
+ const LEVELS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
12
+ /**
13
+ * The level one step below a section's own heading, which is the level a title
14
+ * *inside* that section is composed at.
15
+ *
16
+ * **Why a function and not a second prop.** A block that draws a set of titled
17
+ * cards has two headings in it: the section's own, and one per card. Asking the
18
+ * caller for both is asking them to keep two levels in step by hand, and the two
19
+ * drift the moment a block is embedded one level deeper than it was written for.
20
+ * Deriving the child from the parent means a block moved from an `h2` section to an
21
+ * `h3` one carries its card titles with it, which is the whole reason
22
+ * `headingLevel` is a prop.
23
+ *
24
+ * This is what answers the question for every block that draws a titled card, so
25
+ * the answer is one function rather than six independent guesses. A block that
26
+ * hardcodes `h3` is right exactly once, at the nesting depth it was written for.
27
+ *
28
+ * **The clamp at `h6`, and why it is a clamp rather than a wrap.** A section
29
+ * already at `h6` has no child level, so a card title inside it is asked to be
30
+ * something it cannot be. Wrapping to `h1` would put a card's title above the
31
+ * section that introduces it, which is worse than a sibling of it: a reader
32
+ * navigating by heading would meet the card before the section it belongs to.
33
+ * Holding at `h6` costs a repeated level, which a screen reader announces as the
34
+ * same depth rather than as a break in the outline, and a `h6` section holding
35
+ * cards is already a document that has run out of levels.
36
+ */
37
+ export function childLevel(level) {
38
+ const at = LEVELS.indexOf(level);
39
+ return LEVELS[Math.min(at + 1, LEVELS.length - 1)];
40
+ }
10
41
  /**
11
42
  * The heading a section opens with: an optional eyebrow, the title, an optional
12
43
  * supporting line, and the level of the one heading it renders.
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Select as SelectPrimitive } from '@base-ui/react/select';
4
4
  import { CheckIcon, ChevronDownIcon } from 'lucide-react';
5
- import { cn } from '../../lib/utils';
5
+ import { cn } from '../../lib/utils.js';
6
6
  /**
7
7
  * A control for choosing one option from a list that opens on demand.
8
8
  *
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { cn } from '../../lib/utils';
2
+ import { cn } from '../../lib/utils.js';
3
3
  /**
4
4
  * A hairline rule that divides content.
5
5
  *