@tonycodes-ui/mortar 0.0.0-stage → 0.12.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.
Files changed (107) hide show
  1. package/AGENTS.md +10 -0
  2. package/CHANGELOG.md +268 -0
  3. package/CONTRIBUTING.md +60 -0
  4. package/DESIGN.md +51 -0
  5. package/LICENSE +21 -0
  6. package/README.md +155 -2
  7. package/TESTING.md +83 -0
  8. package/baseline-policy.json +16 -0
  9. package/bin/mortar.mjs +258 -0
  10. package/catalog.json +9900 -0
  11. package/cli/audit.mjs +943 -0
  12. package/cli/data.mjs +176 -0
  13. package/cli/mcp.mjs +216 -0
  14. package/cli/migrations.mjs +128 -0
  15. package/cli/packet.mjs +267 -0
  16. package/cli/search.mjs +246 -0
  17. package/cli/tools.mjs +229 -0
  18. package/design-principles.json +30 -0
  19. package/dist/agent-data.json +1 -0
  20. package/dist/application.d.ts +340 -0
  21. package/dist/application.js +240 -0
  22. package/dist/auth.d.ts +121 -0
  23. package/dist/auth.js +97 -0
  24. package/dist/brand.d.ts +69 -0
  25. package/dist/brand.js +266 -0
  26. package/dist/calendar.d.ts +75 -0
  27. package/dist/calendar.js +310 -0
  28. package/dist/choice.d.ts +36 -0
  29. package/dist/choice.js +36 -0
  30. package/dist/composer.d.ts +28 -0
  31. package/dist/composer.js +47 -0
  32. package/dist/display.d.ts +136 -0
  33. package/dist/display.js +75 -0
  34. package/dist/docs.d.ts +145 -0
  35. package/dist/docs.js +141 -0
  36. package/dist/ecommerce.d.ts +261 -0
  37. package/dist/ecommerce.js +177 -0
  38. package/dist/feedback.d.ts +80 -0
  39. package/dist/feedback.js +98 -0
  40. package/dist/foundation.d.ts +42 -0
  41. package/dist/foundation.js +121 -0
  42. package/dist/heading.d.ts +5 -0
  43. package/dist/heading.js +9 -0
  44. package/dist/icons.d.ts +50 -0
  45. package/dist/icons.js +70 -0
  46. package/dist/illustration.d.ts +47 -0
  47. package/dist/illustration.js +47 -0
  48. package/dist/index.d.ts +36 -0
  49. package/dist/index.js +18 -0
  50. package/dist/inspection.d.ts +24 -0
  51. package/dist/inspection.js +14 -0
  52. package/dist/internal.d.ts +514 -0
  53. package/dist/internal.js +332 -0
  54. package/dist/layout.d.ts +107 -0
  55. package/dist/layout.js +66 -0
  56. package/dist/marketing-sections.d.ts +161 -0
  57. package/dist/marketing-sections.js +58 -0
  58. package/dist/marketing.d.ts +277 -0
  59. package/dist/marketing.js +163 -0
  60. package/dist/navigation.d.ts +35 -0
  61. package/dist/navigation.js +44 -0
  62. package/dist/primitives.d.ts +258 -0
  63. package/dist/primitives.js +244 -0
  64. package/dist/showcase.d.ts +29 -0
  65. package/dist/showcase.js +10 -0
  66. package/dist/workspace.d.ts +80 -0
  67. package/dist/workspace.js +48 -0
  68. package/docs/agents.md +127 -0
  69. package/docs/auth-flows.md +71 -0
  70. package/docs/migrating-to-0.6.md +94 -0
  71. package/docs/theming.md +170 -0
  72. package/package.json +94 -4
  73. package/skills/mortar/SKILL.md +36 -0
  74. package/src/application.css +1487 -0
  75. package/src/application.tsx +1413 -0
  76. package/src/auth.tsx +452 -0
  77. package/src/brand.ts +335 -0
  78. package/src/calendar.css +468 -0
  79. package/src/calendar.tsx +868 -0
  80. package/src/choice.tsx +240 -0
  81. package/src/composer.tsx +158 -0
  82. package/src/data.css +690 -0
  83. package/src/display.tsx +452 -0
  84. package/src/docs.css +704 -0
  85. package/src/docs.tsx +737 -0
  86. package/src/ecommerce.css +810 -0
  87. package/src/ecommerce.tsx +1033 -0
  88. package/src/feedback.tsx +388 -0
  89. package/src/foundation.css +1867 -0
  90. package/src/foundation.tsx +227 -0
  91. package/src/heading.ts +13 -0
  92. package/src/icons.tsx +237 -0
  93. package/src/illustration.tsx +169 -0
  94. package/src/index.ts +233 -0
  95. package/src/inspection.tsx +91 -0
  96. package/src/internal.tsx +700 -0
  97. package/src/layout.tsx +342 -0
  98. package/src/marketing-sections.tsx +621 -0
  99. package/src/marketing.css +2579 -0
  100. package/src/marketing.tsx +1235 -0
  101. package/src/navigation.tsx +118 -0
  102. package/src/overlays.css +350 -0
  103. package/src/primitives.tsx +1135 -0
  104. package/src/showcase.tsx +120 -0
  105. package/src/tokens.css +449 -0
  106. package/src/workspace.tsx +333 -0
  107. package/styles.css +56 -0
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId } from "react";
3
+ import { KitLink } from "./navigation.js";
4
+ import { Check, Close, Paperclip, Person } from "./icons.js";
5
+ import { headingTag } from "./heading.js";
6
+ import { TermList, cx, componentRoot, focusAfterRemoval, classedIntroSlots, } from "./internal.js";
7
+ function initials(name) {
8
+ return name
9
+ .split(/\s+/)
10
+ .filter(Boolean)
11
+ .slice(0, 2)
12
+ .map((part) => part.charAt(0).toUpperCase())
13
+ .join("");
14
+ }
15
+ export function Avatar({ name, src, size = "md", shape = "circle", status, statusPosition = "bottom", placeholder = "initials", decorative = false, className, }) {
16
+ return (_jsxs("span", { ...componentRoot("Avatar"), className: cx(`m-avatar m-avatar-${size}`, className), "data-shape": shape === "circle" ? undefined : shape, ...(decorative
17
+ ? { "aria-hidden": true }
18
+ : {
19
+ role: "img",
20
+ "aria-label": status ? `${name}, ${status}` : name,
21
+ }), children: [src ? (_jsx("img", { src: src, alt: "" })) : placeholder === "icon" ? (_jsx(Person, { className: "m-avatar-placeholder" })) : (_jsx("span", { "aria-hidden": "true", children: initials(name) })), status && (_jsx("span", { className: "m-avatar-status", "data-status": status, "data-position": statusPosition === "top" ? "top" : undefined, "aria-hidden": "true" }))] }));
22
+ }
23
+ export function AvatarGroup({ label, people, max = 4, className, }) {
24
+ const shown = people.slice(0, max), rest = people.length - shown.length;
25
+ return (_jsxs("ul", { ...componentRoot("AvatarGroup"), className: cx("m-avatar-group", className), "aria-label": label, children: [shown.map((person) => (_jsx("li", { children: _jsx(Avatar, { name: person.name, src: person.src, size: "sm" }) }, person.name))), rest > 0 && (_jsx("li", { children: _jsxs("span", { className: "m-avatar m-avatar-sm m-avatar-more", role: "img", "aria-label": `${rest} more`, children: ["+", rest] }) }))] }));
26
+ }
27
+ export function Kbd({ keys, className }) {
28
+ return (_jsx("span", { ...componentRoot("Kbd"), className: cx("m-kbd-group", className), children: keys.map((key, i) => (_jsx("kbd", { className: "m-kbd", children: key }, i))) }));
29
+ }
30
+ export function ProgressBar({ label, value, max = 100, valueLabel, tone, className, }) {
31
+ const labelId = useId();
32
+ const percent = Math.min(100, Math.max(0, (value / max) * 100));
33
+ return (_jsxs("div", { ...componentRoot("ProgressBar"), className: cx("m-progress", className), "data-tone": tone, children: [_jsxs("div", { className: "m-progress-label", children: [_jsx("span", { id: labelId, children: label }), valueLabel && _jsx("span", { "aria-hidden": "true", children: valueLabel })] }), _jsx("div", { className: "m-progress-track", role: "progressbar", "aria-labelledby": labelId, "aria-valuemin": 0, "aria-valuemax": max, "aria-valuenow": value, "aria-valuetext": valueLabel, children: _jsx("span", { style: { width: `${percent}%` } }) })] }));
34
+ }
35
+ export function FilterChip({ label, selected = false, onClick, onRemove, className, }) {
36
+ if (onRemove)
37
+ return (_jsxs("span", { ...componentRoot("FilterChip"), className: cx("m-chip", className), "data-selected": "true", children: [label, _jsx("button", { type: "button", onClick: (event) => {
38
+ const chip = event.currentTarget.parentElement;
39
+ if (chip)
40
+ focusAfterRemoval(chip);
41
+ onRemove();
42
+ }, "aria-label": `Remove ${typeof label === "string" ? label : "filter"}`, children: _jsx(Close, {}) })] }));
43
+ return (_jsxs("button", { type: "button", ...componentRoot("FilterChip"), className: cx("m-chip", className), "aria-pressed": selected, "data-selected": selected || undefined, onClick: onClick, children: [!selected && _jsx("span", { "aria-hidden": "true", children: "+" }), label] }));
44
+ }
45
+ /** Feature or inclusion list with a check marker; replaces ad-hoc list-disc styling. */
46
+ export function CheckList({ items, label, columns = 1, size = "md", className, }) {
47
+ return (_jsx("ul", { ...componentRoot("CheckList"), className: cx("m-check-list", className), "aria-label": label, "data-columns": columns === 1 ? undefined : columns, "data-size": size === "md" ? undefined : size, children: items.map((item, i) => (_jsxs("li", { children: [_jsx(Check, { variant: "list" }), _jsx("span", { children: item })] }, i))) }));
48
+ }
49
+ /** Label/value pairs for detail screens, stacked on narrow screens and in columns when wide. */
50
+ export function DescriptionList({ title, description, headingLevel = 2, items, layout = "columns", variant = "plain", id, className, }) {
51
+ return (_jsxs("div", { id: id, ...componentRoot("DescriptionList"), className: cx("m-description-list", className), "data-layout": layout === "columns" ? undefined : layout, "data-variant": variant === "plain" ? undefined : variant, children: [title && (_jsxs("div", { className: "m-description-heading", children: [...classedIntroSlots({
52
+ title,
53
+ description,
54
+ headingLevel,
55
+ descriptionClassName: null,
56
+ }, "m-description-title")] })), _jsx(TermList, { items: items.map((item) => ({
57
+ term: item.term,
58
+ details: item.details,
59
+ attributes: { "data-action": item.action ? true : undefined },
60
+ after: item.action && (_jsx("dd", { className: "m-description-action", children: item.action })),
61
+ })) })] }));
62
+ }
63
+ /**
64
+ * Responsive grid of cards for people, projects or files. A card's title link
65
+ * covers the card; links and buttons in `media`, `meta`, `details` and
66
+ * `actions` sit above it and stay clickable.
67
+ */
68
+ export function GridList({ label, items, columns = 3, itemHeadingLevel = 3, actionsLayout = "inline", className, }) {
69
+ const Heading = headingTag(itemHeadingLevel);
70
+ return (_jsx("ul", { ...componentRoot("GridList"), className: cx("m-grid-list", className), "aria-label": label, "data-columns": columns === 3 ? undefined : columns, "data-actions": actionsLayout === "split" ? "split" : undefined, children: items.map((item) => (_jsxs("li", { children: [item.cover && (_jsx("div", { className: "m-grid-list-cover", children: _jsx("img", { src: item.cover.src, alt: item.cover.alt, loading: "lazy" }) })), _jsxs("div", { className: "m-grid-list-body", children: [item.media && (_jsx("span", { className: "m-grid-list-media", children: item.media })), _jsxs("div", { className: "m-grid-list-text", children: [_jsx(Heading, { className: "m-grid-list-title", children: item.href ? (_jsx(KitLink, { href: item.href, children: item.title })) : (item.title) }), item.description && _jsx("p", { children: item.description }), item.meta && _jsx("div", { className: "m-grid-list-meta", children: item.meta })] })] }), item.details && item.details.length > 0 && (_jsx(TermList, { className: "m-grid-list-details", items: item.details })), item.actions && (_jsx("div", { className: "m-grid-list-actions", children: item.actions }))] }, item.id))) }));
71
+ }
72
+ /** Files attached to a record, each with a size and a named download link. */
73
+ export function AttachmentList({ files, label = "Attachments", className, }) {
74
+ return (_jsx("ul", { ...componentRoot("AttachmentList"), className: cx("m-attachments", className), "aria-label": label, children: files.map((file) => (_jsxs("li", { children: [_jsx("span", { className: "m-attachment-icon", "aria-hidden": "true", children: file.icon ?? _jsx(Paperclip, {}) }), _jsxs("span", { className: "m-attachment-text", children: [_jsx("span", { className: "m-attachment-name", children: file.name }), file.size && (_jsx("span", { className: "m-attachment-size", children: file.size }))] }), _jsxs(KitLink, { href: file.href, children: [file.actionLabel ?? "Download", _jsxs("span", { className: "m-sr-only", children: [" ", file.name] })] })] }, file.id))) }));
75
+ }
package/dist/docs.d.ts ADDED
@@ -0,0 +1,145 @@
1
+ import { type ReactNode } from "react";
2
+ import { type LinkAction, type Tone } from "./primitives.js";
3
+ import type { NavigationItem } from "./application.js";
4
+ import { type CommandItem } from "./workspace.js";
5
+ import type { ItemGroup, LinkItem } from "./navigation.js";
6
+ import { type HeadingLevel } from "./heading.js";
7
+ export interface DocsShellProps {
8
+ className?: string;
9
+ brand: string;
10
+ /** Logo node shown in place of the placeholder brand mark. */
11
+ logo?: ReactNode;
12
+ /** Use the available content width for explorers and workspaces. */
13
+ layout?: "article" | "wide";
14
+ navigationLabel?: string;
15
+ homeHref?: string;
16
+ version?: ReactNode;
17
+ sections?: Pick<LinkItem, "label" | "href" | "current">[];
18
+ /** Groups of pages; items show their icon and count, and children become disclosures. */
19
+ navigation: ItemGroup<NavigationItem>[];
20
+ toc?: {
21
+ id: string;
22
+ label: string;
23
+ current?: boolean;
24
+ }[];
25
+ tocLabel?: string;
26
+ tocFooter?: ReactNode;
27
+ search?: ReactNode;
28
+ actions?: ReactNode;
29
+ children: ReactNode;
30
+ }
31
+ export declare function DocsShell({ className, brand, logo, layout, navigationLabel, homeHref, version, sections, navigation, toc, tocLabel, tocFooter, search, actions, children, }: DocsShellProps): import("react").JSX.Element;
32
+ export declare function SearchTrigger({ className, onOpen, label, }: {
33
+ className?: string;
34
+ onOpen: () => void;
35
+ label?: string;
36
+ }): import("react").JSX.Element;
37
+ export declare function SearchDialog(props: {
38
+ open: boolean;
39
+ onClose: () => void;
40
+ query: string;
41
+ onQueryChange: (value: string) => void;
42
+ groups: ItemGroup<CommandItem>[];
43
+ attribution?: ReactNode;
44
+ }): import("react").JSX.Element;
45
+ export declare function Steps({ id, className, items, itemHeadingLevel, }: {
46
+ id?: string;
47
+ className?: string;
48
+ items: {
49
+ title: string;
50
+ id?: string;
51
+ description?: ReactNode;
52
+ content?: ReactNode;
53
+ }[];
54
+ /** Level of each step's title. */
55
+ itemHeadingLevel?: HeadingLevel;
56
+ }): import("react").JSX.Element;
57
+ export declare function Callout({ id, className, tone, title, children, }: {
58
+ id?: string;
59
+ className?: string;
60
+ /** neutral: a note. info: a highlighted note. success: a tip. warning and danger: a caution, e.g. a breaking change. */
61
+ tone?: Tone;
62
+ /** Visible lead-in that also names the note; defaults to the tone's word, e.g. "Warning". */
63
+ title?: string;
64
+ children: ReactNode;
65
+ }): import("react").JSX.Element;
66
+ export interface FileNode {
67
+ name: string;
68
+ note?: string;
69
+ highlighted?: boolean;
70
+ children?: FileNode[];
71
+ }
72
+ export declare function FileTree({ id, className, label, items, }: {
73
+ id?: string;
74
+ className?: string;
75
+ label: string;
76
+ items: FileNode[];
77
+ }): import("react").JSX.Element;
78
+ /** Dark code surface with optional language tabs, filename, line numbers and highlighted lines. */
79
+ export declare function CodeTabs({ id, className, tabs, filename, language, lineNumbers, highlightLines, prompt, }: {
80
+ id?: string;
81
+ className?: string;
82
+ tabs: {
83
+ id: string;
84
+ label: string;
85
+ code: string;
86
+ }[];
87
+ filename?: string;
88
+ language?: string;
89
+ lineNumbers?: boolean;
90
+ highlightLines?: number[];
91
+ prompt?: string;
92
+ }): import("react").JSX.Element | null;
93
+ export declare function Release({ id, className, version, date, dateLabel, changes, headingLevel, }: {
94
+ id?: string;
95
+ className?: string;
96
+ /** Level of the version heading; change groups sit one level below. */
97
+ headingLevel?: HeadingLevel;
98
+ version: string;
99
+ date: string;
100
+ dateLabel: string;
101
+ changes: {
102
+ type: "added" | "changed" | "fixed" | "deprecated" | "removed";
103
+ items: ReactNode[];
104
+ }[];
105
+ }): import("react").JSX.Element;
106
+ export declare function ApiEndpoint({ id, className, headingLevel, method, path, title, description, paramsLabel, params, examples, }: {
107
+ id?: string;
108
+ className?: string;
109
+ /** Level of the endpoint title; the parameters heading sits one level below. */
110
+ headingLevel?: HeadingLevel;
111
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
112
+ path: string;
113
+ title: string;
114
+ description?: ReactNode;
115
+ paramsLabel?: string;
116
+ params: {
117
+ name: string;
118
+ type: string;
119
+ required?: boolean;
120
+ description: ReactNode;
121
+ }[];
122
+ /** Request/response samples, typically CodeTabs. */
123
+ examples?: ReactNode;
124
+ }): import("react").JSX.Element;
125
+ export declare function PageFeedback({ id, className, question, value, onChange, updated, }: {
126
+ id?: string;
127
+ className?: string;
128
+ question?: string;
129
+ value: "yes" | "no" | null;
130
+ onChange: (value: "yes" | "no") => void;
131
+ updated?: ReactNode;
132
+ }): import("react").JSX.Element;
133
+ export declare function PagerLinks({ id, className, previous, next, label, }: {
134
+ id?: string;
135
+ className?: string;
136
+ previous?: LinkAction;
137
+ next?: LinkAction;
138
+ /** Names the navigation landmark; keep it distinct from other navigation on the page. */
139
+ label?: string;
140
+ }): import("react").JSX.Element;
141
+ export declare function LinkCards({ id, className, items, }: {
142
+ id?: string;
143
+ className?: string;
144
+ items: Pick<LinkItem, "label" | "href" | "description">[];
145
+ }): import("react").JSX.Element;
package/dist/docs.js ADDED
@@ -0,0 +1,141 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { KitLink } from "./navigation.js";
3
+ import { useEffect, useId, useState } from "react";
4
+ import { Button } from "./primitives.js";
5
+ import { CommandPalette } from "./workspace.js";
6
+ import { Kbd } from "./display.js";
7
+ import { BrandMark } from "./layout.js";
8
+ import { childLevel, headingTag } from "./heading.js";
9
+ import { AsComponentRoot, MobileNavDialog, NavGroups, TermList, cx, componentRoot, rovingIndex, } from "./internal.js";
10
+ export function DocsShell({ className, brand, logo, layout = "article", navigationLabel = "Documentation", homeHref = "/", version, sections = [], navigation, toc = [], tocLabel = "On this page", tocFooter, search, actions, children, }) {
11
+ const [open, setOpen] = useState(false);
12
+ const contentId = useId();
13
+ const tree = (_jsx("nav", { "aria-label": navigationLabel, children: _jsx(NavGroups, { groups: navigation, onNavigate: () => setOpen(false) }) }));
14
+ return (_jsxs("div", { ...componentRoot("DocsShell"), className: cx("m-docs-shell", className), "data-layout": layout === "article" ? undefined : layout, children: [_jsx(KitLink, { className: "m-skip", href: `#${contentId}`, children: "Skip to content" }), _jsxs("header", { className: "m-docs-header", children: [_jsx(Button, { variant: "ghost", className: "m-menu-trigger", onClick: () => setOpen(true), "aria-haspopup": "dialog", "aria-expanded": open, children: "Menu" }), _jsxs(KitLink, { className: "m-brand", href: homeHref, children: [_jsx(BrandMark, { logo: logo }), brand] }), version && _jsx("div", { className: "m-docs-version", children: version }), sections.length > 0 && (_jsx("nav", { "aria-label": "Sections", className: "m-docs-sections", children: sections.map((s, index) => (_jsx(KitLink, { href: s.href, "aria-current": s.current ? "page" : undefined, children: s.label }, `${s.label}:${s.href}:${index}`))) })), _jsxs("div", { className: "m-actions", children: [search, actions] })] }), _jsxs("div", { className: "m-docs-body", children: [_jsx("aside", { className: "m-docs-nav", children: tree }), _jsx("main", { id: contentId, tabIndex: -1, children: children }), toc.length > 0 && (_jsxs("aside", { className: "m-docs-toc", children: [_jsxs("nav", { "aria-label": tocLabel, children: [_jsx("h2", { children: tocLabel }), toc.map((t) => (_jsx(KitLink, { href: `#${t.id}`, "aria-current": t.current ? "location" : undefined, children: t.label }, t.id)))] }), tocFooter && _jsx("div", { className: "m-docs-toc-footer", children: tocFooter })] }))] }), _jsx(MobileNavDialog, { open: open, onClose: () => setOpen(false), title: brand, children: tree })] }));
15
+ }
16
+ /** True when a key press belongs to a text field or an open dialog, not the page. */
17
+ function ownedKeyPress(target) {
18
+ if (!(target instanceof Element))
19
+ return false;
20
+ return Boolean(target.closest('input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role="dialog"], [role="alertdialog"], dialog[open]'));
21
+ }
22
+ export function SearchTrigger({ className, onOpen, label = "Search docs", }) {
23
+ // The shortcut hint is platform-specific; it renders after mount so server
24
+ // and client markup agree.
25
+ const [apple, setApple] = useState();
26
+ useEffect(() => {
27
+ setApple(/Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent));
28
+ }, []);
29
+ useEffect(() => {
30
+ const onKey = (e) => {
31
+ if (!(e.metaKey || e.ctrlKey) ||
32
+ e.altKey ||
33
+ e.shiftKey ||
34
+ e.key.toLowerCase() !== "k" ||
35
+ e.repeat ||
36
+ // Another trigger on the page already handled this press.
37
+ e.defaultPrevented ||
38
+ ownedKeyPress(e.target))
39
+ return;
40
+ e.preventDefault();
41
+ onOpen();
42
+ };
43
+ window.addEventListener("keydown", onKey);
44
+ return () => window.removeEventListener("keydown", onKey);
45
+ }, [onOpen]);
46
+ return (_jsxs("button", { type: "button", ...componentRoot("SearchTrigger"), className: cx("m-search-trigger", className), onClick: onOpen, "aria-keyshortcuts": "Meta+K Control+K", children: [_jsx("span", { children: label }), apple !== undefined && _jsx(Kbd, { keys: [apple ? "⌘K" : "Ctrl K"] })] }));
47
+ }
48
+ export function SearchDialog(props) {
49
+ return (_jsx(AsComponentRoot, { name: "SearchDialog", of: "CommandPalette", children: _jsx(CommandPalette, { ...props, label: "Search documentation", placeholder: "Search docs", footer: _jsxs(_Fragment, { children: [_jsxs("span", { children: [_jsx(Kbd, { keys: ["↑", "↓"] }), " navigate"] }), _jsxs("span", { children: [_jsx(Kbd, { keys: ["↵"] }), " open"] }), props.attribution && (_jsx("span", { className: "m-palette-attribution", children: props.attribution }))] }) }) }));
50
+ }
51
+ export function Steps({ id, className, items, itemHeadingLevel = 3, }) {
52
+ const Heading = headingTag(itemHeadingLevel);
53
+ return (_jsx("ol", { id: id, ...componentRoot("Steps"), className: cx("m-steps", className), children: items.map((item, i) => (_jsxs("li", { children: [_jsx("span", { className: "m-step-number", "aria-hidden": "true", children: i + 1 }), _jsxs("div", { children: [_jsx(Heading, { id: item.id, children: item.title }), item.description && (_jsx("div", { className: "m-step-description", children: item.description })), item.content] })] }, `${item.title}:${i}`))) }));
54
+ }
55
+ const calloutLabel = {
56
+ neutral: "Note",
57
+ info: "Note",
58
+ success: "Tip",
59
+ warning: "Warning",
60
+ danger: "Caution",
61
+ };
62
+ export function Callout({ id, className, tone = "neutral", title, children, }) {
63
+ const labelId = useId();
64
+ return (_jsxs("div", { id: id, ...componentRoot("Callout"), className: cx("m-callout", className), role: "note", "aria-labelledby": labelId, "data-tone": tone === "neutral" ? undefined : tone, children: [_jsx("strong", { id: labelId, children: title || calloutLabel[tone] }), _jsx("span", { "aria-hidden": "true", children: " \u00B7 " }), _jsx("div", { children: children })] }));
65
+ }
66
+ function FileBranch({ nodes }) {
67
+ return (_jsx("ul", { children: nodes.map((node) => (_jsxs("li", { "data-folder": !!node.children || undefined, "data-highlighted": node.highlighted || undefined, children: [_jsxs("span", { className: "m-file-row", children: [_jsx("span", { "aria-hidden": "true", className: "m-file-caret", children: node.children ? "▾" : "" }), _jsx("span", { children: node.name }), node.note && _jsx("small", { children: node.note })] }), node.children && _jsx(FileBranch, { nodes: node.children })] }, node.name))) }));
68
+ }
69
+ export function FileTree({ id, className, label, items, }) {
70
+ return (_jsx("figure", { id: id, ...componentRoot("FileTree"), className: cx("m-file-tree", className), "aria-label": label, children: _jsx(FileBranch, { nodes: items }) }));
71
+ }
72
+ function CopyButton({ text }) {
73
+ const [result, setResult] = useState();
74
+ useEffect(() => {
75
+ if (!result)
76
+ return;
77
+ const t = setTimeout(() => setResult(undefined), 1600);
78
+ return () => clearTimeout(t);
79
+ }, [result]);
80
+ const copy = async () => {
81
+ try {
82
+ // A missing API (insecure context) and a denied permission both fail.
83
+ if (!navigator.clipboard)
84
+ throw Error("Clipboard unavailable");
85
+ await navigator.clipboard.writeText(text);
86
+ setResult("copied");
87
+ }
88
+ catch {
89
+ setResult("failed");
90
+ }
91
+ };
92
+ return (_jsx("button", { type: "button", className: "m-code-copy", "data-result": result, onClick: copy, children: _jsx("span", { "aria-live": "polite", children: result === "copied"
93
+ ? "Copied"
94
+ : result === "failed"
95
+ ? "Copy failed"
96
+ : "Copy" }) }));
97
+ }
98
+ /** Dark code surface with optional language tabs, filename, line numbers and highlighted lines. */
99
+ export function CodeTabs({ id, className, tabs, filename, language, lineNumbers = false, highlightLines = [], prompt, }) {
100
+ const [active, setActive] = useState(tabs[0]?.id);
101
+ const base = useId();
102
+ const current = tabs.find((t) => t.id === active) || tabs[0];
103
+ if (!current)
104
+ return null;
105
+ const lines = current.code.split("\n");
106
+ const tabId = (tab) => `${base}-tab-${tab.id}`;
107
+ const panelId = `${base}-panel`;
108
+ return (_jsxs("figure", { id: id, ...componentRoot("CodeTabs"), className: cx("m-code-tabs", className), children: [_jsxs("div", { className: "m-code-bar", children: [tabs.length > 1 ? (_jsx("div", { role: "tablist", "aria-label": filename || "Code examples", children: tabs.map((t) => (_jsx("button", { type: "button", role: "tab", id: tabId(t), "aria-selected": t.id === current.id, "aria-controls": panelId, tabIndex: t.id === current.id ? 0 : -1, onClick: () => setActive(t.id), onKeyDown: (e) => {
109
+ const n = rovingIndex(e.key, tabs.findIndex((x) => x.id === current.id), tabs.length);
110
+ if (n === null)
111
+ return;
112
+ e.preventDefault();
113
+ const next = tabs[n];
114
+ setActive(next.id);
115
+ document.getElementById(tabId(next))?.focus();
116
+ }, children: t.label }, t.id))) })) : (_jsx("figcaption", { children: filename || current.label })), language && _jsx("span", { className: "m-code-language", children: language }), _jsx(CopyButton, { text: current.code })] }), _jsx("pre", { tabIndex: 0, id: tabs.length > 1 ? panelId : undefined, role: tabs.length > 1 ? "tabpanel" : undefined, "aria-labelledby": tabs.length > 1 ? tabId(current) : undefined, children: _jsx("code", { children: lines.map((line, i) => (_jsxs("span", { className: "m-code-line", "data-highlighted": highlightLines.includes(i + 1) || undefined, children: [lineNumbers && (_jsx("span", { className: "m-code-number", "aria-hidden": "true", children: i + 1 })), prompt && (_jsxs("span", { className: "m-code-prompt", "aria-hidden": "true", children: [prompt, " "] })), line || " "] }, i))) }) })] }));
117
+ }
118
+ export function Release({ id, className, version, date, dateLabel, changes, headingLevel = 3, }) {
119
+ const Heading = headingTag(headingLevel);
120
+ const GroupHeading = headingTag(childLevel(headingLevel));
121
+ return (_jsxs("article", { id: id, ...componentRoot("Release"), className: cx("m-release", className), children: [_jsxs("header", { children: [_jsx(Heading, { className: "m-release-version", children: version }), _jsx("time", { dateTime: date, children: dateLabel })] }), _jsx("div", { children: changes.map((c) => (_jsxs("section", { "data-type": c.type, children: [_jsx(GroupHeading, { className: "m-release-type", children: c.type.charAt(0).toUpperCase() + c.type.slice(1) }), _jsx("ul", { children: c.items.map((item, i) => (_jsx("li", { children: item }, i))) })] }, c.type))) })] }));
122
+ }
123
+ export function ApiEndpoint({ id, className, headingLevel = 2, method, path, title, description, paramsLabel = "Body parameters", params, examples, }) {
124
+ const headingId = useId();
125
+ const Heading = headingTag(headingLevel);
126
+ const ParamsHeading = headingTag(childLevel(headingLevel));
127
+ return (_jsxs("section", { id: id, ...componentRoot("ApiEndpoint"), className: cx("m-api-endpoint", className), "aria-labelledby": headingId, children: [_jsxs("div", { className: "m-api-text", children: [_jsxs("p", { className: "m-api-route", children: [_jsx("span", { "data-method": method, children: method }), _jsx("code", { children: path })] }), _jsx(Heading, { id: headingId, className: "m-api-title", children: title }), description && _jsx("div", { className: "m-api-description", children: description }), _jsx(ParamsHeading, { className: "m-api-params-title", children: paramsLabel }), _jsx(TermList, { className: "m-param-list", items: params.map((p) => ({
128
+ key: p.name,
129
+ term: (_jsxs(_Fragment, { children: [_jsx("code", { children: p.name }), _jsx("span", { children: p.type }), _jsx("span", { "data-required": p.required || undefined, children: p.required ? "Required" : "Optional" })] })),
130
+ details: p.description,
131
+ })) })] }), examples && _jsx("div", { className: "m-api-examples", children: examples })] }));
132
+ }
133
+ export function PageFeedback({ id, className, question = "Was this page helpful?", value, onChange, updated, }) {
134
+ return (_jsxs("div", { id: id, ...componentRoot("PageFeedback"), className: cx("m-page-feedback", className), children: [_jsxs("div", { role: "group", "aria-label": question, children: [_jsx("span", { children: question }), ["yes", "no"].map((v) => (_jsx("button", { type: "button", "aria-pressed": value === v, onClick: () => onChange(v), children: v === "yes" ? "Yes" : "No" }, v)))] }), updated && _jsx("span", { children: updated })] }));
135
+ }
136
+ export function PagerLinks({ id, className, previous, next, label = "Previous and next page", }) {
137
+ return (_jsxs("nav", { id: id, ...componentRoot("PagerLinks"), className: cx("m-pager", className), "aria-label": label, children: [previous ? (_jsxs(KitLink, { href: previous.href, rel: "prev", children: [_jsx("small", { children: "Previous" }), _jsxs("span", { children: [_jsx("span", { "aria-hidden": "true", children: "\u2190 " }), previous.label] })] })) : (_jsx("span", {})), next && (_jsxs(KitLink, { href: next.href, rel: "next", "data-next": "true", children: [_jsx("small", { children: "Next" }), _jsxs("span", { children: [next.label, _jsx("span", { "aria-hidden": "true", children: " \u2192" })] })] }))] }));
138
+ }
139
+ export function LinkCards({ id, className, items, }) {
140
+ return (_jsx("ul", { id: id, ...componentRoot("LinkCards"), className: cx("m-link-cards", className), children: items.map((item, index) => (_jsx("li", { children: _jsxs(KitLink, { href: item.href, children: [_jsxs("strong", { children: [item.label, _jsx("span", { "aria-hidden": "true", children: " \u2192" })] }), item.description && _jsx("span", { children: item.description })] }) }, `${item.label}:${item.href}:${index}`))) }));
141
+ }
@@ -0,0 +1,261 @@
1
+ import { type ReactNode } from "react";
2
+ import { type ProgressStep } from "./application.js";
3
+ import { type HeadingLevel } from "./heading.js";
4
+ export interface ProductImage {
5
+ src: string;
6
+ /** Describe the product shown; images are the main product information. */
7
+ alt: string;
8
+ }
9
+ export interface ProductCard {
10
+ id: string;
11
+ name: string;
12
+ href: string;
13
+ /** Preformatted price, e.g. "£32" or "From £18". */
14
+ price: string;
15
+ image: ProductImage;
16
+ /** Colour, variant or category shown under the name. */
17
+ meta?: string;
18
+ badge?: string;
19
+ /** Supporting text under the name, e.g. in the card variant. */
20
+ description?: string;
21
+ /** Available colours as data: `swatch` is any CSS colour the consumer supplies; the label is announced. */
22
+ swatches?: {
23
+ label: string;
24
+ swatch: string;
25
+ }[];
26
+ /** Control under the card, e.g. an "Add to bag" button; it sits above the card link. */
27
+ action?: ReactNode;
28
+ }
29
+ export interface ProductListProps {
30
+ /** Accessible name of the list. */
31
+ label: string;
32
+ products: ProductCard[];
33
+ columns?: 2 | 3 | 4;
34
+ /** Heading level of each product name (default 3). */
35
+ itemHeadingLevel?: HeadingLevel;
36
+ /**
37
+ * `cards` frames each product with a border, for listings with full
38
+ * details; `overlay` sets the name and price on a panel over the image.
39
+ */
40
+ variant?: "plain" | "cards" | "overlay";
41
+ id?: string;
42
+ className?: string;
43
+ }
44
+ /** Grid of product cards; each card's name link covers the card. */
45
+ export declare function ProductList({ label, products, columns, itemHeadingLevel, variant, id, className, }: ProductListProps): import("react").JSX.Element;
46
+ export interface ProductOption {
47
+ id: string;
48
+ label: string;
49
+ values: {
50
+ value: string;
51
+ label: string;
52
+ /** CSS colour for a swatch; the label remains the accessible name. */
53
+ swatch?: string;
54
+ disabled?: boolean;
55
+ }[];
56
+ }
57
+ export interface ProductOverviewProps {
58
+ name: string;
59
+ price: string;
60
+ description?: ReactNode;
61
+ images: ProductImage[];
62
+ rating?: {
63
+ value: number;
64
+ count: number;
65
+ href?: string;
66
+ };
67
+ options?: ProductOption[];
68
+ /** Selected value per option id. */
69
+ selected?: Record<string, string>;
70
+ onSelect?: (option: string, value: string) => void;
71
+ /** Called when the add form is submitted (button click or Enter). */
72
+ onAdd?: () => void;
73
+ addLabel?: string;
74
+ /** Shows the add button as busy while the item is being added. */
75
+ loading?: boolean;
76
+ details?: string[];
77
+ /** Level of the product name heading (default 1: the product page's title). */
78
+ headingLevel?: HeadingLevel;
79
+ /** Omit the name heading when a container (e.g. a dialog title) already shows it. */
80
+ hideName?: boolean;
81
+ /** `grid` shows every image at once: the first large, the rest tiered beneath it. */
82
+ gallery?: "thumbnails" | "grid";
83
+ id?: string;
84
+ className?: string;
85
+ }
86
+ /** Product page body: gallery, price, rating, option pickers, add to bag and details. */
87
+ export declare function ProductOverview({ name, price, description, images, rating, options, selected, onSelect, onAdd, addLabel, loading, details, headingLevel, hideName, gallery, id, className, }: ProductOverviewProps): import("react").JSX.Element;
88
+ export interface CartItem {
89
+ id: string;
90
+ name: string;
91
+ href: string;
92
+ price: string;
93
+ quantity: number;
94
+ image: ProductImage;
95
+ /** Chosen options, e.g. "Natural · Small". */
96
+ meta?: string;
97
+ /** Availability note, e.g. "In stock" or "Ships in 3 weeks". */
98
+ stock?: string;
99
+ }
100
+ /** Content of an empty state: a title, an optional description and action. */
101
+ export interface EmptyContent {
102
+ title: string;
103
+ description?: string;
104
+ action?: ReactNode;
105
+ }
106
+ export interface ShoppingCartProps {
107
+ items: CartItem[];
108
+ onQuantityChange: (id: string, quantity: number) => void;
109
+ onRemove: (id: string) => void;
110
+ /** Accessible name of the list of lines. */
111
+ label?: string;
112
+ maxQuantity?: number;
113
+ /** Level of each line's product name, and of the empty state's title (default 3). */
114
+ headingLevel?: HeadingLevel;
115
+ /** Shown when there are no items. */
116
+ empty?: EmptyContent;
117
+ /**
118
+ * Beside the lines when wide and below them when narrow, e.g. an
119
+ * OrderSummary with shipping and tax estimates and the checkout action.
120
+ */
121
+ summary?: ReactNode;
122
+ /**
123
+ * Products to suggest under the bag, shown as a ProductList with a heading
124
+ * (default level 2; product names one level below). Also shown when the
125
+ * bag is empty.
126
+ */
127
+ related?: {
128
+ title: string;
129
+ products: ProductCard[];
130
+ headingLevel?: HeadingLevel;
131
+ };
132
+ id?: string;
133
+ className?: string;
134
+ }
135
+ /**
136
+ * Bag contents with per-line quantity and remove controls. After a removal,
137
+ * focus moves to the next line's Remove button (the previous one after the
138
+ * last line), or to the empty state once the bag is empty.
139
+ */
140
+ export declare function ShoppingCart({ summary, related, id, className, ...props }: ShoppingCartProps): import("react").JSX.Element;
141
+ /** Cost breakdown with a distinguished total and a checkout action. */
142
+ export declare function OrderSummary({ title, lines, total, action, headingLevel, children, id, className, }: {
143
+ title?: string;
144
+ /**
145
+ * Cost lines. `hint` explains an estimate under its label, e.g. "Calculated
146
+ * at checkout" or a link to how shipping is worked out.
147
+ */
148
+ lines: {
149
+ label: string;
150
+ value: string;
151
+ hint?: ReactNode;
152
+ }[];
153
+ total: {
154
+ label: string;
155
+ value: string;
156
+ };
157
+ action?: ReactNode;
158
+ headingLevel?: HeadingLevel;
159
+ /** Notes such as a discount code field or delivery estimate. */
160
+ children?: ReactNode;
161
+ id?: string;
162
+ className?: string;
163
+ }): import("react").JSX.Element;
164
+ /** Product overview in a dialog, with a link to the full product page. */
165
+ export declare function ProductQuickView({ open, onClose, href, detailsLabel, className, ...product }: Omit<ProductOverviewProps, "headingLevel" | "hideName" | "id" | "className"> & {
166
+ open: boolean;
167
+ onClose: () => void;
168
+ /** Full product page. */
169
+ href: string;
170
+ detailsLabel?: string;
171
+ /** Class on the dialog's content wrapper. */
172
+ className?: string;
173
+ }): import("react").JSX.Element;
174
+ export interface FilterFacet {
175
+ id: string;
176
+ label: string;
177
+ options: {
178
+ value: string;
179
+ label: string;
180
+ count?: number;
181
+ }[];
182
+ }
183
+ /**
184
+ * Collapsible checkbox facets with counts, plus removable chips for active
185
+ * filters. Every change, Clear all included, calls `onChange` once with the
186
+ * complete next selection.
187
+ */
188
+ export declare function ProductFilters({ facets, selected, onChange, label, id, className, }: {
189
+ facets: FilterFacet[];
190
+ /** Selected values per facet id. */
191
+ selected: Record<string, string[]>;
192
+ /** The complete next selection, keyed by facet id. */
193
+ onChange: (selected: Record<string, string[]>) => void;
194
+ label?: string;
195
+ id?: string;
196
+ className?: string;
197
+ }): import("react").JSX.Element;
198
+ export interface Review {
199
+ id: string;
200
+ author: string;
201
+ rating: number;
202
+ date: string;
203
+ title: string;
204
+ body: ReactNode;
205
+ /** Reviewer's photo, shown with `avatars`; without it the Avatar shows initials. */
206
+ avatar?: string;
207
+ }
208
+ /** Rating summary with a star distribution, followed by individual reviews. */
209
+ export declare function ProductReviews({ title, average, count, distribution, reviews, action, avatars, headingLevel, id, className, }: {
210
+ title?: string;
211
+ average: number;
212
+ count: number;
213
+ distribution: {
214
+ stars: number;
215
+ count: number;
216
+ }[];
217
+ reviews: Review[];
218
+ /** E.g. a "Write a review" button. */
219
+ action?: ReactNode;
220
+ /** Shows each reviewer's Avatar (their `avatar` photo or initials) beside their name. */
221
+ avatars?: boolean;
222
+ /** Level of the title; each review's title is one level below. */
223
+ headingLevel?: HeadingLevel;
224
+ id?: string;
225
+ className?: string;
226
+ }): import("react").JSX.Element;
227
+ export interface Order {
228
+ id: string;
229
+ number: string;
230
+ date: string;
231
+ total: string;
232
+ status: string;
233
+ href: string;
234
+ /** Order-level controls in the header, e.g. "View invoice" or an actions menu. */
235
+ actions?: ReactNode;
236
+ /**
237
+ * Delivery stages shown as a tracker under the status, e.g. Order placed,
238
+ * Processing, Shipped and Delivered, with the latest stage `current`.
239
+ */
240
+ progress?: ProgressStep[];
241
+ items: {
242
+ id: string;
243
+ name: string;
244
+ image: ProductImage;
245
+ quantity: number;
246
+ price: string;
247
+ href?: string;
248
+ /** Per-item controls, e.g. "Buy again" and "View product". */
249
+ actions?: ReactNode;
250
+ }[];
251
+ }
252
+ /** Past orders, each with its summary, status and line items. */
253
+ export declare function OrderHistory({ orders, headingLevel, empty, id, className, }: {
254
+ orders: Order[];
255
+ /** Level of each order's heading, and of the empty state's title (default 2). */
256
+ headingLevel?: HeadingLevel;
257
+ /** Shown when there are no orders. */
258
+ empty?: EmptyContent;
259
+ id?: string;
260
+ className?: string;
261
+ }): import("react").JSX.Element;