@krizaka/ui 3.25.0 → 3.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +2 -1
  2. package/dist/chunk-3S4GHSPZ.js +109 -0
  3. package/dist/chunk-3S4GHSPZ.js.map +1 -0
  4. package/dist/{chunk-FTX66XZY.js → chunk-BCGJZ5PG.js} +3 -3
  5. package/dist/{chunk-FTX66XZY.js.map → chunk-BCGJZ5PG.js.map} +1 -1
  6. package/dist/language-switch.d.ts +120 -0
  7. package/dist/language-switch.js +32 -0
  8. package/dist/language-switch.js.map +1 -0
  9. package/dist/nav-bar.d.ts +40 -2
  10. package/dist/nav-bar.js +68 -12
  11. package/dist/nav-bar.js.map +1 -1
  12. package/dist/site-footer.js +1 -1
  13. package/dist/sites.js +2 -2
  14. package/dist/sites.js.map +1 -1
  15. package/package.json +20 -6
  16. package/registry/account-menu.json +1 -1
  17. package/registry/achievement.json +1 -1
  18. package/registry/action-bar.json +1 -1
  19. package/registry/activity-feed.json +1 -1
  20. package/registry/alert.json +1 -1
  21. package/registry/app-header.json +1 -1
  22. package/registry/app-shell.json +1 -1
  23. package/registry/argument-card.json +1 -1
  24. package/registry/audio-player.json +1 -1
  25. package/registry/auth-layout.json +1 -1
  26. package/registry/avatar.json +1 -1
  27. package/registry/badge.json +1 -1
  28. package/registry/button.json +1 -1
  29. package/registry/captcha.json +1 -1
  30. package/registry/card-grid.json +1 -1
  31. package/registry/card.json +1 -1
  32. package/registry/catalogue-page.json +1 -1
  33. package/registry/changelog-page.json +1 -1
  34. package/registry/chat.json +1 -1
  35. package/registry/checkbox.json +1 -1
  36. package/registry/chip-link.json +1 -1
  37. package/registry/chip.json +1 -1
  38. package/registry/choice-grid.json +1 -1
  39. package/registry/code-block.json +1 -1
  40. package/registry/combobox.json +1 -1
  41. package/registry/command.json +1 -1
  42. package/registry/comparison-table.json +1 -1
  43. package/registry/component-page.json +1 -1
  44. package/registry/confirm-button.json +1 -1
  45. package/registry/contact-page.json +1 -1
  46. package/registry/continue-prompt.json +1 -1
  47. package/registry/countdown.json +1 -1
  48. package/registry/cover-hero.json +1 -1
  49. package/registry/cta-panel.json +1 -1
  50. package/registry/dashboard-layout.json +1 -1
  51. package/registry/data-table.json +1 -1
  52. package/registry/date-picker.json +1 -1
  53. package/registry/decision-list.json +1 -1
  54. package/registry/description-list.json +1 -1
  55. package/registry/detail-layout.json +1 -1
  56. package/registry/dialog.json +1 -1
  57. package/registry/docs-article-page.json +1 -1
  58. package/registry/docs-hub-page.json +1 -1
  59. package/registry/docs-layout.json +1 -1
  60. package/registry/docs-nav.json +1 -1
  61. package/registry/document-viewer.json +1 -1
  62. package/registry/dropdown-menu.json +1 -1
  63. package/registry/empty-state.json +1 -1
  64. package/registry/error-page.json +1 -1
  65. package/registry/examples/language-switch/inline.d.ts +4 -0
  66. package/registry/examples/language-switch/inline.js +11 -0
  67. package/registry/examples/language-switch/inline.tsx +6 -0
  68. package/registry/examples/language-switch/menu.d.ts +4 -0
  69. package/registry/examples/language-switch/menu.js +11 -0
  70. package/registry/examples/language-switch/menu.tsx +6 -0
  71. package/registry/examples/nav-bar/family-product.js +18 -10
  72. package/registry/examples/nav-bar/family-product.tsx +28 -27
  73. package/registry/field.json +1 -1
  74. package/registry/file-upload.json +1 -1
  75. package/registry/filter-bar.json +1 -1
  76. package/registry/force-update-screen.json +1 -1
  77. package/registry/form.json +1 -1
  78. package/registry/gallery-page.json +1 -1
  79. package/registry/genui.json +1 -1
  80. package/registry/glow-card.json +1 -1
  81. package/registry/goal-gauge.json +1 -1
  82. package/registry/how-it-works-page.json +1 -1
  83. package/registry/image-cropper.json +1 -1
  84. package/registry/index.json +37 -5
  85. package/registry/iso.json +1 -1
  86. package/registry/kanban.json +1 -1
  87. package/registry/kbd.json +1 -1
  88. package/registry/landing-page.json +1 -1
  89. package/registry/landscape.json +1 -1
  90. package/registry/language-switch.json +246 -0
  91. package/registry/legal-page.json +1 -1
  92. package/registry/link-card.json +1 -1
  93. package/registry/list.json +1 -1
  94. package/registry/loader.json +1 -1
  95. package/registry/longform-page.json +1 -1
  96. package/registry/maintenance-screen.json +1 -1
  97. package/registry/map.json +2 -2
  98. package/registry/markdown.json +1 -1
  99. package/registry/marketing-layout.json +1 -1
  100. package/registry/marquee.json +1 -1
  101. package/registry/mascot-quote.json +1 -1
  102. package/registry/mascot-state.json +1 -1
  103. package/registry/mascots.json +1 -1
  104. package/registry/match-dialog.json +1 -1
  105. package/registry/media-capture.json +1 -1
  106. package/registry/media-gallery.json +1 -1
  107. package/registry/media-grid.json +1 -1
  108. package/registry/meta-row.json +1 -1
  109. package/registry/meter.json +1 -1
  110. package/registry/nav-bar.json +138 -8
  111. package/registry/number-ticker.json +1 -1
  112. package/registry/opening-hours.json +1 -1
  113. package/registry/page-header.json +1 -1
  114. package/registry/page-hero.json +1 -1
  115. package/registry/page.json +1 -1
  116. package/registry/pagination.json +1 -1
  117. package/registry/photo-grid.json +1 -1
  118. package/registry/pointer-light.json +1 -1
  119. package/registry/popover.json +1 -1
  120. package/registry/pricing-page.json +1 -1
  121. package/registry/product-page.json +1 -1
  122. package/registry/profile-card.json +1 -1
  123. package/registry/progress.json +1 -1
  124. package/registry/promise-card.json +1 -1
  125. package/registry/radio-group.json +1 -1
  126. package/registry/rail-nav.json +2 -2
  127. package/registry/recipes.json +1 -1
  128. package/registry/scroll-stage.json +1 -1
  129. package/registry/search-field.json +1 -1
  130. package/registry/section-backdrop.json +1 -1
  131. package/registry/separator.json +1 -1
  132. package/registry/service-card.json +1 -1
  133. package/registry/settings-layout.json +1 -1
  134. package/registry/showcase.json +1 -1
  135. package/registry/site-footer.json +2 -2
  136. package/registry/sites.json +2 -2
  137. package/registry/skeleton.json +1 -1
  138. package/registry/skip-link.json +1 -1
  139. package/registry/slider.json +1 -1
  140. package/registry/slot-picker.json +1 -1
  141. package/registry/slot.json +1 -1
  142. package/registry/spinner.json +1 -1
  143. package/registry/split-view.json +1 -1
  144. package/registry/stage-banner.json +1 -1
  145. package/registry/stat.json +1 -1
  146. package/registry/status-page.json +1 -1
  147. package/registry/step-progress.json +1 -1
  148. package/registry/step-showcase.json +1 -1
  149. package/registry/stepper.json +1 -1
  150. package/registry/sticky-action.json +1 -1
  151. package/registry/swipe-deck.json +1 -1
  152. package/registry/switch.json +1 -1
  153. package/registry/tabs.json +1 -1
  154. package/registry/tag-input.json +1 -1
  155. package/registry/tag-selector.json +1 -1
  156. package/registry/terminal-replay.json +1 -1
  157. package/registry/theme.json +1 -1
  158. package/registry/toast.json +1 -1
  159. package/registry/toc.json +1 -1
  160. package/registry/tooltip.json +1 -1
  161. package/registry/tree-view.json +1 -1
  162. package/registry/update-prompt.json +1 -1
  163. package/registry/verified-badge.json +1 -1
  164. package/registry/vertical-video.json +1 -1
  165. package/registry/video-player.json +1 -1
  166. package/registry/video-trimmer.json +1 -1
  167. package/registry/virtual.json +1 -1
  168. package/registry/waveform.json +1 -1
