@krizaka/ui 3.48.0 → 3.49.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.
@@ -26,6 +26,7 @@ var sites = [
26
26
  site({ id: "akinaza", name: "Akinaza", url: "https://www.akinaza.com", kind: "product", group: "products", status: "development", mark: "akinaza", repo: "https://github.com/krizaka/akinaza" }),
27
27
  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" }),
28
28
  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" }),
29
+ site({ id: "ts", name: "Krizaka TS", url: "https://ts.krizaka.com", kind: "open-source", group: "open-source", status: "released", mark: "krizaka", repo: "https://github.com/krizaka/krizaka-ts" }),
29
30
  // The motion lab: built in the open on GitHub Pages; what proves itself there moves into Krizaka UI.
30
31
  site({ id: "motion", name: "Krizaka Motion", url: "https://krizaka.github.io/krizaka-motion", kind: "open-source", group: "open-source", status: "development", mark: "krizaka", repo: "https://github.com/krizaka/krizaka-motion" }),
31
32
  site({ id: "github", name: "GitHub", url: "https://github.com/krizaka", kind: "open-source", group: "github", status: "released", mark: "github", repo: "https://github.com/krizaka" })
@@ -61,6 +62,7 @@ var familyLabels = {
61
62
  akinaza: "Running a shop in Qu\xE9bec, for less \u2014 Loi 25 by design.",
62
63
  ui: "Components, tokens, icons and charts \u2014 web and React Native.",
63
64
  java: "Spring Boot building blocks on Maven Central.",
65
+ ts: "TypeScript backend building blocks on npm.",
64
66
  motion: "The motion lab: the kit's motion, played live with its knobs.",
65
67
  github: "Every repository, in the open."
66
68
  },
@@ -86,6 +88,7 @@ var familyLabels = {
86
88
  akinaza: "Tenir une boutique au Qu\xE9bec, pour moins cher \u2014 Loi 25 d\xE8s la conception.",
87
89
  ui: "Composants, jetons, ic\xF4nes et graphiques \u2014 web et React Native.",
88
90
  java: "Des briques Spring Boot sur Maven Central.",
91
+ ts: "Des briques backend TypeScript sur npm.",
89
92
  motion: "Le labo du mouvement : le mouvement du kit, jou\xE9 en direct, avec ses r\xE9glages.",
90
93
  github: "Tous les d\xE9p\xF4ts, au grand jour."
91
94
  },
@@ -105,4 +108,4 @@ export {
105
108
  companyLinks,
106
109
  familyLabels
107
110
  };
108
- //# sourceMappingURL=chunk-P4TTKYC4.js.map
111
+ //# sourceMappingURL=chunk-4LYKBSOG.js.map
@@ -0,0 +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\" | \"ts\" | \"motion\" | \"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 offered (a QA-only app may run; the public page says so). `development`: being built in the\n * open, with a dev site (said \"In development\"). A site with none yet would use its repository as its address.\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://www.orazaka.com\", kind: \"product\", group: \"products\", status: \"released\", mark: \"orazaka\", repo: \"https://github.com/krizaka/orazaka\" }),\n site({ id: \"orochia\", name: \"Orochia\", url: \"https://www.orochia.com\", kind: \"product\", group: \"products\", status: \"released\", mark: \"orochia\", repo: \"https://github.com/krizaka/orochia\" }),\n site({ id: \"yasakia\", name: \"Yasakia\", url: \"https://www.yasakia.com\", kind: \"product\", group: \"products\", status: \"development\", mark: \"yasakia\", repo: \"https://github.com/krizaka/yasakia\" }),\n site({ id: \"yororis\", name: \"Yororis\", url: \"https://www.yororis.com\", kind: \"product\", group: \"products\", status: \"development\", mark: \"yororis\", repo: \"https://github.com/krizaka/yororis\" }),\n site({ id: \"yomizaka\", name: \"Yomizaka\", url: \"https://www.yomizaka.com\", kind: \"product\", group: \"products\", status: \"development\", mark: \"yomizaka\", repo: \"https://github.com/krizaka/yomizaka\" }),\n site({ id: \"kachizaka\", name: \"Kachizaka\", url: \"https://www.kachizaka.com\", kind: \"product\", group: \"products\", status: \"development\", mark: \"kachizaka\", repo: \"https://github.com/krizaka/kachizaka\" }),\n site({ id: \"akinaza\", name: \"Akinaza\", url: \"https://www.akinaza.com\", 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: \"ts\", name: \"Krizaka TS\", url: \"https://ts.krizaka.com\", kind: \"open-source\", group: \"open-source\", status: \"released\", mark: \"krizaka\", repo: \"https://github.com/krizaka/krizaka-ts\" }),\n // The motion lab: built in the open on GitHub Pages; what proves itself there moves into Krizaka UI.\n site({ id: \"motion\", name: \"Krizaka Motion\", url: \"https://krizaka.github.io/krizaka-motion\", kind: \"open-source\", group: \"open-source\", status: \"development\", mark: \"krizaka\", repo: \"https://github.com/krizaka/krizaka-motion\" }),\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 company's own pages, the footer's « Krizaka » column (the brand's home is the signature line's link). */\nexport type CompanyLinkId = \"story\" | \"blog\" | \"contact\";\nexport const companyLinks: readonly { id: CompanyLinkId; url: Record<SiteLocale, string> }[] = [\n { id: \"story\", url: { en: \"https://www.krizaka.com/story\", fr: \"https://www.krizaka.com/fr/story\" } },\n { id: \"blog\", url: { en: \"https://blog.krizaka.com\", fr: \"https://blog.krizaka.com\" } },\n { id: \"contact\", url: { en: \"https://www.krizaka.com/contact\", fr: \"https://www.krizaka.com/fr/contact\" } },\n];\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 /** The language of these words: the company links open in it. @default \"en\" */\n lang?: SiteLocale;\n /** The company's pages in the footer's « Krizaka » column. @default the English words */\n company?: Record<CompanyLinkId, 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: chat, documents, images and video — and the engine under the other products.\",\n orochia: \"A video platform for creators.\",\n yasakia: \"Meet real people: introductions, and photos checked before you see them.\",\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 ts: \"TypeScript backend building blocks on npm.\",\n motion: \"The motion lab: the kit's motion, played live with its knobs.\",\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 lang: \"en\",\n company: { story: \"Our story\", blog: \"Blog\", contact: \"Contact us\" },\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 : conversation, documents, images et vidéo — et le moteur des autres produits.\",\n orochia: \"Une plateforme vidéo pour les créateurs.\",\n yasakia: \"Rencontrez de vraies personnes : des présentations, et des photos vérifiées avant que vous les voyiez.\",\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 ts: \"Des briques backend TypeScript sur npm.\",\n motion: \"Le labo du mouvement : le mouvement du kit, joué en direct, avec ses réglages.\",\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 lang: \"fr\",\n company: { story: \"Notre histoire\", blog: \"Blogue\", contact: \"Nous écrire\" },\n },\n};\n"],"mappings":"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","names":[]}
package/dist/nav-bar.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as tailwind_variants from 'tailwind-variants';
2
2
  import { NavigationMenu } from 'radix-ui';
3
3
  import * as React from 'react';
4
4
  import { LanguageSwitchProps } from './language-switch.js';
5
- import { S as SiteId, F as FamilyLabels } from './sites-inztN_Bv.js';
5
+ import { S as SiteId, F as FamilyLabels } from './sites-DlR8jicc.js';
6
6
 
7
7
  declare const navBar: tailwind_variants.TVReturnType<{
8
8
  floating: {
package/dist/nav-bar.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  GitHubGlyph,
8
8
  familyLabels,
9
9
  sitesByGroup
10
- } from "./chunk-P4TTKYC4.js";
10
+ } from "./chunk-4LYKBSOG.js";
11
11
  import {
12
12
  useSlidingIndicator
13
13
  } from "./chunk-4QN5YJ3D.js";
