@krizaka/ui 3.18.2 → 3.20.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 (186) hide show
  1. package/dist/avatar/defaults.js +6 -1
  2. package/dist/avatar/defaults.js.map +1 -1
  3. package/dist/avatar.js +6 -1
  4. package/dist/avatar.js.map +1 -1
  5. package/dist/blocks.css +11 -0
  6. package/dist/card-grid.d.ts +14 -1
  7. package/dist/card-grid.js +29 -6
  8. package/dist/card-grid.js.map +1 -1
  9. package/dist/chat.d.ts +51 -3
  10. package/dist/chat.js +64 -32
  11. package/dist/chat.js.map +1 -1
  12. package/dist/{chunk-276PTQMQ.js → chunk-3BC4XQ6C.js} +2 -1
  13. package/dist/{chunk-YIBKJ36L.js → chunk-KB7OLYIJ.js} +7 -2
  14. package/dist/{chunk-YIBKJ36L.js.map → chunk-KB7OLYIJ.js.map} +1 -1
  15. package/dist/data-table.js +1 -1
  16. package/dist/file-upload.js +2 -2
  17. package/dist/file-upload.js.map +1 -1
  18. package/dist/match-dialog.js +2 -2
  19. package/dist/media-grid.d.ts +66 -3
  20. package/dist/media-grid.js +312 -65
  21. package/dist/media-grid.js.map +1 -1
  22. package/dist/native.js +6 -1
  23. package/dist/native.js.map +1 -1
  24. package/dist/progress.js +2 -2
  25. package/dist/progress.js.map +1 -1
  26. package/dist/vertical-video.d.ts +7 -1
  27. package/dist/vertical-video.js +7 -5
  28. package/dist/vertical-video.js.map +1 -1
  29. package/package.json +8 -8
  30. package/registry/achievement.json +16 -2
  31. package/registry/action-bar.json +16 -2
  32. package/registry/action-sheet.json +15 -0
  33. package/registry/activity-feed.json +13 -2
  34. package/registry/alert.json +15 -2
  35. package/registry/app-shell.json +15 -2
  36. package/registry/argument-card.json +16 -2
  37. package/registry/audio-player.json +15 -2
  38. package/registry/auth-layout.json +15 -2
  39. package/registry/avatar.json +14 -3
  40. package/registry/badge.json +13 -2
  41. package/registry/button.json +13 -2
  42. package/registry/card-grid.json +69 -4
  43. package/registry/card.json +13 -2
  44. package/registry/catalogue-page.json +15 -2
  45. package/registry/changelog-page.json +15 -2
  46. package/registry/chat.json +115 -10
  47. package/registry/checkbox.json +13 -2
  48. package/registry/chip-link.json +15 -2
  49. package/registry/chip.json +13 -2
  50. package/registry/choice-grid.json +16 -2
  51. package/registry/cn.json +12 -0
  52. package/registry/code-block.json +15 -2
  53. package/registry/combobox.json +13 -2
  54. package/registry/command.json +15 -2
  55. package/registry/component-page.json +15 -2
  56. package/registry/confirm-button.json +15 -2
  57. package/registry/contact-page.json +15 -2
  58. package/registry/continue-prompt.json +16 -2
  59. package/registry/countdown.json +13 -2
  60. package/registry/cta-panel.json +16 -2
  61. package/registry/dashboard-layout.json +15 -2
  62. package/registry/data-table.json +15 -2
  63. package/registry/date-picker.json +13 -2
  64. package/registry/decision-list.json +16 -2
  65. package/registry/description-list.json +13 -2
  66. package/registry/detail-layout.json +15 -2
  67. package/registry/dialog.json +13 -2
  68. package/registry/docs-article-page.json +15 -2
  69. package/registry/docs-hub-page.json +15 -2
  70. package/registry/docs-layout.json +15 -2
  71. package/registry/document-viewer.json +15 -2
  72. package/registry/dropdown-menu.json +15 -2
  73. package/registry/empty-state.json +13 -2
  74. package/registry/error-page.json +14 -2
  75. package/registry/examples/card-grid/foundation.d.ts +4 -0
  76. package/registry/examples/card-grid/foundation.js +38 -0
  77. package/registry/examples/card-grid/foundation.tsx +35 -0
  78. package/registry/examples/chat/conversation.js +2 -2
  79. package/registry/examples/chat/conversation.tsx +4 -3
  80. package/registry/examples/chat/pill-composer.d.ts +4 -0
  81. package/registry/examples/chat/pill-composer.js +34 -0
  82. package/registry/examples/chat/pill-composer.tsx +39 -0
  83. package/registry/examples/glow-card/plans.js +9 -12
  84. package/registry/examples/glow-card/plans.tsx +18 -14
  85. package/registry/examples/media-grid/loading.d.ts +4 -0
  86. package/registry/examples/media-grid/loading.js +28 -0
  87. package/registry/examples/media-grid/loading.tsx +25 -0
  88. package/registry/examples/vertical-video/rail.js +3 -2
  89. package/registry/examples/vertical-video/rail.tsx +4 -3
  90. package/registry/fab.json +15 -0
  91. package/registry/field.json +13 -2
  92. package/registry/file-upload.json +15 -2
  93. package/registry/filter-bar.json +15 -2
  94. package/registry/form.json +13 -2
  95. package/registry/gallery-page.json +15 -2
  96. package/registry/glow-card.json +18 -4
  97. package/registry/goal-gauge.json +13 -2
  98. package/registry/header.json +15 -0
  99. package/registry/how-it-works-page.json +15 -2
  100. package/registry/image-cropper.json +15 -2
  101. package/registry/image-viewer.json +15 -0
  102. package/registry/index.json +1773 -10
  103. package/registry/iso.json +15 -2
  104. package/registry/kanban.json +15 -2
  105. package/registry/kbd.json +14 -2
  106. package/registry/landing-page.json +15 -2
  107. package/registry/landscape.json +15 -2
  108. package/registry/legal-page.json +15 -2
  109. package/registry/link-card.json +16 -2
  110. package/registry/list.json +13 -2
  111. package/registry/loader.json +13 -2
  112. package/registry/longform-page.json +15 -2
  113. package/registry/map.json +14 -3
  114. package/registry/markdown.json +13 -2
  115. package/registry/marketing-layout.json +15 -2
  116. package/registry/marquee.json +15 -2
  117. package/registry/mascot-quote.json +16 -2
  118. package/registry/mascot-state.json +13 -2
  119. package/registry/mascots.json +13 -2
  120. package/registry/match-dialog.json +13 -2
  121. package/registry/media-capture.json +13 -2
  122. package/registry/media-grid.json +341 -13
  123. package/registry/media-picker.json +15 -0
  124. package/registry/meter.json +13 -2
  125. package/registry/nav-bar.json +17 -3
  126. package/registry/number-ticker.json +13 -2
  127. package/registry/onboarding.json +15 -0
  128. package/registry/otp-input.json +15 -0
  129. package/registry/page-header.json +15 -2
  130. package/registry/page-hero.json +16 -2
  131. package/registry/page.json +16 -2
  132. package/registry/pagination.json +13 -2
  133. package/registry/permission-prompt.json +15 -0
  134. package/registry/photo-grid.json +13 -2
  135. package/registry/pointer-light.json +15 -2
  136. package/registry/popover.json +15 -2
  137. package/registry/pricing-page.json +15 -2
  138. package/registry/product-page.json +15 -2
  139. package/registry/profile-card.json +13 -2
  140. package/registry/progress.json +14 -3
  141. package/registry/promise-card.json +16 -2
  142. package/registry/pull-to-refresh.json +15 -0
  143. package/registry/radio-group.json +13 -2
  144. package/registry/recipes.json +14 -2
  145. package/registry/screen.json +15 -0
  146. package/registry/scroll-stage.json +15 -2
  147. package/registry/search-field.json +13 -2
  148. package/registry/section-backdrop.json +15 -2
  149. package/registry/segmented.json +15 -0
  150. package/registry/separator.json +15 -2
  151. package/registry/settings-layout.json +15 -2
  152. package/registry/showcase.json +13 -2
  153. package/registry/site-footer.json +16 -3
  154. package/registry/sites.json +15 -2
  155. package/registry/skeleton.json +13 -2
  156. package/registry/skip-link.json +14 -2
  157. package/registry/slider.json +13 -2
  158. package/registry/slot.json +14 -2
  159. package/registry/spinner.json +13 -2
  160. package/registry/splash-handoff.json +15 -0
  161. package/registry/split-view.json +15 -2
  162. package/registry/stage-banner.json +15 -2
  163. package/registry/stat.json +15 -2
  164. package/registry/status-page.json +15 -2
  165. package/registry/step-progress.json +13 -2
  166. package/registry/stepper.json +13 -2
  167. package/registry/swipe-deck.json +13 -2
  168. package/registry/swipeable-row.json +15 -0
  169. package/registry/switch.json +13 -2
  170. package/registry/tab-bar.json +15 -0
  171. package/registry/tabs.json +13 -2
  172. package/registry/tag-input.json +15 -2
  173. package/registry/tag-selector.json +13 -2
  174. package/registry/terminal-replay.json +15 -2
  175. package/registry/theme.json +13 -2
  176. package/registry/toast.json +13 -2
  177. package/registry/toc.json +15 -2
  178. package/registry/tooltip.json +14 -2
  179. package/registry/tree-view.json +15 -2
  180. package/registry/txt.json +13 -0
  181. package/registry/verified-badge.json +13 -2
  182. package/registry/vertical-video.json +32 -6
  183. package/registry/video-player.json +13 -2
  184. package/registry/video-trimmer.json +15 -2
  185. package/registry/virtual.json +15 -2
  186. /package/dist/{chunk-276PTQMQ.js.map → chunk-3BC4XQ6C.js.map} +0 -0
