@krizaka/ui 3.8.0 → 3.10.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 (342) hide show
  1. package/README.md +13 -9
  2. package/dist/action-bar.js +2 -1
  3. package/dist/action-bar.js.map +1 -1
  4. package/dist/app-shell.d.ts +81 -3
  5. package/dist/app-shell.js +163 -35
  6. package/dist/app-shell.js.map +1 -1
  7. package/dist/audio-player.js +1 -1
  8. package/dist/badge.d.ts +36 -39
  9. package/dist/badge.js +11 -2
  10. package/dist/badge.js.map +1 -1
  11. package/dist/blocks.css +56 -4
  12. package/dist/button-BmCrLCq7.d.ts +114 -0
  13. package/dist/button.d.ts +48 -48
  14. package/dist/button.js +2 -1
  15. package/dist/card.d.ts +111 -111
  16. package/dist/card.js +10 -2
  17. package/dist/card.js.map +1 -1
  18. package/dist/chip.d.ts +24 -24
  19. package/dist/chip.js +10 -2
  20. package/dist/chip.js.map +1 -1
  21. package/dist/{chunk-UOMP6VOZ.js → chunk-276PTQMQ.js} +10 -4
  22. package/dist/chunk-276PTQMQ.js.map +1 -0
  23. package/dist/{chunk-Z6FUPB3Q.js → chunk-2NB27LM7.js} +11 -3
  24. package/dist/chunk-2NB27LM7.js.map +1 -0
  25. package/dist/chunk-2VNYHG4E.js +9 -0
  26. package/dist/chunk-2VNYHG4E.js.map +1 -0
  27. package/dist/{chunk-ND3L5VHT.js → chunk-5Z7FWCV2.js} +2 -2
  28. package/dist/{chunk-YH4LMWRU.js → chunk-7UCPGXKN.js} +11 -3
  29. package/dist/chunk-7UCPGXKN.js.map +1 -0
  30. package/dist/{chunk-AZPKXP3O.js → chunk-AFY35UCB.js} +6 -1
  31. package/dist/{chunk-AZPKXP3O.js.map → chunk-AFY35UCB.js.map} +1 -1
  32. package/dist/{chunk-LCZXS6AK.js → chunk-CKSHLSDH.js} +5 -5
  33. package/dist/chunk-CKSHLSDH.js.map +1 -0
  34. package/dist/chunk-D3T54RSL.js +11 -0
  35. package/dist/chunk-D3T54RSL.js.map +1 -0
  36. package/dist/{chunk-ZKVQGXLD.js → chunk-FKBHWS4N.js} +2 -2
  37. package/dist/{chunk-BRKMQ6JV.js → chunk-FZBWEDQI.js} +1 -23
  38. package/dist/chunk-FZBWEDQI.js.map +1 -0
  39. package/dist/chunk-HG65YAV4.js +379 -0
  40. package/dist/chunk-HG65YAV4.js.map +1 -0
  41. package/dist/{chunk-HCNOOPZE.js → chunk-KVDB4GXV.js} +2 -2
  42. package/dist/chunk-OT6HX3PN.js +85 -0
  43. package/dist/chunk-OT6HX3PN.js.map +1 -0
  44. package/dist/{chunk-RUPNPMP3.js → chunk-OWC2LZD2.js} +11 -3
  45. package/dist/chunk-OWC2LZD2.js.map +1 -0
  46. package/dist/{chunk-FDCMBWUU.js → chunk-REPTKUJ6.js} +4 -4
  47. package/dist/chunk-REPTKUJ6.js.map +1 -0
  48. package/dist/code-block.d.ts +32 -10
  49. package/dist/code-block.js +51 -43
  50. package/dist/code-block.js.map +1 -1
  51. package/dist/combobox.d.ts +3 -3
  52. package/dist/combobox.js +4 -3
  53. package/dist/command.js +3 -2
  54. package/dist/command.js.map +1 -1
  55. package/dist/component-page.js.map +1 -1
  56. package/dist/confirm-button.d.ts +1 -1
  57. package/dist/confirm-button.js +2 -1
  58. package/dist/confirm-button.js.map +1 -1
  59. package/dist/continue-prompt.js +11 -3
  60. package/dist/continue-prompt.js.map +1 -1
  61. package/dist/data-table.js +7 -6
  62. package/dist/data-table.js.map +1 -1
  63. package/dist/date-picker.js +1 -1
  64. package/dist/dialog.js +3 -2
  65. package/dist/dialog.js.map +1 -1
  66. package/dist/file-upload.d.ts +12 -1
  67. package/dist/file-upload.js +73 -89
  68. package/dist/file-upload.js.map +1 -1
  69. package/dist/filter-bar.js +3 -2
  70. package/dist/image-cropper.js +1 -1
  71. package/dist/index.js +9 -367
  72. package/dist/index.js.map +1 -1
  73. package/dist/list.js +1 -1
  74. package/dist/map.js.map +1 -1
  75. package/dist/markdown.d.ts +38 -0
  76. package/dist/markdown.js +150 -0
  77. package/dist/markdown.js.map +1 -0
  78. package/dist/match-dialog.js +2 -1
  79. package/dist/match-dialog.js.map +1 -1
  80. package/dist/media-capture.js +1 -1
  81. package/dist/native/markdown.d.ts +24 -0
  82. package/dist/native/markdown.js +200 -0
  83. package/dist/native/markdown.js.map +1 -0
  84. package/dist/native.d.ts +49 -8
  85. package/dist/native.js +728 -349
  86. package/dist/native.js.map +1 -1
  87. package/dist/nav-bar.d.ts +86 -3
  88. package/dist/nav-bar.js +88 -2
  89. package/dist/nav-bar.js.map +1 -1
  90. package/dist/recipes.d.ts +143 -0
  91. package/dist/recipes.js +180 -0
  92. package/dist/recipes.js.map +1 -0
  93. package/dist/search-field.js +2 -2
  94. package/dist/showcase.d.ts +69 -0
  95. package/dist/showcase.js +218 -0
  96. package/dist/showcase.js.map +1 -0
  97. package/dist/site-footer.d.ts +172 -0
  98. package/dist/site-footer.js +105 -0
  99. package/dist/site-footer.js.map +1 -0
  100. package/dist/sites-CgJrxejx.d.ts +32 -0
  101. package/dist/sites.d.ts +64 -0
  102. package/dist/sites.js +69 -0
  103. package/dist/sites.js.map +1 -0
  104. package/dist/swipe-deck.js +1 -1
  105. package/dist/tag-input.js +4 -3
  106. package/dist/tag-input.js.map +1 -1
  107. package/dist/tag-selector.js +2 -1
  108. package/dist/tag-selector.js.map +1 -1
  109. package/dist/theme.d.ts +1 -1
  110. package/dist/theme.js +2 -1
  111. package/dist/theme.js.map +1 -1
  112. package/dist/tree-view.js +1 -1
  113. package/dist/vertical-video.js +2 -1
  114. package/dist/vertical-video.js.map +1 -1
  115. package/dist/video-player.js +1 -1
  116. package/dist/video-trimmer.js +1 -1
  117. package/dist/virtual.js +2 -2
  118. package/package.json +87 -18
  119. package/registry/achievement.json +1 -1
  120. package/registry/action-bar.json +1 -1
  121. package/registry/activity-feed.json +1 -1
  122. package/registry/alert.json +1 -1
  123. package/registry/app-shell.json +135 -12
  124. package/registry/argument-card.json +1 -1
  125. package/registry/audio-player.json +2 -2
  126. package/registry/auth-layout.json +1 -1
  127. package/registry/avatar.json +1 -1
  128. package/registry/badge.json +9 -3
  129. package/registry/button.json +7 -2
  130. package/registry/card-grid.json +1 -1
  131. package/registry/card.json +8 -3
  132. package/registry/catalogue-page.json +1 -1
  133. package/registry/changelog-page.json +1 -1
  134. package/registry/chat.json +1 -1
  135. package/registry/checkbox.json +1 -1
  136. package/registry/chip-link.json +1 -1
  137. package/registry/chip.json +9 -3
  138. package/registry/choice-grid.json +1 -1
  139. package/registry/code-block.json +65 -21
  140. package/registry/combobox.json +4 -3
  141. package/registry/command.json +1 -1
  142. package/registry/component-page.json +4 -4
  143. package/registry/confirm-button.json +1 -1
  144. package/registry/contact-page.json +1 -1
  145. package/registry/continue-prompt.json +1 -1
  146. package/registry/countdown.json +1 -1
  147. package/registry/cta-panel.json +1 -1
  148. package/registry/dashboard-layout.json +1 -1
  149. package/registry/data-table.json +2 -2
  150. package/registry/date-picker.json +3 -3
  151. package/registry/decision-list.json +1 -1
  152. package/registry/description-list.json +1 -1
  153. package/registry/detail-layout.json +1 -1
  154. package/registry/dialog.json +1 -1
  155. package/registry/docs-article-page.json +1 -1
  156. package/registry/docs-hub-page.json +1 -1
  157. package/registry/docs-layout.json +1 -1
  158. package/registry/dropdown-menu.json +1 -1
  159. package/registry/empty-state.json +1 -1
  160. package/registry/error-page.json +1 -1
  161. package/registry/examples/app-shell/console.d.ts +4 -0
  162. package/registry/examples/app-shell/console.js +93 -0
  163. package/registry/examples/app-shell/console.tsx +103 -0
  164. package/registry/examples/badge/native/dot.dark.png +0 -0
  165. package/registry/examples/badge/native/dot.light.png +0 -0
  166. package/registry/examples/badge/native/scrim.dark.png +0 -0
  167. package/registry/examples/badge/native/scrim.light.png +0 -0
  168. package/registry/examples/badge/native/tones.dark.png +0 -0
  169. package/registry/examples/badge/native/tones.light.png +0 -0
  170. package/registry/examples/card/native/default.dark.png +0 -0
  171. package/registry/examples/card/native/default.light.png +0 -0
  172. package/registry/examples/card/native/elevated.light.png +0 -0
  173. package/registry/examples/chip/native/multiple.dark.png +0 -0
  174. package/registry/examples/chip/native/multiple.light.png +0 -0
  175. package/registry/examples/chip/native/removable.dark.png +0 -0
  176. package/registry/examples/chip/native/removable.light.png +0 -0
  177. package/registry/examples/chip/native/single.dark.png +0 -0
  178. package/registry/examples/chip/native/single.light.png +0 -0
  179. package/registry/examples/chip/native/toggle.dark.png +0 -0
  180. package/registry/examples/chip/native/toggle.light.png +0 -0
  181. package/registry/examples/code-block/annotated.d.ts +4 -0
  182. package/registry/examples/code-block/annotated.js +31 -0
  183. package/registry/examples/code-block/annotated.tsx +26 -0
  184. package/registry/examples/component-page/button.tsx +1 -1
  185. package/registry/examples/form/native/amount.dark.png +0 -0
  186. package/registry/examples/form/native/amount.light.png +0 -0
  187. package/registry/examples/markdown/ai-stream.d.ts +4 -0
  188. package/registry/examples/markdown/ai-stream.js +31 -0
  189. package/registry/examples/markdown/ai-stream.tsx +32 -0
  190. package/registry/examples/markdown/basic.d.ts +4 -0
  191. package/registry/examples/markdown/basic.js +26 -0
  192. package/registry/examples/markdown/basic.tsx +22 -0
  193. package/registry/examples/markdown/native/ai-stream.d.ts +4 -0
  194. package/registry/examples/markdown/native/ai-stream.dark.png +0 -0
  195. package/registry/examples/markdown/native/ai-stream.js +17 -0
  196. package/registry/examples/markdown/native/ai-stream.light.png +0 -0
  197. package/registry/examples/markdown/native/ai-stream.tsx +17 -0
  198. package/registry/examples/markdown/native/basic.d.ts +4 -0
  199. package/registry/examples/markdown/native/basic.dark.png +0 -0
  200. package/registry/examples/markdown/native/basic.js +28 -0
  201. package/registry/examples/markdown/native/basic.light.png +0 -0
  202. package/registry/examples/markdown/native/basic.tsx +27 -0
  203. package/registry/examples/nav-bar/family-admin.d.ts +4 -0
  204. package/registry/examples/nav-bar/family-admin.js +21 -0
  205. package/registry/examples/nav-bar/family-admin.tsx +20 -0
  206. package/registry/examples/nav-bar/family-java.d.ts +4 -0
  207. package/registry/examples/nav-bar/family-java.js +33 -0
  208. package/registry/examples/nav-bar/family-java.tsx +34 -0
  209. package/registry/examples/nav-bar/family-product.d.ts +4 -0
  210. package/registry/examples/nav-bar/family-product.js +35 -0
  211. package/registry/examples/nav-bar/family-product.tsx +39 -0
  212. package/registry/examples/nav-bar/family-ui.d.ts +4 -0
  213. package/registry/examples/nav-bar/family-ui.js +34 -0
  214. package/registry/examples/nav-bar/family-ui.tsx +35 -0
  215. package/registry/examples/nav-bar/family-www.d.ts +4 -0
  216. package/registry/examples/nav-bar/family-www.js +34 -0
  217. package/registry/examples/nav-bar/family-www.tsx +36 -0
  218. package/registry/examples/recipes/tone-chip.d.ts +4 -0
  219. package/registry/examples/recipes/tone-chip.js +46 -0
  220. package/registry/examples/recipes/tone-chip.tsx +32 -0
  221. package/registry/examples/screen/native/profile.dark.png +0 -0
  222. package/registry/examples/screen/native/profile.light.png +0 -0
  223. package/registry/examples/showcase/native/slides.d.ts +4 -0
  224. package/registry/examples/showcase/native/slides.dark.png +0 -0
  225. package/registry/examples/showcase/native/slides.js +27 -0
  226. package/registry/examples/showcase/native/slides.light.png +0 -0
  227. package/registry/examples/showcase/native/slides.tsx +28 -0
  228. package/registry/examples/showcase/prompt-image.d.ts +4 -0
  229. package/registry/examples/showcase/prompt-image.js +29 -0
  230. package/registry/examples/showcase/prompt-image.tsx +28 -0
  231. package/registry/examples/showcase/slides.d.ts +4 -0
  232. package/registry/examples/showcase/slides.js +29 -0
  233. package/registry/examples/showcase/slides.tsx +24 -0
  234. package/registry/examples/site-footer/company.d.ts +4 -0
  235. package/registry/examples/site-footer/company.js +26 -0
  236. package/registry/examples/site-footer/company.tsx +23 -0
  237. package/registry/examples/site-footer/open-source-fr.d.ts +4 -0
  238. package/registry/examples/site-footer/open-source-fr.js +17 -0
  239. package/registry/examples/site-footer/open-source-fr.tsx +17 -0
  240. package/registry/examples/site-footer/product.d.ts +4 -0
  241. package/registry/examples/site-footer/product.js +22 -0
  242. package/registry/examples/site-footer/product.tsx +19 -0
  243. package/registry/examples/sites/family.d.ts +4 -0
  244. package/registry/examples/sites/family.js +20 -0
  245. package/registry/examples/sites/family.tsx +30 -0
  246. package/registry/examples/sites/french.d.ts +4 -0
  247. package/registry/examples/sites/french.js +25 -0
  248. package/registry/examples/sites/french.tsx +25 -0
  249. package/registry/examples/tag-selector/native/interests.dark.png +0 -0
  250. package/registry/examples/tag-selector/native/interests.light.png +0 -0
  251. package/registry/field.json +1 -1
  252. package/registry/file-upload.json +8 -3
  253. package/registry/filter-bar.json +1 -1
  254. package/registry/form.json +2 -2
  255. package/registry/gallery-page.json +1 -1
  256. package/registry/glow-card.json +1 -1
  257. package/registry/goal-gauge.json +1 -1
  258. package/registry/how-it-works-page.json +1 -1
  259. package/registry/image-cropper.json +2 -2
  260. package/registry/index.json +138 -20
  261. package/registry/iso.json +1 -1
  262. package/registry/kanban.json +1 -1
  263. package/registry/kbd.json +1 -1
  264. package/registry/landing-page.json +1 -1
  265. package/registry/landscape.json +1 -1
  266. package/registry/legal-page.json +1 -1
  267. package/registry/link-card.json +1 -1
  268. package/registry/list.json +2 -2
  269. package/registry/loader.json +1 -1
  270. package/registry/longform-page.json +1 -1
  271. package/registry/map.json +3 -3
  272. package/registry/markdown.json +266 -0
  273. package/registry/marketing-layout.json +1 -1
  274. package/registry/marquee.json +1 -1
  275. package/registry/mascot-quote.json +1 -1
  276. package/registry/mascot-state.json +1 -1
  277. package/registry/mascots.json +1 -1
  278. package/registry/match-dialog.json +1 -1
  279. package/registry/media-capture.json +2 -2
  280. package/registry/meter.json +1 -1
  281. package/registry/nav-bar.json +257 -13
  282. package/registry/number-ticker.json +1 -1
  283. package/registry/page-header.json +1 -1
  284. package/registry/page-hero.json +1 -1
  285. package/registry/page.json +1 -1
  286. package/registry/pagination.json +1 -1
  287. package/registry/photo-grid.json +1 -1
  288. package/registry/pointer-light.json +1 -1
  289. package/registry/popover.json +1 -1
  290. package/registry/pricing-page.json +1 -1
  291. package/registry/product-page.json +1 -1
  292. package/registry/profile-card.json +1 -1
  293. package/registry/progress.json +1 -1
  294. package/registry/promise-card.json +1 -1
  295. package/registry/radio-group.json +1 -1
  296. package/registry/recipes.json +99 -0
  297. package/registry/scroll-stage.json +1 -1
  298. package/registry/search-field.json +4 -3
  299. package/registry/section-backdrop.json +1 -1
  300. package/registry/separator.json +1 -1
  301. package/registry/settings-layout.json +1 -1
  302. package/registry/showcase.json +421 -0
  303. package/registry/site-footer.json +389 -0
  304. package/registry/sites.json +93 -0
  305. package/registry/skeleton.json +1 -1
  306. package/registry/skip-link.json +1 -1
  307. package/registry/slider.json +1 -1
  308. package/registry/slot.json +1 -1
  309. package/registry/spinner.json +1 -1
  310. package/registry/split-view.json +1 -1
  311. package/registry/stage-banner.json +1 -1
  312. package/registry/stat.json +1 -1
  313. package/registry/status-page.json +1 -1
  314. package/registry/step-progress.json +1 -1
  315. package/registry/stepper.json +1 -1
  316. package/registry/swipe-deck.json +2 -2
  317. package/registry/switch.json +1 -1
  318. package/registry/tabs.json +1 -1
  319. package/registry/tag-input.json +2 -2
  320. package/registry/tag-selector.json +1 -1
  321. package/registry/terminal-replay.json +1 -1
  322. package/registry/theme.json +1 -1
  323. package/registry/toast.json +1 -1
  324. package/registry/toc.json +1 -1
  325. package/registry/tooltip.json +1 -1
  326. package/registry/tree-view.json +2 -2
  327. package/registry/verified-badge.json +1 -1
  328. package/registry/vertical-video.json +2 -2
  329. package/registry/video-player.json +2 -2
  330. package/registry/video-trimmer.json +2 -2
  331. package/registry/virtual.json +2 -2
  332. package/dist/button-BMAO7dKF.d.ts +0 -114
  333. package/dist/chunk-BRKMQ6JV.js.map +0 -1
  334. package/dist/chunk-FDCMBWUU.js.map +0 -1
  335. package/dist/chunk-LCZXS6AK.js.map +0 -1
  336. package/dist/chunk-RUPNPMP3.js.map +0 -1
  337. package/dist/chunk-UOMP6VOZ.js.map +0 -1
  338. package/dist/chunk-YH4LMWRU.js.map +0 -1
  339. package/dist/chunk-Z6FUPB3Q.js.map +0 -1
  340. /package/dist/{chunk-ND3L5VHT.js.map → chunk-5Z7FWCV2.js.map} +0 -0
  341. /package/dist/{chunk-ZKVQGXLD.js.map → chunk-FKBHWS4N.js.map} +0 -0
  342. /package/dist/{chunk-HCNOOPZE.js.map → chunk-KVDB4GXV.js.map} +0 -0