@@ -43,6 +43,18 @@ declare const settingsLayout: tailwind_variants.TVReturnType<{
43
43
  nav: string;
44
44
  navList: string;
45
45
  navLink: string;
46
+ sidebar: string;
47
+ sidebarList: string;
48
+ group: string;
49
+ groupLabel: string;
50
+ groupLink: string;
51
+ groupItems: string;
52
+ menu: string;
53
+ menuSummary: string;
54
+ menuChevron: string;
55
+ menuPanel: string;
56
+ navBadge: string;
57
+ header: string;
46
58
  content: string;
47
59
  section: string;
48
60
  sectionHead: string;
@@ -76,6 +88,18 @@ declare const settingsLayout: tailwind_variants.TVReturnType<{
76
88
  nav: string;
77
89
  navList: string;
78
90
  navLink: string;
91
+ sidebar: string;
92
+ sidebarList: string;
93
+ group: string;
94
+ groupLabel: string;
95
+ groupLink: string;
96
+ groupItems: string;
97
+ menu: string;
98
+ menuSummary: string;
99
+ menuChevron: string;
100
+ menuPanel: string;
101
+ navBadge: string;
102
+ header: string;
79
103
  content: string;
80
104
  section: string;
81
105
  sectionHead: string;
@@ -109,6 +133,18 @@ declare const settingsLayout: tailwind_variants.TVReturnType<{
109
133
  nav: string;
110
134
  navList: string;
111
135
  navLink: string;
136
+ sidebar: string;
137
+ sidebarList: string;
138
+ group: string;
139
+ groupLabel: string;
140
+ groupLink: string;
141
+ groupItems: string;
142
+ menu: string;
143
+ menuSummary: string;
144
+ menuChevron: string;
145
+ menuPanel: string;
146
+ navBadge: string;
147
+ header: string;
112
148
  content: string;
113
149
  section: string;
114
150
  sectionHead: string;
@@ -134,14 +170,27 @@ interface SettingsNavItem {
134
170
  icon?: React.ReactNode;
135
171
  /** The section shown now: `aria-current`. */
136
172
  current?: boolean;
173
+ /** A small count or status after the label (`3`, `New`). */
174
+ badge?: React.ReactNode;
137
175
  }
176
+ interface SettingsNavGroup {
177
+ /** Its id. */
178
+ id: string;
179
+ /** The group's title (`Library`, `Money`). */
180
+ label: string;
181
+ items: readonly SettingsNavItem[];
182
+ }
183
+ /** Past this many sections a phone gets one menu instead of a strip of chips (« never more than about 5 items in a horizontal tab bar »). */
184
+ declare const SETTINGS_CHIPS_MAX = 5;
138
185
  type SettingsLayoutProps = Omit<React.ComponentProps<"div">, "title"> & {
139
186
  /** The screen's title (`Settings`). */
140
187
  title?: React.ReactNode;
141
188
  /** One sentence under it. */
142
189
  description?: React.ReactNode;
143
- /** The sections. */
144
- nav: readonly SettingsNavItem[];
190
+ /** What the screen is about, above the sections: who the account is, with their actions. Replaces `title` and `description`. */
191
+ header?: React.ReactNode;
192
+ /** The sections, alone or in titled groups. */
193
+ nav: readonly SettingsNavItem[] | readonly SettingsNavGroup[];
145
194
  /** The navigation's accessible name (`Settings sections`). */
146
195
  navLabel: string;
147
196
  /** The app's link component for the sections (Next's `Link`); an `<a>` otherwise. */
@@ -154,8 +203,8 @@ type SettingsLayoutProps = Omit<React.ComponentProps<"div">, "title"> & {
154
203
  /** The current section's forms (`SettingsSection`). */
155
204
  children: React.ReactNode;
156
205
  };
157
- /** A settings screen: sections on the left (chips on a phone), forms on the right. */
158
- declare function SettingsLayout({ title, description, nav, navLabel, linkAs: Link, variant, className, children, ...props }: SettingsLayoutProps): React.JSX.Element;
206
+ /** A settings screen: sections on the left (chips on a phone, one menu past five), forms on the right. */
207
+ declare function SettingsLayout({ title, description, header, nav, navLabel, linkAs: Link, variant, className, children, ...props }: SettingsLayoutProps): React.JSX.Element;
159
208
  interface SectionSaveLabels {
160
209
  /** “Save”. */
161
210
  save: string;
@@ -219,4 +268,4 @@ type SettingsRowProps = Omit<React.ComponentProps<"div">, "title"> & {
219
268
  /** One setting: its label and help on the left, its control or value on the right. Put rows in a flat `SettingsSection`. */
220
269
  declare function SettingsRow({ label, description, htmlFor, className, children, ...props }: SettingsRowProps): React.JSX.Element;
221
270
 
222
- export { SAVED_VISIBLE_MS, SaveStatus, type SaveTracker, SettingsLayout, type SettingsLayoutProps, type SettingsNavItem, SettingsRow, type SettingsRowProps, SettingsSection, type SettingsSectionProps, settingsLayout, useSaveStatus };
271
+ export { SAVED_VISIBLE_MS, SETTINGS_CHIPS_MAX, SaveStatus, type SaveTracker, SettingsLayout, type SettingsLayoutProps, type SettingsNavGroup, type SettingsNavItem, SettingsRow, type SettingsRowProps, SettingsSection, type SettingsSectionProps, settingsLayout, useSaveStatus };
@@ -186,6 +186,19 @@ var settingsLayout = tv2({
186
186
  nav: "-mx-4 overflow-x-auto overflow-y-hidden px-4 py-1 -my-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden lg:mx-0 lg:overflow-visible lg:px-0 lg:py-0 lg:my-0",
187
187
  navList: "flex w-max gap-2 lg:sticky lg:top-20 lg:w-auto lg:flex-col lg:gap-0.5",
188
188
  navLink: "flex h-9 items-center gap-2.5 whitespace-nowrap rounded-sm border border-border-default bg-surface-1 px-3.5 text-sm font-medium text-fg-secondary no-underline hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current]:border-accent aria-[current]:text-fg lg:rounded-lg lg:border-transparent lg:bg-transparent lg:px-3 lg:hover:bg-surface-2 lg:aria-[current]:bg-accent-soft [&>svg]:h-4 [&>svg]:w-4",
189
+ // Past five sections a strip of chips is a puzzle: the sidebar groups them, a phone gets one menu (a disclosure: no script).
190
+ sidebar: "hidden lg:block",
191
+ sidebarList: "flex flex-col gap-6 lg:sticky lg:top-20",
192
+ group: "flex flex-col gap-1",
193
+ groupLabel: "px-3 text-xs font-semibold text-fg-secondary",
194
+ groupLink: "flex h-9 items-center gap-2.5 whitespace-nowrap rounded-lg px-3 text-sm font-medium text-fg-secondary no-underline hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current]:bg-accent-soft aria-[current]:text-fg [&>svg]:h-4 [&>svg]:w-4",
195
+ groupItems: "m-0 flex list-none flex-col gap-0.5 p-0",
196
+ menu: "group lg:hidden",
197
+ menuSummary: "flex h-11 cursor-pointer list-none items-center justify-between gap-3 rounded-lg border border-border-default bg-surface-1 px-3.5 text-sm font-medium text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-details-marker]:hidden group-open:border-accent",
198
+ menuChevron: "h-4 w-4 shrink-0 text-fg-secondary transition-transform group-open:rotate-180 motion-reduce:transition-none",
199
+ menuPanel: "mt-2 rounded-lg border border-border-default bg-surface-1 p-2",
200
+ navBadge: "ml-auto font-mono text-[11px] text-fg-secondary",
201
+ header: "flex flex-wrap items-center gap-x-4 gap-y-3 border-b border-border-subtle pb-5",
189
202
  content: "flex min-w-0 flex-col gap-6",
190
203
  section: "rounded-xl border border-border-default bg-surface-1",
191
204
  sectionHead: "flex flex-col gap-1 px-5 pt-5",
@@ -215,18 +228,45 @@ var settingsLayout = tv2({
215
228
  },
216
229
  defaultVariants: { variant: "card" }
217
230
  });
218
- function SettingsLayout({ title, description, nav, navLabel, linkAs: Link = "a", variant = "card", className, children, ...props }) {
231
+ var SETTINGS_CHIPS_MAX = 5;
232
+ var isGrouped = (nav) => nav.length > 0 && "items" in nav[0];
233
+ function NavLink({ item, Link, className }) {
234
+ const s = settingsLayout();
235
+ return /* @__PURE__ */ jsxs3(Link, { href: item.href, className, "aria-current": item.current ? item.href.startsWith("#") ? "location" : "page" : void 0, children: [
236
+ item.icon,
237
+ item.label,
238
+ item.badge !== void 0 && /* @__PURE__ */ jsx3("span", { className: s.navBadge(), children: item.badge })
239
+ ] });
240
+ }
241
+ function NavGroups({ groups, Link }) {
242
+ const s = settingsLayout();
243
+ return /* @__PURE__ */ jsx3("div", { className: s.sidebarList(), children: groups.map((group) => /* @__PURE__ */ jsxs3("div", { className: s.group(), children: [
244
+ /* @__PURE__ */ jsx3("h2", { className: s.groupLabel(), children: group.label }),
245
+ /* @__PURE__ */ jsx3("ul", { className: s.groupItems(), children: group.items.map((item) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(NavLink, { item, Link, className: s.groupLink() }) }, item.id)) })
246
+ ] }, group.id)) });
247
+ }
248
+ function SettingsLayout({ title, description, header, nav, navLabel, linkAs: Link = "a", variant = "card", className, children, ...props }) {
219
249
  const s = settingsLayout({ variant });
250
+ const groups = isGrouped(nav) ? nav : [{ id: "sections", label: "", items: nav }];
251
+ const items = groups.flatMap((g) => g.items);
252
+ const asMenu = items.length > SETTINGS_CHIPS_MAX;
253
+ const current = items.find((i) => i.current);
220
254
  return /* @__PURE__ */ jsxs3("div", { className: s.root({ className }), ...props, children: [
221
- (title || description) && /* @__PURE__ */ jsxs3("div", { className: s.head(), children: [
255
+ header ? /* @__PURE__ */ jsx3("div", { className: s.header(), children: header }) : (title || description) && /* @__PURE__ */ jsxs3("div", { className: s.head(), children: [
222
256
  title && /* @__PURE__ */ jsx3("h1", { className: s.title(), children: title }),
223
257
  description && /* @__PURE__ */ jsx3("p", { className: s.description(), children: description })
224
258
  ] }),
225
259
  /* @__PURE__ */ jsxs3("div", { className: s.columns(), children: [
226
- /* @__PURE__ */ jsx3("nav", { "aria-label": navLabel, className: s.nav(), children: /* @__PURE__ */ jsx3("ul", { className: s.navList(), children: nav.map((item) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs3(Link, { href: item.href, className: s.navLink(), "aria-current": item.current ? item.href.startsWith("#") ? "location" : "page" : void 0, children: [
227
- item.icon,
228
- item.label
229
- ] }) }, item.id)) }) }),
260
+ asMenu ? /* @__PURE__ */ jsxs3("nav", { "aria-label": navLabel, children: [
261
+ /* @__PURE__ */ jsxs3("details", { className: s.menu(), "data-settings-menu": "", children: [
262
+ /* @__PURE__ */ jsxs3("summary", { className: s.menuSummary(), children: [
263
+ /* @__PURE__ */ jsx3("span", { className: "truncate", children: current?.label ?? navLabel }),
264
+ /* @__PURE__ */ jsx3("svg", { "aria-hidden": true, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", className: s.menuChevron(), children: /* @__PURE__ */ jsx3("path", { d: "m4 6 4 4 4-4" }) })
265
+ ] }),
266
+ /* @__PURE__ */ jsx3("div", { className: s.menuPanel(), children: /* @__PURE__ */ jsx3(NavGroups, { groups, Link }) })
267
+ ] }),
268
+ /* @__PURE__ */ jsx3("div", { className: s.sidebar(), children: /* @__PURE__ */ jsx3(NavGroups, { groups, Link }) })
269
+ ] }) : /* @__PURE__ */ jsx3("nav", { "aria-label": navLabel, className: s.nav(), children: /* @__PURE__ */ jsx3("ul", { className: s.navList(), children: items.map((item) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(NavLink, { item, Link, className: s.navLink() }) }, item.id)) }) }),
230
270
  /* @__PURE__ */ jsx3("div", { className: s.content(), children })
231
271
  ] })
232
272
  ] });
@@ -285,6 +325,7 @@ function SettingsRow({ label, description, htmlFor, className, children, ...prop
285
325
  }
286
326
  export {
287
327
  SAVED_VISIBLE_MS,
328
+ SETTINGS_CHIPS_MAX,
288
329
  SaveStatus,
289
330
  SettingsLayout,
290
331
  SettingsRow,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/settings-layout/save-status.ts","../src/settings-layout/settings-layout.tsx","../src/button/button.tsx","../src/spinner/glyph.tsx","../src/button/recipe.ts"],"sourcesContent":["import * as React from \"react\";\n\n/** Where an inline save is: nothing to say, in flight, done (for a moment), or failed. */\nexport const SaveStatus = { Idle: \"idle\", Saving: \"saving\", Saved: \"saved\", Error: \"error\" } as const;\nexport type SaveStatus = (typeof SaveStatus)[keyof typeof SaveStatus];\n\n/** How long « Saved » stays before the section goes quiet again. */\n// @fixed: an interface timing (how long a confirmation reads), not a business setting.\nexport const SAVED_VISIBLE_MS = 4000;\n\nexport interface SaveTracker {\n status: SaveStatus;\n /** The failure of the last try, for the message beside the button. */\n error: unknown;\n /** Runs a save: `saving`, then `saved` (back to `idle` after a moment) or `error`. Never rejects. */\n track: (save: () => Promise<unknown>) => Promise<boolean>;\n /** Clears the status (the form was edited again, or the save was undone). */\n reset: () => void;\n}\n\n/** The status of one section's save. Feed it to `SettingsSection`'s `save.status`. */\nexport function useSaveStatus(): SaveTracker {\n const [status, setStatus] = React.useState<SaveStatus>(SaveStatus.Idle);\n const [error, setError] = React.useState<unknown>(undefined);\n const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const clear = React.useCallback(() => {\n if (timer.current) clearTimeout(timer.current);\n timer.current = undefined;\n }, []);\n React.useEffect(() => clear, [clear]);\n const reset = React.useCallback(() => {\n clear();\n setStatus(SaveStatus.Idle);\n setError(undefined);\n }, [clear]);\n const track = React.useCallback(\n async (save: () => Promise<unknown>) => {\n clear();\n setStatus(SaveStatus.Saving);\n setError(undefined);\n try {\n await save();\n setStatus(SaveStatus.Saved);\n timer.current = setTimeout(() => setStatus(SaveStatus.Idle), SAVED_VISIBLE_MS);\n return true;\n } catch (failure) {\n setError(failure);\n setStatus(SaveStatus.Error);\n return false;\n }\n },\n [clear],\n );\n return { status, error, track, reset };\n}\n","// Server-safe. Settings, read from the products' account and administration screens: a list of sections on the left\n// (a row of chips that scrolls on a phone), the current section's forms on the right, each form a titled card with its\n// own save at the foot. The sections are links (one page each) or anchors (one long page).\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { Button } from \"../button/button\";\nimport { SaveStatus } from \"./save-status\";\n\nexport const settingsLayout = tv({\n slots: {\n root: \"flex w-full flex-col gap-6\",\n head: \"flex flex-col gap-1\",\n title: \"font-display text-2xl font-extrabold tracking-tight text-fg\",\n description: \"max-w-2xl text-sm text-fg-secondary\",\n columns: \"grid grid-cols-1 gap-6 lg:grid-cols-[13rem_minmax(0,1fr)] lg:gap-10\",\n nav: \"-mx-4 overflow-x-auto overflow-y-hidden px-4 py-1 -my-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden lg:mx-0 lg:overflow-visible lg:px-0 lg:py-0 lg:my-0\",\n navList: \"flex w-max gap-2 lg:sticky lg:top-20 lg:w-auto lg:flex-col lg:gap-0.5\",\n navLink:\n \"flex h-9 items-center gap-2.5 whitespace-nowrap rounded-sm border border-border-default bg-surface-1 px-3.5 text-sm font-medium text-fg-secondary no-underline \" +\n \"hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current]:border-accent aria-[current]:text-fg \" +\n \"lg:rounded-lg lg:border-transparent lg:bg-transparent lg:px-3 lg:hover:bg-surface-2 lg:aria-[current]:bg-accent-soft [&>svg]:h-4 [&>svg]:w-4\",\n content: \"flex min-w-0 flex-col gap-6\",\n section: \"rounded-xl border border-border-default bg-surface-1\",\n sectionHead: \"flex flex-col gap-1 px-5 pt-5\",\n sectionTitle: \"text-base font-bold text-fg\",\n sectionDescription: \"text-sm text-fg-secondary\",\n sectionBody: \"flex flex-col gap-4 px-5 py-5\",\n sectionFooter: \"flex flex-wrap items-center justify-end gap-2 border-t border-border-subtle bg-surface-2/40 px-5 py-3 rounded-b-xl\",\n // The flat look: no card, hairline-divided rows (Linear / Vercel / GitHub settings).\n rows: \"flex flex-col divide-y divide-border-subtle border-y border-border-subtle\",\n row: \"flex flex-col gap-2 py-4 sm:flex-row sm:items-center sm:justify-between sm:gap-6\",\n rowText: \"flex min-w-0 flex-col gap-0.5\",\n rowLabel: \"text-sm font-medium text-fg\",\n rowDescription: \"text-sm text-fg-secondary\",\n rowControl: \"flex min-w-0 shrink-0 items-center gap-2 text-sm text-fg sm:max-w-[60%] sm:justify-end\",\n },\n variants: {\n variant: {\n card: {},\n flat: {\n content: \"max-w-[45rem] gap-10\",\n section: \"rounded-none border-0 bg-transparent\",\n sectionHead: \"gap-0.5 px-0 pb-3 pt-0\",\n sectionBody: \"gap-0 px-0 py-0\",\n sectionFooter: \"justify-start border-t-0 bg-transparent px-0 pb-0 pt-4\",\n },\n },\n },\n defaultVariants: { variant: \"card\" },\n});\n\nexport interface SettingsNavItem {\n /** Its id (the anchor of a section on one long page). */\n id: string;\n /** Its words. */\n label: string;\n /** Where it leads: a page (`/settings/billing`) or an anchor (`#billing`). */\n href: string;\n /** Its icon (a signature icon). */\n icon?: React.ReactNode;\n /** The section shown now: `aria-current`. */\n current?: boolean;\n}\n\nexport type SettingsLayoutProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n /** The screen's title (`Settings`). */\n title?: React.ReactNode;\n /** One sentence under it. */\n description?: React.ReactNode;\n /** The sections. */\n nav: readonly SettingsNavItem[];\n /** The navigation's accessible name (`Settings sections`). */\n navLabel: string;\n /** The app's link component for the sections (Next's `Link`); an `<a>` otherwise. */\n linkAs?: React.ElementType;\n /**\n * `card` (each form a titled card) or `flat` (one calm column of at most 45rem, rows divided by hairlines).\n * @default \"card\"\n */\n variant?: \"card\" | \"flat\";\n /** The current section's forms (`SettingsSection`). */\n children: React.ReactNode;\n};\n\n/** A settings screen: sections on the left (chips on a phone), forms on the right. */\nexport function SettingsLayout({ title, description, nav, navLabel, linkAs: Link = \"a\", variant = \"card\", className, children, ...props }: SettingsLayoutProps) {\n const s = settingsLayout({ variant });\n return (\n <div className={s.root({ className })} {...props}>\n {(title || description) && (\n <div className={s.head()}>\n {title && <h1 className={s.title()}>{title}</h1>}\n {description && <p className={s.description()}>{description}</p>}\n </div>\n )}\n <div className={s.columns()}>\n <nav aria-label={navLabel} className={s.nav()}>\n <ul className={s.navList()}>\n {nav.map((item) => (\n <li key={item.id}>\n <Link href={item.href} className={s.navLink()} aria-current={item.current ? (item.href.startsWith(\"#\") ? \"location\" : \"page\") : undefined}>\n {item.icon}\n {item.label}\n </Link>\n </li>\n ))}\n </ul>\n </nav>\n <div className={s.content()}>{children}</div>\n </div>\n </div>\n );\n}\n\nexport interface SectionSaveLabels {\n /** “Save”. */\n save: string;\n /** “Saving…”. */\n saving: string;\n /** “Saved”. */\n saved: string;\n /** What failed, one sentence (“Could not save. Try again.”). */\n error: string;\n /** “Undo” — only shown after a save, when `onUndo` is given. */\n undo: string;\n /** “Discard changes” — only shown while dirty, when `onDiscard` is given. */\n discard: string;\n}\n\n/** The one save of a section: its dirty state, its status, its words. */\nexport interface SectionSave {\n /** Whether a field differs from what is saved: the save is enabled only then. */\n dirty: boolean;\n /** Where the save is (`useSaveStatus().status`). */\n status: SaveStatus;\n /** Saves the section (the form's submit, Enter included). */\n onSave: () => void;\n /** Puts the previous values back after a save, optimistic update included. */\n onUndo?: () => void;\n /** Throws the edits away while dirty. */\n onDiscard?: () => void;\n labels: SectionSaveLabels;\n}\n\nexport type SettingsSectionProps = Omit<React.ComponentProps<\"section\">, \"title\"> & {\n /**\n * One save for the whole section: the section becomes a form, the foot shows the Save (enabled while dirty), the\n * result in a live region (saved, or the error as an alert) and an undo after a save. Replaces `footer`.\n */\n save?: SectionSave;\n /** The section's title: it names the region (and is its anchor's target when `id` is set). */\n title: React.ReactNode;\n /** What these settings change, in one sentence. */\n description?: React.ReactNode;\n /** At the foot: the section's own save (a Button), a note. */\n footer?: React.ReactNode;\n /**\n * `card` (a titled card) or `flat` (no card: a title, then hairline-divided `SettingsRow`s).\n * @default \"card\"\n */\n variant?: \"card\" | \"flat\";\n /** The fields (or, flat, the `SettingsRow`s). A section of a title, a sentence and a foot action has none. */\n children?: React.ReactNode;\n};\n\n/** A group of settings: a titled card with its fields and its own save, or flat rows under a title. */\nexport function SettingsSection({ title, description, footer, save, variant = \"card\", id, className, children, ...props }: SettingsSectionProps) {\n const s = settingsLayout({ variant });\n const auto = React.useId();\n const titleId = `${id ?? auto}-title`;\n const head = (\n <div className={s.sectionHead()}>\n <h2 id={titleId} className={s.sectionTitle()}>\n {title}\n </h2>\n {description && <p className={s.sectionDescription()}>{description}</p>}\n </div>\n );\n const body = children === undefined ? null : (\n <div className={variant === \"flat\" ? s.rows() : s.sectionBody()} data-settings-rows={variant === \"flat\" ? \"\" : undefined}>\n {children}\n </div>\n );\n return (\n <section id={id} aria-labelledby={titleId} data-dirty={save?.dirty ? \"\" : undefined} className={s.section({ className })} {...props}>\n {save ? (\n <form\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n if (save.dirty && save.status !== SaveStatus.Saving) save.onSave();\n }}\n >\n {head}\n {body}\n <div className={s.sectionFooter()}>\n <SaveFeedback save={save} />\n {save.onDiscard && save.dirty ? (\n <Button type=\"button\" variant=\"ghost\" onClick={save.onDiscard}>\n {save.labels.discard}\n </Button>\n ) : null}\n <Button type=\"submit\" variant=\"primary\" disabled={!save.dirty || save.status === SaveStatus.Saving} loading={save.status === SaveStatus.Saving}>\n {save.labels.save}\n </Button>\n </div>\n </form>\n ) : (\n <>\n {head}\n {body}\n {footer && <div className={s.sectionFooter()}>{footer}</div>}\n </>\n )}\n </section>\n );\n}\n\n/** The result of the last save: « Saved » (with Undo) in a polite live region, a failure as an alert. The region stays mounted so the change is announced. */\nfunction SaveFeedback({ save }: { save: SectionSave }) {\n const failed = save.status === SaveStatus.Error;\n const saved = save.status === SaveStatus.Saved;\n return (\n <div className=\"mr-auto flex min-w-0 items-center gap-2 text-sm\">\n <span role={failed ? \"alert\" : \"status\"} aria-live={failed ? \"assertive\" : \"polite\"} className={failed ? \"text-danger-text\" : \"text-fg-secondary\"}>\n {failed ? save.labels.error : saved ? save.labels.saved : save.status === SaveStatus.Saving ? save.labels.saving : \"\"}\n </span>\n {saved && save.onUndo ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={save.onUndo}>\n {save.labels.undo}\n </Button>\n ) : null}\n </div>\n );\n}\n\nexport type SettingsRowProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n /** What the setting is (`Username`). */\n label: React.ReactNode;\n /** The help text under it, one sentence. */\n description?: React.ReactNode;\n /** The id of the control, so the label names it (a `<label htmlFor>`). Leave out for a plain value. */\n htmlFor?: string;\n /** The control or the value, on the right (stacked under the label on a phone). */\n children?: React.ReactNode;\n};\n\n/** One setting: its label and help on the left, its control or value on the right. Put rows in a flat `SettingsSection`. */\nexport function SettingsRow({ label, description, htmlFor, className, children, ...props }: SettingsRowProps) {\n const s = settingsLayout();\n const Label = htmlFor ? \"label\" : \"span\";\n return (\n <div className={s.row({ className })} {...props}>\n <div className={s.rowText()}>\n <Label className={s.rowLabel()} {...(htmlFor ? { htmlFor } : {})}>\n {label}\n </Label>\n {description && <span className={s.rowDescription()}>{description}</span>}\n </div>\n {children !== undefined && <div className={s.rowControl()}>{children}</div>}\n </div>\n );\n}\n","// Server-safe: no hook, no context. `buttonVariants` styles a <Link> or a Server Component without the component.\nimport { Slot } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { LoaderGlyph } from \"../spinner/glyph\";\nimport { buttonRecipe } from \"./recipe\";\n\nexport const buttonVariants = tv(buttonRecipe);\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>;\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n ButtonVariants & {\n /**\n * The look: `primary` (the accent), `gradient` (accent → accent-2), `secondary` (a raised surface), `outline`,\n * `ghost`, `danger`.\n * @default \"secondary\"\n */\n variant?: ButtonVariants[\"variant\"];\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"md\"\n */\n size?: ButtonVariants[\"size\"];\n /**\n * `rounded` (an 8 px radius, the radius of a field) · `pill`. `cut` is deprecated (krizaka-ui#393: the chamfer is\n * retired) — it draws `rounded` and warns in development; it will be removed in 4.0.\n * @default \"rounded\"\n */\n shape?: ButtonVariants[\"shape\"];\n /** Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. */\n asChild?: boolean;\n /**\n * Disables and signals the wait (aria-busy, data-loading) and shows the Krizaka loader glyph before the label; the\n * button keeps its colour.\n */\n loading?: boolean;\n /**\n * Between 0 and 1, while `loading`: the glyph's trace and a light fill across the button grow to the value (an\n * upload, an export).\n */\n progress?: number | null;\n /**\n * How the action ended: `success` draws a check, `error` a cross (and the button shakes its head once); a ring of\n * the status colour opens around it. Clear it to return to rest.\n */\n status?: \"success\" | \"error\" | null;\n /** A message announced politely by assistive technology when it changes (“Saved”, “Upload failed”) — passed translated. */\n announce?: string;\n /**\n * Shows the glyph while `loading` / with a `status`. `false`: you draw your own.\n * @default true\n */\n indicator?: boolean;\n /** @deprecated Since 3.31 (krizaka-ui#393): does nothing — the hover is quiet. Removed in 4.0. */\n shimmer?: boolean;\n /** @deprecated Since 3.31 (krizaka-ui#393): does nothing — a button never moves under the pointer. Removed in 4.0. */\n magnetic?: boolean;\n };\n\nconst STATUS_TEXT = { success: \"text-fg-success\", error: \"text-fg-danger\" } as const;\n\nlet warnedCut = false;\n/** `shape=\"cut\"` (the chamfer) is retired: one warning per page in development, never in production. */\nfunction warnCut() {\n if (warnedCut || typeof process === \"undefined\" || process.env.NODE_ENV === \"production\") return;\n warnedCut = true;\n console.warn('@krizaka/ui/button: shape=\"cut\" is deprecated (krizaka-ui#393) and now draws the default rounded shape. Remove the prop.');\n}\n\nexport function Button({\n asChild,\n loading,\n progress,\n status,\n announce,\n indicator = true,\n shimmer: _shimmer,\n magnetic: _magnetic,\n variant,\n size,\n shape,\n className,\n disabled,\n children,\n ...props\n}: ButtonProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n if (shape === \"cut\") warnCut();\n const known = loading && typeof progress === \"number\" && Number.isFinite(progress);\n const ratio = known ? Math.min(1, Math.max(0, progress)) : 0;\n const glyph = indicator && !asChild && (loading || status) ? (status ?? \"loading\") : null;\n // On a filled variant the glyph stays in the label's colour; on the others it takes the status colour.\n const filled = variant === \"primary\" || variant === \"gradient\";\n const button = (\n <Comp\n type={asChild ? undefined : \"button\"}\n aria-busy={loading || undefined}\n data-loading={loading ? \"\" : undefined}\n data-variant={variant ?? \"secondary\"}\n data-kz-status={status ?? undefined}\n disabled={asChild ? undefined : disabled || loading}\n aria-disabled={asChild && (disabled || loading) ? true : undefined}\n className={buttonVariants({\n variant,\n size,\n shape,\n className: cn(known && \"relative isolate overflow-hidden\", className),\n })}\n {...props}\n >\n {asChild ? (\n children\n ) : (\n <>\n {known && (\n <span aria-hidden className=\"absolute inset-y-0 left-0 -z-10 bg-current opacity-15 transition-[width] duration-500\" style={{ width: `${ratio * 100}%` }} />\n )}\n {glyph && (\n <span\n aria-hidden\n data-slot=\"indicator\"\n className=\"-my-1 grid grid-cols-[1fr] transition-[grid-template-columns,margin,opacity] duration-300 starting:-mr-2 starting:grid-cols-[0fr] starting:opacity-0\"\n >\n <span className=\"overflow-hidden\">\n <LoaderGlyph state={glyph} value={known ? ratio : null} className={cn(\"h-4 w-4\", glyph !== \"loading\" && !filled && STATUS_TEXT[glyph])} />\n </span>\n </span>\n )}\n {children}\n </>\n )}\n </Comp>\n );\n if (announce === undefined || asChild) return button;\n return (\n <>\n {button}\n <span role=\"status\" className=\"sr-only\">\n {announce}\n </span>\n </>\n );\n}\n\nexport type IconButtonProps = Omit<ButtonProps, \"size\"> & {\n /** The accessible name (and tooltip) of the button — required, passed translated. */\n label: string;\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"icon\"\n */\n size?: ButtonProps[\"size\"];\n};\n\n/** An icon-only button: `label` is required, it is its accessible name. Loading or with a status, the glyph takes the icon's place. */\nexport function IconButton({ label, size = \"icon\", children, ...props }: IconButtonProps) {\n const swap = props.indicator !== false && !props.asChild && (props.loading || props.status);\n return (\n <Button aria-label={label} title={label} size={size} {...props}>\n {swap ? null : children}\n </Button>\n );\n}\n","// The Krizaka loader glyph, server-safe: a hexagon (the marks' shape) traced by a segment of light around a node that\n// breathes; determinate, it fills to its value; done, the trace closes and a check (or a cross) draws itself. Animated by\n// motion.css (`kz-loader-*`); still under prefers-reduced-motion — a segment at rest and the node lit, never blank.\nimport type * as React from \"react\";\n\n/** The hexagon, pointy top, radius 9 around (12, 12). */\nconst HEX = \"M12 3 19.79 7.5 19.79 16.5 12 21 4.21 16.5 4.21 7.5Z\";\n\n/** What the glyph shows: a wait (with an optional value), or how it ended. */\nexport type LoaderGlyphState = \"loading\" | \"success\" | \"error\";\n\nexport interface LoaderGlyphProps extends Omit<React.ComponentProps<\"svg\">, \"children\"> {\n /** loading · success · error. */\n state?: LoaderGlyphState;\n /** Between 0 and 1: a determinate wait, the trace grows to it. Absent: indeterminate. */\n value?: number | null;\n}\n\n/** The glyph alone (decorative): the Spinner, the Loader and the Button's `loading` draw it. */\nexport function LoaderGlyph({ state = \"loading\", value, ...props }: LoaderGlyphProps) {\n const known = typeof value === \"number\" && Number.isFinite(value);\n const fill = state !== \"loading\" ? 100 : known ? Math.round(Math.min(1, Math.max(0, value)) * 100) : null;\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden data-state={state} data-progress={fill === null ? undefined : fill} {...props}>\n <path d={HEX} stroke=\"currentColor\" strokeOpacity=\"0.22\" strokeWidth=\"2.2\" strokeLinejoin=\"round\" />\n <path\n d={HEX}\n pathLength={100}\n stroke=\"currentColor\"\n strokeWidth=\"2.2\"\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n className=\"kz-loader-trace\"\n strokeDasharray={fill === null ? undefined : `${fill} ${100 - fill}`}\n />\n {state === \"loading\" && <circle cx=\"12\" cy=\"12\" r=\"2.6\" fill=\"currentColor\" className=\"kz-loader-node\" />}\n {state === \"success\" && <path d=\"M8.3 12.3l2.6 2.6 4.9-5.3\" pathLength={20} stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className=\"kz-loader-mark\" />}\n {state === \"error\" && <path d=\"M9.4 9.4l5.2 5.2M14.6 9.4l-5.2 5.2\" pathLength={20} stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\" className=\"kz-loader-mark\" />}\n </svg>\n );\n}\n","// The button's variants, written once as classes of the Krizaka tokens: the web wears them with `tv(buttonRecipe)`\n// (button.tsx), React Native reads the same classes into styles (native/button.tsx, `nativeRecipe`).\nimport { defineRecipe } from \"../recipes/define\";\n\nexport const buttonRecipe = defineRecipe({\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition duration-150 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 \" +\n \"disabled:pointer-events-none disabled:opacity-40 data-loading:cursor-progress active:scale-[0.98] \" +\n // Busy is not unavailable: a loading button keeps its colour (it is disabled only so it is not pressed twice).\n \"[&[data-loading]]:opacity-100\",\n variants: {\n variant: {\n primary: \"bg-accent text-on-accent shadow-sm hover:bg-accent-hover\",\n // The brand's signature: accent → accent-2 (on-accent reads on both stops, tested in @krizaka/tokens).\n gradient: \"bg-linear-to-r from-accent to-accent-2 text-on-accent shadow-sm hover:brightness-110\",\n secondary: \"border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3\",\n outline: \"border border-border-default bg-transparent text-fg hover:border-accent hover:text-fg-accent\",\n ghost: \"text-fg-secondary hover:bg-surface-2 hover:text-fg\",\n // The border and the tint carry the danger; the label stays the primary text role, legible on the tint at rest\n // and hovered over every surface (`text-fg-danger` is for text set directly on a surface).\n danger: \"border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20\",\n },\n size: { sm: \"h-8 px-3 text-xs\", md: \"h-10 px-4 text-sm\", lg: \"h-12 px-6 text-sm\", icon: \"h-10 w-10\" },\n // One clean shape (krizaka-ui#393): an 8 px radius (`--kz-radius-sm`, the radius of a field — a button beside an\n // input lines up with it), the pill on demand. `cut` (the retired chamfer) is a deprecated alias of `rounded`.\n shape: { rounded: \"rounded-sm\", pill: \"rounded-full\", cut: \"rounded-sm\" },\n },\n defaultVariants: { variant: \"secondary\", size: \"md\", shape: \"rounded\" },\n});\n"],"mappings":"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","names":["React","tv","jsx","jsxs","Fragment","jsx","jsxs","tv"]}
1
+ {"version":3,"sources":["../src/settings-layout/save-status.ts","../src/settings-layout/settings-layout.tsx","../src/button/button.tsx","../src/spinner/glyph.tsx","../src/button/recipe.ts"],"sourcesContent":["import * as React from \"react\";\n\n/** Where an inline save is: nothing to say, in flight, done (for a moment), or failed. */\nexport const SaveStatus = { Idle: \"idle\", Saving: \"saving\", Saved: \"saved\", Error: \"error\" } as const;\nexport type SaveStatus = (typeof SaveStatus)[keyof typeof SaveStatus];\n\n/** How long « Saved » stays before the section goes quiet again. */\n// @fixed: an interface timing (how long a confirmation reads), not a business setting.\nexport const SAVED_VISIBLE_MS = 4000;\n\nexport interface SaveTracker {\n status: SaveStatus;\n /** The failure of the last try, for the message beside the button. */\n error: unknown;\n /** Runs a save: `saving`, then `saved` (back to `idle` after a moment) or `error`. Never rejects. */\n track: (save: () => Promise<unknown>) => Promise<boolean>;\n /** Clears the status (the form was edited again, or the save was undone). */\n reset: () => void;\n}\n\n/** The status of one section's save. Feed it to `SettingsSection`'s `save.status`. */\nexport function useSaveStatus(): SaveTracker {\n const [status, setStatus] = React.useState<SaveStatus>(SaveStatus.Idle);\n const [error, setError] = React.useState<unknown>(undefined);\n const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const clear = React.useCallback(() => {\n if (timer.current) clearTimeout(timer.current);\n timer.current = undefined;\n }, []);\n React.useEffect(() => clear, [clear]);\n const reset = React.useCallback(() => {\n clear();\n setStatus(SaveStatus.Idle);\n setError(undefined);\n }, [clear]);\n const track = React.useCallback(\n async (save: () => Promise<unknown>) => {\n clear();\n setStatus(SaveStatus.Saving);\n setError(undefined);\n try {\n await save();\n setStatus(SaveStatus.Saved);\n timer.current = setTimeout(() => setStatus(SaveStatus.Idle), SAVED_VISIBLE_MS);\n return true;\n } catch (failure) {\n setError(failure);\n setStatus(SaveStatus.Error);\n return false;\n }\n },\n [clear],\n );\n return { status, error, track, reset };\n}\n","// Server-safe. Settings, read from the products' account and administration screens: a list of sections on the left\n// (a row of chips that scrolls on a phone, a disclosure menu past five), the current section's forms on the right, each\n// form a titled card with its own save at the foot. The sections are links (one page each) or anchors (one long page),\n// alone or in titled groups.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { Button } from \"../button/button\";\nimport { SaveStatus } from \"./save-status\";\n\nexport const settingsLayout = tv({\n slots: {\n root: \"flex w-full flex-col gap-6\",\n head: \"flex flex-col gap-1\",\n title: \"font-display text-2xl font-extrabold tracking-tight text-fg\",\n description: \"max-w-2xl text-sm text-fg-secondary\",\n columns: \"grid grid-cols-1 gap-6 lg:grid-cols-[13rem_minmax(0,1fr)] lg:gap-10\",\n nav: \"-mx-4 overflow-x-auto overflow-y-hidden px-4 py-1 -my-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden lg:mx-0 lg:overflow-visible lg:px-0 lg:py-0 lg:my-0\",\n navList: \"flex w-max gap-2 lg:sticky lg:top-20 lg:w-auto lg:flex-col lg:gap-0.5\",\n navLink:\n \"flex h-9 items-center gap-2.5 whitespace-nowrap rounded-sm border border-border-default bg-surface-1 px-3.5 text-sm font-medium text-fg-secondary no-underline \" +\n \"hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current]:border-accent aria-[current]:text-fg \" +\n \"lg:rounded-lg lg:border-transparent lg:bg-transparent lg:px-3 lg:hover:bg-surface-2 lg:aria-[current]:bg-accent-soft [&>svg]:h-4 [&>svg]:w-4\",\n // Past five sections a strip of chips is a puzzle: the sidebar groups them, a phone gets one menu (a disclosure: no script).\n sidebar: \"hidden lg:block\",\n sidebarList: \"flex flex-col gap-6 lg:sticky lg:top-20\",\n group: \"flex flex-col gap-1\",\n groupLabel: \"px-3 text-xs font-semibold text-fg-secondary\",\n groupLink:\n \"flex h-9 items-center gap-2.5 whitespace-nowrap rounded-lg px-3 text-sm font-medium text-fg-secondary no-underline hover:bg-surface-2 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current]:bg-accent-soft aria-[current]:text-fg [&>svg]:h-4 [&>svg]:w-4\",\n groupItems: \"m-0 flex list-none flex-col gap-0.5 p-0\",\n menu: \"group lg:hidden\",\n menuSummary:\n \"flex h-11 cursor-pointer list-none items-center justify-between gap-3 rounded-lg border border-border-default bg-surface-1 px-3.5 text-sm font-medium text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-details-marker]:hidden group-open:border-accent\",\n menuChevron: \"h-4 w-4 shrink-0 text-fg-secondary transition-transform group-open:rotate-180 motion-reduce:transition-none\",\n menuPanel: \"mt-2 rounded-lg border border-border-default bg-surface-1 p-2\",\n navBadge: \"ml-auto font-mono text-[11px] text-fg-secondary\",\n header: \"flex flex-wrap items-center gap-x-4 gap-y-3 border-b border-border-subtle pb-5\",\n content: \"flex min-w-0 flex-col gap-6\",\n section: \"rounded-xl border border-border-default bg-surface-1\",\n sectionHead: \"flex flex-col gap-1 px-5 pt-5\",\n sectionTitle: \"text-base font-bold text-fg\",\n sectionDescription: \"text-sm text-fg-secondary\",\n sectionBody: \"flex flex-col gap-4 px-5 py-5\",\n sectionFooter: \"flex flex-wrap items-center justify-end gap-2 border-t border-border-subtle bg-surface-2/40 px-5 py-3 rounded-b-xl\",\n // The flat look: no card, hairline-divided rows (Linear / Vercel / GitHub settings).\n rows: \"flex flex-col divide-y divide-border-subtle border-y border-border-subtle\",\n row: \"flex flex-col gap-2 py-4 sm:flex-row sm:items-center sm:justify-between sm:gap-6\",\n rowText: \"flex min-w-0 flex-col gap-0.5\",\n rowLabel: \"text-sm font-medium text-fg\",\n rowDescription: \"text-sm text-fg-secondary\",\n rowControl: \"flex min-w-0 shrink-0 items-center gap-2 text-sm text-fg sm:max-w-[60%] sm:justify-end\",\n },\n variants: {\n variant: {\n card: {},\n flat: {\n content: \"max-w-[45rem] gap-10\",\n section: \"rounded-none border-0 bg-transparent\",\n sectionHead: \"gap-0.5 px-0 pb-3 pt-0\",\n sectionBody: \"gap-0 px-0 py-0\",\n sectionFooter: \"justify-start border-t-0 bg-transparent px-0 pb-0 pt-4\",\n },\n },\n },\n defaultVariants: { variant: \"card\" },\n});\n\nexport interface SettingsNavItem {\n /** Its id (the anchor of a section on one long page). */\n id: string;\n /** Its words. */\n label: string;\n /** Where it leads: a page (`/settings/billing`) or an anchor (`#billing`). */\n href: string;\n /** Its icon (a signature icon). */\n icon?: React.ReactNode;\n /** The section shown now: `aria-current`. */\n current?: boolean;\n /** A small count or status after the label (`3`, `New`). */\n badge?: React.ReactNode;\n}\n\nexport interface SettingsNavGroup {\n /** Its id. */\n id: string;\n /** The group's title (`Library`, `Money`). */\n label: string;\n items: readonly SettingsNavItem[];\n}\n\n/** Past this many sections a phone gets one menu instead of a strip of chips (« never more than about 5 items in a horizontal tab bar »). */\nexport const SETTINGS_CHIPS_MAX = 5;\n\nconst isGrouped = (nav: readonly SettingsNavItem[] | readonly SettingsNavGroup[]): nav is readonly SettingsNavGroup[] => nav.length > 0 && \"items\" in nav[0]!;\n\nexport type SettingsLayoutProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n /** The screen's title (`Settings`). */\n title?: React.ReactNode;\n /** One sentence under it. */\n description?: React.ReactNode;\n /** What the screen is about, above the sections: who the account is, with their actions. Replaces `title` and `description`. */\n header?: React.ReactNode;\n /** The sections, alone or in titled groups. */\n nav: readonly SettingsNavItem[] | readonly SettingsNavGroup[];\n /** The navigation's accessible name (`Settings sections`). */\n navLabel: string;\n /** The app's link component for the sections (Next's `Link`); an `<a>` otherwise. */\n linkAs?: React.ElementType;\n /**\n * `card` (each form a titled card) or `flat` (one calm column of at most 45rem, rows divided by hairlines).\n * @default \"card\"\n */\n variant?: \"card\" | \"flat\";\n /** The current section's forms (`SettingsSection`). */\n children: React.ReactNode;\n};\n\nfunction NavLink({ item, Link, className }: { item: SettingsNavItem; Link: React.ElementType; className: string }) {\n const s = settingsLayout();\n return (\n <Link href={item.href} className={className} aria-current={item.current ? (item.href.startsWith(\"#\") ? \"location\" : \"page\") : undefined}>\n {item.icon}\n {item.label}\n {item.badge !== undefined && <span className={s.navBadge()}>{item.badge}</span>}\n </Link>\n );\n}\n\nfunction NavGroups({ groups, Link }: { groups: readonly SettingsNavGroup[]; Link: React.ElementType }) {\n const s = settingsLayout();\n return (\n <div className={s.sidebarList()}>\n {groups.map((group) => (\n <div key={group.id} className={s.group()}>\n <h2 className={s.groupLabel()}>{group.label}</h2>\n <ul className={s.groupItems()}>\n {group.items.map((item) => (\n <li key={item.id}>\n <NavLink item={item} Link={Link} className={s.groupLink()} />\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n );\n}\n\n/** A settings screen: sections on the left (chips on a phone, one menu past five), forms on the right. */\nexport function SettingsLayout({ title, description, header, nav, navLabel, linkAs: Link = \"a\", variant = \"card\", className, children, ...props }: SettingsLayoutProps) {\n const s = settingsLayout({ variant });\n const groups: readonly SettingsNavGroup[] = isGrouped(nav) ? nav : [{ id: \"sections\", label: \"\", items: nav }];\n const items = groups.flatMap((g) => g.items);\n const asMenu = items.length > SETTINGS_CHIPS_MAX;\n const current = items.find((i) => i.current);\n return (\n <div className={s.root({ className })} {...props}>\n {header ? (\n <div className={s.header()}>{header}</div>\n ) : (\n (title || description) && (\n <div className={s.head()}>\n {title && <h1 className={s.title()}>{title}</h1>}\n {description && <p className={s.description()}>{description}</p>}\n </div>\n )\n )}\n <div className={s.columns()}>\n {asMenu ? (\n <nav aria-label={navLabel}>\n <details className={s.menu()} data-settings-menu=\"\">\n <summary className={s.menuSummary()}>\n <span className=\"truncate\">{current?.label ?? navLabel}</span>\n <svg aria-hidden viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className={s.menuChevron()}>\n <path d=\"m4 6 4 4 4-4\" />\n </svg>\n </summary>\n <div className={s.menuPanel()}>\n <NavGroups groups={groups} Link={Link} />\n </div>\n </details>\n <div className={s.sidebar()}>\n <NavGroups groups={groups} Link={Link} />\n </div>\n </nav>\n ) : (\n <nav aria-label={navLabel} className={s.nav()}>\n <ul className={s.navList()}>\n {items.map((item) => (\n <li key={item.id}>\n <NavLink item={item} Link={Link} className={s.navLink()} />\n </li>\n ))}\n </ul>\n </nav>\n )}\n <div className={s.content()}>{children}</div>\n </div>\n </div>\n );\n}\n\nexport interface SectionSaveLabels {\n /** “Save”. */\n save: string;\n /** “Saving…”. */\n saving: string;\n /** “Saved”. */\n saved: string;\n /** What failed, one sentence (“Could not save. Try again.”). */\n error: string;\n /** “Undo” — only shown after a save, when `onUndo` is given. */\n undo: string;\n /** “Discard changes” — only shown while dirty, when `onDiscard` is given. */\n discard: string;\n}\n\n/** The one save of a section: its dirty state, its status, its words. */\nexport interface SectionSave {\n /** Whether a field differs from what is saved: the save is enabled only then. */\n dirty: boolean;\n /** Where the save is (`useSaveStatus().status`). */\n status: SaveStatus;\n /** Saves the section (the form's submit, Enter included). */\n onSave: () => void;\n /** Puts the previous values back after a save, optimistic update included. */\n onUndo?: () => void;\n /** Throws the edits away while dirty. */\n onDiscard?: () => void;\n labels: SectionSaveLabels;\n}\n\nexport type SettingsSectionProps = Omit<React.ComponentProps<\"section\">, \"title\"> & {\n /**\n * One save for the whole section: the section becomes a form, the foot shows the Save (enabled while dirty), the\n * result in a live region (saved, or the error as an alert) and an undo after a save. Replaces `footer`.\n */\n save?: SectionSave;\n /** The section's title: it names the region (and is its anchor's target when `id` is set). */\n title: React.ReactNode;\n /** What these settings change, in one sentence. */\n description?: React.ReactNode;\n /** At the foot: the section's own save (a Button), a note. */\n footer?: React.ReactNode;\n /**\n * `card` (a titled card) or `flat` (no card: a title, then hairline-divided `SettingsRow`s).\n * @default \"card\"\n */\n variant?: \"card\" | \"flat\";\n /** The fields (or, flat, the `SettingsRow`s). A section of a title, a sentence and a foot action has none. */\n children?: React.ReactNode;\n};\n\n/** A group of settings: a titled card with its fields and its own save, or flat rows under a title. */\nexport function SettingsSection({ title, description, footer, save, variant = \"card\", id, className, children, ...props }: SettingsSectionProps) {\n const s = settingsLayout({ variant });\n const auto = React.useId();\n const titleId = `${id ?? auto}-title`;\n const head = (\n <div className={s.sectionHead()}>\n <h2 id={titleId} className={s.sectionTitle()}>\n {title}\n </h2>\n {description && <p className={s.sectionDescription()}>{description}</p>}\n </div>\n );\n const body = children === undefined ? null : (\n <div className={variant === \"flat\" ? s.rows() : s.sectionBody()} data-settings-rows={variant === \"flat\" ? \"\" : undefined}>\n {children}\n </div>\n );\n return (\n <section id={id} aria-labelledby={titleId} data-dirty={save?.dirty ? \"\" : undefined} className={s.section({ className })} {...props}>\n {save ? (\n <form\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n if (save.dirty && save.status !== SaveStatus.Saving) save.onSave();\n }}\n >\n {head}\n {body}\n <div className={s.sectionFooter()}>\n <SaveFeedback save={save} />\n {save.onDiscard && save.dirty ? (\n <Button type=\"button\" variant=\"ghost\" onClick={save.onDiscard}>\n {save.labels.discard}\n </Button>\n ) : null}\n <Button type=\"submit\" variant=\"primary\" disabled={!save.dirty || save.status === SaveStatus.Saving} loading={save.status === SaveStatus.Saving}>\n {save.labels.save}\n </Button>\n </div>\n </form>\n ) : (\n <>\n {head}\n {body}\n {footer && <div className={s.sectionFooter()}>{footer}</div>}\n </>\n )}\n </section>\n );\n}\n\n/** The result of the last save: « Saved » (with Undo) in a polite live region, a failure as an alert. The region stays mounted so the change is announced. */\nfunction SaveFeedback({ save }: { save: SectionSave }) {\n const failed = save.status === SaveStatus.Error;\n const saved = save.status === SaveStatus.Saved;\n return (\n <div className=\"mr-auto flex min-w-0 items-center gap-2 text-sm\">\n <span role={failed ? \"alert\" : \"status\"} aria-live={failed ? \"assertive\" : \"polite\"} className={failed ? \"text-danger-text\" : \"text-fg-secondary\"}>\n {failed ? save.labels.error : saved ? save.labels.saved : save.status === SaveStatus.Saving ? save.labels.saving : \"\"}\n </span>\n {saved && save.onUndo ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={save.onUndo}>\n {save.labels.undo}\n </Button>\n ) : null}\n </div>\n );\n}\n\nexport type SettingsRowProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n /** What the setting is (`Username`). */\n label: React.ReactNode;\n /** The help text under it, one sentence. */\n description?: React.ReactNode;\n /** The id of the control, so the label names it (a `<label htmlFor>`). Leave out for a plain value. */\n htmlFor?: string;\n /** The control or the value, on the right (stacked under the label on a phone). */\n children?: React.ReactNode;\n};\n\n/** One setting: its label and help on the left, its control or value on the right. Put rows in a flat `SettingsSection`. */\nexport function SettingsRow({ label, description, htmlFor, className, children, ...props }: SettingsRowProps) {\n const s = settingsLayout();\n const Label = htmlFor ? \"label\" : \"span\";\n return (\n <div className={s.row({ className })} {...props}>\n <div className={s.rowText()}>\n <Label className={s.rowLabel()} {...(htmlFor ? { htmlFor } : {})}>\n {label}\n </Label>\n {description && <span className={s.rowDescription()}>{description}</span>}\n </div>\n {children !== undefined && <div className={s.rowControl()}>{children}</div>}\n </div>\n );\n}\n","// Server-safe: no hook, no context. `buttonVariants` styles a <Link> or a Server Component without the component.\nimport { Slot } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { LoaderGlyph } from \"../spinner/glyph\";\nimport { buttonRecipe } from \"./recipe\";\n\nexport const buttonVariants = tv(buttonRecipe);\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>;\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n ButtonVariants & {\n /**\n * The look: `primary` (the accent), `gradient` (accent → accent-2), `secondary` (a raised surface), `outline`,\n * `ghost`, `danger`.\n * @default \"secondary\"\n */\n variant?: ButtonVariants[\"variant\"];\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"md\"\n */\n size?: ButtonVariants[\"size\"];\n /**\n * `rounded` (an 8 px radius, the radius of a field) · `pill`. `cut` is deprecated (krizaka-ui#393: the chamfer is\n * retired) — it draws `rounded` and warns in development; it will be removed in 4.0.\n * @default \"rounded\"\n */\n shape?: ButtonVariants[\"shape\"];\n /** Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. */\n asChild?: boolean;\n /**\n * Disables and signals the wait (aria-busy, data-loading) and shows the Krizaka loader glyph before the label; the\n * button keeps its colour.\n */\n loading?: boolean;\n /**\n * Between 0 and 1, while `loading`: the glyph's trace and a light fill across the button grow to the value (an\n * upload, an export).\n */\n progress?: number | null;\n /**\n * How the action ended: `success` draws a check, `error` a cross (and the button shakes its head once); a ring of\n * the status colour opens around it. Clear it to return to rest.\n */\n status?: \"success\" | \"error\" | null;\n /** A message announced politely by assistive technology when it changes (“Saved”, “Upload failed”) — passed translated. */\n announce?: string;\n /**\n * Shows the glyph while `loading` / with a `status`. `false`: you draw your own.\n * @default true\n */\n indicator?: boolean;\n /** @deprecated Since 3.31 (krizaka-ui#393): does nothing — the hover is quiet. Removed in 4.0. */\n shimmer?: boolean;\n /** @deprecated Since 3.31 (krizaka-ui#393): does nothing — a button never moves under the pointer. Removed in 4.0. */\n magnetic?: boolean;\n };\n\nconst STATUS_TEXT = { success: \"text-fg-success\", error: \"text-fg-danger\" } as const;\n\nlet warnedCut = false;\n/** `shape=\"cut\"` (the chamfer) is retired: one warning per page in development, never in production. */\nfunction warnCut() {\n if (warnedCut || typeof process === \"undefined\" || process.env.NODE_ENV === \"production\") return;\n warnedCut = true;\n console.warn('@krizaka/ui/button: shape=\"cut\" is deprecated (krizaka-ui#393) and now draws the default rounded shape. Remove the prop.');\n}\n\nexport function Button({\n asChild,\n loading,\n progress,\n status,\n announce,\n indicator = true,\n shimmer: _shimmer,\n magnetic: _magnetic,\n variant,\n size,\n shape,\n className,\n disabled,\n children,\n ...props\n}: ButtonProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n if (shape === \"cut\") warnCut();\n const known = loading && typeof progress === \"number\" && Number.isFinite(progress);\n const ratio = known ? Math.min(1, Math.max(0, progress)) : 0;\n const glyph = indicator && !asChild && (loading || status) ? (status ?? \"loading\") : null;\n // On a filled variant the glyph stays in the label's colour; on the others it takes the status colour.\n const filled = variant === \"primary\" || variant === \"gradient\";\n const button = (\n <Comp\n type={asChild ? undefined : \"button\"}\n aria-busy={loading || undefined}\n data-loading={loading ? \"\" : undefined}\n data-variant={variant ?? \"secondary\"}\n data-kz-status={status ?? undefined}\n disabled={asChild ? undefined : disabled || loading}\n aria-disabled={asChild && (disabled || loading) ? true : undefined}\n className={buttonVariants({\n variant,\n size,\n shape,\n className: cn(known && \"relative isolate overflow-hidden\", className),\n })}\n {...props}\n >\n {asChild ? (\n children\n ) : (\n <>\n {known && (\n <span aria-hidden className=\"absolute inset-y-0 left-0 -z-10 bg-current opacity-15 transition-[width] duration-500\" style={{ width: `${ratio * 100}%` }} />\n )}\n {glyph && (\n <span\n aria-hidden\n data-slot=\"indicator\"\n className=\"-my-1 grid grid-cols-[1fr] transition-[grid-template-columns,margin,opacity] duration-300 starting:-mr-2 starting:grid-cols-[0fr] starting:opacity-0\"\n >\n <span className=\"overflow-hidden\">\n <LoaderGlyph state={glyph} value={known ? ratio : null} className={cn(\"h-4 w-4\", glyph !== \"loading\" && !filled && STATUS_TEXT[glyph])} />\n </span>\n </span>\n )}\n {children}\n </>\n )}\n </Comp>\n );\n if (announce === undefined || asChild) return button;\n return (\n <>\n {button}\n <span role=\"status\" className=\"sr-only\">\n {announce}\n </span>\n </>\n );\n}\n\nexport type IconButtonProps = Omit<ButtonProps, \"size\"> & {\n /** The accessible name (and tooltip) of the button — required, passed translated. */\n label: string;\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"icon\"\n */\n size?: ButtonProps[\"size\"];\n};\n\n/** An icon-only button: `label` is required, it is its accessible name. Loading or with a status, the glyph takes the icon's place. */\nexport function IconButton({ label, size = \"icon\", children, ...props }: IconButtonProps) {\n const swap = props.indicator !== false && !props.asChild && (props.loading || props.status);\n return (\n <Button aria-label={label} title={label} size={size} {...props}>\n {swap ? null : children}\n </Button>\n );\n}\n","// The Krizaka loader glyph, server-safe: a hexagon (the marks' shape) traced by a segment of light around a node that\n// breathes; determinate, it fills to its value; done, the trace closes and a check (or a cross) draws itself. Animated by\n// motion.css (`kz-loader-*`); still under prefers-reduced-motion — a segment at rest and the node lit, never blank.\nimport type * as React from \"react\";\n\n/** The hexagon, pointy top, radius 9 around (12, 12). */\nconst HEX = \"M12 3 19.79 7.5 19.79 16.5 12 21 4.21 16.5 4.21 7.5Z\";\n\n/** What the glyph shows: a wait (with an optional value), or how it ended. */\nexport type LoaderGlyphState = \"loading\" | \"success\" | \"error\";\n\nexport interface LoaderGlyphProps extends Omit<React.ComponentProps<\"svg\">, \"children\"> {\n /** loading · success · error. */\n state?: LoaderGlyphState;\n /** Between 0 and 1: a determinate wait, the trace grows to it. Absent: indeterminate. */\n value?: number | null;\n}\n\n/** The glyph alone (decorative): the Spinner, the Loader and the Button's `loading` draw it. */\nexport function LoaderGlyph({ state = \"loading\", value, ...props }: LoaderGlyphProps) {\n const known = typeof value === \"number\" && Number.isFinite(value);\n const fill = state !== \"loading\" ? 100 : known ? Math.round(Math.min(1, Math.max(0, value)) * 100) : null;\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden data-state={state} data-progress={fill === null ? undefined : fill} {...props}>\n <path d={HEX} stroke=\"currentColor\" strokeOpacity=\"0.22\" strokeWidth=\"2.2\" strokeLinejoin=\"round\" />\n <path\n d={HEX}\n pathLength={100}\n stroke=\"currentColor\"\n strokeWidth=\"2.2\"\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n className=\"kz-loader-trace\"\n strokeDasharray={fill === null ? undefined : `${fill} ${100 - fill}`}\n />\n {state === \"loading\" && <circle cx=\"12\" cy=\"12\" r=\"2.6\" fill=\"currentColor\" className=\"kz-loader-node\" />}\n {state === \"success\" && <path d=\"M8.3 12.3l2.6 2.6 4.9-5.3\" pathLength={20} stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className=\"kz-loader-mark\" />}\n {state === \"error\" && <path d=\"M9.4 9.4l5.2 5.2M14.6 9.4l-5.2 5.2\" pathLength={20} stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\" className=\"kz-loader-mark\" />}\n </svg>\n );\n}\n","// The button's variants, written once as classes of the Krizaka tokens: the web wears them with `tv(buttonRecipe)`\n// (button.tsx), React Native reads the same classes into styles (native/button.tsx, `nativeRecipe`).\nimport { defineRecipe } from \"../recipes/define\";\n\nexport const buttonRecipe = defineRecipe({\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition duration-150 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 \" +\n \"disabled:pointer-events-none disabled:opacity-40 data-loading:cursor-progress active:scale-[0.98] \" +\n // Busy is not unavailable: a loading button keeps its colour (it is disabled only so it is not pressed twice).\n \"[&[data-loading]]:opacity-100\",\n variants: {\n variant: {\n primary: \"bg-accent text-on-accent shadow-sm hover:bg-accent-hover\",\n // The brand's signature: accent → accent-2 (on-accent reads on both stops, tested in @krizaka/tokens).\n gradient: \"bg-linear-to-r from-accent to-accent-2 text-on-accent shadow-sm hover:brightness-110\",\n secondary: \"border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3\",\n outline: \"border border-border-default bg-transparent text-fg hover:border-accent hover:text-fg-accent\",\n ghost: \"text-fg-secondary hover:bg-surface-2 hover:text-fg\",\n // The border and the tint carry the danger; the label stays the primary text role, legible on the tint at rest\n // and hovered over every surface (`text-fg-danger` is for text set directly on a surface).\n danger: \"border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20\",\n },\n size: { sm: \"h-8 px-3 text-xs\", md: \"h-10 px-4 text-sm\", lg: \"h-12 px-6 text-sm\", icon: \"h-10 w-10\" },\n // One clean shape (krizaka-ui#393): an 8 px radius (`--kz-radius-sm`, the radius of a field — a button beside an\n // input lines up with it), the pill on demand. `cut` (the retired chamfer) is a deprecated alias of `rounded`.\n shape: { rounded: \"rounded-sm\", pill: \"rounded-full\", cut: \"rounded-sm\" },\n },\n defaultVariants: { variant: \"secondary\", size: \"md\", shape: \"rounded\" },\n});\n"],"mappings":"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","names":["React","tv","jsx","jsxs","Fragment","jsx","jsxs","tv"]}
@@ -1,6 +1,6 @@
1
1
  import * as tailwind_variants from 'tailwind-variants';
2
2
  import * as React from 'react';
3
- import { S as SiteId, F as FamilyLabels } from './sites-inztN_Bv.js';
3
+ import { S as SiteId, F as FamilyLabels } from './sites-DlR8jicc.js';
4
4
 
5
5
  declare const siteFooter: tailwind_variants.TVReturnType<{
6
6
  variant: {
@@ -5,7 +5,7 @@ import {
5
5
  familyLabels,
6
6
  siteById,
7
7
  sitesByGroup
8
- } from "./chunk-P4TTKYC4.js";
8
+ } from "./chunk-4LYKBSOG.js";
9
9
  import {
10
10
  KrizakaLogo
11
11
  } from "./chunk-4E5IUZHR.js";
@@ -1,5 +1,5 @@
1
1
  /** A site of the family. */
2
- type SiteId = "krizaka" | "orazaka" | "orochia" | "yasakia" | "yororis" | "yomizaka" | "kachizaka" | "akinaza" | "ui" | "java" | "motion" | "github";
2
+ type SiteId = "krizaka" | "orazaka" | "orochia" | "yasakia" | "yororis" | "yomizaka" | "kachizaka" | "akinaza" | "ui" | "java" | "ts" | "motion" | "github";
3
3
  /** What a site is: the company, a product, or an open-source project. It chooses the footer's signature line. */
4
4
  type SiteKind = "company" | "product" | "open-source";
5
5
  /**
package/dist/sites.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** A site of the family. */
2
- type SiteId = "krizaka" | "orazaka" | "orochia" | "yasakia" | "yororis" | "yomizaka" | "kachizaka" | "akinaza" | "ui" | "java" | "motion" | "github";
2
+ type SiteId = "krizaka" | "orazaka" | "orochia" | "yasakia" | "yororis" | "yomizaka" | "kachizaka" | "akinaza" | "ui" | "java" | "ts" | "motion" | "github";
3
3
  /** What a site is: the company, a product, or an open-source project. It chooses the footer's signature line. */
4
4
  type SiteKind = "company" | "product" | "open-source";
5
5
  /**
package/dist/sites.js CHANGED
@@ -11,6 +11,7 @@ var sites = [
11
11
  site({ id: "akinaza", name: "Akinaza", url: "https://www.akinaza.com", kind: "product", group: "products", status: "development", mark: "akinaza", repo: "https://github.com/krizaka/akinaza" }),
12
12
  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" }),
13
13
  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" }),
14
+ site({ id: "ts", name: "Krizaka TS", url: "https://ts.krizaka.com", kind: "open-source", group: "open-source", status: "released", mark: "krizaka", repo: "https://github.com/krizaka/krizaka-ts" }),
14
15
  // The motion lab: built in the open on GitHub Pages; what proves itself there moves into Krizaka UI.
15
16
  site({ id: "motion", name: "Krizaka Motion", url: "https://krizaka.github.io/krizaka-motion", kind: "open-source", group: "open-source", status: "development", mark: "krizaka", repo: "https://github.com/krizaka/krizaka-motion" }),
16
17
  site({ id: "github", name: "GitHub", url: "https://github.com/krizaka", kind: "open-source", group: "github", status: "released", mark: "github", repo: "https://github.com/krizaka" })
@@ -46,6 +47,7 @@ var familyLabels = {
46
47
  akinaza: "Running a shop in Qu\xE9bec, for less \u2014 Loi 25 by design.",
47
48
  ui: "Components, tokens, icons and charts \u2014 web and React Native.",
48
49
  java: "Spring Boot building blocks on Maven Central.",
50
+ ts: "TypeScript backend building blocks on npm.",
49
51
  motion: "The motion lab: the kit's motion, played live with its knobs.",
50
52
  github: "Every repository, in the open."
51
53
  },
@@ -71,6 +73,7 @@ var familyLabels = {
71
73
  akinaza: "Tenir une boutique au Qu\xE9bec, pour moins cher \u2014 Loi 25 d\xE8s la conception.",
72
74
  ui: "Composants, jetons, ic\xF4nes et graphiques \u2014 web et React Native.",
73
75
  java: "Des briques Spring Boot sur Maven Central.",
76
+ ts: "Des briques backend TypeScript sur npm.",
74
77
  motion: "Le labo du mouvement : le mouvement du kit, jou\xE9 en direct, avec ses r\xE9glages.",
75
78
  github: "Tous les d\xE9p\xF4ts, au grand jour."
76
79
  },