@martlet/react 0.0.0-stage → 0.1.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 +14 -0
- package/LICENSE +21 -0
- package/NOTICE.md +7 -0
- package/README.md +57 -2
- package/dist/index.d.ts +121 -0
- package/dist/index.js +144 -0
- package/dist/styles.css +243 -0
- package/package.json +42 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @martlet/react changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — experimental candidate, unpublished
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
- Add explicit AppShell navigation placement, navigation label, and header color-mode composition; preserve existing defaults.
|
|
10
|
+
- Add native family reference, application grammar, collection/editor consumer, and independently usable MCP/skill tooling in the workbench.
|
|
11
|
+
|
|
12
|
+
- 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.
|
|
13
|
+
- Add TableOfContents depth and index/inline variants; separate article contents from supporting context; add DefinitionList rows/stack/grid layout choices in both families.
|
|
14
|
+
- 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,58 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @martlet/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Experimental 0.1.0. MIT licensed. Source package in the [design workbench](https://github.com/howardjp/design-workbench). Intended for experimental publication under npm’s `next` tag; it is not a stable public API. Check registry availability before installing.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Button, Field, TextInput } from '@martlet/react';
|
|
7
|
+
import '@martlet/react/styles.css';
|
|
8
|
+
import '@martlet/theme/fonts.css'; // optional local fonts
|
|
9
|
+
|
|
10
|
+
<div className="martlet" data-martlet="light">
|
|
11
|
+
<Field label="Component name" hint="Use the exported API name.">
|
|
12
|
+
{props => <TextInput {...props} name="name" required />}
|
|
13
|
+
</Field>
|
|
14
|
+
<Button type="submit">Save component</Button>
|
|
15
|
+
</div>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install @martlet/react@0.1.0 react@19 react-dom@19
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Exports: AppShell, Button, TextInput, Select, Textarea, Field, Notice, Badge, DataTable, Dialog, Checkbox, Fieldset, Disclosure, ErrorSummary, Article, TableOfContents, FileInput, Radio, Pagination, DefinitionList, Switch, Tabs, and FieldControl/SummaryError/ContentsItem/DefinitionItem (types). Native attributes and refs on native controls follow React 19 conventions. Button defaults to `type="button"`. Field supplies a stable id and associated hint/error descriptions through a render function. Dialog uses the native modal top layer; keep it mounted and control `open`. Escape calls `onDismiss`; provide visible dismissal buttons. DataTable wraps a semantic table in a named, focusable overflow region. Notice announces success/info politely and errors assertively.
|
|
23
|
+
|
|
24
|
+
Use one AppShell per document; its skip link targets main. Styles are scoped to `.martlet` and require the matching data attribute for light/dark colors. Both families can coexist in a document. Application spacing roles are experimental. No app persistence or registry state is embedded in the components.
|
|
25
|
+
|
|
26
|
+
Build with `npm run build` at the workspace root. `npm run pack:react` produces installable tarballs for testing without publishing. React 19 and the theme package are required. Build artifacts include JavaScript, declarations, and CSS. The workbench exercises editing, validation, saving, server failures, conflicts, empty results, modal dismissal, light/dark rendering, responsive layout, and automated accessibility checks. Manual assistive-technology review and sustained use remain required before stability.
|
|
27
|
+
|
|
28
|
+
See the [component contracts](../../docs/components.md) for properties, states, keyboard behavior, and acceptance criteria for every export. Checkbox is binary; mixed state is not yet a public contract. Fieldset keeps the native legend and disabled-group behavior. Disclosure uses native details/summary toggling.
|
|
29
|
+
|
|
30
|
+
ErrorSummary accepts a title, message/targetId error entries, native div ref, and optional recovery children. Focus it after a failed operation; its links focus explicit Field IDs or focusable fieldsets. Field now accepts an optional explicit id, retaining generated IDs by default. ErrorSummary does not manage application drafts or trigger focus itself.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Article composes an accessible title, optional inline summary/metadata/contents/aside, body, and footer. It accepts native article props/ref and titleLevel 1 (default) or 2; use coherent headings inside. TableOfContents accepts readonly id/label items, native nav props/ref, and a label defaulting to On this page. Its native fragment anchors target caller-supplied unique IDs; use tabIndex={-1} on target headings for keyboard focus. The components also render as static HTML. An application loading content asynchronously owns initial fragment restoration. Neither component parses Markdown or raw HTML, fetches records, or manages routing.
|
|
34
|
+
|
|
35
|
+
Both support light/dark modes, long text, a bounded reading measure, narrow single-column reflow, and print styles. Article keeps supporting material while TableOfContents is omitted from print. The workbench reading room is the current consumer. Print-media checks do not replace manual printer/PDF pagination, screen-reader, forced-colors, RTL, and non-Chromium review. All twenty-two components remain experimental.
|
|
36
|
+
|
|
37
|
+
FileInput forwards native uncontrolled input props and ref, fixes type=file, and excludes value/defaultValue from its TypeScript surface. Use it inside Field, with accept/multiple/name/disabled as needed. The consumer reads the native FileList, validates content/size, manages upload or import state, and may clear via ref.current.value="". The workbench consumes it for reviewed local JSON specification import; no transport, drag/drop, or file-security claim is embedded. Native chooser wording varies by operating system. Keyboard chooser activation, disabled/invalid/description semantics, long-content reflow, light/dark modes, and isolated-package rendering are checked; manual assistive technology and non-Chromium/mobile chooser review remain open.
|
|
38
|
+
|
|
39
|
+
Radio uses a native radio input with a required visible label, optional id, and forwarded native attributes/ref. className applies to its label wrapper. Give exclusive options the same name and distinct values, with a separate name per group; compose them inside Fieldset/legend. Native Tab, arrow keys, and Space retain browser behavior. The caller manages checked state, observations, validation, and disabled groups. The readiness-review consumer checks native navigation, group/error links, disabled controls, persistence, failures/conflicts, and responsive light/dark rendering. No custom ARIA radio model or promotion/release logic is embedded.
|
|
40
|
+
|
|
41
|
+
Pagination accepts controlled one-based page, pageCount, onPageChange, disabled, label (default Pagination), and native nav attributes/ref. Previous/next are native secondary buttons; first/last boundaries and disabled lock movement. Position is announced politely. Finite numbers are truncated and clamped; invalid counts/pages become one. The caller owns fetching, page-size, result count, URL state, loading/error recovery, and focus decisions. Labels currently use English. Buttons retain native Tab/Enter/Space behavior; no custom arrow model or embedded routing is supplied. The real review queue is its first consumer.
|
|
42
|
+
|
|
43
|
+
DefinitionList accepts readonly items containing stable unique id, term and description React nodes, plus native dl attributes/ref (children are composed from items). Each entry renders a native div/dt/dd group. It adds no landmark, heading, fetching, parsing, selection, or fake table role. Give a standalone list accessible context using a nearby heading or native ARIA attributes. Empty arrays produce an empty dl; consumer empty-state text remains separate. Long/multiline text wraps. Martlet stacks terms and descriptions with generous rhythm; Krigsvold aligns leading term columns and values, collapsing at narrow widths. The real specification-conflict comparison composes it into independent family layouts.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
Switch adds a native binary checkbox with role=switch and fixed visible label. It accepts checked/defaultChecked, change, name/value, disabled and native attributes/ref; the visual track is decorative. ClassName passes to the input. Space and label activation remain native. The settings consumer controls future browser draft protection, with ordinary Save committing the preference.
|
|
48
|
+
|
|
49
|
+
Tabs provides labelled, controlled, manually activated panels. Supply unique item IDs, labels, content and optional disabled flags, plus value/onValueChange and a label. Horizontal arrows (vertical Up/Down when configured) and Home/End move focus among enabled tabs; Enter/Space select. Panels remain mounted, linked by generated IDs, and hidden when inactive. The first enabled item is the fallback for invalid/disabled selection; provide at least one enabled item. Root div props/ref pass through. Routing, persistence, right-to-left arrow adaptation and automatic activation are not supplied.
|
|
50
|
+
|
|
51
|
+
Both are experimental and consumed by the workbench Settings workflow. The package now exposes 22 components. See [release preparation](../../docs/release-policy.md) and [independent documentation-site direction](../../docs/react-sites.md).
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
AppShell supports `navigationPlacement="default" | "top" | "side" | "none"`, an explicit `navigationLabel`, and optional `headerMode="light" | "dark"`. Existing defaults remain compatible. Placement is a task composition choice in both languages; none omits primary navigation while retaining the brand, utilities and single main landmark. See [the UI grammar](../../docs/languages/martlet/grammar.md) and [MCP and builder skills](../../docs/mcp.md).
|
|
55
|
+
|
|
56
|
+
## Source reconciliation
|
|
57
|
+
|
|
58
|
+
The October 23 origin audit is reconciled in this experimental release. TableOfContents accepts optional depth 2/3 per item and variant index/inline; DefinitionList accepts layout rows/stack/grid. Article contents precede body while supporting aside content follows it. AppShell carries neither a decorative top stripe nor a built-in colophon. Existing AppShell navigation defaults are retained for compatibility, with top/side/none available in both families. See docs/languages/martlet/decisions.md for source roles and explicit application extensions. The complete grammar remains experimental; theme 2.1.0 is unchanged.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
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, children, }: {
|
|
14
|
+
id?: string;
|
|
15
|
+
label: string;
|
|
16
|
+
hint?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
children: (props: FieldControl) => ReactNode;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
export declare function Notice({ kind, children, }: {
|
|
21
|
+
kind?: "info" | "success" | "error";
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
24
|
+
export declare function Badge({ children }: {
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}): import("react").JSX.Element;
|
|
27
|
+
export declare function DataTable({ caption, children, className, ...props }: ComponentPropsWithRef<"table"> & {
|
|
28
|
+
caption: string;
|
|
29
|
+
}): import("react").JSX.Element;
|
|
30
|
+
export declare function AppShell({ brand, navigationLabel, navigationPlacement, headerMode, navigation, utilities, children, }: {
|
|
31
|
+
brand: ReactNode;
|
|
32
|
+
navigationLabel?: string;
|
|
33
|
+
navigationPlacement?: "default" | "top" | "side" | "none";
|
|
34
|
+
headerMode?: "light" | "dark";
|
|
35
|
+
navigation: ReactNode;
|
|
36
|
+
utilities: ReactNode;
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
39
|
+
export declare function Dialog({ open, title, onDismiss, children, }: {
|
|
40
|
+
open: boolean;
|
|
41
|
+
title: string;
|
|
42
|
+
onDismiss: () => void;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}): import("react").JSX.Element;
|
|
45
|
+
export declare function Checkbox({ label, id, className, ...props }: Omit<ComponentPropsWithRef<"input">, "type"> & {
|
|
46
|
+
label: ReactNode;
|
|
47
|
+
}): import("react").JSX.Element;
|
|
48
|
+
export declare function Fieldset({ legend, hint, error, children, className, ...props }: ComponentPropsWithRef<"fieldset"> & {
|
|
49
|
+
legend: ReactNode;
|
|
50
|
+
hint?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
}): import("react").JSX.Element;
|
|
53
|
+
export declare function Disclosure({ summary, children, className, ...props }: Omit<ComponentPropsWithRef<"details">, "children"> & {
|
|
54
|
+
summary: ReactNode;
|
|
55
|
+
children: ReactNode;
|
|
56
|
+
}): import("react").JSX.Element;
|
|
57
|
+
export type SummaryError = {
|
|
58
|
+
message: string;
|
|
59
|
+
targetId?: string;
|
|
60
|
+
};
|
|
61
|
+
export declare function ErrorSummary({ title, errors, children, className, ...props }: Omit<ComponentPropsWithRef<"div">, "title"> & {
|
|
62
|
+
title: ReactNode;
|
|
63
|
+
errors?: SummaryError[];
|
|
64
|
+
}): import("react").JSX.Element;
|
|
65
|
+
export type ContentsItem = {
|
|
66
|
+
id: string;
|
|
67
|
+
label: ReactNode;
|
|
68
|
+
depth?: 2 | 3;
|
|
69
|
+
};
|
|
70
|
+
export declare function TableOfContents({ items, label, variant, className, ...props }: Omit<ComponentPropsWithRef<"nav">, "children"> & {
|
|
71
|
+
items: readonly ContentsItem[];
|
|
72
|
+
label?: string;
|
|
73
|
+
variant?: "index" | "inline";
|
|
74
|
+
}): import("react").JSX.Element;
|
|
75
|
+
export declare function Article({ title, titleLevel, eyebrow, summary, metadata, contents, aside, contextLabel, footer, children, className, ...props }: Omit<ComponentPropsWithRef<"article">, "title"> & {
|
|
76
|
+
title: ReactNode;
|
|
77
|
+
titleLevel?: 1 | 2;
|
|
78
|
+
eyebrow?: ReactNode;
|
|
79
|
+
summary?: ReactNode;
|
|
80
|
+
metadata?: ReactNode;
|
|
81
|
+
contents?: ReactNode;
|
|
82
|
+
aside?: ReactNode;
|
|
83
|
+
contextLabel?: string;
|
|
84
|
+
footer?: ReactNode;
|
|
85
|
+
}): import("react").JSX.Element;
|
|
86
|
+
export declare function FileInput({ className, ...props }: Omit<ComponentPropsWithRef<"input">, "type" | "value" | "defaultValue">): import("react").JSX.Element;
|
|
87
|
+
export declare function Radio({ label, id, className, ...props }: Omit<ComponentPropsWithRef<"input">, "type"> & {
|
|
88
|
+
label: ReactNode;
|
|
89
|
+
}): import("react").JSX.Element;
|
|
90
|
+
export declare function Pagination({ page, pageCount, onPageChange, disabled, label, className, ...props }: Omit<ComponentPropsWithRef<"nav">, "children"> & {
|
|
91
|
+
page: number;
|
|
92
|
+
pageCount: number;
|
|
93
|
+
onPageChange: (page: number) => void;
|
|
94
|
+
disabled?: boolean;
|
|
95
|
+
label?: string;
|
|
96
|
+
}): import("react").JSX.Element;
|
|
97
|
+
export type DefinitionItem = {
|
|
98
|
+
id: string;
|
|
99
|
+
term: ReactNode;
|
|
100
|
+
description: ReactNode;
|
|
101
|
+
};
|
|
102
|
+
export declare function DefinitionList({ items, layout, className, ...props }: Omit<ComponentPropsWithRef<"dl">, "children"> & {
|
|
103
|
+
items: readonly DefinitionItem[];
|
|
104
|
+
layout?: "rows" | "stack" | "grid";
|
|
105
|
+
}): import("react").JSX.Element;
|
|
106
|
+
export declare function Switch({ label, id, className, ...props }: Omit<ComponentPropsWithRef<"input">, "type" | "role"> & {
|
|
107
|
+
label: ReactNode;
|
|
108
|
+
}): import("react").JSX.Element;
|
|
109
|
+
export type TabItem = {
|
|
110
|
+
id: string;
|
|
111
|
+
label: ReactNode;
|
|
112
|
+
content: ReactNode;
|
|
113
|
+
disabled?: boolean;
|
|
114
|
+
};
|
|
115
|
+
export declare function Tabs({ items, value, onValueChange, label, orientation, className, ...props }: Omit<ComponentPropsWithRef<"div">, "children" | "onChange"> & {
|
|
116
|
+
items: readonly TabItem[];
|
|
117
|
+
value: string;
|
|
118
|
+
onValueChange: (id: string) => void;
|
|
119
|
+
label: string;
|
|
120
|
+
orientation?: "horizontal" | "vertical";
|
|
121
|
+
}): import("react").JSX.Element;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useId, useRef, } from "react";
|
|
3
|
+
export function Button({ variant = "primary", className = "", type = "button", ...props }) {
|
|
4
|
+
return (_jsx("button", { type: type, className: `martlet-button martlet-button--${variant} ${className}`, ...props }));
|
|
5
|
+
}
|
|
6
|
+
export function TextInput({ className = "", ...props }) {
|
|
7
|
+
return _jsx("input", { className: `martlet-input ${className}`, ...props });
|
|
8
|
+
}
|
|
9
|
+
export function Select({ className = "", ...props }) {
|
|
10
|
+
return _jsx("select", { className: `martlet-input ${className}`, ...props });
|
|
11
|
+
}
|
|
12
|
+
export function Textarea({ className = "", ...props }) {
|
|
13
|
+
return _jsx("textarea", { className: `martlet-input ${className}`, ...props });
|
|
14
|
+
}
|
|
15
|
+
export function Field({ id: providedId, label, hint, error, children, }) {
|
|
16
|
+
const generatedId = useId();
|
|
17
|
+
const id = providedId ?? generatedId;
|
|
18
|
+
const descriptions = [hint && `${id}-hint`, error && `${id}-error`].filter(Boolean).join(" ") ||
|
|
19
|
+
undefined;
|
|
20
|
+
return (_jsxs("div", { className: "martlet-field", children: [_jsx("label", { htmlFor: id, children: label }), children({
|
|
21
|
+
id,
|
|
22
|
+
"aria-describedby": descriptions,
|
|
23
|
+
"aria-invalid": error ? true : undefined,
|
|
24
|
+
}), hint && (_jsx("p", { id: `${id}-hint`, className: "martlet-hint", children: hint })), error && (_jsx("p", { id: `${id}-error`, className: "martlet-error", children: error }))] }));
|
|
25
|
+
}
|
|
26
|
+
export function Notice({ kind = "info", children, }) {
|
|
27
|
+
return (_jsx("div", { className: `martlet-notice martlet-notice--${kind}`, role: kind === "error" ? "alert" : "status", children: children }));
|
|
28
|
+
}
|
|
29
|
+
export function Badge({ children }) {
|
|
30
|
+
return _jsx("span", { className: "martlet-badge", children: children });
|
|
31
|
+
}
|
|
32
|
+
export function DataTable({ caption, children, className = "", ...props }) {
|
|
33
|
+
return (_jsx("div", { className: "martlet-table-scroll", role: "region", "aria-label": caption, tabIndex: 0, children: _jsxs("table", { className: `martlet-table ${className}`, ...props, children: [_jsx("caption", { children: caption }), children] }) }));
|
|
34
|
+
}
|
|
35
|
+
export function AppShell({ brand, navigationLabel = "Workbench", navigationPlacement = "default", headerMode, navigation, utilities, children, }) {
|
|
36
|
+
return (_jsxs("div", { className: "martlet-app", "data-navigation": navigationPlacement, children: [_jsx("a", { className: "martlet-skip", href: "#main", children: "Skip to content" }), _jsxs("header", { className: "martlet-app-header", "data-martlet": headerMode, children: [_jsx("div", { className: "martlet-app-brand", children: brand }), navigationPlacement !== "none" && (_jsx("nav", { "aria-label": navigationLabel, children: navigation })), _jsx("div", { className: "martlet-app-utilities", children: utilities })] }), _jsx("main", { id: "main", tabIndex: -1, children: children })] }));
|
|
37
|
+
}
|
|
38
|
+
export function Dialog({ open, title, onDismiss, children, }) {
|
|
39
|
+
const ref = useRef(null);
|
|
40
|
+
const id = useId();
|
|
41
|
+
useLayoutEffect(() => {
|
|
42
|
+
const dialog = ref.current;
|
|
43
|
+
if (!dialog)
|
|
44
|
+
return;
|
|
45
|
+
if (open && !dialog.open)
|
|
46
|
+
dialog.showModal();
|
|
47
|
+
if (!open && dialog.open)
|
|
48
|
+
dialog.close();
|
|
49
|
+
}, [open]);
|
|
50
|
+
return (_jsxs("dialog", { className: "martlet-dialog", ref: ref, "aria-labelledby": id, onCancel: (event) => {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
onDismiss();
|
|
53
|
+
}, children: [_jsx("h2", { id: id, children: title }), children] }));
|
|
54
|
+
}
|
|
55
|
+
export function Checkbox({ label, id, className = "", ...props }) {
|
|
56
|
+
const generatedId = useId();
|
|
57
|
+
const controlId = id ?? generatedId;
|
|
58
|
+
return (_jsxs("label", { className: `martlet-checkbox ${className}`, htmlFor: controlId, children: [_jsx("input", { ...props, id: controlId, type: "checkbox" }), label] }));
|
|
59
|
+
}
|
|
60
|
+
export function Fieldset({ legend, hint, error, children, className = "", ...props }) {
|
|
61
|
+
const id = useId();
|
|
62
|
+
const describedBy = [props["aria-describedby"], hint && `${id}-hint`, error && `${id}-error`]
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.join(" ") || undefined;
|
|
65
|
+
return (_jsxs("fieldset", { ...props, className: `martlet-fieldset ${className}`, "aria-describedby": describedBy, "aria-invalid": error ? true : props["aria-invalid"], children: [_jsx("legend", { children: legend }), hint && (_jsx("p", { id: `${id}-hint`, className: "martlet-hint", children: hint })), children, error && (_jsx("p", { id: `${id}-error`, className: "martlet-error", children: error }))] }));
|
|
66
|
+
}
|
|
67
|
+
export function Disclosure({ summary, children, className = "", ...props }) {
|
|
68
|
+
return (_jsxs("details", { ...props, className: `martlet-disclosure ${className}`, children: [_jsx("summary", { children: summary }), children] }));
|
|
69
|
+
}
|
|
70
|
+
export function ErrorSummary({ title, errors = [], children, className = "", ...props }) {
|
|
71
|
+
const titleId = useId();
|
|
72
|
+
return (_jsxs("div", { ...props, tabIndex: -1, role: "alert", "aria-labelledby": titleId, className: `martlet-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) => {
|
|
73
|
+
const target = document.getElementById(error.targetId);
|
|
74
|
+
if (target) {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
target.focus();
|
|
77
|
+
}
|
|
78
|
+
}, children: error.message })) : (error.message) }, index))) })), children] }));
|
|
79
|
+
}
|
|
80
|
+
export function TableOfContents({ items, label = "On this page", variant = "index", className = "", ...props }) {
|
|
81
|
+
return (_jsxs("nav", { ...props, "aria-label": label, className: `martlet-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))) })] }));
|
|
82
|
+
}
|
|
83
|
+
export function Article({ title, titleLevel = 1, eyebrow, summary, metadata, contents, aside, contextLabel = "Article context", footer, children, className = "", ...props }) {
|
|
84
|
+
const titleId = useId();
|
|
85
|
+
const Heading = titleLevel === 1 ? "h1" : "h2";
|
|
86
|
+
return (_jsxs("article", { ...props, "aria-labelledby": titleId, className: `martlet-article ${className}`, children: [_jsxs("header", { className: "martlet-article-header", children: [eyebrow && _jsx("p", { className: "martlet-article-eyebrow", children: eyebrow }), _jsx(Heading, { id: titleId, children: title }), summary && _jsx("p", { className: "martlet-article-summary", children: summary }), metadata && _jsx("div", { className: "martlet-article-metadata", children: metadata })] }), _jsxs("div", { className: "martlet-article-layout", "data-contents": !!contents, children: [contents && _jsx("div", { className: "martlet-article-context", children: contents }), _jsxs("div", { className: "martlet-prose", children: [children, aside && _jsx("aside", { className: "martlet-article-support", "aria-label": contextLabel, children: aside })] })] }), footer && _jsx("footer", { className: "martlet-article-footer", children: footer })] }));
|
|
87
|
+
}
|
|
88
|
+
export function FileInput({ className = "", ...props }) {
|
|
89
|
+
return (_jsx("input", { ...props, type: "file", className: `martlet-file-input ${className}` }));
|
|
90
|
+
}
|
|
91
|
+
export function Radio({ label, id, className = "", ...props }) {
|
|
92
|
+
const generatedId = useId();
|
|
93
|
+
const controlId = id ?? generatedId;
|
|
94
|
+
return (_jsxs("label", { className: `martlet-radio ${className}`, htmlFor: controlId, children: [_jsx("input", { ...props, id: controlId, type: "radio" }), label] }));
|
|
95
|
+
}
|
|
96
|
+
export function Pagination({ page, pageCount, onPageChange, disabled = false, label = "Pagination", className = "", ...props }) {
|
|
97
|
+
const count = Number.isFinite(pageCount)
|
|
98
|
+
? Math.max(1, Math.trunc(pageCount))
|
|
99
|
+
: 1;
|
|
100
|
+
const current = Number.isFinite(page)
|
|
101
|
+
? Math.min(count, Math.max(1, Math.trunc(page)))
|
|
102
|
+
: 1;
|
|
103
|
+
return (_jsxs("nav", { ...props, "aria-label": label, className: `martlet-pagination ${className}`, children: [_jsx(Button, { 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, { variant: "secondary", disabled: disabled || current === count, onClick: () => onPageChange(current + 1), children: "Next page" })] }));
|
|
104
|
+
}
|
|
105
|
+
export function DefinitionList({ items, layout = "rows", className = "", ...props }) {
|
|
106
|
+
return (_jsx("dl", { ...props, className: `martlet-definitions ${className}`, "data-layout": layout, children: items.map((item) => (_jsxs("div", { children: [_jsx("dt", { children: item.term }), _jsx("dd", { children: item.description })] }, item.id))) }));
|
|
107
|
+
}
|
|
108
|
+
export function Switch({ label, id, className = "", ...props }) {
|
|
109
|
+
const generatedId = useId();
|
|
110
|
+
return (_jsxs("label", { htmlFor: id ?? generatedId, className: "martlet-switch", children: [_jsxs("span", { className: "martlet-switch-control", children: [_jsx("input", { ...props, id: id ?? generatedId, type: "checkbox", role: "switch", className: className }), _jsx("span", { className: "martlet-switch-track", "aria-hidden": "true", children: _jsx("span", {}) })] }), _jsx("span", { children: label })] }));
|
|
111
|
+
}
|
|
112
|
+
export function Tabs({ items, value, onValueChange, label, orientation = "horizontal", className = "", ...props }) {
|
|
113
|
+
const generatedId = useId();
|
|
114
|
+
const buttons = useRef([]);
|
|
115
|
+
const enabled = items
|
|
116
|
+
.map((item, index) => (item.disabled ? -1 : index))
|
|
117
|
+
.filter((index) => index >= 0);
|
|
118
|
+
const selected = items.findIndex((item) => item.id === value && !item.disabled);
|
|
119
|
+
const active = selected >= 0 ? selected : (enabled[0] ?? -1);
|
|
120
|
+
return (_jsxs("div", { ...props, className: `martlet-tabs ${className}`, children: [_jsx("div", { role: "tablist", "aria-label": label, "aria-orientation": orientation, onKeyDown: (event) => {
|
|
121
|
+
const target = event.target.closest('[role="tab"]');
|
|
122
|
+
const index = buttons.current.findIndex((button) => button === target);
|
|
123
|
+
if (index < 0)
|
|
124
|
+
return;
|
|
125
|
+
const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
|
|
126
|
+
const previousKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
|
|
127
|
+
let next;
|
|
128
|
+
if (event.key === "Home")
|
|
129
|
+
next = enabled[0];
|
|
130
|
+
else if (event.key === "End")
|
|
131
|
+
next = enabled.at(-1);
|
|
132
|
+
else if (event.key === nextKey)
|
|
133
|
+
next = enabled[(enabled.indexOf(index) + 1) % enabled.length];
|
|
134
|
+
else if (event.key === previousKey)
|
|
135
|
+
next =
|
|
136
|
+
enabled[(enabled.indexOf(index) - 1 + enabled.length) % enabled.length];
|
|
137
|
+
if (next !== undefined) {
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
buttons.current[next]?.focus();
|
|
140
|
+
}
|
|
141
|
+
}, children: items.map((item, index) => (_jsx("button", { type: "button", role: "tab", ref: (element) => {
|
|
142
|
+
buttons.current[index] = element;
|
|
143
|
+
}, id: `${generatedId}-tab-${index}`, "aria-controls": `${generatedId}-panel-${index}`, "aria-selected": index === active, tabIndex: index === active ? 0 : -1, disabled: item.disabled, 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)))] }));
|
|
144
|
+
}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
@import "@martlet/theme/css";
|
|
2
|
+
@import "@martlet/theme/foundation.css";
|
|
3
|
+
/* Source roles: see docs/languages/martlet/decisions.md. Theme 2.1.0 is unchanged. */
|
|
4
|
+
.martlet {
|
|
5
|
+
--ui-bg: var(--martlet-surface-canvas);
|
|
6
|
+
--ui-surface: var(--martlet-surface-subtle);
|
|
7
|
+
--ui-text: var(--martlet-text);
|
|
8
|
+
--ui-heading: var(--martlet-text-strong);
|
|
9
|
+
--ui-muted: var(--martlet-text-subtle);
|
|
10
|
+
--ui-line: var(--martlet-border-subtle);
|
|
11
|
+
--ui-accent: var(--martlet-accent);
|
|
12
|
+
--ui-action: var(--martlet-accent);
|
|
13
|
+
--ui-action-hover: var(--martlet-accent-hover);
|
|
14
|
+
--ui-on-action: var(--martlet-on-accent);
|
|
15
|
+
--martlet-field-gap: .5rem;
|
|
16
|
+
--martlet-group-gap: 1.5rem;
|
|
17
|
+
--martlet-region-gap: 2.5rem;
|
|
18
|
+
--martlet-page-gutter: var(--martlet-gutter);
|
|
19
|
+
background: var(--ui-bg); color: var(--ui-text);
|
|
20
|
+
font: 1.075rem/1.72 var(--martlet-font-serif);
|
|
21
|
+
}
|
|
22
|
+
.martlet[data-martlet="light"] {
|
|
23
|
+
--ui-text: var(--martlet-color-ink);
|
|
24
|
+
--ui-heading: var(--martlet-color-navy-900);
|
|
25
|
+
--ui-surface: var(--martlet-color-cream);
|
|
26
|
+
--ui-muted: var(--martlet-color-slate-700);
|
|
27
|
+
--ui-line: var(--martlet-color-slate-300);
|
|
28
|
+
--ui-action: var(--martlet-color-blue-600);
|
|
29
|
+
--ui-action-hover: var(--martlet-color-navy-900);
|
|
30
|
+
--ui-on-action: white;
|
|
31
|
+
}
|
|
32
|
+
.martlet *, .martlet *::before, .martlet *::after { box-sizing: border-box; }
|
|
33
|
+
.martlet :where(img,svg,video) { max-inline-size:100%; block-size:auto; }
|
|
34
|
+
.martlet :where(a) { color:var(--martlet-link); text-underline-offset:var(--martlet-link-underline-offset); text-decoration-thickness:.08em; }
|
|
35
|
+
.martlet :where(a:hover) { color:var(--martlet-link-hover); }
|
|
36
|
+
.martlet :where(a,button,input,select,textarea,[tabindex]):focus-visible { outline:var(--martlet-focus-width) solid var(--martlet-focus-ring); outline-offset:var(--martlet-focus-gap); }
|
|
37
|
+
.martlet :where(h1,h2,h3,h4,h5,h6,p) { margin-block:0; }
|
|
38
|
+
.martlet :where(h1,h2,h3) { color:var(--ui-heading); font-family:var(--martlet-font-serif); font-weight:400; line-height:var(--martlet-leading-heading); text-wrap:balance; }
|
|
39
|
+
.martlet :where(h1) { font-size:var(--martlet-text-h1); }
|
|
40
|
+
.martlet :where(h2) { font-size:var(--martlet-text-h2); }
|
|
41
|
+
.martlet :where(h3) { font-size:var(--martlet-text-h3); }
|
|
42
|
+
.martlet :where(button,input,select,textarea,table,nav) { font-family:var(--martlet-font-sans); }
|
|
43
|
+
.martlet :where(code,pre,kbd,samp) { font-family:var(--martlet-font-mono); }
|
|
44
|
+
.martlet-button { display:inline-flex; align-items:center; justify-content:center; gap:.5rem; min-block-size:var(--martlet-control-height); min-inline-size:var(--martlet-control-height); padding:var(--martlet-button-padding); border:1px solid var(--ui-action); border-radius:var(--martlet-radius-control); color:var(--ui-on-action); background:var(--ui-action); font:700 1rem/1.5 var(--martlet-font-sans); cursor:pointer; }
|
|
45
|
+
.martlet-button:hover:not(:disabled) { background:var(--ui-action-hover); border-color:var(--ui-action-hover); color:var(--ui-on-action); }
|
|
46
|
+
.martlet-button--secondary { background:transparent; border-color:var(--ui-action); color:var(--ui-action); }
|
|
47
|
+
.martlet-button--secondary:hover:not(:disabled) { background:var(--martlet-button-secondary-hover); color:var(--ui-text); }
|
|
48
|
+
.martlet-button--quiet { background:transparent; border-color:transparent; color:var(--martlet-link); }
|
|
49
|
+
.martlet-button--quiet:hover:not(:disabled) { background:var(--martlet-hover-bg); border-color:transparent; color:var(--martlet-link-hover); }
|
|
50
|
+
.martlet-button:disabled { background:var(--martlet-disabled-bg); color:var(--martlet-disabled-text); border-color:var(--martlet-disabled-bg); cursor:default; }
|
|
51
|
+
.martlet-input, .martlet-file-input { display:block; inline-size:100%; min-inline-size:0; min-block-size:var(--martlet-control-height); padding:.65rem .8rem; border:1px solid var(--martlet-input-border); border-radius:var(--martlet-radius-sm); background:var(--martlet-input-bg); color:var(--martlet-input-text); font:1rem/1.5 var(--martlet-font-sans); }
|
|
52
|
+
.martlet textarea { resize:vertical; min-block-size:7rem; }
|
|
53
|
+
.martlet-input[aria-invalid="true"], .martlet-file-input[aria-invalid="true"] { border-color:var(--martlet-danger); }
|
|
54
|
+
.martlet-input:disabled, .martlet-file-input:disabled { background:var(--martlet-disabled-bg); color:var(--martlet-disabled-text); cursor:default; }
|
|
55
|
+
.martlet-field { display:grid; gap:var(--martlet-field-gap); min-inline-size:0; align-content:start; font-family:var(--martlet-font-sans); }
|
|
56
|
+
.martlet-field label, .martlet-fieldset legend { color:var(--ui-heading); font:700 .86rem/1.5 var(--martlet-font-sans); letter-spacing:.02em; }
|
|
57
|
+
.martlet-hint { color:var(--ui-muted); font-size:.875rem; }
|
|
58
|
+
.martlet-error { color:var(--martlet-danger); font-size:.875rem; }
|
|
59
|
+
.martlet-checkbox, .martlet-radio { display:inline-flex; gap:.5rem; align-items:center; min-block-size:2.8rem; cursor:pointer; font:1rem/1.5 var(--martlet-font-sans); }
|
|
60
|
+
.martlet-checkbox input, .martlet-radio input { inline-size:1.2rem; block-size:1.2rem; accent-color:var(--ui-action); flex:none; }
|
|
61
|
+
.martlet-checkbox:has(input:disabled), .martlet-radio:has(input:disabled) { color:var(--martlet-disabled-text); cursor:default; }
|
|
62
|
+
.martlet-fieldset { border:1px solid var(--ui-line); padding:var(--martlet-group-gap); margin:0; min-inline-size:0; }
|
|
63
|
+
.martlet-fieldset legend { padding-inline:.3rem; }
|
|
64
|
+
.martlet-notice { padding:1rem 1.5rem; border-inline-start:4px solid var(--martlet-info); background:var(--martlet-info-bg); color:var(--martlet-info-text); font-family:var(--martlet-font-sans); }
|
|
65
|
+
.martlet-notice--success { border-color:var(--martlet-success); background:var(--martlet-success-bg); color:var(--martlet-success-text); }
|
|
66
|
+
.martlet-notice--error { border-color:var(--martlet-danger); background:var(--martlet-danger-bg); color:var(--martlet-danger-text); }
|
|
67
|
+
.martlet-badge { display:inline-block; font:.8rem/1.5 var(--martlet-font-sans); padding:.2rem .5rem; color:var(--ui-muted); }
|
|
68
|
+
.martlet-error-summary { padding:1rem 1.5rem; border-inline-start:4px solid var(--martlet-danger); background:var(--martlet-danger-bg); color:var(--martlet-danger-text); display:grid; gap:1rem; min-inline-size:0; font-family:var(--martlet-font-sans); }
|
|
69
|
+
.martlet-error-summary ul { margin:0; padding-inline-start:1.25rem; }
|
|
70
|
+
.martlet-error-summary a { color:inherit; text-decoration:underline; }
|
|
71
|
+
.martlet-table-scroll { overflow:auto; max-inline-size:100%; }
|
|
72
|
+
.martlet-table { border-collapse:collapse; inline-size:100%; text-align:start; font:.94rem/1.5 var(--martlet-font-sans); }
|
|
73
|
+
.martlet-table caption { text-align:start; padding-block:0 1rem; color:var(--ui-muted); font-weight:700; }
|
|
74
|
+
.martlet-table :where(th,td) { padding:.8rem .9rem; border-block-end:1px solid var(--ui-line); vertical-align:top; text-align:start; }
|
|
75
|
+
.martlet-table thead th { color:var(--ui-heading); background:var(--ui-surface); font-size:.8rem; letter-spacing:.06em; text-transform:uppercase; }
|
|
76
|
+
.martlet-table tr[data-selected="true"] { background:var(--martlet-active-bg); }
|
|
77
|
+
.martlet-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(--martlet-font-sans); }
|
|
78
|
+
.martlet-skip:focus { inset-block-start:1rem; }
|
|
79
|
+
/* Application frame extension; website navigation belongs to the host. */
|
|
80
|
+
.martlet-app { min-block-size:100vh; font:1rem/1.65 var(--martlet-font-sans); }
|
|
81
|
+
.martlet-app main { min-inline-size:0; padding:var(--martlet-page-gutter); }
|
|
82
|
+
.martlet-app-header { display:flex; flex-wrap:wrap; gap:1.5rem; padding:1rem var(--martlet-page-gutter); border-block-end:1px solid var(--ui-line); align-items:center; }
|
|
83
|
+
.martlet-app-brand { font:700 1.25rem/1.4 var(--martlet-font-serif); }
|
|
84
|
+
.martlet-app-header nav { display:flex; gap:.5rem; flex-wrap:wrap; }
|
|
85
|
+
.martlet-app-header nav a { display:inline-flex; align-items:center; min-block-size:2.8rem; padding:.65rem .75rem; text-decoration:none; }
|
|
86
|
+
.martlet-app-header nav a[aria-current] { border-block-end:2px solid var(--ui-accent); font-weight:700; }
|
|
87
|
+
.martlet-app-utilities { margin-inline-start:auto; display:flex; flex-wrap:wrap; align-items:center; gap:1rem; }
|
|
88
|
+
.martlet-app-header[data-martlet] { --ui-bg:var(--martlet-bg); --ui-text:var(--martlet-text); --ui-heading:var(--martlet-text-strong); --ui-muted:var(--martlet-text-subtle); --ui-line:var(--martlet-border-subtle); --ui-accent:var(--martlet-accent); --ui-action:var(--martlet-accent); --ui-action-hover:var(--martlet-accent-hover); --ui-on-action:var(--martlet-on-accent); color:var(--ui-text); }
|
|
89
|
+
.martlet-app[data-navigation="side"] { display:grid; grid-template-columns:16rem minmax(0,1fr); }
|
|
90
|
+
.martlet-app[data-navigation="side"] > .martlet-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
|
+
.martlet-app[data-navigation="side"] > .martlet-app-header > nav { flex-direction:column; }
|
|
92
|
+
.martlet-app[data-navigation="side"] > .martlet-app-header > .martlet-app-utilities { margin-inline-start:0; }
|
|
93
|
+
.martlet-disclosure > summary { cursor:pointer; min-block-size:2.8rem; padding:.6rem .25rem; font-family:var(--martlet-font-sans); }
|
|
94
|
+
.martlet-dialog { border:1px solid var(--martlet-border-strong); padding:1.5rem; background:var(--martlet-surface-overlay); color:var(--martlet-text); inline-size:min(32rem,calc(100vw - 2rem)); max-block-size:calc(100dvh - 2rem); font:1rem/1.65 var(--martlet-font-sans); }
|
|
95
|
+
.martlet-dialog::backdrop { background:var(--martlet-overlay-scrim); }
|
|
96
|
+
.martlet-dialog h2 { margin-block-end:1rem; color:inherit; }
|
|
97
|
+
.martlet-dialog .actions { margin-block-start:1.5rem; }
|
|
98
|
+
/* TOC anatomy follows the document index and inline article source objects. */
|
|
99
|
+
.martlet-contents { min-inline-size:0; font:.88rem/1.5 var(--martlet-font-sans); }
|
|
100
|
+
.martlet-contents > strong { display:block; margin-block-end:.75rem; color:var(--martlet-color-gold-600); font:700 .78rem/1.5 var(--martlet-font-sans); letter-spacing:.1em; text-transform:uppercase; }
|
|
101
|
+
.martlet-contents ul { margin:0; padding:0; list-style:none; }
|
|
102
|
+
.martlet-contents li { margin:.25rem 0; padding-inline-start:.85rem; }
|
|
103
|
+
.martlet-contents li[data-depth="2"] { padding-inline-start:0; font-weight:700; margin-block-start:1.25rem; }
|
|
104
|
+
.martlet-contents li[data-depth="2"]:first-child { margin-block-start:.5rem; }
|
|
105
|
+
.martlet-contents a { display:block; padding-block:.35rem; text-decoration:none; }
|
|
106
|
+
.martlet-contents a:hover { text-decoration:underline; }
|
|
107
|
+
.martlet-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; border-block-start:2px solid var(--martlet-color-gold-500); scrollbar-width:thin; }
|
|
108
|
+
.martlet-contents[data-variant="inline"] { margin-block-end:2.5rem; padding:1.2rem 1.4rem; background:var(--ui-surface); border-block-start:2px solid var(--martlet-color-gold-500); }
|
|
109
|
+
.martlet-contents[data-variant="inline"] ul { columns:2 16rem; column-gap:2rem; }
|
|
110
|
+
.martlet-contents[data-variant="inline"] li { break-inside:avoid; }
|
|
111
|
+
.martlet-article { min-inline-size:0; container-type:inline-size; container-name:martlet-article; }
|
|
112
|
+
.martlet-article-header { display:grid; gap:1rem; padding-block:1.5rem 2rem; }
|
|
113
|
+
.martlet-article-eyebrow { color:var(--martlet-color-gold-600); font:700 .78rem/1.5 var(--martlet-font-sans); text-transform:uppercase; letter-spacing:.1em; }
|
|
114
|
+
.martlet-article-summary { max-inline-size:var(--martlet-reading-width); font:1.2rem/1.6 var(--martlet-font-serif); }
|
|
115
|
+
.martlet-article-metadata, .martlet-article-footer { font:.85rem/1.55 var(--martlet-font-sans); color:var(--ui-muted); }
|
|
116
|
+
.martlet-article-metadata dl { display:flex; flex-wrap:wrap; gap:1rem 2rem; margin:0; }
|
|
117
|
+
.martlet-article-metadata dt { font-weight:700; }
|
|
118
|
+
.martlet-article-metadata dd { margin:0; }
|
|
119
|
+
.martlet-article-layout { display:grid; grid-template-columns:minmax(0,var(--martlet-reading-width)); justify-content:center; gap:clamp(2rem,4vw,4rem); align-items:start; padding-block:2rem; }
|
|
120
|
+
.martlet-article-layout[data-contents="true"] { grid-template-columns:16rem minmax(0,var(--martlet-reading-width)); }
|
|
121
|
+
.martlet-article-context { min-inline-size:0; font:.94rem/1.65 var(--martlet-font-sans); }
|
|
122
|
+
.martlet-article-support { margin-block-start:2.5rem; display:grid; gap:1.5rem; font:.94rem/1.65 var(--martlet-font-sans); }
|
|
123
|
+
.martlet-prose { min-inline-size:0; max-inline-size:var(--martlet-reading-width); font:1.075rem/1.72 var(--martlet-font-serif); }
|
|
124
|
+
.martlet-prose > * + * { margin-block-start:1.25rem; }
|
|
125
|
+
.martlet-prose :where(h2,h3,h4) { scroll-margin-block-start:6rem; }
|
|
126
|
+
.martlet-prose h2 { margin-block:2.2em 1rem; }
|
|
127
|
+
.martlet-prose h3 { margin-block:2rem .75rem; }
|
|
128
|
+
.martlet-prose :where(ul,ol) { padding-inline-start:1.5rem; }
|
|
129
|
+
.martlet-prose li + li { margin-block-start:.5rem; }
|
|
130
|
+
.martlet-prose :where(code,pre) { font-family:var(--martlet-font-mono); font-size:.92em; }
|
|
131
|
+
.martlet-prose pre { padding:1.5rem; border-radius:var(--martlet-radius-md); max-inline-size:100%; overflow:auto; background:var(--martlet-code-bg); color:var(--martlet-code-text); }
|
|
132
|
+
.martlet-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
|
+
.martlet-prose blockquote { margin:2.5rem 0; border-inline-start:4px solid var(--martlet-color-gold-500); padding:.25rem 0 .25rem 1.5rem; color:var(--ui-muted); font:1.15em/1.65 var(--martlet-font-serif); }
|
|
134
|
+
.martlet-prose img { max-inline-size:100%; block-size:auto; }
|
|
135
|
+
.martlet-prose figcaption { color:var(--ui-muted); font:.82rem/1.45 var(--martlet-font-sans); padding:.7rem .25rem; }
|
|
136
|
+
.martlet-article-footer { border-block-start:1px solid var(--ui-line); padding-block:1.5rem; }
|
|
137
|
+
.martlet-file-input::file-selector-button { min-block-size:2.8rem; padding:.5rem .8rem; margin-inline-end:.75rem; font:inherit; border:1px solid var(--martlet-border-strong); background:var(--martlet-button-secondary-bg); color:var(--martlet-button-secondary-text); cursor:pointer; }
|
|
138
|
+
.martlet-file-input:disabled::file-selector-button { background:var(--martlet-disabled-bg); color:var(--martlet-disabled-text); cursor:default; }
|
|
139
|
+
.martlet-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(--martlet-font-sans); }
|
|
140
|
+
.martlet-pagination p { margin:0; font-variant-numeric:tabular-nums; }
|
|
141
|
+
.martlet-definitions { margin:0; font-family:var(--martlet-font-sans); }
|
|
142
|
+
.martlet-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
|
+
.martlet-definitions dt { font-weight:700; }
|
|
144
|
+
.martlet-definitions dd { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; }
|
|
145
|
+
/* Shared application extensions: no invented family-opposite ornament. */
|
|
146
|
+
.martlet-switch { display:inline-flex; gap:.75rem; align-items:center; min-block-size:2.8rem; cursor:pointer; font:1rem/1.5 var(--martlet-font-sans); }
|
|
147
|
+
.martlet-switch-control { position:relative; flex:none; display:inline-flex; }
|
|
148
|
+
.martlet-switch input { position:absolute; inset:0; inline-size:100%; block-size:100%; opacity:0; margin:0; cursor:inherit; }
|
|
149
|
+
.martlet-switch-track { inline-size:2.6rem; block-size:1.5rem; border:2px solid var(--martlet-border-strong); background:var(--martlet-button-secondary-bg); display:flex; align-items:center; padding:.15rem; border-radius:1rem; }
|
|
150
|
+
.martlet-switch-track > span { inline-size:.95rem; block-size:.95rem; border-radius:50%; background:var(--martlet-button-secondary-text); }
|
|
151
|
+
.martlet-switch input:checked + .martlet-switch-track { background:var(--ui-action); border-color:var(--ui-action); justify-content:flex-end; }
|
|
152
|
+
.martlet-switch input:checked + .martlet-switch-track > span { background:var(--ui-on-action); }
|
|
153
|
+
.martlet-switch input:focus-visible + .martlet-switch-track { outline:3px solid var(--martlet-focus-ring); outline-offset:3px; }
|
|
154
|
+
.martlet-switch:has(input:disabled) { cursor:default; color:var(--martlet-disabled-text); }
|
|
155
|
+
.martlet-switch input:disabled + .martlet-switch-track { background:var(--martlet-disabled-bg); border-color:var(--martlet-disabled-text); }
|
|
156
|
+
.martlet-switch input:disabled + .martlet-switch-track > span { background:var(--martlet-disabled-text); }
|
|
157
|
+
.martlet-tabs { min-inline-size:0; font:1rem/1.5 var(--martlet-font-sans); }
|
|
158
|
+
.martlet-tabs [role="tablist"] { display:flex; flex-wrap:wrap; gap:.5rem; border-block-end:1px solid var(--ui-line); }
|
|
159
|
+
.martlet-tabs [role="tablist"][aria-orientation="vertical"] { flex-direction:column; }
|
|
160
|
+
.martlet-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
|
+
.martlet-tabs [role="tab"][aria-selected="true"] { border-block-end-color:var(--ui-action); font-weight:700; }
|
|
162
|
+
.martlet-tabs [role="tab"]:disabled { color:var(--martlet-disabled-text); cursor:default; }
|
|
163
|
+
.martlet-tabs [role="tabpanel"] { padding-block:1.5rem; outline-offset:3px; }
|
|
164
|
+
.martlet-tabs [role="tabpanel"][hidden] { display:none; }
|
|
165
|
+
@container martlet-article (max-width:74rem) {
|
|
166
|
+
.martlet-article-layout[data-contents="true"] { grid-template-columns:minmax(0,1fr); }
|
|
167
|
+
.martlet-contents[data-variant="index"] { position:static; max-block-size:none; overflow:visible; padding:1.2rem 1.4rem; background:var(--ui-surface); }
|
|
168
|
+
.martlet-contents[data-variant="index"] ul { columns:2 16rem; column-gap:2rem; }
|
|
169
|
+
.martlet-contents[data-variant="index"] li { break-inside:avoid; }
|
|
170
|
+
}
|
|
171
|
+
@media(max-width:55rem) {
|
|
172
|
+
.martlet-app[data-navigation="side"] { display:block; }
|
|
173
|
+
.martlet-app[data-navigation="side"] > .martlet-app-header { flex-direction:row; border-inline-end:0; border-block-end:1px solid var(--ui-line); padding:1rem; }
|
|
174
|
+
.martlet-app[data-navigation="side"] > .martlet-app-header > nav { flex-direction:row; }
|
|
175
|
+
.martlet-app-utilities { margin-inline-start:0; }
|
|
176
|
+
.martlet-definitions > div { grid-template-columns:minmax(0,1fr); gap:.5rem; }
|
|
177
|
+
}
|
|
178
|
+
@media(max-width:40rem) { .martlet-contents ul { columns:1 !important; } }
|
|
179
|
+
@media(max-aspect-ratio:4/5) {
|
|
180
|
+
.martlet-article-layout[data-contents="true"] { grid-template-columns:minmax(0,1fr); }
|
|
181
|
+
.martlet-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) { .martlet *, .martlet *::before, .martlet *::after { transition:none !important; animation:none !important; scroll-behavior:auto !important; } }
|
|
184
|
+
@media(forced-colors:active) {
|
|
185
|
+
.martlet-switch-track { forced-color-adjust:none; background:Canvas; border-color:CanvasText; }
|
|
186
|
+
.martlet-switch-track > span { background:CanvasText; }
|
|
187
|
+
.martlet-switch input:checked + .martlet-switch-track { background:Highlight; border-color:Highlight; }
|
|
188
|
+
.martlet-switch input:checked + .martlet-switch-track > span { background:HighlightText; }
|
|
189
|
+
.martlet-switch input:disabled + .martlet-switch-track { background:Canvas; border-color:GrayText; }
|
|
190
|
+
.martlet-switch input:disabled + .martlet-switch-track > span { background:GrayText; }
|
|
191
|
+
.martlet-tabs [role="tab"][aria-selected="true"] { border-block-end-color:Highlight; }
|
|
192
|
+
}
|
|
193
|
+
@media print {
|
|
194
|
+
.martlet-article { background:white; color:black; }
|
|
195
|
+
.martlet-article-layout { display:block; }
|
|
196
|
+
.martlet-contents { display:none; }
|
|
197
|
+
.martlet-prose { max-inline-size:none; }
|
|
198
|
+
.martlet-prose :where(h2,h3,h4) { break-after:avoid; }
|
|
199
|
+
.martlet-prose :where(pre,blockquote,figure) { break-inside:avoid; }
|
|
200
|
+
.martlet-article :is(.martlet-article-context,pre) { background:white; color:black; }
|
|
201
|
+
.martlet-article :is(.martlet-article-metadata,.martlet-article-footer,a) { color:black; }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* J-G focus treatment and J-C short section rule. */
|
|
205
|
+
.martlet :where(a,button,input,select,textarea,[tabindex]):focus-visible { outline:3px solid white; outline-offset:3px; box-shadow:0 0 0 6px var(--martlet-color-navy-900); }
|
|
206
|
+
.martlet-prose h2::after { content:""; display:block; inline-size:3rem; block-size:2px; margin-block-start:.6rem; background:var(--martlet-color-gold-500); }
|
|
207
|
+
.martlet[data-martlet="dark"] .martlet-contents > strong, .martlet[data-martlet="dark"] .martlet-article-eyebrow { color:var(--martlet-color-gold-300); }
|
|
208
|
+
|
|
209
|
+
.martlet-article-context { position:sticky; inset-block-start:6rem; }
|
|
210
|
+
.martlet-definitions[data-layout="stack"] > div { display:block; }
|
|
211
|
+
.martlet-definitions[data-layout="stack"] dd { margin-block-start:.5rem; }
|
|
212
|
+
.martlet-definitions[data-layout="grid"] { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:1.5rem; }
|
|
213
|
+
.martlet-definitions[data-layout="grid"] > div { display:block; }
|
|
214
|
+
@container martlet-article (max-width:74rem) { .martlet-article-context { position:static; } }
|
|
215
|
+
@media(max-aspect-ratio:4/5) { .martlet-article-context { position:static; } }
|
|
216
|
+
@media(max-width:48rem) { .martlet-definitions[data-layout="grid"] { grid-template-columns:minmax(0,1fr); } }
|
|
217
|
+
|
|
218
|
+
/* Technical identifiers may wrap locally; ordinary prose keeps natural word boundaries. */
|
|
219
|
+
.martlet :is(h1,h2,h3,h4,h5,h6,a,dd) { overflow-wrap:break-word; }
|
|
220
|
+
|
|
221
|
+
/* Bounded application labels must shrink at enlarged text sizes. Prose is separate. */
|
|
222
|
+
.martlet-app-header > *, .martlet-app-utilities > * { min-inline-size:0; max-inline-size:100%; }
|
|
223
|
+
.martlet-app-brand, .martlet-app-header nav a { overflow-wrap:anywhere; }
|
|
224
|
+
.martlet-button { max-inline-size:100%; overflow-wrap:anywhere; white-space:normal; }
|
|
225
|
+
.martlet-checkbox, .martlet-radio, .martlet-switch { max-inline-size:100%; overflow-wrap:anywhere; }
|
|
226
|
+
.martlet-checkbox > span, .martlet-radio > span, .martlet-switch > span:not(.martlet-switch-control) { min-inline-size:0; }
|
|
227
|
+
.martlet-field, .martlet-fieldset, .martlet-notice, .martlet-error-summary, .martlet-badge { overflow-wrap:anywhere; }
|
|
228
|
+
.martlet-tabs [role="tab"] { max-inline-size:100%; min-inline-size:0; }
|
|
229
|
+
|
|
230
|
+
.martlet-field, .martlet-error-summary { grid-template-columns:minmax(0,1fr); }
|
|
231
|
+
.martlet-field > *, .martlet-error-summary > * { min-inline-size:0; }
|
|
232
|
+
|
|
233
|
+
/* Existing darker gold improves small-label contrast on the source cream inset. */
|
|
234
|
+
.martlet-contents[data-variant="inline"] > strong { color:var(--martlet-color-gold-700); }
|
|
235
|
+
@container martlet-article (max-width:74rem) { .martlet-contents[data-variant="index"] > strong { color:var(--martlet-color-gold-700); } }
|
|
236
|
+
@media(max-aspect-ratio:4/5) { .martlet-contents[data-variant="index"] > strong { color:var(--martlet-color-gold-700); } }
|
|
237
|
+
.martlet[data-martlet="dark"] .martlet-contents > strong { color:var(--martlet-color-gold-300); }
|
|
238
|
+
|
|
239
|
+
.martlet-article-header { grid-template-columns:minmax(0,1fr); }
|
|
240
|
+
.martlet-article-header > *, .martlet-article-metadata dl > * { min-inline-size:0; max-inline-size:100%; }
|
|
241
|
+
.martlet-article-metadata { overflow-wrap:anywhere; }
|
|
242
|
+
|
|
243
|
+
.martlet-disclosure > summary { overflow-wrap:anywhere; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martlet/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Martlet experimental React components, developed in a working component registry",
|
|
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
|
+
"@martlet/theme": "2.1.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "node ../../scripts/clean-package.mjs martlet && tsc -p tsconfig.json && node ../../scripts/copy-styles.mjs martlet"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/howardjp/design-workbench.git",
|
|
37
|
+
"directory": "packages/martlet-react"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"tag": "next",
|
|
42
|
+
"provenance": true
|
|
43
|
+
}
|
|
44
|
+
}
|