@krigsvold/react 0.0.0-stage → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # @krigsvold/react changelog
2
+
3
+ ## 0.2.0 — unpublished transfer candidate
4
+
5
+ Prepared standalone source, exact core dependency and independent build. This is not an approved release. Existing version 0.1.0 is published; the historical heading below records its earlier preparation state.
6
+
7
+ ## 0.1.0 — experimental candidate, unpublished
8
+
9
+ Initial 22-component React 19 API with native form controls, modal dialog, manual-activation tabs, article compositions, table and pagination. Includes family CSS, TypeScript declarations and MIT source.
10
+
11
+ Behavior and keyboard regression checks cover both families. Manual screen-reader review, RTL/localization and sustained consumer evidence remain open. See docs/components.md and docs/release-policy.md in the source repository. No prior React version requires migration.
12
+
13
+ - Add explicit AppShell navigation placement, navigation label, and header color-mode composition; preserve existing defaults.
14
+ - Add native family reference, application grammar, collection/editor consumer, and independently usable MCP/skill tooling in the workbench.
15
+
16
+ - Reconcile source type, control, table, quote and index roles; remove unsupported shell/title ornament and built-in colophon. Keep published theme 2.1.0 unchanged.
17
+ - Add TableOfContents depth and index/inline variants; separate article contents from supporting context; add DefinitionList rows/stack/grid layout choices in both families.
18
+ - Correct invalid styling to require aria-invalid=true; preserve native control and recovery behavior.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 James P. Howard, II
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE.md ADDED
@@ -0,0 +1,7 @@
1
+ # Third-party notices
2
+
3
+ The workbench source, specifications, and React component source are copyright 2026 James P. Howard, II and distributed under MIT. Dependencies retain their own licenses and notices.
4
+
5
+ The React packages consume the separately distributed @martlet/theme and @krigsvold/theme packages. Local font files come through their Fontsource dependencies. Installed metadata identifies Cambo, Cinzel, Lato, IBM Plex Mono, Roboto, and Roboto Mono as OFL-1.1, and Roboto Slab as Apache-2.0. Preserve the font packages' included copyright and license files when redistributing them. No proprietary font is bundled or required.
6
+
7
+ React and React DOM retain their upstream MIT licenses. Build/test tools retain their own licenses. See each installed dependency's package and license files for the complete notices. No external UI-block code or commercial component catalogue is included.
package/README.md CHANGED
@@ -1,3 +1,17 @@
1
- # Temporary Holding Version
1
+ # Krigsvold React (experimental)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ This independent repository contains the experimental `@krigsvold/react` component implementation. Existing npm releases remain available. Standalone staging is prepared in the reviewed `stage-release.yml` workflow and `react-production-release` environment; npm trust and publication authority must be verified during the coordinated workbench cutover.
4
+
5
+ ## Development
6
+
7
+ Use Node 24: `npm ci --ignore-scripts`, then `npm test`. `npm pack --ignore-scripts` creates a local candidate archive. The public repository keeps `private:true` in its manifest to prevent accidental publication; the reviewed staging workflow creates a publishable manifest only in an isolated export.
8
+
9
+ The exact published `@krigsvold/core` dependency supplies styles. Import `@krigsvold/react/styles.css`; load `@krigsvold/core/fonts.css` separately if desired. The public component API is experimental. Refer to [the historical component contracts](https://github.com/k3jph/design-workbench/blob/80a5635abe4de7a07a1983de0b3ef796b101e047/docs/components.md) for behavior and acceptance criteria.
10
+
11
+ The independent reference catalog contains all 50 canonical recipes, three dashboards and the scientific workspace. Browser verification covers Chromium, Firefox and WebKit at root and `/preview/`. `npm run build:gallery` builds the static site in `examples/react-catalog/dist/krigsvold`; use the examples index at `/examples/`. `docs-delivery.json` explicitly disables the advanced remote archive pathway until live Worker isolation and independent anti-rollback authority are verified. The ordinary static documentation build is the production delivery path; it makes no administrator-immutable archive claim. Automated checks do not replace manual assistive-technology assessment. See PROVENANCE.md for the source baseline and ownership transition.
12
+
13
+ Build a complete deployment from a clean committed checkout with `npm run build:reference -- /fresh/output/path`. This assembles root and `/preview` catalogs plus checksum-verified static `/docs/v/0.2.0/` snapshots using the existing documentation implementation. `version.json` records the exact source, Core integrity and explicitly disabled advanced archive mode. The output is static-only and needs no storage credentials or Worker bindings beyond assets.
14
+
15
+ After npm stage approval, independently verify the exact registry archive and `npm audit signatures --json --include-attestations` output with `RELEASE_TAG=v0.2.0 APPROVED_SHA=<reviewed-main-sha> node scripts/tag-release-cli.mjs verify-stage-provenance <audit.json> <archive.tgz>`. This operation pins the standalone staging workflow, `main`, dispatch event and reviewed commit; the disabled tag workflow is a different identity.
16
+
17
+ Reproduce publishable archives with npm 11.15.0: export the exact reviewed source with `git archive`, set only the exported manifest to `private:false`, install and build in that isolated export, run `node scripts/normalize-package-modes.mjs .` there, then `npm pack --ignore-scripts`. All published regular files use mode 0644 and directories 0755; the normalizer refuses a Git working tree, executable-bin package or symlink payload.
@@ -0,0 +1,123 @@
1
+ import { type ReactNode, type ComponentPropsWithRef } from "react";
2
+ export declare function Button({ variant, className, type, ...props }: ComponentPropsWithRef<"button"> & {
3
+ variant?: "primary" | "secondary" | "quiet";
4
+ }): import("react").JSX.Element;
5
+ export declare function TextInput({ className, ...props }: ComponentPropsWithRef<"input">): import("react").JSX.Element;
6
+ export declare function Select({ className, ...props }: ComponentPropsWithRef<"select">): import("react").JSX.Element;
7
+ export declare function Textarea({ className, ...props }: ComponentPropsWithRef<"textarea">): import("react").JSX.Element;
8
+ export type FieldControl = {
9
+ id: string;
10
+ "aria-describedby"?: string;
11
+ "aria-invalid"?: true;
12
+ };
13
+ export declare function Field({ id: providedId, label, hint, error, hintId: providedHintId, errorId: providedErrorId, children, }: {
14
+ id?: string;
15
+ label: string;
16
+ hint?: string;
17
+ error?: string;
18
+ hintId?: string;
19
+ errorId?: string;
20
+ children: (props: FieldControl) => ReactNode;
21
+ }): import("react").JSX.Element;
22
+ export declare function Notice({ kind, children, }: {
23
+ kind?: "info" | "success" | "error";
24
+ children: ReactNode;
25
+ }): import("react").JSX.Element;
26
+ export declare function Badge({ children }: {
27
+ children: ReactNode;
28
+ }): import("react").JSX.Element;
29
+ export declare function DataTable({ caption, children, className, ...props }: ComponentPropsWithRef<"table"> & {
30
+ caption: string;
31
+ }): import("react").JSX.Element;
32
+ export declare function AppShell({ brand, navigationLabel, navigationPlacement, headerMode, navigation, utilities, children, }: {
33
+ brand: ReactNode;
34
+ navigationLabel?: string;
35
+ navigationPlacement?: "default" | "top" | "side" | "none";
36
+ headerMode?: "light" | "dark";
37
+ navigation: ReactNode;
38
+ utilities: ReactNode;
39
+ children: ReactNode;
40
+ }): import("react").JSX.Element;
41
+ export declare function Dialog({ open, title, onDismiss, children, }: {
42
+ open: boolean;
43
+ title: string;
44
+ onDismiss: () => void;
45
+ children: ReactNode;
46
+ }): import("react").JSX.Element;
47
+ export declare function Checkbox({ label, id, className, ...props }: Omit<ComponentPropsWithRef<"input">, "type"> & {
48
+ label: ReactNode;
49
+ }): import("react").JSX.Element;
50
+ export declare function Fieldset({ legend, hint, error, children, className, ...props }: ComponentPropsWithRef<"fieldset"> & {
51
+ legend: ReactNode;
52
+ hint?: string;
53
+ error?: string;
54
+ }): import("react").JSX.Element;
55
+ export declare function Disclosure({ summary, children, className, ...props }: Omit<ComponentPropsWithRef<"details">, "children"> & {
56
+ summary: ReactNode;
57
+ children: ReactNode;
58
+ }): import("react").JSX.Element;
59
+ export type SummaryError = {
60
+ message: string;
61
+ targetId?: string;
62
+ };
63
+ export declare function ErrorSummary({ title, errors, children, className, ...props }: Omit<ComponentPropsWithRef<"div">, "title"> & {
64
+ title: ReactNode;
65
+ errors?: SummaryError[];
66
+ }): import("react").JSX.Element;
67
+ export type ContentsItem = {
68
+ id: string;
69
+ label: ReactNode;
70
+ depth?: 2 | 3;
71
+ };
72
+ export declare function TableOfContents({ items, label, variant, className, ...props }: Omit<ComponentPropsWithRef<"nav">, "children"> & {
73
+ items: readonly ContentsItem[];
74
+ label?: string;
75
+ variant?: "index" | "inline";
76
+ }): import("react").JSX.Element;
77
+ export declare function Article({ title, titleLevel, eyebrow, summary, metadata, contents, aside, contextLabel, footer, children, className, ...props }: Omit<ComponentPropsWithRef<"article">, "title"> & {
78
+ title: ReactNode;
79
+ titleLevel?: 1 | 2;
80
+ eyebrow?: ReactNode;
81
+ summary?: ReactNode;
82
+ metadata?: ReactNode;
83
+ contents?: ReactNode;
84
+ aside?: ReactNode;
85
+ contextLabel?: string;
86
+ footer?: ReactNode;
87
+ }): import("react").JSX.Element;
88
+ export declare function FileInput({ className, ...props }: Omit<ComponentPropsWithRef<"input">, "type" | "value" | "defaultValue">): import("react").JSX.Element;
89
+ export declare function Radio({ label, id, className, ...props }: Omit<ComponentPropsWithRef<"input">, "type"> & {
90
+ label: ReactNode;
91
+ }): import("react").JSX.Element;
92
+ export declare function Pagination({ page, pageCount, onPageChange, disabled, label, className, ...props }: Omit<ComponentPropsWithRef<"nav">, "children"> & {
93
+ page: number;
94
+ pageCount: number;
95
+ onPageChange: (page: number) => void;
96
+ disabled?: boolean;
97
+ label?: string;
98
+ }): import("react").JSX.Element;
99
+ export type DefinitionItem = {
100
+ id: string;
101
+ term: ReactNode;
102
+ description: ReactNode;
103
+ };
104
+ export declare function DefinitionList({ items, layout, className, ...props }: Omit<ComponentPropsWithRef<"dl">, "children"> & {
105
+ items: readonly DefinitionItem[];
106
+ layout?: "rows" | "stack" | "grid";
107
+ }): import("react").JSX.Element;
108
+ export declare function Switch({ label, id, className, ...props }: Omit<ComponentPropsWithRef<"input">, "type" | "role"> & {
109
+ label: ReactNode;
110
+ }): import("react").JSX.Element;
111
+ export type TabItem = {
112
+ id: string;
113
+ label: ReactNode;
114
+ content: ReactNode;
115
+ disabled?: boolean;
116
+ };
117
+ export declare function Tabs({ items, value, onValueChange, label, orientation, className, ...props }: Omit<ComponentPropsWithRef<"div">, "children" | "onChange"> & {
118
+ items: readonly TabItem[];
119
+ value: string;
120
+ onValueChange: (id: string) => void;
121
+ label: string;
122
+ orientation?: "horizontal" | "vertical";
123
+ }): import("react").JSX.Element;
package/dist/index.js ADDED
@@ -0,0 +1,208 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useId, useRef, useState, } from "react";
3
+ export function Button({ variant = "primary", className = "", type = "button", ...props }) {
4
+ return (_jsx("button", { type: type, className: `krigsvold-button krigsvold-button--${variant} ${className}`, ...props }));
5
+ }
6
+ export function TextInput({ className = "", ...props }) {
7
+ return _jsx("input", { className: `krigsvold-input ${className}`, ...props });
8
+ }
9
+ export function Select({ className = "", ...props }) {
10
+ return _jsx("select", { className: `krigsvold-input ${className}`, ...props });
11
+ }
12
+ export function Textarea({ className = "", ...props }) {
13
+ return _jsx("textarea", { className: `krigsvold-input ${className}`, ...props });
14
+ }
15
+ export function Field({ id: providedId, label, hint, error, hintId: providedHintId, errorId: providedErrorId, children, }) {
16
+ const generatedId = useId();
17
+ const id = providedId ?? generatedId;
18
+ // Description IDs belong to this Field, independent of a caller-supplied control ID.
19
+ const hintId = providedHintId ?? `${generatedId}-hint`;
20
+ const errorId = providedErrorId ?? `${generatedId}-error`;
21
+ const descriptions = [hint && hintId, error && errorId].filter(Boolean).join(" ") || undefined;
22
+ return (_jsxs("div", { className: "krigsvold-field", children: [_jsx("label", { htmlFor: id, children: label }), children({
23
+ id,
24
+ "aria-describedby": descriptions,
25
+ "aria-invalid": error ? true : undefined,
26
+ }), hint && (_jsx("p", { id: hintId, className: "krigsvold-hint", children: hint })), error && (_jsx("p", { id: errorId, className: "krigsvold-error", children: error }))] }));
27
+ }
28
+ export function Notice({ kind = "info", children, }) {
29
+ return (_jsx("div", { className: `krigsvold-notice krigsvold-notice--${kind}`, role: kind === "error" ? "alert" : "status", children: children }));
30
+ }
31
+ export function Badge({ children }) {
32
+ return _jsx("span", { className: "krigsvold-badge", children: children });
33
+ }
34
+ export function DataTable({ caption, children, className = "", ...props }) {
35
+ return (_jsx("div", { className: "krigsvold-table-scroll", role: "region", "aria-label": caption, tabIndex: 0, children: _jsxs("table", { className: `krigsvold-table ${className}`, ...props, children: [_jsx("caption", { children: caption }), children] }) }));
36
+ }
37
+ export function AppShell({ brand, navigationLabel = "Workbench", navigationPlacement = "default", headerMode, navigation, utilities, children, }) {
38
+ return (_jsxs("div", { className: "krigsvold-app", "data-navigation": navigationPlacement, children: [_jsx("a", { className: "krigsvold-skip", href: "#main", children: "Skip to content" }), _jsxs("header", { className: "krigsvold-app-header", "data-krigsvold": headerMode, children: [_jsx("div", { className: "krigsvold-app-brand", children: brand }), navigationPlacement !== "none" && (_jsx("nav", { "aria-label": navigationLabel, children: navigation })), _jsx("div", { className: "krigsvold-app-utilities", children: utilities })] }), _jsx("main", { id: "main", tabIndex: -1, children: children })] }));
39
+ }
40
+ export function Dialog({ open, title, onDismiss, children, }) {
41
+ const ref = useRef(null);
42
+ const suppressedCloseEvents = useRef(0);
43
+ const id = useId();
44
+ useLayoutEffect(() => {
45
+ const dialog = ref.current;
46
+ if (!dialog)
47
+ return;
48
+ if (open && !dialog.open)
49
+ dialog.showModal();
50
+ if (!open && dialog.open) {
51
+ // The close event is queued; distinguish prop-driven closes from native ones.
52
+ suppressedCloseEvents.current += 1;
53
+ dialog.close();
54
+ }
55
+ }, [open]);
56
+ return (_jsxs("dialog", { className: "krigsvold-dialog", ref: ref, "aria-labelledby": id, onClose: () => {
57
+ if (suppressedCloseEvents.current > 0) {
58
+ suppressedCloseEvents.current -= 1;
59
+ }
60
+ else if (open && !ref.current?.open) {
61
+ onDismiss();
62
+ }
63
+ }, onCancel: (event) => {
64
+ event.preventDefault();
65
+ onDismiss();
66
+ }, children: [_jsx("h2", { id: id, children: title }), children] }));
67
+ }
68
+ export function Checkbox({ label, id, className = "", ...props }) {
69
+ const generatedId = useId();
70
+ const controlId = id ?? generatedId;
71
+ return (_jsxs("label", { className: `krigsvold-checkbox ${className}`, htmlFor: controlId, children: [_jsx("input", { ...props, id: controlId, type: "checkbox" }), label] }));
72
+ }
73
+ export function Fieldset({ legend, hint, error, children, className = "", ...props }) {
74
+ const id = useId();
75
+ const describedBy = [props["aria-describedby"], hint && `${id}-hint`, error && `${id}-error`]
76
+ .filter(Boolean)
77
+ .join(" ") || undefined;
78
+ return (_jsxs("fieldset", { ...props, className: `krigsvold-fieldset ${className}`, "aria-describedby": describedBy, "aria-invalid": error ? true : props["aria-invalid"], children: [_jsx("legend", { children: legend }), hint && (_jsx("p", { id: `${id}-hint`, className: "krigsvold-hint", children: hint })), children, error && (_jsx("p", { id: `${id}-error`, className: "krigsvold-error", children: error }))] }));
79
+ }
80
+ export function Disclosure({ summary, children, className = "", ...props }) {
81
+ return (_jsxs("details", { ...props, className: `krigsvold-disclosure ${className}`, children: [_jsx("summary", { children: summary }), children] }));
82
+ }
83
+ export function ErrorSummary({ title, errors = [], children, className = "", ...props }) {
84
+ const titleId = useId();
85
+ return (_jsxs("div", { ...props, tabIndex: -1, role: "alert", "aria-labelledby": titleId, className: `krigsvold-error-summary ${className}`, children: [_jsx("strong", { id: titleId, children: title }), errors.length > 0 && (_jsx("ul", { children: errors.map((error, index) => (_jsx("li", { children: error.targetId ? (_jsx("a", { href: `#${error.targetId}`, onClick: (event) => {
86
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
87
+ return;
88
+ const owner = event.currentTarget.ownerDocument;
89
+ const target = owner.getElementById(error.targetId);
90
+ if (target) {
91
+ target.focus();
92
+ // If focus cannot move, retain the link's native fragment navigation.
93
+ if (owner.activeElement === target)
94
+ event.preventDefault();
95
+ }
96
+ }, children: error.message })) : (error.message) }, index))) })), children] }));
97
+ }
98
+ export function TableOfContents({ items, label = "On this page", variant = "index", className = "", ...props }) {
99
+ return (_jsxs("nav", { ...props, "aria-label": label, className: `krigsvold-contents ${className}`, "data-variant": variant, children: [_jsx("strong", { children: label }), _jsx("ul", { children: items.map((item) => (_jsx("li", { "data-depth": item.depth ?? 2, children: _jsx("a", { href: `#${encodeURIComponent(item.id)}`, children: item.label }) }, item.id))) })] }));
100
+ }
101
+ export function Article({ title, titleLevel = 1, eyebrow, summary, metadata, contents, aside, contextLabel = "Article context", footer, children, className = "", ...props }) {
102
+ const titleId = useId();
103
+ const Heading = titleLevel === 1 ? "h1" : "h2";
104
+ return (_jsxs("article", { ...props, "aria-labelledby": titleId, className: `krigsvold-article ${className}`, children: [_jsxs("header", { className: "krigsvold-article-header", children: [eyebrow && _jsx("p", { className: "krigsvold-article-eyebrow", children: eyebrow }), _jsx(Heading, { id: titleId, children: title }), summary && _jsx("p", { className: "krigsvold-article-summary", children: summary }), metadata && (_jsx("div", { className: "krigsvold-article-metadata", children: metadata }))] }), _jsxs("div", { className: "krigsvold-article-layout", "data-contents": !!contents, children: [contents && _jsx("div", { className: "krigsvold-article-context", children: contents }), _jsxs("div", { className: "krigsvold-prose", children: [children, aside && _jsx("aside", { className: "krigsvold-article-support", "aria-label": contextLabel, children: aside })] })] }), footer && _jsx("footer", { className: "krigsvold-article-footer", children: footer })] }));
105
+ }
106
+ export function FileInput({ className = "", ...props }) {
107
+ return (_jsx("input", { ...props, type: "file", className: `krigsvold-file-input ${className}` }));
108
+ }
109
+ export function Radio({ label, id, className = "", ...props }) {
110
+ const generatedId = useId();
111
+ const controlId = id ?? generatedId;
112
+ return (_jsxs("label", { className: `krigsvold-radio ${className}`, htmlFor: controlId, children: [_jsx("input", { ...props, id: controlId, type: "radio" }), label] }));
113
+ }
114
+ export function Pagination({ page, pageCount, onPageChange, disabled = false, label = "Pagination", className = "", ...props }) {
115
+ const count = Number.isFinite(pageCount)
116
+ ? Math.max(1, Math.trunc(pageCount))
117
+ : 1;
118
+ const current = Number.isFinite(page)
119
+ ? Math.min(count, Math.max(1, Math.trunc(page)))
120
+ : 1;
121
+ const previousRef = useRef(null);
122
+ const nextRef = useRef(null);
123
+ const focusedControl = useRef(null);
124
+ useLayoutEffect(() => {
125
+ if (disabled || count === 1) {
126
+ focusedControl.current = null;
127
+ return;
128
+ }
129
+ const owner = previousRef.current?.ownerDocument;
130
+ if (!owner)
131
+ return;
132
+ const active = owner.activeElement;
133
+ if (active !== owner.body && active !== previousRef.current && active !== nextRef.current) {
134
+ focusedControl.current = null;
135
+ return;
136
+ }
137
+ if (focusedControl.current === "next" && current === count && current > 1)
138
+ previousRef.current?.focus();
139
+ else if (focusedControl.current === "previous" && current === 1 && count > 1)
140
+ nextRef.current?.focus();
141
+ }, [current, count, disabled]);
142
+ return (_jsxs("nav", { ...props, "aria-label": label, className: `krigsvold-pagination ${className}`, children: [_jsx(Button, { ref: previousRef, onFocus: () => { focusedControl.current = "previous"; }, onBlur: (event) => { if (!event.currentTarget.disabled)
143
+ focusedControl.current = null; }, variant: "secondary", disabled: disabled || current === 1, onClick: () => onPageChange(current - 1), children: "Previous page" }), _jsxs("p", { "aria-live": "polite", "aria-atomic": "true", children: ["Page ", current, " of ", count] }), _jsx(Button, { ref: nextRef, onFocus: () => { focusedControl.current = "next"; }, onBlur: (event) => { if (!event.currentTarget.disabled)
144
+ focusedControl.current = null; }, variant: "secondary", disabled: disabled || current === count, onClick: () => onPageChange(current + 1), children: "Next page" })] }));
145
+ }
146
+ export function DefinitionList({ items, layout = "rows", className = "", ...props }) {
147
+ return (_jsx("dl", { ...props, className: `krigsvold-definitions ${className}`, "data-layout": layout, children: items.map((item) => (_jsxs("div", { children: [_jsx("dt", { children: item.term }), _jsx("dd", { children: item.description })] }, item.id))) }));
148
+ }
149
+ export function Switch({ label, id, className = "", ...props }) {
150
+ const generatedId = useId();
151
+ return (_jsxs("label", { htmlFor: id ?? generatedId, className: "krigsvold-switch", children: [_jsxs("span", { className: "krigsvold-switch-control", children: [_jsx("input", { ...props, id: id ?? generatedId, type: "checkbox", role: "switch", className: className }), _jsx("span", { className: "krigsvold-switch-track", "aria-hidden": "true", children: _jsx("span", {}) })] }), _jsx("span", { children: label })] }));
152
+ }
153
+ export function Tabs({ items, value, onValueChange, label, orientation = "horizontal", className = "", ...props }) {
154
+ const generatedId = useId();
155
+ const buttons = useRef([]);
156
+ const [focusedId, setFocusedId] = useState(null);
157
+ const enabled = items
158
+ .map((item, index) => (item.disabled ? -1 : index))
159
+ .filter((index) => index >= 0);
160
+ const selected = items.findIndex((item) => item.id === value && !item.disabled);
161
+ const active = selected >= 0 ? selected : (enabled[0] ?? -1);
162
+ const focused = items.findIndex((item) => item.id === focusedId && !item.disabled);
163
+ const tabStop = focused >= 0 ? focused : active;
164
+ const lastFocusedTab = useRef(null);
165
+ useLayoutEffect(() => {
166
+ const previous = lastFocusedTab.current;
167
+ if (previous && (!previous.isConnected || previous.disabled)) {
168
+ lastFocusedTab.current = null;
169
+ const document = previous.ownerDocument;
170
+ if (document.hasFocus() && (document.activeElement === document.body || document.activeElement === previous)) {
171
+ buttons.current[active]?.focus();
172
+ }
173
+ }
174
+ });
175
+ return (_jsxs("div", { ...props, className: `krigsvold-tabs ${className}`, children: [_jsx("div", { role: "tablist", "aria-label": label, "aria-orientation": orientation, onBlur: (event) => {
176
+ if (!(event.relatedTarget instanceof Node) || !event.currentTarget.contains(event.relatedTarget)) {
177
+ setFocusedId(null);
178
+ const prior = lastFocusedTab.current;
179
+ // Keep the last tab only when React has just disabled or removed it;
180
+ // an ordinary blur, including a move to outside content, clears it.
181
+ if (event.relatedTarget || !prior || (prior.isConnected && !prior.disabled))
182
+ lastFocusedTab.current = null;
183
+ }
184
+ }, onKeyDown: (event) => {
185
+ const target = event.target.closest('[role="tab"]');
186
+ const index = buttons.current.findIndex((button) => button === target);
187
+ if (index < 0)
188
+ return;
189
+ const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
190
+ const previousKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
191
+ let next;
192
+ if (event.key === "Home")
193
+ next = enabled[0];
194
+ else if (event.key === "End")
195
+ next = enabled.at(-1);
196
+ else if (event.key === nextKey)
197
+ next = enabled[(enabled.indexOf(index) + 1) % enabled.length];
198
+ else if (event.key === previousKey)
199
+ next =
200
+ enabled[(enabled.indexOf(index) - 1 + enabled.length) % enabled.length];
201
+ if (next !== undefined) {
202
+ event.preventDefault();
203
+ buttons.current[next]?.focus();
204
+ }
205
+ }, children: items.map((item, index) => (_jsx("button", { type: "button", role: "tab", ref: (element) => {
206
+ buttons.current[index] = element;
207
+ }, id: `${generatedId}-tab-${index}`, "aria-controls": `${generatedId}-panel-${index}`, "aria-selected": index === active, tabIndex: index === tabStop ? 0 : -1, disabled: item.disabled, onFocus: (event) => { lastFocusedTab.current = event.currentTarget; setFocusedId(item.id); }, onClick: () => onValueChange(item.id), children: item.label }, item.id))) }), items.map((item, index) => (_jsx("div", { role: "tabpanel", id: `${generatedId}-panel-${index}`, "aria-labelledby": `${generatedId}-tab-${index}`, hidden: index !== active, tabIndex: 0, children: item.content }, item.id)))] }));
208
+ }
@@ -0,0 +1,240 @@
1
+ @import "@krigsvold/core/css";
2
+ @import "@krigsvold/core/foundation.css";
3
+ /* Source roles: see docs/languages/krigsvold/decisions.md. Theme 2.1.0 is unchanged. */
4
+ .krigsvold {
5
+ --ui-bg: var(--krigsvold-surface-canvas);
6
+ --ui-surface: var(--krigsvold-surface-subtle);
7
+ --ui-text: var(--krigsvold-text);
8
+ --ui-heading: var(--krigsvold-text-strong);
9
+ --ui-muted: var(--krigsvold-text-subtle);
10
+ --ui-line: var(--krigsvold-border-subtle);
11
+ --ui-accent: var(--krigsvold-accent);
12
+ --ui-action: var(--krigsvold-accent);
13
+ --ui-action-hover: var(--krigsvold-accent-hover);
14
+ --ui-on-action: var(--krigsvold-on-accent);
15
+ --krigsvold-field-gap: .5rem;
16
+ --krigsvold-group-gap: 1.5rem;
17
+ --krigsvold-region-gap: 3rem;
18
+ --krigsvold-page-gutter: var(--krigsvold-gutter);
19
+ background: var(--ui-bg); color: var(--ui-text);
20
+ font: 1rem/1.65 var(--krigsvold-font-sans);
21
+ }
22
+ .krigsvold[data-krigsvold="light"] {
23
+ --ui-text: var(--krigsvold-color-ink);
24
+ --ui-heading: var(--krigsvold-color-navy-950);
25
+ --ui-surface: var(--krigsvold-color-ice-100);
26
+ --ui-muted: var(--krigsvold-color-muted);
27
+ --ui-line: var(--krigsvold-color-rule);
28
+ --ui-action: var(--krigsvold-color-navy-900);
29
+ --ui-action-hover: var(--krigsvold-color-blue-600);
30
+ --ui-on-action: white;
31
+ }
32
+ .krigsvold *, .krigsvold *::before, .krigsvold *::after { box-sizing: border-box; }
33
+ .krigsvold :where(img,svg,video) { max-inline-size:100%; block-size:auto; }
34
+ .krigsvold :where(a) { color:var(--krigsvold-link); text-underline-offset:var(--krigsvold-link-underline-offset); text-decoration-thickness:.08em; }
35
+ .krigsvold :where(a:hover) { color:var(--krigsvold-link-hover); }
36
+ .krigsvold :where(a,button,input,select,textarea,[tabindex]):focus-visible { outline:var(--krigsvold-focus-width) solid var(--krigsvold-focus-ring); outline-offset:var(--krigsvold-focus-gap); }
37
+ .krigsvold :where(h1,h2,h3,h4,h5,h6,p) { margin-block:0; }
38
+ .krigsvold :where(h1,h2,h3) { color:var(--ui-heading); font-family:var(--krigsvold-font-serif); font-weight:700; line-height:var(--krigsvold-leading-heading); text-wrap:balance; letter-spacing:var(--krigsvold-tracking-heading); }
39
+ .krigsvold :where(h1) { font-size:var(--krigsvold-text-h1); }
40
+ .krigsvold :where(h2) { font-size:var(--krigsvold-text-h2); }
41
+ .krigsvold :where(h3) { font-size:var(--krigsvold-text-h3); }
42
+ .krigsvold :where(button,input,select,textarea,table,nav) { font-family:var(--krigsvold-font-sans); }
43
+ .krigsvold :where(code,pre,kbd,samp) { font-family:var(--krigsvold-font-mono); }
44
+ .krigsvold-button { display:inline-flex; align-items:center; justify-content:center; gap:.5rem; min-block-size:var(--krigsvold-control-height); min-inline-size:var(--krigsvold-control-height); padding:var(--krigsvold-button-padding); border:1px solid var(--ui-action); border-radius:var(--krigsvold-radius-control); color:var(--ui-on-action); background:var(--ui-action); font:700 var(--krigsvold-text-button)/var(--krigsvold-leading-button) var(--krigsvold-font-sans); text-transform:uppercase; letter-spacing:var(--krigsvold-tracking-button); cursor:pointer; }
45
+ .krigsvold-button:hover:not(:disabled) { background:var(--ui-action-hover); border-color:var(--ui-action-hover); color:var(--ui-on-action); }
46
+ .krigsvold-button--secondary { background:transparent; border-color:var(--ui-action); color:var(--ui-action); }
47
+ .krigsvold-button--secondary:hover:not(:disabled) { background:var(--krigsvold-button-secondary-hover); color:var(--ui-text); }
48
+ .krigsvold-button--quiet { background:transparent; border-color:transparent; color:var(--krigsvold-link); }
49
+ .krigsvold-button--quiet:hover:not(:disabled) { background:var(--krigsvold-hover-bg); border-color:transparent; color:var(--krigsvold-link-hover); }
50
+ .krigsvold-button:disabled { background:var(--krigsvold-disabled-bg); color:var(--krigsvold-disabled-text); border-color:var(--krigsvold-disabled-bg); cursor:default; }
51
+ .krigsvold-input, .krigsvold-file-input { display:block; inline-size:100%; min-inline-size:0; min-block-size:var(--krigsvold-control-height); padding:var(--krigsvold-control-padding); border:1px solid var(--krigsvold-input-border); border-radius:var(--krigsvold-radius-sm); background:var(--krigsvold-input-bg); color:var(--krigsvold-input-text); font:1rem/1.5 var(--krigsvold-font-sans); }
52
+ .krigsvold textarea { resize:vertical; min-block-size:7rem; }
53
+ .krigsvold-input[aria-invalid="true"], .krigsvold-file-input[aria-invalid="true"] { border-color:var(--krigsvold-danger); }
54
+ .krigsvold-input:disabled, .krigsvold-file-input:disabled { background:var(--krigsvold-disabled-bg); color:var(--krigsvold-disabled-text); cursor:default; }
55
+ .krigsvold-field { display:grid; gap:var(--krigsvold-field-gap); min-inline-size:0; align-content:start; font-family:var(--krigsvold-font-sans); }
56
+ .krigsvold-field label, .krigsvold-fieldset legend { color:var(--ui-heading); font:700 .9rem/1.5 var(--krigsvold-font-sans); }
57
+ .krigsvold-hint { color:var(--ui-muted); font-size:.875rem; }
58
+ .krigsvold-error { color:var(--krigsvold-danger); font-size:.875rem; }
59
+ .krigsvold-checkbox, .krigsvold-radio { color:var(--ui-text); display:inline-flex; gap:.5rem; align-items:center; min-block-size:2.8rem; cursor:pointer; font:1rem/1.5 var(--krigsvold-font-sans); }
60
+ .krigsvold-checkbox input, .krigsvold-radio input { inline-size:1.2rem; block-size:1.2rem; accent-color:var(--ui-action); flex:none; }
61
+ .krigsvold-checkbox:has(input:disabled), .krigsvold-radio:has(input:disabled) { color:var(--krigsvold-disabled-text); cursor:default; }
62
+ .krigsvold-fieldset { border:1px solid var(--ui-line); padding:var(--krigsvold-group-gap); margin:0; min-inline-size:0; }
63
+ .krigsvold-fieldset legend { padding-inline:.3rem; }
64
+ .krigsvold-notice { padding:1rem 1.5rem; border-inline-start:4px solid var(--krigsvold-info); background:var(--krigsvold-info-bg); color:var(--krigsvold-info-text); font-family:var(--krigsvold-font-sans); }
65
+ .krigsvold-notice--success { border-color:var(--krigsvold-success); background:var(--krigsvold-success-bg); color:var(--krigsvold-success-text); }
66
+ .krigsvold-notice--error { border-color:var(--krigsvold-danger); background:var(--krigsvold-danger-bg); color:var(--krigsvold-danger-text); }
67
+ .krigsvold-badge { display:inline-block; font:.8rem/1.5 var(--krigsvold-font-sans); padding:.2rem .5rem; color:var(--ui-muted); }
68
+ .krigsvold-error-summary { padding:1rem 1.5rem; border-inline-start:4px solid var(--krigsvold-danger); background:var(--krigsvold-danger-bg); color:var(--krigsvold-danger-text); display:grid; gap:1rem; min-inline-size:0; font-family:var(--krigsvold-font-sans); }
69
+ .krigsvold-error-summary ul { margin:0; padding-inline-start:1.25rem; }
70
+ .krigsvold-error-summary a { color:inherit; text-decoration:underline; }
71
+ .krigsvold-table-scroll { overflow:auto; max-inline-size:100%; }
72
+ .krigsvold-table { border-collapse:collapse; inline-size:100%; text-align:start; font:.95rem/1.65 var(--krigsvold-font-sans); }
73
+ .krigsvold-table caption { text-align:start; padding-block:0 1rem; color:var(--ui-muted); font-weight:700; }
74
+ .krigsvold-table :where(th,td) { min-inline-size:8rem; padding:.85rem .7rem; border-block-end:1px solid var(--ui-line); vertical-align:top; text-align:start; }
75
+ .krigsvold-table thead th { color:var(--ui-heading); }
76
+ .krigsvold-table tr[data-selected="true"] { background:var(--krigsvold-active-bg); }
77
+ .krigsvold-skip { position:fixed; inset-block-start:-5rem; inset-inline-start:1rem; z-index:2000; background:var(--ui-bg); color:var(--ui-text); padding:.75rem 1rem; font:700 1rem/1.5 var(--krigsvold-font-sans); }
78
+ .krigsvold-skip:focus { inset-block-start:1rem; }
79
+ /* Application frame extension; website navigation belongs to the host. */
80
+ .krigsvold-app { min-block-size:100vh; font:1rem/1.65 var(--krigsvold-font-sans); }
81
+ .krigsvold-app main { min-inline-size:0; padding:var(--krigsvold-page-gutter); }
82
+ .krigsvold-app-header { display:flex; flex-wrap:wrap; gap:1.5rem; padding:1rem var(--krigsvold-page-gutter); border-block-end:1px solid var(--ui-line); align-items:center; }
83
+ .krigsvold-app-brand { font:700 1.25rem/1.4 var(--krigsvold-font-serif); }
84
+ .krigsvold-app-header nav { display:flex; gap:.5rem; flex-wrap:wrap; }
85
+ .krigsvold-app-header nav a { display:inline-flex; align-items:center; min-block-size:2.8rem; padding:.65rem .75rem; text-decoration:none; }
86
+ .krigsvold-app-header nav a[aria-current] { border-block-end:2px solid var(--ui-accent); font-weight:700; }
87
+ .krigsvold-app-utilities { margin-inline-start:auto; display:flex; flex-wrap:wrap; align-items:center; gap:1rem; }
88
+ .krigsvold-app-header[data-krigsvold] { --ui-bg:var(--krigsvold-bg); --ui-text:var(--krigsvold-text); --ui-heading:var(--krigsvold-text-strong); --ui-muted:var(--krigsvold-text-subtle); --ui-line:var(--krigsvold-border-subtle); --ui-accent:var(--krigsvold-accent); --ui-action:var(--krigsvold-accent); --ui-action-hover:var(--krigsvold-accent-hover); --ui-on-action:var(--krigsvold-on-accent); color:var(--ui-text); }
89
+ .krigsvold-app[data-navigation="side"] { display:grid; grid-template-columns:16rem minmax(0,1fr); }
90
+ .krigsvold-app[data-navigation="side"] > .krigsvold-app-header { flex-direction:column; align-items:stretch; border-block-end:0; border-inline-end:1px solid var(--ui-line); padding:2rem 1.5rem; }
91
+ .krigsvold-app[data-navigation="side"] > .krigsvold-app-header > nav { flex-direction:column; }
92
+ .krigsvold-app[data-navigation="side"] > .krigsvold-app-header > .krigsvold-app-utilities { margin-inline-start:0; }
93
+ .krigsvold-disclosure > summary { cursor:pointer; min-block-size:2.8rem; padding:.6rem .25rem; font-family:var(--krigsvold-font-sans); }
94
+ .krigsvold-dialog { border:1px solid var(--krigsvold-border-strong); padding:1.5rem; background:var(--krigsvold-surface-overlay); color:var(--krigsvold-text); inline-size:min(32rem,calc(100vw - 2rem)); max-block-size:calc(100dvh - 2rem); font:1rem/1.65 var(--krigsvold-font-sans); }
95
+ .krigsvold-dialog::backdrop { background:var(--krigsvold-overlay-scrim); }
96
+ .krigsvold-dialog h2 { margin-block-end:1rem; color:inherit; }
97
+ .krigsvold-dialog .actions { margin-block-start:1.5rem; }
98
+ /* TOC anatomy follows the document index and inline article source objects. */
99
+ .krigsvold-contents { min-inline-size:0; font:.88rem/1.5 var(--krigsvold-font-sans); }
100
+ .krigsvold-contents > strong { display:block; margin-block-end:.75rem; color:var(--krigsvold-color-blue-600); font:700 .78rem/1.5 var(--krigsvold-font-sans); letter-spacing:.16em; text-transform:uppercase; }
101
+ .krigsvold-contents ul { margin:0; padding:0; list-style:none; }
102
+ .krigsvold-contents li { margin:.25rem 0; padding-inline-start:.85rem; }
103
+ .krigsvold-contents li[data-depth="2"] { padding-inline-start:0; font-weight:700; margin-block-start:1.25rem; }
104
+ .krigsvold-contents li[data-depth="2"]:first-child { margin-block-start:.5rem; }
105
+ .krigsvold-contents a { display:block; padding-block:.35rem; text-decoration:none; }
106
+ .krigsvold-contents a:hover { text-decoration:underline; }
107
+ .krigsvold-contents[data-variant="index"] { position:sticky; inset-block-start:6rem; max-block-size:calc(100dvh - 8rem); overflow-y:auto; padding:1.2rem .5rem .5rem 0; scrollbar-width:thin; }
108
+ .krigsvold-contents[data-variant="inline"] { margin-block-end:2.5rem; padding:1.2rem 1.4rem; background:var(--ui-surface); }
109
+ .krigsvold-contents[data-variant="inline"] ul { columns:2 16rem; column-gap:2rem; }
110
+ .krigsvold-contents[data-variant="inline"] li { break-inside:avoid; }
111
+ .krigsvold-article { min-inline-size:0; container-type:inline-size; container-name:krigsvold-article; }
112
+ .krigsvold-article-header { display:grid; gap:1rem; padding-block:1.5rem 2rem; }
113
+ .krigsvold-article-eyebrow { color:var(--krigsvold-color-blue-600); font:700 .78rem/1.5 var(--krigsvold-font-sans); text-transform:uppercase; letter-spacing:.16em; }
114
+ .krigsvold-article-summary { max-inline-size:var(--krigsvold-reading-width); font:1.28rem/1.65 var(--krigsvold-font-serif); }
115
+ .krigsvold-article-metadata, .krigsvold-article-footer { font:.85rem/1.55 var(--krigsvold-font-sans); color:var(--ui-muted); }
116
+ .krigsvold-article-metadata dl { display:flex; flex-wrap:wrap; gap:1rem 2rem; margin:0; }
117
+ .krigsvold-article-metadata dt { font-weight:700; }
118
+ .krigsvold-article-metadata dd { margin:0; }
119
+ .krigsvold-article-layout { display:grid; grid-template-columns:minmax(0,var(--krigsvold-reading-width)); justify-content:center; gap:clamp(2rem,4vw,4rem); align-items:start; padding-block:2rem; }
120
+ .krigsvold-article-layout[data-contents="true"] { grid-template-columns:16rem minmax(0,var(--krigsvold-reading-width)); }
121
+ .krigsvold-article-context { min-inline-size:0; font:.94rem/1.65 var(--krigsvold-font-sans); }
122
+ .krigsvold-article-support { margin-block-start:2.5rem; display:grid; gap:1.5rem; font:.94rem/1.65 var(--krigsvold-font-sans); }
123
+ .krigsvold-prose { min-inline-size:0; max-inline-size:var(--krigsvold-reading-width); font:1.06rem/1.65 var(--krigsvold-font-sans); }
124
+ .krigsvold-prose > * + * { margin-block-start:1.25rem; }
125
+ .krigsvold-prose :where(h2,h3,h4) { scroll-margin-block-start:6rem; }
126
+ .krigsvold-prose h2 { margin-block:3.4rem 1rem; }
127
+ .krigsvold-prose h3 { margin-block:2rem .75rem; }
128
+ .krigsvold-prose :where(ul,ol) { padding-inline-start:1.5rem; }
129
+ .krigsvold-prose li + li { margin-block-start:.5rem; }
130
+ .krigsvold-prose :where(code,pre) { font-family:var(--krigsvold-font-mono); font-size:.92em; }
131
+ .krigsvold-prose pre { padding:1.5rem; border-radius:var(--krigsvold-radius-md); max-inline-size:100%; overflow:auto; background:var(--krigsvold-code-bg); color:var(--krigsvold-code-text); }
132
+ .krigsvold-prose :not(pre) > code { padding:.08em .3em; border:1px solid var(--ui-line); border-radius:.2rem; background:var(--ui-surface); font-size:.88em; overflow-wrap:anywhere; white-space:break-spaces; }
133
+ .krigsvold-prose blockquote { margin:2.5rem 0; border-inline-start:4px solid var(--krigsvold-color-blue-600); padding:.25rem 0 .25rem 1.5rem; color:var(--ui-muted); font:1.15em/1.65 var(--krigsvold-font-serif); }
134
+ .krigsvold-prose img { max-inline-size:100%; block-size:auto; }
135
+ .krigsvold-prose figcaption { color:var(--ui-muted); font:.82rem/1.45 var(--krigsvold-font-sans); padding:.7rem .25rem; }
136
+ .krigsvold-article-footer { border-block-start:1px solid var(--ui-line); padding-block:1.5rem; }
137
+ .krigsvold-file-input::file-selector-button { min-block-size:2.8rem; padding:.5rem .8rem; margin-inline-end:.75rem; font:inherit; border:1px solid var(--krigsvold-border-strong); background:var(--krigsvold-button-secondary-bg); color:var(--krigsvold-button-secondary-text); cursor:pointer; }
138
+ .krigsvold-file-input:disabled::file-selector-button { background:var(--krigsvold-disabled-bg); color:var(--krigsvold-disabled-text); cursor:default; }
139
+ .krigsvold-pagination { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:1rem; margin-block-start:2.5rem; font:1rem/1.5 var(--krigsvold-font-sans); }
140
+ .krigsvold-pagination p { margin:0; font-variant-numeric:tabular-nums; }
141
+ .krigsvold-definitions { margin:0; font-family:var(--krigsvold-font-sans); }
142
+ .krigsvold-definitions > div { display:grid; grid-template-columns:minmax(8rem,.35fr) minmax(0,1fr); gap:1rem; padding:1rem 0; border-block-end:1px solid var(--ui-line); min-inline-size:0; }
143
+ .krigsvold-definitions dt { font-weight:700; }
144
+ .krigsvold-definitions dd { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; }
145
+ /* Shared application extensions: no invented family-opposite ornament. */
146
+ .krigsvold-switch { display:inline-flex; gap:.75rem; align-items:center; min-block-size:2.8rem; cursor:pointer; font:1rem/1.5 var(--krigsvold-font-sans); }
147
+ .krigsvold-switch-control { position:relative; flex:none; display:inline-flex; }
148
+ .krigsvold-switch input { position:absolute; inset:0; inline-size:100%; block-size:100%; opacity:0; margin:0; cursor:inherit; }
149
+ .krigsvold-switch-track { inline-size:2.6rem; block-size:1.5rem; border:2px solid var(--krigsvold-border-strong); background:var(--krigsvold-button-secondary-bg); display:flex; align-items:center; padding:.15rem; border-radius:1rem; }
150
+ .krigsvold-switch-track > span { inline-size:.95rem; block-size:.95rem; border-radius:50%; background:var(--krigsvold-button-secondary-text); }
151
+ .krigsvold-switch input:checked + .krigsvold-switch-track { background:var(--ui-action); border-color:var(--ui-action); justify-content:flex-end; }
152
+ .krigsvold-switch input:checked + .krigsvold-switch-track > span { background:var(--ui-on-action); }
153
+ .krigsvold-switch input:focus-visible + .krigsvold-switch-track { outline:3px solid var(--krigsvold-focus-ring); outline-offset:3px; }
154
+ .krigsvold-switch:has(input:disabled) { cursor:default; color:var(--krigsvold-disabled-text); }
155
+ .krigsvold-switch input:disabled + .krigsvold-switch-track { background:var(--krigsvold-disabled-bg); border-color:var(--krigsvold-disabled-text); }
156
+ .krigsvold-switch input:disabled + .krigsvold-switch-track > span { background:var(--krigsvold-disabled-text); }
157
+ .krigsvold-tabs { min-inline-size:0; font:1rem/1.5 var(--krigsvold-font-sans); }
158
+ .krigsvold-tabs [role="tablist"] { display:flex; flex-wrap:wrap; gap:.5rem; border-block-end:1px solid var(--ui-line); }
159
+ .krigsvold-tabs [role="tablist"][aria-orientation="vertical"] { flex-direction:column; }
160
+ .krigsvold-tabs [role="tab"] { font:inherit; color:var(--ui-text); background:transparent; border:0; border-block-end:3px solid transparent; padding:.75rem 1rem; min-block-size:2.8rem; cursor:pointer; overflow-wrap:anywhere; }
161
+ .krigsvold-tabs [role="tab"][aria-selected="true"] { border-block-end-color:var(--ui-action); font-weight:700; }
162
+ .krigsvold-tabs [role="tab"]:disabled { color:var(--krigsvold-disabled-text); cursor:default; }
163
+ .krigsvold-tabs [role="tabpanel"] { padding-block:1.5rem; outline-offset:3px; }
164
+ .krigsvold-tabs [role="tabpanel"][hidden] { display:none; }
165
+ @container krigsvold-article (max-width:74rem) {
166
+ .krigsvold-article-layout[data-contents="true"] { grid-template-columns:minmax(0,1fr); }
167
+ .krigsvold-contents[data-variant="index"] { position:static; max-block-size:none; overflow:visible; padding:1.2rem 1.4rem; background:var(--ui-surface); }
168
+ .krigsvold-contents[data-variant="index"] ul { columns:2 16rem; column-gap:2rem; }
169
+ .krigsvold-contents[data-variant="index"] li { break-inside:avoid; }
170
+ }
171
+ @media(max-width:55rem) {
172
+ .krigsvold-app[data-navigation="side"] { display:block; }
173
+ .krigsvold-app[data-navigation="side"] > .krigsvold-app-header { flex-direction:row; border-inline-end:0; border-block-end:1px solid var(--ui-line); padding:1rem; }
174
+ .krigsvold-app[data-navigation="side"] > .krigsvold-app-header > nav { flex-direction:row; }
175
+ .krigsvold-app-utilities { margin-inline-start:0; }
176
+ .krigsvold-definitions > div { grid-template-columns:minmax(0,1fr); gap:.5rem; }
177
+ }
178
+ @media(max-width:40rem) { .krigsvold-contents ul { columns:1 !important; } }
179
+ @media(max-aspect-ratio:4/5) {
180
+ .krigsvold-article-layout[data-contents="true"] { grid-template-columns:minmax(0,1fr); }
181
+ .krigsvold-contents[data-variant="index"] { position:static; max-block-size:none; padding:1.2rem 1.4rem; background:var(--ui-surface); }
182
+ }
183
+ @media(prefers-reduced-motion:reduce) { .krigsvold *, .krigsvold *::before, .krigsvold *::after { transition:none !important; animation:none !important; scroll-behavior:auto !important; } }
184
+ @media(forced-colors:active) {
185
+ .krigsvold-switch-track { forced-color-adjust:none; background:Canvas; border-color:CanvasText; }
186
+ .krigsvold-switch-track > span { background:CanvasText; }
187
+ .krigsvold-switch input:checked + .krigsvold-switch-track { background:Highlight; border-color:Highlight; }
188
+ .krigsvold-switch input:checked + .krigsvold-switch-track > span { background:HighlightText; }
189
+ .krigsvold-switch input:disabled + .krigsvold-switch-track { background:Canvas; border-color:GrayText; }
190
+ .krigsvold-switch input:disabled + .krigsvold-switch-track > span { background:GrayText; }
191
+ .krigsvold-tabs [role="tab"][aria-selected="true"] { border-block-end-color:Highlight; }
192
+ }
193
+ @media print {
194
+ .krigsvold-article { background:white; color:black; }
195
+ .krigsvold-article-layout { display:block; }
196
+ .krigsvold-contents { display:none; }
197
+ .krigsvold-prose { max-inline-size:none; }
198
+ .krigsvold-prose :where(h2,h3,h4) { break-after:avoid; }
199
+ .krigsvold-prose :where(pre,blockquote,figure) { break-inside:avoid; }
200
+ .krigsvold-article :is(.krigsvold-article-context,pre) { background:white; color:black; }
201
+ .krigsvold-article :is(.krigsvold-article-metadata,.krigsvold-article-footer,a) { color:black; }
202
+ }
203
+
204
+ /* Existing workbench default retained; explicit placement remains available to both. */
205
+ .krigsvold-app[data-navigation="default"] { display:grid; grid-template-columns:16rem minmax(0,1fr); }
206
+ .krigsvold-app[data-navigation="default"] > .krigsvold-app-header { flex-direction:column; align-items:stretch; border-block-end:0; border-inline-end:1px solid var(--ui-line); padding:2rem 1.5rem; }
207
+ .krigsvold-app[data-navigation="default"] > .krigsvold-app-header > nav { flex-direction:column; }
208
+ .krigsvold-app[data-navigation="default"] > .krigsvold-app-header > .krigsvold-app-utilities { margin-inline-start:0; }
209
+ .krigsvold[data-krigsvold="dark"] .krigsvold-contents > strong, .krigsvold[data-krigsvold="dark"] .krigsvold-article-eyebrow { color:var(--krigsvold-color-ice-400); }
210
+ @media(max-width:55rem) { .krigsvold-app[data-navigation="default"] { display:block; } .krigsvold-app[data-navigation="default"] > .krigsvold-app-header { flex-direction:row; border-inline-end:0; border-block-end:1px solid var(--ui-line); padding:1rem; } .krigsvold-app[data-navigation="default"] > .krigsvold-app-header > nav { flex-direction:row; } }
211
+
212
+ .krigsvold-article-context { position:sticky; inset-block-start:6rem; }
213
+ .krigsvold-definitions[data-layout="stack"] > div { display:block; }
214
+ .krigsvold-definitions[data-layout="stack"] dd { margin-block-start:.5rem; }
215
+ .krigsvold-definitions[data-layout="grid"] { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:1.5rem; }
216
+ .krigsvold-definitions[data-layout="grid"] > div { display:block; }
217
+ @container krigsvold-article (max-width:74rem) { .krigsvold-article-context { position:static; } }
218
+ @media(max-aspect-ratio:4/5) { .krigsvold-article-context { position:static; } }
219
+ @media(max-width:48rem) { .krigsvold-definitions[data-layout="grid"] { grid-template-columns:minmax(0,1fr); } }
220
+
221
+ /* Technical identifiers may wrap locally; ordinary prose keeps natural word boundaries. */
222
+ .krigsvold :is(h1,h2,h3,h4,h5,h6,a,dd) { overflow-wrap:break-word; }
223
+
224
+ /* Bounded application labels must shrink at enlarged text sizes. Prose is separate. */
225
+ .krigsvold-app-header > *, .krigsvold-app-utilities > * { min-inline-size:0; max-inline-size:100%; }
226
+ .krigsvold-app-brand, .krigsvold-app-header nav a { overflow-wrap:anywhere; }
227
+ .krigsvold-button { max-inline-size:100%; overflow-wrap:anywhere; white-space:normal; }
228
+ .krigsvold-checkbox, .krigsvold-radio, .krigsvold-switch { max-inline-size:100%; overflow-wrap:anywhere; }
229
+ .krigsvold-checkbox > span, .krigsvold-radio > span, .krigsvold-switch > span:not(.krigsvold-switch-control) { min-inline-size:0; }
230
+ .krigsvold-field, .krigsvold-fieldset, .krigsvold-notice, .krigsvold-error-summary, .krigsvold-badge { overflow-wrap:anywhere; }
231
+ .krigsvold-tabs [role="tab"] { max-inline-size:100%; min-inline-size:0; }
232
+
233
+ .krigsvold-field, .krigsvold-error-summary { grid-template-columns:minmax(0,1fr); }
234
+ .krigsvold-field > *, .krigsvold-error-summary > * { min-inline-size:0; }
235
+
236
+ .krigsvold-article-header { grid-template-columns:minmax(0,1fr); }
237
+ .krigsvold-article-header > *, .krigsvold-article-metadata dl > * { min-inline-size:0; max-inline-size:100%; }
238
+ .krigsvold-article-metadata { overflow-wrap:anywhere; }
239
+
240
+ .krigsvold-disclosure > summary { overflow-wrap:anywhere; }
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@krigsvold/react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Experimental Krigsvold React components",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "private": false,
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "LICENSE",
12
+ "NOTICE.md",
13
+ "CHANGELOG.md"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./styles.css": "./dist/styles.css"
21
+ },
22
+ "sideEffects": [
23
+ "**/*.css"
24
+ ],
25
+ "peerDependencies": {
26
+ "react": "^19.0.0"
27
+ },
28
+ "dependencies": {
29
+ "@krigsvold/core": "2.2.4"
30
+ },
31
+ "scripts": {
32
+ "build": "node build.mjs",
33
+ "test": "npm run build && node --test tests/*.test.mjs",
34
+ "test:browser": "DESIGN_FAMILY=krigsvold node tests/browser-check.mjs",
35
+ "build:gallery": "NIGHTJAR_FAMILY=krigsvold node examples/react-catalog/build.mjs",
36
+ "build:reference": "node scripts/build-reference-deployment.mjs"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/krigsvolddesign/react.git"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public",
44
+ "tag": "next",
45
+ "provenance": true
46
+ },
47
+ "devDependencies": {
48
+ "@types/react": "19.3.0",
49
+ "react": "19.3.1",
50
+ "react-dom": "19.3.1",
51
+ "typescript": "7.0.2",
52
+ "@axe-core/playwright": "4.11.1",
53
+ "playwright": "1.64.1",
54
+ "esbuild": "0.27.4",
55
+ "vite": "8.3.5",
56
+ "markdown-it": "14.3.3",
57
+ "markdown-it-footnote": "4.0.0",
58
+ "sanitize-html": "2.18.1"
59
+ }
60
+ }