@@ -8,6 +8,20 @@
8
8
  "deprecated": null,
9
9
  "category": "blocks",
10
10
  "platforms": "web",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "n/a",
17
+ "reason": "A block of a public site's page; an app screen on a phone is built from Screen, Header and Card.",
18
+ "use": "card"
19
+ },
20
+ "desktop": {
21
+ "state": "n/a",
22
+ "reason": "A public site's page, not a window of the Mac app."
23
+ }
24
+ },
11
25
  "whenToUse": [
12
26
  "Entry points of a hub (`LinkCard`), the reasons of a product page (`ArgumentCard`), plans, products.",
13
27
  "Any set of peer cards that should read as one group with one rhythm."
@@ -27,7 +41,8 @@
27
41
  "`fit=\"fit\"` for two or three cards that should share the whole width; `fill` keeps a lone card at its size.",
28
42
  "Keep `reveal` on below the first screen, off on it; `columns` makes the stagger restart on each row.",
29
43
  "The default `align=\"stretch\"` gives a row one height; the cards pin their foot to the bottom (`LinkCard` facts + way in, `ArgumentCard` actions) so the footers line up. `align=\"start\"` for cards that unfold.",
30
- "`as=\"ul\"` when the cards are a list of links."
44
+ "`as=\"ul\"` when the cards are a list of links.",
45
+ "`foundation` when one thing (an engine, a platform) carries all the others: it takes its own card, wide, above (`foundationPosition=\"end\"` for under), and the cards sit in a hairline frame joined to it. Say the relation with `foundationLabel`; set the hue on `className`."
31
46
  ],
