@krizaka/ui 3.20.0 → 3.22.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 (263) hide show
  1. package/README.md +1 -0
  2. package/dist/action-bar.js +4 -4
  3. package/dist/app-shell.js +1 -1
  4. package/dist/argument-card.js +4 -4
  5. package/dist/blocks.css +1 -1
  6. package/dist/button.js +3 -2
  7. package/dist/card.js +3 -3
  8. package/dist/{chunk-5MH64NIL.js → chunk-2L67YTNV.js} +2 -2
  9. package/dist/{chunk-2NB27LM7.js → chunk-3IN6TOPM.js} +4 -4
  10. package/dist/{chunk-VDR2EP6K.js → chunk-4QN5YJ3D.js} +17 -5
  11. package/dist/chunk-4QN5YJ3D.js.map +1 -0
  12. package/dist/chunk-AQSZJJAJ.js +126 -0
  13. package/dist/chunk-AQSZJJAJ.js.map +1 -0
  14. package/dist/{chunk-3GODX3D6.js → chunk-QDXV544C.js} +2 -2
  15. package/dist/chunk-QDXV544C.js.map +1 -0
  16. package/dist/chunk-R6NK6ZNY.js +1080 -0
  17. package/dist/chunk-R6NK6ZNY.js.map +1 -0
  18. package/dist/chunk-TDG7ED2W.js +1 -0
  19. package/dist/chunk-TDG7ED2W.js.map +1 -0
  20. package/dist/{chunk-LIPK4UCS.js → chunk-YCIBJ6OQ.js} +140 -209
  21. package/dist/chunk-YCIBJ6OQ.js.map +1 -0
  22. package/dist/code-block.js +17 -5
  23. package/dist/code-block.js.map +1 -1
  24. package/dist/comparison-table.d.ts +44 -0
  25. package/dist/comparison-table.js +84 -0
  26. package/dist/comparison-table.js.map +1 -0
  27. package/dist/data-table.js +19 -1066
  28. package/dist/data-table.js.map +1 -1
  29. package/dist/date-picker.js +5 -5
  30. package/dist/date-picker.js.map +1 -1
  31. package/dist/force-update-screen.d.ts +80 -0
  32. package/dist/force-update-screen.js +38 -0
  33. package/dist/force-update-screen.js.map +1 -0
  34. package/dist/genui.d.ts +219 -0
  35. package/dist/genui.js +506 -0
  36. package/dist/genui.js.map +1 -0
  37. package/dist/index.d.ts +17 -24
  38. package/dist/index.js +1 -1
  39. package/dist/maintenance-screen.d.ts +68 -0
  40. package/dist/maintenance-screen.js +35 -0
  41. package/dist/maintenance-screen.js.map +1 -0
  42. package/dist/marketing-layout.js +1 -1
  43. package/dist/marketing-layout.js.map +1 -1
  44. package/dist/media-grid.js +44 -152
  45. package/dist/media-grid.js.map +1 -1
  46. package/dist/meta-row.d.ts +54 -0
  47. package/dist/meta-row.js +35 -0
  48. package/dist/meta-row.js.map +1 -0
  49. package/dist/native.d.ts +92 -16
  50. package/dist/native.js +873 -814
  51. package/dist/native.js.map +1 -1
  52. package/dist/nav-bar.d.ts +30 -7
  53. package/dist/nav-bar.js +71 -17
  54. package/dist/nav-bar.js.map +1 -1
  55. package/dist/page-hero.js +5 -5
  56. package/dist/radio-group.js +1 -1
  57. package/dist/settings-layout.js +1 -1
  58. package/dist/settings-layout.js.map +1 -1
  59. package/dist/site-footer.js +2 -2
  60. package/dist/skip-link.js +1 -1
  61. package/dist/step-showcase.d.ts +62 -0
  62. package/dist/step-showcase.js +227 -0
  63. package/dist/step-showcase.js.map +1 -0
  64. package/dist/tabs.js +2 -2
  65. package/dist/tabs.js.map +1 -1
  66. package/dist/update-prompt.d.ts +86 -0
  67. package/dist/update-prompt.js +41 -0
  68. package/dist/update-prompt.js.map +1 -0
  69. package/dist/video-player.d.ts +1 -1
  70. package/dist/video-player.js +16 -3
  71. package/dist/video-player.js.map +1 -1
  72. package/package.json +108 -9
  73. package/registry/achievement.json +1 -1
  74. package/registry/action-bar.json +1 -1
  75. package/registry/activity-feed.json +1 -1
  76. package/registry/alert.json +1 -1
  77. package/registry/app-shell.json +1 -1
  78. package/registry/argument-card.json +1 -1
  79. package/registry/audio-player.json +1 -1
  80. package/registry/auth-layout.json +1 -1
  81. package/registry/avatar.json +1 -1
  82. package/registry/badge.json +1 -1
  83. package/registry/button.json +1 -1
  84. package/registry/card-grid.json +1 -1
  85. package/registry/card.json +1 -1
  86. package/registry/catalogue-page.json +1 -1
  87. package/registry/changelog-page.json +1 -1
  88. package/registry/chat.json +1 -1
  89. package/registry/checkbox.json +1 -1
  90. package/registry/chip-link.json +1 -1
  91. package/registry/chip.json +1 -1
  92. package/registry/choice-grid.json +1 -1
  93. package/registry/code-block.json +1 -1
  94. package/registry/combobox.json +1 -1
  95. package/registry/command.json +1 -1
  96. package/registry/comparison-table.json +207 -0
  97. package/registry/component-page.json +1 -1
  98. package/registry/confirm-button.json +1 -1
  99. package/registry/contact-page.json +1 -1
  100. package/registry/continue-prompt.json +1 -1
  101. package/registry/countdown.json +1 -1
  102. package/registry/cta-panel.json +1 -1
  103. package/registry/dashboard-layout.json +1 -1
  104. package/registry/data-table.json +1 -1
  105. package/registry/date-picker.json +3 -3
  106. package/registry/decision-list.json +1 -1
  107. package/registry/description-list.json +1 -1
  108. package/registry/detail-layout.json +1 -1
  109. package/registry/dialog.json +1 -1
  110. package/registry/docs-article-page.json +1 -1
  111. package/registry/docs-hub-page.json +1 -1
  112. package/registry/docs-layout.json +1 -1
  113. package/registry/document-viewer.json +1 -1
  114. package/registry/dropdown-menu.json +1 -1
  115. package/registry/empty-state.json +1 -1
  116. package/registry/error-page.json +1 -1
  117. package/registry/examples/comparison-table/versus.d.ts +4 -0
  118. package/registry/examples/comparison-table/versus.js +28 -0
  119. package/registry/examples/comparison-table/versus.tsx +20 -0
  120. package/registry/examples/dialog/native/sheet.dark.png +0 -0
  121. package/registry/examples/dialog/native/sheet.light.png +0 -0
  122. package/registry/examples/force-update-screen/default.d.ts +4 -0
  123. package/registry/examples/force-update-screen/default.js +25 -0
  124. package/registry/examples/force-update-screen/default.tsx +19 -0
  125. package/registry/examples/force-update-screen/native/default.d.ts +4 -0
  126. package/registry/examples/force-update-screen/native/default.dark.png +0 -0
  127. package/registry/examples/force-update-screen/native/default.js +24 -0
  128. package/registry/examples/force-update-screen/native/default.light.png +0 -0
  129. package/registry/examples/force-update-screen/native/default.tsx +18 -0
  130. package/registry/examples/genui/streaming.d.ts +4 -0
  131. package/registry/examples/genui/streaming.js +18 -0
  132. package/registry/examples/genui/streaming.tsx +14 -0
  133. package/registry/examples/genui/table.d.ts +4 -0
  134. package/registry/examples/genui/table.js +19 -0
  135. package/registry/examples/genui/table.tsx +14 -0
  136. package/registry/examples/maintenance-screen/default.d.ts +4 -0
  137. package/registry/examples/maintenance-screen/default.js +22 -0
  138. package/registry/examples/maintenance-screen/default.tsx +16 -0
  139. package/registry/examples/maintenance-screen/native/default.d.ts +4 -0
  140. package/registry/examples/maintenance-screen/native/default.dark.png +0 -0
  141. package/registry/examples/maintenance-screen/native/default.js +21 -0
  142. package/registry/examples/maintenance-screen/native/default.light.png +0 -0
  143. package/registry/examples/maintenance-screen/native/default.tsx +15 -0
  144. package/registry/examples/meta-row/native/video.d.ts +4 -0
  145. package/registry/examples/meta-row/native/video.dark.png +0 -0
  146. package/registry/examples/meta-row/native/video.js +11 -0
  147. package/registry/examples/meta-row/native/video.light.png +0 -0
  148. package/registry/examples/meta-row/native/video.tsx +5 -0
  149. package/registry/examples/meta-row/status.d.ts +4 -0
  150. package/registry/examples/meta-row/status.js +12 -0
  151. package/registry/examples/meta-row/status.tsx +7 -0
  152. package/registry/examples/meta-row/video.d.ts +4 -0
  153. package/registry/examples/meta-row/video.js +12 -0
  154. package/registry/examples/meta-row/video.tsx +7 -0
  155. package/registry/examples/nav-bar/family-product.js +16 -13
  156. package/registry/examples/nav-bar/family-product.tsx +19 -19
  157. package/registry/examples/step-showcase/desktop.d.ts +4 -0
  158. package/registry/examples/step-showcase/desktop.js +24 -0
  159. package/registry/examples/step-showcase/desktop.tsx +19 -0
  160. package/registry/examples/step-showcase/phone.d.ts +4 -0
  161. package/registry/examples/step-showcase/phone.js +24 -0
  162. package/registry/examples/step-showcase/phone.tsx +19 -0
  163. package/registry/examples/update-prompt/default.d.ts +4 -0
  164. package/registry/examples/update-prompt/default.js +24 -0
  165. package/registry/examples/update-prompt/default.tsx +18 -0
  166. package/registry/examples/update-prompt/native/default.d.ts +4 -0
  167. package/registry/examples/update-prompt/native/default.dark.png +0 -0
  168. package/registry/examples/update-prompt/native/default.js +23 -0
  169. package/registry/examples/update-prompt/native/default.light.png +0 -0
  170. package/registry/examples/update-prompt/native/default.tsx +17 -0
  171. package/registry/field.json +1 -1
  172. package/registry/file-upload.json +1 -1
  173. package/registry/filter-bar.json +1 -1
  174. package/registry/force-update-screen.json +323 -0
  175. package/registry/form.json +1 -1
  176. package/registry/gallery-page.json +1 -1
  177. package/registry/genui.json +409 -0
  178. package/registry/glow-card.json +1 -1
  179. package/registry/goal-gauge.json +1 -1
  180. package/registry/how-it-works-page.json +1 -1
  181. package/registry/image-cropper.json +1 -1
  182. package/registry/index.json +223 -4
  183. package/registry/iso.json +1 -1
  184. package/registry/kanban.json +1 -1
  185. package/registry/kbd.json +1 -1
  186. package/registry/landing-page.json +1 -1
  187. package/registry/landscape.json +1 -1
  188. package/registry/legal-page.json +1 -1
  189. package/registry/link-card.json +1 -1
  190. package/registry/list.json +1 -1
  191. package/registry/loader.json +1 -1
  192. package/registry/longform-page.json +1 -1
  193. package/registry/maintenance-screen.json +259 -0
  194. package/registry/map.json +2 -2
  195. package/registry/markdown.json +1 -1
  196. package/registry/marketing-layout.json +1 -1
  197. package/registry/marquee.json +1 -1
  198. package/registry/mascot-quote.json +1 -1
  199. package/registry/mascot-state.json +1 -1
  200. package/registry/mascots.json +1 -1
  201. package/registry/match-dialog.json +1 -1
  202. package/registry/media-capture.json +1 -1
  203. package/registry/media-grid.json +2 -2
  204. package/registry/meta-row.json +215 -0
  205. package/registry/meter.json +1 -1
  206. package/registry/nav-bar.json +58 -25
  207. package/registry/number-ticker.json +1 -1
  208. package/registry/page-header.json +1 -1
  209. package/registry/page-hero.json +1 -1
  210. package/registry/page.json +1 -1
  211. package/registry/pagination.json +1 -1
  212. package/registry/photo-grid.json +1 -1
  213. package/registry/pointer-light.json +1 -1
  214. package/registry/popover.json +1 -1
  215. package/registry/pricing-page.json +1 -1
  216. package/registry/product-page.json +1 -1
  217. package/registry/profile-card.json +1 -1
  218. package/registry/progress.json +1 -1
  219. package/registry/promise-card.json +1 -1
  220. package/registry/radio-group.json +2 -2
  221. package/registry/recipes.json +1 -1
  222. package/registry/scroll-stage.json +1 -1
  223. package/registry/search-field.json +1 -1
  224. package/registry/section-backdrop.json +1 -1
  225. package/registry/separator.json +1 -1
  226. package/registry/settings-layout.json +2 -2
  227. package/registry/showcase.json +1 -1
  228. package/registry/site-footer.json +13 -13
  229. package/registry/sites.json +1 -1
  230. package/registry/skeleton.json +1 -1
  231. package/registry/skip-link.json +2 -2
  232. package/registry/slider.json +1 -1
  233. package/registry/slot.json +1 -1
  234. package/registry/spinner.json +1 -1
  235. package/registry/split-view.json +1 -1
  236. package/registry/stage-banner.json +1 -1
  237. package/registry/stat.json +1 -1
  238. package/registry/status-page.json +1 -1
  239. package/registry/step-progress.json +1 -1
  240. package/registry/step-showcase.json +296 -0
  241. package/registry/stepper.json +1 -1
  242. package/registry/swipe-deck.json +1 -1
  243. package/registry/switch.json +1 -1
  244. package/registry/tabs.json +3 -3
  245. package/registry/tag-input.json +1 -1
  246. package/registry/tag-selector.json +1 -1
  247. package/registry/terminal-replay.json +1 -1
  248. package/registry/theme.json +1 -1
  249. package/registry/toast.json +1 -1
  250. package/registry/toc.json +1 -1
  251. package/registry/tooltip.json +1 -1
  252. package/registry/tree-view.json +1 -1
  253. package/registry/update-prompt.json +316 -0
  254. package/registry/verified-badge.json +1 -1
  255. package/registry/vertical-video.json +1 -1
  256. package/registry/video-player.json +4 -4
  257. package/registry/video-trimmer.json +1 -1
  258. package/registry/virtual.json +1 -1
  259. package/dist/chunk-3GODX3D6.js.map +0 -1
  260. package/dist/chunk-LIPK4UCS.js.map +0 -1
  261. package/dist/chunk-VDR2EP6K.js.map +0 -1
  262. /package/dist/{chunk-5MH64NIL.js.map → chunk-2L67YTNV.js.map} +0 -0
  263. /package/dist/{chunk-2NB27LM7.js.map → chunk-3IN6TOPM.js.map} +0 -0
