@martlet/react 0.1.0 → 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 +4 -0
- package/README.md +9 -50
- package/dist/index.d.ts +3 -1
- package/dist/index.js +76 -12
- package/dist/styles.css +12 -15
- package/package.json +22 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @martlet/react changelog
|
|
2
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
|
+
|
|
3
7
|
## 0.1.0 — experimental candidate, unpublished
|
|
4
8
|
|
|
5
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.
|
package/README.md
CHANGED
|
@@ -1,58 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Martlet React (experimental)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This independent repository contains the experimental `@martlet/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
4
|
|
|
5
|
-
|
|
6
|
-
import { Button, Field, TextInput } from '@martlet/react';
|
|
7
|
-
import '@martlet/react/styles.css';
|
|
8
|
-
import '@martlet/theme/fonts.css'; // optional local fonts
|
|
5
|
+
## Development
|
|
9
6
|
|
|
10
|
-
|
|
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
|
-
```
|
|
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.
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
npm install @martlet/react@0.1.0 react@19 react-dom@19
|
|
20
|
-
```
|
|
9
|
+
The exact published `@martlet/core` dependency supplies styles. Import `@martlet/react/styles.css`; load `@martlet/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.
|
|
21
10
|
|
|
22
|
-
|
|
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/martlet`; 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.
|
|
23
12
|
|
|
24
|
-
|
|
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.
|
|
25
14
|
|
|
26
|
-
|
|
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.
|
|
27
16
|
|
|
28
|
-
|
|
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.
|
|
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.
|
package/dist/index.d.ts
CHANGED
|
@@ -10,11 +10,13 @@ export type FieldControl = {
|
|
|
10
10
|
"aria-describedby"?: string;
|
|
11
11
|
"aria-invalid"?: true;
|
|
12
12
|
};
|
|
13
|
-
export declare function Field({ id: providedId, label, hint, error, children, }: {
|
|
13
|
+
export declare function Field({ id: providedId, label, hint, error, hintId: providedHintId, errorId: providedErrorId, children, }: {
|
|
14
14
|
id?: string;
|
|
15
15
|
label: string;
|
|
16
16
|
hint?: string;
|
|
17
17
|
error?: string;
|
|
18
|
+
hintId?: string;
|
|
19
|
+
errorId?: string;
|
|
18
20
|
children: (props: FieldControl) => ReactNode;
|
|
19
21
|
}): import("react").JSX.Element;
|
|
20
22
|
export declare function Notice({ kind, children, }: {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useLayoutEffect, useId, useRef, } from "react";
|
|
2
|
+
import { useLayoutEffect, useId, useRef, useState, } from "react";
|
|
3
3
|
export function Button({ variant = "primary", className = "", type = "button", ...props }) {
|
|
4
4
|
return (_jsx("button", { type: type, className: `martlet-button martlet-button--${variant} ${className}`, ...props }));
|
|
5
5
|
}
|
|
@@ -12,16 +12,18 @@ export function Select({ className = "", ...props }) {
|
|
|
12
12
|
export function Textarea({ className = "", ...props }) {
|
|
13
13
|
return _jsx("textarea", { className: `martlet-input ${className}`, ...props });
|
|
14
14
|
}
|
|
15
|
-
export function Field({ id: providedId, label, hint, error, children, }) {
|
|
15
|
+
export function Field({ id: providedId, label, hint, error, hintId: providedHintId, errorId: providedErrorId, children, }) {
|
|
16
16
|
const generatedId = useId();
|
|
17
17
|
const id = providedId ?? generatedId;
|
|
18
|
-
|
|
19
|
-
|
|
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;
|
|
20
22
|
return (_jsxs("div", { className: "martlet-field", children: [_jsx("label", { htmlFor: id, children: label }), children({
|
|
21
23
|
id,
|
|
22
24
|
"aria-describedby": descriptions,
|
|
23
25
|
"aria-invalid": error ? true : undefined,
|
|
24
|
-
}), hint && (_jsx("p", { id:
|
|
26
|
+
}), hint && (_jsx("p", { id: hintId, className: "martlet-hint", children: hint })), error && (_jsx("p", { id: errorId, className: "martlet-error", children: error }))] }));
|
|
25
27
|
}
|
|
26
28
|
export function Notice({ kind = "info", children, }) {
|
|
27
29
|
return (_jsx("div", { className: `martlet-notice martlet-notice--${kind}`, role: kind === "error" ? "alert" : "status", children: children }));
|
|
@@ -37,6 +39,7 @@ export function AppShell({ brand, navigationLabel = "Workbench", navigationPlace
|
|
|
37
39
|
}
|
|
38
40
|
export function Dialog({ open, title, onDismiss, children, }) {
|
|
39
41
|
const ref = useRef(null);
|
|
42
|
+
const suppressedCloseEvents = useRef(0);
|
|
40
43
|
const id = useId();
|
|
41
44
|
useLayoutEffect(() => {
|
|
42
45
|
const dialog = ref.current;
|
|
@@ -44,10 +47,20 @@ export function Dialog({ open, title, onDismiss, children, }) {
|
|
|
44
47
|
return;
|
|
45
48
|
if (open && !dialog.open)
|
|
46
49
|
dialog.showModal();
|
|
47
|
-
if (!open && dialog.open)
|
|
50
|
+
if (!open && dialog.open) {
|
|
51
|
+
// The close event is queued; distinguish prop-driven closes from native ones.
|
|
52
|
+
suppressedCloseEvents.current += 1;
|
|
48
53
|
dialog.close();
|
|
54
|
+
}
|
|
49
55
|
}, [open]);
|
|
50
|
-
return (_jsxs("dialog", { className: "martlet-dialog", ref: ref, "aria-labelledby": id,
|
|
56
|
+
return (_jsxs("dialog", { className: "martlet-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) => {
|
|
51
64
|
event.preventDefault();
|
|
52
65
|
onDismiss();
|
|
53
66
|
}, children: [_jsx("h2", { id: id, children: title }), children] }));
|
|
@@ -70,10 +83,15 @@ export function Disclosure({ summary, children, className = "", ...props }) {
|
|
|
70
83
|
export function ErrorSummary({ title, errors = [], children, className = "", ...props }) {
|
|
71
84
|
const titleId = useId();
|
|
72
85
|
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
|
-
|
|
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);
|
|
74
90
|
if (target) {
|
|
75
|
-
event.preventDefault();
|
|
76
91
|
target.focus();
|
|
92
|
+
// If focus cannot move, retain the link's native fragment navigation.
|
|
93
|
+
if (owner.activeElement === target)
|
|
94
|
+
event.preventDefault();
|
|
77
95
|
}
|
|
78
96
|
}, children: error.message })) : (error.message) }, index))) })), children] }));
|
|
79
97
|
}
|
|
@@ -100,7 +118,30 @@ export function Pagination({ page, pageCount, onPageChange, disabled = false, la
|
|
|
100
118
|
const current = Number.isFinite(page)
|
|
101
119
|
? Math.min(count, Math.max(1, Math.trunc(page)))
|
|
102
120
|
: 1;
|
|
103
|
-
|
|
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: `martlet-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" })] }));
|
|
104
145
|
}
|
|
105
146
|
export function DefinitionList({ items, layout = "rows", className = "", ...props }) {
|
|
106
147
|
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))) }));
|
|
@@ -112,12 +153,35 @@ export function Switch({ label, id, className = "", ...props }) {
|
|
|
112
153
|
export function Tabs({ items, value, onValueChange, label, orientation = "horizontal", className = "", ...props }) {
|
|
113
154
|
const generatedId = useId();
|
|
114
155
|
const buttons = useRef([]);
|
|
156
|
+
const [focusedId, setFocusedId] = useState(null);
|
|
115
157
|
const enabled = items
|
|
116
158
|
.map((item, index) => (item.disabled ? -1 : index))
|
|
117
159
|
.filter((index) => index >= 0);
|
|
118
160
|
const selected = items.findIndex((item) => item.id === value && !item.disabled);
|
|
119
161
|
const active = selected >= 0 ? selected : (enabled[0] ?? -1);
|
|
120
|
-
|
|
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: `martlet-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) => {
|
|
121
185
|
const target = event.target.closest('[role="tab"]');
|
|
122
186
|
const index = buttons.current.findIndex((button) => button === target);
|
|
123
187
|
if (index < 0)
|
|
@@ -140,5 +204,5 @@ export function Tabs({ items, value, onValueChange, label, orientation = "horizo
|
|
|
140
204
|
}
|
|
141
205
|
}, children: items.map((item, index) => (_jsx("button", { type: "button", role: "tab", ref: (element) => {
|
|
142
206
|
buttons.current[index] = element;
|
|
143
|
-
}, id: `${generatedId}-tab-${index}`, "aria-controls": `${generatedId}-panel-${index}`, "aria-selected": index === active, tabIndex: index ===
|
|
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)))] }));
|
|
144
208
|
}
|
package/dist/styles.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
@import "@martlet/
|
|
2
|
-
@import "@martlet/
|
|
1
|
+
@import "@martlet/core/css";
|
|
2
|
+
@import "@martlet/core/foundation.css";
|
|
3
3
|
/* Source roles: see docs/languages/martlet/decisions.md. Theme 2.1.0 is unchanged. */
|
|
4
4
|
.martlet {
|
|
5
5
|
--ui-bg: var(--martlet-surface-canvas);
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
.martlet-field label, .martlet-fieldset legend { color:var(--ui-heading); font:700 .86rem/1.5 var(--martlet-font-sans); letter-spacing:.02em; }
|
|
57
57
|
.martlet-hint { color:var(--ui-muted); font-size:.875rem; }
|
|
58
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); }
|
|
59
|
+
.martlet-checkbox, .martlet-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(--martlet-font-sans); }
|
|
60
60
|
.martlet-checkbox input, .martlet-radio input { inline-size:1.2rem; block-size:1.2rem; accent-color:var(--ui-action); flex:none; }
|
|
61
61
|
.martlet-checkbox:has(input:disabled), .martlet-radio:has(input:disabled) { color:var(--martlet-disabled-text); cursor:default; }
|
|
62
62
|
.martlet-fieldset { border:1px solid var(--ui-line); padding:var(--martlet-group-gap); margin:0; min-inline-size:0; }
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
.martlet-table-scroll { overflow:auto; max-inline-size:100%; }
|
|
72
72
|
.martlet-table { border-collapse:collapse; inline-size:100%; text-align:start; font:.94rem/1.5 var(--martlet-font-sans); }
|
|
73
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; }
|
|
74
|
+
.martlet-table :where(th,td) { min-inline-size:8rem; padding:.8rem .9rem; border-block-end:1px solid var(--ui-line); vertical-align:top; text-align:start; }
|
|
75
75
|
.martlet-table thead th { color:var(--ui-heading); background:var(--ui-surface); font-size:.8rem; letter-spacing:.06em; text-transform:uppercase; }
|
|
76
76
|
.martlet-table tr[data-selected="true"] { background:var(--martlet-active-bg); }
|
|
77
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); }
|
|
@@ -105,6 +105,9 @@
|
|
|
105
105
|
.martlet-contents a { display:block; padding-block:.35rem; text-decoration:none; }
|
|
106
106
|
.martlet-contents a:hover { text-decoration:underline; }
|
|
107
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
|
+
/* A document index is compact, including after narrow-container reflow. */
|
|
109
|
+
.martlet-contents[data-variant="index"] li[data-depth="2"] { margin-block-start:.35rem; }
|
|
110
|
+
.martlet-contents[data-variant="index"] li:first-child { margin-block-start:0; }
|
|
108
111
|
.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
112
|
.martlet-contents[data-variant="inline"] ul { columns:2 16rem; column-gap:2rem; }
|
|
110
113
|
.martlet-contents[data-variant="inline"] li { break-inside:avoid; }
|
|
@@ -162,10 +165,10 @@
|
|
|
162
165
|
.martlet-tabs [role="tab"]:disabled { color:var(--martlet-disabled-text); cursor:default; }
|
|
163
166
|
.martlet-tabs [role="tabpanel"] { padding-block:1.5rem; outline-offset:3px; }
|
|
164
167
|
.martlet-tabs [role="tabpanel"][hidden] { display:none; }
|
|
165
|
-
@container martlet-article (max-width:
|
|
168
|
+
@container martlet-article (max-width:56rem) {
|
|
166
169
|
.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:
|
|
168
|
-
.martlet-contents[data-variant="index"] ul { columns:
|
|
170
|
+
.martlet-contents[data-variant="index"] { position:static; max-block-size:none; overflow:visible; max-inline-size:22rem; padding:1rem; background:var(--ui-surface); }
|
|
171
|
+
.martlet-contents[data-variant="index"] ul { columns:1; }
|
|
169
172
|
.martlet-contents[data-variant="index"] li { break-inside:avoid; }
|
|
170
173
|
}
|
|
171
174
|
@media(max-width:55rem) {
|
|
@@ -176,10 +179,6 @@
|
|
|
176
179
|
.martlet-definitions > div { grid-template-columns:minmax(0,1fr); gap:.5rem; }
|
|
177
180
|
}
|
|
178
181
|
@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
182
|
@media(prefers-reduced-motion:reduce) { .martlet *, .martlet *::before, .martlet *::after { transition:none !important; animation:none !important; scroll-behavior:auto !important; } }
|
|
184
183
|
@media(forced-colors:active) {
|
|
185
184
|
.martlet-switch-track { forced-color-adjust:none; background:Canvas; border-color:CanvasText; }
|
|
@@ -211,8 +210,7 @@
|
|
|
211
210
|
.martlet-definitions[data-layout="stack"] dd { margin-block-start:.5rem; }
|
|
212
211
|
.martlet-definitions[data-layout="grid"] { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:1.5rem; }
|
|
213
212
|
.martlet-definitions[data-layout="grid"] > div { display:block; }
|
|
214
|
-
@container martlet-article (max-width:
|
|
215
|
-
@media(max-aspect-ratio:4/5) { .martlet-article-context { position:static; } }
|
|
213
|
+
@container martlet-article (max-width:56rem) { .martlet-article-context { position:static; } }
|
|
216
214
|
@media(max-width:48rem) { .martlet-definitions[data-layout="grid"] { grid-template-columns:minmax(0,1fr); } }
|
|
217
215
|
|
|
218
216
|
/* Technical identifiers may wrap locally; ordinary prose keeps natural word boundaries. */
|
|
@@ -232,8 +230,7 @@
|
|
|
232
230
|
|
|
233
231
|
/* Existing darker gold improves small-label contrast on the source cream inset. */
|
|
234
232
|
.martlet-contents[data-variant="inline"] > strong { color:var(--martlet-color-gold-700); }
|
|
235
|
-
@container martlet-article (max-width:
|
|
236
|
-
@media(max-aspect-ratio:4/5) { .martlet-contents[data-variant="index"] > strong { color:var(--martlet-color-gold-700); } }
|
|
233
|
+
@container martlet-article (max-width:56rem) { .martlet-contents[data-variant="index"] > strong { color:var(--martlet-color-gold-700); } }
|
|
237
234
|
.martlet[data-martlet="dark"] .martlet-contents > strong { color:var(--martlet-color-gold-300); }
|
|
238
235
|
|
|
239
236
|
.martlet-article-header { grid-template-columns:minmax(0,1fr); }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martlet/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Martlet
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Experimental Martlet React components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -26,19 +26,35 @@
|
|
|
26
26
|
"react": "^19.0.0"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@martlet/
|
|
29
|
+
"@martlet/core": "2.2.3"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "node
|
|
32
|
+
"build": "node build.mjs",
|
|
33
|
+
"test": "npm run build && node --test tests/*.test.mjs",
|
|
34
|
+
"test:browser": "DESIGN_FAMILY=martlet node tests/browser-check.mjs",
|
|
35
|
+
"build:gallery": "NIGHTJAR_FAMILY=martlet node examples/react-catalog/build.mjs",
|
|
36
|
+
"build:reference": "node scripts/build-reference-deployment.mjs"
|
|
33
37
|
},
|
|
34
38
|
"repository": {
|
|
35
39
|
"type": "git",
|
|
36
|
-
"url": "git+https://github.com/
|
|
37
|
-
"directory": "packages/martlet-react"
|
|
40
|
+
"url": "git+https://github.com/martletdesign/react.git"
|
|
38
41
|
},
|
|
39
42
|
"publishConfig": {
|
|
40
43
|
"access": "public",
|
|
41
44
|
"tag": "next",
|
|
42
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"
|
|
43
59
|
}
|
|
44
60
|
}
|