32
47
  "accessibility": {
33
48
  "keyboard": [],
@@ -85,6 +100,27 @@
85
100
  "description": "`fill` keeps empty tracks (a lone card keeps its width); `fit` stretches the cards of a short row across the width.",
86
101
  "required": false
87
102
  },
103
+ {
104
+ "name": "foundation",
105
+ "type": "ReactNode",
106
+ "default": null,
107
+ "description": "A foundation: one wide card that the cards of the grid stand on (an engine, a platform, a shared service). It spans\nthe full width beside the grid, and a hairline joins it to a frame around the cards — \"everything inside runs on it\".\nThe frame takes the hue of `className` (a `data-hue` or a brand class on the grid sets it for the whole block).",
108
+ "required": false
109
+ },
110
+ {
111
+ "name": "foundationLabel",
112
+ "type": "ReactNode",
113
+ "default": null,
114
+ "description": "A short mono label on the frame's edge, naming the relation (`Powered by Orazaka`). Only with `foundation`.",
115
+ "required": false
116
+ },
117
+ {
118
+ "name": "foundationPosition",
119
+ "type": "\"start\" | \"end\"",
120
+ "default": "start",
121
+ "description": "Where the foundation sits: above the cards, or under them.",
122
+ "required": false
123
+ },
88
124
  {
89
125
  "name": "light",
90
126
  "type": "boolean",
@@ -126,6 +162,14 @@
126
162
  "module": "@krizaka/ui/registry/examples/card-grid/aligned",
127
163
  "code": "import { ProductLogo } from \"@krizaka/ui\";\nimport { CardGrid } from \"@krizaka/ui/card-grid\";\nimport { LinkCard } from \"@krizaka/ui/link-card\";\nimport { Facts } from \"@krizaka/ui/page\";\n\n// A row of cards whose sentences have different lengths: the cells share one height and each card pins its facts\n// and its way in to the bottom, so the status and the link of every card sit on the same line.\nexport default function CardGridAligned() {\n return (\n <CardGrid className=\"w-full max-w-4xl\" min={220} columns={3} reveal={false}>\n <LinkCard layout=\"stack\" href=\"#orazaka\" icon={<ProductLogo id=\"orazaka\" size={28} animated={false} />} title=\"Orazaka\" description=\"Sovereign AI on your own servers: chat, image, video and voice.\" extra={<Facts items={[{ label: \"Dev preview\", tone: \"ok\" }]} />} cta=\"Open orazaka\" />\n <LinkCard layout=\"stack\" href=\"#orochia\" icon={<ProductLogo id=\"orochia\" size={28} animated={false} />} title=\"Orochia\" description=\"Creators, paid.\" extra={<Facts items={[{ label: \"Coming soon\", tone: \"wip\" }]} />} cta=\"Open orochia\" />\n <LinkCard layout=\"stack\" href=\"#yororis\" icon={<ProductLogo id=\"yororis\" size={28} animated={false} />} title=\"Yororis\" description=\"A quiet place to keep what matters to you, built around the moments of a day and the threads between them.\" extra={<Facts items={[{ label: \"Under construction\", tone: \"wip\" }]} />} cta=\"Open yororis\" />\n </CardGrid>\n );\n}\n"
128
164
  },
