@krizaka/ui 3.19.0 → 3.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action-bar.js +4 -4
- package/dist/argument-card.js +1 -1
- package/dist/avatar/defaults.js +6 -1
- package/dist/avatar/defaults.js.map +1 -1
- package/dist/avatar.js +6 -1
- package/dist/avatar.js.map +1 -1
- package/dist/blocks.css +1 -1
- package/dist/button.js +3 -2
- package/dist/card.js +3 -3
- package/dist/chat.js +1 -1
- package/dist/{chunk-276PTQMQ.js → chunk-3BC4XQ6C.js} +2 -1
- package/dist/{chunk-2NB27LM7.js → chunk-3IN6TOPM.js} +4 -4
- package/dist/{chunk-VDR2EP6K.js → chunk-4QN5YJ3D.js} +17 -5
- package/dist/chunk-4QN5YJ3D.js.map +1 -0
- package/dist/{chunk-YIBKJ36L.js → chunk-KB7OLYIJ.js} +7 -2
- package/dist/{chunk-YIBKJ36L.js.map → chunk-KB7OLYIJ.js.map} +1 -1
- package/dist/chunk-TDG7ED2W.js +1 -0
- package/dist/chunk-TDG7ED2W.js.map +1 -0
- package/dist/code-block.js +17 -5
- package/dist/code-block.js.map +1 -1
- package/dist/data-table.js +1 -1
- package/dist/date-picker.js +2 -2
- package/dist/date-picker.js.map +1 -1
- package/dist/file-upload.js +2 -2
- package/dist/file-upload.js.map +1 -1
- package/dist/force-update-screen.d.ts +80 -0
- package/dist/force-update-screen.js +38 -0
- package/dist/force-update-screen.js.map +1 -0
- package/dist/maintenance-screen.d.ts +68 -0
- package/dist/maintenance-screen.js +35 -0
- package/dist/maintenance-screen.js.map +1 -0
- package/dist/match-dialog.js +2 -2
- package/dist/media-grid.d.ts +66 -3
- package/dist/media-grid.js +313 -66
- package/dist/media-grid.js.map +1 -1
- package/dist/native.d.ts +68 -1
- package/dist/native.js +773 -682
- package/dist/native.js.map +1 -1
- package/dist/nav-bar.d.ts +30 -7
- package/dist/nav-bar.js +69 -15
- package/dist/nav-bar.js.map +1 -1
- package/dist/page-hero.js +3 -3
- package/dist/progress.js +2 -2
- package/dist/progress.js.map +1 -1
- package/dist/radio-group.js +1 -1
- package/dist/settings-layout.js +1 -1
- package/dist/settings-layout.js.map +1 -1
- package/dist/tabs.js +2 -2
- package/dist/tabs.js.map +1 -1
- package/dist/update-prompt.d.ts +86 -0
- package/dist/update-prompt.js +41 -0
- package/dist/update-prompt.js.map +1 -0
- package/dist/vertical-video.d.ts +7 -1
- package/dist/vertical-video.js +7 -5
- package/dist/vertical-video.js.map +1 -1
- package/dist/video-player.d.ts +1 -1
- package/dist/video-player.js +16 -3
- package/dist/video-player.js.map +1 -1
- package/package.json +50 -8
- package/registry/achievement.json +16 -2
- package/registry/action-bar.json +16 -2
- package/registry/action-sheet.json +15 -0
- package/registry/activity-feed.json +13 -2
- package/registry/alert.json +15 -2
- package/registry/app-shell.json +15 -2
- package/registry/argument-card.json +16 -2
- package/registry/audio-player.json +15 -2
- package/registry/auth-layout.json +15 -2
- package/registry/avatar.json +14 -3
- package/registry/badge.json +13 -2
- package/registry/button.json +13 -2
- package/registry/card-grid.json +16 -2
- package/registry/card.json +13 -2
- package/registry/catalogue-page.json +15 -2
- package/registry/changelog-page.json +15 -2
- package/registry/chat.json +15 -4
- package/registry/checkbox.json +13 -2
- package/registry/chip-link.json +15 -2
- package/registry/chip.json +13 -2
- package/registry/choice-grid.json +16 -2
- package/registry/cn.json +12 -0
- package/registry/code-block.json +15 -2
- package/registry/combobox.json +13 -2
- package/registry/command.json +15 -2
- package/registry/component-page.json +15 -2
- package/registry/confirm-button.json +15 -2
- package/registry/contact-page.json +15 -2
- package/registry/continue-prompt.json +16 -2
- package/registry/countdown.json +13 -2
- package/registry/cta-panel.json +16 -2
- package/registry/dashboard-layout.json +15 -2
- package/registry/data-table.json +15 -2
- package/registry/date-picker.json +15 -4
- package/registry/decision-list.json +16 -2
- package/registry/description-list.json +13 -2
- package/registry/detail-layout.json +15 -2
- package/registry/dialog.json +13 -2
- package/registry/docs-article-page.json +15 -2
- package/registry/docs-hub-page.json +15 -2
- package/registry/docs-layout.json +15 -2
- package/registry/document-viewer.json +15 -2
- package/registry/dropdown-menu.json +15 -2
- package/registry/empty-state.json +13 -2
- package/registry/error-page.json +14 -2
- package/registry/examples/chat/conversation.js +2 -2
- package/registry/examples/chat/conversation.tsx +4 -3
- package/registry/examples/force-update-screen/default.d.ts +4 -0
- package/registry/examples/force-update-screen/default.js +25 -0
- package/registry/examples/force-update-screen/default.tsx +19 -0
- package/registry/examples/force-update-screen/native/default.d.ts +4 -0
- package/registry/examples/force-update-screen/native/default.dark.png +0 -0
- package/registry/examples/force-update-screen/native/default.js +24 -0
- package/registry/examples/force-update-screen/native/default.light.png +0 -0
- package/registry/examples/force-update-screen/native/default.tsx +18 -0
- package/registry/examples/glow-card/plans.js +9 -12
- package/registry/examples/glow-card/plans.tsx +18 -14
- package/registry/examples/maintenance-screen/default.d.ts +4 -0
- package/registry/examples/maintenance-screen/default.js +22 -0
- package/registry/examples/maintenance-screen/default.tsx +16 -0
- package/registry/examples/maintenance-screen/native/default.d.ts +4 -0
- package/registry/examples/maintenance-screen/native/default.dark.png +0 -0
- package/registry/examples/maintenance-screen/native/default.js +21 -0
- package/registry/examples/maintenance-screen/native/default.light.png +0 -0
- package/registry/examples/maintenance-screen/native/default.tsx +15 -0
- package/registry/examples/media-grid/loading.d.ts +4 -0
- package/registry/examples/media-grid/loading.js +28 -0
- package/registry/examples/media-grid/loading.tsx +25 -0
- package/registry/examples/nav-bar/family-product.js +16 -13
- package/registry/examples/nav-bar/family-product.tsx +19 -19
- package/registry/examples/update-prompt/default.d.ts +4 -0
- package/registry/examples/update-prompt/default.js +24 -0
- package/registry/examples/update-prompt/default.tsx +18 -0
- package/registry/examples/update-prompt/native/default.d.ts +4 -0
- package/registry/examples/update-prompt/native/default.dark.png +0 -0
- package/registry/examples/update-prompt/native/default.js +23 -0
- package/registry/examples/update-prompt/native/default.light.png +0 -0
- package/registry/examples/update-prompt/native/default.tsx +17 -0
- package/registry/examples/vertical-video/rail.js +3 -2
- package/registry/examples/vertical-video/rail.tsx +4 -3
- package/registry/fab.json +15 -0
- package/registry/field.json +13 -2
- package/registry/file-upload.json +15 -2
- package/registry/filter-bar.json +15 -2
- package/registry/force-update-screen.json +323 -0
- package/registry/form.json +13 -2
- package/registry/gallery-page.json +15 -2
- package/registry/glow-card.json +18 -4
- package/registry/goal-gauge.json +13 -2
- package/registry/header.json +15 -0
- package/registry/how-it-works-page.json +15 -2
- package/registry/image-cropper.json +15 -2
- package/registry/image-viewer.json +15 -0
- package/registry/index.json +1864 -8
- package/registry/iso.json +15 -2
- package/registry/kanban.json +15 -2
- package/registry/kbd.json +14 -2
- package/registry/landing-page.json +15 -2
- package/registry/landscape.json +15 -2
- package/registry/legal-page.json +15 -2
- package/registry/link-card.json +16 -2
- package/registry/list.json +13 -2
- package/registry/loader.json +13 -2
- package/registry/longform-page.json +15 -2
- package/registry/maintenance-screen.json +259 -0
- package/registry/map.json +14 -3
- package/registry/markdown.json +13 -2
- package/registry/marketing-layout.json +15 -2
- package/registry/marquee.json +15 -2
- package/registry/mascot-quote.json +16 -2
- package/registry/mascot-state.json +13 -2
- package/registry/mascots.json +13 -2
- package/registry/match-dialog.json +13 -2
- package/registry/media-capture.json +13 -2
- package/registry/media-grid.json +342 -14
- package/registry/media-picker.json +15 -0
- package/registry/meter.json +13 -2
- package/registry/nav-bar.json +61 -14
- package/registry/number-ticker.json +13 -2
- package/registry/onboarding.json +15 -0
- package/registry/otp-input.json +15 -0
- package/registry/page-header.json +15 -2
- package/registry/page-hero.json +16 -2
- package/registry/page.json +16 -2
- package/registry/pagination.json +13 -2
- package/registry/permission-prompt.json +15 -0
- package/registry/photo-grid.json +13 -2
- package/registry/pointer-light.json +15 -2
- package/registry/popover.json +15 -2
- package/registry/pricing-page.json +15 -2
- package/registry/product-page.json +15 -2
- package/registry/profile-card.json +13 -2
- package/registry/progress.json +14 -3
- package/registry/promise-card.json +16 -2
- package/registry/pull-to-refresh.json +15 -0
- package/registry/radio-group.json +14 -3
- package/registry/recipes.json +14 -2
- package/registry/screen.json +15 -0
- package/registry/scroll-stage.json +15 -2
- package/registry/search-field.json +13 -2
- package/registry/section-backdrop.json +15 -2
- package/registry/segmented.json +15 -0
- package/registry/separator.json +15 -2
- package/registry/settings-layout.json +16 -3
- package/registry/showcase.json +13 -2
- package/registry/site-footer.json +16 -3
- package/registry/sites.json +15 -2
- package/registry/skeleton.json +13 -2
- package/registry/skip-link.json +14 -2
- package/registry/slider.json +13 -2
- package/registry/slot.json +14 -2
- package/registry/spinner.json +13 -2
- package/registry/splash-handoff.json +15 -0
- package/registry/split-view.json +15 -2
- package/registry/stage-banner.json +15 -2
- package/registry/stat.json +15 -2
- package/registry/status-page.json +15 -2
- package/registry/step-progress.json +13 -2
- package/registry/stepper.json +13 -2
- package/registry/swipe-deck.json +13 -2
- package/registry/swipeable-row.json +15 -0
- package/registry/switch.json +13 -2
- package/registry/tab-bar.json +15 -0
- package/registry/tabs.json +15 -4
- package/registry/tag-input.json +15 -2
- package/registry/tag-selector.json +13 -2
- package/registry/terminal-replay.json +15 -2
- package/registry/theme.json +13 -2
- package/registry/toast.json +13 -2
- package/registry/toc.json +15 -2
- package/registry/tooltip.json +14 -2
- package/registry/tree-view.json +15 -2
- package/registry/txt.json +13 -0
- package/registry/update-prompt.json +316 -0
- package/registry/verified-badge.json +13 -2
- package/registry/vertical-video.json +32 -6
- package/registry/video-player.json +16 -5
- package/registry/video-trimmer.json +15 -2
- package/registry/virtual.json +15 -2
- package/dist/chunk-VDR2EP6K.js.map +0 -1
- /package/dist/{chunk-276PTQMQ.js.map → chunk-3BC4XQ6C.js.map} +0 -0
- /package/dist/{chunk-2NB27LM7.js.map → chunk-3IN6TOPM.js.map} +0 -0
package/registry/filter-bar.json
CHANGED
|
@@ -8,6 +8,19 @@
|
|
|
8
8
|
"deprecated": null,
|
|
9
9
|
"category": "forms",
|
|
10
10
|
"platforms": "web",
|
|
11
|
+
"parity": {
|
|
12
|
+
"web": {
|
|
13
|
+
"state": "yes"
|
|
14
|
+
},
|
|
15
|
+
"native": {
|
|
16
|
+
"state": "gap",
|
|
17
|
+
"reason": "No phone list screen filters yet; when one does, chips and a sheet.",
|
|
18
|
+
"issue": "krizaka/krizaka-ui#277"
|
|
19
|
+
},
|
|
20
|
+
"desktop": {
|
|
21
|
+
"state": "yes"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
11
24
|
"whenToUse": [
|
|
12
25
|
"Above a table or a grid people narrow with several criteria at once: status, type, owner, period, a query.",
|
|
13
26
|
"When the filters must be visible and undoable one by one, and shared in a link."
|
|
@@ -210,7 +223,7 @@
|
|
|
210
223
|
},
|
|
211
224
|
{
|
|
212
225
|
"path": "filter-bar/meta.ts",
|
|
213
|
-
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Filter bar\",\n summary: \"The bar above a list: the controls that narrow it, the filters in force as removable chips, “Clear filters”, and the count of results — the state kept by the app, in the address.\",\n status: \"beta\",\n category: \"forms\",\n platforms: \"web\",\n whenToUse: [\n \"Above a table or a grid people narrow with several criteria at once: status, type, owner, period, a query.\",\n \"When the filters must be visible and undoable one by one, and shared in a link.\",\n ],\n whenNotToUse: [\n { when: \"For one choice among a few views of a list: chips in a single group.\", use: \"chip\" },\n { when: \"For a search alone: a search field.\", use: \"search-field\" },\n ],\n bestPractices: [\n \"Keep the filters in the address: `readFilterParams` on load, `writeFilterParams` (which resets the page) on change.\",\n \"Give each active chip the filter's name and value (“Status: Failed”), not the value alone.\",\n \"Count the results in `results` (“42 jobs”): it is announced when it changes.\",\n \"Use `FilterMenu` for a list of values (checkboxes, or radios with `multiple={false}`), a `DateRangePicker` for a period.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Enter / Space / Arrow Down\", action: \"Opens a filter's menu.\" },\n { keys: \"Arrow keys\", action: \"Move between the options of an open menu; Space checks one, the menu stays open.\" },\n { keys: \"Esc\", action: \"Closes the menu and returns to its button.\" },\n ],\n notes: [\n \"The bar is a `group` named by `label`; each chip's remove button is named by `removeLabel` (“Remove Status: Failed”).\",\n \"The count of results is a polite live region: a change of filter is heard without moving the focus.\",\n \"`FilterMenu` is a Radix menu of checkbox (or radio) items: their state is announced as they toggle.\",\n ],\n },\n related: [\"search-field\", \"chip\", \"dropdown-menu\", \"date-picker\", \"data-table\"],\n web: {\n imports: [\"FilterBar\", \"FilterMenu\", \"readFilterParams\", \"writeFilterParams\"],\n examples: [\n { name: \"jobs\", title: \"Jobs\", description: \"A search, a status menu (several values), a model menu (one value), the chips and the count.\" },\n { name: \"menu\", title: \"A filter's menu\", description: \"`FilterMenu` open: checkboxes with the count each value would give.\" },\n { name: \"address\", title: \"In the address\", description: \"The filters read from and written to `URLSearchParams`, the page reset on change.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
226
|
+
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Filter bar\",\n summary: \"The bar above a list: the controls that narrow it, the filters in force as removable chips, “Clear filters”, and the count of results — the state kept by the app, in the address.\",\n status: \"beta\",\n category: \"forms\",\n platforms: \"web\",\n parity: { native: { status: \"gap\", issue: \"krizaka/krizaka-ui#277\", reason: \"No phone list screen filters yet; when one does, chips and a sheet.\" }, desktop: \"web\" },\n whenToUse: [\n \"Above a table or a grid people narrow with several criteria at once: status, type, owner, period, a query.\",\n \"When the filters must be visible and undoable one by one, and shared in a link.\",\n ],\n whenNotToUse: [\n { when: \"For one choice among a few views of a list: chips in a single group.\", use: \"chip\" },\n { when: \"For a search alone: a search field.\", use: \"search-field\" },\n ],\n bestPractices: [\n \"Keep the filters in the address: `readFilterParams` on load, `writeFilterParams` (which resets the page) on change.\",\n \"Give each active chip the filter's name and value (“Status: Failed”), not the value alone.\",\n \"Count the results in `results` (“42 jobs”): it is announced when it changes.\",\n \"Use `FilterMenu` for a list of values (checkboxes, or radios with `multiple={false}`), a `DateRangePicker` for a period.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Enter / Space / Arrow Down\", action: \"Opens a filter's menu.\" },\n { keys: \"Arrow keys\", action: \"Move between the options of an open menu; Space checks one, the menu stays open.\" },\n { keys: \"Esc\", action: \"Closes the menu and returns to its button.\" },\n ],\n notes: [\n \"The bar is a `group` named by `label`; each chip's remove button is named by `removeLabel` (“Remove Status: Failed”).\",\n \"The count of results is a polite live region: a change of filter is heard without moving the focus.\",\n \"`FilterMenu` is a Radix menu of checkbox (or radio) items: their state is announced as they toggle.\",\n ],\n },\n related: [\"search-field\", \"chip\", \"dropdown-menu\", \"date-picker\", \"data-table\"],\n web: {\n imports: [\"FilterBar\", \"FilterMenu\", \"readFilterParams\", \"writeFilterParams\"],\n examples: [\n { name: \"jobs\", title: \"Jobs\", description: \"A search, a status menu (several values), a model menu (one value), the chips and the count.\" },\n { name: \"menu\", title: \"A filter's menu\", description: \"`FilterMenu` open: checkboxes with the count each value would give.\" },\n { name: \"address\", title: \"In the address\", description: \"The filters read from and written to `URLSearchParams`, the page reset on change.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
214
227
|
},
|
|
215
228
|
{
|
|
216
229
|
"path": "glyphs.tsx",
|
|
@@ -218,7 +231,7 @@
|
|
|
218
231
|
},
|
|
219
232
|
{
|
|
220
233
|
"path": "meta.ts",
|
|
221
|
-
"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"
|
|
234
|
+
"content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
|
|
222
235
|
}
|
|
223
236
|
],
|
|
224
237
|
"dependencies": [
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "force-update-screen",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"title": "Force update screen",
|
|
5
|
+
"summary": "The blocking screen of a version that can no longer run: the mark, one sentence, one button to the store.",
|
|
6
|
+
"description": "The blocking screen of a version that can no longer run: the mark, one sentence, one button to the store.",
|
|
7
|
+
"status": "beta",
|
|
8
|
+
"deprecated": null,
|
|
9
|
+
"category": "feedback",
|
|
10
|
+
"platforms": "both",
|
|
11
|
+
"parity": {
|
|
12
|
+
"web": {
|
|
13
|
+
"state": "yes"
|
|
14
|
+
},
|
|
15
|
+
"native": {
|
|
16
|
+
"state": "yes"
|
|
17
|
+
},
|
|
18
|
+
"desktop": {
|
|
19
|
+
"state": "yes"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"whenToUse": [
|
|
23
|
+
"When the backend says `updateMode: force` or answers `426 Upgrade Required`.",
|
|
24
|
+
"In place of the whole app: nothing behind it must be reachable."
|
|
25
|
+
],
|
|
26
|
+
"whenNotToUse": [
|
|
27
|
+
{
|
|
28
|
+
"when": "When the installed version still works: invite, do not block.",
|
|
29
|
+
"use": "update-prompt"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"when": "When the service is down on purpose.",
|
|
33
|
+
"use": "maintenance-screen"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"bestPractices": [
|
|
37
|
+
"Say why in one sentence and give one way out: the store button.",
|
|
38
|
+
"Show the installed and the required version, in mono.",
|
|
39
|
+
"Offer “Check again” for the person who just updated from the store."
|
|
40
|
+
],
|
|
41
|
+
"accessibility": {
|
|
42
|
+
"keyboard": [],
|
|
43
|
+
"notes": [
|
|
44
|
+
"A `main` with the title as its `h1`; the update button takes focus at once.",
|
|
45
|
+
"The mark is decorative."
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"related": [
|
|
49
|
+
"update-prompt",
|
|
50
|
+
"maintenance-screen",
|
|
51
|
+
"error-page"
|
|
52
|
+
],
|
|
53
|
+
"web": {
|
|
54
|
+
"entry": "@krizaka/ui/force-update-screen",
|
|
55
|
+
"import": "import { ForceUpdateScreen } from \"@krizaka/ui/force-update-screen\";",
|
|
56
|
+
"props": [
|
|
57
|
+
{
|
|
58
|
+
"component": "ForceUpdateScreen",
|
|
59
|
+
"description": "The forced update: one thing to do, and the way to do it.",
|
|
60
|
+
"props": [
|
|
61
|
+
{
|
|
62
|
+
"name": "onUpdate",
|
|
63
|
+
"type": "() => void",
|
|
64
|
+
"default": null,
|
|
65
|
+
"description": "Opens the store page.",
|
|
66
|
+
"required": true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "title",
|
|
70
|
+
"type": "ReactNode",
|
|
71
|
+
"default": null,
|
|
72
|
+
"description": "The headline (“Update to keep going”).",
|
|
73
|
+
"required": true
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "updateLabel",
|
|
77
|
+
"type": "string",
|
|
78
|
+
"default": null,
|
|
79
|
+
"description": "The button that updates (“Update the app”).",
|
|
80
|
+
"required": true
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "mark",
|
|
84
|
+
"type": "ReactNode",
|
|
85
|
+
"default": null,
|
|
86
|
+
"description": "The product's mark or mascot (decorative).",
|
|
87
|
+
"required": false
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "message",
|
|
91
|
+
"type": "ReactNode",
|
|
92
|
+
"default": null,
|
|
93
|
+
"description": "Why, in a sentence — the backend's localized `message`.",
|
|
94
|
+
"required": false
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "onRetry",
|
|
98
|
+
"type": "(() => void)",
|
|
99
|
+
"default": null,
|
|
100
|
+
"description": "Checks the backend again.",
|
|
101
|
+
"required": false
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "retryLabel",
|
|
105
|
+
"type": "string",
|
|
106
|
+
"default": null,
|
|
107
|
+
"description": "A secondary way (“Check again”) once the update is installed — optional.",
|
|
108
|
+
"required": false
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "versionLabel",
|
|
112
|
+
"type": "ReactNode",
|
|
113
|
+
"default": null,
|
|
114
|
+
"description": "The versions, in words and mono (“You have 1.1.0 · 1.2.0 or later is required”).",
|
|
115
|
+
"required": false
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
"examples": [
|
|
121
|
+
{
|
|
122
|
+
"name": "default",
|
|
123
|
+
"title": "Default",
|
|
124
|
+
"description": "The title, the sentence, the versions and the store button.",
|
|
125
|
+
"path": "examples/force-update-screen/default.tsx",
|
|
126
|
+
"module": "@krizaka/ui/registry/examples/force-update-screen/default",
|
|
127
|
+
"code": "import { ProductLogo } from \"@krizaka/ui\";\nimport { ForceUpdateScreen } from \"@krizaka/ui/force-update-screen\";\n\n// The blocking screen: the backend answered 426, or `updateMode: \"force\"`. It replaces the whole app.\nexport default function ForceUpdateScreenDefault() {\n return (\n <ForceUpdateScreen\n className=\"min-h-0\"\n mark={<ProductLogo id=\"orochia\" size={96} animated={false} />}\n title=\"Update to keep going\"\n message=\"This version can no longer talk to our servers. Updating takes a minute and keeps everything you have.\"\n versionLabel=\"You have 1.1.0 · 1.2.0 or later is required\"\n updateLabel=\"Update the app\"\n onUpdate={() => undefined}\n retryLabel=\"I already updated\"\n onRetry={() => undefined}\n />\n );\n}\n"
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"native": {
|
|
132
|
+
"entry": "@krizaka/ui/native",
|
|
133
|
+
"import": "import { ForceUpdateScreen } from \"@krizaka/ui/native\";",
|
|
134
|
+
"differences": [
|
|
135
|
+
"The buttons are native `Button`s (with `label`).",
|
|
136
|
+
"Fills the screen; style with `style`, not `className`."
|
|
137
|
+
],
|
|
138
|
+
"props": [
|
|
139
|
+
{
|
|
140
|
+
"component": "ForceUpdateScreen",
|
|
141
|
+
"description": "The forced update: replaces the app, with one thing to do and the way to do it.",
|
|
142
|
+
"props": [
|
|
143
|
+
{
|
|
144
|
+
"name": "onUpdate",
|
|
145
|
+
"type": "() => void",
|
|
146
|
+
"default": null,
|
|
147
|
+
"description": "Opens the store page (`Linking.openURL(storeUrl)`).",
|
|
148
|
+
"required": true
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "title",
|
|
152
|
+
"type": "string",
|
|
153
|
+
"default": null,
|
|
154
|
+
"description": "The headline (“Update to keep going”) — passed translated.",
|
|
155
|
+
"required": true
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "updateLabel",
|
|
159
|
+
"type": "string",
|
|
160
|
+
"default": null,
|
|
161
|
+
"description": "The button that updates (“Update the app”).",
|
|
162
|
+
"required": true
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "fill",
|
|
166
|
+
"type": "boolean",
|
|
167
|
+
"default": "true",
|
|
168
|
+
"description": "Fill the whole screen (default). `false` for a card in a catalogue.",
|
|
169
|
+
"required": false
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"name": "mark",
|
|
173
|
+
"type": "ReactNode",
|
|
174
|
+
"default": null,
|
|
175
|
+
"description": "The product's mark or mascot (decorative).",
|
|
176
|
+
"required": false
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"name": "message",
|
|
180
|
+
"type": "string",
|
|
181
|
+
"default": null,
|
|
182
|
+
"description": "Why, in a sentence — the backend's localized `message`.",
|
|
183
|
+
"required": false
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "onRetry",
|
|
187
|
+
"type": "(() => void)",
|
|
188
|
+
"default": null,
|
|
189
|
+
"description": "Checks the backend again (`useAppConfig().refresh`).",
|
|
190
|
+
"required": false
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "retryLabel",
|
|
194
|
+
"type": "string",
|
|
195
|
+
"default": null,
|
|
196
|
+
"description": "A secondary way (“I already updated”) — with `onRetry`.",
|
|
197
|
+
"required": false
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "style",
|
|
201
|
+
"type": "StyleProp<ViewStyle>",
|
|
202
|
+
"default": null,
|
|
203
|
+
"description": "Styles merged last.",
|
|
204
|
+
"required": false
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"name": "versionLabel",
|
|
208
|
+
"type": "string",
|
|
209
|
+
"default": null,
|
|
210
|
+
"description": "The versions, in mono (“You have 1.1.0 · 1.2.0 or later is required”).",
|
|
211
|
+
"required": false
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
"examples": [
|
|
217
|
+
{
|
|
218
|
+
"name": "default",
|
|
219
|
+
"title": "Default",
|
|
220
|
+
"description": "The blocking screen with the store button.",
|
|
221
|
+
"path": "examples/force-update-screen/native/default.tsx",
|
|
222
|
+
"module": "@krizaka/ui/registry/examples/force-update-screen/native/default",
|
|
223
|
+
"code": "import { ForceUpdateScreen, ProductMark } from \"@krizaka/ui/native\";\n\n// The blocking screen: the backend answered 426. `onUpdate` opens the store page (`Linking.openURL(storeUrl)`).\nexport default function NativeForceUpdateScreenDefault() {\n return (\n <ForceUpdateScreen\n fill={false}\n mark={<ProductMark id=\"orochia\" size={96} animated={false} />}\n title=\"Update to keep going\"\n message=\"This version can no longer talk to our servers. Updating takes a minute and keeps everything you have.\"\n versionLabel=\"You have 1.1.0 · 1.2.0 or later is required\"\n updateLabel=\"Update the app\"\n onUpdate={() => undefined}\n retryLabel=\"I already updated\"\n onRetry={() => undefined}\n />\n );\n}\n",
|
|
224
|
+
"screenshots": {
|
|
225
|
+
"dark": "examples/force-update-screen/native/default.dark.png",
|
|
226
|
+
"light": "examples/force-update-screen/native/default.light.png"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
"files": [
|
|
232
|
+
{
|
|
233
|
+
"path": "force-update-screen/force-update-screen.tsx",
|
|
234
|
+
"content": "// Server-safe. The blocking screen: this version can no longer run. It replaces the app, it is not laid over it —\n// nothing behind it is reachable. Every word is a prop; the store URL and the versions come from the backend.\nimport { Button } from \"@krizaka/ui/button\";\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const forceUpdateScreen = tv({\n slots: {\n root: \"mx-auto flex min-h-dvh w-full max-w-md flex-col items-center justify-center gap-4 px-6 py-12 text-center text-fg\",\n mark: \"flex h-24 w-24 items-center justify-center [&>svg]:h-24 [&>svg]:w-24\",\n title: \"text-2xl font-semibold text-fg\",\n message: \"text-fg-secondary\",\n version: \"font-mono text-xs text-fg-tertiary\",\n actions: \"mt-2 flex w-full flex-col gap-2\",\n },\n});\n\nexport type ForceUpdateScreenProps = Omit<React.ComponentProps<\"main\">, \"title\"> & {\n /** The headline (“Update to keep going”). */\n title: React.ReactNode;\n /** Why, in a sentence — the backend's localized `message`. */\n message?: React.ReactNode;\n /** The versions, in words and mono (“You have 1.1.0 · 1.2.0 or later is required”). */\n versionLabel?: React.ReactNode;\n /** The product's mark or mascot (decorative). */\n mark?: React.ReactNode;\n /** The button that updates (“Update the app”). */\n updateLabel: string;\n /** Opens the store page. */\n onUpdate: () => void;\n /** A secondary way (“Check again”) once the update is installed — optional. */\n retryLabel?: string;\n /** Checks the backend again. */\n onRetry?: () => void;\n};\n\n/** The forced update: one thing to do, and the way to do it. */\nexport function ForceUpdateScreen({ title, message, versionLabel, mark, updateLabel, onUpdate, retryLabel, onRetry, className, ...props }: ForceUpdateScreenProps) {\n const s = forceUpdateScreen();\n return (\n <main data-kind=\"update-force\" className={s.root({ className })} {...props}>\n {mark && (\n <div aria-hidden className={s.mark()}>\n {mark}\n </div>\n )}\n <h1 className={s.title()}>{title}</h1>\n {message && <p className={s.message()}>{message}</p>}\n {versionLabel && <p className={s.version()}>{versionLabel}</p>}\n <div className={s.actions()}>\n <Button variant=\"primary\" size=\"lg\" shape=\"cut\" shimmer autoFocus onClick={onUpdate}>\n {updateLabel}\n </Button>\n {retryLabel && onRetry && (\n <Button variant=\"ghost\" onClick={onRetry}>\n {retryLabel}\n </Button>\n )}\n </div>\n </main>\n );\n}\n"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"path": "force-update-screen/index.ts",
|
|
238
|
+
"content": "export { ForceUpdateScreen, forceUpdateScreen,type ForceUpdateScreenProps } from \"./force-update-screen\";\n"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"path": "force-update-screen/meta.ts",
|
|
242
|
+
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Force update screen\",\n summary: \"The blocking screen of a version that can no longer run: the mark, one sentence, one button to the store.\",\n status: \"beta\",\n category: \"feedback\",\n platforms: \"both\",\n parity: { desktop: \"web\" },\n whenToUse: [\"When the backend says `updateMode: force` or answers `426 Upgrade Required`.\", \"In place of the whole app: nothing behind it must be reachable.\"],\n whenNotToUse: [{\"when\": \"When the installed version still works: invite, do not block.\", \"use\": \"update-prompt\"}, {\"when\": \"When the service is down on purpose.\", \"use\": \"maintenance-screen\"}],\n bestPractices: [\"Say why in one sentence and give one way out: the store button.\", \"Show the installed and the required version, in mono.\", \"Offer “Check again” for the person who just updated from the store.\"],\n accessibility: {\n keyboard: [],\n notes: [\"A `main` with the title as its `h1`; the update button takes focus at once.\", \"The mark is decorative.\"],\n },\n related: [\"update-prompt\", \"maintenance-screen\", \"error-page\"],\n web: {\n imports: [\"ForceUpdateScreen\"],\n examples: [{ name: \"default\", title: \"Default\", description: \"The title, the sentence, the versions and the store button.\" }],\n },\n native: {\n imports: [\"ForceUpdateScreen\"],\n differences: [\"The buttons are native `Button`s (with `label`).\", \"Fills the screen; style with `style`, not `className`.\"],\n examples: [{ name: \"default\", title: \"Default\", description: \"The blocking screen with the store button.\" }],\n },\n} satisfies ComponentMeta;\n"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"path": "meta.ts",
|
|
246
|
+
"content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
|
|
247
|
+
}
|
|
248
|
+
],
|
|
249
|
+
"dependencies": [
|
|
250
|
+
"tailwind-variants@^3.3.1"
|
|
251
|
+
],
|
|
252
|
+
"registryDependencies": [
|
|
253
|
+
"button"
|
|
254
|
+
],
|
|
255
|
+
"demo": {
|
|
256
|
+
"path": "examples/force-update-screen/default.tsx",
|
|
257
|
+
"content": "import { ProductLogo } from \"@krizaka/ui\";\nimport { ForceUpdateScreen } from \"@krizaka/ui/force-update-screen\";\n\n// The blocking screen: the backend answered 426, or `updateMode: \"force\"`. It replaces the whole app.\nexport default function ForceUpdateScreenDefault() {\n return (\n <ForceUpdateScreen\n className=\"min-h-0\"\n mark={<ProductLogo id=\"orochia\" size={96} animated={false} />}\n title=\"Update to keep going\"\n message=\"This version can no longer talk to our servers. Updating takes a minute and keeps everything you have.\"\n versionLabel=\"You have 1.1.0 · 1.2.0 or later is required\"\n updateLabel=\"Update the app\"\n onUpdate={() => undefined}\n retryLabel=\"I already updated\"\n onRetry={() => undefined}\n />\n );\n}\n"
|
|
258
|
+
},
|
|
259
|
+
"props": [
|
|
260
|
+
{
|
|
261
|
+
"component": "ForceUpdateScreen",
|
|
262
|
+
"description": "The forced update: one thing to do, and the way to do it.",
|
|
263
|
+
"props": [
|
|
264
|
+
{
|
|
265
|
+
"name": "onUpdate",
|
|
266
|
+
"type": "() => void",
|
|
267
|
+
"default": null,
|
|
268
|
+
"description": "Opens the store page.",
|
|
269
|
+
"required": true
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"name": "title",
|
|
273
|
+
"type": "ReactNode",
|
|
274
|
+
"default": null,
|
|
275
|
+
"description": "The headline (“Update to keep going”).",
|
|
276
|
+
"required": true
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"name": "updateLabel",
|
|
280
|
+
"type": "string",
|
|
281
|
+
"default": null,
|
|
282
|
+
"description": "The button that updates (“Update the app”).",
|
|
283
|
+
"required": true
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"name": "mark",
|
|
287
|
+
"type": "ReactNode",
|
|
288
|
+
"default": null,
|
|
289
|
+
"description": "The product's mark or mascot (decorative).",
|
|
290
|
+
"required": false
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"name": "message",
|
|
294
|
+
"type": "ReactNode",
|
|
295
|
+
"default": null,
|
|
296
|
+
"description": "Why, in a sentence — the backend's localized `message`.",
|
|
297
|
+
"required": false
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"name": "onRetry",
|
|
301
|
+
"type": "(() => void)",
|
|
302
|
+
"default": null,
|
|
303
|
+
"description": "Checks the backend again.",
|
|
304
|
+
"required": false
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"name": "retryLabel",
|
|
308
|
+
"type": "string",
|
|
309
|
+
"default": null,
|
|
310
|
+
"description": "A secondary way (“Check again”) once the update is installed — optional.",
|
|
311
|
+
"required": false
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"name": "versionLabel",
|
|
315
|
+
"type": "ReactNode",
|
|
316
|
+
"default": null,
|
|
317
|
+
"description": "The versions, in words and mono (“You have 1.1.0 · 1.2.0 or later is required”).",
|
|
318
|
+
"required": false
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
}
|
package/registry/form.json
CHANGED
|
@@ -8,6 +8,17 @@
|
|
|
8
8
|
"deprecated": null,
|
|
9
9
|
"category": "forms",
|
|
10
10
|
"platforms": "both",
|
|
11
|
+
"parity": {
|
|
12
|
+
"web": {
|
|
13
|
+
"state": "yes"
|
|
14
|
+
},
|
|
15
|
+
"native": {
|
|
16
|
+
"state": "yes"
|
|
17
|
+
},
|
|
18
|
+
"desktop": {
|
|
19
|
+
"state": "yes"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
11
22
|
"whenToUse": [
|
|
12
23
|
"Any form that sends something: sign-up, profile, upload details, an amount, an audience, a report, settings.",
|
|
13
24
|
"When the server must re-check the same rules: the schema is shared (a product keeps it in `packages/core`) and `@krizaka/ui/form/server` parses with it in a Server Action.",
|
|
@@ -2070,7 +2081,7 @@
|
|
|
2070
2081
|
},
|
|
2071
2082
|
{
|
|
2072
2083
|
"path": "form/meta.ts",
|
|
2073
|
-
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Form\",\n summary:\n \"A form = a schema (zod 4) + the package's fields, on TanStack Form v1: each error shows once the field is left and goes as soon as it is fixed, in the person's language (i18n keys, never the engine's English), a value can be checked on the server as it is typed, and the server's refusals land under their fields — web and React Native, the same schema.\",\n status: \"beta\",\n category: \"forms\",\n platforms: \"both\",\n whenToUse: [\n \"Any form that sends something: sign-up, profile, upload details, an amount, an audience, a report, settings.\",\n \"When the server must re-check the same rules: the schema is shared (a product keeps it in `packages/core`) and `@krizaka/ui/form/server` parses with it in a Server Action.\",\n \"When a value depends on the server (a username's availability): `validateAsync` on the field, debounced and aborted.\",\n ],\n whenNotToUse: [\n { when: \"For a single search box that filters as you type: no submit, no validation.\", use: \"search-field\" },\n { when: \"For a filter bar over a list: its state is the address, not a form.\", use: \"filter-bar\" },\n { when: \"For a setting that applies at once on its own (a theme switch): the control alone.\", use: \"switch\" },\n ],\n bestPractices: [\n \"Define the form once (`createKrizakaForm({ schema, defaultValues })`) beside the API's contract; use it in components with `form.useForm({ onSubmit })`.\",\n \"Name refusals with keys in the schema (`z.string().regex(re, \\\"signUp.username.format\\\")`) and give the words in `messages/*.json`; the generic ones are `form.errors.*` (required, tooShort {minimum}…).\",\n \"Send with a mutation of @krizaka/query (`onSubmit: (v) => mutation.mutateAsync(v)`) or a Server Action (`useFormWithAction`): a thrown `KrizakaApiError` or `{ ok: false, problem }` puts its `fields` under their fields.\",\n \"Never disable the submit button for an invalid form: a refused submit shows every error and focuses the first.\",\n \"Pick the field for the value: `DateField entry=\\\"parts\\\"` (typed) or `entry=\\\"wheel\\\"` for a memorable date (a birth date, bounded by `max`), the calendar for a date near today; `Money` stores cents; `RadioGroup` for two to five visible choices (`variant` list · cards · buttons · segmented), `Select` beyond.\",\n \"Need a control the package does not have? `form.Field` gives its value, setter, error and ids — never import @tanstack/* in a product.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Enter\", action: \"Sends the form from a text field (web); the keyboard's return key moves to the next field (native).\" },\n { keys: \"Tab\", action: \"Leaves a field: from then on its error shows, and goes as soon as it is fixed.\" },\n ],\n notes: [\n \"Every control is named by its visible label; its hint and its error are in `aria-describedby`, and an invalid control has `aria-invalid`.\",\n \"Errors are announced (`role=\\\"alert\\\"`); a refused submit focuses the first invalid field on the web and announces the first error on native.\",\n \"An availability check names its wait (`checkingLabel`) and says when the value is free (`availableHint`): never by colour alone.\",\n \"A date of birth is three labelled boxes in the locale's order with numeric keypads and `bday` autofill, not a calendar to scroll through decades.\",\n ],\n },\n related: [\"field\", \"checkbox\", \"switch\", \"radio-group\", \"combobox\", \"date-picker\", \"tag-input\", \"file-upload\", \"slider\"],\n web: {\n imports: [\"createKrizakaForm\", \"useFormWithAction\", \"latestBirthDate\", \"useKrizakaField\", \"formMessage\"],\n examples: [\n { name: \"sign-up\", title: \"Sign-up\", description: \"Orochia's sign-up: a username checked as it is typed with a free one offered, an 18+ date of birth typed as day · month · year, a server refusal on its field.\" },\n { name: \"profile\", title: \"Profile\", description: \"Saved by a mutation of @krizaka/query: categories, tags, a list of links that grows (`form.FieldArray`).\" },\n { name: \"upload\", title: \"Upload\", description: \"A video and what describes it — the file, a title, a description, tags, who can watch — sent together.\" },\n { name: \"amount\", title: \"Amount\", description: \"A tip stored in cents (`Money`), typed the locale's way; presets set the field from outside.\" },\n { name: \"audience\", title: \"Audience\", description: \"Cards to choose who watches, the price only for subscribers (`form.Watch`), a publication date.\" },\n { name: \"settings\", title: \"Settings\", description: \"Sent by a Server Action (`useFormWithAction`): switches, a menu, a slider, a choice.\" },\n { name: \"report\", title: \"Report\", description: \"One reason among a few, details, and blocking the account in the same step.\" },\n ],\n },\n native: {\n entry: \"@krizaka/ui/native/form\",\n imports: [\"createKrizakaForm\", \"TextField\", \"DateField\", \"Switch\", \"RadioGroup\", \"Select\", \"Combobox\", \"Slider\", \"Money\", \"Submit\", \"FormError\"],\n differences: [\n \"Import `createKrizakaForm` from `@krizaka/ui/native/form`: the same definition (schema, defaults, i18n), the same `useForm`, `form.*` fields drawn with the native ones.\",\n \"`form.Form` is a View; `form.Submit` takes `label` and announces the first error of a refused submit (no focus to move).\",\n \"`DateField` is three wheels by default (`entry=\\\"parts\\\"`: typed day · month · year); `RadioGroup` has the native looks (`variant` list · cards · buttons · segmented); `Select` and `Combobox` open a `Sheet`; `Switch` is a settings row drawn by the theme; `Slider` floats its value; `Money` uses the decimal keypad. Each control plays its haptic when the app passes `haptics` to the `ThemeProvider`.\",\n \"No `Combobox`, `TagInput`, `FileUpload` or `Slider` field yet: a screen of its own (a picker) sets the value, or `form.Field` draws it.\",\n ],\n examples: [\n { name: \"sign-up\", title: \"Sign-up\", description: \"E-mail, a username checked as it is typed, the 18+ birth date as day · month · year, the terms.\" },\n { name: \"profile\", title: \"Profile\", description: \"Saved by a mutation of @krizaka/query; a category as chips, the list of links grows.\" },\n { name: \"upload\", title: \"Upload details\", description: \"Title, description and audience of a video picked from the library.\" },\n { name: \"amount\", title: \"Amount\", description: \"A tip in cents on the decimal keypad, typed the French way; presets set the field.\" },\n { name: \"audience\", title: \"Audience\", description: \"The price only when the video is for subscribers; comments on or off.\" },\n { name: \"settings\", title: \"Settings\", description: \"The system's switches and chips for a choice, saved in one go.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
2084
|
+
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Form\",\n summary:\n \"A form = a schema (zod 4) + the package's fields, on TanStack Form v1: each error shows once the field is left and goes as soon as it is fixed, in the person's language (i18n keys, never the engine's English), a value can be checked on the server as it is typed, and the server's refusals land under their fields — web and React Native, the same schema.\",\n status: \"beta\",\n category: \"forms\",\n platforms: \"both\",\n parity: { desktop: \"web\" },\n whenToUse: [\n \"Any form that sends something: sign-up, profile, upload details, an amount, an audience, a report, settings.\",\n \"When the server must re-check the same rules: the schema is shared (a product keeps it in `packages/core`) and `@krizaka/ui/form/server` parses with it in a Server Action.\",\n \"When a value depends on the server (a username's availability): `validateAsync` on the field, debounced and aborted.\",\n ],\n whenNotToUse: [\n { when: \"For a single search box that filters as you type: no submit, no validation.\", use: \"search-field\" },\n { when: \"For a filter bar over a list: its state is the address, not a form.\", use: \"filter-bar\" },\n { when: \"For a setting that applies at once on its own (a theme switch): the control alone.\", use: \"switch\" },\n ],\n bestPractices: [\n \"Define the form once (`createKrizakaForm({ schema, defaultValues })`) beside the API's contract; use it in components with `form.useForm({ onSubmit })`.\",\n \"Name refusals with keys in the schema (`z.string().regex(re, \\\"signUp.username.format\\\")`) and give the words in `messages/*.json`; the generic ones are `form.errors.*` (required, tooShort {minimum}…).\",\n \"Send with a mutation of @krizaka/query (`onSubmit: (v) => mutation.mutateAsync(v)`) or a Server Action (`useFormWithAction`): a thrown `KrizakaApiError` or `{ ok: false, problem }` puts its `fields` under their fields.\",\n \"Never disable the submit button for an invalid form: a refused submit shows every error and focuses the first.\",\n \"Pick the field for the value: `DateField entry=\\\"parts\\\"` (typed) or `entry=\\\"wheel\\\"` for a memorable date (a birth date, bounded by `max`), the calendar for a date near today; `Money` stores cents; `RadioGroup` for two to five visible choices (`variant` list · cards · buttons · segmented), `Select` beyond.\",\n \"Need a control the package does not have? `form.Field` gives its value, setter, error and ids — never import @tanstack/* in a product.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Enter\", action: \"Sends the form from a text field (web); the keyboard's return key moves to the next field (native).\" },\n { keys: \"Tab\", action: \"Leaves a field: from then on its error shows, and goes as soon as it is fixed.\" },\n ],\n notes: [\n \"Every control is named by its visible label; its hint and its error are in `aria-describedby`, and an invalid control has `aria-invalid`.\",\n \"Errors are announced (`role=\\\"alert\\\"`); a refused submit focuses the first invalid field on the web and announces the first error on native.\",\n \"An availability check names its wait (`checkingLabel`) and says when the value is free (`availableHint`): never by colour alone.\",\n \"A date of birth is three labelled boxes in the locale's order with numeric keypads and `bday` autofill, not a calendar to scroll through decades.\",\n ],\n },\n related: [\"field\", \"checkbox\", \"switch\", \"radio-group\", \"combobox\", \"date-picker\", \"tag-input\", \"file-upload\", \"slider\"],\n web: {\n imports: [\"createKrizakaForm\", \"useFormWithAction\", \"latestBirthDate\", \"useKrizakaField\", \"formMessage\"],\n examples: [\n { name: \"sign-up\", title: \"Sign-up\", description: \"Orochia's sign-up: a username checked as it is typed with a free one offered, an 18+ date of birth typed as day · month · year, a server refusal on its field.\" },\n { name: \"profile\", title: \"Profile\", description: \"Saved by a mutation of @krizaka/query: categories, tags, a list of links that grows (`form.FieldArray`).\" },\n { name: \"upload\", title: \"Upload\", description: \"A video and what describes it — the file, a title, a description, tags, who can watch — sent together.\" },\n { name: \"amount\", title: \"Amount\", description: \"A tip stored in cents (`Money`), typed the locale's way; presets set the field from outside.\" },\n { name: \"audience\", title: \"Audience\", description: \"Cards to choose who watches, the price only for subscribers (`form.Watch`), a publication date.\" },\n { name: \"settings\", title: \"Settings\", description: \"Sent by a Server Action (`useFormWithAction`): switches, a menu, a slider, a choice.\" },\n { name: \"report\", title: \"Report\", description: \"One reason among a few, details, and blocking the account in the same step.\" },\n ],\n },\n native: {\n entry: \"@krizaka/ui/native/form\",\n imports: [\"createKrizakaForm\", \"TextField\", \"DateField\", \"Switch\", \"RadioGroup\", \"Select\", \"Combobox\", \"Slider\", \"Money\", \"Submit\", \"FormError\"],\n differences: [\n \"Import `createKrizakaForm` from `@krizaka/ui/native/form`: the same definition (schema, defaults, i18n), the same `useForm`, `form.*` fields drawn with the native ones.\",\n \"`form.Form` is a View; `form.Submit` takes `label` and announces the first error of a refused submit (no focus to move).\",\n \"`DateField` is three wheels by default (`entry=\\\"parts\\\"`: typed day · month · year); `RadioGroup` has the native looks (`variant` list · cards · buttons · segmented); `Select` and `Combobox` open a `Sheet`; `Switch` is a settings row drawn by the theme; `Slider` floats its value; `Money` uses the decimal keypad. Each control plays its haptic when the app passes `haptics` to the `ThemeProvider`.\",\n \"No `Combobox`, `TagInput`, `FileUpload` or `Slider` field yet: a screen of its own (a picker) sets the value, or `form.Field` draws it.\",\n ],\n examples: [\n { name: \"sign-up\", title: \"Sign-up\", description: \"E-mail, a username checked as it is typed, the 18+ birth date as day · month · year, the terms.\" },\n { name: \"profile\", title: \"Profile\", description: \"Saved by a mutation of @krizaka/query; a category as chips, the list of links grows.\" },\n { name: \"upload\", title: \"Upload details\", description: \"Title, description and audience of a video picked from the library.\" },\n { name: \"amount\", title: \"Amount\", description: \"A tip in cents on the decimal keypad, typed the French way; presets set the field.\" },\n { name: \"audience\", title: \"Audience\", description: \"The price only when the video is for subscribers; comments on or off.\" },\n { name: \"settings\", title: \"Settings\", description: \"The system's switches and chips for a choice, saved in one go.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
2074
2085
|
},
|
|
2075
2086
|
{
|
|
2076
2087
|
"path": "form/money.ts",
|
|
@@ -2086,7 +2097,7 @@
|
|
|
2086
2097
|
},
|
|
2087
2098
|
{
|
|
2088
2099
|
"path": "meta.ts",
|
|
2089
|
-
"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"
|
|
2100
|
+
"content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"achievement\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"settings-layout\"\n | \"showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\ninterface ExampleBase {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: string;\n });\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
|
|
2090
2101
|
}
|
|
2091
2102
|
],
|
|
2092
2103
|
"dependencies": [
|