@@ -180,7 +180,7 @@
180
180
  "files": [
181
181
  {
182
182
  "path": "meta.ts",
183
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
183
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
184
184
  },
185
185
  {
186
186
  "path": "status-page/index.ts",
@@ -193,7 +193,7 @@
193
193
  "files": [
194
194
  {
195
195
  "path": "meta.ts",
196
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
196
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
197
197
  },
198
198
  {
199
199
  "path": "step-progress/index.ts",
@@ -0,0 +1,296 @@
1
+ {
2
+ "name": "step-showcase",
3
+ "type": "primitive",
4
+ "title": "Step showcase",
5
+ "summary": "A product's guided tour in one device frame — phone, browser or macOS window: the steps beside it, each real recording played muted then the next, crossfading; it plays only in view, holds on hover and focus, and keeps a pause button.",
6
+ "description": "A product's guided tour in one device frame — phone, browser or macOS window: the steps beside it, each real recording played muted then the next, crossfading; it plays only in view, holds on hover and focus, and keeps a pause button.",
7
+ "status": "beta",
8
+ "deprecated": null,
9
+ "category": "blocks",
10
+ "platforms": "web",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "n/a",
17
+ "reason": "A product site's tour of the app; inside the phone app the real screens are the tour."
18
+ },
19
+ "desktop": {
20
+ "state": "yes"
21
+ }
22
+ },
23
+ "whenToUse": [
24
+ "A landing page's “see it work”: three to five steps of the real app recorded on the device it runs on.",
25
+ "The same tour on several devices: one `StepShowcase` per tab (Web · Mobile · Mac)."
26
+ ],
27
+ "whenNotToUse": [
28
+ {
29
+ "when": "For a gallery of results that should move on its own.",
30
+ "use": "showcase"
31
+ },
32
+ {
33
+ "when": "For a single video with its controls.",
34
+ "use": "video-player"
35
+ },
36
+ {
37
+ "when": "For the steps of a form the person is filling.",
38
+ "use": "step-progress"
39
+ }
40
+ ],
41
+ "bestPractices": [
42
+ "Record the real app (a demo persona, never a mock-up); 4–8 s per step, muted, no captions burnt in.",
43
+ "Give every step a poster (the first frame) and an `alt`: it is what loads first and all that shows under reduced motion.",
44
+ "A title of two or three words and one line per step; the order is the journey (sign in → make → ask → follow).",
45
+ "WebM first, MP4 as the fallback; a step without a video stays `duration` ms."
46
+ ],
47
+ "accessibility": {
48
+ "keyboard": [
49
+ {
50
+ "keys": "Tab",
51
+ "action": "The current step, then play / pause."
52
+ },
53
+ {
54
+ "keys": "↑ ↓ ← →",
55
+ "action": "The previous or next step."
56
+ },
57
+ {
58
+ "keys": "Enter / Space",
59
+ "action": "On a step: show it. On play / pause: stops or restarts the tour."
60
+ }
61
+ ],
62
+ "notes": [
63
+ "A region (`aria-roledescription=\"carousel\"`) named by `label`; the steps are a vertical `tablist`, the frame its `tabpanel` named by the current step.",
64
+ "WCAG 2.2.2: a visible pause button; the tour also holds while the pointer is over it, while a focus is inside and while it is off screen.",
65
+ "The video is decorative (`aria-hidden`): the poster's `alt` and the step's words carry the meaning. No native controls, never sound.",
66
+ "Under `prefers-reduced-motion`: posters only, no autoplay, the steps change on demand."
67
+ ]
68
+ },
69
+ "related": [
70
+ "showcase",
71
+ "video-player",
72
+ "tabs",
73
+ "landing-page"
74
+ ],
75
+ "web": {
76
+ "entry": "@krizaka/ui/step-showcase",
77
+ "import": "import { StepShowcase, DeviceFrame, stepShowcaseLabels } from \"@krizaka/ui/step-showcase\";",
78
+ "props": [
79
+ {
80
+ "component": "DeviceFrame",
81
+ "description": "A phone, a browser window or a macOS window around a screen of content.",
82
+ "props": [
83
+ {
84
+ "name": "kind",
85
+ "type": "DeviceKind",
86
+ "default": null,
87
+ "description": "The device: a phone, a browser window or a macOS window.",
88
+ "required": true
89
+ },
90
+ {
91
+ "name": "title",
92
+ "type": "string",
93
+ "default": null,
94
+ "description": "The browser's address or the window's title (“dev.orazaka.com”, “Orazaka”).",
95
+ "required": false
96
+ }
97
+ ]
98
+ },
99
+ {
100
+ "component": "StepShowcase",
101
+ "description": "One device frame, its steps beside it, playing each recording then the next — a product's guided tour.",
102
+ "props": [
103
+ {
104
+ "name": "device",
105
+ "type": "DeviceKind",
106
+ "default": null,
107
+ "description": "The device the recordings were made on.",
108
+ "required": true
109
+ },
110
+ {
111
+ "name": "label",
112
+ "type": "string",
113
+ "default": null,
114
+ "description": "The sequence's accessible name (“Orazaka in your pocket”), passed translated.",
115
+ "required": true
116
+ },
117
+ {
118
+ "name": "steps",
119
+ "type": "readonly ShowcaseStep[]",
120
+ "default": null,
121
+ "description": "The steps, in the order of the journey.",
122
+ "required": true
123
+ },
124
+ {
125
+ "name": "autoplay",
126
+ "type": "boolean",
127
+ "default": "true",
128
+ "description": "Moves on by itself (never under reduced motion).",
129
+ "required": false
130
+ },
131
+ {
132
+ "name": "defaultIndex",
133
+ "type": "number",
134
+ "default": "0",
135
+ "description": "The step shown first.",
136
+ "required": false
137
+ },
138
+ {
139
+ "name": "deviceTitle",
140
+ "type": "string",
141
+ "default": null,
142
+ "description": "The browser's address or the window's title.",
143
+ "required": false
144
+ },
145
+ {
146
+ "name": "labels",
147
+ "type": "StepShowcaseLabels",
148
+ "default": "{\n play: \"Play the steps\",\n pause: \"Pause the steps\",\n step: (n, total) => `Step ${n} of ${total}`,\n}",
149
+ "description": "The controls' words, translated.",
150
+ "required": false
151
+ },
152
+ {
153
+ "name": "onIndexChange",
154
+ "type": "((index: number, user: boolean) => void)",
155
+ "default": null,
156
+ "description": "Called when the step changes — `user` is true when the visitor asked for it.",
157
+ "required": false
158
+ }
159
+ ]
160
+ }
161
+ ],
162
+ "examples": [
163
+ {
164
+ "name": "phone",
165
+ "title": "In your pocket",
166
+ "description": "Four steps of a phone app beside one phone; resize to a phone: dots, swipe.",
167
+ "path": "examples/step-showcase/phone.tsx",
168
+ "module": "@krizaka/ui/registry/examples/step-showcase/phone",
169
+ "code": "import { StepShowcase } from \"@krizaka/ui/step-showcase\";\n\nconst still = (n: number) => `data:image/svg+xml,${encodeURIComponent(`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 90 195\"><rect width=\"90\" height=\"195\" fill=\"#1b1f2a\"/><text x=\"45\" y=\"100\" fill=\"#8a93a6\" font-size=\"28\" text-anchor=\"middle\">${n}</text></svg>`)}`;\n\n// The real app, step by step: one phone, the steps beside it, each recording then the next (here, stills of 4 s).\nexport default function StepShowcasePhone() {\n return (\n <StepShowcase\n label=\"Orazaka in your pocket\"\n device=\"phone\"\n steps={[\n { id: \"sign-in\", title: \"Sign in\", description: \"Your workspace, on your phone.\", poster: still(1), alt: \"The sign-in screen\", duration: 4000 },\n { id: \"image\", title: \"Make an image\", description: \"A Studio, a prompt, the result.\", poster: still(2), alt: \"A Studio run\", duration: 4000 },\n { id: \"ask\", title: \"Ask the model\", description: \"Answers from your own models.\", poster: still(3), alt: \"The chat\", duration: 4000 },\n { id: \"jobs\", title: \"Follow your jobs\", description: \"Every run, its state and its result.\", poster: still(4), alt: \"The jobs list\", duration: 4000 },\n ]}\n />\n );\n}\n"
170
+ },
171
+ {
172
+ "name": "desktop",
173
+ "title": "On your Mac",
174
+ "description": "A macOS window: the control panel, the local stack starting, the workspace.",
175
+ "path": "examples/step-showcase/desktop.tsx",
176
+ "module": "@krizaka/ui/registry/examples/step-showcase/desktop",
177
+ "code": "import { StepShowcase } from \"@krizaka/ui/step-showcase\";\n\nconst still = (n: number) => `data:image/svg+xml,${encodeURIComponent(`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 160 100\"><rect width=\"160\" height=\"100\" fill=\"#1b1f2a\"/><text x=\"80\" y=\"58\" fill=\"#8a93a6\" font-size=\"22\" text-anchor=\"middle\">${n}</text></svg>`)}`;\n\n// A macOS window: the control panel, the local stack starting, the workspace, a run.\nexport default function StepShowcaseDesktop() {\n return (\n <StepShowcase\n label=\"Orazaka on your Mac\"\n device=\"desktop\"\n deviceTitle=\"Orazaka\"\n steps={[\n { id: \"panel\", title: \"The control panel\", description: \"Connected to your workspace, or local.\", poster: still(1), alt: \"The control panel\", duration: 4000 },\n { id: \"start\", title: \"Start the local stack\", description: \"Each service turns green.\", poster: still(2), alt: \"Services starting\", duration: 4000 },\n { id: \"open\", title: \"Open the workspace\", description: \"The same app, on your Mac.\", poster: still(3), alt: \"The workspace\", duration: 4000 },\n ]}\n />\n );\n}\n"
178
+ }
179
+ ]
180
+ },
181
+ "native": null,
182
+ "files": [
183
+ {
184
+ "path": "meta.ts",
185
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
186
+ },
187
+ {
188
+ "path": "step-showcase/device-frame.tsx",
189
+ "content": "// Server-safe. The frame a product's recording plays in: a phone, a browser window or a macOS window — drawn with the\n// tokens (no image), so it follows dark and light. The content fills the screen area (`object-cover` media).\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\n\nexport type DeviceKind = \"phone\" | \"browser\" | \"desktop\";\n\nexport interface DeviceFrameProps extends React.ComponentProps<\"div\"> {\n /** The device: a phone, a browser window or a macOS window. */\n kind: DeviceKind;\n /** The browser's address or the window's title (“dev.orazaka.com”, “Orazaka”). */\n title?: string;\n}\n\nconst screen: Record<DeviceKind, string> = {\n phone: \"aspect-[9/19.5]\",\n browser: \"aspect-[16/10]\",\n desktop: \"aspect-[16/10]\",\n};\n\n/** A phone, a browser window or a macOS window around a screen of content. */\nexport function DeviceFrame({ kind, title, className, children, ...props }: DeviceFrameProps) {\n if (kind === \"phone\") {\n return (\n <div data-device=\"phone\" className={cn(\"relative mx-auto w-full max-w-[18rem] rounded-[2.4rem] border border-border-strong bg-surface-3 p-2 shadow-2xl\", className)} {...props}>\n <div className={cn(\"relative overflow-hidden rounded-[1.9rem] bg-surface-0\", screen.phone)}>\n {children}\n <span aria-hidden className=\"absolute left-1/2 top-2 h-5 w-20 -translate-x-1/2 rounded-full bg-surface-3\" />\n </div>\n </div>\n );\n }\n return (\n <div data-device={kind} className={cn(\"relative w-full overflow-hidden rounded-xl border border-border-default bg-surface-1 shadow-2xl\", className)} {...props}>\n <div className=\"flex h-8 items-center gap-1.5 border-b border-border-subtle bg-surface-2 px-3\">\n <span aria-hidden className=\"h-2.5 w-2.5 rounded-full bg-danger/80\" />\n <span aria-hidden className=\"h-2.5 w-2.5 rounded-full bg-warning/80\" />\n <span aria-hidden className=\"h-2.5 w-2.5 rounded-full bg-success/80\" />\n {title &&\n (kind === \"browser\" ? (\n <span className=\"mx-auto max-w-[60%] truncate rounded-md bg-surface-1 px-3 py-0.5 font-mono text-[11px] text-fg-secondary\">{title}</span>\n ) : (\n <span className=\"mx-auto truncate text-xs font-semibold text-fg-secondary\">{title}</span>\n ))}\n </div>\n <div className={cn(\"relative overflow-hidden bg-surface-0\", screen[kind])}>{children}</div>\n </div>\n );\n}\n"
190
+ },
191
+ {
192
+ "path": "step-showcase/index.ts",
193
+ "content": "/** @krizaka/ui/step-showcase — `StepShowcase`, a guided sequence of recordings in one device frame; `DeviceFrame` (phone, browser, macOS window). Client. */\nexport { DeviceFrame, type DeviceFrameProps, type DeviceKind } from \"./device-frame\";\nexport { type ShowcaseStep, StepShowcase, type StepShowcaseLabels, stepShowcaseLabels, type StepShowcaseProps } from \"./step-showcase\";\n"
194
+ },
195
+ {
196
+ "path": "step-showcase/meta.ts",
197
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Step showcase\",\n summary:\n \"A product's guided tour in one device frame — phone, browser or macOS window: the steps beside it, each real recording played muted then the next, crossfading; it plays only in view, holds on hover and focus, and keeps a pause button.\",\n status: \"beta\",\n category: \"blocks\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"A product site's tour of the app; inside the phone app the real screens are the tour.\" }, desktop: \"web\" },\n whenToUse: [\n \"A landing page's “see it work”: three to five steps of the real app recorded on the device it runs on.\",\n \"The same tour on several devices: one `StepShowcase` per tab (Web · Mobile · Mac).\",\n ],\n whenNotToUse: [\n { when: \"For a gallery of results that should move on its own.\", use: \"showcase\" },\n { when: \"For a single video with its controls.\", use: \"video-player\" },\n { when: \"For the steps of a form the person is filling.\", use: \"step-progress\" },\n ],\n bestPractices: [\n \"Record the real app (a demo persona, never a mock-up); 4–8 s per step, muted, no captions burnt in.\",\n \"Give every step a poster (the first frame) and an `alt`: it is what loads first and all that shows under reduced motion.\",\n \"A title of two or three words and one line per step; the order is the journey (sign in → make → ask → follow).\",\n \"WebM first, MP4 as the fallback; a step without a video stays `duration` ms.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Tab\", action: \"The current step, then play / pause.\" },\n { keys: \"↑ ↓ ← →\", action: \"The previous or next step.\" },\n { keys: \"Enter / Space\", action: \"On a step: show it. On play / pause: stops or restarts the tour.\" },\n ],\n notes: [\n \"A region (`aria-roledescription=\\\"carousel\\\"`) named by `label`; the steps are a vertical `tablist`, the frame its `tabpanel` named by the current step.\",\n \"WCAG 2.2.2: a visible pause button; the tour also holds while the pointer is over it, while a focus is inside and while it is off screen.\",\n \"The video is decorative (`aria-hidden`): the poster's `alt` and the step's words carry the meaning. No native controls, never sound.\",\n \"Under `prefers-reduced-motion`: posters only, no autoplay, the steps change on demand.\",\n ],\n },\n related: [\"showcase\", \"video-player\", \"tabs\", \"landing-page\"],\n web: {\n imports: [\"StepShowcase\", \"DeviceFrame\", \"stepShowcaseLabels\"],\n examples: [\n { name: \"phone\", title: \"In your pocket\", description: \"Four steps of a phone app beside one phone; resize to a phone: dots, swipe.\" },\n { name: \"desktop\", title: \"On your Mac\", description: \"A macOS window: the control panel, the local stack starting, the workspace.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
198
+ },
199
+ {
200
+ "path": "step-showcase/step-showcase.tsx",
201
+ "content": "// Client (timers through the video, observers, swipe). A guided sequence in one device frame: the steps listed beside\n// it (a vertical tab list: number · title · one line, a bar filling under the current one), the frame playing the\n// step's recording muted, then moving on to the next; the recordings crossfade. It plays only while in view, holds on\n// hover and focus, keeps a visible play / pause button, and never shows a video's native controls. Recordings load\n// lazily (posters first, the current step's video once the block has been seen). Reduced motion: posters, manual steps.\n// Below `md` the list becomes dots under the frame, the frame swipes, the current step's words show under it.\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { DeviceFrame, type DeviceKind } from \"./device-frame\";\n\nconst FILL_CSS = \".kz-step-fill{transform-origin:left;animation:kz-step-fill var(--kz-step-ms,6000ms) linear forwards}@keyframes kz-step-fill{from{transform:scaleX(0)}to{transform:scaleX(1)}}\";\nconst REDUCED = \"(prefers-reduced-motion: reduce)\";\nconst noop = () => () => undefined;\nconst readReduced = () => Boolean(typeof window !== \"undefined\" && window.matchMedia?.(REDUCED).matches);\nconst subscribeReduced = (notify: () => void) => {\n const q = window.matchMedia?.(REDUCED);\n q?.addEventListener?.(\"change\", notify);\n return () => q?.removeEventListener?.(\"change\", notify);\n};\n\nexport interface ShowcaseStep {\n /** A stable key. */\n id: string;\n /** The step's name (“Make an image”), passed translated. */\n title: string;\n /** One line under it. */\n description: string;\n /** The still shown first, and the only picture under reduced motion. */\n poster: string;\n /** What the poster shows, for assistive technology. */\n alt: string;\n /** The recording: WebM and / or MP4. Without one, the poster stays `duration` ms. */\n video?: { webm?: string; mp4?: string };\n /** Milliseconds a step without a video stays. @default 6000 */\n duration?: number;\n}\n\nexport interface StepShowcaseLabels {\n /** The button's name while paused. */\n play: string;\n /** The button's name while it plays. */\n pause: string;\n /** A step's position in words: `(2, 4) => \"Step 2 of 4\"`. */\n step: (n: number, total: number) => string;\n}\n\nexport const stepShowcaseLabels: StepShowcaseLabels = {\n play: \"Play the steps\",\n pause: \"Pause the steps\",\n step: (n, total) => `Step ${n} of ${total}`,\n};\n\nexport interface StepShowcaseProps extends Omit<React.ComponentProps<\"section\">, \"children\"> {\n /** The sequence's accessible name (“Orazaka in your pocket”), passed translated. */\n label: string;\n /** The steps, in the order of the journey. */\n steps: readonly ShowcaseStep[];\n /** The device the recordings were made on. */\n device: DeviceKind;\n /** The browser's address or the window's title. */\n deviceTitle?: string;\n /** Moves on by itself (never under reduced motion). @default true */\n autoplay?: boolean;\n /** The controls' words, translated. */\n labels?: StepShowcaseLabels;\n /** The step shown first. @default 0 */\n defaultIndex?: number;\n /** Called when the step changes — `user` is true when the visitor asked for it. */\n onIndexChange?: (index: number, user: boolean) => void;\n}\n\n/** One device frame, its steps beside it, playing each recording then the next — a product's guided tour. */\nexport function StepShowcase({ label, steps, device, deviceTitle, autoplay = true, labels = stepShowcaseLabels, defaultIndex = 0, onIndexChange, className, ...props }: StepShowcaseProps) {\n const total = steps.length;\n const id = React.useId();\n const root = React.useRef<HTMLElement>(null);\n const video = React.useRef<HTMLVideoElement>(null);\n const bar = React.useRef<HTMLSpanElement>(null);\n const swipe = React.useRef<number | null>(null);\n const [index, setIndex] = React.useState(defaultIndex);\n const [paused, setPaused] = React.useState(false);\n const [hold, setHold] = React.useState({ hover: false, focus: false });\n const [inView, setInView] = React.useState(false);\n const [seen, setSeen] = React.useState(false);\n const reduced = React.useSyncExternalStore(subscribeReduced, readReduced, () => false);\n const mounted = React.useSyncExternalStore(noop, () => true, () => false);\n const current = steps[Math.min(index, total - 1)];\n const auto = autoplay && !reduced && mounted;\n const running = auto && !paused && !hold.hover && !hold.focus && inView;\n\n React.useEffect(() => {\n const host = root.current;\n if (!host || typeof IntersectionObserver === \"undefined\") return;\n const io = new IntersectionObserver((e) => {\n const visible = e.some((x) => x.isIntersecting);\n setInView(visible);\n if (visible) setSeen(true);\n }, { threshold: 0.35 });\n io.observe(host);\n return () => io.disconnect();\n }, []);\n\n const go = React.useCallback(\n (to: number, user: boolean) => {\n const next = (to + total) % total;\n setIndex(next);\n if (bar.current) bar.current.style.transform = \"scaleX(0)\";\n onIndexChange?.(next, user);\n },\n [total, onIndexChange],\n );\n\n // The recording runs only while the sequence does; its time is the bar.\n React.useEffect(() => {\n const v = video.current;\n if (!v) return;\n if (running) v.play()?.catch?.(() => undefined);\n else v.pause();\n }, [running, index, seen]);\n\n const playable = Boolean(current?.video) && seen && !reduced;\n const onTime = (e: React.SyntheticEvent<HTMLVideoElement>) => {\n const v = e.currentTarget;\n if (bar.current && v.duration) bar.current.style.transform = `scaleX(${v.currentTime / v.duration})`;\n };\n\n return (\n <section\n ref={root}\n aria-roledescription=\"carousel\"\n aria-label={label}\n className={cn(\"grid items-center gap-6 md:grid-cols-[minmax(0,20rem)_minmax(0,1fr)] md:gap-10\", className)}\n onPointerEnter={() => setHold((h) => ({ ...h, hover: true }))}\n onPointerLeave={() => setHold((h) => ({ ...h, hover: false }))}\n onFocus={() => setHold((h) => ({ ...h, focus: true }))}\n onBlur={(e) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setHold((h) => ({ ...h, focus: false }));\n }}\n {...props}\n >\n <style href=\"kz-step-showcase\" precedence=\"medium\">{FILL_CSS}</style>\n <div className=\"order-2 grid gap-3 md:order-1\">\n <div role=\"tablist\" aria-orientation=\"vertical\" aria-label={label} className=\"flex justify-center gap-2 md:grid md:gap-1\">\n {steps.map((s, i) => {\n const active = i === index;\n return (\n <button\n key={s.id}\n type=\"button\"\n role=\"tab\"\n id={`${id}-tab-${i}`}\n aria-selected={active}\n aria-controls={`${id}-panel`}\n tabIndex={active ? 0 : -1}\n onClick={() => go(i, true)}\n onKeyDown={(e) => {\n if (e.key === \"ArrowDown\" || e.key === \"ArrowRight\") go(i + 1, true);\n else if (e.key === \"ArrowUp\" || e.key === \"ArrowLeft\") go(i - 1, true);\n else return;\n e.preventDefault();\n }}\n className={cn(\n \"group relative rounded-xl text-left outline-hidden transition-colors focus-visible:ring-2 focus-visible:ring-ring\",\n \"max-md:h-2.5 max-md:w-2.5 max-md:rounded-full max-md:bg-border-strong max-md:aria-selected:w-6 max-md:aria-selected:bg-accent\",\n \"md:overflow-hidden md:p-4 md:hover:bg-surface-2 md:aria-selected:bg-surface-2\",\n )}\n >\n <span className=\"max-md:sr-only\">\n <span className=\"flex items-baseline gap-3\">\n <span className={cn(\"font-mono text-xs\", active ? \"text-fg-accent\" : \"text-fg-secondary\")}>{String(i + 1).padStart(2, \"0\")}</span>\n <span className={cn(\"text-sm font-semibold\", active ? \"text-fg\" : \"text-fg-secondary\")}>{s.title}</span>\n </span>\n <span className=\"mt-1 block pl-8 text-xs leading-relaxed text-fg-secondary\">{s.description}</span>\n </span>\n {active && auto && (\n <span aria-hidden className=\"absolute inset-x-4 bottom-0 hidden h-0.5 overflow-hidden rounded-full bg-border-subtle md:block\">\n <span\n key={`${s.id}-${playable}`}\n ref={bar}\n data-fill\n className={cn(\"block h-full bg-accent\", !playable && \"kz-step-fill\")}\n style={{ transform: playable ? \"scaleX(0)\" : undefined, animationPlayState: running ? \"running\" : \"paused\", [\"--kz-step-ms\" as string]: `${s.duration ?? 6000}ms` }}\n onAnimationEnd={() => go(index + 1, false)}\n />\n </span>\n )}\n </button>\n );\n })}\n </div>\n <p aria-hidden className=\"text-center text-sm md:hidden\">\n <span className=\"font-semibold text-fg\">{current?.title}</span> <span className=\"text-fg-secondary\">{current?.description}</span>\n </p>\n {auto && (\n <button type=\"button\" onClick={() => setPaused((p) => !p)} aria-pressed={paused} className=\"mx-auto inline-flex h-8 items-center gap-2 rounded-full border border-border-subtle px-3 text-xs font-medium text-fg-secondary hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring md:mx-0 md:w-fit\">\n <svg viewBox=\"0 0 24 24\" className=\"h-3.5 w-3.5\" fill=\"currentColor\" aria-hidden>\n {paused ? <path d=\"M8 5v14l11-7z\" /> : <path d=\"M7 5h4v14H7zM13 5h4v14h-4z\" />}\n </svg>\n {paused ? labels.play : labels.pause}\n </button>\n )}\n </div>\n\n <div\n id={`${id}-panel`}\n role=\"tabpanel\"\n aria-roledescription=\"slide\"\n aria-labelledby={`${id}-tab-${index}`}\n className=\"order-1 touch-pan-y md:order-2\"\n onPointerDown={(e) => {\n swipe.current = e.clientX;\n }}\n onPointerUp={(e) => {\n if (swipe.current === null) return;\n const dx = e.clientX - swipe.current;\n swipe.current = null;\n if (Math.abs(dx) > 40) go(index + (dx < 0 ? 1 : -1), true);\n }}\n >\n <DeviceFrame kind={device} title={deviceTitle}>\n {steps.map((s, i) => {\n const active = i === index;\n return (\n <div key={s.id} data-step={s.id} aria-hidden={!active} className={cn(\"absolute inset-0 transition-opacity duration-500 motion-reduce:transition-none\", active ? \"opacity-100\" : \"opacity-0\")}>\n <img src={s.poster} alt={active ? s.alt : \"\"} loading={i === 0 ? \"eager\" : \"lazy\"} decoding=\"async\" className=\"h-full w-full object-cover\" />\n {active && playable && s.video && (\n <video\n ref={video}\n key={s.id}\n muted\n playsInline\n preload=\"metadata\"\n poster={s.poster}\n aria-hidden\n tabIndex={-1}\n onTimeUpdate={onTime}\n onEnded={() => go(index + 1, false)}\n className=\"absolute inset-0 h-full w-full object-cover\"\n >\n {s.video.webm && <source src={s.video.webm} type=\"video/webm\" />}\n {s.video.mp4 && <source src={s.video.mp4} type=\"video/mp4\" />}\n </video>\n )}\n </div>\n );\n })}\n </DeviceFrame>\n <span className=\"sr-only\" aria-live=\"off\">\n {labels.step(index + 1, total)}\n </span>\n </div>\n </section>\n );\n}\n"
202
+ }
203
+ ],
204
+ "dependencies": [],
205
+ "registryDependencies": [
206
+ "cn"
207
+ ],
208
+ "demo": {
209
+ "path": "examples/step-showcase/phone.tsx",
210
+ "content": "import { StepShowcase } from \"@krizaka/ui/step-showcase\";\n\nconst still = (n: number) => `data:image/svg+xml,${encodeURIComponent(`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 90 195\"><rect width=\"90\" height=\"195\" fill=\"#1b1f2a\"/><text x=\"45\" y=\"100\" fill=\"#8a93a6\" font-size=\"28\" text-anchor=\"middle\">${n}</text></svg>`)}`;\n\n// The real app, step by step: one phone, the steps beside it, each recording then the next (here, stills of 4 s).\nexport default function StepShowcasePhone() {\n return (\n <StepShowcase\n label=\"Orazaka in your pocket\"\n device=\"phone\"\n steps={[\n { id: \"sign-in\", title: \"Sign in\", description: \"Your workspace, on your phone.\", poster: still(1), alt: \"The sign-in screen\", duration: 4000 },\n { id: \"image\", title: \"Make an image\", description: \"A Studio, a prompt, the result.\", poster: still(2), alt: \"A Studio run\", duration: 4000 },\n { id: \"ask\", title: \"Ask the model\", description: \"Answers from your own models.\", poster: still(3), alt: \"The chat\", duration: 4000 },\n { id: \"jobs\", title: \"Follow your jobs\", description: \"Every run, its state and its result.\", poster: still(4), alt: \"The jobs list\", duration: 4000 },\n ]}\n />\n );\n}\n"
211
+ },
212
+ "props": [
213
+ {
214
+ "component": "DeviceFrame",
215
+ "description": "A phone, a browser window or a macOS window around a screen of content.",
216
+ "props": [
217
+ {
218
+ "name": "kind",
219
+ "type": "DeviceKind",
220
+ "default": null,
221
+ "description": "The device: a phone, a browser window or a macOS window.",
222
+ "required": true
223
+ },
224
+ {
225
+ "name": "title",
226
+ "type": "string",
227
+ "default": null,
228
+ "description": "The browser's address or the window's title (“dev.orazaka.com”, “Orazaka”).",
229
+ "required": false
230
+ }
231
+ ]
232
+ },
233
+ {
234
+ "component": "StepShowcase",
235
+ "description": "One device frame, its steps beside it, playing each recording then the next — a product's guided tour.",
236
+ "props": [
237
+ {
238
+ "name": "device",
239
+ "type": "DeviceKind",
240
+ "default": null,
241
+ "description": "The device the recordings were made on.",
242
+ "required": true
243
+ },
244
+ {
245
+ "name": "label",
246
+ "type": "string",
247
+ "default": null,
248
+ "description": "The sequence's accessible name (“Orazaka in your pocket”), passed translated.",
249
+ "required": true
250
+ },
251
+ {
252
+ "name": "steps",
253
+ "type": "readonly ShowcaseStep[]",
254
+ "default": null,
255
+ "description": "The steps, in the order of the journey.",
256
+ "required": true
257
+ },
258
+ {
259
+ "name": "autoplay",
260
+ "type": "boolean",
261
+ "default": "true",
262
+ "description": "Moves on by itself (never under reduced motion).",
263
+ "required": false
264
+ },
265
+ {
266
+ "name": "defaultIndex",
267
+ "type": "number",
268
+ "default": "0",
269
+ "description": "The step shown first.",
270
+ "required": false
271
+ },
272
+ {
273
+ "name": "deviceTitle",
274
+ "type": "string",
275
+ "default": null,
276
+ "description": "The browser's address or the window's title.",
277
+ "required": false
278
+ },
279
+ {
280
+ "name": "labels",
281
+ "type": "StepShowcaseLabels",
282
+ "default": "{\n play: \"Play the steps\",\n pause: \"Pause the steps\",\n step: (n, total) => `Step ${n} of ${total}`,\n}",
283
+ "description": "The controls' words, translated.",
284
+ "required": false
285
+ },
286
+ {
287
+ "name": "onIndexChange",
288
+ "type": "((index: number, user: boolean) => void)",
289
+ "default": null,
290
+ "description": "Called when the step changes — `user` is true when the visitor asked for it.",
291
+ "required": false
292
+ }
293
+ ]
294
+ }
295
+ ]
296
+ }
@@ -193,7 +193,7 @@
193
193
  "files": [
194
194
  {
195
195
  "path": "meta.ts",
196
- "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
196
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
197
197
  },
198
198
  {
199
199
  "path": "stepper/index.ts",