165
+ {
166
+ "name": "foundation",
167
+ "title": "A foundation",
168
+ "description": "`foundation`: one wide card the others stand on, a hairline to a frame around them.",
169
+ "path": "examples/card-grid/foundation.tsx",
170
+ "module": "@krizaka/ui/registry/examples/card-grid/foundation",
171
+ "code": "import { ProductLogo } from \"@krizaka/ui\";\nimport { ArgumentCard } from \"@krizaka/ui/argument-card\";\nimport { CardGrid } from \"@krizaka/ui/card-grid\";\nimport { LinkCard } from \"@krizaka/ui/link-card\";\n\nconst products = [\n { id: \"orochia\", name: \"Orochia\", hook: \"Recommendations, search and captions.\" },\n { id: \"yomizaka\", name: \"Yomizaka\", hook: \"Document checks and signed reports.\" },\n { id: \"kachizaka\", name: \"Kachizaka\", hook: \"Live match feeds.\" },\n] as const;\n\n// `foundation`: one wide card that the cards below stand on, a hairline stem to a frame around them. One column on a phone.\nexport default function CardGridFoundation() {\n return (\n <CardGrid\n className=\"w-full max-w-5xl\"\n min={240}\n reveal={false}\n foundationLabel=\"Powered by Orazaka\"\n foundation={\n <LinkCard\n href=\"#orazaka\"\n icon={<ProductLogo id=\"orazaka\" size={28} animated={false} />}\n title=\"The engine\"\n description=\"Every product's AI runs on Orazaka: one pipeline, your models, your network.\"\n cta=\"Open the engine\"\n />\n }\n >\n {products.map((p) => (\n <ArgumentCard key={p.id} id={`product-${p.id}`} icon={<ProductLogo id={p.id} size={32} animated={false} />} title={p.name} hook={{ label: \"Uses it for\", text: p.hook }} more={{ href: `#${p.id}`, label: \"Details\" }} />\n ))}\n </CardGrid>\n );\n}\n"
172
+ },
129
173
  {
130
174
  "name": "reveal",
131
175
  "title": "A list of sections",
@@ -140,7 +184,7 @@
140
184
  "files": [
141
185
  {
142
186
  "path": "card-grid/card-grid.tsx",
143
- "content": "// Client (the pointer's light). A responsive grid of cards (blocks.css): one column on a phone, the cells of a row\n// share their height, each card rises once as it enters the viewport — staggered by column — and the pointer carries a\n// light over the card it is on.\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { POINTER_LIGHT_TARGET, usePointerLight } from \"../pointer-light/pointer-light\";\n\nconst STEP_MS = 70;\n\nexport type CardGridProps = Omit<React.HTMLAttributes<HTMLElement>, \"children\"> & {\n /** The cards: one cell each. */\n children: React.ReactNode;\n /**\n * The narrowest a column may be, in pixels: the grid fits as many columns as the width allows.\n * @default 300\n */\n min?: number;\n /**\n * `fill` keeps empty tracks (a lone card keeps its width); `fit` stretches the cards of a short row across the width.\n * @default \"fill\"\n */\n fit?: \"fill\" | \"fit\";\n /**\n * `stretch` gives the cells of a row one height; `start` lets each card keep its own (cards that open a panel).\n * @default \"stretch\"\n */\n align?: \"stretch\" | \"start\";\n /**\n * The cards rise once into place (`[data-reveal]`, MotionObserver), staggered by column. `false` on the first screen.\n * @default true\n */\n reveal?: boolean;\n /**\n * The columns the stagger counts (the delay restarts on every row).\n * @default 3\n */\n columns?: number;\n /**\n * The light that follows the pointer over the cards (`[data-kz-light]`).\n * @default true\n */\n light?: boolean;\n /** `ul` when the cards are a list (each cell an `li`). */\n as?: \"div\" | \"ul\" | \"ol\";\n};\n\n/** A responsive grid of cards: equal rows, a staggered reveal, the pointer's light. */\nexport function CardGrid({ children, min = 300, fit = \"fill\", align = \"stretch\", reveal = true, columns = 3, light = true, as: Tag = \"div\", className, style, ...props }: CardGridProps) {\n const ref = React.useRef<HTMLElement>(null);\n usePointerLight(light ? ref : NO_REF, POINTER_LIGHT_TARGET);\n const Cell = Tag === \"div\" ? \"div\" : \"li\";\n return (\n <Tag\n ref={ref as React.Ref<never>}\n className={cn(\"kz-cards\", className)}\n data-fit={fit === \"fit\" ? \"\" : undefined}\n data-align={align === \"start\" ? \"start\" : undefined}\n style={{ \"--kz-cards-min\": `${min}px`, ...style } as React.CSSProperties}\n {...props}\n >\n {React.Children.toArray(children).map((child, i) => {\n const step = Math.min(i % Math.max(columns, 1), 5);\n return (\n <Cell\n key={React.isValidElement(child) && child.key != null ? child.key : i}\n className=\"kz-cards-cell\"\n data-reveal={reveal ? \"\" : undefined}\n style={reveal && step ? ({ \"--kz-delay\": `${step * STEP_MS}ms` } as React.CSSProperties) : undefined}\n >\n {child}\n </Cell>\n );\n })}\n </Tag>\n );\n}\n\nconst NO_REF: React.RefObject<HTMLElement | null> = { current: null };\n"
187
+ "content": "// Client (the pointer's light). A responsive grid of cards (blocks.css): one column on a phone, the cells of a row\n// share their height, each card rises once as it enters the viewport — staggered by column — and the pointer carries a\n// light over the card it is on.\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { POINTER_LIGHT_TARGET, usePointerLight } from \"../pointer-light/pointer-light\";\n\nconst STEP_MS = 70;\n\nexport type CardGridProps = Omit<React.HTMLAttributes<HTMLElement>, \"children\"> & {\n /** The cards: one cell each. */\n children: React.ReactNode;\n /**\n * The narrowest a column may be, in pixels: the grid fits as many columns as the width allows.\n * @default 300\n */\n min?: number;\n /**\n * `fill` keeps empty tracks (a lone card keeps its width); `fit` stretches the cards of a short row across the width.\n * @default \"fill\"\n */\n fit?: \"fill\" | \"fit\";\n /**\n * `stretch` gives the cells of a row one height; `start` lets each card keep its own (cards that open a panel).\n * @default \"stretch\"\n */\n align?: \"stretch\" | \"start\";\n /**\n * The cards rise once into place (`[data-reveal]`, MotionObserver), staggered by column. `false` on the first screen.\n * @default true\n */\n reveal?: boolean;\n /**\n * The columns the stagger counts (the delay restarts on every row).\n * @default 3\n */\n columns?: number;\n /**\n * The light that follows the pointer over the cards (`[data-kz-light]`).\n * @default true\n */\n light?: boolean;\n /** `ul` when the cards are a list (each cell an `li`). */\n as?: \"div\" | \"ul\" | \"ol\";\n /**\n * A foundation: one wide card that the cards of the grid stand on (an engine, a platform, a shared service). It spans\n * the full width beside the grid, and a hairline joins it to a frame around the cards — \"everything inside runs on it\".\n * The frame takes the hue of `className` (a `data-hue` or a brand class on the grid sets it for the whole block).\n */\n foundation?: React.ReactNode;\n /**\n * Where the foundation sits: above the cards, or under them.\n * @default \"start\"\n */\n foundationPosition?: \"start\" | \"end\";\n /** A short mono label on the frame's edge, naming the relation (`Powered by Orazaka`). Only with `foundation`. */\n foundationLabel?: React.ReactNode;\n};\n\n/** A responsive grid of cards: equal rows, a staggered reveal, the pointer's light. */\nexport function CardGrid({\n children,\n min = 300,\n fit = \"fill\",\n align = \"stretch\",\n reveal = true,\n columns = 3,\n light = true,\n as: Tag = \"div\",\n foundation,\n foundationPosition = \"start\",\n foundationLabel,\n className,\n style,\n ...props\n}: CardGridProps) {\n const ref = React.useRef<HTMLElement>(null);\n usePointerLight(light ? ref : NO_REF, POINTER_LIGHT_TARGET);\n const Cell = Tag === \"div\" ? \"div\" : \"li\";\n const grid = (\n <Tag\n ref={foundation ? undefined : (ref as React.Ref<never>)}\n className={cn(\"kz-cards\", !foundation && className)}\n data-fit={fit === \"fit\" ? \"\" : undefined}\n data-align={align === \"start\" ? \"start\" : undefined}\n style={{ \"--kz-cards-min\": `${min}px`, ...(foundation ? null : style) } as React.CSSProperties}\n {...props}\n >\n {React.Children.toArray(children).map((child, i) => {\n const step = Math.min(i % Math.max(columns, 1), 5);\n return (\n <Cell\n key={React.isValidElement(child) && child.key != null ? child.key : i}\n className=\"kz-cards-cell\"\n data-reveal={reveal ? \"\" : undefined}\n style={reveal && step ? ({ \"--kz-delay\": `${step * STEP_MS}ms` } as React.CSSProperties) : undefined}\n >\n {child}\n </Cell>\n );\n })}\n </Tag>\n );\n if (!foundation) return grid;\n return (\n <div ref={ref as React.Ref<HTMLDivElement>} className={cn(\"kz-foundation\", className)} data-position={foundationPosition} style={style}>\n <div className=\"kz-foundation-base\" data-reveal={reveal ? \"\" : undefined}>\n {foundation}\n </div>\n <div className=\"kz-foundation-group\">\n {foundationLabel && <span className=\"kz-foundation-label\">{foundationLabel}</span>}\n {grid}\n </div>\n </div>\n );\n}\n\nconst NO_REF: React.RefObject<HTMLElement | null> = { current: null };\n"
144
188
  },
145
189
  {
146
190
  "path": "card-grid/index.ts",
@@ -148,11 +192,11 @@
148
192
  },
149
193
  {
150
194
  "path": "card-grid/meta.ts",
151
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Card grid\",\n summary:\n \"A responsive grid of cards: as many columns as the width allows (one on a phone), the cells of a row at one height, a staggered reveal and the light that follows the pointer.\",\n status: \"beta\",\n category: \"blocks\",\n platforms: \"web\",\n whenToUse: [\n \"Entry points of a hub (`LinkCard`), the reasons of a product page (`ArgumentCard`), plans, products.\",\n \"Any set of peer cards that should read as one group with one rhythm.\",\n ],\n whenNotToUse: [\n { when: \"For a sequence the reader follows in order.\", use: \"stepper\" },\n { when: \"For rows of data to compare or sort.\", use: \"data-table\" },\n ],\n bestPractices: [\n \"Set `min` to the narrowest a card still reads well; the grid does the rest at every width.\",\n \"`fit=\\\"fit\\\"` for two or three cards that should share the whole width; `fill` keeps a lone card at its size.\",\n \"Keep `reveal` on below the first screen, off on it; `columns` makes the stagger restart on each row.\",\n \"The default `align=\\\"stretch\\\"` gives a row one height; the cards pin their foot to the bottom (`LinkCard` facts + way in, `ArgumentCard` actions) so the footers line up. `align=\\\"start\\\"` for cards that unfold.\",\n \"`as=\\\"ul\\\"` when the cards are a list of links.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"The grid adds no role; `as=\\\"ul\\\"` makes it a list (each cell an `li`).\",\n \"The pointer's light is decorative and never runs on touch screens or under `prefers-reduced-motion`.\",\n \"Revealed cells are only hidden visually until they enter the viewport; under reduced motion they never move.\",\n ],\n },\n related: [\"link-card\", \"argument-card\", \"pointer-light\"],\n web: {\n imports: [\"CardGrid\"],\n examples: [\n { name: \"products\", title: \"Two halves\", description: \"`fit=\\\"fit\\\"`: two cards share the whole width.\" },\n { name: \"aligned\", title: \"An aligned row\", description: \"Cards with sentences of different lengths: one height, and the facts and the way in of every card on the same line.\" },\n { name: \"reveal\", title: \"A list of sections\", description: \"`as=\\\"ul\\\"`, six links that rise staggered by column on a real page.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
195
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Card grid\",\n summary:\n \"A responsive grid of cards: as many columns as the width allows (one on a phone), the cells of a row at one height, a staggered reveal and the light that follows the pointer.\",\n status: \"beta\",\n category: \"blocks\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"A block of a public site's page; an app screen on a phone is built from Screen, Header and Card.\", use: \"card\" }, desktop: { status: \"n/a\", reason: \"A public site's page, not a window of the Mac app.\" } },\n whenToUse: [\n \"Entry points of a hub (`LinkCard`), the reasons of a product page (`ArgumentCard`), plans, products.\",\n \"Any set of peer cards that should read as one group with one rhythm.\",\n ],\n whenNotToUse: [\n { when: \"For a sequence the reader follows in order.\", use: \"stepper\" },\n { when: \"For rows of data to compare or sort.\", use: \"data-table\" },\n ],\n bestPractices: [\n \"Set `min` to the narrowest a card still reads well; the grid does the rest at every width.\",\n \"`fit=\\\"fit\\\"` for two or three cards that should share the whole width; `fill` keeps a lone card at its size.\",\n \"Keep `reveal` on below the first screen, off on it; `columns` makes the stagger restart on each row.\",\n \"The default `align=\\\"stretch\\\"` gives a row one height; the cards pin their foot to the bottom (`LinkCard` facts + way in, `ArgumentCard` actions) so the footers line up. `align=\\\"start\\\"` for cards that unfold.\",\n \"`as=\\\"ul\\\"` when the cards are a list of links.\",\n \"`foundation` when one thing (an engine, a platform) carries all the others: it takes its own card, wide, above (`foundationPosition=\\\"end\\\"` for under), and the cards sit in a hairline frame joined to it. Say the relation with `foundationLabel`; set the hue on `className`.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"The grid adds no role; `as=\\\"ul\\\"` makes it a list (each cell an `li`).\",\n \"The pointer's light is decorative and never runs on touch screens or under `prefers-reduced-motion`.\",\n \"Revealed cells are only hidden visually until they enter the viewport; under reduced motion they never move.\",\n ],\n },\n related: [\"link-card\", \"argument-card\", \"pointer-light\"],\n web: {\n imports: [\"CardGrid\"],\n examples: [\n { name: \"products\", title: \"Two halves\", description: \"`fit=\\\"fit\\\"`: two cards share the whole width.\" },\n { name: \"aligned\", title: \"An aligned row\", description: \"Cards with sentences of different lengths: one height, and the facts and the way in of every card on the same line.\" },\n { name: \"foundation\", title: \"A foundation\", description: \"`foundation`: one wide card the others stand on, a hairline to a frame around them.\" },\n { name: \"reveal\", title: \"A list of sections\", description: \"`as=\\\"ul\\\"`, six links that rise staggered by column on a real page.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
152
196
  },
153
197
  {
154
198
  "path": "meta.ts",
155
- "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"
199
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
156
200
  }
157
201
  ],
158
202
  "dependencies": [],
@@ -204,6 +248,27 @@
204
248
  "description": "`fill` keeps empty tracks (a lone card keeps its width); `fit` stretches the cards of a short row across the width.",
205
249
  "required": false
206
250
  },