package/README.md CHANGED
@@ -147,7 +147,8 @@ light or a product's — is a set of tokens.
147
147
  | `@krizaka/ui/skip-link` | `SkipLink` (`href`, default `#content`; the label as children): the first tab stop, hidden until focused, Enter moves focus to the content (WCAG 2.4.1) | client |
148
148
  | `@krizaka/ui/kbd` | `Kbd` (`size` sm·md), `kbdVariants` | server |
149
149
  | `@krizaka/ui/tabs` | `Tabs.Root/List/Trigger/Content` (`variant` underline·segmented·pills on the root, `orientation`, `value`/`defaultValue` + `onValueChange`; one light glides to the active tab), `tabs` (the slots) | client |
150
- | `@krizaka/ui/nav-bar` | `NavBar.Root` (`label`, `compactAfter`, `floating`) `/Brand` `/Family` (`current`, `labels`, `defaultOpen`: the family switcher on the brand, the mobile panel's last group inside `Mobile`) `/Badge` (`env`, `version`) `/Items/Link` (`active`) `/Menu` (`label`, `columns`) `/MenuLink` (`title`, `description`, `icon`) `/Actions` `/Repo` (`href`, `label`) `/Mobile` (`openLabel`, `closeLabel`) `/MobileLink`, `navBar` (the slots) | client |
150
+ | `@krizaka/ui/language-switch` | `LanguageSwitch` (`label`, `current`, `languages`, `variant`, `linkAs`), `languagesFor(path)`, `languageHref(path, to)` | client |
151
+ | `@krizaka/ui/nav-bar` | `NavBar.Root` (`label`, `compactAfter`, `floating`) `/Brand` `/Family` (`current`, `labels`, `defaultOpen`: the family switcher on the brand, the mobile panel's last group inside `Mobile`) `/Badge` (`env`, `version`) `/Items/Link` (`active`) `/Menu` (`label`, `columns`) `/MenuLink` (`title`, `description`, `icon`) `/Actions` `/Repo` (`href`, `label`) `/Language` (the language switch: a menu in the bar, a row in the mobile panel) `/Mobile` (`openLabel`, `closeLabel`) `/MobilePreferences` (`label`) `/MobileLink`, `navBar` (the slots) | client |
151
152
  | `@krizaka/ui/sites` | The Krizaka family as data: `sites` (id, name, url, host, kind, group, status, mark, repo), `siteGroups`, `siteById`, `sitesByGroup`, `familyLabels` (`en`, `fr`) | server |
152
153
  | `@krizaka/ui/site-footer` | `SiteFooter` (`current`, `labels`, `version`, `env`, `legal`; own columns as children) `.Column` (`title`) `.Link` (`active`, `asChild`): the footer of every Krizaka site — the family in columns, “A Krizaka company / product / open-source project”, the version, Montréal | client |
153
154
  | `@krizaka/ui/chip` | `Chip` (`selected`/`defaultSelected` + `onSelectedChange`, `size` sm·md; `removable` + `removeLabel` + `onRemove`), `Chip.Group` (`type` single·multiple, `label`, `required` for single), `chip` (the slots) | client |
@@ -0,0 +1,109 @@
1
+ "use client";
2
+
3
+ // src/language-switch/language-switch.tsx
4
+ import * as React from "react";
5
+ import { tv } from "tailwind-variants";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ var languageSwitch = tv({
8
+ slots: {
9
+ root: "relative inline-flex",
10
+ trigger: "group/language inline-flex h-9 shrink-0 select-none items-center gap-1.5 rounded-full px-2.5 font-mono text-xs font-semibold uppercase tracking-wide text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg aria-expanded:bg-surface-3 aria-expanded:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
11
+ glyph: "h-4 w-4 shrink-0",
12
+ panel: "kz-pop absolute right-0 top-full z-50 mt-2 grid min-w-44 max-w-[calc(100vw-2rem)] gap-0.5 rounded-xl border border-border-default bg-surface-1 p-1.5 shadow-lg",
13
+ heading: "px-2.5 pb-1 pt-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary",
14
+ item: "flex min-h-9 items-center justify-between gap-6 rounded-lg px-2.5 py-2 text-sm font-medium text-fg no-underline outline-hidden transition-colors hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring aria-[current=true]:bg-accent-soft",
15
+ check: "h-4 w-4 shrink-0 text-fg-accent",
16
+ inline: "inline-flex items-center gap-1 rounded-full border border-border-subtle bg-surface-2/60 p-0.5",
17
+ inlineLink: "inline-flex min-h-9 items-center rounded-full px-3 text-sm font-medium text-fg-secondary no-underline transition-colors hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=true]:bg-surface-3 aria-[current=true]:text-fg"
18
+ }
19
+ });
20
+ var s = languageSwitch();
21
+ var Globe = ({ className }) => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", className, fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
22
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
23
+ /* @__PURE__ */ jsx("path", { d: "M3 12h18M12 3c2.5 2.6 3.8 5.6 3.8 9S14.5 18.4 12 21c-2.5-2.6-3.8-5.6-3.8-9S9.5 5.6 12 3Z" })
24
+ ] });
25
+ function LanguageSwitch({ label, current, languages, variant = "menu", linkAs, defaultOpen = false, className }) {
26
+ const Link = linkAs ?? "a";
27
+ const id = React.useId();
28
+ const [open, setOpen] = React.useState(defaultOpen);
29
+ const root = React.useRef(null);
30
+ const trigger = React.useRef(null);
31
+ const active = languages.find((l) => l.code === current);
32
+ React.useEffect(() => {
33
+ if (!open) return;
34
+ const onPress = (e) => {
35
+ if (e.target instanceof Node && !root.current?.contains(e.target)) setOpen(false);
36
+ };
37
+ document.addEventListener("pointerdown", onPress);
38
+ return () => document.removeEventListener("pointerdown", onPress);
39
+ }, [open]);
40
+ if (variant === "inline") {
41
+ return /* @__PURE__ */ jsx("div", { role: "group", "aria-label": label, className: s.inline({ className }), children: languages.map((l) => /* @__PURE__ */ jsx(Link, { href: l.href, hrefLang: l.code, lang: l.code, "aria-current": l.code === current ? "true" : void 0, className: s.inlineLink(), children: l.name }, l.code)) });
42
+ }
43
+ const links = () => [...root.current?.querySelectorAll("[data-language]") ?? []];
44
+ const close = (focus) => {
45
+ setOpen(false);
46
+ if (focus) trigger.current?.focus();
47
+ };
48
+ return /* @__PURE__ */ jsxs(
49
+ "div",
50
+ {
51
+ ref: root,
52
+ className: s.root({ className }),
53
+ onKeyDown: (e) => {
54
+ if (e.key === "Escape" && open) {
55
+ e.stopPropagation();
56
+ close(true);
57
+ }
58
+ if (open && (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Home" || e.key === "End")) {
59
+ e.preventDefault();
60
+ const all = links();
61
+ const at = all.indexOf(document.activeElement);
62
+ const next = e.key === "Home" ? 0 : e.key === "End" ? all.length - 1 : (at + (e.key === "ArrowDown" ? 1 : -1) + all.length) % all.length;
63
+ all[next]?.focus();
64
+ }
65
+ },
66
+ onBlur: (e) => {
67
+ if (open && !root.current?.contains(e.relatedTarget)) setOpen(false);
68
+ },
69
+ children: [
70
+ /* @__PURE__ */ jsxs(
71
+ "button",
72
+ {
73
+ ref: trigger,
74
+ type: "button",
75
+ "aria-label": active ? `${label}: ${active.name}` : label,
76
+ "aria-expanded": open,
77
+ "aria-controls": id,
78
+ onClick: () => setOpen(!open),
79
+ onKeyDown: (e) => {
80
+ if (e.key === "ArrowDown") {
81
+ e.preventDefault();
82
+ setOpen(true);
83
+ requestAnimationFrame(() => (links().find((l) => l.getAttribute("aria-current") === "true") ?? links()[0])?.focus());
84
+ }
85
+ },
86
+ className: s.trigger(),
87
+ children: [
88
+ /* @__PURE__ */ jsx(Globe, { className: s.glyph() }),
89
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-code": current, className: "after:content-[attr(data-code)]" })
90
+ ]
91
+ }
92
+ ),
93
+ open && /* @__PURE__ */ jsxs("div", { id, role: "group", "aria-label": label, className: s.panel(), children: [
94
+ /* @__PURE__ */ jsx("p", { "aria-hidden": true, className: s.heading(), children: label }),
95
+ languages.map((l) => /* @__PURE__ */ jsxs(Link, { "data-language": "", href: l.href, hrefLang: l.code, lang: l.code, "aria-current": l.code === current ? "true" : void 0, className: s.item(), children: [
96
+ l.name,
97
+ l.code === current && /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: s.check(), fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) })
98
+ ] }, l.code))
99
+ ] })
100
+ ]
101
+ }
102
+ );
103
+ }
104
+
105
+ export {
106
+ languageSwitch,
107
+ LanguageSwitch
108
+ };
109
+ //# sourceMappingURL=chunk-3S4GHSPZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/language-switch/language-switch.tsx"],"sourcesContent":["// Client (a disclosure). The one language switch of the family: a globe and the current code (FR), opening a short list of\n// the languages by their own names — links with `hreflang` that keep the page. A disclosure, not a Radix menu: the items\n// are navigation, and the bar must not pay for a menu engine (Escape, outside press, focus-out and the arrows are here).\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport type { Language } from \"./href\";\n\nexport const languageSwitch = tv({\n slots: {\n root: \"relative inline-flex\",\n trigger:\n \"group/language inline-flex h-9 shrink-0 select-none items-center gap-1.5 rounded-full px-2.5 font-mono text-xs font-semibold uppercase tracking-wide text-fg-secondary transition-colors \" +\n \"hover:bg-surface-3 hover:text-fg aria-expanded:bg-surface-3 aria-expanded:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n glyph: \"h-4 w-4 shrink-0\",\n panel:\n \"kz-pop absolute right-0 top-full z-50 mt-2 grid min-w-44 max-w-[calc(100vw-2rem)] gap-0.5 rounded-xl border border-border-default bg-surface-1 p-1.5 shadow-lg\",\n heading: \"px-2.5 pb-1 pt-1.5 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n item:\n \"flex min-h-9 items-center justify-between gap-6 rounded-lg px-2.5 py-2 text-sm font-medium text-fg no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring aria-[current=true]:bg-accent-soft\",\n check: \"h-4 w-4 shrink-0 text-fg-accent\",\n inline: \"inline-flex items-center gap-1 rounded-full border border-border-subtle bg-surface-2/60 p-0.5\",\n inlineLink:\n \"inline-flex min-h-9 items-center rounded-full px-3 text-sm font-medium text-fg-secondary no-underline transition-colors hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=true]:bg-surface-3 aria-[current=true]:text-fg\",\n },\n});\nconst s = languageSwitch();\n\nexport type LanguageSwitchProps = {\n /** The control's accessible name (“Language”, “Langue”) — passed translated. */\n label: string;\n /** The code of the page's language (`fr`). */\n current: string;\n /** The languages offered, each with its name in its own language and the address of the same page (`languagesFor`). */\n languages: readonly Language[];\n /**\n * `menu`: a globe and the code, opening a menu (the bar). `inline`: every language in a row (a mobile panel, a settings page).\n * @default \"menu\"\n */\n variant?: \"menu\" | \"inline\";\n /** Your router's `<Link>` (it receives `href`, `hrefLang`, `lang`). A plain `<a>` otherwise. */\n linkAs?: React.ElementType;\n /** Opens the menu at first render (a story, a screenshot). */\n defaultOpen?: boolean;\n /** Classes of the control. */\n className?: string;\n};\n\nconst Globe = ({ className }: { className?: string }) => (\n <svg viewBox=\"0 0 24 24\" className={className} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M3 12h18M12 3c2.5 2.6 3.8 5.6 3.8 9S14.5 18.4 12 21c-2.5-2.6-3.8-5.6-3.8-9S9.5 5.6 12 3Z\" />\n </svg>\n);\n\n/**\n * The language switch, the same on every Krizaka site and app: a compact control — a globe and the current code\n * (`FR`) — that opens a menu of the languages named in their own language (Français · English), each a link with\n * `hreflang` to the same page. It shows the language you are in, never the “other” one. `variant=\"inline\"` lays the\n * languages in a row for a phone's panel or a settings page.\n */\nexport function LanguageSwitch({ label, current, languages, variant = \"menu\", linkAs, defaultOpen = false, className }: LanguageSwitchProps) {\n const Link = linkAs ?? \"a\";\n const id = React.useId();\n const [open, setOpen] = React.useState(defaultOpen);\n const root = React.useRef<HTMLDivElement>(null);\n const trigger = React.useRef<HTMLButtonElement>(null);\n const active = languages.find((l) => l.code === current);\n\n React.useEffect(() => {\n if (!open) return;\n // A press outside closes it (the page under it stays usable).\n const onPress = (e: PointerEvent) => {\n if (e.target instanceof Node && !root.current?.contains(e.target)) setOpen(false);\n };\n document.addEventListener(\"pointerdown\", onPress);\n return () => document.removeEventListener(\"pointerdown\", onPress);\n }, [open]);\n\n if (variant === \"inline\") {\n return (\n <div role=\"group\" aria-label={label} className={s.inline({ className })}>\n {languages.map((l) => (\n <Link key={l.code} href={l.href} hrefLang={l.code} lang={l.code} aria-current={l.code === current ? \"true\" : undefined} className={s.inlineLink()}>\n {l.name}\n </Link>\n ))}\n </div>\n );\n }\n\n const links = () => [...(root.current?.querySelectorAll<HTMLElement>(\"[data-language]\") ?? [])];\n const close = (focus: boolean) => {\n setOpen(false);\n if (focus) trigger.current?.focus();\n };\n return (\n <div\n ref={root}\n className={s.root({ className })}\n onKeyDown={(e) => {\n if (e.key === \"Escape\" && open) {\n e.stopPropagation();\n close(true);\n }\n if (open && (e.key === \"ArrowDown\" || e.key === \"ArrowUp\" || e.key === \"Home\" || e.key === \"End\")) {\n e.preventDefault();\n const all = links();\n const at = all.indexOf(document.activeElement as HTMLElement);\n const next = e.key === \"Home\" ? 0 : e.key === \"End\" ? all.length - 1 : (at + (e.key === \"ArrowDown\" ? 1 : -1) + all.length) % all.length;\n all[next]?.focus();\n }\n }}\n // Tab out of the switch closes it.\n onBlur={(e) => {\n if (open && !root.current?.contains(e.relatedTarget as Node | null)) setOpen(false);\n }}\n >\n <button\n ref={trigger}\n type=\"button\"\n aria-label={active ? `${label}: ${active.name}` : label}\n aria-expanded={open}\n aria-controls={id}\n onClick={() => setOpen(!open)}\n onKeyDown={(e) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setOpen(true);\n requestAnimationFrame(() => (links().find((l) => l.getAttribute(\"aria-current\") === \"true\") ?? links()[0])?.focus());\n }\n }}\n className={s.trigger()}\n >\n <Globe className={s.glyph()} />\n {/* Drawn by CSS: the code is no text of the button, so its name (`Language: English`) never mismatches what is visible. */}\n <span aria-hidden data-code={current} className=\"after:content-[attr(data-code)]\" />\n </button>\n {open && (\n <div id={id} role=\"group\" aria-label={label} className={s.panel()}>\n <p aria-hidden className={s.heading()}>\n {label}\n </p>\n {languages.map((l) => (\n <Link key={l.code} data-language=\"\" href={l.href} hrefLang={l.code} lang={l.code} aria-current={l.code === current ? \"true\" : undefined} className={s.item()}>\n {l.name}\n {l.code === current && (\n <svg viewBox=\"0 0 24 24\" className={s.check()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n )}\n </Link>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -50,7 +50,7 @@ var familyLabels = {
50
50
  orochia: "A video platform for creators.",
