@krizaka/ui 3.19.0 → 3.21.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 (241) hide show
  1. package/dist/action-bar.js +4 -4
  2. package/dist/argument-card.js +1 -1
  3. package/dist/avatar/defaults.js +6 -1
  4. package/dist/avatar/defaults.js.map +1 -1
  5. package/dist/avatar.js +6 -1
  6. package/dist/avatar.js.map +1 -1
  7. package/dist/blocks.css +1 -1
  8. package/dist/button.js +3 -2
  9. package/dist/card.js +3 -3
  10. package/dist/chat.js +1 -1
  11. package/dist/{chunk-276PTQMQ.js → chunk-3BC4XQ6C.js} +2 -1
  12. package/dist/{chunk-2NB27LM7.js → chunk-3IN6TOPM.js} +4 -4
  13. package/dist/{chunk-VDR2EP6K.js → chunk-4QN5YJ3D.js} +17 -5
  14. package/dist/chunk-4QN5YJ3D.js.map +1 -0
  15. package/dist/{chunk-YIBKJ36L.js → chunk-KB7OLYIJ.js} +7 -2
  16. package/dist/{chunk-YIBKJ36L.js.map → chunk-KB7OLYIJ.js.map} +1 -1
  17. package/dist/chunk-TDG7ED2W.js +1 -0
  18. package/dist/chunk-TDG7ED2W.js.map +1 -0
  19. package/dist/code-block.js +17 -5
  20. package/dist/code-block.js.map +1 -1
  21. package/dist/data-table.js +1 -1
  22. package/dist/date-picker.js +2 -2
  23. package/dist/date-picker.js.map +1 -1
  24. package/dist/file-upload.js +2 -2
  25. package/dist/file-upload.js.map +1 -1
  26. package/dist/force-update-screen.d.ts +80 -0
  27. package/dist/force-update-screen.js +38 -0
  28. package/dist/force-update-screen.js.map +1 -0
  29. package/dist/maintenance-screen.d.ts +68 -0
  30. package/dist/maintenance-screen.js +35 -0
  31. package/dist/maintenance-screen.js.map +1 -0
  32. package/dist/match-dialog.js +2 -2
  33. package/dist/media-grid.d.ts +66 -3
  34. package/dist/media-grid.js +313 -66
  35. package/dist/media-grid.js.map +1 -1
  36. package/dist/native.d.ts +68 -1
  37. package/dist/native.js +773 -682
  38. package/dist/native.js.map +1 -1
  39. package/dist/nav-bar.d.ts +30 -7
  40. package/dist/nav-bar.js +69 -15
  41. package/dist/nav-bar.js.map +1 -1
  42. package/dist/page-hero.js +3 -3
  43. package/dist/progress.js +2 -2
  44. package/dist/progress.js.map +1 -1
  45. package/dist/radio-group.js +1 -1
  46. package/dist/settings-layout.js +1 -1
  47. package/dist/settings-layout.js.map +1 -1
  48. package/dist/tabs.js +2 -2
  49. package/dist/tabs.js.map +1 -1
  50. package/dist/update-prompt.d.ts +86 -0
  51. package/dist/update-prompt.js +41 -0
  52. package/dist/update-prompt.js.map +1 -0
  53. package/dist/vertical-video.d.ts +7 -1
  54. package/dist/vertical-video.js +7 -5
  55. package/dist/vertical-video.js.map +1 -1
  56. package/dist/video-player.d.ts +1 -1
  57. package/dist/video-player.js +16 -3
  58. package/dist/video-player.js.map +1 -1
  59. package/package.json +50 -8
  60. package/registry/achievement.json +16 -2
  61. package/registry/action-bar.json +16 -2
  62. package/registry/action-sheet.json +15 -0
  63. package/registry/activity-feed.json +13 -2
  64. package/registry/alert.json +15 -2
  65. package/registry/app-shell.json +15 -2
  66. package/registry/argument-card.json +16 -2
  67. package/registry/audio-player.json +15 -2
  68. package/registry/auth-layout.json +15 -2
  69. package/registry/avatar.json +14 -3
  70. package/registry/badge.json +13 -2
  71. package/registry/button.json +13 -2
  72. package/registry/card-grid.json +16 -2
  73. package/registry/card.json +13 -2
  74. package/registry/catalogue-page.json +15 -2
  75. package/registry/changelog-page.json +15 -2
  76. package/registry/chat.json +15 -4
  77. package/registry/checkbox.json +13 -2
  78. package/registry/chip-link.json +15 -2
  79. package/registry/chip.json +13 -2
  80. package/registry/choice-grid.json +16 -2
  81. package/registry/cn.json +12 -0
  82. package/registry/code-block.json +15 -2
  83. package/registry/combobox.json +13 -2
  84. package/registry/command.json +15 -2
  85. package/registry/component-page.json +15 -2
  86. package/registry/confirm-button.json +15 -2
  87. package/registry/contact-page.json +15 -2
  88. package/registry/continue-prompt.json +16 -2
  89. package/registry/countdown.json +13 -2
  90. package/registry/cta-panel.json +16 -2
  91. package/registry/dashboard-layout.json +15 -2
  92. package/registry/data-table.json +15 -2
  93. package/registry/date-picker.json +15 -4
  94. package/registry/decision-list.json +16 -2
  95. package/registry/description-list.json +13 -2
  96. package/registry/detail-layout.json +15 -2
  97. package/registry/dialog.json +13 -2
  98. package/registry/docs-article-page.json +15 -2
  99. package/registry/docs-hub-page.json +15 -2
  100. package/registry/docs-layout.json +15 -2
  101. package/registry/document-viewer.json +15 -2
  102. package/registry/dropdown-menu.json +15 -2
  103. package/registry/empty-state.json +13 -2
  104. package/registry/error-page.json +14 -2
  105. package/registry/examples/chat/conversation.js +2 -2
  106. package/registry/examples/chat/conversation.tsx +4 -3
  107. package/registry/examples/force-update-screen/default.d.ts +4 -0
  108. package/registry/examples/force-update-screen/default.js +25 -0
  109. package/registry/examples/force-update-screen/default.tsx +19 -0
  110. package/registry/examples/force-update-screen/native/default.d.ts +4 -0
  111. package/registry/examples/force-update-screen/native/default.dark.png +0 -0
  112. package/registry/examples/force-update-screen/native/default.js +24 -0
  113. package/registry/examples/force-update-screen/native/default.light.png +0 -0
  114. package/registry/examples/force-update-screen/native/default.tsx +18 -0
  115. package/registry/examples/glow-card/plans.js +9 -12
  116. package/registry/examples/glow-card/plans.tsx +18 -14
  117. package/registry/examples/maintenance-screen/default.d.ts +4 -0
  118. package/registry/examples/maintenance-screen/default.js +22 -0
  119. package/registry/examples/maintenance-screen/default.tsx +16 -0
  120. package/registry/examples/maintenance-screen/native/default.d.ts +4 -0
  121. package/registry/examples/maintenance-screen/native/default.dark.png +0 -0
  122. package/registry/examples/maintenance-screen/native/default.js +21 -0
  123. package/registry/examples/maintenance-screen/native/default.light.png +0 -0
  124. package/registry/examples/maintenance-screen/native/default.tsx +15 -0
  125. package/registry/examples/media-grid/loading.d.ts +4 -0
  126. package/registry/examples/media-grid/loading.js +28 -0
  127. package/registry/examples/media-grid/loading.tsx +25 -0
  128. package/registry/examples/nav-bar/family-product.js +16 -13
  129. package/registry/examples/nav-bar/family-product.tsx +19 -19
  130. package/registry/examples/update-prompt/default.d.ts +4 -0
  131. package/registry/examples/update-prompt/default.js +24 -0
  132. package/registry/examples/update-prompt/default.tsx +18 -0
  133. package/registry/examples/update-prompt/native/default.d.ts +4 -0
  134. package/registry/examples/update-prompt/native/default.dark.png +0 -0
  135. package/registry/examples/update-prompt/native/default.js +23 -0
  136. package/registry/examples/update-prompt/native/default.light.png +0 -0
  137. package/registry/examples/update-prompt/native/default.tsx +17 -0
  138. package/registry/examples/vertical-video/rail.js +3 -2
  139. package/registry/examples/vertical-video/rail.tsx +4 -3
  140. package/registry/fab.json +15 -0
  141. package/registry/field.json +13 -2
  142. package/registry/file-upload.json +15 -2
  143. package/registry/filter-bar.json +15 -2
  144. package/registry/force-update-screen.json +323 -0
  145. package/registry/form.json +13 -2
  146. package/registry/gallery-page.json +15 -2
  147. package/registry/glow-card.json +18 -4
  148. package/registry/goal-gauge.json +13 -2
  149. package/registry/header.json +15 -0
  150. package/registry/how-it-works-page.json +15 -2
  151. package/registry/image-cropper.json +15 -2
  152. package/registry/image-viewer.json +15 -0
  153. package/registry/index.json +1864 -8
  154. package/registry/iso.json +15 -2
  155. package/registry/kanban.json +15 -2
  156. package/registry/kbd.json +14 -2
  157. package/registry/landing-page.json +15 -2
  158. package/registry/landscape.json +15 -2
  159. package/registry/legal-page.json +15 -2
  160. package/registry/link-card.json +16 -2
  161. package/registry/list.json +13 -2
  162. package/registry/loader.json +13 -2
  163. package/registry/longform-page.json +15 -2
  164. package/registry/maintenance-screen.json +259 -0
  165. package/registry/map.json +14 -3
  166. package/registry/markdown.json +13 -2
  167. package/registry/marketing-layout.json +15 -2
  168. package/registry/marquee.json +15 -2
  169. package/registry/mascot-quote.json +16 -2
  170. package/registry/mascot-state.json +13 -2
  171. package/registry/mascots.json +13 -2
  172. package/registry/match-dialog.json +13 -2
  173. package/registry/media-capture.json +13 -2
  174. package/registry/media-grid.json +342 -14
  175. package/registry/media-picker.json +15 -0
  176. package/registry/meter.json +13 -2
  177. package/registry/nav-bar.json +61 -14
  178. package/registry/number-ticker.json +13 -2
  179. package/registry/onboarding.json +15 -0
  180. package/registry/otp-input.json +15 -0
  181. package/registry/page-header.json +15 -2
  182. package/registry/page-hero.json +16 -2
  183. package/registry/page.json +16 -2
  184. package/registry/pagination.json +13 -2
  185. package/registry/permission-prompt.json +15 -0
  186. package/registry/photo-grid.json +13 -2
  187. package/registry/pointer-light.json +15 -2
  188. package/registry/popover.json +15 -2
  189. package/registry/pricing-page.json +15 -2
  190. package/registry/product-page.json +15 -2
  191. package/registry/profile-card.json +13 -2
  192. package/registry/progress.json +14 -3
  193. package/registry/promise-card.json +16 -2
  194. package/registry/pull-to-refresh.json +15 -0
  195. package/registry/radio-group.json +14 -3
  196. package/registry/recipes.json +14 -2
  197. package/registry/screen.json +15 -0
  198. package/registry/scroll-stage.json +15 -2
  199. package/registry/search-field.json +13 -2
  200. package/registry/section-backdrop.json +15 -2
  201. package/registry/segmented.json +15 -0
  202. package/registry/separator.json +15 -2
  203. package/registry/settings-layout.json +16 -3
  204. package/registry/showcase.json +13 -2
  205. package/registry/site-footer.json +16 -3
  206. package/registry/sites.json +15 -2
  207. package/registry/skeleton.json +13 -2
  208. package/registry/skip-link.json +14 -2
  209. package/registry/slider.json +13 -2
  210. package/registry/slot.json +14 -2
  211. package/registry/spinner.json +13 -2
  212. package/registry/splash-handoff.json +15 -0
  213. package/registry/split-view.json +15 -2
  214. package/registry/stage-banner.json +15 -2
  215. package/registry/stat.json +15 -2
  216. package/registry/status-page.json +15 -2
  217. package/registry/step-progress.json +13 -2
  218. package/registry/stepper.json +13 -2
  219. package/registry/swipe-deck.json +13 -2
  220. package/registry/swipeable-row.json +15 -0
  221. package/registry/switch.json +13 -2
  222. package/registry/tab-bar.json +15 -0
  223. package/registry/tabs.json +15 -4
  224. package/registry/tag-input.json +15 -2
  225. package/registry/tag-selector.json +13 -2
  226. package/registry/terminal-replay.json +15 -2
  227. package/registry/theme.json +13 -2
  228. package/registry/toast.json +13 -2
  229. package/registry/toc.json +15 -2
  230. package/registry/tooltip.json +14 -2
  231. package/registry/tree-view.json +15 -2
  232. package/registry/txt.json +13 -0
  233. package/registry/update-prompt.json +316 -0
  234. package/registry/verified-badge.json +13 -2
  235. package/registry/vertical-video.json +32 -6
  236. package/registry/video-player.json +16 -5
  237. package/registry/video-trimmer.json +15 -2
  238. package/registry/virtual.json +15 -2
  239. package/dist/chunk-VDR2EP6K.js.map +0 -1
  240. /package/dist/{chunk-276PTQMQ.js.map → chunk-3BC4XQ6C.js.map} +0 -0
  241. /package/dist/{chunk-2NB27LM7.js.map → chunk-3IN6TOPM.js.map} +0 -0