251
+ {
252
+ "name": "foundation",
253
+ "type": "ReactNode",
254
+ "default": null,
255
+ "description": "A foundation: one wide card that the cards of the grid stand on (an engine, a platform, a shared service). It spans\nthe full width beside the grid, and a hairline joins it to a frame around the cards — \"everything inside runs on it\".\nThe frame takes the hue of `className` (a `data-hue` or a brand class on the grid sets it for the whole block).",
256
+ "required": false
257
+ },
258
+ {
259
+ "name": "foundationLabel",
260
+ "type": "ReactNode",
261
+ "default": null,
262
+ "description": "A short mono label on the frame's edge, naming the relation (`Powered by Orazaka`). Only with `foundation`.",
263
+ "required": false
264
+ },
265
+ {
266
+ "name": "foundationPosition",
267
+ "type": "\"start\" | \"end\"",
268
+ "default": "start",
269
+ "description": "Where the foundation sits: above the cards, or under them.",
270
+ "required": false
271
+ },
207
272
  {
208
273
  "name": "light",
209
274
  "type": "boolean",
@@ -8,6 +8,17 @@
8
8
  "deprecated": null,
9
9
  "category": "layout",
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
  "To present one item of a collection: a video, a creator, a guide, a plan.",
13
24
  "As a link to the item (`asChild` + `interactive`): the whole card is one target.",
@@ -422,7 +433,7 @@
422
433
  },
