@krizaka/ui 3.43.0 → 3.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blocks.css +12 -0
- package/dist/chip.d.ts +6 -0
- package/dist/chip.js +7 -2
- package/dist/chip.js.map +1 -1
- package/dist/choice-grid.d.ts +5 -1
- package/dist/choice-grid.js +94 -30
- package/dist/choice-grid.js.map +1 -1
- package/dist/chunk-HHFWK2HH.js +163 -0
- package/dist/chunk-HHFWK2HH.js.map +1 -0
- package/dist/{chunk-AAP3U66V.js → chunk-KBQD6HE3.js} +2 -2
- package/dist/chunk-R3LTSMS5.js +75 -0
- package/dist/chunk-R3LTSMS5.js.map +1 -0
- package/dist/{chunk-S5LP7KKW.js → chunk-YHKTYFOQ.js} +2 -2
- package/dist/{chunk-WYQ277F2.js → chunk-ZQW7LF4Y.js} +2 -2
- package/dist/code-block.js +94 -31
- package/dist/code-block.js.map +1 -1
- package/dist/combobox.d.ts +6 -6
- package/dist/combobox.js +2 -2
- package/dist/data-table.js +3 -3
- package/dist/filter-bar.js +2 -2
- package/dist/genui.js +3 -3
- package/dist/nav-bar.js +3 -3
- package/dist/radio-group.d.ts +5 -1
- package/dist/radio-group.js +5 -1
- package/dist/radio-group.js.map +1 -1
- package/dist/router.d.ts +51 -0
- package/dist/router.js +18 -0
- package/dist/router.js.map +1 -0
- package/dist/tabs.d.ts +42 -3
- package/dist/tabs.js +51 -3
- package/dist/tabs.js.map +1 -1
- package/dist/tag-input.js +2 -2
- package/dist/tag-selector.js +1 -1
- package/package.json +18 -7
- package/registry/account-menu.json +1 -1
- package/registry/achievement.json +1 -1
- package/registry/action-bar.json +1 -1
- package/registry/activity-feed.json +1 -1
- package/registry/alert.json +1 -1
- package/registry/anatomy-plate.json +1 -1
- package/registry/app-header.json +1 -1
- package/registry/app-shell.json +1 -1
- package/registry/argument-card.json +1 -1
- package/registry/audio-player.json +1 -1
- package/registry/auth-layout.json +1 -1
- package/registry/avatar.json +1 -1
- package/registry/badge.json +1 -1
- package/registry/bird-plate.json +1 -1
- package/registry/button.json +1 -1
- package/registry/captcha.json +1 -1
- package/registry/card-grid.json +1 -1
- package/registry/card.json +1 -1
- package/registry/catalogue-page.json +1 -1
- package/registry/changelog-page.json +1 -1
- package/registry/chat.json +1 -1
- package/registry/checkbox.json +1 -1
- package/registry/chip-link.json +1 -1
- package/registry/chip-row.json +1 -1
- package/registry/chip.json +32 -3
- package/registry/choice-grid.json +32 -3
- package/registry/code-block.json +1 -1
- package/registry/combobox.json +1 -1
- package/registry/command.json +1 -1
- package/registry/comparison-table.json +1 -1
- package/registry/component-page.json +1 -1
- package/registry/confirm-button.json +1 -1
- package/registry/contact-page.json +1 -1
- package/registry/continue-prompt.json +1 -1
- package/registry/countdown.json +1 -1
- package/registry/cover-hero.json +1 -1
- package/registry/cta-panel.json +1 -1
- package/registry/dashboard-layout.json +1 -1
- package/registry/data-table.json +1 -1
- package/registry/date-picker.json +1 -1
- package/registry/decision-list.json +1 -1
- package/registry/description-list.json +1 -1
- package/registry/detail-layout.json +1 -1
- package/registry/dialog.json +1 -1
- package/registry/docs-article-page.json +1 -1
- package/registry/docs-hub-page.json +1 -1
- package/registry/docs-layout.json +1 -1
- package/registry/docs-nav.json +1 -1
- package/registry/document-viewer.json +1 -1
- package/registry/dropdown-menu.json +1 -1
- package/registry/elephant.json +2 -2
- package/registry/empty-state.json +1 -1
- package/registry/engine-flow.json +1 -1
- package/registry/error-page.json +1 -1
- package/registry/examples/router/use-route-state.d.ts +4 -0
- package/registry/examples/router/use-route-state.js +20 -0
- package/registry/examples/router/use-route-state.tsx +20 -0
- package/registry/examples/tabs/routes.d.ts +4 -0
- package/registry/examples/tabs/routes.js +16 -0
- package/registry/examples/tabs/routes.tsx +15 -0
- package/registry/field.json +1 -1
- package/registry/file-upload.json +1 -1
- package/registry/filter-bar.json +1 -1
- package/registry/flock.json +1 -1
- package/registry/footnote.json +1 -1
- package/registry/force-update-screen.json +1 -1
- package/registry/form.json +1 -1
- package/registry/gallery-page.json +1 -1
- package/registry/genui.json +1 -1
- package/registry/glow-card.json +1 -1
- package/registry/goal-gauge.json +1 -1
- package/registry/how-it-works-page.json +1 -1
- package/registry/image-cropper.json +1 -1
- package/registry/index.json +45 -11
- package/registry/iso.json +1 -1
- package/registry/kanban.json +1 -1
- package/registry/kbd.json +1 -1
- package/registry/landing-page.json +1 -1
- package/registry/landscape.json +1 -1
- package/registry/language-switch.json +1 -1
- package/registry/legal-page.json +1 -1
- package/registry/link-card.json +1 -1
- package/registry/list.json +1 -1
- package/registry/loader.json +1 -1
- package/registry/longform-page.json +1 -1
- package/registry/maintenance-screen.json +1 -1
- package/registry/map.json +2 -2
- package/registry/markdown.json +1 -1
- package/registry/marketing-layout.json +1 -1
- package/registry/marquee.json +1 -1
- package/registry/mascot-quote.json +1 -1
- package/registry/mascot-state.json +1 -1
- package/registry/mascots.json +1 -1
- package/registry/match-dialog.json +1 -1
- package/registry/media-capture.json +1 -1
- package/registry/media-gallery.json +1 -1
- package/registry/media-grid.json +1 -1
- package/registry/meta-row.json +1 -1
- package/registry/meter.json +1 -1
- package/registry/nav-bar.json +2 -2
- package/registry/number-ticker.json +1 -1
- package/registry/opening-hours.json +1 -1
- package/registry/page-header.json +1 -1
- package/registry/page-hero.json +1 -1
- package/registry/page-index.json +1 -1
- package/registry/page.json +1 -1
- package/registry/pagination.json +1 -1
- package/registry/photo-grid.json +1 -1
- package/registry/pointer-light.json +1 -1
- package/registry/popover.json +1 -1
- package/registry/pricing-page.json +1 -1
- package/registry/product-page.json +1 -1
- package/registry/profile-card.json +1 -1
- package/registry/progress.json +1 -1
- package/registry/promise-card.json +1 -1
- package/registry/radio-group.json +32 -3
- package/registry/rail-nav.json +2 -2
- package/registry/recipes.json +1 -1
- package/registry/router.json +123 -0
- package/registry/scroll-stage.json +1 -1
- package/registry/search-field.json +1 -1
- package/registry/section-backdrop.json +1 -1
- package/registry/separator.json +1 -1
- package/registry/service-card.json +1 -1
- package/registry/settings-layout.json +1 -1
- package/registry/showcase.json +1 -1
- package/registry/site-footer.json +2 -2
- package/registry/sites.json +1 -1
- package/registry/skeleton.json +1 -1
- package/registry/skip-link.json +1 -1
- package/registry/slider.json +1 -1
- package/registry/slot-picker.json +1 -1
- package/registry/slot.json +1 -1
- package/registry/specimen-tag.json +1 -1
- package/registry/spinner.json +1 -1
- package/registry/split-view.json +1 -1
- package/registry/stage-banner.json +1 -1
- package/registry/stat.json +1 -1
- package/registry/status-page.json +1 -1
- package/registry/step-progress.json +1 -1
- package/registry/step-showcase.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/sticky-action.json +1 -1
- package/registry/swipe-deck.json +1 -1
- package/registry/switch.json +1 -1
- package/registry/tabs.json +154 -7
- package/registry/tag-input.json +1 -1
- package/registry/tag-selector.json +1 -1
- package/registry/terminal-replay.json +1 -1
- package/registry/theme.json +1 -1
- package/registry/toast.json +1 -1
- package/registry/toc.json +1 -1
- package/registry/tooltip.json +1 -1
- package/registry/tree-view.json +1 -1
- package/registry/update-prompt.json +1 -1
- package/registry/verified-badge.json +1 -1
- package/registry/vertical-video.json +1 -1
- package/registry/video-player.json +1 -1
- package/registry/video-trimmer.json +1 -1
- package/registry/virtual.json +1 -1
- package/registry/waveform.json +1 -1
- package/dist/chunk-C3NF7V5Y.js +0 -100
- package/dist/chunk-C3NF7V5Y.js.map +0 -1
- /package/dist/{chunk-AAP3U66V.js.map → chunk-KBQD6HE3.js.map} +0 -0
- /package/dist/{chunk-S5LP7KKW.js.map → chunk-YHKTYFOQ.js.map} +0 -0
- /package/dist/{chunk-WYQ277F2.js.map → chunk-ZQW7LF4Y.js.map} +0 -0
package/dist/blocks.css
CHANGED
|
@@ -78,6 +78,18 @@
|
|
|
78
78
|
}
|
|
79
79
|
.kz-section :is(h2, h3)[id] { scroll-margin-top: var(--kz-anchor-offset, 96px); }
|
|
80
80
|
|
|
81
|
+
/* Products wear plain Krizaka UI, in their brand colour (Oussama, 2026-10-22): a product's root carries `data-surface="product"` (an app themed at :root, no brand class) or its `.brand-<id>`; the margin red is the notebook's, for the
|
|
82
|
+
Krizaka sites (www, the blog, ui.). Under a product's brand the margin ink is the brand's accent, so no eyebrow, folio or
|
|
83
|
+
margin note shows red there, and the faces are the sans and the system monospace. */
|
|
84
|
+
:is([data-surface="product"], .brand-orazaka, .brand-orochia, .brand-yasakia, .brand-yororis, .brand-yomizaka, .brand-kachizaka, .brand-akinaza) {
|
|
85
|
+
--kz-paper-rule-margin: var(--kz-accent-text);
|
|
86
|
+
/* Nor the notebook's faces: headings in the sans (Young Serif and Courier Prime are Cahier's), code in the system monospace. */
|
|
87
|
+
--kz-font-display: var(--kz-font-sans);
|
|
88
|
+
--kz-font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
89
|
+
}
|
|
90
|
+
:is([data-surface="product"], .brand-orazaka, .brand-orochia, .brand-yasakia, .brand-yororis, .brand-yomizaka, .brand-kachizaka, .brand-akinaza) :is(.kz-heading-title, .kz-hero-title) {
|
|
91
|
+
font-weight: 650; /* the serif had one weight; the sans carries the hierarchy with a semibold */
|
|
92
|
+
}
|
|
81
93
|
/* Cahier (ADR-004, #417): the eyebrow is a note in the margin's voice — the typewriter face, lower case, untracked,
|
|
82
94
|
inked in the margin red. No more uppercase tracked overline above every heading. */
|
|
83
95
|
.kz-eyebrow {
|
package/dist/chip.d.ts
CHANGED
|
@@ -99,6 +99,10 @@ type ChipGroupProps = (GroupBase & {
|
|
|
99
99
|
onValueChange?: (value: string) => void;
|
|
100
100
|
/** Keeps one chip selected: pressing the selected chip does not clear it (default `false`). */
|
|
101
101
|
required?: boolean;
|
|
102
|
+
/** Binds the choice to this search parameter of the URL (`?type=…`): the URL is the state. Without it the state is local — only for an ephemeral choice. */
|
|
103
|
+
routeKey?: string;
|
|
104
|
+
/** The values the parameter may hold (the chips' `value`s). */
|
|
105
|
+
routeValues?: readonly string[];
|
|
102
106
|
}) | (GroupBase & {
|
|
103
107
|
/** Any number of choices: the chips are pressed buttons. */
|
|
104
108
|
type: "multiple";
|
|
@@ -109,6 +113,8 @@ type ChipGroupProps = (GroupBase & {
|
|
|
109
113
|
/** Called with the new values on every press. */
|
|
110
114
|
onValueChange?: (value: string[]) => void;
|
|
111
115
|
required?: never;
|
|
116
|
+
routeKey?: never;
|
|
117
|
+
routeValues?: never;
|
|
112
118
|
});
|
|
113
119
|
/** Chips that choose together: `type` single · multiple, `value`/`defaultValue` + `onValueChange`. */
|
|
114
120
|
declare function ChipGroup({ label, size, className, children, ...props }: ChipGroupProps): React.JSX.Element;
|
package/dist/chip.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import {
|
|
3
|
+
useRouteBinding
|
|
4
|
+
} from "./chunk-R3LTSMS5.js";
|
|
2
5
|
import {
|
|
3
6
|
defineRecipe
|
|
4
7
|
} from "./chunk-D3T54RSL.js";
|
|
@@ -56,13 +59,15 @@ function ChipBase(props) {
|
|
|
56
59
|
);
|
|
57
60
|
}
|
|
58
61
|
function ChipGroup({ label, size, className, children, ...props }) {
|
|
62
|
+
const single = props.type === "single" ? props : void 0;
|
|
63
|
+
const routed = useRouteBinding(single?.routeKey, single?.routeValues, { value: single?.value, defaultValue: single?.defaultValue, onValueChange: single?.onValueChange });
|
|
59
64
|
const s = chip({ size });
|
|
60
65
|
const context = React.useMemo(() => ({ size }), [size]);
|
|
61
66
|
const common = { "aria-label": label, className: s.group({ className }) };
|
|
62
67
|
let group;
|
|
63
68
|
if (props.type === "single") {
|
|
64
|
-
const { type: _type, value, defaultValue, onValueChange, required, ...rest } = props;
|
|
65
|
-
group = /* @__PURE__ */ jsx(SingleGroup, { ...rest, ...common, value, defaultValue, onValueChange, required, children });
|
|
69
|
+
const { type: _type, value: _value, defaultValue: _defaultValue, onValueChange: _onValueChange, required, routeKey: _routeKey, routeValues: _routeValues, ...rest } = props;
|
|
70
|
+
group = /* @__PURE__ */ jsx(SingleGroup, { ...rest, ...common, value: routed.value, defaultValue: routed.defaultValue, onValueChange: routed.onValueChange, required, children });
|
|
66
71
|
} else {
|
|
67
72
|
const { type: _type, required: _required, ...rest } = props;
|
|
68
73
|
group = /* @__PURE__ */ jsx(ToggleGroupPrimitive.Root, { type: "multiple", ...rest, ...common, children });
|
package/dist/chip.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/chip/chip.tsx","../src/chip/recipe.ts"],"sourcesContent":["// Client (a context). A chip is a pressable pill: alone it is a Radix Toggle (aria-pressed), inside `Chip.Group` an\n// item of a Radix Toggle Group (roving focus with the arrows; `single` = radio semantics, `multiple` = pressed\n// buttons). A `removable` chip is a static label with its own remove button (two buttons cannot nest).\nimport { Toggle as TogglePrimitive, ToggleGroup as ToggleGroupPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { chipRecipe } from \"./recipe\";\n\nexport const chip = tv(chipRecipe);\n\nexport type ChipVariants = VariantProps<typeof chip>;\n\ntype GroupContext = { size: ChipVariants[\"size\"] };\nconst ChipGroupContext = React.createContext<GroupContext | null>(null);\n\nfunction RemoveIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-3.5 w-3.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n\ntype ToggleChipProps = Omit<React.ComponentProps<\"button\">, \"value\" | \"defaultValue\"> &\n ChipVariants & {\n /**\n * sm · md (inside `Chip.Group`, the group's size by default).\n * @default \"md\"\n */\n size?: ChipVariants[\"size\"];\n /** Inside `Chip.Group`: the value this chip stands for (required there, ignored alone). */\n value?: string;\n /** Alone: pressed or not (`defaultSelected` uncontrolled), `onSelectedChange` on every press. */\n selected?: boolean;\n /** Alone: pressed at first, uncontrolled. */\n defaultSelected?: boolean;\n /** Alone: called with the new state on every press. */\n onSelectedChange?: (selected: boolean) => void;\n /** A pressable chip (the default); `true` makes it a static chip with a remove button. */\n removable?: false;\n };\n\ntype RemovableChipProps = Omit<React.ComponentProps<\"span\">, \"onSelect\"> &\n ChipVariants & {\n /** A static chip (a chosen tag, an applied filter) with a remove button. */\n removable: true;\n /** The accessible name of the remove button — passed translated, e.g. \"Remove #night\". */\n removeLabel: string;\n /** Runs when the remove button is pressed. */\n onRemove: () => void;\n /** Disables the remove button. */\n disabled?: boolean;\n };\n\nexport type ChipProps = ToggleChipProps | RemovableChipProps;\n\n/** A selectable pill (`selected` / `onSelectedChange`), an item of `Chip.Group` (`value`), or `removable`. */\nfunction ChipBase(props: ChipProps) {\n const group = React.useContext(ChipGroupContext);\n if (props.removable) {\n const { removable: _removable, removeLabel, onRemove, disabled, size, className, children, ...rest } = props;\n const s = chip({ size: size ?? group?.size });\n return (\n <span data-removable=\"\" className={s.base({ className })} {...rest}>\n {children}\n <button type=\"button\" aria-label={removeLabel} title={removeLabel} disabled={disabled} onClick={onRemove} className={s.remove()}>\n <RemoveIcon />\n </button>\n </span>\n );\n }\n const { removable: _removable, value, selected, defaultSelected, onSelectedChange, size, className, ...rest } = props;\n const s = chip({ size: size ?? group?.size });\n if (group) {\n if (value === undefined) throw new Error(\"Chip: a chip inside Chip.Group needs a `value`.\");\n return <ToggleGroupPrimitive.Item value={value} className={s.base({ className })} {...rest} />;\n }\n return (\n <TogglePrimitive.Root\n pressed={selected}\n defaultPressed={defaultSelected}\n onPressedChange={onSelectedChange}\n className={s.base({ className })}\n {...rest}\n />\n );\n}\n\ntype GroupBase = Omit<React.ComponentProps<\"div\">, \"defaultValue\" | \"dir\"> &\n ChipVariants & {\n /**\n * sm · md: the size of every chip of the group.\n * @default \"md\"\n */\n size?: ChipVariants[\"size\"];\n /** The group's accessible name — passed translated (\"Filter by format\"). */\n label?: string;\n /** The arrow keys that move: left / right (horizontal, a row) or up / down (vertical, a column). */\n orientation?: \"horizontal\" | \"vertical\";\n /** Arrow keys wrap from the last chip to the first (default `true`). */\n loop?: boolean;\n /** Disables every chip of the group. */\n disabled?: boolean;\n };\n\nexport type ChipGroupProps =\n | (GroupBase & {\n /** One choice: the chips behave as radios. */\n type: \"single\";\n /** The chosen value (controlled), `\"\"` when none. */\n value?: string;\n /** The value chosen at first, uncontrolled. */\n defaultValue?: string;\n /** Called with the new value on every choice. */\n onValueChange?: (value: string) => void;\n /** Keeps one chip selected: pressing the selected chip does not clear it (default `false`). */\n required?: boolean;\n })\n | (GroupBase & {\n /** Any number of choices: the chips are pressed buttons. */\n type: \"multiple\";\n /** The chosen values (controlled). */\n value?: string[];\n /** The values chosen at first, uncontrolled. */\n defaultValue?: string[];\n /** Called with the new values on every press. */\n onValueChange?: (value: string[]) => void;\n required?: never;\n });\n\n/** Chips that choose together: `type` single · multiple, `value`/`defaultValue` + `onValueChange`. */\nexport function ChipGroup({ label, size, className, children, ...props }: ChipGroupProps) {\n const s = chip({ size });\n const context = React.useMemo(() => ({ size }), [size]);\n const common = { \"aria-label\": label, className: s.group({ className }) };\n let group: React.ReactNode;\n if (props.type === \"single\") {\n const { type: _type, value, defaultValue, onValueChange, required, ...rest } = props;\n group = <SingleGroup {...rest} {...common} value={value} defaultValue={defaultValue} onValueChange={onValueChange} required={required}>{children}</SingleGroup>;\n } else {\n const { type: _type, required: _required, ...rest } = props;\n group = (\n <ToggleGroupPrimitive.Root type=\"multiple\" {...rest} {...common}>\n {children}\n </ToggleGroupPrimitive.Root>\n );\n }\n return <ChipGroupContext.Provider value={context}>{group}</ChipGroupContext.Provider>;\n}\n\n/** Radix's single group, plus `required`: the value is held here so that an empty change can be refused. */\nfunction SingleGroup({\n value,\n defaultValue,\n onValueChange,\n required,\n ...props\n}: Omit<GroupBase, \"size\" | \"label\"> & {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n required?: boolean;\n}) {\n const [inner, setInner] = React.useState(defaultValue ?? \"\");\n const current = value ?? inner;\n return (\n <ToggleGroupPrimitive.Root\n type=\"single\"\n value={current}\n onValueChange={(next) => {\n if (required && !next) return;\n if (value === undefined) setInner(next);\n onValueChange?.(next);\n }}\n {...props}\n />\n );\n}\n\n/** `Chip` (selectable, removable) and `Chip.Group` (single · multiple). */\nexport const Chip = Object.assign(ChipBase, { Group: ChipGroup });\n","// The chip's parts and sizes, written once as classes of the Krizaka tokens: the web wears them with `tv(chipRecipe)`,\n// React Native reads the same classes into styles (native/chip.tsx). `data-[state=on]:` is the chosen chip.\nimport { defineRecipe } from \"../recipes/define\";\n\nexport const chipRecipe = defineRecipe({\n slots: {\n group: \"flex flex-wrap items-center gap-2 data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch\",\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-1.5 whitespace-nowrap rounded-sm border font-semibold transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40 \" +\n \"border-border-default bg-surface-1 text-fg-secondary hover:border-border-strong hover:text-fg \" +\n \"data-[state=on]:border-accent data-[state=on]:bg-accent-soft data-[state=on]:text-fg\",\n remove:\n \"-mr-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n variants: {\n size: { sm: { base: \"h-7 px-3 text-xs\" }, md: { base: \"h-9 px-3.5 text-xs\" } },\n },\n defaultVariants: { size: \"md\" },\n});\n"],"mappings":"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","names":["_removable","size","className","rest","s"]}
|
|
1
|
+
{"version":3,"sources":["../src/chip/chip.tsx","../src/chip/recipe.ts"],"sourcesContent":["// Client (a context). A chip is a pressable pill: alone it is a Radix Toggle (aria-pressed), inside `Chip.Group` an\n// item of a Radix Toggle Group (roving focus with the arrows; `single` = radio semantics, `multiple` = pressed\n// buttons). A `removable` chip is a static label with its own remove button (two buttons cannot nest).\nimport { Toggle as TogglePrimitive, ToggleGroup as ToggleGroupPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { useRouteBinding } from \"../router\";\nimport { chipRecipe } from \"./recipe\";\n\nexport const chip = tv(chipRecipe);\n\nexport type ChipVariants = VariantProps<typeof chip>;\n\ntype GroupContext = { size: ChipVariants[\"size\"] };\nconst ChipGroupContext = React.createContext<GroupContext | null>(null);\n\nfunction RemoveIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-3.5 w-3.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n\ntype ToggleChipProps = Omit<React.ComponentProps<\"button\">, \"value\" | \"defaultValue\"> &\n ChipVariants & {\n /**\n * sm · md (inside `Chip.Group`, the group's size by default).\n * @default \"md\"\n */\n size?: ChipVariants[\"size\"];\n /** Inside `Chip.Group`: the value this chip stands for (required there, ignored alone). */\n value?: string;\n /** Alone: pressed or not (`defaultSelected` uncontrolled), `onSelectedChange` on every press. */\n selected?: boolean;\n /** Alone: pressed at first, uncontrolled. */\n defaultSelected?: boolean;\n /** Alone: called with the new state on every press. */\n onSelectedChange?: (selected: boolean) => void;\n /** A pressable chip (the default); `true` makes it a static chip with a remove button. */\n removable?: false;\n };\n\ntype RemovableChipProps = Omit<React.ComponentProps<\"span\">, \"onSelect\"> &\n ChipVariants & {\n /** A static chip (a chosen tag, an applied filter) with a remove button. */\n removable: true;\n /** The accessible name of the remove button — passed translated, e.g. \"Remove #night\". */\n removeLabel: string;\n /** Runs when the remove button is pressed. */\n onRemove: () => void;\n /** Disables the remove button. */\n disabled?: boolean;\n };\n\nexport type ChipProps = ToggleChipProps | RemovableChipProps;\n\n/** A selectable pill (`selected` / `onSelectedChange`), an item of `Chip.Group` (`value`), or `removable`. */\nfunction ChipBase(props: ChipProps) {\n const group = React.useContext(ChipGroupContext);\n if (props.removable) {\n const { removable: _removable, removeLabel, onRemove, disabled, size, className, children, ...rest } = props;\n const s = chip({ size: size ?? group?.size });\n return (\n <span data-removable=\"\" className={s.base({ className })} {...rest}>\n {children}\n <button type=\"button\" aria-label={removeLabel} title={removeLabel} disabled={disabled} onClick={onRemove} className={s.remove()}>\n <RemoveIcon />\n </button>\n </span>\n );\n }\n const { removable: _removable, value, selected, defaultSelected, onSelectedChange, size, className, ...rest } = props;\n const s = chip({ size: size ?? group?.size });\n if (group) {\n if (value === undefined) throw new Error(\"Chip: a chip inside Chip.Group needs a `value`.\");\n return <ToggleGroupPrimitive.Item value={value} className={s.base({ className })} {...rest} />;\n }\n return (\n <TogglePrimitive.Root\n pressed={selected}\n defaultPressed={defaultSelected}\n onPressedChange={onSelectedChange}\n className={s.base({ className })}\n {...rest}\n />\n );\n}\n\ntype GroupBase = Omit<React.ComponentProps<\"div\">, \"defaultValue\" | \"dir\"> &\n ChipVariants & {\n /**\n * sm · md: the size of every chip of the group.\n * @default \"md\"\n */\n size?: ChipVariants[\"size\"];\n /** The group's accessible name — passed translated (\"Filter by format\"). */\n label?: string;\n /** The arrow keys that move: left / right (horizontal, a row) or up / down (vertical, a column). */\n orientation?: \"horizontal\" | \"vertical\";\n /** Arrow keys wrap from the last chip to the first (default `true`). */\n loop?: boolean;\n /** Disables every chip of the group. */\n disabled?: boolean;\n };\n\nexport type ChipGroupProps =\n | (GroupBase & {\n /** One choice: the chips behave as radios. */\n type: \"single\";\n /** The chosen value (controlled), `\"\"` when none. */\n value?: string;\n /** The value chosen at first, uncontrolled. */\n defaultValue?: string;\n /** Called with the new value on every choice. */\n onValueChange?: (value: string) => void;\n /** Keeps one chip selected: pressing the selected chip does not clear it (default `false`). */\n required?: boolean;\n /** Binds the choice to this search parameter of the URL (`?type=…`): the URL is the state. Without it the state is local — only for an ephemeral choice. */\n routeKey?: string;\n /** The values the parameter may hold (the chips' `value`s). */\n routeValues?: readonly string[];\n })\n | (GroupBase & {\n /** Any number of choices: the chips are pressed buttons. */\n type: \"multiple\";\n /** The chosen values (controlled). */\n value?: string[];\n /** The values chosen at first, uncontrolled. */\n defaultValue?: string[];\n /** Called with the new values on every press. */\n onValueChange?: (value: string[]) => void;\n required?: never;\n routeKey?: never;\n routeValues?: never;\n });\n\n/** Chips that choose together: `type` single · multiple, `value`/`defaultValue` + `onValueChange`. */\nexport function ChipGroup({ label, size, className, children, ...props }: ChipGroupProps) {\n const single = props.type === \"single\" ? props : undefined;\n const routed = useRouteBinding(single?.routeKey, single?.routeValues, { value: single?.value, defaultValue: single?.defaultValue, onValueChange: single?.onValueChange });\n const s = chip({ size });\n const context = React.useMemo(() => ({ size }), [size]);\n const common = { \"aria-label\": label, className: s.group({ className }) };\n let group: React.ReactNode;\n if (props.type === \"single\") {\n const { type: _type, value: _value, defaultValue: _defaultValue, onValueChange: _onValueChange, required, routeKey: _routeKey, routeValues: _routeValues, ...rest } = props;\n group = <SingleGroup {...rest} {...common} value={routed.value} defaultValue={routed.defaultValue} onValueChange={routed.onValueChange} required={required}>{children}</SingleGroup>;\n } else {\n const { type: _type, required: _required, ...rest } = props;\n group = (\n <ToggleGroupPrimitive.Root type=\"multiple\" {...rest} {...common}>\n {children}\n </ToggleGroupPrimitive.Root>\n );\n }\n return <ChipGroupContext.Provider value={context}>{group}</ChipGroupContext.Provider>;\n}\n\n/** Radix's single group, plus `required`: the value is held here so that an empty change can be refused. */\nfunction SingleGroup({\n value,\n defaultValue,\n onValueChange,\n required,\n ...props\n}: Omit<GroupBase, \"size\" | \"label\"> & {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n required?: boolean;\n}) {\n const [inner, setInner] = React.useState(defaultValue ?? \"\");\n const current = value ?? inner;\n return (\n <ToggleGroupPrimitive.Root\n type=\"single\"\n value={current}\n onValueChange={(next) => {\n if (required && !next) return;\n if (value === undefined) setInner(next);\n onValueChange?.(next);\n }}\n {...props}\n />\n );\n}\n\n/** `Chip` (selectable, removable) and `Chip.Group` (single · multiple). */\nexport const Chip = Object.assign(ChipBase, { Group: ChipGroup });\n","// The chip's parts and sizes, written once as classes of the Krizaka tokens: the web wears them with `tv(chipRecipe)`,\n// React Native reads the same classes into styles (native/chip.tsx). `data-[state=on]:` is the chosen chip.\nimport { defineRecipe } from \"../recipes/define\";\n\nexport const chipRecipe = defineRecipe({\n slots: {\n group: \"flex flex-wrap items-center gap-2 data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch\",\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-1.5 whitespace-nowrap rounded-sm border font-semibold transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40 \" +\n \"border-border-default bg-surface-1 text-fg-secondary hover:border-border-strong hover:text-fg \" +\n \"data-[state=on]:border-accent data-[state=on]:bg-accent-soft data-[state=on]:text-fg\",\n remove:\n \"-mr-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n variants: {\n size: { sm: { base: \"h-7 px-3 text-xs\" }, md: { base: \"h-9 px-3.5 text-xs\" } },\n },\n defaultVariants: { size: \"md\" },\n});\n"],"mappings":"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","names":["_removable","size","className","rest","s"]}
|
package/dist/choice-grid.d.ts
CHANGED
|
@@ -53,6 +53,10 @@ type ChoiceGridProps = Omit<React.HTMLAttributes<HTMLDivElement>, "defaultValue"
|
|
|
53
53
|
defaultValue?: string;
|
|
54
54
|
/** Makes the grid a radio group: called with the id chosen by click, Enter, Space or the arrow keys. */
|
|
55
55
|
onValueChange?: (id: string) => void;
|
|
56
|
+
/** Binds the choice to this search parameter of the URL (`?type=…`): the URL is the state. Without it the state is local — only for an ephemeral choice. */
|
|
57
|
+
routeKey?: string;
|
|
58
|
+
/** The ids the parameter may hold; the first is the default unless `defaultValue` says otherwise. */
|
|
59
|
+
routeValues?: readonly string[];
|
|
56
60
|
/** The app's link component for the tiles' internal `href`s (Next's `Link`); an `<a>` otherwise. */
|
|
57
61
|
linkAs?: LinkComponent;
|
|
58
62
|
/**
|
|
@@ -79,6 +83,6 @@ type ChoiceGridProps = Omit<React.HTMLAttributes<HTMLDivElement>, "defaultValue"
|
|
|
79
83
|
* and choose, Home/End jump, the chosen tile is marked. With `href` on the items, a list of links. Otherwise a list
|
|
80
84
|
* named by `label` that presents the line-up.
|
|
81
85
|
*/
|
|
82
|
-
declare function ChoiceGrid({ label, items, tag, caption, prompt, value, defaultValue, onValueChange, linkAs, columns, variant, brand, animated, className, style, ...props }: ChoiceGridProps): React.JSX.Element;
|
|
86
|
+
declare function ChoiceGrid({ label, items, tag, caption, prompt, value: valueProp, defaultValue: defaultValueProp, onValueChange: onValueChangeProp, routeKey, routeValues, linkAs, columns, variant, brand, animated, className, style, ...props }: ChoiceGridProps): React.JSX.Element;
|
|
83
87
|
|
|
84
88
|
export { ChoiceGrid, type ChoiceGridProps, type ChoiceItem };
|
package/dist/choice-grid.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-WU4VLSEA.js";
|
|
9
9
|
|
|
10
10
|
// src/choice-grid/choice-grid.tsx
|
|
11
|
-
import * as
|
|
11
|
+
import * as React2 from "react";
|
|
12
12
|
|
|
13
13
|
// src/block-link.tsx
|
|
14
14
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -25,8 +25,69 @@ function BlockLink({
|
|
|
25
25
|
return /* @__PURE__ */ jsx(Comp, { href, ...props });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// src/router/route-state.tsx
|
|
29
|
+
import * as React from "react";
|
|
30
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
31
|
+
var CHANGE = "kz-route-state";
|
|
32
|
+
var browserAdapter = {
|
|
33
|
+
search: () => typeof window === "undefined" ? "" : window.location.search,
|
|
34
|
+
navigate: (search, { replace }) => {
|
|
35
|
+
const url = `${window.location.pathname}${search}${window.location.hash}`;
|
|
36
|
+
window.history[replace ? "replaceState" : "pushState"](window.history.state, "", url);
|
|
37
|
+
window.dispatchEvent(new Event(CHANGE));
|
|
38
|
+
},
|
|
39
|
+
subscribe: (onChange) => {
|
|
40
|
+
window.addEventListener("popstate", onChange);
|
|
41
|
+
window.addEventListener(CHANGE, onChange);
|
|
42
|
+
return () => {
|
|
43
|
+
window.removeEventListener("popstate", onChange);
|
|
44
|
+
window.removeEventListener(CHANGE, onChange);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var AdapterContext = React.createContext(browserAdapter);
|
|
49
|
+
function readRouteValue(search, key, { values, default: fallback }) {
|
|
50
|
+
const raw = new URLSearchParams(search).get(key);
|
|
51
|
+
if (raw === null) return fallback;
|
|
52
|
+
return !values || values.includes(raw) ? raw : fallback;
|
|
53
|
+
}
|
|
54
|
+
function writeRouteValue(search, key, value, fallback) {
|
|
55
|
+
const params = new URLSearchParams(search);
|
|
56
|
+
if (value === fallback) params.delete(key);
|
|
57
|
+
else params.set(key, value);
|
|
58
|
+
const text = params.toString();
|
|
59
|
+
return text ? `?${text}` : "";
|
|
60
|
+
}
|
|
61
|
+
function useRouteState(key, { values, default: fallback, history = "push" }) {
|
|
62
|
+
const adapter = React.useContext(AdapterContext);
|
|
63
|
+
const search = React.useSyncExternalStore(adapter.subscribe, adapter.search, () => "");
|
|
64
|
+
const value = key === void 0 ? fallback : readRouteValue(search, key, { values, default: fallback });
|
|
65
|
+
const set = React.useCallback(
|
|
66
|
+
(next) => {
|
|
67
|
+
if (key === void 0) return;
|
|
68
|
+
adapter.navigate(writeRouteValue(adapter.search(), key, next, fallback), { replace: history === "replace" });
|
|
69
|
+
},
|
|
70
|
+
[adapter, key, fallback, history]
|
|
71
|
+
);
|
|
72
|
+
return [value, set];
|
|
73
|
+
}
|
|
74
|
+
function useRouteBinding(routeKey, routeValues, props) {
|
|
75
|
+
const initial = props.defaultValue ?? props.value ?? routeValues?.[0] ?? "";
|
|
76
|
+
const [value, set] = useRouteState(routeKey, { values: routeValues, default: initial });
|
|
77
|
+
if (routeKey === void 0) return props;
|
|
78
|
+
return {
|
|
79
|
+
...props,
|
|
80
|
+
value,
|
|
81
|
+
defaultValue: void 0,
|
|
82
|
+
onValueChange: (next) => {
|
|
83
|
+
set(next);
|
|
84
|
+
props.onValueChange?.(next);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
28
89
|
// src/choice-grid/choice-grid.tsx
|
|
29
|
-
import { Fragment, jsx as
|
|
90
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
30
91
|
var CORNERS = ["tl", "tr", "bl", "br"];
|
|
31
92
|
function ChoiceGrid({
|
|
32
93
|
label,
|
|
@@ -34,9 +95,11 @@ function ChoiceGrid({
|
|
|
34
95
|
tag,
|
|
35
96
|
caption,
|
|
36
97
|
prompt,
|
|
37
|
-
value,
|
|
38
|
-
defaultValue,
|
|
39
|
-
onValueChange,
|
|
98
|
+
value: valueProp,
|
|
99
|
+
defaultValue: defaultValueProp,
|
|
100
|
+
onValueChange: onValueChangeProp,
|
|
101
|
+
routeKey,
|
|
102
|
+
routeValues,
|
|
40
103
|
linkAs,
|
|
41
104
|
columns,
|
|
42
105
|
variant,
|
|
@@ -46,10 +109,11 @@ function ChoiceGrid({
|
|
|
46
109
|
style,
|
|
47
110
|
...props
|
|
48
111
|
}) {
|
|
112
|
+
const { value, defaultValue, onValueChange } = useRouteBinding(routeKey, routeValues, { value: valueProp, defaultValue: defaultValueProp, onValueChange: onValueChangeProp });
|
|
49
113
|
const selectable = onValueChange !== void 0 || value !== void 0;
|
|
50
|
-
const [inner, setInner] =
|
|
114
|
+
const [inner, setInner] = React2.useState(defaultValue);
|
|
51
115
|
const current = value ?? inner;
|
|
52
|
-
const refs =
|
|
116
|
+
const refs = React2.useRef([]);
|
|
53
117
|
const enabled = items.map((item, i) => item.disabled ? -1 : i).filter((i) => i >= 0);
|
|
54
118
|
const chosenIndex = items.findIndex((item) => item.id === current && !item.disabled);
|
|
55
119
|
const focusIndex = chosenIndex >= 0 ? chosenIndex : enabled[0] ?? 0;
|
|
@@ -75,40 +139,40 @@ function ChoiceGrid({
|
|
|
75
139
|
const Item = selectable ? "div" : "li";
|
|
76
140
|
const vars = { "--kzs-cols": columns ?? Math.min(items.length, 4), ...style };
|
|
77
141
|
return /* @__PURE__ */ jsxs("div", { "data-kzs-scene": "", "data-animated": animated ? "" : void 0, className: cn("kzs-choice", storyVariantClass(variant), brandClass(brand), className), style: vars, ...props, children: [
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
-
/* @__PURE__ */
|
|
142
|
+
/* @__PURE__ */ jsx3("span", { className: "kzs-texture", "aria-hidden": true }),
|
|
143
|
+
/* @__PURE__ */ jsx3("span", { className: "kzs-scan kzs-loop", "aria-hidden": true }),
|
|
80
144
|
(tag || caption) && /* @__PURE__ */ jsxs("p", { className: "kzs-choice-head", children: [
|
|
81
|
-
tag && /* @__PURE__ */
|
|
82
|
-
tag && caption && /* @__PURE__ */
|
|
83
|
-
caption && /* @__PURE__ */
|
|
145
|
+
tag && /* @__PURE__ */ jsx3("span", { className: "kzs-choice-tag", children: tag }),
|
|
146
|
+
tag && caption && /* @__PURE__ */ jsx3("span", { className: "kzs-choice-sep", "aria-hidden": true }),
|
|
147
|
+
caption && /* @__PURE__ */ jsx3("span", { className: "kzs-choice-caption", children: caption })
|
|
84
148
|
] }),
|
|
85
|
-
prompt && /* @__PURE__ */
|
|
86
|
-
/* @__PURE__ */
|
|
149
|
+
prompt && /* @__PURE__ */ jsx3("p", { className: "kzs-choice-prompt", children: prompt }),
|
|
150
|
+
/* @__PURE__ */ jsx3(Grid, { className: "kzs-choice-grid", role: selectable ? "radiogroup" : void 0, "aria-label": label, children: items.map((item, i) => {
|
|
87
151
|
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
88
|
-
/* @__PURE__ */
|
|
89
|
-
/* @__PURE__ */
|
|
90
|
-
CORNERS.map((at) => /* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ jsx3("span", { className: "kzs-choice-aura", "aria-hidden": true }),
|
|
153
|
+
/* @__PURE__ */ jsx3("span", { className: "kzs-choice-flash", "aria-hidden": true }),
|
|
154
|
+
CORNERS.map((at) => /* @__PURE__ */ jsx3("span", { className: "kzs-corner", "data-at": at, "aria-hidden": true }, at)),
|
|
91
155
|
(item.badge || item.eyebrow || item.icon) && /* @__PURE__ */ jsxs("span", { className: "kzs-choice-top", children: [
|
|
92
|
-
item.badge ? /* @__PURE__ */
|
|
156
|
+
item.badge ? /* @__PURE__ */ jsx3("span", { className: "kzs-choice-badge", children: item.badge }) : /* @__PURE__ */ jsx3("span", {}),
|
|
93
157
|
(item.eyebrow || item.icon) && /* @__PURE__ */ jsxs("span", { className: "kzs-choice-eyebrow", children: [
|
|
94
|
-
item.icon && /* @__PURE__ */
|
|
158
|
+
item.icon && /* @__PURE__ */ jsx3("span", { "aria-hidden": true, children: item.icon }),
|
|
95
159
|
item.eyebrow
|
|
96
160
|
] })
|
|
97
161
|
] }),
|
|
98
162
|
/* @__PURE__ */ jsxs("span", { className: "kzs-choice-frame", "aria-hidden": true, children: [
|
|
99
|
-
/* @__PURE__ */
|
|
100
|
-
/* @__PURE__ */
|
|
163
|
+
/* @__PURE__ */ jsx3("span", { className: "kzs-choice-floor" }),
|
|
164
|
+
/* @__PURE__ */ jsx3("span", { className: "kzs-choice-visual", children: item.visual })
|
|
101
165
|
] }),
|
|
102
|
-
/* @__PURE__ */
|
|
103
|
-
item.title && /* @__PURE__ */
|
|
104
|
-
item.extra && /* @__PURE__ */
|
|
105
|
-
item.meta && /* @__PURE__ */
|
|
106
|
-
/* @__PURE__ */
|
|
166
|
+
/* @__PURE__ */ jsx3("span", { className: "kzs-choice-name", children: item.name }),
|
|
167
|
+
item.title && /* @__PURE__ */ jsx3("span", { className: "kzs-choice-title", children: item.title }),
|
|
168
|
+
item.extra && /* @__PURE__ */ jsx3("span", { className: "kzs-choice-extra", children: item.extra }),
|
|
169
|
+
item.meta && /* @__PURE__ */ jsx3("span", { className: "kzs-choice-meta", children: item.meta }),
|
|
170
|
+
/* @__PURE__ */ jsx3("span", { className: "kzs-choice-check", "aria-hidden": true, children: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", width: "12", height: "12", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx3("path", { d: "m5 12 5 5 9-10" }) }) })
|
|
107
171
|
] });
|
|
108
172
|
const itemStyle = { "--i": i };
|
|
109
173
|
let tile;
|
|
110
174
|
if (selectable)
|
|
111
|
-
tile = /* @__PURE__ */
|
|
175
|
+
tile = /* @__PURE__ */ jsx3(
|
|
112
176
|
"button",
|
|
113
177
|
{
|
|
114
178
|
ref: (el) => {
|
|
@@ -128,10 +192,10 @@ function ChoiceGrid({
|
|
|
128
192
|
}
|
|
129
193
|
);
|
|
130
194
|
else if (item.href)
|
|
131
|
-
tile = /* @__PURE__ */
|
|
195
|
+
tile = /* @__PURE__ */ jsx3(BlockLink, { href: item.href, linkAs, className: "kzs-choice-item", "data-link": "", style: itemStyle, children: body });
|
|
132
196
|
else
|
|
133
|
-
tile = /* @__PURE__ */
|
|
134
|
-
return /* @__PURE__ */
|
|
197
|
+
tile = /* @__PURE__ */ jsx3("div", { className: "kzs-choice-item", style: itemStyle, children: body });
|
|
198
|
+
return /* @__PURE__ */ jsx3(Item, { className: cn("kzs-choice-slot", brandClass(item.brand)), style: itemStyle, children: tile }, item.id);
|
|
135
199
|
}) })
|
|
136
200
|
] });
|
|
137
201
|
}
|
package/dist/choice-grid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/choice-grid/choice-grid.tsx","../src/block-link.tsx"],"sourcesContent":["// Client. A row of choices shown as tiles — a visual, a name, a line (story.css): pick a plan, a persona, a team, a side.\n// Selectable (`onValueChange`), it is a radio group with roving focus; with `href` on its items, a set of links (a\n// line-up that leads somewhere); otherwise a showcase of the line-up.\nimport * as React from \"react\";\n\nimport { BlockLink, type LinkComponent } from \"../block-link\";\nimport { cn } from \"../cn\";\nimport { brandClass, type StoryVariant, storyVariantClass } from \"../story-variant\";\n\nexport interface ChoiceItem {\n /** The value of the choice (and its React key). */\n id: string;\n /** What represents it: a mark, an avatar, an illustration. Decorative — the name carries the meaning. */\n visual: React.ReactNode;\n /** Its name, short. */\n name: React.ReactNode;\n /** One line under the name: what it is, its role. */\n title?: React.ReactNode;\n /** A small line above the visual (a family, a tier), after its `icon`. */\n eyebrow?: React.ReactNode;\n /** A small glyph before the eyebrow (decorative). */\n icon?: React.ReactNode;\n /** A short tag in the top corner (“P1”, “New”, “Popular”). */\n badge?: React.ReactNode;\n /** Under the line: a gauge (`StatGauge`), facts, a price. */\n extra?: React.ReactNode;\n /** At the bottom: where the tile leads (“Chapter 2 ↓”, “See the plan”). */\n meta?: React.ReactNode;\n /** Makes the tile a link (ignored when the grid is selectable). */\n href?: string;\n /** A brand scope for this tile (`orazaka` → `.brand-orazaka`): its accent becomes that brand's. */\n brand?: string;\n /** Not selectable. */\n disabled?: boolean;\n}\n\nexport type ChoiceGridProps = Omit<React.HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"onChange\"> & {\n /** The accessible name of the group (“Choose your plan”) — passed translated. */\n label: string;\n /** The choices, in reading order. */\n items: readonly ChoiceItem[];\n /** A short mono line above the grid (a step, a tag). */\n tag?: React.ReactNode;\n /** A line beside the tag: what the choice means. */\n caption?: React.ReactNode;\n /** A call above the tiles, larger (“Choose your plan”). */\n prompt?: React.ReactNode;\n /** The chosen id (controlled). */\n value?: string;\n /** The chosen id at first (uncontrolled). */\n defaultValue?: string;\n /** Makes the grid a radio group: called with the id chosen by click, Enter, Space or the arrow keys. */\n onValueChange?: (id: string) => void;\n /** The app's link component for the tiles' internal `href`s (Next's `Link`); an `<a>` otherwise. */\n linkAs?: LinkComponent;\n /**\n * The number of columns (one on a phone).\n * @default min(items, 4)\n */\n columns?: number;\n /**\n * `default`: sober tiles. `arcade`: the select screen — the brand's light on each card, corners that lock, a flash.\n * @default \"default\"\n */\n variant?: StoryVariant;\n /** A brand scope for the whole grid (`orochia` → `.brand-orochia`). */\n brand?: string;\n /**\n * The tiles' arrival on mount (arcade: the corners locking one by one, the gauges filling). Always off under\n * `prefers-reduced-motion`.\n * @default true\n */\n animated?: boolean;\n};\n\nconst CORNERS = [\"tl\", \"tr\", \"bl\", \"br\"] as const;\n\n/**\n * A line-up of choices as tiles. With `onValueChange` (or `value`), a radio group: one tab stop, the arrow keys move\n * and choose, Home/End jump, the chosen tile is marked. With `href` on the items, a list of links. Otherwise a list\n * named by `label` that presents the line-up.\n */\nexport function ChoiceGrid({\n label,\n items,\n tag,\n caption,\n prompt,\n value,\n defaultValue,\n onValueChange,\n linkAs,\n columns,\n variant,\n brand,\n animated = true,\n className,\n style,\n ...props\n}: ChoiceGridProps) {\n const selectable = onValueChange !== undefined || value !== undefined;\n const [inner, setInner] = React.useState(defaultValue);\n const current = value ?? inner;\n const refs = React.useRef<(HTMLButtonElement | null)[]>([]);\n const enabled = items.map((item, i) => (item.disabled ? -1 : i)).filter((i) => i >= 0);\n const chosenIndex = items.findIndex((item) => item.id === current && !item.disabled);\n const focusIndex = chosenIndex >= 0 ? chosenIndex : (enabled[0] ?? 0);\n\n const choose = (i: number) => {\n const item = items[i];\n if (!item || item.disabled) return;\n if (value === undefined) setInner(item.id);\n onValueChange?.(item.id);\n };\n const onKeyDown = (e: React.KeyboardEvent, i: number) => {\n const at = enabled.indexOf(i);\n let next: number | undefined;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") next = enabled[(at + 1) % enabled.length];\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") next = enabled[(at - 1 + enabled.length) % enabled.length];\n else if (e.key === \"Home\") next = enabled[0];\n else if (e.key === \"End\") next = enabled[enabled.length - 1];\n if (next === undefined) return;\n e.preventDefault();\n refs.current[next]?.focus();\n choose(next);\n };\n\n // A radio group holds radios, not list items; a showcase or a set of links is a list named by `label`.\n const Grid = selectable ? \"div\" : \"ul\";\n const Item = selectable ? \"div\" : \"li\";\n const vars = { \"--kzs-cols\": columns ?? Math.min(items.length, 4), ...style } as React.CSSProperties;\n return (\n <div data-kzs-scene=\"\" data-animated={animated ? \"\" : undefined} className={cn(\"kzs-choice\", storyVariantClass(variant), brandClass(brand), className)} style={vars} {...props}>\n <span className=\"kzs-texture\" aria-hidden />\n <span className=\"kzs-scan kzs-loop\" aria-hidden />\n {(tag || caption) && (\n <p className=\"kzs-choice-head\">\n {tag && <span className=\"kzs-choice-tag\">{tag}</span>}\n {tag && caption && <span className=\"kzs-choice-sep\" aria-hidden />}\n {caption && <span className=\"kzs-choice-caption\">{caption}</span>}\n </p>\n )}\n {prompt && <p className=\"kzs-choice-prompt\">{prompt}</p>}\n <Grid className=\"kzs-choice-grid\" role={selectable ? \"radiogroup\" : undefined} aria-label={label}>\n {items.map((item, i) => {\n const body = (\n <>\n <span className=\"kzs-choice-aura\" aria-hidden />\n <span className=\"kzs-choice-flash\" aria-hidden />\n {CORNERS.map((at) => (\n <span key={at} className=\"kzs-corner\" data-at={at} aria-hidden />\n ))}\n {(item.badge || item.eyebrow || item.icon) && (\n <span className=\"kzs-choice-top\">\n {item.badge ? <span className=\"kzs-choice-badge\">{item.badge}</span> : <span />}\n {(item.eyebrow || item.icon) && (\n <span className=\"kzs-choice-eyebrow\">\n {item.icon && <span aria-hidden>{item.icon}</span>}\n {item.eyebrow}\n </span>\n )}\n </span>\n )}\n <span className=\"kzs-choice-frame\" aria-hidden>\n <span className=\"kzs-choice-floor\" />\n <span className=\"kzs-choice-visual\">{item.visual}</span>\n </span>\n <span className=\"kzs-choice-name\">{item.name}</span>\n {item.title && <span className=\"kzs-choice-title\">{item.title}</span>}\n {item.extra && <span className=\"kzs-choice-extra\">{item.extra}</span>}\n {item.meta && <span className=\"kzs-choice-meta\">{item.meta}</span>}\n <span className=\"kzs-choice-check\" aria-hidden>\n <svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"m5 12 5 5 9-10\" />\n </svg>\n </span>\n </>\n );\n const itemStyle = { \"--i\": i } as React.CSSProperties;\n let tile: React.ReactNode;\n if (selectable)\n tile = (\n <button\n ref={(el) => {\n refs.current[i] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={current === item.id}\n tabIndex={i === focusIndex ? 0 : -1}\n disabled={item.disabled}\n data-selectable=\"\"\n className=\"kzs-choice-item\"\n style={itemStyle}\n onClick={() => choose(i)}\n onKeyDown={(e) => onKeyDown(e, i)}\n >\n {body}\n </button>\n );\n else if (item.href)\n tile = (\n <BlockLink href={item.href} linkAs={linkAs} className=\"kzs-choice-item\" data-link=\"\" style={itemStyle}>\n {body}\n </BlockLink>\n );\n else\n tile = (\n <div className=\"kzs-choice-item\" style={itemStyle}>\n {body}\n </div>\n );\n return (\n <Item key={item.id} className={cn(\"kzs-choice-slot\", brandClass(item.brand))} style={itemStyle}>\n {tile}\n </Item>\n );\n })}\n </Grid>\n </div>\n );\n}\n","// The links of the page blocks (PageHero's back link, LinkCard, ArgumentCard, ActionBar): an internal path renders\n// through the app's router link (`linkAs`, e.g. Next's `Link`), anything else is an `<a>` that opens in a new tab.\n// Shared by the block entries; never exported on its own.\nimport type * as React from \"react\";\n\n/** The app's link component (Next's `Link`, React Router's `Link`…): receives `href`, `className` and the children. */\nexport type LinkComponent = React.ElementType<{ href: string; className?: string; children?: React.ReactNode; \"aria-label\"?: string; \"aria-describedby\"?: string }>;\n\n/** An address that leaves the app: an absolute URL or a protocol (`mailto:`). */\nexport const isExternal = (href: string) => /^[a-z][a-z0-9+.-]*:/i.test(href);\n\nexport function BlockLink({\n href,\n linkAs: Link = \"a\",\n external = isExternal(href),\n rel,\n ...props\n}: Omit<React.ComponentProps<\"a\">, \"href\"> & { href: string; linkAs?: LinkComponent; external?: boolean }) {\n if (external) return <a href={href} target=\"_blank\" rel={rel ?? \"noopener noreferrer\"} {...props} />;\n const Comp = Link as React.ElementType;\n return <Comp href={href} {...props} />;\n}\n"],"mappings":"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","names":["jsx"]}
|
|
1
|
+
{"version":3,"sources":["../src/choice-grid/choice-grid.tsx","../src/block-link.tsx","../src/router/route-state.tsx"],"sourcesContent":["// Client. A row of choices shown as tiles — a visual, a name, a line (story.css): pick a plan, a persona, a team, a side.\n// Selectable (`onValueChange`), it is a radio group with roving focus; with `href` on its items, a set of links (a\n// line-up that leads somewhere); otherwise a showcase of the line-up.\nimport * as React from \"react\";\n\nimport { BlockLink, type LinkComponent } from \"../block-link\";\nimport { cn } from \"../cn\";\nimport { useRouteBinding } from \"../router\";\nimport { brandClass, type StoryVariant, storyVariantClass } from \"../story-variant\";\n\nexport interface ChoiceItem {\n /** The value of the choice (and its React key). */\n id: string;\n /** What represents it: a mark, an avatar, an illustration. Decorative — the name carries the meaning. */\n visual: React.ReactNode;\n /** Its name, short. */\n name: React.ReactNode;\n /** One line under the name: what it is, its role. */\n title?: React.ReactNode;\n /** A small line above the visual (a family, a tier), after its `icon`. */\n eyebrow?: React.ReactNode;\n /** A small glyph before the eyebrow (decorative). */\n icon?: React.ReactNode;\n /** A short tag in the top corner (“P1”, “New”, “Popular”). */\n badge?: React.ReactNode;\n /** Under the line: a gauge (`StatGauge`), facts, a price. */\n extra?: React.ReactNode;\n /** At the bottom: where the tile leads (“Chapter 2 ↓”, “See the plan”). */\n meta?: React.ReactNode;\n /** Makes the tile a link (ignored when the grid is selectable). */\n href?: string;\n /** A brand scope for this tile (`orazaka` → `.brand-orazaka`): its accent becomes that brand's. */\n brand?: string;\n /** Not selectable. */\n disabled?: boolean;\n}\n\nexport type ChoiceGridProps = Omit<React.HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"onChange\"> & {\n /** The accessible name of the group (“Choose your plan”) — passed translated. */\n label: string;\n /** The choices, in reading order. */\n items: readonly ChoiceItem[];\n /** A short mono line above the grid (a step, a tag). */\n tag?: React.ReactNode;\n /** A line beside the tag: what the choice means. */\n caption?: React.ReactNode;\n /** A call above the tiles, larger (“Choose your plan”). */\n prompt?: React.ReactNode;\n /** The chosen id (controlled). */\n value?: string;\n /** The chosen id at first (uncontrolled). */\n defaultValue?: string;\n /** Makes the grid a radio group: called with the id chosen by click, Enter, Space or the arrow keys. */\n onValueChange?: (id: string) => void;\n /** Binds the choice to this search parameter of the URL (`?type=…`): the URL is the state. Without it the state is local — only for an ephemeral choice. */\n routeKey?: string;\n /** The ids the parameter may hold; the first is the default unless `defaultValue` says otherwise. */\n routeValues?: readonly string[];\n /** The app's link component for the tiles' internal `href`s (Next's `Link`); an `<a>` otherwise. */\n linkAs?: LinkComponent;\n /**\n * The number of columns (one on a phone).\n * @default min(items, 4)\n */\n columns?: number;\n /**\n * `default`: sober tiles. `arcade`: the select screen — the brand's light on each card, corners that lock, a flash.\n * @default \"default\"\n */\n variant?: StoryVariant;\n /** A brand scope for the whole grid (`orochia` → `.brand-orochia`). */\n brand?: string;\n /**\n * The tiles' arrival on mount (arcade: the corners locking one by one, the gauges filling). Always off under\n * `prefers-reduced-motion`.\n * @default true\n */\n animated?: boolean;\n};\n\nconst CORNERS = [\"tl\", \"tr\", \"bl\", \"br\"] as const;\n\n/**\n * A line-up of choices as tiles. With `onValueChange` (or `value`), a radio group: one tab stop, the arrow keys move\n * and choose, Home/End jump, the chosen tile is marked. With `href` on the items, a list of links. Otherwise a list\n * named by `label` that presents the line-up.\n */\nexport function ChoiceGrid({\n label,\n items,\n tag,\n caption,\n prompt,\n value: valueProp,\n defaultValue: defaultValueProp,\n onValueChange: onValueChangeProp,\n routeKey,\n routeValues,\n linkAs,\n columns,\n variant,\n brand,\n animated = true,\n className,\n style,\n ...props\n}: ChoiceGridProps) {\n const { value, defaultValue, onValueChange } = useRouteBinding(routeKey, routeValues, { value: valueProp, defaultValue: defaultValueProp, onValueChange: onValueChangeProp });\n const selectable = onValueChange !== undefined || value !== undefined;\n const [inner, setInner] = React.useState(defaultValue);\n const current = value ?? inner;\n const refs = React.useRef<(HTMLButtonElement | null)[]>([]);\n const enabled = items.map((item, i) => (item.disabled ? -1 : i)).filter((i) => i >= 0);\n const chosenIndex = items.findIndex((item) => item.id === current && !item.disabled);\n const focusIndex = chosenIndex >= 0 ? chosenIndex : (enabled[0] ?? 0);\n\n const choose = (i: number) => {\n const item = items[i];\n if (!item || item.disabled) return;\n if (value === undefined) setInner(item.id);\n onValueChange?.(item.id);\n };\n const onKeyDown = (e: React.KeyboardEvent, i: number) => {\n const at = enabled.indexOf(i);\n let next: number | undefined;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") next = enabled[(at + 1) % enabled.length];\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") next = enabled[(at - 1 + enabled.length) % enabled.length];\n else if (e.key === \"Home\") next = enabled[0];\n else if (e.key === \"End\") next = enabled[enabled.length - 1];\n if (next === undefined) return;\n e.preventDefault();\n refs.current[next]?.focus();\n choose(next);\n };\n\n // A radio group holds radios, not list items; a showcase or a set of links is a list named by `label`.\n const Grid = selectable ? \"div\" : \"ul\";\n const Item = selectable ? \"div\" : \"li\";\n const vars = { \"--kzs-cols\": columns ?? Math.min(items.length, 4), ...style } as React.CSSProperties;\n return (\n <div data-kzs-scene=\"\" data-animated={animated ? \"\" : undefined} className={cn(\"kzs-choice\", storyVariantClass(variant), brandClass(brand), className)} style={vars} {...props}>\n <span className=\"kzs-texture\" aria-hidden />\n <span className=\"kzs-scan kzs-loop\" aria-hidden />\n {(tag || caption) && (\n <p className=\"kzs-choice-head\">\n {tag && <span className=\"kzs-choice-tag\">{tag}</span>}\n {tag && caption && <span className=\"kzs-choice-sep\" aria-hidden />}\n {caption && <span className=\"kzs-choice-caption\">{caption}</span>}\n </p>\n )}\n {prompt && <p className=\"kzs-choice-prompt\">{prompt}</p>}\n <Grid className=\"kzs-choice-grid\" role={selectable ? \"radiogroup\" : undefined} aria-label={label}>\n {items.map((item, i) => {\n const body = (\n <>\n <span className=\"kzs-choice-aura\" aria-hidden />\n <span className=\"kzs-choice-flash\" aria-hidden />\n {CORNERS.map((at) => (\n <span key={at} className=\"kzs-corner\" data-at={at} aria-hidden />\n ))}\n {(item.badge || item.eyebrow || item.icon) && (\n <span className=\"kzs-choice-top\">\n {item.badge ? <span className=\"kzs-choice-badge\">{item.badge}</span> : <span />}\n {(item.eyebrow || item.icon) && (\n <span className=\"kzs-choice-eyebrow\">\n {item.icon && <span aria-hidden>{item.icon}</span>}\n {item.eyebrow}\n </span>\n )}\n </span>\n )}\n <span className=\"kzs-choice-frame\" aria-hidden>\n <span className=\"kzs-choice-floor\" />\n <span className=\"kzs-choice-visual\">{item.visual}</span>\n </span>\n <span className=\"kzs-choice-name\">{item.name}</span>\n {item.title && <span className=\"kzs-choice-title\">{item.title}</span>}\n {item.extra && <span className=\"kzs-choice-extra\">{item.extra}</span>}\n {item.meta && <span className=\"kzs-choice-meta\">{item.meta}</span>}\n <span className=\"kzs-choice-check\" aria-hidden>\n <svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"m5 12 5 5 9-10\" />\n </svg>\n </span>\n </>\n );\n const itemStyle = { \"--i\": i } as React.CSSProperties;\n let tile: React.ReactNode;\n if (selectable)\n tile = (\n <button\n ref={(el) => {\n refs.current[i] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={current === item.id}\n tabIndex={i === focusIndex ? 0 : -1}\n disabled={item.disabled}\n data-selectable=\"\"\n className=\"kzs-choice-item\"\n style={itemStyle}\n onClick={() => choose(i)}\n onKeyDown={(e) => onKeyDown(e, i)}\n >\n {body}\n </button>\n );\n else if (item.href)\n tile = (\n <BlockLink href={item.href} linkAs={linkAs} className=\"kzs-choice-item\" data-link=\"\" style={itemStyle}>\n {body}\n </BlockLink>\n );\n else\n tile = (\n <div className=\"kzs-choice-item\" style={itemStyle}>\n {body}\n </div>\n );\n return (\n <Item key={item.id} className={cn(\"kzs-choice-slot\", brandClass(item.brand))} style={itemStyle}>\n {tile}\n </Item>\n );\n })}\n </Grid>\n </div>\n );\n}\n","// The links of the page blocks (PageHero's back link, LinkCard, ArgumentCard, ActionBar): an internal path renders\n// through the app's router link (`linkAs`, e.g. Next's `Link`), anything else is an `<a>` that opens in a new tab.\n// Shared by the block entries; never exported on its own.\nimport type * as React from \"react\";\n\n/** The app's link component (Next's `Link`, React Router's `Link`…): receives `href`, `className` and the children. */\nexport type LinkComponent = React.ElementType<{ href: string; className?: string; children?: React.ReactNode; \"aria-label\"?: string; \"aria-describedby\"?: string }>;\n\n/** An address that leaves the app: an absolute URL or a protocol (`mailto:`). */\nexport const isExternal = (href: string) => /^[a-z][a-z0-9+.-]*:/i.test(href);\n\nexport function BlockLink({\n href,\n linkAs: Link = \"a\",\n external = isExternal(href),\n rel,\n ...props\n}: Omit<React.ComponentProps<\"a\">, \"href\"> & { href: string; linkAs?: LinkComponent; external?: boolean }) {\n if (external) return <a href={href} target=\"_blank\" rel={rel ?? \"noopener noreferrer\"} {...props} />;\n const Comp = Link as React.ElementType;\n return <Comp href={href} {...props} />;\n}\n","// Client. The URL is the state: a selection the user would want to share, reload or come back to (a tab, a type, a\n// filter) lives in a search parameter, never in `useState` (Oussama, 2026-10-22: « changer les tabs doit changer les routes »).\n// One primitive reads and writes it: `useRouteState`. The selection components take `routeKey` and bind to it.\nimport * as React from \"react\";\n\n/** How a router moves and reads the address: the browser's by default, your framework's through `RouteStateProvider`. */\nexport interface RouteAdapter {\n /** The current query string, with or without `?` (`\"\"` on the server). */\n search: () => string;\n /** Go to `search` (`?type=bail`, or `\"\"`), keeping the path and the hash; `replace` keeps the history as it is. */\n navigate: (search: string, options: { replace: boolean }) => void;\n /** Calls `onChange` when the address changes under you (back, forward, another navigation); returns the unsubscribe. */\n subscribe: (onChange: () => void) => () => void;\n}\n\nconst CHANGE = \"kz-route-state\";\n\n/** The History API: any web app (Vite, a static site). Next.js apps pass their router's adapter once, in the layout. */\nexport const browserAdapter: RouteAdapter = {\n search: () => (typeof window === \"undefined\" ? \"\" : window.location.search),\n navigate: (search, { replace }) => {\n const url = `${window.location.pathname}${search}${window.location.hash}`;\n window.history[replace ? \"replaceState\" : \"pushState\"](window.history.state, \"\", url);\n window.dispatchEvent(new Event(CHANGE));\n },\n subscribe: (onChange) => {\n window.addEventListener(\"popstate\", onChange);\n window.addEventListener(CHANGE, onChange);\n return () => {\n window.removeEventListener(\"popstate\", onChange);\n window.removeEventListener(CHANGE, onChange);\n };\n },\n};\n\nconst AdapterContext = React.createContext<RouteAdapter>(browserAdapter);\n\nexport interface RouteStateProviderProps {\n /** How your router reads the query string, navigates and tells about changes. */\n adapter: RouteAdapter;\n children: React.ReactNode;\n}\n\n/** Gives `useRouteState` your router: `<RouteStateProvider adapter={nextAdapter}>` once, in the root layout. */\nexport function RouteStateProvider({ adapter, children }: RouteStateProviderProps) {\n return <AdapterContext.Provider value={adapter}>{children}</AdapterContext.Provider>;\n}\n\nexport interface RouteStateOptions<V extends string> {\n /** The values the parameter may hold, as a const list: anything else in the address reads as the default. Omit for free text. */\n values?: readonly V[];\n /** The value when the parameter is absent. It is never written to the address (a clean URL). */\n default: V;\n /** `push` (the default) keeps each change in the history, `replace` overwrites it: use `replace` for a filter typed or toggled in passing. */\n history?: \"push\" | \"replace\";\n}\n\n/** The value a query string holds for `key`, checked against `values`. Pure: the hook and the tests share it. */\nexport function readRouteValue<V extends string>(search: string, key: string, { values, default: fallback }: Pick<RouteStateOptions<V>, \"values\" | \"default\">): V {\n const raw = new URLSearchParams(search).get(key);\n if (raw === null) return fallback;\n return !values || (values as readonly string[]).includes(raw) ? (raw as V) : fallback;\n}\n\n/** The query string with `key` set to `value` (removed when it is the default); every other parameter is kept. Pure. */\nexport function writeRouteValue(search: string, key: string, value: string, fallback: string): string {\n const params = new URLSearchParams(search);\n if (value === fallback) params.delete(key);\n else params.set(key, value);\n const text = params.toString();\n return text ? `?${text}` : \"\";\n}\n\n/**\n * A value kept in the URL's search parameter `key`: `const [type, setType] = useRouteState(\"type\", { values: TYPES, default: \"auto\" })`\n * with `const TYPES = [\"auto\", \"lease\", \"contract\", \"invoice\", \"other\"] as const`. The value is typed from the list, an\n * unknown one in the address reads as the default, the default is not written, other parameters are kept.\n * `key` undefined makes the hook inert (it returns the default) so a component can call it unconditionally.\n */\nexport function useRouteState<V extends string>(key: string | undefined, { values, default: fallback, history = \"push\" }: RouteStateOptions<V>): readonly [V, (next: V) => void] {\n const adapter = React.useContext(AdapterContext);\n const search = React.useSyncExternalStore(adapter.subscribe, adapter.search, () => \"\");\n const value = key === undefined ? fallback : readRouteValue(search, key, { values, default: fallback });\n const set = React.useCallback(\n (next: V) => {\n if (key === undefined) return;\n adapter.navigate(writeRouteValue(adapter.search(), key, next, fallback), { replace: history === \"replace\" });\n },\n [adapter, key, fallback, history],\n );\n return [value, set] as const;\n}\n\n/** What a selection component spreads on its root: `value` and `onValueChange` bound to the URL when `routeKey` is set. */\nexport interface RouteBindable {\n value?: string | null;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n}\n\n/** The props of a selection component bound to the URL (`routeKey`), or its own, unchanged, when there is no `routeKey`. */\nexport function useRouteBinding<P extends RouteBindable>(routeKey: string | undefined, routeValues: readonly string[] | undefined, props: P): P {\n const initial = props.defaultValue ?? props.value ?? routeValues?.[0] ?? \"\";\n const [value, set] = useRouteState<string>(routeKey, { values: routeValues, default: initial });\n if (routeKey === undefined) return props;\n return {\n ...props,\n value,\n defaultValue: undefined,\n onValueChange: (next: string) => {\n set(next);\n props.onValueChange?.(next);\n },\n };\n}\n"],"mappings":"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","names":["React","jsx","jsx"]}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
defineRecipe
|
|
4
|
+
} from "./chunk-D3T54RSL.js";
|
|
5
|
+
|
|
6
|
+
// src/chip/chip.tsx
|
|
7
|
+
import { Toggle as TogglePrimitive, ToggleGroup as ToggleGroupPrimitive } from "radix-ui";
|
|
8
|
+
import * as React2 from "react";
|
|
9
|
+
import { tv } from "tailwind-variants";
|
|
10
|
+
|
|
11
|
+
// src/router/route-state.tsx
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
import { jsx } from "react/jsx-runtime";
|
|
14
|
+
var CHANGE = "kz-route-state";
|
|
15
|
+
var browserAdapter = {
|
|
16
|
+
search: () => typeof window === "undefined" ? "" : window.location.search,
|
|
17
|
+
navigate: (search, { replace }) => {
|
|
18
|
+
const url = `${window.location.pathname}${search}${window.location.hash}`;
|
|
19
|
+
window.history[replace ? "replaceState" : "pushState"](window.history.state, "", url);
|
|
20
|
+
window.dispatchEvent(new Event(CHANGE));
|
|
21
|
+
},
|
|
22
|
+
subscribe: (onChange) => {
|
|
23
|
+
window.addEventListener("popstate", onChange);
|
|
24
|
+
window.addEventListener(CHANGE, onChange);
|
|
25
|
+
return () => {
|
|
26
|
+
window.removeEventListener("popstate", onChange);
|
|
27
|
+
window.removeEventListener(CHANGE, onChange);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var AdapterContext = React.createContext(browserAdapter);
|
|
32
|
+
function readRouteValue(search, key, { values, default: fallback }) {
|
|
33
|
+
const raw = new URLSearchParams(search).get(key);
|
|
34
|
+
if (raw === null) return fallback;
|
|
35
|
+
return !values || values.includes(raw) ? raw : fallback;
|
|
36
|
+
}
|
|
37
|
+
function writeRouteValue(search, key, value, fallback) {
|
|
38
|
+
const params = new URLSearchParams(search);
|
|
39
|
+
if (value === fallback) params.delete(key);
|
|
40
|
+
else params.set(key, value);
|
|
41
|
+
const text = params.toString();
|
|
42
|
+
return text ? `?${text}` : "";
|
|
43
|
+
}
|
|
44
|
+
function useRouteState(key, { values, default: fallback, history = "push" }) {
|
|
45
|
+
const adapter = React.useContext(AdapterContext);
|
|
46
|
+
const search = React.useSyncExternalStore(adapter.subscribe, adapter.search, () => "");
|
|
47
|
+
const value = key === void 0 ? fallback : readRouteValue(search, key, { values, default: fallback });
|
|
48
|
+
const set = React.useCallback(
|
|
49
|
+
(next) => {
|
|
50
|
+
if (key === void 0) return;
|
|
51
|
+
adapter.navigate(writeRouteValue(adapter.search(), key, next, fallback), { replace: history === "replace" });
|
|
52
|
+
},
|
|
53
|
+
[adapter, key, fallback, history]
|
|
54
|
+
);
|
|
55
|
+
return [value, set];
|
|
56
|
+
}
|
|
57
|
+
function useRouteBinding(routeKey, routeValues, props) {
|
|
58
|
+
const initial = props.defaultValue ?? props.value ?? routeValues?.[0] ?? "";
|
|
59
|
+
const [value, set] = useRouteState(routeKey, { values: routeValues, default: initial });
|
|
60
|
+
if (routeKey === void 0) return props;
|
|
61
|
+
return {
|
|
62
|
+
...props,
|
|
63
|
+
value,
|
|
64
|
+
defaultValue: void 0,
|
|
65
|
+
onValueChange: (next) => {
|
|
66
|
+
set(next);
|
|
67
|
+
props.onValueChange?.(next);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/chip/recipe.ts
|
|
73
|
+
var chipRecipe = defineRecipe({
|
|
74
|
+
slots: {
|
|
75
|
+
group: "flex flex-wrap items-center gap-2 data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch",
|
|
76
|
+
base: "inline-flex shrink-0 select-none items-center justify-center gap-1.5 whitespace-nowrap rounded-sm border font-semibold transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40 border-border-default bg-surface-1 text-fg-secondary hover:border-border-strong hover:text-fg data-[state=on]:border-accent data-[state=on]:bg-accent-soft data-[state=on]:text-fg",
|
|
77
|
+
remove: "-mr-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
|
|
78
|
+
},
|
|
79
|
+
variants: {
|
|
80
|
+
size: { sm: { base: "h-7 px-3 text-xs" }, md: { base: "h-9 px-3.5 text-xs" } }
|
|
81
|
+
},
|
|
82
|
+
defaultVariants: { size: "md" }
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// src/chip/chip.tsx
|
|
86
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
87
|
+
var chip = tv(chipRecipe);
|
|
88
|
+
var ChipGroupContext = React2.createContext(null);
|
|
89
|
+
function RemoveIcon() {
|
|
90
|
+
return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", className: "h-3.5 w-3.5", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "M18 6 6 18M6 6l12 12" }) });
|
|
91
|
+
}
|
|
92
|
+
function ChipBase(props) {
|
|
93
|
+
const group = React2.useContext(ChipGroupContext);
|
|
94
|
+
if (props.removable) {
|
|
95
|
+
const { removable: _removable2, removeLabel, onRemove, disabled, size: size2, className: className2, children, ...rest2 } = props;
|
|
96
|
+
const s2 = chip({ size: size2 ?? group?.size });
|
|
97
|
+
return /* @__PURE__ */ jsxs("span", { "data-removable": "", className: s2.base({ className: className2 }), ...rest2, children: [
|
|
98
|
+
children,
|
|
99
|
+
/* @__PURE__ */ jsx2("button", { type: "button", "aria-label": removeLabel, title: removeLabel, disabled, onClick: onRemove, className: s2.remove(), children: /* @__PURE__ */ jsx2(RemoveIcon, {}) })
|
|
100
|
+
] });
|
|
101
|
+
}
|
|
102
|
+
const { removable: _removable, value, selected, defaultSelected, onSelectedChange, size, className, ...rest } = props;
|
|
103
|
+
const s = chip({ size: size ?? group?.size });
|
|
104
|
+
if (group) {
|
|
105
|
+
if (value === void 0) throw new Error("Chip: a chip inside Chip.Group needs a `value`.");
|
|
106
|
+
return /* @__PURE__ */ jsx2(ToggleGroupPrimitive.Item, { value, className: s.base({ className }), ...rest });
|
|
107
|
+
}
|
|
108
|
+
return /* @__PURE__ */ jsx2(
|
|
109
|
+
TogglePrimitive.Root,
|
|
110
|
+
{
|
|
111
|
+
pressed: selected,
|
|
112
|
+
defaultPressed: defaultSelected,
|
|
113
|
+
onPressedChange: onSelectedChange,
|
|
114
|
+
className: s.base({ className }),
|
|
115
|
+
...rest
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function ChipGroup({ label, size, className, children, ...props }) {
|
|
120
|
+
const single = props.type === "single" ? props : void 0;
|
|
121
|
+
const routed = useRouteBinding(single?.routeKey, single?.routeValues, { value: single?.value, defaultValue: single?.defaultValue, onValueChange: single?.onValueChange });
|
|
122
|
+
const s = chip({ size });
|
|
123
|
+
const context = React2.useMemo(() => ({ size }), [size]);
|
|
124
|
+
const common = { "aria-label": label, className: s.group({ className }) };
|
|
125
|
+
let group;
|
|
126
|
+
if (props.type === "single") {
|
|
127
|
+
const { type: _type, value: _value, defaultValue: _defaultValue, onValueChange: _onValueChange, required, routeKey: _routeKey, routeValues: _routeValues, ...rest } = props;
|
|
128
|
+
group = /* @__PURE__ */ jsx2(SingleGroup, { ...rest, ...common, value: routed.value, defaultValue: routed.defaultValue, onValueChange: routed.onValueChange, required, children });
|
|
129
|
+
} else {
|
|
130
|
+
const { type: _type, required: _required, ...rest } = props;
|
|
131
|
+
group = /* @__PURE__ */ jsx2(ToggleGroupPrimitive.Root, { type: "multiple", ...rest, ...common, children });
|
|
132
|
+
}
|
|
133
|
+
return /* @__PURE__ */ jsx2(ChipGroupContext.Provider, { value: context, children: group });
|
|
134
|
+
}
|
|
135
|
+
function SingleGroup({
|
|
136
|
+
value,
|
|
137
|
+
defaultValue,
|
|
138
|
+
onValueChange,
|
|
139
|
+
required,
|
|
140
|
+
...props
|
|
141
|
+
}) {
|
|
142
|
+
const [inner, setInner] = React2.useState(defaultValue ?? "");
|
|
143
|
+
const current = value ?? inner;
|
|
144
|
+
return /* @__PURE__ */ jsx2(
|
|
145
|
+
ToggleGroupPrimitive.Root,
|
|
146
|
+
{
|
|
147
|
+
type: "single",
|
|
148
|
+
value: current,
|
|
149
|
+
onValueChange: (next) => {
|
|
150
|
+
if (required && !next) return;
|
|
151
|
+
if (value === void 0) setInner(next);
|
|
152
|
+
onValueChange?.(next);
|
|
153
|
+
},
|
|
154
|
+
...props
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
var Chip = Object.assign(ChipBase, { Group: ChipGroup });
|
|
159
|
+
|
|
160
|
+
export {
|
|
161
|
+
Chip
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=chunk-HHFWK2HH.js.map
|