@@ -8,6 +8,17 @@
8
8
  "deprecated": null,
9
9
  "category": "forms",
10
10
  "platforms": "both",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "yes"
17
+ },
18
+ "desktop": {
19
+ "state": "yes"
20
+ }
21
+ },
11
22
  "whenToUse": [
12
23
  "In a form, to choose exactly one of two to five options that should all be seen: a frequency, a quality.",
13
24
  "`RadioGroup.Card` when each option needs more than a line: an amount, a plan, a payment method.",
@@ -308,7 +319,7 @@
308
319
  },
309
320
  {
310
321
  "path": "meta.ts",
311
- "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 * `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 /** 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"
322
+ "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 | \"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 | \"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 | \"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 | \"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"
312
323
  },
313
324
  {
314
325
  "path": "radio-group/index.ts",
@@ -316,7 +327,7 @@
316
327
  },
317
328
  {
318
329
  "path": "radio-group/meta.ts",
319
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Radio group\",\n summary: \"One choice among a few options, all visible: dots with their words, whole cards, tactile keys that press in, or a segmented control.\",\n status: \"beta\",\n category: \"forms\",\n platforms: \"both\",\n whenToUse: [\n \"In a form, to choose exactly one of two to five options that should all be seen: a frequency, a quality.\",\n \"`RadioGroup.Card` when each option needs more than a line: an amount, a plan, a payment method.\",\n \"`RadioGroup.Button` (`variant=\\\"buttons\\\"`) for short, tactile choices made in a tap: a size, a tip, a level.\",\n \"`RadioGroup.Segment` (`variant=\\\"segmented\\\"`) for two to four short choices of a form on one line: a billing period, a unit.\",\n ],\n whenNotToUse: [\n { when: \"For many options (a country).\", use: \"field\" },\n { when: \"For independent options that can all be chosen.\", use: \"checkbox\" },\n { when: \"To filter a list at once, without a form.\", use: \"chip\" },\n { when: \"To switch between views.\", use: \"tabs\" },\n ],\n bestPractices: [\n \"Give the group a `label`: the question being answered.\",\n \"Pre-select the safest or most common option with `defaultValue` when there is one.\",\n \"Keep the labels parallel and short.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Tab\", action: \"Moves into the group (one tab stop, on the chosen option) and out of it.\" },\n { keys: \"Arrow keys\", action: \"Move to the next or previous option and choose it.\" },\n ],\n notes: [\"Radix RadioGroup: `role=\\\"radiogroup\\\"` named by `label`, `role=\\\"radio\\\"` with `aria-checked` on each option.\", \"Disabled options are skipped by the arrows.\", \"A tactile key\\u2019s `hint` is part of its name; the pressed look follows the checked state, never colour alone (it sinks).\"],\n },\n related: [\"checkbox\", \"chip\", \"field\", \"switch\"],\n web: {\n imports: [\"RadioGroup\"],\n examples: [\n { name: \"items\", title: \"Items\", description: \"Dots and their words, one disabled.\" },\n { name: \"cards\", title: \"Cards\", description: \"Whole cards: the chosen one carries the accent.\" },\n { name: \"horizontal\", title: \"Horizontal\", description: \"`orientation=\\\"horizontal\\\"`: the items in a row.\" },\n { name: \"buttons\", title: \"Tactile keys\", description: \"`variant=\\\"buttons\\\"`: keys raised on their ledge, pressed in while held — the chosen one stays sunk.\" },\n { name: \"segmented\", title: \"Segmented\", description: \"`variant=\\\"segmented\\\"`: one light glides to the chosen segment.\" },\n ],\n },\n native: {\n imports: [\"RadioGroup\"],\n differences: [\n \"One component with `options` (`value`, `label`, `description`, `hint`, `disabled`) and `variant` — no `Item` / `Card` / `Button` / `Segment` parts.\",\n \"Each press plays a haptic when the app passes `haptics`: `impact` for a tactile key, `selection` otherwise.\",\n \"`error` shows under the group and is announced.\",\n ],\n examples: [\n { name: \"list\", title: \"List\", description: \"Dots and their words in 44-pt rows, one disabled.\" },\n { name: \"buttons\", title: \"Tactile keys\", description: \"Keys on a ledge that sink when pressed and stay sunk once chosen.\" },\n { name: \"cards\", title: \"Cards\", description: \"Whole cards, the chosen one tinted with its check lit.\" },\n { name: \"segmented\", title: \"Segmented\", description: \"A track of equal segments: a form's choice.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
330
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Radio group\",\n summary: \"One choice among a few options, all visible: dots with their words, whole cards, tactile keys that press in, or a segmented control.\",\n status: \"beta\",\n category: \"forms\",\n platforms: \"both\",\n parity: { desktop: \"web\" },\n whenToUse: [\n \"In a form, to choose exactly one of two to five options that should all be seen: a frequency, a quality.\",\n \"`RadioGroup.Card` when each option needs more than a line: an amount, a plan, a payment method.\",\n \"`RadioGroup.Button` (`variant=\\\"buttons\\\"`) for short, tactile choices made in a tap: a size, a tip, a level.\",\n \"`RadioGroup.Segment` (`variant=\\\"segmented\\\"`) for two to four short choices of a form on one line: a billing period, a unit.\",\n ],\n whenNotToUse: [\n { when: \"For many options (a country).\", use: \"field\" },\n { when: \"For independent options that can all be chosen.\", use: \"checkbox\" },\n { when: \"To filter a list at once, without a form.\", use: \"chip\" },\n { when: \"To switch between views.\", use: \"tabs\" },\n ],\n bestPractices: [\n \"Give the group a `label`: the question being answered.\",\n \"Pre-select the safest or most common option with `defaultValue` when there is one.\",\n \"Keep the labels parallel and short.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Tab\", action: \"Moves into the group (one tab stop, on the chosen option) and out of it.\" },\n { keys: \"Arrow keys\", action: \"Move to the next or previous option and choose it.\" },\n ],\n notes: [\"Radix RadioGroup: `role=\\\"radiogroup\\\"` named by `label`, `role=\\\"radio\\\"` with `aria-checked` on each option.\", \"Disabled options are skipped by the arrows.\", \"A tactile key\\u2019s `hint` is part of its name; the pressed look follows the checked state, never colour alone (it sinks).\"],\n },\n related: [\"checkbox\", \"chip\", \"field\", \"switch\"],\n web: {\n imports: [\"RadioGroup\"],\n examples: [\n { name: \"items\", title: \"Items\", description: \"Dots and their words, one disabled.\" },\n { name: \"cards\", title: \"Cards\", description: \"Whole cards: the chosen one carries the accent.\" },\n { name: \"horizontal\", title: \"Horizontal\", description: \"`orientation=\\\"horizontal\\\"`: the items in a row.\" },\n { name: \"buttons\", title: \"Tactile keys\", description: \"`variant=\\\"buttons\\\"`: keys raised on their ledge, pressed in while held — the chosen one stays sunk.\" },\n { name: \"segmented\", title: \"Segmented\", description: \"`variant=\\\"segmented\\\"`: one light glides to the chosen segment.\" },\n ],\n },\n native: {\n imports: [\"RadioGroup\"],\n differences: [\n \"One component with `options` (`value`, `label`, `description`, `hint`, `disabled`) and `variant` — no `Item` / `Card` / `Button` / `Segment` parts.\",\n \"Each press plays a haptic when the app passes `haptics`: `impact` for a tactile key, `selection` otherwise.\",\n \"`error` shows under the group and is announced.\",\n ],\n examples: [\n { name: \"list\", title: \"List\", description: \"Dots and their words in 44-pt rows, one disabled.\" },\n { name: \"buttons\", title: \"Tactile keys\", description: \"Keys on a ledge that sink when pressed and stay sunk once chosen.\" },\n { name: \"cards\", title: \"Cards\", description: \"Whole cards, the chosen one tinted with its check lit.\" },\n { name: \"segmented\", title: \"Segmented\", description: \"A track of equal segments: a form's choice.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
320
331
  },