423
434
  {
424
435
  "path": "card/meta.ts",
425
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Card\",\n summary: \"The composed chassis of every card: a media with overlays, a body with a title, a description, stats and a footer.\",\n status: \"stable\",\n category: \"layout\",\n platforms: \"both\",\n whenToUse: [\n \"To present one item of a collection: a video, a creator, a guide, a plan.\",\n \"As a link to the item (`asChild` + `interactive`): the whole card is one target.\",\n \"To group a few figures under a title (`Card.Stat`).\",\n ],\n whenNotToUse: [\n { when: \"To frame a whole page section: use the page layout, not a card around everything.\" },\n { when: \"For a single key figure without a frame.\", use: \"stat\" },\n { when: \"For a message about the page.\", use: \"alert\" },\n ],\n bestPractices: [\n \"Build product cards from these parts and override any of them with `className` — never fork the card.\",\n \"Keep one primary target per card: either the whole card is a link, or it holds buttons — not both.\",\n \"Give `Card.Media` a fixed `aspect` so a grid stays aligned while images load.\",\n \"`Card.Image` loads lazily; pass `loading=\\\"eager\\\"` (and `fetchPriority=\\\"high\\\"`) to the first cards of a page, above the fold.\",\n \"`reveal={index}` with one `MotionObserver` per page for the entrance; it is shown at once under reduced motion.\",\n ],\n accessibility: {\n keyboard: [{ keys: \"Tab / Enter\", action: \"An `asChild` link card is one focusable link that Enter follows.\" }],\n notes: [\n \"`Card.Title` is a heading (`h3` by default, `as` to change the level) so cards are reachable by heading.\",\n \"A card that is a link is named by its content: keep the title first.\",\n \"The image fallback is decorative and hidden from assistive technology.\",\n ],\n },\n related: [\"badge\", \"stat\", \"avatar\", \"skeleton\"],\n web: {\n imports: [\"Card\"],\n examples: [\n { name: \"media-card\", title: \"Media card\", description: \"An image, badges on the media, a title, a description and a footer.\" },\n { name: \"media-fallback\", title: \"Media fallback\", description: \"No image: the `fallback` on the accent gradient.\" },\n { name: \"interactive-link\", title: \"Interactive link\", description: \"`asChild` + `<a>`: the whole card is a link, with the spotlight and the lift.\" },\n { name: \"stat-card\", title: \"Stat card\", description: \"Figures put forward with `Card.Stat`, `tone=\\\"elevated\\\"`.\" },\n { name: \"reveal-grid\", title: \"Reveal grid\", description: \"Cards entering in cascade with `reveal` and `MotionObserver`.\" },\n ],\n },\n native: {\n imports: [\"Card\"],\n differences: [\n \"`onPress` on `Card.Root` makes the whole card a button (no `asChild`, no `interactive`).\",\n \"Built on `View`, `Image` and `Text`: style with `style`, not `className`.\",\n \"No `Card.Stat` and no `reveal`.\",\n ],\n examples: [\n { name: \"default\", title: \"Default\", description: \"A media, a body and a footer.\" },\n { name: \"elevated\", title: \"Elevated\", description: \"`tone=\\\"elevated\\\"`: one step higher, with a shadow.\" },\n { name: \"pressable\", title: \"Pressable\", description: \"`onPress`: the whole card is a button.\" },\n { name: \"body\", title: \"Text and an action\", description: \"A body only, with a button.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
436
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Card\",\n summary: \"The composed chassis of every card: a media with overlays, a body with a title, a description, stats and a footer.\",\n status: \"stable\",\n category: \"layout\",\n platforms: \"both\",\n parity: { desktop: \"web\" },\n whenToUse: [\n \"To present one item of a collection: a video, a creator, a guide, a plan.\",\n \"As a link to the item (`asChild` + `interactive`): the whole card is one target.\",\n \"To group a few figures under a title (`Card.Stat`).\",\n ],\n whenNotToUse: [\n { when: \"To frame a whole page section: use the page layout, not a card around everything.\" },\n { when: \"For a single key figure without a frame.\", use: \"stat\" },\n { when: \"For a message about the page.\", use: \"alert\" },\n ],\n bestPractices: [\n \"Build product cards from these parts and override any of them with `className` — never fork the card.\",\n \"Keep one primary target per card: either the whole card is a link, or it holds buttons — not both.\",\n \"Give `Card.Media` a fixed `aspect` so a grid stays aligned while images load.\",\n \"`Card.Image` loads lazily; pass `loading=\\\"eager\\\"` (and `fetchPriority=\\\"high\\\"`) to the first cards of a page, above the fold.\",\n \"`reveal={index}` with one `MotionObserver` per page for the entrance; it is shown at once under reduced motion.\",\n ],\n accessibility: {\n keyboard: [{ keys: \"Tab / Enter\", action: \"An `asChild` link card is one focusable link that Enter follows.\" }],\n notes: [\n \"`Card.Title` is a heading (`h3` by default, `as` to change the level) so cards are reachable by heading.\",\n \"A card that is a link is named by its content: keep the title first.\",\n \"The image fallback is decorative and hidden from assistive technology.\",\n ],\n },\n related: [\"badge\", \"stat\", \"avatar\", \"skeleton\"],\n web: {\n imports: [\"Card\"],\n examples: [\n { name: \"media-card\", title: \"Media card\", description: \"An image, badges on the media, a title, a description and a footer.\" },\n { name: \"media-fallback\", title: \"Media fallback\", description: \"No image: the `fallback` on the accent gradient.\" },\n { name: \"interactive-link\", title: \"Interactive link\", description: \"`asChild` + `<a>`: the whole card is a link, with the spotlight and the lift.\" },\n { name: \"stat-card\", title: \"Stat card\", description: \"Figures put forward with `Card.Stat`, `tone=\\\"elevated\\\"`.\" },\n { name: \"reveal-grid\", title: \"Reveal grid\", description: \"Cards entering in cascade with `reveal` and `MotionObserver`.\" },\n ],\n },\n native: {\n imports: [\"Card\"],\n differences: [\n \"`onPress` on `Card.Root` makes the whole card a button (no `asChild`, no `interactive`).\",\n \"Built on `View`, `Image` and `Text`: style with `style`, not `className`.\",\n \"No `Card.Stat` and no `reveal`.\",\n ],\n examples: [\n { name: \"default\", title: \"Default\", description: \"A media, a body and a footer.\" },\n { name: \"elevated\", title: \"Elevated\", description: \"`tone=\\\"elevated\\\"`: one step higher, with a shadow.\" },\n { name: \"pressable\", title: \"Pressable\", description: \"`onPress`: the whole card is a button.\" },\n { name: \"body\", title: \"Text and an action\", description: \"A body only, with a button.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
426
437
  },
