@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,176 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
4
+ import { Dialog, DialogContent, DialogTitle } from './dialog.js';
5
+ import { locate, RANKS } from '../../lib/rank.js';
6
+ import { cn } from '../../lib/utils.js';
7
+ /**
8
+ * A searchable list of commands, over the Dialog this system already has.
9
+ *
10
+ * **It ranks, and that is the difference between this and a filtered menu.** A
11
+ * palette that filters without ordering shows every command containing the query
12
+ * in whatever order the groups happened to be declared, so the command you meant
13
+ * sits below one that merely mentions your query. Ranking by where the match falls
14
+ * puts it first, and the whole premise of a palette is that typing three letters
15
+ * is faster than navigating to it.
16
+ *
17
+ * **It is composed on the Dialog rather than beside it.** Focus trapping, Escape,
18
+ * the portal, the scroll lock and the return of focus to the trigger are five
19
+ * behaviours that are correct in the Dialog and would be five chances to get one
20
+ * wrong here. A palette that rolled its own overlay would be a second answer to
21
+ * all five questions, and the second answer is the one that ships the bug.
22
+ *
23
+ * **The matched run is emphasised by weight, not by a background.** A Mark is the
24
+ * right treatment in a list of results, where the match is the reason the row is
25
+ * there and a full background is the point. In a palette the match is a hint while
26
+ * the row's label is what the reader is reading, and a saturated background on
27
+ * every matched character fights the text it is inside. So the palette uses the
28
+ * quieter of the two, and the two surfaces are not the same surface. See the Mark
29
+ * documentation for the other half of that decision.
30
+ *
31
+ * **The search field keeps focus for the whole interaction.** Arrows move a
32
+ * highlight through the list rather than moving focus into it, so a reader can keep
33
+ * typing while they choose, and the highlighted row carries the state. That is the
34
+ * combobox pattern and it is the reason the arrows do not steal the caret.
35
+ */
36
+ function CommandPalette({ open, onOpenChange, label, inputLabel, placeholder, groups, suggest, empty, className, }) {
37
+ const [query, setQuery] = useState('');
38
+ const [active, setActive] = useState(0);
39
+ const listRef = useRef(null);
40
+ const titleId = useId();
41
+ const trimmed = query.trim();
42
+ /**
43
+ * The ranked result: groups in their declared order, items within a group by
44
+ * rank. A group whose every item filtered out disappears rather than rendering
45
+ * an empty heading, because a heading over nothing is a divider a reader has to
46
+ * read past.
47
+ */
48
+ const results = useMemo(() => {
49
+ if (trimmed === '') {
50
+ // With nothing typed there is nothing to rank, so every row is a row with no
51
+ // match to emphasise. Suggestions replace the groups rather than joining
52
+ // them, because a palette that opens showing both a suggestion list and the
53
+ // full command list is a palette with two answers to what it is for.
54
+ return suggest
55
+ ? [
56
+ {
57
+ id: '__suggest',
58
+ label: suggest.label,
59
+ hits: suggest.items.map((item) => ({ item, rank: RANKS.prefix })),
60
+ },
61
+ ]
62
+ : groups.map((group) => ({
63
+ id: group.id,
64
+ label: group.label,
65
+ hits: group.items.map((item) => ({ item, rank: RANKS.prefix })),
66
+ }));
67
+ }
68
+ const scored = [];
69
+ for (const group of groups) {
70
+ const hits = group.items
71
+ .map((item) => {
72
+ const inLabel = locate(item.label, trimmed);
73
+ if (inLabel.rank < RANKS.keyword)
74
+ return { item, rank: inLabel.rank, range: inLabel.range };
75
+ // Keywords are searched only after the label misses, so a keyword can
76
+ // never outrank a label match. A reader who typed the name of a command
77
+ // wants that command, and a synonym is a fallback rather than a rival.
78
+ const keywordHit = (item.keywords ?? []).some((word) => word.toLowerCase().includes(trimmed.toLowerCase()));
79
+ return keywordHit ? { item, rank: RANKS.keyword } : null;
80
+ })
81
+ .filter((hit) => hit !== null)
82
+ .sort((a, b) => a.rank - b.rank);
83
+ if (hits.length > 0)
84
+ scored.push({ id: group.id, label: group.label, hits });
85
+ }
86
+ // The groups are ordered by their best hit, not by where they were declared.
87
+ // Ranking only *within* a group leaves the premise of the surface broken: a
88
+ // command in a later group that matches exactly sits below a command in an
89
+ // earlier group that matches badly, so a reader still scans. Ordering the
90
+ // groups by their strongest member puts the best answer at the top and keeps
91
+ // the headings, which is the whole of what a grouped list is for. The sort is
92
+ // stable, so groups whose best hits tie keep the order they were declared in.
93
+ return scored.sort((a, b) => Math.min(...a.hits.map((hit) => hit.rank)) - Math.min(...b.hits.map((hit) => hit.rank)));
94
+ }, [groups, suggest, trimmed]);
95
+ /** The flat order, which is the order the arrows walk. */
96
+ const flat = useMemo(() => results.flatMap((group) => group.hits.map((hit) => hit.item)), [results]);
97
+ // The highlight is clamped rather than reset on every keystroke. Resetting to
98
+ // zero is the obvious choice and it is wrong: a reader who arrows down three
99
+ // rows and then types one more character is choosing from a list that just
100
+ // changed under them, and yanking the highlight back to the top discards where
101
+ // they were. Clamping keeps the position when it still exists.
102
+ const activeIndex = Math.min(active, Math.max(flat.length - 1, 0));
103
+ useEffect(() => {
104
+ if (!open) {
105
+ // Reset on close rather than on open, so the palette does not reopen showing
106
+ // the last query typed into it. A surface that remembers its filter between
107
+ // visits is a surface whose state the reader has to reason about.
108
+ setQuery('');
109
+ setActive(0);
110
+ }
111
+ }, [open]);
112
+ const run = useCallback((item) => {
113
+ onOpenChange(false);
114
+ item.onSelect();
115
+ }, [onOpenChange]);
116
+ const onKeyDown = (event) => {
117
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
118
+ if (flat.length === 0)
119
+ return;
120
+ event.preventDefault();
121
+ // Wraps, and that is the one place a list is allowed to: a list that stops
122
+ // at the end makes a reader who overshot press Up to come back, and a
123
+ // palette is a transient surface where overshoot is common.
124
+ setActive((index) => {
125
+ const next = event.key === 'ArrowDown' ? index + 1 : index - 1;
126
+ return (next + flat.length) % flat.length;
127
+ });
128
+ return;
129
+ }
130
+ if (event.key === 'Enter') {
131
+ const item = flat[activeIndex];
132
+ // Enter with nothing highlighted does nothing rather than running the first
133
+ // item, because running a command a reader did not point at is the one
134
+ // outcome a palette must never produce.
135
+ if (item === undefined)
136
+ return;
137
+ event.preventDefault();
138
+ run(item);
139
+ }
140
+ };
141
+ /** Keeps the highlighted row in view when the arrows move it. */
142
+ useEffect(() => {
143
+ const list = listRef.current;
144
+ if (list === null)
145
+ return;
146
+ const row = list.querySelector('[data-active="true"]');
147
+ row?.scrollIntoView({ block: 'nearest' });
148
+ }, [activeIndex]);
149
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent
150
+ // The palette is a wide, top-anchored surface rather than a centred card,
151
+ // and it carries no visible heading: the search field is what a reader
152
+ // looks at, and a heading above it would be a label for the surface the
153
+ // reader is already in. The DialogTitle is still rendered and still names
154
+ // the dialog for assistive technology, which is the whole reason it
155
+ // exists separately from anything visible.
156
+ , {
157
+ // The palette is a wide, top-anchored surface rather than a centred card,
158
+ // and it carries no visible heading: the search field is what a reader
159
+ // looks at, and a heading above it would be a label for the surface the
160
+ // reader is already in. The DialogTitle is still rendered and still names
161
+ // the dialog for assistive technology, which is the whole reason it
162
+ // exists separately from anything visible.
163
+ side: "top", showCloseButton: false, className: cn('max-w-xl gap-0 p-0', className), children: [_jsx(DialogTitle, { className: "sr-only", children: label }), _jsx("div", { className: "border-border flex items-center border-b px-3", children: _jsx("input", { "data-slot": "command-palette-input", type: "text", role: "combobox", "aria-expanded": flat.length > 0, "aria-controls": `${titleId}-list`, "aria-activedescendant": flat[activeIndex] === undefined ? undefined : `${titleId}-item-${flat[activeIndex]?.id}`, "aria-label": inputLabel, ...(placeholder === undefined ? null : { placeholder }), value: query, onChange: (event) => {
164
+ setQuery(event.target.value);
165
+ setActive(0);
166
+ }, onKeyDown: onKeyDown,
167
+ // The field is focused when the palette opens, so a reader can type
168
+ // immediately. Nothing else in the surface is focusable, which is what
169
+ // makes this a combobox rather than a list of buttons.
170
+ autoFocus: true, className: "placeholder:text-muted-foreground h-12 w-full bg-transparent text-sm outline-none" }) }), _jsx("div", { "data-slot": "command-palette-list", id: `${titleId}-list`, ref: listRef, role: "listbox", "aria-label": label, className: "max-h-80 overflow-y-auto p-1", children: results.length === 0 ? (_jsxs("p", { "data-slot": "command-palette-empty", className: "text-muted-foreground px-3 py-6 text-center text-sm", children: [empty.message(trimmed), empty.hint === undefined ? null : (_jsx("span", { className: "mt-1 block text-xs", children: empty.hint }))] })) : (results.map((group) => (_jsxs("div", { "data-slot": "command-palette-group", className: "mb-1 last:mb-0", children: [_jsx("div", { "data-slot": "command-palette-group-label", className: "text-muted-foreground bg-popover text-xs font-medium tracking-wide uppercase sticky top-0 px-3 py-1.5", children: group.label }), group.hits.map((hit) => {
171
+ const index = flat.indexOf(hit.item);
172
+ const isActive = index === activeIndex;
173
+ return (_jsxs("div", { id: `${titleId}-item-${hit.item.id}`, "data-slot": "command-palette-item", "data-active": isActive, role: "option", "aria-selected": isActive, onMouseEnter: () => setActive(index), onClick: () => run(hit.item), className: cn('flex cursor-pointer items-baseline justify-between gap-3 rounded-sm px-3 py-2 text-sm', isActive && 'bg-accent text-accent-foreground', !isActive && 'text-muted-foreground hover:text-foreground'), children: [_jsx("span", { className: "min-w-0 truncate", children: hit.range === undefined ? (hit.item.label) : (_jsxs(_Fragment, { children: [hit.item.label.slice(0, hit.range[0]), _jsx("strong", { className: "text-foreground font-semibold", children: hit.item.label.slice(hit.range[0], hit.range[1]) }), hit.item.label.slice(hit.range[1])] })) }), hit.item.hint === undefined ? null : (_jsx("span", { className: "shrink-0 text-xs", children: hit.item.hint }))] }, hit.item.id));
174
+ })] }, group.id)))) })] }) }));
175
+ }
176
+ export { CommandPalette };
@@ -0,0 +1,82 @@
1
+ import type { ComponentProps } from 'react';
2
+ /**
3
+ * The props the Command row accepts.
4
+ *
5
+ * The row holds no words of its own. `label` is the command, `hint` is whatever
6
+ * the caller puts at the far end of it, and `keywords` are the extra words that
7
+ * should find it. Every one of the three is a prop because the reader's language
8
+ * is the caller's, and a row that ships an English label ships a claim about a
9
+ * command in a product that is not this system's.
10
+ */
11
+ export interface CommandProps extends Omit<ComponentProps<'div'>, 'onSelect'> {
12
+ /** The words on the row. */
13
+ label: string;
14
+ /**
15
+ * A short run at the far end of the row, usually the keyboard shortcut.
16
+ *
17
+ * It is set apart from the label rather than shown on a second line, because a
18
+ * two-line row doubles the height of every row in the list and a palette is a
19
+ * surface a reader scans rather than reads.
20
+ */
21
+ hint?: string;
22
+ /**
23
+ * Words that should also find this command.
24
+ *
25
+ * The row does not render them, and that is the point of accepting them: a
26
+ * caller keeps one object per command and spreads it onto the row, so the same
27
+ * object can be read by whatever ranks the list. Accepting the field here is
28
+ * what lets that spread be total rather than a hand-picked three fields.
29
+ */
30
+ keywords?: readonly string[];
31
+ /**
32
+ * The characters of `label` that matched the query, as a start and an end.
33
+ *
34
+ * The row does not decide where the match is; whoever ranked the list does, and
35
+ * passes the range down. That keeps one ranking in the system rather than one
36
+ * here and a different one in the palette.
37
+ */
38
+ matchRange?: readonly [number, number];
39
+ /**
40
+ * Whether this is the highlighted row.
41
+ *
42
+ * The highlight is drawn on the row rather than moved as focus, so a reader can
43
+ * keep typing in the field while they choose. A list that moves DOM focus into
44
+ * itself takes the caret away mid-word, which is why the palette and the
45
+ * combobox both drive this from the arrow keys instead.
46
+ */
47
+ active?: boolean;
48
+ /** Called when the reader chooses the command. */
49
+ onSelect?: () => void;
50
+ /** Called when the pointer moves onto the row, so the highlight can follow it. */
51
+ onHover?: () => void;
52
+ }
53
+ /**
54
+ * One command, as a row in a list of commands.
55
+ *
56
+ * **This is the leaf; `CommandPalette` is the composite.** The palette is the
57
+ * dialog, the overlay behaviour, the scroll lock and the return of focus. This
58
+ * is one line of what it draws, for a caller who wants the row without the
59
+ * dialog: a settings page whose whole search list is fourteen commands, a
60
+ * command menu on a button, a launcher inside a panel that is already a
61
+ * floating surface and does not want a second overlay stacked on it.
62
+ *
63
+ * **It ranks nothing.** The row takes `matchRange` as a prop because deciding
64
+ * where a query matches is one decision and this system makes it once, in the
65
+ * shared scorer the palette and the combobox both call. A row that scored its own
66
+ * label would be a second scorer that starts agreeing with the first and stops.
67
+ *
68
+ * **The match is emphasised by weight, not by a colour.** A `<strong>` inherits
69
+ * whatever ink the row already has, so it is correct on the quiet row and on the
70
+ * highlighted one. A colour would be a second ink to keep in step with the two row
71
+ * states, and a saturated background on every matched character fights the label
72
+ * it sits inside.
73
+ *
74
+ * **The row takes `role="option"` and belongs in a listbox.** The parent owns the
75
+ * `role="listbox"`, the `aria-activedescendant` wiring and the roving `tabIndex`;
76
+ * the row is one option inside it. The row is not focusable by Tab, because the
77
+ * field above it keeps the caret for the whole interaction. It can still take
78
+ * programmatic focus, and it draws a full-strength ring when it does.
79
+ */
80
+ declare function Command({ className, label, hint, keywords, matchRange, active, onSelect, onHover, ...props }: CommandProps): import("react").JSX.Element;
81
+ export { Command };
82
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/components/ui/command.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3C;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAC3E,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,iBAAS,OAAO,CAAC,EACf,SAAS,EACT,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,MAAc,EACd,QAAQ,EACR,OAAO,EACP,GAAG,KAAK,EACT,EAAE,YAAY,+BAqCd;AAED,OAAO,EAAE,OAAO,EAAE,CAAA"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from '../../lib/utils.js';
3
+ /**
4
+ * One command, as a row in a list of commands.
5
+ *
6
+ * **This is the leaf; `CommandPalette` is the composite.** The palette is the
7
+ * dialog, the overlay behaviour, the scroll lock and the return of focus. This
8
+ * is one line of what it draws, for a caller who wants the row without the
9
+ * dialog: a settings page whose whole search list is fourteen commands, a
10
+ * command menu on a button, a launcher inside a panel that is already a
11
+ * floating surface and does not want a second overlay stacked on it.
12
+ *
13
+ * **It ranks nothing.** The row takes `matchRange` as a prop because deciding
14
+ * where a query matches is one decision and this system makes it once, in the
15
+ * shared scorer the palette and the combobox both call. A row that scored its own
16
+ * label would be a second scorer that starts agreeing with the first and stops.
17
+ *
18
+ * **The match is emphasised by weight, not by a colour.** A `<strong>` inherits
19
+ * whatever ink the row already has, so it is correct on the quiet row and on the
20
+ * highlighted one. A colour would be a second ink to keep in step with the two row
21
+ * states, and a saturated background on every matched character fights the label
22
+ * it sits inside.
23
+ *
24
+ * **The row takes `role="option"` and belongs in a listbox.** The parent owns the
25
+ * `role="listbox"`, the `aria-activedescendant` wiring and the roving `tabIndex`;
26
+ * the row is one option inside it. The row is not focusable by Tab, because the
27
+ * field above it keeps the caret for the whole interaction. It can still take
28
+ * programmatic focus, and it draws a full-strength ring when it does.
29
+ */
30
+ function Command({ className, label, hint, keywords, matchRange, active = false, onSelect, onHover, ...props }) {
31
+ return (_jsxs("div", { "data-slot": "command", "data-active": active, "data-keywords": keywords?.join(), role: "option", "aria-selected": active, onClick: onSelect, onMouseEnter: onHover,
32
+ // The row suppresses the browser outline and draws a ring of its own at full
33
+ // strength, because the class that suppressed it is the only indicator a reader
34
+ // who moved focus onto a row programmatically would otherwise have.
35
+ className: cn('text-muted-foreground hover:text-foreground flex cursor-pointer items-baseline justify-between gap-3 rounded-sm px-3 py-2 text-sm outline-none', 'focus-visible:ring-ring focus-visible:ring-[3px]', active && 'bg-accent text-accent-foreground', className), ...props, children: [_jsx("span", { className: "min-w-0 truncate", children: matchRange === undefined ? (label) : (_jsxs(_Fragment, { children: [label.slice(0, matchRange[0]), _jsx("strong", { className: "font-semibold", children: label.slice(matchRange[0], matchRange[1]) }), label.slice(matchRange[1])] })) }), hint === undefined ? null : _jsx("span", { className: "shrink-0 text-xs", children: hint })] }));
36
+ }
37
+ export { Command };
@@ -0,0 +1,151 @@
1
+ import type { ComponentProps } from 'react';
2
+ /**
3
+ * The props the ContextMenu root forwards to Base UI.
4
+ *
5
+ * Declared here rather than re-exported from Base UI so no upstream type
6
+ * crosses the package seam (ticket 07 section 6).
7
+ */
8
+ export interface ContextMenuProps {
9
+ /** The controlled open state. */
10
+ open?: boolean;
11
+ /** The initially open state, for an uncontrolled context menu. */
12
+ defaultOpen?: boolean;
13
+ /** Called when the open state changes. */
14
+ onOpenChange?: (open: boolean) => void;
15
+ /** The area the menu belongs to, and the menu itself. */
16
+ children?: React.ReactNode;
17
+ }
18
+ /**
19
+ * The props ContextMenuTrigger forwards to Base UI.
20
+ *
21
+ * The trigger is an area, not a control. It renders a `<div>` and carries no
22
+ * role of its own, because there is no gesture a keyboard reader can be taught
23
+ * to perform here that is not already a shortcut, and an element that pretends
24
+ * to be a button and is not one is a lie the Tab order then repeats.
25
+ */
26
+ export interface ContextMenuTriggerProps extends ComponentProps<'div'> {
27
+ }
28
+ /**
29
+ * The props ContextMenuContent forwards to Base UI.
30
+ *
31
+ * Declared here rather than re-exported from Base UI so no upstream type
32
+ * crosses the package seam (ticket 07 section 6).
33
+ */
34
+ export interface ContextMenuContentProps extends ComponentProps<'div'> {
35
+ /**
36
+ * The accessible name of this menu, read before its rows.
37
+ *
38
+ * Required rather than defaulted, because a context menu is opened by a
39
+ * gesture on a region rather than by a control the reader chose, so nothing
40
+ * on the page has already named it. Three menus in one table, each announced
41
+ * as "menu", is a page where a screen reader user cannot tell which surface
42
+ * they are in. The name says what the menu acts on, and it is the caller's
43
+ * word because only the caller knows what the region is.
44
+ */
45
+ label: string;
46
+ /** The distance in pixels between the pointer and the menu. @defaultValue 0 */
47
+ sideOffset?: number;
48
+ /** The distance in pixels along the alignment axis. @defaultValue 0 */
49
+ alignOffset?: number;
50
+ }
51
+ /** The props ContextMenuItem forwards to Base UI. */
52
+ export interface ContextMenuItemProps extends ComponentProps<'div'> {
53
+ /** Whether the item reads as a destructive action. @defaultValue default */
54
+ variant?: 'default' | 'destructive';
55
+ /** Whether the item ignores user interaction. */
56
+ disabled?: boolean;
57
+ }
58
+ /** The props ContextMenuLinkItem forwards to Base UI. */
59
+ export interface ContextMenuLinkItemProps extends ComponentProps<'a'> {
60
+ /** Whether the link reads as a destructive action. @defaultValue default */
61
+ variant?: 'default' | 'destructive';
62
+ }
63
+ /** The props ContextMenuLabel forwards to Base UI. */
64
+ export interface ContextMenuLabelProps extends ComponentProps<'div'> {
65
+ }
66
+ /** The props ContextMenuSeparator forwards to Base UI. */
67
+ export interface ContextMenuSeparatorProps extends ComponentProps<'div'> {
68
+ }
69
+ /** The props ContextMenuGroup forwards to Base UI. */
70
+ export interface ContextMenuGroupProps extends ComponentProps<'div'> {
71
+ }
72
+ /** The props ContextMenuSub forwards to Base UI. */
73
+ export interface ContextMenuSubProps {
74
+ /** The controlled open state of the submenu. */
75
+ open?: boolean;
76
+ /** The initially open state of the submenu. */
77
+ defaultOpen?: boolean;
78
+ /** Called when the submenu opens or closes. */
79
+ onOpenChange?: (open: boolean) => void;
80
+ /** The submenu trigger and its content. */
81
+ children?: React.ReactNode;
82
+ }
83
+ /** The props ContextMenuSubTrigger forwards to Base UI. */
84
+ export interface ContextMenuSubTriggerProps extends ComponentProps<'div'> {
85
+ }
86
+ /** The props ContextMenuSubContent forwards to Base UI. */
87
+ export interface ContextMenuSubContentProps extends ComponentProps<'div'> {
88
+ /** The distance in pixels between the submenu and the trigger. @defaultValue 0 */
89
+ sideOffset?: number;
90
+ /** The offset in pixels along the alignment axis. @defaultValue 0 */
91
+ alignOffset?: number;
92
+ }
93
+ /**
94
+ * A menu of commands opened by the secondary pointer gesture over a region.
95
+ *
96
+ * **It knows whether each row is a command or a destination, and so should the
97
+ * caller.** `ContextMenuItem` is a command: a `<div>` with `role="menuitem"`,
98
+ * operated by click or Enter, doing something. `ContextMenuLinkItem` is a
99
+ * destination: a real `<a href>`, so it can be opened in a new tab, copied by
100
+ * address, and crawled. A context menu holding "Copy link" beside "Open in new
101
+ * tab" is the case that gets a consumer's product wrong, because the second is
102
+ * the one a reader expects to middle-click. Splitting the two into two parts is
103
+ * what makes that decision visible at the call site rather than at the review.
104
+ *
105
+ * **Nothing here is the only route to a command.** The gesture is secondary: it
106
+ * is unreachable by a keyboard and undiscoverable by a touch reader, so every
107
+ * row here must be reachable somewhere else on the page. The trigger is a plain
108
+ * `<div>` with no role for the same reason: an element that looks like a button
109
+ * and is not one teaches a reader a gesture that then does nothing.
110
+ *
111
+ * The menu is navigable with the arrow keys and with typeahead, and Escape or an
112
+ * outside press closes it. A menu is a list of commands, so a command belonging
113
+ * on the surface belongs on the surface, not behind a right-click.
114
+ */
115
+ declare function ContextMenu({ ...props }: ContextMenuProps): import("react").JSX.Element;
116
+ /**
117
+ * The region the menu belongs to. A secondary press inside it opens the menu.
118
+ *
119
+ * It renders a `<div>` and nothing more, because it is a region rather than a
120
+ * control. Base UI also treats a long press as the open gesture, which is what
121
+ * makes the menu reachable at all on a touch screen, and it is the reason a
122
+ * context menu is an addition to a surface rather than a substitute for one.
123
+ */
124
+ declare function ContextMenuTrigger({ className, ...props }: ContextMenuTriggerProps): import("react").JSX.Element;
125
+ /** The floating menu surface, opened at the pointer. */
126
+ declare function ContextMenuContent({ className, label, sideOffset, alignOffset, ...props }: ContextMenuContentProps): import("react").JSX.Element;
127
+ /** One command in the menu. It does something rather than going somewhere. */
128
+ declare function ContextMenuItem({ className, variant, ...props }: ContextMenuItemProps): import("react").JSX.Element;
129
+ /**
130
+ * One destination in the menu, rendered as a native anchor.
131
+ *
132
+ * A link is a `<a href>` and nothing else, so the browser's own affordances
133
+ * apply: middle-click opens it in a new tab, and "copy link address" copies a
134
+ * real address. A command styled as a link has neither, and a reader who
135
+ * middle-clicks a `div` gets nothing at all.
136
+ */
137
+ declare function ContextMenuLinkItem({ className, variant, ...props }: ContextMenuLinkItemProps): import("react").JSX.Element;
138
+ /** A non-interactive heading inside a group of rows. */
139
+ declare function ContextMenuLabel({ className, ...props }: ContextMenuLabelProps): import("react").JSX.Element;
140
+ /** A rule between groups of rows. */
141
+ declare function ContextMenuSeparator({ className, ...props }: ContextMenuSeparatorProps): import("react").JSX.Element;
142
+ /** A labelled set of related rows. */
143
+ declare function ContextMenuGroup({ className, ...props }: ContextMenuGroupProps): import("react").JSX.Element;
144
+ /** A group of rows that opens into a second menu. */
145
+ declare function ContextMenuSub({ ...props }: ContextMenuSubProps): import("react").JSX.Element;
146
+ /** The row that opens a submenu. */
147
+ declare function ContextMenuSubTrigger({ className, children, ...props }: ContextMenuSubTriggerProps): import("react").JSX.Element;
148
+ /** The nested menu surface. */
149
+ declare function ContextMenuSubContent({ className, sideOffset, alignOffset, ...props }: ContextMenuSubContentProps): import("react").JSX.Element;
150
+ export { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuLinkItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuGroup, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent, };
151
+ //# sourceMappingURL=context-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-menu.d.ts","sourceRoot":"","sources":["../../../src/components/ui/context-menu.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ContextMenu as ContextMenuPrimitive } from '@base-ui/react/context-menu';
4
+ import { ChevronRightIcon } from 'lucide-react';
5
+ import { cn } from '../../lib/utils.js';
6
+ /**
7
+ * The menu surface's own class string, shared by the top level and a submenu.
8
+ *
9
+ * The panel takes focus when the menu opens, so it is a focusable element and it
10
+ * draws the focus ring at full strength rather than suppressing the outline and
11
+ * inheriting nothing. A menu item carries `role="menuitem"` and Base UI moves a
12
+ * highlighted state through the list instead of a DOM focus ring, so the ring
13
+ * belongs here and nowhere else in the surface.
14
+ */
15
+ const POPUP = 'bg-popover text-popover-foreground z-50 max-h-(--available-height) min-w-40 overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md outline-none transition-[opacity,transform] duration-fast ease-out focus-visible:ring-ring focus-visible:ring-[3px] data-[starting-style]:scale-95 data-[starting-style]:opacity-0 data-[ending-style]:scale-95 data-[ending-style]:opacity-0';
16
+ /** One row in a context menu, whether it is a command or a link. */
17
+ const ROW = 'focus:bg-accent focus:text-accent-foreground data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*="size-"])]:size-4';
18
+ /**
19
+ * A menu of commands opened by the secondary pointer gesture over a region.
20
+ *
21
+ * **It knows whether each row is a command or a destination, and so should the
22
+ * caller.** `ContextMenuItem` is a command: a `<div>` with `role="menuitem"`,
23
+ * operated by click or Enter, doing something. `ContextMenuLinkItem` is a
24
+ * destination: a real `<a href>`, so it can be opened in a new tab, copied by
25
+ * address, and crawled. A context menu holding "Copy link" beside "Open in new
26
+ * tab" is the case that gets a consumer's product wrong, because the second is
27
+ * the one a reader expects to middle-click. Splitting the two into two parts is
28
+ * what makes that decision visible at the call site rather than at the review.
29
+ *
30
+ * **Nothing here is the only route to a command.** The gesture is secondary: it
31
+ * is unreachable by a keyboard and undiscoverable by a touch reader, so every
32
+ * row here must be reachable somewhere else on the page. The trigger is a plain
33
+ * `<div>` with no role for the same reason: an element that looks like a button
34
+ * and is not one teaches a reader a gesture that then does nothing.
35
+ *
36
+ * The menu is navigable with the arrow keys and with typeahead, and Escape or an
37
+ * outside press closes it. A menu is a list of commands, so a command belonging
38
+ * on the surface belongs on the surface, not behind a right-click.
39
+ */
40
+ function ContextMenu({ ...props }) {
41
+ return _jsx(ContextMenuPrimitive.Root, { ...props });
42
+ }
43
+ /**
44
+ * The region the menu belongs to. A secondary press inside it opens the menu.
45
+ *
46
+ * It renders a `<div>` and nothing more, because it is a region rather than a
47
+ * control. Base UI also treats a long press as the open gesture, which is what
48
+ * makes the menu reachable at all on a touch screen, and it is the reason a
49
+ * context menu is an addition to a surface rather than a substitute for one.
50
+ */
51
+ function ContextMenuTrigger({ className, ...props }) {
52
+ return (_jsx(ContextMenuPrimitive.Trigger, { "data-slot": "context-menu-trigger", className: cn(className), ...props }));
53
+ }
54
+ /** The floating menu surface, opened at the pointer. */
55
+ function ContextMenuContent({ className, label, sideOffset = 0, alignOffset = 0, ...props }) {
56
+ return (_jsx(ContextMenuPrimitive.Portal, { children: _jsx(ContextMenuPrimitive.Positioner, { sideOffset: sideOffset, alignOffset: alignOffset, children: _jsx(ContextMenuPrimitive.Popup, { "data-slot": "context-menu-content", "aria-label": label, className: cn(POPUP, className), ...props }) }) }));
57
+ }
58
+ /** One command in the menu. It does something rather than going somewhere. */
59
+ function ContextMenuItem({ className, variant = 'default', ...props }) {
60
+ return (_jsx(ContextMenuPrimitive.Item, { "data-slot": "context-menu-item", "data-variant": variant, className: cn(ROW, 'data-[variant=destructive]:text-destructive data-[variant=destructive]:data-[highlighted]:bg-destructive/10', className), ...props }));
61
+ }
62
+ /**
63
+ * One destination in the menu, rendered as a native anchor.
64
+ *
65
+ * A link is a `<a href>` and nothing else, so the browser's own affordances
66
+ * apply: middle-click opens it in a new tab, and "copy link address" copies a
67
+ * real address. A command styled as a link has neither, and a reader who
68
+ * middle-clicks a `div` gets nothing at all.
69
+ */
70
+ function ContextMenuLinkItem({ className, variant = 'default', ...props }) {
71
+ return (_jsx(ContextMenuPrimitive.LinkItem, { "data-slot": "context-menu-link-item", "data-variant": variant, className: cn(ROW, 'data-[variant=destructive]:text-destructive data-[variant=destructive]:data-[highlighted]:bg-destructive/10', className), ...props }));
72
+ }
73
+ /** A non-interactive heading inside a group of rows. */
74
+ function ContextMenuLabel({ className, ...props }) {
75
+ return (_jsx(ContextMenuPrimitive.GroupLabel, { "data-slot": "context-menu-label", className: cn('px-2 py-1.5 text-xs font-medium', className), ...props }));
76
+ }
77
+ /** A rule between groups of rows. */
78
+ function ContextMenuSeparator({ className, ...props }) {
79
+ return (_jsx(ContextMenuPrimitive.Separator, { "data-slot": "context-menu-separator", className: cn('bg-border -mx-1 my-1 h-px', className), ...props }));
80
+ }
81
+ /** A labelled set of related rows. */
82
+ function ContextMenuGroup({ className, ...props }) {
83
+ return (_jsx(ContextMenuPrimitive.Group, { "data-slot": "context-menu-group", className: cn(className), ...props }));
84
+ }
85
+ /** A group of rows that opens into a second menu. */
86
+ function ContextMenuSub({ ...props }) {
87
+ return _jsx(ContextMenuPrimitive.SubmenuRoot, { ...props });
88
+ }
89
+ /** The row that opens a submenu. */
90
+ function ContextMenuSubTrigger({ className, children, ...props }) {
91
+ return (_jsxs(ContextMenuPrimitive.SubmenuTrigger, { "data-slot": "context-menu-sub-trigger", className: cn(ROW, className), ...props, children: [children, _jsx(ChevronRightIcon, { className: "ml-auto size-4" })] }));
92
+ }
93
+ /** The nested menu surface. */
94
+ function ContextMenuSubContent({ className, sideOffset = 0, alignOffset = 0, ...props }) {
95
+ return (_jsx(ContextMenuPrimitive.Portal, { children: _jsx(ContextMenuPrimitive.Positioner, { sideOffset: sideOffset, alignOffset: alignOffset, children: _jsx(ContextMenuPrimitive.Popup, { "data-slot": "context-menu-sub-content", className: cn(POPUP, className), ...props }) }) }));
96
+ }
97
+ export { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuLinkItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuGroup, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent, };
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cva } from 'class-variance-authority';
3
- import { cn } from '../../lib/utils';
3
+ import { cn } from '../../lib/utils.js';
4
4
  /*
5
5
  * Why this is a second Component rather than an `as` or `href` prop on `Button`.
6
6
  * The design system has already answered this twice, in the two places a trail