321
332
  {
322
333
  "path": "radio-group/radio-group.tsx",
@@ -324,7 +335,7 @@
324
335
  },
325
336
  {
326
337
  "path": "sliding-indicator.ts",
327
- "content": "// The shared sliding indicator (NavBar, Tabs): one element that glides from item to item instead of each item lighting\n// up on its own. It writes the target's box as CSS variables on the list (`--kz-ind-x/y/w/h`) and `data-indicator=\"on\"`\n// — no re-render, no animation frame: the glide is a CSS transition (none under reduced motion). Before hydration the\n// items keep their own active style, so nothing flashes.\nimport * as React from \"react\";\n\nconst useIsoLayoutEffect = typeof window === \"undefined\" ? React.useEffect : React.useLayoutEffect;\n\nexport function useSlidingIndicator<T extends HTMLElement>({ item, active, follow = false }: { item: string; active: string; follow?: boolean }) {\n const ref = React.useRef<T>(null);\n const place = React.useCallback((target: Element | null) => {\n const list = ref.current;\n if (!list) return;\n if (!(target instanceof HTMLElement) || !list.contains(target)) {\n list.removeAttribute(\"data-indicator\");\n return;\n }\n const a = target.getBoundingClientRect();\n const b = list.getBoundingClientRect();\n list.style.setProperty(\"--kz-ind-x\", `${a.left - b.left + list.scrollLeft}px`);\n list.style.setProperty(\"--kz-ind-y\", `${a.top - b.top + list.scrollTop}px`);\n list.style.setProperty(\"--kz-ind-w\", `${a.width}px`);\n list.style.setProperty(\"--kz-ind-h\", `${a.height}px`);\n list.setAttribute(\"data-indicator\", \"on\");\n }, []);\n const rest = React.useCallback(() => place(ref.current?.querySelector(active) ?? null), [place, active]);\n\n useIsoLayoutEffect(() => {\n const list = ref.current;\n if (!list) return;\n rest();\n const resize = new ResizeObserver(rest);\n resize.observe(list);\n const changes = new MutationObserver(rest);\n changes.observe(list, { subtree: true, attributes: true, attributeFilter: [\"data-state\", \"aria-current\", \"data-active\"] });\n return () => {\n resize.disconnect();\n changes.disconnect();\n };\n }, [rest]);\n\n const handlers = follow\n ? {\n onPointerOver: (e: React.PointerEvent) => place((e.target as Element).closest(item)),\n onPointerLeave: rest,\n onFocus: (e: React.FocusEvent) => place((e.target as Element).closest(item)),\n onBlur: rest,\n }\n : {};\n return { ref, handlers };\n}\n"
338
+ "content": "// The shared sliding indicator (NavBar, Tabs): one element that glides from item to item instead of each item lighting\n// up on its own. It writes the target's box as CSS variables on the list (`--kz-ind-x/y/w/h`) and `data-indicator=\"on\"`\n// — no re-render, no animation frame: the glide is a CSS transition (none under reduced motion). Before hydration the\n// items keep their own active style, so nothing flashes.\nimport * as React from \"react\";\n\nconst useIsoLayoutEffect = typeof window === \"undefined\" ? React.useEffect : React.useLayoutEffect;\n\n/**\n * `item`: the selector of an item; `active`: of the current one, where the light rests; `prefer`: an item that, when\n * present, holds the light before the current one (the NavBar's open menu); `follow`: the light follows the pointer\n * and the focus — over anything that is not an item (an open menu's panel) it goes back to rest.\n */\nexport function useSlidingIndicator<T extends HTMLElement>({ item, active, prefer, follow = false }: { item: string; active: string; prefer?: string; follow?: boolean }) {\n const ref = React.useRef<T>(null);\n const place = React.useCallback((target: Element | null) => {\n const list = ref.current;\n if (!list) return;\n if (!(target instanceof HTMLElement) || !list.contains(target)) {\n list.removeAttribute(\"data-indicator\");\n return;\n }\n const a = target.getBoundingClientRect();\n const b = list.getBoundingClientRect();\n list.style.setProperty(\"--kz-ind-x\", `${a.left - b.left + list.scrollLeft}px`);\n list.style.setProperty(\"--kz-ind-y\", `${a.top - b.top + list.scrollTop}px`);\n list.style.setProperty(\"--kz-ind-w\", `${a.width}px`);\n list.style.setProperty(\"--kz-ind-h\", `${a.height}px`);\n list.setAttribute(\"data-indicator\", \"on\");\n }, []);\n const rest = React.useCallback(\n () => {\n const list = ref.current;\n // An item re-rendered under a resting pointer (a menu closing) keeps the light the pointer gave it.\n const hovered = follow ? list?.querySelector(`${item}:hover`) : null;\n place((prefer ? list?.querySelector(prefer) : null) ?? hovered ?? list?.querySelector(active) ?? null);\n },\n [place, active, prefer, item, follow],\n );\n const follow_ = React.useCallback((target: EventTarget) => {\n const hit = target instanceof Element ? target.closest(item) : null;\n if (hit && ref.current?.contains(hit)) place(hit);\n else rest();\n }, [item, place, rest]);\n\n useIsoLayoutEffect(() => {\n const list = ref.current;\n if (!list) return;\n rest();\n const resize = new ResizeObserver(rest);\n resize.observe(list);\n const changes = new MutationObserver(rest);\n changes.observe(list, { subtree: true, attributes: true, attributeFilter: [\"data-state\", \"aria-current\", \"data-active\"] });\n return () => {\n resize.disconnect();\n changes.disconnect();\n };\n }, [rest]);\n\n const handlers = follow\n ? {\n onPointerOver: (e: React.PointerEvent) => follow_(e.target),\n onPointerLeave: rest,\n onFocus: (e: React.FocusEvent) => follow_(e.target),\n onBlur: rest,\n }\n : {};\n return { ref, handlers };\n}\n"
328
339
  },