427
438
  {
428
439
  "path": "card/recipe.ts",
@@ -430,7 +441,7 @@
430
441
  },
431
442
  {
432
443
  "path": "meta.ts",
433
- "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"
444
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
434
445
  }
435
446
  ],
436
447
  "dependencies": [
@@ -8,6 +8,19 @@
8
8
  "deprecated": null,
9
9
  "category": "templates",
10
10
  "platforms": "web",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "n/a",
17
+ "reason": "A whole page of a public site; a phone app's screens are built from Screen, Header and the native primitives."
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
  "Components, packs, use cases, repositories.",
13
26
  "Any index the reader narrows down."
@@ -153,11 +166,11 @@
153
166
  },
154
167
  {
155
168
  "path": "catalogue-page/meta.ts",
156
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Catalogue page\",\n summary: \"A catalogue or an index: the search in the hero, the filters in a sticky column beside the results (folded above them on a phone), the count, the results.\",\n status: \"beta\",\n category: \"templates\",\n platforms: \"web\",\n whenToUse: [\"Components, packs, use cases, repositories.\", \"Any index the reader narrows down.\"],\n whenNotToUse: [\n { when: \"For media to look at.\", use: \"gallery-page\" },\n { when: \"For rows to sort and select.\", use: \"data-table\" },\n ],\n bestPractices: [\"Filters as links: the address keeps them, back works.\", \"Say the count in words, politely announced.\", \"Words and content arrive as props; the motion is the platform's (`[data-reveal]`, still under reduced motion).\"],\n accessibility: {\n keyboard: [],\n notes: [\"The count is a polite live region; the filters a native disclosure on a phone.\", \"Results are a list of links.\"],\n },\n related: [\"filter-bar\", \"chip-link\", \"card-grid\"],\n web: {\n imports: [\"CataloguePage\"],\n examples: [\n { name: \"components\", title: \"Components\", description: \"Krizaka UI's components: search, platform filters, compact link cards.\", template: true, purpose: \"explain\", why: \"A reader looking for the right piece scans a filterable catalogue instead of reading every page.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
169
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Catalogue page\",\n summary: \"A catalogue or an index: the search in the hero, the filters in a sticky column beside the results (folded above them on a phone), the count, the results.\",\n status: \"beta\",\n category: \"templates\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"A whole page of a public site; a phone app's screens are built from Screen, Header and the native primitives.\" }, desktop: { status: \"n/a\", reason: \"A public site's page, not a window of the Mac app.\" } },\n whenToUse: [\"Components, packs, use cases, repositories.\", \"Any index the reader narrows down.\"],\n whenNotToUse: [\n { when: \"For media to look at.\", use: \"gallery-page\" },\n { when: \"For rows to sort and select.\", use: \"data-table\" },\n ],\n bestPractices: [\"Filters as links: the address keeps them, back works.\", \"Say the count in words, politely announced.\", \"Words and content arrive as props; the motion is the platform's (`[data-reveal]`, still under reduced motion).\"],\n accessibility: {\n keyboard: [],\n notes: [\"The count is a polite live region; the filters a native disclosure on a phone.\", \"Results are a list of links.\"],\n },\n related: [\"filter-bar\", \"chip-link\", \"card-grid\"],\n web: {\n imports: [\"CataloguePage\"],\n examples: [\n { name: \"components\", title: \"Components\", description: \"Krizaka UI's components: search, platform filters, compact link cards.\", template: true, purpose: \"explain\", why: \"A reader looking for the right piece scans a filterable catalogue instead of reading every page.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
157
170
  },