51
51
  yasakia: "The dating app where you talk first.",
52
52
  yororis: "A listening space with a face and a voice.",
53
- yomizaka: "Your documents, read and checked by Orazaka.",
53
+ yomizaka: "Yomizaka reads your leases, contracts and invoices.",
54
54
  kachizaka: "Predictions among friends \u2014 live data, in real time.",
55
55
  akinaza: "Running a shop in Qu\xE9bec, for less \u2014 Loi 25 by design.",
56
56
  ui: "Components, tokens, icons and charts \u2014 web and React Native.",
@@ -72,7 +72,7 @@ var familyLabels = {
72
72
  orochia: "Une plateforme vid\xE9o pour les cr\xE9ateurs.",
73
73
  yasakia: "L\u2019app de rencontre o\xF9 l\u2019on se parle d\u2019abord.",
74
74
  yororis: "Un espace d\u2019\xE9coute avec un visage et une voix.",
75
- yomizaka: "Vos documents, lus et v\xE9rifi\xE9s par Orazaka.",
75
+ yomizaka: "Yomizaka lit vos baux, contrats et factures.",
76
76
  kachizaka: "Les pronostics entre amis \u2014 des donn\xE9es en direct.",
77
77
  akinaza: "Tenir une boutique au Qu\xE9bec, pour moins cher \u2014 Loi 25 d\xE8s la conception.",
78
78
  ui: "Composants, jetons, ic\xF4nes et graphiques \u2014 web et React Native.",
@@ -92,4 +92,4 @@ export {
92
92
  sitesByGroup,
93
93
  familyLabels
94
94
  };
95
- //# sourceMappingURL=chunk-FTX66XZY.js.map
95
+ //# sourceMappingURL=chunk-BCGJZ5PG.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/family-mark.tsx","../src/sites/sites.ts"],"sourcesContent":["// The mark of a site of the family (`@krizaka/ui/sites`): its brand's `ProductLogo`, still (a list of marks never\n// moves), or GitHub's, in the text colour. Shared by `NavBar.Family` and `SiteFooter` — not an entry of its own.\nimport type * as React from \"react\";\n\nimport { ProductLogo } from \"./marks/ProductLogo\";\nimport type { SiteMark } from \"./sites/sites\";\n\n/** GitHub's mark, in the text colour (both themes). */\nexport function GitHubGlyph({ size = 16, className }: { size?: number; className?: string }) {\n return (\n <svg viewBox=\"0 0 16 16\" width={size} height={size} fill=\"currentColor\" aria-hidden className={className}>\n <path d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z\" />\n </svg>\n );\n}\n\n/** A site's mark, decorative (its name is always written beside it). */\nexport function FamilyMark({ mark, size }: { mark: SiteMark; size: number }): React.ReactElement {\n if (mark === \"github\") return <GitHubGlyph size={Math.round(size * 0.8)} />;\n return <ProductLogo id={mark} size={size} animated={false} />;\n}\n","// Server-safe: plain data. The Krizaka family — every site Krizaka runs, once. The family switcher of the bar\n// (`NavBar.Family`) and the common footer (`SiteFooter`) both read it, so adding a site is one release of this package:\n// every bar and every footer of the family follow.\n//\n// The words of the family (a site's one line, the groups, the statuses, the footer's signature) are brand data, written\n// here in English and French (`familyLabels`) — a site passes the set of its locale, or its own translation.\n\n/** A site of the family. */\nexport type SiteId = \"krizaka\" | \"orazaka\" | \"orochia\" | \"yasakia\" | \"yororis\" | \"yomizaka\" | \"kachizaka\" | \"akinaza\" | \"ui\" | \"java\" | \"github\";\n\n/** What a site is: the company, a product, or an open-source project. It chooses the footer's signature line. */\nexport type SiteKind = \"company\" | \"product\" | \"open-source\";\n\n/**\n * `released`: open to visit. `upcoming`: announced, not released yet (said \"Coming soon\"). `construction`: under\n * construction — nothing deployed beyond its page, nothing offered. `development`: being built in the open, nothing\n * deployed yet — its address is its repository (said \"In development\").\n */\nexport type SiteStatus = \"released\" | \"upcoming\" | \"construction\" | \"development\";\n\n/** The groups of the family, in reading order: Krizaka · Products · Open source · GitHub. */\nexport type SiteGroup = \"krizaka\" | \"products\" | \"open-source\" | \"github\";\n\n/** The mark a site wears: a brand of `ProductLogo` (Krizaka UI and Krizaka Java wear Krizaka's), or GitHub's. */\nexport type SiteMark = \"krizaka\" | \"orazaka\" | \"orochia\" | \"yasakia\" | \"yororis\" | \"yomizaka\" | \"kachizaka\" | \"akinaza\" | \"github\";\n\nexport interface Site {\n readonly id: SiteId;\n /** The name, never translated. */\n readonly name: string;\n /** The address of the site (its home). */\n readonly url: string;\n /** The address without the scheme, as people say it (`ui.krizaka.com`). */\n readonly host: string;\n readonly kind: SiteKind;\n readonly group: SiteGroup;\n readonly status: SiteStatus;\n readonly mark: SiteMark;\n /** The repository the site is built from. */\n readonly repo: string;\n}\n\nconst site = (s: Omit<Site, \"host\">): Site => ({ ...s, host: s.url.replace(/^https?:\\/\\//, \"\").replace(/\\/$/, \"\") });\n\n/** Every site of the family, in reading order (the group order, then the order inside each group). */\nexport const sites: readonly Site[] = [\n site({ id: \"krizaka\", name: \"Krizaka\", url: \"https://www.krizaka.com\", kind: \"company\", group: \"krizaka\", status: \"released\", mark: \"krizaka\", repo: \"https://github.com/krizaka/krizaka-com\" }),\n site({ id: \"orazaka\", name: \"Orazaka\", url: \"https://dev.orazaka.com\", kind: \"product\", group: \"products\", status: \"released\", mark: \"orazaka\", repo: \"https://github.com/krizaka/orazaka\" }),\n site({ id: \"orochia\", name: \"Orochia\", url: \"https://dev.orochia.com\", kind: \"product\", group: \"products\", status: \"released\", mark: \"orochia\", repo: \"https://github.com/krizaka/orochia\" }),\n site({ id: \"yasakia\", name: \"Yasakia\", url: \"https://dev.yasakia.com\", kind: \"product\", group: \"products\", status: \"development\", mark: \"yasakia\", repo: \"https://github.com/krizaka/yasakia\" }),\n site({ id: \"yororis\", name: \"Yororis\", url: \"https://dev.yororis.com\", kind: \"product\", group: \"products\", status: \"construction\", mark: \"yororis\", repo: \"https://github.com/krizaka/yororis\" }),\n site({ id: \"yomizaka\", name: \"Yomizaka\", url: \"https://dev.yomizaka.com\", kind: \"product\", group: \"products\", status: \"development\", mark: \"yomizaka\", repo: \"https://github.com/krizaka/yomizaka\" }),\n // In development: nothing deployed yet, so the address is the repository (never a dead link to an unborn dev site).\n site({ id: \"kachizaka\", name: \"Kachizaka\", url: \"https://github.com/krizaka/kachizaka\", kind: \"product\", group: \"products\", status: \"development\", mark: \"kachizaka\", repo: \"https://github.com/krizaka/kachizaka\" }),\n site({ id: \"akinaza\", name: \"Akinaza\", url: \"https://github.com/krizaka/akinaza\", kind: \"product\", group: \"products\", status: \"development\", mark: \"akinaza\", repo: \"https://github.com/krizaka/akinaza\" }),\n site({ id: \"ui\", name: \"Krizaka UI\", url: \"https://ui.krizaka.com\", kind: \"open-source\", group: \"open-source\", status: \"released\", mark: \"krizaka\", repo: \"https://github.com/krizaka/krizaka-ui\" }),\n site({ id: \"java\", name: \"Krizaka Java\", url: \"https://java.krizaka.com\", kind: \"open-source\", group: \"open-source\", status: \"released\", mark: \"krizaka\", repo: \"https://github.com/krizaka/krizaka-build\" }),\n site({ id: \"github\", name: \"GitHub\", url: \"https://github.com/krizaka\", kind: \"open-source\", group: \"github\", status: \"released\", mark: \"github\", repo: \"https://github.com/krizaka\" }),\n];\n\n/** The groups, in reading order. */\nexport const siteGroups: readonly SiteGroup[] = [\"krizaka\", \"products\", \"open-source\", \"github\"];\n\n/** A site by id. */\nexport function siteById(id: SiteId): Site {\n const found = sites.find((s) => s.id === id);\n if (!found) throw new Error(`Unknown Krizaka site \"${id}\".`);\n return found;\n}\n\n/** The family grouped, in reading order: `[{ group, sites }]`, empty groups left out. */\nexport function sitesByGroup(): { group: SiteGroup; sites: Site[] }[] {\n return siteGroups.map((group) => ({ group, sites: sites.filter((s) => s.group === group) })).filter((g) => g.sites.length > 0);\n}\n\n/** The languages the family's words are written in. */\nexport type SiteLocale = \"en\" | \"fr\";\n\n/** The words of the family: the switcher, the footer, each site's one line. */\nexport interface FamilyLabels {\n /** The family's name: the switcher's navigation and its button, the footer's navigation (“Krizaka family”). */\n family: string;\n /** The heading of each group. */\n groups: Record<SiteGroup, string>;\n /** The badge of a site that is not released. */\n status: Record<Exclude<SiteStatus, \"released\">, string>;\n /** Says which site is the current one (“You are here”). */\n current: string;\n /** One line per site: what it is. */\n descriptions: Record<SiteId, string>;\n /** The footer's signature, by the kind of the current site (“A Krizaka product”). */\n signature: Record<SiteKind, string>;\n /** The footer's place line (“Made in Montréal”). */\n place: string;\n /** The accessible name of the version (“Version”). */\n version: string;\n}\n\n/** The family's words in English and French. Pass `familyLabels[locale]`, or your own translation of the same shape. */\nexport const familyLabels: Record<SiteLocale, FamilyLabels> = {\n en: {\n family: \"Krizaka family\",\n groups: { krizaka: \"Krizaka\", products: \"Products\", \"open-source\": \"Open source\", github: \"GitHub\" },\n status: { upcoming: \"Coming soon\", construction: \"Under construction\", development: \"In development\" },\n current: \"You are here\",\n descriptions: {\n krizaka: \"The company: our products, our open source, our story.\",\n orazaka: \"Sovereign AI, on your own servers.\",\n orochia: \"A video platform for creators.\",\n yasakia: \"The dating app where you talk first.\",\n yororis: \"A listening space with a face and a voice.\",\n yomizaka: \"Your documents, read and checked by Orazaka.\",\n kachizaka: \"Predictions among friends — live data, in real time.\",\n akinaza: \"Running a shop in Québec, for less — Loi 25 by design.\",\n ui: \"Components, tokens, icons and charts — web and React Native.\",\n java: \"Spring Boot building blocks on Maven Central.\",\n github: \"Every repository, in the open.\",\n },\n signature: { company: \"A Krizaka company\", product: \"A Krizaka product\", \"open-source\": \"A Krizaka open-source project\" },\n place: \"Made in Montréal\",\n version: \"Version\",\n },\n fr: {\n family: \"Famille Krizaka\",\n groups: { krizaka: \"Krizaka\", products: \"Produits\", \"open-source\": \"Open source\", github: \"GitHub\" },\n status: { upcoming: \"Bientôt\", construction: \"En construction\", development: \"En développement\" },\n current: \"Vous êtes ici\",\n descriptions: {\n krizaka: \"La compagnie : nos produits, notre open source, notre histoire.\",\n orazaka: \"L’IA souveraine, sur vos propres serveurs.\",\n orochia: \"Une plateforme vidéo pour les créateurs.\",\n yasakia: \"L’app de rencontre où l’on se parle d’abord.\",\n yororis: \"Un espace d’écoute avec un visage et une voix.\",\n yomizaka: \"Vos documents, lus et vérifiés par Orazaka.\",\n kachizaka: \"Les pronostics entre amis — des données en direct.\",\n akinaza: \"Tenir une boutique au Québec, pour moins cher — Loi 25 dès la conception.\",\n ui: \"Composants, jetons, icônes et graphiques — web et React Native.\",\n java: \"Des briques Spring Boot sur Maven Central.\",\n github: \"Tous les dépôts, au grand jour.\",\n },\n signature: { company: \"Une compagnie Krizaka\", product: \"Un produit Krizaka\", \"open-source\": \"Un projet open source de Krizaka\" },\n place: \"Fait à Montréal\",\n version: \"Version\",\n },\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/family-mark.tsx","../src/sites/sites.ts"],"sourcesContent":["// The mark of a site of the family (`@krizaka/ui/sites`): its brand's `ProductLogo`, still (a list of marks never\n// moves), or GitHub's, in the text colour. Shared by `NavBar.Family` and `SiteFooter` — not an entry of its own.\nimport type * as React from \"react\";\n\nimport { ProductLogo } from \"./marks/ProductLogo\";\nimport type { SiteMark } from \"./sites/sites\";\n\n/** GitHub's mark, in the text colour (both themes). */\nexport function GitHubGlyph({ size = 16, className }: { size?: number; className?: string }) {\n return (\n <svg viewBox=\"0 0 16 16\" width={size} height={size} fill=\"currentColor\" aria-hidden className={className}>\n <path d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z\" />\n </svg>\n );\n}\n\n/** A site's mark, decorative (its name is always written beside it). */\nexport function FamilyMark({ mark, size }: { mark: SiteMark; size: number }): React.ReactElement {\n if (mark === \"github\") return <GitHubGlyph size={Math.round(size * 0.8)} />;\n return <ProductLogo id={mark} size={size} animated={false} />;\n}\n","// Server-safe: plain data. The Krizaka family — every site Krizaka runs, once. The family switcher of the bar\n// (`NavBar.Family`) and the common footer (`SiteFooter`) both read it, so adding a site is one release of this package:\n// every bar and every footer of the family follow.\n//\n// The words of the family (a site's one line, the groups, the statuses, the footer's signature) are brand data, written\n// here in English and French (`familyLabels`) — a site passes the set of its locale, or its own translation.\n\n/** A site of the family. */\nexport type SiteId = \"krizaka\" | \"orazaka\" | \"orochia\" | \"yasakia\" | \"yororis\" | \"yomizaka\" | \"kachizaka\" | \"akinaza\" | \"ui\" | \"java\" | \"github\";\n\n/** What a site is: the company, a product, or an open-source project. It chooses the footer's signature line. */\nexport type SiteKind = \"company\" | \"product\" | \"open-source\";\n\n/**\n * `released`: open to visit. `upcoming`: announced, not released yet (said \"Coming soon\"). `construction`: under\n * construction — nothing deployed beyond its page, nothing offered. `development`: being built in the open, nothing\n * deployed yet — its address is its repository (said \"In development\").\n */\nexport type SiteStatus = \"released\" | \"upcoming\" | \"construction\" | \"development\";\n\n/** The groups of the family, in reading order: Krizaka · Products · Open source · GitHub. */\nexport type SiteGroup = \"krizaka\" | \"products\" | \"open-source\" | \"github\";\n\n/** The mark a site wears: a brand of `ProductLogo` (Krizaka UI and Krizaka Java wear Krizaka's), or GitHub's. */\nexport type SiteMark = \"krizaka\" | \"orazaka\" | \"orochia\" | \"yasakia\" | \"yororis\" | \"yomizaka\" | \"kachizaka\" | \"akinaza\" | \"github\";\n\nexport interface Site {\n readonly id: SiteId;\n /** The name, never translated. */\n readonly name: string;\n /** The address of the site (its home). */\n readonly url: string;\n /** The address without the scheme, as people say it (`ui.krizaka.com`). */\n readonly host: string;\n readonly kind: SiteKind;\n readonly group: SiteGroup;\n readonly status: SiteStatus;\n readonly mark: SiteMark;\n /** The repository the site is built from. */\n readonly repo: string;\n}\n\nconst site = (s: Omit<Site, \"host\">): Site => ({ ...s, host: s.url.replace(/^https?:\\/\\//, \"\").replace(/\\/$/, \"\") });\n\n/** Every site of the family, in reading order (the group order, then the order inside each group). */\nexport const sites: readonly Site[] = [\n site({ id: \"krizaka\", name: \"Krizaka\", url: \"https://www.krizaka.com\", kind: \"company\", group: \"krizaka\", status: \"released\", mark: \"krizaka\", repo: \"https://github.com/krizaka/krizaka-com\" }),\n site({ id: \"orazaka\", name: \"Orazaka\", url: \"https://dev.orazaka.com\", kind: \"product\", group: \"products\", status: \"released\", mark: \"orazaka\", repo: \"https://github.com/krizaka/orazaka\" }),\n site({ id: \"orochia\", name: \"Orochia\", url: \"https://dev.orochia.com\", kind: \"product\", group: \"products\", status: \"released\", mark: \"orochia\", repo: \"https://github.com/krizaka/orochia\" }),\n site({ id: \"yasakia\", name: \"Yasakia\", url: \"https://dev.yasakia.com\", kind: \"product\", group: \"products\", status: \"development\", mark: \"yasakia\", repo: \"https://github.com/krizaka/yasakia\" }),\n site({ id: \"yororis\", name: \"Yororis\", url: \"https://dev.yororis.com\", kind: \"product\", group: \"products\", status: \"construction\", mark: \"yororis\", repo: \"https://github.com/krizaka/yororis\" }),\n site({ id: \"yomizaka\", name: \"Yomizaka\", url: \"https://dev.yomizaka.com\", kind: \"product\", group: \"products\", status: \"development\", mark: \"yomizaka\", repo: \"https://github.com/krizaka/yomizaka\" }),\n // In development: nothing deployed yet, so the address is the repository (never a dead link to an unborn dev site).\n site({ id: \"kachizaka\", name: \"Kachizaka\", url: \"https://github.com/krizaka/kachizaka\", kind: \"product\", group: \"products\", status: \"development\", mark: \"kachizaka\", repo: \"https://github.com/krizaka/kachizaka\" }),\n site({ id: \"akinaza\", name: \"Akinaza\", url: \"https://github.com/krizaka/akinaza\", kind: \"product\", group: \"products\", status: \"development\", mark: \"akinaza\", repo: \"https://github.com/krizaka/akinaza\" }),\n site({ id: \"ui\", name: \"Krizaka UI\", url: \"https://ui.krizaka.com\", kind: \"open-source\", group: \"open-source\", status: \"released\", mark: \"krizaka\", repo: \"https://github.com/krizaka/krizaka-ui\" }),\n site({ id: \"java\", name: \"Krizaka Java\", url: \"https://java.krizaka.com\", kind: \"open-source\", group: \"open-source\", status: \"released\", mark: \"krizaka\", repo: \"https://github.com/krizaka/krizaka-build\" }),\n site({ id: \"github\", name: \"GitHub\", url: \"https://github.com/krizaka\", kind: \"open-source\", group: \"github\", status: \"released\", mark: \"github\", repo: \"https://github.com/krizaka\" }),\n];\n\n/** The groups, in reading order. */\nexport const siteGroups: readonly SiteGroup[] = [\"krizaka\", \"products\", \"open-source\", \"github\"];\n\n/** A site by id. */\nexport function siteById(id: SiteId): Site {\n const found = sites.find((s) => s.id === id);\n if (!found) throw new Error(`Unknown Krizaka site \"${id}\".`);\n return found;\n}\n\n/** The family grouped, in reading order: `[{ group, sites }]`, empty groups left out. */\nexport function sitesByGroup(): { group: SiteGroup; sites: Site[] }[] {\n return siteGroups.map((group) => ({ group, sites: sites.filter((s) => s.group === group) })).filter((g) => g.sites.length > 0);\n}\n\n/** The languages the family's words are written in. */\nexport type SiteLocale = \"en\" | \"fr\";\n\n/** The words of the family: the switcher, the footer, each site's one line. */\nexport interface FamilyLabels {\n /** The family's name: the switcher's navigation and its button, the footer's navigation (“Krizaka family”). */\n family: string;\n /** The heading of each group. */\n groups: Record<SiteGroup, string>;\n /** The badge of a site that is not released. */\n status: Record<Exclude<SiteStatus, \"released\">, string>;\n /** Says which site is the current one (“You are here”). */\n current: string;\n /** One line per site: what it is. */\n descriptions: Record<SiteId, string>;\n /** The footer's signature, by the kind of the current site (“A Krizaka product”). */\n signature: Record<SiteKind, string>;\n /** The footer's place line (“Made in Montréal”). */\n place: string;\n /** The accessible name of the version (“Version”). */\n version: string;\n}\n\n/** The family's words in English and French. Pass `familyLabels[locale]`, or your own translation of the same shape. */\nexport const familyLabels: Record<SiteLocale, FamilyLabels> = {\n en: {\n family: \"Krizaka family\",\n groups: { krizaka: \"Krizaka\", products: \"Products\", \"open-source\": \"Open source\", github: \"GitHub\" },\n status: { upcoming: \"Coming soon\", construction: \"Under construction\", development: \"In development\" },\n current: \"You are here\",\n descriptions: {\n krizaka: \"The company: our products, our open source, our story.\",\n orazaka: \"Sovereign AI, on your own servers.\",\n orochia: \"A video platform for creators.\",\n yasakia: \"The dating app where you talk first.\",\n yororis: \"A listening space with a face and a voice.\",\n yomizaka: \"Yomizaka reads your leases, contracts and invoices.\",\n kachizaka: \"Predictions among friends — live data, in real time.\",\n akinaza: \"Running a shop in Québec, for less — Loi 25 by design.\",\n ui: \"Components, tokens, icons and charts — web and React Native.\",\n java: \"Spring Boot building blocks on Maven Central.\",\n github: \"Every repository, in the open.\",\n },\n signature: { company: \"A Krizaka company\", product: \"A Krizaka product\", \"open-source\": \"A Krizaka open-source project\" },\n place: \"Made in Montréal\",\n version: \"Version\",\n },\n fr: {\n family: \"Famille Krizaka\",\n groups: { krizaka: \"Krizaka\", products: \"Produits\", \"open-source\": \"Open source\", github: \"GitHub\" },\n status: { upcoming: \"Bientôt\", construction: \"En construction\", development: \"En développement\" },\n current: \"Vous êtes ici\",\n descriptions: {\n krizaka: \"La compagnie : nos produits, notre open source, notre histoire.\",\n orazaka: \"L’IA souveraine, sur vos propres serveurs.\",\n orochia: \"Une plateforme vidéo pour les créateurs.\",\n yasakia: \"L’app de rencontre où l’on se parle d’abord.\",\n yororis: \"Un espace d’écoute avec un visage et une voix.\",\n yomizaka: \"Yomizaka lit vos baux, contrats et factures.\",\n kachizaka: \"Les pronostics entre amis — des données en direct.\",\n akinaza: \"Tenir une boutique au Québec, pour moins cher — Loi 25 dès la conception.\",\n ui: \"Composants, jetons, icônes et graphiques — web et React Native.\",\n java: \"Des briques Spring Boot sur Maven Central.\",\n github: \"Tous les dépôts, au grand jour.\",\n },\n signature: { company: \"Une compagnie Krizaka\", product: \"Un produit Krizaka\", \"open-source\": \"Un projet open source de Krizaka\" },\n place: \"Fait à Montréal\",\n version: \"Version\",\n },\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,120 @@
1
+ import * as tailwind_variants from 'tailwind-variants';
2
+ import * as React from 'react';
3
+
4
+ /** A language the site speaks: its code (`fr`), its name in its own language (`Français`) and the address of the page in it. */
5
+ interface Language {
6
+ code: string;
7
+ name: string;
8
+ href: string;
9
+ }
10
+ /** The languages of the family, each named in its own language — the switch never shows “French” to a French reader. */
11
+ declare const languageNames: Readonly<Record<string, string>>;
12
+ /**
13
+ * The same page in another language: `/fr/baux?x=1#a` → `/en/baux?x=1#a`. The first segment is the language when it is
14
+ * one of `locales`; an address without a language prefix gets one. Query and hash are kept.
15
+ */
16
+ declare function languageHref(path: string, to: string, locales?: readonly string[]): string;
17
+ /** The `languages` of a switch for the page at `path`, in the order of `locales`, each named in its own language. */
18
+ declare function languagesFor(path: string, locales?: readonly string[]): Language[];
19
+
20
+ declare const languageSwitch: tailwind_variants.TVReturnType<{
21
+ [key: string]: {
22
+ [key: string]: tailwind_variants.ClassValue | {
23
+ heading?: tailwind_variants.ClassValue;
24
+ root?: tailwind_variants.ClassValue;
25
+ glyph?: tailwind_variants.ClassValue;
26
+ panel?: tailwind_variants.ClassValue;
27
+ check?: tailwind_variants.ClassValue;
28
+ inline?: tailwind_variants.ClassValue;
29
+ trigger?: tailwind_variants.ClassValue;
30
+ item?: tailwind_variants.ClassValue;
31
+ inlineLink?: tailwind_variants.ClassValue;
32
+ };
33
+ };
34
+ } | {
35
+ [x: string]: {
36
+ [x: string]: tailwind_variants.ClassValue | {
37
+ heading?: tailwind_variants.ClassValue;
38
+ root?: tailwind_variants.ClassValue;
39
+ glyph?: tailwind_variants.ClassValue;
40
+ panel?: tailwind_variants.ClassValue;
41
+ check?: tailwind_variants.ClassValue;
42
+ inline?: tailwind_variants.ClassValue;
43
+ trigger?: tailwind_variants.ClassValue;
44
+ item?: tailwind_variants.ClassValue;
45
+ inlineLink?: tailwind_variants.ClassValue;
46
+ };
47
+ };
48
+ } | {}, {
49
+ root: string;
50
+ trigger: string;
51
+ glyph: string;
52
+ panel: string;
53
+ heading: string;
54
+ item: string;
55
+ check: string;
56
+ inline: string;
57
+ inlineLink: string;
58
+ }, undefined, {
59
+ [key: string]: {
60
+ [key: string]: tailwind_variants.ClassValue | {
61
+ heading?: tailwind_variants.ClassValue;
62
+ root?: tailwind_variants.ClassValue;
63
+ glyph?: tailwind_variants.ClassValue;
64
+ panel?: tailwind_variants.ClassValue;
65
+ check?: tailwind_variants.ClassValue;
66
+ inline?: tailwind_variants.ClassValue;
67
+ trigger?: tailwind_variants.ClassValue;
68
+ item?: tailwind_variants.ClassValue;
69
+ inlineLink?: tailwind_variants.ClassValue;
70
+ };
71
+ };
72
+ } | {}, {
73
+ root: string;
74
+ trigger: string;
75
+ glyph: string;
76
+ panel: string;
77
+ heading: string;
78
+ item: string;
79
+ check: string;
80
+ inline: string;
81
+ inlineLink: string;
82
+ }, tailwind_variants.TVReturnTypeLike<unknown, {
83
+ root: string;
84
+ trigger: string;
85
+ glyph: string;
86
+ panel: string;
87
+ heading: string;
88
+ item: string;
89
+ check: string;
90
+ inline: string;
91
+ inlineLink: string;
92
+ }>>;
93
+ type LanguageSwitchProps = {
94
+ /** The control's accessible name (“Language”, “Langue”) — passed translated. */
95
+ label: string;
96
+ /** The code of the page's language (`fr`). */
97
+ current: string;
98
+ /** The languages offered, each with its name in its own language and the address of the same page (`languagesFor`). */
99
+ languages: readonly Language[];
100
+ /**
101
+ * `menu`: a globe and the code, opening a menu (the bar). `inline`: every language in a row (a mobile panel, a settings page).
102
+ * @default "menu"
103
+ */
104
+ variant?: "menu" | "inline";
105
+ /** Your router's `<Link>` (it receives `href`, `hrefLang`, `lang`). A plain `<a>` otherwise. */
106
+ linkAs?: React.ElementType;
107
+ /** Opens the menu at first render (a story, a screenshot). */
108
+ defaultOpen?: boolean;
109
+ /** Classes of the control. */
110
+ className?: string;
111
+ };
112
+ /**
113
+ * The language switch, the same on every Krizaka site and app: a compact control — a globe and the current code
114
+ * (`FR`) — that opens a menu of the languages named in their own language (Français · English), each a link with
115
+ * `hreflang` to the same page. It shows the language you are in, never the “other” one. `variant="inline"` lays the
116
+ * languages in a row for a phone's panel or a settings page.
117
+ */
118
+ declare function LanguageSwitch({ label, current, languages, variant, linkAs, defaultOpen, className }: LanguageSwitchProps): React.JSX.Element;
119
+
120
+ export { type Language, LanguageSwitch, type LanguageSwitchProps, languageHref, languageNames, languageSwitch, languagesFor };
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import {
3
+ LanguageSwitch,
4
+ languageSwitch
5
+ } from "./chunk-3S4GHSPZ.js";
6
+
7
+ // src/language-switch/href.ts
8
+ var languageNames = {
9
+ en: "English",
10
+ fr: "Fran\xE7ais",
11
+ es: "Espa\xF1ol",
12
+ de: "Deutsch",
13
+ it: "Italiano",
14
+ pt: "Portugu\xEAs"
15
+ };
16
+ function languageHref(path, to, locales = ["en", "fr"]) {
17
+ const [, pathname = "/", rest = ""] = /^([^?#]*)(.*)$/.exec(path) ?? [];
18
+ const segments = pathname.split("/").filter(Boolean);
19
+ if (segments[0] && locales.includes(segments[0])) segments.shift();
20
+ return `/${[to, ...segments].join("/")}${rest}`;
21
+ }
22
+ function languagesFor(path, locales = ["en", "fr"]) {
23
+ return locales.map((code) => ({ code, name: languageNames[code] ?? code.toUpperCase(), href: languageHref(path, code, locales) }));
24
+ }
25
+ export {
26
+ LanguageSwitch,
27
+ languageHref,
28
+ languageNames,
29
+ languageSwitch,
30
+ languagesFor
31
+ };
32
+ //# sourceMappingURL=language-switch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/language-switch/href.ts"],"sourcesContent":["/** A language the site speaks: its code (`fr`), its name in its own language (`Français`) and the address of the page in it. */\nexport interface Language {\n code: string;\n name: string;\n href: string;\n}\n\n/** The languages of the family, each named in its own language — the switch never shows “French” to a French reader. */\nexport const languageNames: Readonly<Record<string, string>> = {\n en: \"English\",\n fr: \"Français\",\n es: \"Español\",\n de: \"Deutsch\",\n it: \"Italiano\",\n pt: \"Português\",\n};\n\n/**\n * The same page in another language: `/fr/baux?x=1#a` → `/en/baux?x=1#a`. The first segment is the language when it is\n * one of `locales`; an address without a language prefix gets one. Query and hash are kept.\n */\nexport function languageHref(path: string, to: string, locales: readonly string[] = [\"en\", \"fr\"]): string {\n const [, pathname = \"/\", rest = \"\"] = /^([^?#]*)(.*)$/.exec(path) ?? [];\n const segments = pathname.split(\"/\").filter(Boolean);\n if (segments[0] && locales.includes(segments[0])) segments.shift();\n return `/${[to, ...segments].join(\"/\")}${rest}`;\n}\n\n/** The `languages` of a switch for the page at `path`, in the order of `locales`, each named in its own language. */\nexport function languagesFor(path: string, locales: readonly string[] = [\"en\", \"fr\"]): Language[] {\n return locales.map((code) => ({ code, name: languageNames[code] ?? code.toUpperCase(), href: languageHref(path, code, locales) }));\n}\n"],"mappings":";;;;;;;AAQO,IAAM,gBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAMO,SAAS,aAAa,MAAc,IAAY,UAA6B,CAAC,MAAM,IAAI,GAAW;AACxG,QAAM,CAAC,EAAE,WAAW,KAAK,OAAO,EAAE,IAAI,iBAAiB,KAAK,IAAI,KAAK,CAAC;AACtE,QAAM,WAAW,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AACnD,MAAI,SAAS,CAAC,KAAK,QAAQ,SAAS,SAAS,CAAC,CAAC,EAAG,UAAS,MAAM;AACjE,SAAO,IAAI,CAAC,IAAI,GAAG,QAAQ,EAAE,KAAK,GAAG,CAAC,GAAG,IAAI;AAC/C;AAGO,SAAS,aAAa,MAAc,UAA6B,CAAC,MAAM,IAAI,GAAe;AAChG,SAAO,QAAQ,IAAI,CAAC,UAAU,EAAE,MAAM,MAAM,cAAc,IAAI,KAAK,KAAK,YAAY,GAAG,MAAM,aAAa,MAAM,MAAM,OAAO,EAAE,EAAE;AACnI;","names":[]}
package/dist/nav-bar.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as tailwind_variants from 'tailwind-variants';
2
2
  import { NavigationMenu } from 'radix-ui';
3
3
  import * as React from 'react';
4
+ import { LanguageSwitchProps } from './language-switch.js';
4
5
  import { S as SiteId, F as FamilyLabels } from './sites-CHp_vELn.js';
5
6
 
6
7
  declare const navBar: tailwind_variants.TVReturnType<{
@@ -252,6 +253,18 @@ type NavBarBadgeProps = React.ComponentProps<"span"> & {
252
253
  };
253
254
  /** A small mono badge beside the brand: the environment and the version (`DEV · v1.2.3`). Hidden below `sm`. */
254
255
  declare function NavBarBadge({ env, version, className, children, ...props }: NavBarBadgeProps): React.JSX.Element;
256
+ type NavBarLanguageProps = Omit<LanguageSwitchProps, "variant">;
257
+ /**
258
+ * The language switch, the same on every site: in the bar a globe and the current code (FR) opening a short menu of
259
+ * the languages by their own names; inside `NavBar.Mobile` the same languages as a row in the preferences.
260
+ */
261
+ declare function NavBarLanguage(props: NavBarLanguageProps): React.JSX.Element;
262
+ type NavBarMobilePreferencesProps = Omit<React.ComponentProps<"div">, "title"> & {
263
+ /** The group's heading (“Preferences”) — passed translated. */
264
+ label: string;
265
+ };
266
+ /** The last group of the mobile panel: the preferences (theme, `NavBar.Language`), in a row under their heading. */
267
+ declare function NavBarMobilePreferences({ label, className, children, ...props }: NavBarMobilePreferencesProps): React.JSX.Element;
255
268
  type NavBarRepoProps = Omit<React.ComponentProps<"a">, "children"> & {
256
269
  /** The repository the site is built from. */
257
270
  href: string;
@@ -261,7 +274,7 @@ type NavBarRepoProps = Omit<React.ComponentProps<"a">, "children"> & {
261
274
  /** The site's repository on GitHub, an icon link of `NavBar.Actions`. */
262
275
  declare function NavBarRepo({ label, className, ...props }: NavBarRepoProps): React.JSX.Element;
263
276
  /**
264
- * `NavBar.Root/Brand/Family/Badge/Items/Link/Menu/MenuLink/Actions/Repo/Mobile/MobileGroup/MobileLink` — the Krizaka navigation bar,
277
+ * `NavBar.Root/Brand/Family/Badge/Items/Link/Menu/MenuLink/Actions/Repo/Language/Mobile/MobileGroup/MobilePreferences/MobileLink` — the Krizaka navigation bar,
265
278
  * the same on every site of the family: floating, tightening on scroll, the family switcher on the brand, one light
266
279
  * gliding between its items, accessible menus, a mobile panel.
267
280
  */
@@ -276,9 +289,34 @@ declare const NavBar: {
276
289
  MenuLink: typeof NavBarMenuLink;
277
290
  Actions: typeof NavBarActions;
278
291
  Repo: typeof NavBarRepo;
292
+ Language: typeof NavBarLanguage;
279
293
  Mobile: typeof NavBarMobile;
294
+ MobilePreferences: typeof NavBarMobilePreferences;
280
295
  MobileGroup: typeof NavBarMobileGroup;
281
296
  MobileLink: typeof NavBarMobileLink;
282
297
  };
283
298
 
284
- export { NavBar, NavBarActions, NavBarBadge, type NavBarBadgeProps, NavBarBrand, type NavBarBrandProps, NavBarFamily, type NavBarFamilyProps, NavBarItems, type NavBarItemsProps, NavBarLink, type NavBarLinkProps, NavBarMenu, NavBarMenuLink, type NavBarMenuLinkProps, type NavBarMenuProps, NavBarMobile, NavBarMobileGroup, type NavBarMobileGroupProps, NavBarMobileLink, type NavBarMobileLinkProps, type NavBarMobileProps, NavBarRepo, type NavBarRepoProps, NavBarRoot, type NavBarRootProps, navBar };
299
+ /**
300
+ * Where a menu panel of the bar sits: its left edge under its trigger's, pushed back inside the page when it would run
301
+ * past the right edge, never closer to the left edge than the gutter. All values are pixels of the page (a bounding
302
+ * rect's `left`, a width), the result is the panel's `left` relative to the bar's navigation (`root`). Pure, so a
303
+ * viewport of 360 px and one of 1280 px are tested without a browser.
304
+ */
305
+ interface PlacePanel {
306
+ /** The trigger's left edge. */
307
+ trigger: number;
308
+ /** The navigation's left edge (the panel is positioned inside it). */
309
+ root: number;
310
+ /** The panel's width. */
311
+ panel: number;
312
+ /** The window's width. */
313
+ window: number;
314
+ /**
315
+ * The space kept between the panel and the window's edges.
316
+ * @default 16
317
+ */
318
+ gutter?: number;
319
+ }
320
+ declare function placePanel({ trigger, root, panel, window, gutter }: PlacePanel): number;
321
+
322
+ export { NavBar, NavBarActions, NavBarBadge, type NavBarBadgeProps, NavBarBrand, type NavBarBrandProps, NavBarFamily, type NavBarFamilyProps, NavBarItems, type NavBarItemsProps, NavBarLanguage, type NavBarLanguageProps, NavBarLink, type NavBarLinkProps, NavBarMenu, NavBarMenuLink, type NavBarMenuLinkProps, type NavBarMenuProps, NavBarMobile, NavBarMobileGroup, type NavBarMobileGroupProps, NavBarMobileLink, type NavBarMobileLinkProps, NavBarMobilePreferences, type NavBarMobilePreferencesProps, type NavBarMobileProps, NavBarRepo, type NavBarRepoProps, NavBarRoot, type NavBarRootProps, type PlacePanel, navBar, placePanel };