package/README.md CHANGED
@@ -147,7 +147,9 @@ light or a product's — is a set of tokens.
147
147
  | `@krizaka/ui/skip-link` | `SkipLink` (`href`, default `#content`; the label as children): the first tab stop, hidden until focused, Enter moves focus to the content (WCAG 2.4.1) | client |
148
148
  | `@krizaka/ui/kbd` | `Kbd` (`size` sm·md), `kbdVariants` | server |
149
149
  | `@krizaka/ui/tabs` | `Tabs.Root/List/Trigger/Content` (`variant` underline·segmented·pills on the root, `orientation`, `value`/`defaultValue` + `onValueChange`; one light glides to the active tab), `tabs` (the slots) | client |
150
- | `@krizaka/ui/nav-bar` | `NavBar.Root` (`label`, `compactAfter`, `floating`) `/Brand/Items/Link` (`active`) `/Menu` (`label`, `columns`) `/MenuLink` (`title`, `description`, `icon`) `/Actions/Mobile` (`openLabel`, `closeLabel`) `/MobileLink`, `navBar` (the slots) | client |
150
+ | `@krizaka/ui/nav-bar` | `NavBar.Root` (`label`, `compactAfter`, `floating`) `/Brand` `/Family` (`current`, `labels`, `defaultOpen`: the family switcher on the brand, the mobile panel's last group inside `Mobile`) `/Badge` (`env`, `version`) `/Items/Link` (`active`) `/Menu` (`label`, `columns`) `/MenuLink` (`title`, `description`, `icon`) `/Actions` `/Repo` (`href`, `label`) `/Mobile` (`openLabel`, `closeLabel`) `/MobileLink`, `navBar` (the slots) | client |
151
+ | `@krizaka/ui/sites` | The Krizaka family as data: `sites` (id, name, url, host, kind, group, status, mark, repo), `siteGroups`, `siteById`, `sitesByGroup`, `familyLabels` (`en`, `fr`) | server |
152
+ | `@krizaka/ui/site-footer` | `SiteFooter` (`current`, `labels`, `version`, `env`, `legal`; own columns as children) `.Column` (`title`) `.Link` (`active`, `asChild`): the footer of every Krizaka site — the family in columns, “A Krizaka company / product / open-source project”, the version, Montréal | client |
151
153
  | `@krizaka/ui/chip` | `Chip` (`selected`/`defaultSelected` + `onSelectedChange`, `size` sm·md; `removable` + `removeLabel` + `onRemove`), `Chip.Group` (`type` single·multiple, `label`, `required` for single), `chip` (the slots) | client |
152
154
  | `@krizaka/ui/chip-link` | `ChipLink` (a filter that is a link: `selected`, `current`, `size`, `asChild`), `chipVariants` (`size`, `selected`) | server |
153
155
  | `@krizaka/ui/switch` | `Switch` (`checked`/`defaultChecked` + `onCheckedChange`, `label` unless a visible label names it, `size` sm·md, `invalid`), `switchVariants` | client |
@@ -185,7 +187,7 @@ light or a product's — is a set of tokens.
185
187
  | `@krizaka/ui/date-picker` | `DatePicker`, `DateRangePicker` (`presets`), `Calendar` — ISO dates, the locale's names through `@krizaka/intl`, `min`/`max`/`isDateDisabled`, the APG grid keyboard | client |
186
188
  | `@krizaka/ui/search-field` | `SearchField` (`onSearch` debounced, `onSubmit`, `shortcut` ⌘K, `suggestions`, `clearLabel`, `landmark`) | client |
187
189
  | `@krizaka/ui/video-player` | `VideoPlayer` (`src` or a signed-source function, `loadHls={() => import("hls.js")}` (optional peer, loaded only without native HLS), `tracks`, `playbackRates`, `preload`, `pauseWhenHidden`, `overlay` for a gate, `labels`; keyboard Space/K, J/L, arrows, M, F, C) | client |
188
- | `@krizaka/ui/file-upload` | `Dropzone` (`accept`, `maxSize`, `maxFiles`, refusals with reasons), `UploadList` (preview, progress, speed, pause/resume/retry/cancel), `useUploadQueue` (`upload(file, { signal, onProgress, resumed })`: Tus-ready without depending on it), `acceptsFile`, `formatBytes` | client |
190
+ | `@krizaka/ui/file-upload` | `Dropzone` (`accept`, `maxSize`, `maxFiles`, refusals with reasons), `UploadList` (preview, progress, speed, pause/resume/retry/cancel), `useUploadQueue` (`upload(file, { signal, onProgress, resumed })`: Tus-ready without depending on it; `retries`, `backoff`, `shouldRetry` — a queue of `@krizaka/pacer`), `acceptsFile`, `formatBytes` | client |
189
191
  | `@krizaka/ui/media-capture` | `MediaCapture` (`mode` photo·video·both, `facing`, `maxDuration`, permission asked on a gesture, the file picker as the way out, `onCapture({ blob, kind, url })`) | client |
190
192
  | `@krizaka/ui/video-trimmer` | `VideoTrimmer` (`duration`, `value` [start, end], `minLength`, `step`/`largeStep`, `frames` or children, `time` + `onSeek`, `classNames` per part, `renderHandle`) | client |
191
193
  | `@krizaka/ui/audio-player` | `AudioPlayer` (a seekable waveform: `peaks` or `computePeaks`, `bars`, `playbackRates`, `duration`), `resamplePeaks`, `computeAudioPeaks` | client |
@@ -198,11 +200,13 @@ light or a product's — is a set of tokens.
198
200
  | `@krizaka/ui/argument-card` | `ArgumentCard` (compact: `icon`, `title`, `meta`, `facts`, `hook` — the trap in one line —, `more`, `actions`; in a panel: `points`, `rule`, `details`, `panelLabel`) | server |
199
201
  | `@krizaka/ui/mascot-quote` | `MascotQuote` (`bird`, `mood`, `label`, the sentence as children, `variant` inline·callout, `animated`) | client |
200
202
  | `@krizaka/ui/decision-list` | `DecisionList` (`decisions`: `chose`, `refused`, `because`, `cost`; `labels`, `reveal`) | server |
201
- | `@krizaka/ui/code-block` | `CodeBlock` (`code`, `language`, `file`, `copyLabel`, `copiedLabel`, `highlight`), `CodeTabs` (`label`, `tabs`), `highlight` | client |
203
+ | `@krizaka/ui/code-block` | `CodeBlock` (`code`, `language`, `file`, `lineNumbers`, `annotations` — highlight · added · removed · focus · dim · error · warning —, `copyLabel`, `copiedLabel`, `highlight`), `CodeTabs` (`label`, `tabs`), `highlight`, `normalizeCodeLanguage` — on TanStack Highlight (TS, TSX, JS, JSON, YAML, Bash, HTML, CSS, Java, Kotlin, diff) | client |
204
+ | `@krizaka/ui/markdown` | `Markdown` (the source as children; `streaming`, `headingIds`, `codeLabels`, `linkAs`), `parseSource`, `healStreaming` — TanStack Markdown on the tokens, raw HTML never rendered; `@krizaka/ui/native/markdown`: the same AST in `Text` and `View` (`onLinkPress`) | server |
205
+ | `@krizaka/ui/recipes` | `defineRecipe` (the variants of a component as classes of the tokens: `tv(recipe)` on the web), `nativeRecipe(recipe).resolve(props, theme)` (the phone's styles from the same classes), `ROLES`, `withAlpha` | server |
202
206
  | `@krizaka/ui/action-bar` | `ActionBar` (`actions`: `href`, `label`, `icon`; `variant` buttons·icons — the words in a CSS tooltip —, `primaryFirst`, `size`, `label`, `linkAs`) | server |
203
207
  | `@krizaka/ui/cta-panel` | `CtaPanel` (`id`, `title`, `body`, the actions as children, `level`, `reveal`) | server |
204
208
  | `@krizaka/ui/toc` | `Toc` (`label`, `items`; the section being read marked), `TocLayout` (`toc`, the sections as children) | client |
205
- | `@krizaka/ui/app-shell` | `AppShell.Root` (`defaultCollapsed`, `collapsed` + `onCollapsedChange`) `/Sidebar` (`label`, `brand`, `footer`, `collapseLabels`, `closeLabel`; a drawer on a phone) `/Group/Item` (`icon`, `label`, `current`, `badge`, `asChild`) `/Body/Header` (`title`, `menuLabel`, `actions`) `/Main` — see [Layouts and templates](#layouts-and-templates) | client |
209
+ | `@krizaka/ui/app-shell` | `AppShell.Root` (`defaultCollapsed`, `collapsed` + `onCollapsedChange`) `/Sidebar` (`label`, `brand`, `footer`, `collapseLabels`, `closeLabel`; a drawer on a phone) `/Group/Item` (`icon`, `label`, `current`, `badge`, `shortcut`, `asChild`) `/Body/Header` (`title`, `menuLabel`, `actions`) `/Search` (`label`, `onOpen`, `shortcut` ⌘K — opens the app's `CommandDialog`) `/Main`; `collapseShortcut` (⌘B), `useShortcuts` — see [Layouts and templates](#layouts-and-templates) | client |
206
210
  | `@krizaka/ui/dashboard-layout` | `DashboardLayout` (`title`, `eyebrow`, `description`, `actions`, `toolbar`, `headingAs`), `Widget` (`title`, `description`, `action`, `span` 1·2·3·full, `level`) | server |
207
211
  | `@krizaka/ui/settings-layout` | `SettingsLayout` (`title`, `description`, `nav`, `navLabel`, `linkAs`), `SettingsSection` (`title`, `description`, `footer`) | server |
208
212
  | `@krizaka/ui/auth-layout` | `AuthLayout` (`brand`, `title`, `description`, `footer`, `legal`, `promise`, `aside`, `asideFooter`) | server |
@@ -250,7 +254,7 @@ applies it before the first paint (no flash), the provider reports it. A persist
250
254
 
251
255
  ## Page blocks
252
256
 
253
- > Category **blocks** of krizaka.com/docs/ui. The pieces every page of a Krizaka site is made of — one frame, one hero,
257
+ > Category **blocks** of ui.krizaka.com. The pieces every page of a Krizaka site is made of — one frame, one hero,
254
258
  > sections on one rhythm, cards that lead or argue, decisions, code, calls to act — so every page reads the same.
255
259
 
256
260
  ```tsx
@@ -280,7 +284,7 @@ import { LinkCard } from "@krizaka/ui/link-card";
280
284
 
281
285
  ## Page templates
282
286
 
283
- > Category **templates** of krizaka.com/docs/ui: every page of a Krizaka site has its template.
287
+ > Category **templates** of ui.krizaka.com: every page of a Krizaka site has its template.
284
288
 
285
289
  Fifteen templates, each a **distinct composition** of the blocks — not the same card grid everywhere: `LandingPage`
286
290
  (split hero with the product alive, a band of proofs, a bento), `ProductPage` (split hero in the product's brand,
@@ -300,7 +304,7 @@ boundary stays where it is: a template is a Server Component.
300
304
 
301
305
  ## Layouts and screens
302
306
 
303
- > Category **layouts** of krizaka.com/docs/ui, and **templates**: whole screens composed from the platform.
307
+ > Category **layouts** of ui.krizaka.com, and **templates**: whole screens composed from the platform.
304
308
 
305
309
  The shells of the products' screens, read from their real screens (Orochia web, admin and mobile; Orazaka web-client,
306
310
  web-admin and mobile; krizaka.com): `AppShell` (sidebar that collapses, header, drawer on a phone), `DashboardLayout`
@@ -315,7 +319,7 @@ React Native shells are `@krizaka/ui/native`'s (DS4): same names where the conce
315
319
 
316
320
  ## Registry — the code drives the documentation
317
321
 
318
- Every component is also published as data: **[krizaka.com/docs/ui](https://www.krizaka.com/docs/ui)** is generated
322
+ Every component is also published as data: **[ui.krizaka.com](https://ui.krizaka.com)** is generated
319
323
  from it at every build of the site (one page per component: when to use it, live examples with their code for the web
320
324
  and React Native, props, accessibility), and, later, a `npx @krizaka/cli add <name>` may copy a primitive into a
321
325
  product that forks it knowingly. Built by `scripts/build-registry.mjs` after tsup, from three sources in the code:
@@ -497,7 +501,7 @@ classes the components use are generated in your stylesheet.
497
501
  ```
498
502
 
499
503
  Every component, with when to use it, live examples and their code (web and React Native) and its props:
500
- [krizaka.com/docs/ui](https://www.krizaka.com/docs/ui).
504
+ [ui.krizaka.com](https://ui.krizaka.com).
501
505
 
502
506
  ## Develop
503
507
 
@@ -7,7 +7,8 @@ import {
7
7
  } from "./chunk-XCBVFMNV.js";
8
8
  import {
9
9
  buttonVariants
10
- } from "./chunk-Z6FUPB3Q.js";
10
+ } from "./chunk-2NB27LM7.js";
11
+ import "./chunk-2VNYHG4E.js";
11
12
  import {
12
13
  cn
13
14
  } from "./chunk-4LOMIQVX.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/action-bar/action-bar.tsx"],"sourcesContent":["// Server-safe. A row of actions that leave or move on (blocks.css): discuss · propose · report, read the docs, see the\n// code. `buttons`: buttons with their words — the end of a page or a section. `icons`: icon buttons whose words are the\n// accessible name and a tooltip on hover and focus (CSS only: no script, no portal) — the foot of a card.\nimport type * as React from \"react\";\n\nimport { BlockLink, isExternal, type LinkComponent } from \"../block-link\";\nimport { buttonVariants } from \"../button/button\";\nimport { cn } from \"../cn\";\nimport { ExternalGlyph } from \"../glyphs\";\n\nexport interface Action {\n /** Where it leads. An absolute URL opens in a new tab, with the external mark. */\n href: string;\n /** Its words (the accessible name, and the tooltip of an icon action). */\n label: string;\n /** Its icon (a signature icon): decorative. Required by `variant=\"icons\"`. */\n icon?: React.ReactNode;\n}\n\nexport type ActionBarProps = React.HTMLAttributes<HTMLUListElement> & {\n /** The actions, in order of importance. */\n actions: readonly Action[];\n /**\n * `buttons` (with their words) or `icons` (icon buttons, the words in a tooltip).\n * @default \"buttons\"\n */\n variant?: \"buttons\" | \"icons\";\n /**\n * The first action as the primary button (the one action a block is about); the others stay secondary.\n * @default false\n */\n primaryFirst?: boolean;\n /**\n * The buttons' size: `sm` in a card, `md` in a section, `lg` on a hero or a closing call.\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** What the actions are about (`Talk about Krizaka Java`): the list's accessible name. */\n label?: string;\n /** The app's link component for internal addresses (Next's `Link`); an `<a>` otherwise. */\n linkAs?: LinkComponent;\n};\n\n/** A row of link actions: buttons with their words, or icon buttons with a tooltip. */\nexport function ActionBar({ actions, variant = \"buttons\", primaryFirst = false, size = \"md\", label, linkAs, className, ...props }: ActionBarProps) {\n const icons = variant === \"icons\";\n return (\n <ul className={cn(\"kz-actions\", className)} data-variant={icons ? \"icons\" : undefined} aria-label={label} {...props}>\n {actions.map((a, i) => {\n const external = isExternal(a.href);\n return (\n <li key={a.href}>\n {icons ? (\n <BlockLink\n href={a.href}\n linkAs={linkAs}\n external={external}\n aria-label={a.label}\n data-tip={a.label}\n className={buttonVariants({ variant: \"ghost\", size: \"icon\", shape: \"cut\", className: size === \"sm\" ? \"h-9 w-9\" : undefined })}\n >\n {a.icon}\n </BlockLink>\n ) : (\n <BlockLink\n href={a.href}\n linkAs={linkAs}\n external={external}\n className={buttonVariants({ variant: primaryFirst && i === 0 ? \"primary\" : \"secondary\", size, shape: \"cut\", className: \"gap-2\" })}\n >\n {a.icon}\n {a.label}\n {external && <ExternalGlyph className=\"h-3 w-3 opacity-70\" />}\n </BlockLink>\n )}\n </li>\n );\n })}\n </ul>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqDc,cAWA,YAXA;AATP,SAAS,UAAU,EAAE,SAAS,UAAU,WAAW,eAAe,OAAO,OAAO,MAAM,OAAO,QAAQ,WAAW,GAAG,MAAM,GAAmB;AACjJ,QAAM,QAAQ,YAAY;AAC1B,SACE,oBAAC,QAAG,WAAW,GAAG,cAAc,SAAS,GAAG,gBAAc,QAAQ,UAAU,QAAW,cAAY,OAAQ,GAAG,OAC3G,kBAAQ,IAAI,CAAC,GAAG,MAAM;AACrB,UAAM,WAAW,WAAW,EAAE,IAAI;AAClC,WACE,oBAAC,QACE,kBACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,EAAE;AAAA,QACR;AAAA,QACA;AAAA,QACA,cAAY,EAAE;AAAA,QACd,YAAU,EAAE;AAAA,QACZ,WAAW,eAAe,EAAE,SAAS,SAAS,MAAM,QAAQ,OAAO,OAAO,WAAW,SAAS,OAAO,YAAY,OAAU,CAAC;AAAA,QAE3H,YAAE;AAAA;AAAA,IACL,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,EAAE;AAAA,QACR;AAAA,QACA;AAAA,QACA,WAAW,eAAe,EAAE,SAAS,gBAAgB,MAAM,IAAI,YAAY,aAAa,MAAM,OAAO,OAAO,WAAW,QAAQ,CAAC;AAAA,QAE/H;AAAA,YAAE;AAAA,UACF,EAAE;AAAA,UACF,YAAY,oBAAC,iBAAc,WAAU,sBAAqB;AAAA;AAAA;AAAA,IAC7D,KAtBK,EAAE,IAwBX;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/action-bar/action-bar.tsx"],"sourcesContent":["// Server-safe. A row of actions that leave or move on (blocks.css): discuss · propose · report, read the docs, see the\n// code. `buttons`: buttons with their words — the end of a page or a section. `icons`: icon buttons whose words are the\n// accessible name and a tooltip on hover and focus (CSS only: no script, no portal) — the foot of a card.\nimport type * as React from \"react\";\n\nimport { BlockLink, isExternal, type LinkComponent } from \"../block-link\";\nimport { buttonVariants } from \"../button/button\";\nimport { cn } from \"../cn\";\nimport { ExternalGlyph } from \"../glyphs\";\n\nexport interface Action {\n /** Where it leads. An absolute URL opens in a new tab, with the external mark. */\n href: string;\n /** Its words (the accessible name, and the tooltip of an icon action). */\n label: string;\n /** Its icon (a signature icon): decorative. Required by `variant=\"icons\"`. */\n icon?: React.ReactNode;\n}\n\nexport type ActionBarProps = React.HTMLAttributes<HTMLUListElement> & {\n /** The actions, in order of importance. */\n actions: readonly Action[];\n /**\n * `buttons` (with their words) or `icons` (icon buttons, the words in a tooltip).\n * @default \"buttons\"\n */\n variant?: \"buttons\" | \"icons\";\n /**\n * The first action as the primary button (the one action a block is about); the others stay secondary.\n * @default false\n */\n primaryFirst?: boolean;\n /**\n * The buttons' size: `sm` in a card, `md` in a section, `lg` on a hero or a closing call.\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** What the actions are about (`Talk about Krizaka Java`): the list's accessible name. */\n label?: string;\n /** The app's link component for internal addresses (Next's `Link`); an `<a>` otherwise. */\n linkAs?: LinkComponent;\n};\n\n/** A row of link actions: buttons with their words, or icon buttons with a tooltip. */\nexport function ActionBar({ actions, variant = \"buttons\", primaryFirst = false, size = \"md\", label, linkAs, className, ...props }: ActionBarProps) {\n const icons = variant === \"icons\";\n return (\n <ul className={cn(\"kz-actions\", className)} data-variant={icons ? \"icons\" : undefined} aria-label={label} {...props}>\n {actions.map((a, i) => {\n const external = isExternal(a.href);\n return (\n <li key={a.href}>\n {icons ? (\n <BlockLink\n href={a.href}\n linkAs={linkAs}\n external={external}\n aria-label={a.label}\n data-tip={a.label}\n className={buttonVariants({ variant: \"ghost\", size: \"icon\", shape: \"cut\", className: size === \"sm\" ? \"h-9 w-9\" : undefined })}\n >\n {a.icon}\n </BlockLink>\n ) : (\n <BlockLink\n href={a.href}\n linkAs={linkAs}\n external={external}\n className={buttonVariants({ variant: primaryFirst && i === 0 ? \"primary\" : \"secondary\", size, shape: \"cut\", className: \"gap-2\" })}\n >\n {a.icon}\n {a.label}\n {external && <ExternalGlyph className=\"h-3 w-3 opacity-70\" />}\n </BlockLink>\n )}\n </li>\n );\n })}\n </ul>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAqDc,cAWA,YAXA;AATP,SAAS,UAAU,EAAE,SAAS,UAAU,WAAW,eAAe,OAAO,OAAO,MAAM,OAAO,QAAQ,WAAW,GAAG,MAAM,GAAmB;AACjJ,QAAM,QAAQ,YAAY;AAC1B,SACE,oBAAC,QAAG,WAAW,GAAG,cAAc,SAAS,GAAG,gBAAc,QAAQ,UAAU,QAAW,cAAY,OAAQ,GAAG,OAC3G,kBAAQ,IAAI,CAAC,GAAG,MAAM;AACrB,UAAM,WAAW,WAAW,EAAE,IAAI;AAClC,WACE,oBAAC,QACE,kBACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,EAAE;AAAA,QACR;AAAA,QACA;AAAA,QACA,cAAY,EAAE;AAAA,QACd,YAAU,EAAE;AAAA,QACZ,WAAW,eAAe,EAAE,SAAS,SAAS,MAAM,QAAQ,OAAO,OAAO,WAAW,SAAS,OAAO,YAAY,OAAU,CAAC;AAAA,QAE3H,YAAE;AAAA;AAAA,IACL,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,EAAE;AAAA,QACR;AAAA,QACA;AAAA,QACA,WAAW,eAAe,EAAE,SAAS,gBAAgB,MAAM,IAAI,YAAY,aAAa,MAAM,OAAO,OAAO,WAAW,QAAQ,CAAC;AAAA,QAE/H;AAAA,YAAE;AAAA,UACF,EAAE;AAAA,UACF,YAAY,oBAAC,iBAAc,WAAU,sBAAqB;AAAA;AAAA;AAAA,IAC7D,KAtBK,EAAE,IAwBX;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":[]}
@@ -9,6 +9,7 @@ declare const appShell: tailwind_variants.TVReturnType<{
9
9
  header?: tailwind_variants.ClassValue;
10
10
  main?: tailwind_variants.ClassValue;
11
11
  nav?: tailwind_variants.ClassValue;
12
+ search?: tailwind_variants.ClassValue;
12
13
  title?: tailwind_variants.ClassValue;
13
14
  group?: tailwind_variants.ClassValue;
14
15
  root?: tailwind_variants.ClassValue;
@@ -16,6 +17,9 @@ declare const appShell: tailwind_variants.TVReturnType<{
16
17
  overlay?: tailwind_variants.ClassValue;
17
18
  brand?: tailwind_variants.ClassValue;
18
19
  itemLabel?: tailwind_variants.ClassValue;
20
+ itemHint?: tailwind_variants.ClassValue;
21
+ searchLabel?: tailwind_variants.ClassValue;
22
+ searchKeys?: tailwind_variants.ClassValue;
19
23
  actions?: tailwind_variants.ClassValue;
20
24
  sidebar?: tailwind_variants.ClassValue;
21
25
  item?: tailwind_variants.ClassValue;
@@ -32,6 +36,7 @@ declare const appShell: tailwind_variants.TVReturnType<{
32
36
  header?: tailwind_variants.ClassValue;
33
37
  main?: tailwind_variants.ClassValue;
34
38
  nav?: tailwind_variants.ClassValue;
39
+ search?: tailwind_variants.ClassValue;
35
40
  title?: tailwind_variants.ClassValue;
36
41
  group?: tailwind_variants.ClassValue;
37
42
  root?: tailwind_variants.ClassValue;
@@ -39,6 +44,9 @@ declare const appShell: tailwind_variants.TVReturnType<{
39
44
  overlay?: tailwind_variants.ClassValue;
40
45
  brand?: tailwind_variants.ClassValue;
41
46
  itemLabel?: tailwind_variants.ClassValue;
47
+ itemHint?: tailwind_variants.ClassValue;
48
+ searchLabel?: tailwind_variants.ClassValue;
49
+ searchKeys?: tailwind_variants.ClassValue;
42
50
  actions?: tailwind_variants.ClassValue;
43
51
  sidebar?: tailwind_variants.ClassValue;
44
52
  item?: tailwind_variants.ClassValue;
@@ -57,6 +65,7 @@ declare const appShell: tailwind_variants.TVReturnType<{
57
65
  group: string;
58
66
  item: string;
59
67
  itemLabel: string;
68
+ itemHint: string;
60
69
  itemBadge: string;
61
70
  footer: string;
62
71
  collapse: string;
@@ -64,6 +73,9 @@ declare const appShell: tailwind_variants.TVReturnType<{
64
73
  header: string;
65
74
  menuButton: string;
66
75
  title: string;
76
+ search: string;
77
+ searchLabel: string;
78
+ searchKeys: string;
67
79
  actions: string;
68
80
  main: string;
69
81
  }, undefined, {
@@ -74,6 +86,7 @@ declare const appShell: tailwind_variants.TVReturnType<{
74
86
  header?: tailwind_variants.ClassValue;
75
87
  main?: tailwind_variants.ClassValue;
76
88
  nav?: tailwind_variants.ClassValue;
89
+ search?: tailwind_variants.ClassValue;
77
90
  title?: tailwind_variants.ClassValue;
78
91
  group?: tailwind_variants.ClassValue;
79
92
  root?: tailwind_variants.ClassValue;
@@ -81,6 +94,9 @@ declare const appShell: tailwind_variants.TVReturnType<{
81
94
  overlay?: tailwind_variants.ClassValue;
82
95
  brand?: tailwind_variants.ClassValue;
83
96
  itemLabel?: tailwind_variants.ClassValue;
97
+ itemHint?: tailwind_variants.ClassValue;
98
+ searchLabel?: tailwind_variants.ClassValue;
99
+ searchKeys?: tailwind_variants.ClassValue;
84
100
  actions?: tailwind_variants.ClassValue;
85
101
  sidebar?: tailwind_variants.ClassValue;
86
102
  item?: tailwind_variants.ClassValue;
@@ -99,6 +115,7 @@ declare const appShell: tailwind_variants.TVReturnType<{
99
115
  group: string;
100
116
  item: string;
101
117
  itemLabel: string;
118
+ itemHint: string;
102
119
  itemBadge: string;
103
120
  footer: string;
104
121
  collapse: string;
@@ -106,6 +123,9 @@ declare const appShell: tailwind_variants.TVReturnType<{
106
123
  header: string;
107
124
  menuButton: string;
108
125
  title: string;
126
+ search: string;
127
+ searchLabel: string;
128
+ searchKeys: string;
109
129
  actions: string;
110
130
  main: string;
111
131
  }, tailwind_variants.TVReturnTypeLike<unknown, {
@@ -118,6 +138,7 @@ declare const appShell: tailwind_variants.TVReturnType<{
118
138
  group: string;
119
139
  item: string;
120
140
  itemLabel: string;
141
+ itemHint: string;
121
142
  itemBadge: string;
122
143
  footer: string;
123
144
  collapse: string;
@@ -125,6 +146,9 @@ declare const appShell: tailwind_variants.TVReturnType<{
125
146
  header: string;
126
147
  menuButton: string;
127
148
  title: string;
149
+ search: string;
150
+ searchLabel: string;
151
+ searchKeys: string;
128
152
  actions: string;
129
153
  main: string;
130
154
  }>>;
@@ -137,9 +161,15 @@ type AppShellRootProps = React.ComponentProps<"div"> & {
137
161
  onCollapsedChange?: (collapsed: boolean) => void;
138
162
  /** The words of the "skip to content" link (`Skip to content`), the first tab stop; it lands on `AppShell.Main`. Without it, no link. */
139
163
  skipLabel?: string;
164
+ /**
165
+ * The shortcut that folds and unfolds the sidebar (a combination: `mod+b` is ⌘B on a Mac, Ctrl+B elsewhere), or `false`
166
+ * for none.
167
+ * @default "mod+b"
168
+ */
169
+ collapseShortcut?: string | false;
140
170
  };
141
171
  /** The shell: holds the collapse (desktop) and the drawer (phone) state. Children: Sidebar, then Body. */
142
- declare function AppShellRoot({ defaultCollapsed, collapsed: controlled, onCollapsedChange, skipLabel, className, children, ...props }: AppShellRootProps): React.JSX.Element;
172
+ declare function AppShellRoot({ defaultCollapsed, collapsed: controlled, onCollapsedChange, skipLabel, collapseShortcut, className, children, ...props }: AppShellRootProps): React.JSX.Element;
143
173
  type AppShellSidebarProps = Omit<React.ComponentProps<"nav">, "children"> & {
144
174
  /** The navigation's accessible name (`Studio`, `Administration`). */
145
175
  label: string;
@@ -171,13 +201,18 @@ type AppShellItemProps = Omit<React.ComponentProps<"a">, "children"> & {
171
201
  current?: boolean;
172
202
  /** A count beside it (unread, pending): a number or a few characters. */
173
203
  badge?: React.ReactNode;
204
+ /**
205
+ * A keyboard shortcut that goes to it: a sequence (`g d`: G then D) or a combination (`mod+1`). It clicks the item, so a
206
+ * router link navigates. The keys are shown on hover and focus when the sidebar is open.
207
+ */
208
+ shortcut?: string;
174
209
  /** Renders your link (Next's `Link`) with the item's look: pass it as the only child. */
175
210
  asChild?: boolean;
176
211
  /** Your link element when `asChild`. */
177
212
  children?: React.ReactElement;
178
213
  };
179
214
  /** A destination of the sidebar: icon, words, a count. */
180
- declare function AppShellItem({ icon, label, current, badge, asChild, children, className, ...props }: AppShellItemProps): React.JSX.Element;
215
+ declare function AppShellItem({ icon, label, current, badge, shortcut, asChild, children, className, ...props }: AppShellItemProps): React.JSX.Element;
181
216
  /** The column right of the sidebar: Header, then Main. */
182
217
  declare function AppShellBody({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
183
218
  type AppShellHeaderProps = Omit<React.ComponentProps<"header">, "title"> & {
@@ -190,6 +225,19 @@ type AppShellHeaderProps = Omit<React.ComponentProps<"header">, "title"> & {
190
225
  };
191
226
  /** The top of the screen: the drawer's button (phone), the title, the actions. Sticky. */
192
227
  declare function AppShellHeader({ title, menuLabel, actions, className, children, ...props }: AppShellHeaderProps): React.JSX.Element;
228
+ type AppShellSearchProps = Omit<React.ComponentProps<"button">, "children" | "onClick"> & {
229
+ /** The words ("Search", "Search creators"): the accessible name, shown beside the keys on a desktop. */
230
+ label: string;
231
+ /** Called when the button is pressed or the shortcut is typed: open your command palette (`CommandDialog`) here. */
232
+ onOpen: () => void;
233
+ /**
234
+ * The shortcut that opens it (a combination); shown on its keys.
235
+ * @default "mod+k"
236
+ */
237
+ shortcut?: string;
238
+ };
239
+ /** The header's search: a button that looks like a field (a magnifier, its words, ⌘K) and opens the app's command palette. */
240
+ declare function AppShellSearch({ label, onOpen, shortcut, className, ...props }: AppShellSearchProps): React.JSX.Element;
193
241
  /** The screen's content. */
194
242
  declare function AppShellMain({ className, ...props }: React.ComponentProps<"main">): React.JSX.Element;
195
243
  declare const AppShell: {
@@ -199,7 +247,37 @@ declare const AppShell: {
199
247
  Item: typeof AppShellItem;
200
248
  Body: typeof AppShellBody;
201
249
  Header: typeof AppShellHeader;
250
+ Search: typeof AppShellSearch;
202
251
  Main: typeof AppShellMain;
203
252
  };
204
253
 
205
- export { AppShell, AppShellBody, AppShellGroup, type AppShellGroupProps, AppShellHeader, type AppShellHeaderProps, AppShellItem, type AppShellItemProps, AppShellMain, AppShellRoot, type AppShellRootProps, AppShellSidebar, type AppShellSidebarProps, appShell };
254
+ type ShortcutMap = Readonly<Record<string, ((event: KeyboardEvent) => void) | undefined>>;
255
+ type Combo = {
256
+ key: string;
257
+ mod: boolean;
258
+ ctrl: boolean;
259
+ alt: boolean;
260
+ shift: boolean;
261
+ meta: boolean;
262
+ };
263
+ /** `"mod+shift+k"` → its parts. A lone `+` key is written `plus`. */
264
+ declare function parseCombo(combo: string): Combo;
265
+ /** Whether a key event is this combination: its modifiers exactly (`mod` is ⌘ or Ctrl by platform), then its key. */
266
+ declare function matchesCombo(event: Pick<KeyboardEvent, "key" | "metaKey" | "ctrlKey" | "altKey" | "shiftKey">, combo: string, apple?: boolean): boolean;
267
+ /** The keys of a shortcut to show on a keycap: `["⌘", "K"]` on a Mac, `["Ctrl", "K"]` elsewhere; a sequence `g d` is `["G", "D"]`. */
268
+ declare function formatShortcut(shortcut: string, apple?: boolean): string[];
269
+ /** Whether the person is on an Apple device — `false` on the server and in the first client render (no mismatch), then right. */
270
+ declare function useApple(): boolean;
271
+ /** The `aria-keyshortcuts` of a combination (`Control+K`, `Meta+K` on a Mac); a sequence has none (it is not a chord). */
272
+ declare function ariaKeyShortcuts(shortcut: string, apple?: boolean): string | undefined;
273
+ /**
274
+ * Binds shortcuts for as long as the component lives (`enabled` turns them off). Each key is a combination or a sequence,
275
+ * each value runs when it is pressed; the handler may change on every render. A shortcut that fires is `preventDefault`ed.
276
+ * A sequence waits `timeout` ms for its next key.
277
+ */
278
+ declare function useShortcuts(map: ShortcutMap, { enabled, timeout }?: {
279
+ enabled?: boolean;
280
+ timeout?: number;
281
+ }): void;
282
+
283
+ export { AppShell, AppShellBody, AppShellGroup, type AppShellGroupProps, AppShellHeader, type AppShellHeaderProps, AppShellItem, type AppShellItemProps, AppShellMain, AppShellRoot, type AppShellRootProps, AppShellSearch, type AppShellSearchProps, AppShellSidebar, type AppShellSidebarProps, type ShortcutMap, appShell, ariaKeyShortcuts, formatShortcut, matchesCombo, parseCombo, useApple, useShortcuts };
package/dist/app-shell.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import {
3
3
  ChevronGlyph,
4
- CloseGlyph
5
- } from "./chunk-AZPKXP3O.js";
4
+ CloseGlyph,
5
+ SearchGlyph
6
+ } from "./chunk-AFY35UCB.js";
6
7
  import {
7
8
  cn
8
9
  } from "./chunk-G3ETON2A.js";
@@ -12,10 +13,111 @@ import {
12
13
 
13
14
  // src/app-shell/app-shell.tsx
14
15
  import { Dialog as DialogPrimitive, Slot } from "radix-ui";
15
- import * as React from "react";
16
+ import * as React3 from "react";
17
+ import { tv as tv2 } from "tailwind-variants";
18
+
19
+ // src/kbd/kbd.tsx
16
20
  import { tv } from "tailwind-variants";
17
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
18
- var appShell = tv({
21
+ import { jsx } from "react/jsx-runtime";
22
+ var kbdVariants = tv({
23
+ base: "inline-flex shrink-0 items-center justify-center rounded-md border border-border-default border-b-2 bg-surface-2 font-mono font-semibold text-fg-secondary",
24
+ variants: { size: { sm: "h-5 min-w-5 px-1 text-[10px]", md: "h-6 min-w-6 px-1.5 text-xs" } },
25
+ defaultVariants: { size: "md" }
26
+ });
27
+ function Kbd({ size, className, ...props }) {
28
+ return /* @__PURE__ */ jsx("kbd", { className: kbdVariants({ size, className }), ...props });
29
+ }
30
+
31
+ // src/app-shell/shortcuts.ts
32
+ import * as React2 from "react";
33
+
34
+ // src/media.ts
35
+ import * as React from "react";
36
+ function useLatest(value) {
37
+ const ref = React.useRef(value);
38
+ React.useLayoutEffect(() => {
39
+ ref.current = value;
40
+ });
41
+ return ref;
42
+ }
43
+
44
+ // src/app-shell/shortcuts.ts
45
+ var isApple = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
46
+ var ALIASES = { esc: "escape", return: "enter", space: " ", spacebar: " ", up: "arrowup", down: "arrowdown", left: "arrowleft", right: "arrowright", del: "delete", plus: "+" };
47
+ function parseCombo(combo) {
48
+ const parts = combo.toLowerCase().split("+").filter(Boolean);
49
+ const key = ALIASES[parts.at(-1) ?? ""] ?? parts.at(-1) ?? "";
50
+ const has = (name) => parts.slice(0, -1).includes(name);
51
+ return { key, mod: has("mod"), ctrl: has("ctrl"), alt: has("alt"), shift: has("shift"), meta: has("meta") };
52
+ }
53
+ function matchesCombo(event, combo, apple = isApple()) {
54
+ const want = parseCombo(combo);
55
+ const meta = want.meta || want.mod && apple;
56
+ const ctrl = want.ctrl || want.mod && !apple;
57
+ if (event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== want.alt) return false;
58
+ if (want.shift && !event.shiftKey) return false;
59
+ return event.key.toLowerCase() === want.key;
60
+ }
61
+ function formatShortcut(shortcut, apple = isApple()) {
62
+ if (shortcut.includes(" ")) return shortcut.split(/\s+/).map((key2) => key2.toUpperCase());
63
+ const parts = shortcut.split("+").filter(Boolean);
64
+ const key = parts.at(-1) ?? "";
65
+ const names = { mod: ["\u2318", "Ctrl"], meta: ["\u2318", "Win"], ctrl: ["\u2303", "Ctrl"], alt: ["\u2325", "Alt"], shift: ["\u21E7", "Shift"] };
66
+ const shown = parts.slice(0, -1).map((part) => names[part.toLowerCase()]?.[apple ? 0 : 1] ?? part);
67
+ const glyph = { escape: "Esc", enter: "\u21B5", arrowup: "\u2191", arrowdown: "\u2193", arrowleft: "\u2190", arrowright: "\u2192", " ": "Space" };
68
+ const last = glyph[ALIASES[key.toLowerCase()] ?? key.toLowerCase()] ?? key.toUpperCase();
69
+ return [...shown, last];
70
+ }
71
+ var noop = () => () => void 0;
72
+ function useApple() {
73
+ return React2.useSyncExternalStore(noop, isApple, () => false);
74
+ }
75
+ function ariaKeyShortcuts(shortcut, apple = isApple()) {
76
+ if (shortcut.includes(" ")) return void 0;
77
+ const parts = shortcut.split("+").filter(Boolean);
78
+ const names = { mod: apple ? "Meta" : "Control", meta: "Meta", ctrl: "Control", alt: "Alt", shift: "Shift" };
79
+ const key = parts.at(-1) ?? "";
80
+ return [...parts.slice(0, -1).map((part) => names[part.toLowerCase()] ?? part), key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1)].join("+");
81
+ }
82
+ function isEditable(target) {
83
+ const element = target instanceof HTMLElement ? target : null;
84
+ if (!element) return { field: false, rich: false };
85
+ const rich = element.isContentEditable;
86
+ return { field: rich || /^(input|textarea|select)$/i.test(element.tagName), rich };
87
+ }
88
+ function useShortcuts(map, { enabled = true, timeout = 900 } = {}) {
89
+ const latest = useLatest(map);
90
+ const names = Object.keys(map).sort().join("\n");
91
+ React2.useEffect(() => {
92
+ if (!enabled) return;
93
+ const bindings = names.split("\n").filter(Boolean);
94
+ let buffer = [];
95
+ const onKeyDown = (event) => {
96
+ if (event.defaultPrevented || event.isComposing || event.repeat || !event.key) return;
97
+ const editing = isEditable(event.target);
98
+ const chord = event.metaKey || event.ctrlKey || event.altKey;
99
+ const now = Date.now();
100
+ buffer = [...buffer.filter((entry) => now - entry.at <= timeout), { key: event.key.toLowerCase(), at: now }].slice(-4);
101
+ for (const binding of bindings) {
102
+ const handler = latest.current[binding];
103
+ if (!handler) continue;
104
+ const keys = binding.toLowerCase().split(/\s+/).map((key) => ALIASES[key] ?? key);
105
+ const fired = keys.length > 1 ? !chord && !editing.field && buffer.length >= keys.length && keys.every((key, i) => buffer[buffer.length - keys.length + i]?.key === key) : matchesCombo(event, binding) && (chord ? !editing.rich : !editing.field);
106
+ if (!fired) continue;
107
+ event.preventDefault();
108
+ buffer = [];
109
+ handler(event);
110
+ return;
111
+ }
112
+ };
113
+ document.addEventListener("keydown", onKeyDown);
114
+ return () => document.removeEventListener("keydown", onKeyDown);
115
+ }, [enabled, names, latest, timeout]);
116
+ }
117
+
118
+ // src/app-shell/app-shell.tsx
119
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
120
+ var appShell = tv2({
19
121
  slots: {
20
122
  root: "flex min-h-dvh w-full bg-surface-0 text-fg",
21
123
  sidebar: "sticky top-0 hidden h-dvh shrink-0 flex-col border-r border-border-subtle bg-surface-1 transition-[width] duration-300 motion-reduce:transition-none md:flex w-64 data-collapsed:w-[4.25rem]",
@@ -26,6 +128,7 @@ var appShell = tv({
26
128
  group: "mt-3 mb-1 px-3 font-mono text-[10.5px] font-semibold uppercase tracking-[0.14em] text-fg-secondary [[data-collapsed]_&]:sr-only",
27
129
  item: "group/item relative flex h-10 items-center gap-3 rounded-lg px-3 text-sm font-medium text-fg-secondary no-underline transition-colors hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:bg-accent-soft aria-[current=page]:text-fg aria-[current=page]:before:absolute aria-[current=page]:before:inset-y-2 aria-[current=page]:before:-left-2 aria-[current=page]:before:w-1 aria-[current=page]:before:rounded-full aria-[current=page]:before:bg-accent [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [&>svg]:h-[18px] [&>svg]:w-[18px] [&>svg]:shrink-0",
28
130
  itemLabel: "min-w-0 flex-1 truncate [[data-collapsed]_&]:sr-only",
131
+ itemHint: "ml-auto hidden items-center gap-0.5 text-fg-secondary group-hover/item:flex group-focus-visible/item:flex [[data-collapsed]_&]:!hidden",
29
132
  itemBadge: "ml-auto inline-flex min-w-5 items-center justify-center rounded-full bg-accent px-1.5 text-[11px] font-bold text-on-accent [[data-collapsed]_&]:absolute [[data-collapsed]_&]:right-1.5 [[data-collapsed]_&]:top-1 [[data-collapsed]_&]:min-w-4 [[data-collapsed]_&]:px-1 [[data-collapsed]_&]:text-[10px]",
30
133
  footer: "shrink-0 border-t border-border-subtle p-2",
31
134
  collapse: "hidden h-9 w-full items-center gap-2 rounded-lg px-3 text-xs font-medium text-fg-secondary hover:bg-surface-2 hover:text-fg md:flex focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0",
@@ -33,31 +136,35 @@ var appShell = tv({
33
136
  header: "sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-border-subtle bg-surface-0/85 px-4 backdrop-blur-xl md:px-6",
34
137
  menuButton: "inline-flex h-9 w-9 items-center justify-center rounded-lg border border-border-subtle bg-surface-1 text-fg md:hidden focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
35
138
  title: "min-w-0 flex-1 truncate font-display text-base font-bold tracking-tight",
139
+ search: "inline-flex h-9 min-w-9 items-center justify-center gap-2 rounded-lg border border-border-subtle bg-surface-1 px-2.5 text-sm text-fg-secondary transition-colors hover:border-border-default hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring md:w-56 md:justify-start [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0",
140
+ searchLabel: "sr-only md:not-sr-only md:flex-1 md:truncate md:text-left",
141
+ searchKeys: "hidden items-center gap-0.5 md:flex",
36
142
  actions: "ml-auto flex shrink-0 items-center gap-2",
37
143
  main: "min-w-0 flex-1 px-4 py-6 md:px-6 md:py-8"
38
144
  }
39
145
  });
40
146
  var s = appShell();
41
- var ShellContext = React.createContext(null);
147
+ var ShellContext = React3.createContext(null);
42
148
  var useShell = () => {
43
- const ctx = React.useContext(ShellContext);
149
+ const ctx = React3.useContext(ShellContext);
44
150
  if (!ctx) throw new Error("AppShell parts must be inside <AppShell.Root>");
45
151
  return ctx;
46
152
  };
47
- function AppShellRoot({ defaultCollapsed = false, collapsed: controlled, onCollapsedChange, skipLabel, className, children, ...props }) {
48
- const [own, setOwn] = React.useState(defaultCollapsed);
49
- const [open, setOpen] = React.useState(false);
153
+ function AppShellRoot({ defaultCollapsed = false, collapsed: controlled, onCollapsedChange, skipLabel, collapseShortcut = "mod+b", className, children, ...props }) {
154
+ const [own, setOwn] = React3.useState(defaultCollapsed);
155
+ const [open, setOpen] = React3.useState(false);
50
156
  const collapsed = controlled ?? own;
51
- const setCollapsed = React.useCallback(
157
+ const setCollapsed = React3.useCallback(
52
158
  (value2) => {
53
159
  if (controlled === void 0) setOwn(value2);
54
160
  onCollapsedChange?.(value2);
55
161
  },
56
162
  [controlled, onCollapsedChange]
57
163
  );
58
- const value = React.useMemo(() => ({ collapsed, setCollapsed, open, setOpen, inDrawer: false }), [collapsed, setCollapsed, open]);
59
- return /* @__PURE__ */ jsx(ShellContext.Provider, { value, children: /* @__PURE__ */ jsxs("div", { className: s.root({ className }), ...props, children: [
60
- skipLabel && /* @__PURE__ */ jsx(SkipLink, { href: "#content", children: skipLabel }),
164
+ const value = React3.useMemo(() => ({ collapsed, setCollapsed, open, setOpen, inDrawer: false }), [collapsed, setCollapsed, open]);
165
+ useShortcuts(collapseShortcut ? { [collapseShortcut]: () => setCollapsed(!collapsed) } : {});
166
+ return /* @__PURE__ */ jsx2(ShellContext.Provider, { value, children: /* @__PURE__ */ jsxs("div", { className: s.root({ className }), ...props, children: [
167
+ skipLabel && /* @__PURE__ */ jsx2(SkipLink, { href: "#content", children: skipLabel }),
61
168
  children
62
169
  ] }) });
63
170
  }
@@ -66,14 +173,14 @@ function SidebarContent({ brand, children, footer, collapseLabels, closeLabel })
66
173
  return /* @__PURE__ */ jsxs(Fragment, { children: [
67
174
  /* @__PURE__ */ jsxs("div", { className: s.brand(), children: [
68
175
  brand,
69
- inDrawer && /* @__PURE__ */ jsx(DialogPrimitive.Close, { className: cn(s.menuButton(), "ml-auto flex"), "aria-label": closeLabel, children: /* @__PURE__ */ jsx(CloseGlyph, { className: "h-4 w-4" }) })
176
+ inDrawer && /* @__PURE__ */ jsx2(DialogPrimitive.Close, { className: cn(s.menuButton(), "ml-auto flex"), "aria-label": closeLabel, children: /* @__PURE__ */ jsx2(CloseGlyph, { className: "h-4 w-4" }) })
70
177
  ] }),
71
- /* @__PURE__ */ jsx("div", { className: s.nav(), children }),
178
+ /* @__PURE__ */ jsx2("div", { className: s.nav(), children }),
72
179
  (footer || collapseLabels && !inDrawer) && /* @__PURE__ */ jsxs("div", { className: s.footer(), children: [
73
180
  footer,
74
181
  collapseLabels && !inDrawer && /* @__PURE__ */ jsxs("button", { type: "button", className: s.collapse(), "aria-expanded": !collapsed, onClick: () => setCollapsed(!collapsed), children: [
75
- /* @__PURE__ */ jsx(ChevronGlyph, { direction: collapsed ? "right" : "left", className: "h-4 w-4" }),
76
- /* @__PURE__ */ jsx("span", { className: collapsed ? "sr-only" : void 0, children: collapsed ? collapseLabels[1] : collapseLabels[0] })
182
+ /* @__PURE__ */ jsx2(ChevronGlyph, { direction: collapsed ? "right" : "left", className: "h-4 w-4" }),
183
+ /* @__PURE__ */ jsx2("span", { className: collapsed ? "sr-only" : void 0, children: collapsed ? collapseLabels[1] : collapseLabels[0] })
77
184
  ] })
78
185
  ] })
79
186
  ] });
@@ -82,43 +189,56 @@ function AppShellSidebar({ label, brand, children, footer, collapseLabels, close
82
189
  const shell = useShell();
83
190
  const content = { brand, children, footer, collapseLabels, closeLabel };
84
191
  return /* @__PURE__ */ jsxs(Fragment, { children: [
85
- /* @__PURE__ */ jsx("nav", { "aria-label": label, "data-collapsed": shell.collapsed ? "" : void 0, className: s.sidebar({ className }), ...props, children: /* @__PURE__ */ jsx(SidebarContent, { ...content }) }),
86
- /* @__PURE__ */ jsx(DialogPrimitive.Root, { open: shell.open, onOpenChange: shell.setOpen, children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [
87
- /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: s.overlay() }),
192
+ /* @__PURE__ */ jsx2("nav", { "aria-label": label, "data-collapsed": shell.collapsed ? "" : void 0, className: s.sidebar({ className }), ...props, children: /* @__PURE__ */ jsx2(SidebarContent, { ...content }) }),
193
+ /* @__PURE__ */ jsx2(DialogPrimitive.Root, { open: shell.open, onOpenChange: shell.setOpen, children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [
194
+ /* @__PURE__ */ jsx2(DialogPrimitive.Overlay, { className: s.overlay() }),
88
195
  /* @__PURE__ */ jsxs(DialogPrimitive.Content, { className: s.drawer(), "aria-describedby": void 0, children: [
89
- /* @__PURE__ */ jsx(DialogPrimitive.Title, { className: "sr-only", children: label }),
90
- /* @__PURE__ */ jsx(ShellContext.Provider, { value: { ...shell, collapsed: false, inDrawer: true }, children: /* @__PURE__ */ jsx("nav", { "aria-label": label, className: "flex min-h-0 flex-1 flex-col", onClick: (e) => e.target.closest("a") && shell.setOpen(false), children: /* @__PURE__ */ jsx(SidebarContent, { ...content }) }) })
196
+ /* @__PURE__ */ jsx2(DialogPrimitive.Title, { className: "sr-only", children: label }),
197
+ /* @__PURE__ */ jsx2(ShellContext.Provider, { value: { ...shell, collapsed: false, inDrawer: true }, children: /* @__PURE__ */ jsx2("nav", { "aria-label": label, className: "flex min-h-0 flex-1 flex-col", onClick: (e) => e.target.closest("a") && shell.setOpen(false), children: /* @__PURE__ */ jsx2(SidebarContent, { ...content }) }) })
91
198
  ] })
92
199
  ] }) })
93
200
  ] });
94
201
  }
95
202
  function AppShellGroup({ className, ...props }) {
96
- return /* @__PURE__ */ jsx("p", { className: s.group({ className }), ...props });
203
+ return /* @__PURE__ */ jsx2("p", { className: s.group({ className }), ...props });
97
204
  }
98
- function AppShellItem({ icon, label, current, badge, asChild, children, className, ...props }) {
99
- const { collapsed } = useShell();
205
+ function AppShellItem({ icon, label, current, badge, shortcut, asChild, children, className, ...props }) {
206
+ const { collapsed, inDrawer } = useShell();
207
+ const element = React3.useRef(null);
208
+ const apple = useApple();
209
+ useShortcuts(shortcut && !inDrawer ? { [shortcut]: () => element.current?.click() } : {});
100
210
  const Comp = asChild ? Slot.Root : "a";
101
211
  const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
102
212
  icon,
103
- /* @__PURE__ */ jsx("span", { className: s.itemLabel(), children: label }),
104
- badge != null && /* @__PURE__ */ jsx("span", { className: s.itemBadge(), children: badge })
213
+ /* @__PURE__ */ jsx2("span", { className: s.itemLabel(), children: label }),
214
+ badge != null && /* @__PURE__ */ jsx2("span", { className: s.itemBadge(), children: badge }),
215
+ shortcut && !inDrawer ? /* @__PURE__ */ jsx2("span", { className: s.itemHint(), "aria-hidden": true, children: formatShortcut(shortcut, apple).map((key) => /* @__PURE__ */ jsx2(Kbd, { size: "sm", children: key }, key)) }) : null
105
216
  ] });
106
- return /* @__PURE__ */ jsx(Comp, { "aria-current": current ? "page" : void 0, title: collapsed ? label : void 0, className: s.item({ className }), ...props, children: asChild && children ? React.cloneElement(children, void 0, inner) : inner });
217
+ return /* @__PURE__ */ jsx2(Comp, { ref: element, "aria-current": current ? "page" : void 0, "aria-keyshortcuts": shortcut ? ariaKeyShortcuts(shortcut, apple) : void 0, title: collapsed ? label : void 0, className: s.item({ className }), ...props, children: asChild && children ? React3.cloneElement(children, void 0, inner) : inner });
107
218
  }
108
219
  function AppShellBody({ className, ...props }) {
109
- return /* @__PURE__ */ jsx("div", { className: s.body({ className }), ...props });
220
+ return /* @__PURE__ */ jsx2("div", { className: s.body({ className }), ...props });
110
221
  }
111
222
  function AppShellHeader({ title, menuLabel, actions, className, children, ...props }) {
112
223
  const { open, setOpen } = useShell();
113
224
  return /* @__PURE__ */ jsxs("header", { className: s.header({ className }), ...props, children: [
114
- /* @__PURE__ */ jsx("button", { type: "button", className: s.menuButton(), "aria-label": menuLabel, "aria-expanded": open, "aria-haspopup": "dialog", onClick: () => setOpen(true), children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M4 7h16M4 12h16M4 17h10" }) }) }),
115
- title && /* @__PURE__ */ jsx("h1", { className: s.title(), children: title }),
225
+ /* @__PURE__ */ jsx2("button", { type: "button", className: s.menuButton(), "aria-label": menuLabel, "aria-expanded": open, "aria-haspopup": "dialog", onClick: () => setOpen(true), children: /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "M4 7h16M4 12h16M4 17h10" }) }) }),
226
+ title && /* @__PURE__ */ jsx2("h1", { className: s.title(), children: title }),
116
227
  children,
117
- actions && /* @__PURE__ */ jsx("div", { className: s.actions(), children: actions })
228
+ actions && /* @__PURE__ */ jsx2("div", { className: s.actions(), children: actions })
229
+ ] });
230
+ }
231
+ function AppShellSearch({ label, onOpen, shortcut = "mod+k", className, ...props }) {
232
+ const apple = useApple();
233
+ useShortcuts({ [shortcut]: () => onOpen() });
234
+ return /* @__PURE__ */ jsxs("button", { type: "button", className: s.search({ className }), "aria-keyshortcuts": ariaKeyShortcuts(shortcut, apple), onClick: onOpen, ...props, children: [
235
+ /* @__PURE__ */ jsx2(SearchGlyph, {}),
236
+ /* @__PURE__ */ jsx2("span", { className: s.searchLabel(), children: label }),
237
+ /* @__PURE__ */ jsx2("span", { className: s.searchKeys(), "aria-hidden": true, children: formatShortcut(shortcut, apple).map((key) => /* @__PURE__ */ jsx2(Kbd, { size: "sm", children: key }, key)) })
118
238
  ] });
119
239
  }
120
240
  function AppShellMain({ className, ...props }) {
121
- return /* @__PURE__ */ jsx("main", { id: "content", tabIndex: -1, className: s.main({ className }), ...props });
241
+ return /* @__PURE__ */ jsx2("main", { id: "content", tabIndex: -1, className: s.main({ className }), ...props });
122
242
  }
123
243
  var AppShell = {
124
244
  Root: AppShellRoot,
@@ -127,6 +247,7 @@ var AppShell = {
127
247
  Item: AppShellItem,
128
248
  Body: AppShellBody,
129
249
  Header: AppShellHeader,
250
+ Search: AppShellSearch,
130
251
  Main: AppShellMain
131
252
  };
132
253
  export {
@@ -137,7 +258,14 @@ export {
137
258
  AppShellItem,
138
259
  AppShellMain,
139
260
  AppShellRoot,
261
+ AppShellSearch,
140
262
  AppShellSidebar,
141
- appShell
263
+ appShell,
264
+ ariaKeyShortcuts,
265
+ formatShortcut,
266
+ matchesCombo,
267
+ parseCombo,
268
+ useApple,
269
+ useShortcuts
142
270
  };
143
271
  //# sourceMappingURL=app-shell.js.map