158
171
  {
159
172
  "path": "meta.ts",
160
- "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"
173
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
161
174
  }
162
175
  ],
163
176
  "dependencies": [],
@@ -8,6 +8,19 @@
8
8
  "deprecated": null,
9
9
  "category": "templates",
10
10
  "platforms": "web",
11
+ "parity": {
12
+ "web": {
13
+ "state": "yes"
14
+ },
15
+ "native": {
16
+ "state": "n/a",
17
+ "reason": "A whole page of a public site; a phone app's screens are built from Screen, Header and the native primitives."
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
  "Release notes, a product's changelog, a decisions log.",
13
26
  "Any history read newest first."
@@ -139,11 +152,11 @@
139
152
  },
140
153
  {
141
154
  "path": "changelog-page/meta.ts",
142
- "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Changelog page\",\n summary: \"What changed and why: each entry with its date and version sticking beside it, its changes by kind in words and colour, its reasons under it.\",\n status: \"beta\",\n category: \"templates\",\n platforms: \"web\",\n whenToUse: [\"Release notes, a product's changelog, a decisions log.\", \"Any history read newest first.\"],\n whenNotToUse: [\n { when: \"For the decisions of one product, compared.\", use: \"decision-list\" },\n { when: \"For where things stand now.\", use: \"status-page\" },\n ],\n bestPractices: [\"Newest first; one entry per release.\", \"Say the kind of each change (added, changed, fixed, removed) — the colour only repeats it.\", \"Words and content arrive as props; the motion is the platform's (`[data-reveal]`, still under reduced motion).\"],\n accessibility: {\n keyboard: [],\n notes: [\"The log is a named ordered list; each date a `time` with its ISO value.\", \"Kinds are words, not only colours.\"],\n },\n related: [\"decision-list\", \"status-page\", \"docs-article-page\"],\n web: {\n imports: [\"ChangelogPage\"],\n examples: [\n { name: \"releases\", title: \"Krizaka UI releases\", description: \"Two releases with their changes by kind.\", template: true, purpose: \"explain\", why: \"A user checks what changed in each release and whether it concerns them.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
155
+ "content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Changelog page\",\n summary: \"What changed and why: each entry with its date and version sticking beside it, its changes by kind in words and colour, its reasons under it.\",\n status: \"beta\",\n category: \"templates\",\n platforms: \"web\",\n parity: { native: { status: \"n/a\", reason: \"A whole page of a public site; a phone app's screens are built from Screen, Header and the native primitives.\" }, desktop: { status: \"n/a\", reason: \"A public site's page, not a window of the Mac app.\" } },\n whenToUse: [\"Release notes, a product's changelog, a decisions log.\", \"Any history read newest first.\"],\n whenNotToUse: [\n { when: \"For the decisions of one product, compared.\", use: \"decision-list\" },\n { when: \"For where things stand now.\", use: \"status-page\" },\n ],\n bestPractices: [\"Newest first; one entry per release.\", \"Say the kind of each change (added, changed, fixed, removed) — the colour only repeats it.\", \"Words and content arrive as props; the motion is the platform's (`[data-reveal]`, still under reduced motion).\"],\n accessibility: {\n keyboard: [],\n notes: [\"The log is a named ordered list; each date a `time` with its ISO value.\", \"Kinds are words, not only colours.\"],\n },\n related: [\"decision-list\", \"status-page\", \"docs-article-page\"],\n web: {\n imports: [\"ChangelogPage\"],\n examples: [\n { name: \"releases\", title: \"Krizaka UI releases\", description: \"Two releases with their changes by kind.\", template: true, purpose: \"explain\", why: \"A user checks what changed in each release and whether it concerns them.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
143
156
  },
144
157
  {
145
158
  "path": "meta.ts",
146
- "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"
159
+ "content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"activity-feed\"\n | \"action-sheet\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"document-viewer\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"map\"\n | \"markdown\"\n | \"longform-page\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"split-view\"\n | \"splash-handoff\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
147
160
  }
148
161
  ],
149
162
  "dependencies": [],