329
340
  {
330
341
  "path": "tv.ts",
@@ -8,6 +8,18 @@
8
8
  "deprecated": null,
9
9
  "category": "foundations",
10
10
  "platforms": "web",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "n/a",
17
+ "reason": "The recipes are already shared: the native Button, Badge, Chip and Card read the same definitions through nativeRecipe."
18
+ },
19
+ "desktop": {
20
+ "state": "yes"
21
+ }
22
+ },
11
23
  "whenToUse": [
12
24
  "A component that exists on the web and on the phone, whose variants (a look, a size, a tone) must stay the same on both.",
13
25
  "A product composite that adds variants to a primitive and wants its React Native twin to follow."
@@ -64,7 +76,7 @@
64
76
  "files": [
65
77
  {
66
78
  "path": "meta.ts",
67
- "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 * `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 /** 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"
79
+ "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 | \"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 | \"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 | \"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 | \"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"
68
80
  },
69
81
  {
70
82
  "path": "recipes/define.ts",
@@ -76,7 +88,7 @@
76
88
  },
77
89
  {
78
90
  "path": "recipes/meta.ts",
79
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Recipes\",\n summary:\n \"The variants of a component written once, as classes of the Krizaka tokens: the web wears them with `tv(recipe)`, React Native reads the same classes into styles on the theme's roles with `nativeRecipe(recipe).resolve(props, theme)`. Button, Badge, Chip and Card are built this way, so the two platforms cannot name different tokens.\",\n status: \"beta\",\n category: \"foundations\",\n platforms: \"web\",\n whenToUse: [\n \"A component that exists on the web and on the phone, whose variants (a look, a size, a tone) must stay the same on both.\",\n \"A product composite that adds variants to a primitive and wants its React Native twin to follow.\",\n ],\n whenNotToUse: [\n { when: \"To merge class names on the web: `cn` does that.\", use: \"cn\" },\n { when: \"For a component that has a web side only: write `tv({ … })` as the primitives do.\" },\n { when: \"For the layout, the pointer and focus states, shadows and transitions: they stay on the web side of the recipe, the phone ignores them.\" },\n ],\n bestPractices: [\n \"Write colours as roles (`bg-accent`, `text-fg-secondary`, `border-border-default`, `ring-accent/40`) and sizes in Tailwind units (`h-10`, `px-4`, `text-sm`, `text-[11px]`): those are what the phone can read.\",\n \"Sizes are a variant dimension; states are prefixes: `hover:` is for the pointer (web only), `disabled:` and `data-[state=on]:` are read on both.\",\n \"Keep what the phone draws differently in its component (the radius of a shape, the pressed opacity, a shadow): the recipe holds colours, sizes and type.\",\n \"Check the pair in a test: resolve every variant on both themes and fail on a class that is neither mapped nor declared web-only (see `recipes.test.ts`).\",\n \"Pass the native theme to `resolve` (`useTheme().theme`) and wrap the result in `StyleSheet` in the component; the recipe itself imports nothing from React Native.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"A recipe only carries colours and sizes: the contrast of each pair is the tokens' (text roles ≥ 4.5:1 on the surfaces they are meant for, tested in @krizaka/tokens).\",\n \"Focus rings and the pointer's states are web classes and stay on the web side; a phone has the platform's own focus and press feedback.\",\n ],\n },\n related: [\"button\", \"badge\", \"chip\", \"card\", \"cn\"],\n web: {\n imports: [\"defineRecipe\", \"nativeRecipe\", \"withAlpha\", \"ROLES\"],\n examples: [{ name: \"tone-chip\", title: \"What the phone reads\", description: \"A recipe of two tones and two sizes, and what `nativeRecipe` reads from its classes: points, colours on a theme, and the classes only the web has.\" }],\n },\n} satisfies ComponentMeta;\n"
91
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Recipes\",\n summary:\n \"The variants of a component written once, as classes of the Krizaka tokens: the web wears them with `tv(recipe)`, React Native reads the same classes into styles on the theme's roles with `nativeRecipe(recipe).resolve(props, theme)`. Button, Badge, Chip and Card are built this way, so the two platforms cannot name different tokens.\",\n status: \"beta\",\n category: \"foundations\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"The recipes are already shared: the native Button, Badge, Chip and Card read the same definitions through nativeRecipe.\" }, desktop: \"web\" },\n whenToUse: [\n \"A component that exists on the web and on the phone, whose variants (a look, a size, a tone) must stay the same on both.\",\n \"A product composite that adds variants to a primitive and wants its React Native twin to follow.\",\n ],\n whenNotToUse: [\n { when: \"To merge class names on the web: `cn` does that.\", use: \"cn\" },\n { when: \"For a component that has a web side only: write `tv({ … })` as the primitives do.\" },\n { when: \"For the layout, the pointer and focus states, shadows and transitions: they stay on the web side of the recipe, the phone ignores them.\" },\n ],\n bestPractices: [\n \"Write colours as roles (`bg-accent`, `text-fg-secondary`, `border-border-default`, `ring-accent/40`) and sizes in Tailwind units (`h-10`, `px-4`, `text-sm`, `text-[11px]`): those are what the phone can read.\",\n \"Sizes are a variant dimension; states are prefixes: `hover:` is for the pointer (web only), `disabled:` and `data-[state=on]:` are read on both.\",\n \"Keep what the phone draws differently in its component (the radius of a shape, the pressed opacity, a shadow): the recipe holds colours, sizes and type.\",\n \"Check the pair in a test: resolve every variant on both themes and fail on a class that is neither mapped nor declared web-only (see `recipes.test.ts`).\",\n \"Pass the native theme to `resolve` (`useTheme().theme`) and wrap the result in `StyleSheet` in the component; the recipe itself imports nothing from React Native.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"A recipe only carries colours and sizes: the contrast of each pair is the tokens' (text roles ≥ 4.5:1 on the surfaces they are meant for, tested in @krizaka/tokens).\",\n \"Focus rings and the pointer's states are web classes and stay on the web side; a phone has the platform's own focus and press feedback.\",\n ],\n },\n related: [\"button\", \"badge\", \"chip\", \"card\", \"cn\"],\n web: {\n imports: [\"defineRecipe\", \"nativeRecipe\", \"withAlpha\", \"ROLES\"],\n examples: [{ name: \"tone-chip\", title: \"What the phone reads\", description: \"A recipe of two tones and two sizes, and what `nativeRecipe` reads from its classes: points, colours on a theme, and the classes only the web has.\" }],\n },\n} satisfies ComponentMeta;\n"
80
92
  },
81
93
  {
82
94
  "path": "recipes/native.ts",
@@ -8,6 +8,21 @@
8
8
  "deprecated": null,
9
9
  "category": "layout",
10
10
  "platforms": "native",
11
+ "parity": {
12
+ "web": {
13
+ "state": "n/a",
14
+ "reason": "A web screen's frame is the application shell.",
15
+ "use": "app-shell"
16
+ },
17
+ "native": {
18
+ "state": "yes"
19
+ },
20
+ "desktop": {
21
+ "state": "n/a",
22
+ "reason": "The Mac app's window is the application shell.",
23
+ "use": "app-shell"
24
+ }
25
+ },
11
26
  "whenToUse": [
12
27
  "Every screen of a React Native app.",
13
28
  "The starting point of a new app: copy a template and fill it."
@@ -8,6 +8,19 @@
8
8
  "deprecated": null,
9
9
  "category": "layout",
10
10
  "platforms": "web",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "n/a",
17
+ "reason": "Part of the storytelling kit of scroll-driven site pages; a phone app tells its first steps with Onboarding."
18
+ },
19
+ "desktop": {
20
+ "state": "n/a",
21
+ "reason": "A public site's page, not a window of the Mac app."
22
+ }
23
+ },
11
24
  "whenToUse": [
12
25
  "A page that tells a story in chapters: an about page, a product tour, a launch, a year in review.",
13
26
  "An onboarding or a long landing page whose sections should arrive one at a time, at reading speed.",
@@ -119,7 +132,7 @@
119
132
  "files": [
120
133
  {
121
134
  "path": "meta.ts",
122
- "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 * `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 /** 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"
135
+ "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 | \"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 | \"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 | \"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 | \"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"
123
136
  },
124
137
  {
125
138
  "path": "scroll-stage/index.ts",
@@ -127,7 +140,7 @@
127
140
  },
128
141
  {
129
142
  "path": "scroll-stage/meta.ts",
130
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Scroll stage\",\n summary:\n \"The conductor of a told page: chapters (`Scene`) that play their entrance once, in order, as they scroll into view — the visual, then the announcement, then the words — with one slow loop each, running only while on screen.\",\n status: \"beta\",\n category: \"layout\",\n platforms: \"web\",\n whenToUse: [\n \"A page that tells a story in chapters: an about page, a product tour, a launch, a year in review.\",\n \"An onboarding or a long landing page whose sections should arrive one at a time, at reading speed.\",\n \"To give a whole page the arcade flavour of the storytelling kit at once (`variant=\\\"arcade\\\"`).\",\n ],\n whenNotToUse: [\n { when: \"Ordinary pages: blocks that simply fade in as they enter.\", use: \"page\" },\n { when: \"Working screens (a dashboard, a form, a table): nothing there should wait to appear.\" },\n ],\n bestPractices: [\n \"One `ScrollStage` per page, around the chapters; one `Scene` per chapter (a `section` by default).\",\n \"Mark the words of a chapter with `data-kzs-reveal`: they arrive after its visual and its banner (`--kzs-delay` to change when).\",\n \"Keep the first screen out of any scene's entrance: what the reader sees on arrival is painted at once.\",\n \"Import the kit's styles once: `import \\\"@krizaka/ui/story.css\\\"`.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"Under `prefers-reduced-motion` the stage never switches motion on: everything is visible and still.\",\n \"Nothing is hidden from assistive technology or from a page without JavaScript: content is only held invisible once the stage is on, until its scene enters.\",\n \"Each scene has at most one loop, slow and faint, paused off screen and never behind a paragraph.\",\n ],\n },\n related: [\"stage-banner\", \"goal-gauge\", \"profile-card\", \"choice-grid\", \"achievement\", \"continue-prompt\"],\n web: {\n imports: [\"ScrollStage\", \"Scene\"],\n examples: [\n { name: \"chapters\", title: \"Chapters\", description: \"Two chapters of an onboarding: each one's banner, then its words, then its gauge — once, as they scroll in.\" },\n { name: \"arcade\", title: \"Arcade flavour\", description: \"`variant=\\\"arcade\\\"` once on the stage: a final round, the boss gauge draining.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
143
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Scroll stage\",\n summary:\n \"The conductor of a told page: chapters (`Scene`) that play their entrance once, in order, as they scroll into view — the visual, then the announcement, then the words — with one slow loop each, running only while on screen.\",\n status: \"beta\",\n category: \"layout\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"Part of the storytelling kit of scroll-driven site pages; a phone app tells its first steps with Onboarding.\" }, desktop: { status: \"n/a\", reason: \"A public site's page, not a window of the Mac app.\" } },\n whenToUse: [\n \"A page that tells a story in chapters: an about page, a product tour, a launch, a year in review.\",\n \"An onboarding or a long landing page whose sections should arrive one at a time, at reading speed.\",\n \"To give a whole page the arcade flavour of the storytelling kit at once (`variant=\\\"arcade\\\"`).\",\n ],\n whenNotToUse: [\n { when: \"Ordinary pages: blocks that simply fade in as they enter.\", use: \"page\" },\n { when: \"Working screens (a dashboard, a form, a table): nothing there should wait to appear.\" },\n ],\n bestPractices: [\n \"One `ScrollStage` per page, around the chapters; one `Scene` per chapter (a `section` by default).\",\n \"Mark the words of a chapter with `data-kzs-reveal`: they arrive after its visual and its banner (`--kzs-delay` to change when).\",\n \"Keep the first screen out of any scene's entrance: what the reader sees on arrival is painted at once.\",\n \"Import the kit's styles once: `import \\\"@krizaka/ui/story.css\\\"`.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"Under `prefers-reduced-motion` the stage never switches motion on: everything is visible and still.\",\n \"Nothing is hidden from assistive technology or from a page without JavaScript: content is only held invisible once the stage is on, until its scene enters.\",\n \"Each scene has at most one loop, slow and faint, paused off screen and never behind a paragraph.\",\n ],\n },\n related: [\"stage-banner\", \"goal-gauge\", \"profile-card\", \"choice-grid\", \"achievement\", \"continue-prompt\"],\n web: {\n imports: [\"ScrollStage\", \"Scene\"],\n examples: [\n { name: \"chapters\", title: \"Chapters\", description: \"Two chapters of an onboarding: each one's banner, then its words, then its gauge — once, as they scroll in.\" },\n { name: \"arcade\", title: \"Arcade flavour\", description: \"`variant=\\\"arcade\\\"` once on the stage: a final round, the boss gauge draining.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
131
144
  },
132
145
  {
133
146
  "path": "scroll-stage/scroll-stage.tsx",
@@ -8,6 +8,17 @@
8
8
  "deprecated": null,
9
9
  "category": "forms",
10
10
  "platforms": "both",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "yes"
17
+ },
18
+ "desktop": {
19
+ "state": "yes"
20
+ }
21
+ },
11
22
  "whenToUse": [
12
23
  "At the top of a list or a page to find by words: videos, creators, jobs, users.",
13
24
  "With `shortcut` for the site's global search, reachable from anywhere with ⌘K.",
@@ -330,7 +341,7 @@
330
341
  },
331
342
  {
332
343
  "path": "meta.ts",
333
- "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 * `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 /** 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"
344
+ "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 | \"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 | \"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 | \"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 | \"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"
334
345
  },
335
346
  {
336
347
  "path": "search-field/index.ts",
@@ -338,7 +349,7 @@
338
349
  },
339
350
  {
340
351
  "path": "search-field/meta.ts",
341
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Search field\",\n summary: \"A search box: a clear button, a global ⌘K / Ctrl+K shortcut, a debounced `onSearch`, Enter to submit, and suggestions with the keyboard.\",\n status: \"beta\",\n category: \"forms\",\n platforms: \"both\",\n whenToUse: [\n \"At the top of a list or a page to find by words: videos, creators, jobs, users.\",\n \"With `shortcut` for the site's global search, reachable from anywhere with ⌘K.\",\n \"With `suggestions` for recent searches or completions under the field.\",\n ],\n whenNotToUse: [\n { when: \"To choose a value from a list (the result is the value, not a search): a combobox.\", use: \"combobox\" },\n { when: \"For a palette of commands and destinations: the command palette.\", use: \"command\" },\n ],\n bestPractices: [\n \"Name it by what it searches (“Search videos”), and say it in the placeholder too.\",\n \"Fetch in `onSearch` (debounced), navigate in `onSubmit`; keep the query in the address.\",\n \"Show the shortcut only for the site's main search: one ⌘K per page.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"⌘ K / Ctrl K\", action: \"Focus the field from anywhere (with `shortcut=\\\"k\\\"`).\" },\n { keys: \"Enter\", action: \"Submit the query, or pick the active suggestion.\" },\n { keys: \"Esc\", action: \"Close the suggestions, then clear the query.\" },\n { keys: \"Arrow Down / Arrow Up\", action: \"Move through the suggestions.\" },\n ],\n notes: [\n \"An `input type=\\\"search\\\"` named by `label`, inside a `search` landmark (`landmark={false}` inside a form that is one).\",\n \"With suggestions it follows the combobox pattern (`aria-activedescendant`, a `listbox`).\",\n \"The clear button is named by `clearLabel`; the shortcut's keys are decorative (`aria-hidden`).\",\n ],\n },\n related: [\"combobox\", \"command\", \"filter-bar\", \"kbd\"],\n web: {\n imports: [\"SearchField\"],\n examples: [\n { name: \"global\", title: \"Global search\", description: \"The site's search with its ⌘K shortcut, submitting to a results page.\" },\n { name: \"suggestions\", title: \"Recent searches\", description: \"Suggestions under the field, walked with the arrows.\" },\n { name: \"toolbar\", title: \"In a toolbar\", description: \"`size=\\\"sm\\\"`, filtering a list as you type.\" },\n ],\n },\n native: {\n imports: [\"SearchField\"],\n differences: [\n \"No shortcut and no suggestions: the keyboard's search key submits, and results are a screen of their own.\",\n \"`clearLabel` is required; `role=\\\"searchbox\\\"` names the input.\",\n \"`cancelLabel` with `onCancel` adds the platform's Cancel button beside the field.\",\n ],\n examples: [\n { name: \"default\", title: \"Default\", description: \"The glyph, the query, the clear button and the keyboard's search key.\" },\n { name: \"cancel\", title: \"With Cancel\", description: \"The iOS search bar: a Cancel button that clears and closes the keyboard.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
352
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Search field\",\n summary: \"A search box: a clear button, a global ⌘K / Ctrl+K shortcut, a debounced `onSearch`, Enter to submit, and suggestions with the keyboard.\",\n status: \"beta\",\n category: \"forms\",\n platforms: \"both\",\n parity: { desktop: \"web\" },\n whenToUse: [\n \"At the top of a list or a page to find by words: videos, creators, jobs, users.\",\n \"With `shortcut` for the site's global search, reachable from anywhere with ⌘K.\",\n \"With `suggestions` for recent searches or completions under the field.\",\n ],\n whenNotToUse: [\n { when: \"To choose a value from a list (the result is the value, not a search): a combobox.\", use: \"combobox\" },\n { when: \"For a palette of commands and destinations: the command palette.\", use: \"command\" },\n ],\n bestPractices: [\n \"Name it by what it searches (“Search videos”), and say it in the placeholder too.\",\n \"Fetch in `onSearch` (debounced), navigate in `onSubmit`; keep the query in the address.\",\n \"Show the shortcut only for the site's main search: one ⌘K per page.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"⌘ K / Ctrl K\", action: \"Focus the field from anywhere (with `shortcut=\\\"k\\\"`).\" },\n { keys: \"Enter\", action: \"Submit the query, or pick the active suggestion.\" },\n { keys: \"Esc\", action: \"Close the suggestions, then clear the query.\" },\n { keys: \"Arrow Down / Arrow Up\", action: \"Move through the suggestions.\" },\n ],\n notes: [\n \"An `input type=\\\"search\\\"` named by `label`, inside a `search` landmark (`landmark={false}` inside a form that is one).\",\n \"With suggestions it follows the combobox pattern (`aria-activedescendant`, a `listbox`).\",\n \"The clear button is named by `clearLabel`; the shortcut's keys are decorative (`aria-hidden`).\",\n ],\n },\n related: [\"combobox\", \"command\", \"filter-bar\", \"kbd\"],\n web: {\n imports: [\"SearchField\"],\n examples: [\n { name: \"global\", title: \"Global search\", description: \"The site's search with its ⌘K shortcut, submitting to a results page.\" },\n { name: \"suggestions\", title: \"Recent searches\", description: \"Suggestions under the field, walked with the arrows.\" },\n { name: \"toolbar\", title: \"In a toolbar\", description: \"`size=\\\"sm\\\"`, filtering a list as you type.\" },\n ],\n },\n native: {\n imports: [\"SearchField\"],\n differences: [\n \"No shortcut and no suggestions: the keyboard's search key submits, and results are a screen of their own.\",\n \"`clearLabel` is required; `role=\\\"searchbox\\\"` names the input.\",\n \"`cancelLabel` with `onCancel` adds the platform's Cancel button beside the field.\",\n ],\n examples: [\n { name: \"default\", title: \"Default\", description: \"The glyph, the query, the clear button and the keyboard's search key.\" },\n { name: \"cancel\", title: \"With Cancel\", description: \"The iOS search bar: a Cancel button that clears and closes the keyboard.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
342
353
  },
343
354
  {
344
355
  "path": "search-field/search-field.tsx",