@tonycodes-ui/mortar 0.0.0-stage → 0.11.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/AGENTS.md +10 -0
- package/CHANGELOG.md +276 -0
- package/CONTRIBUTING.md +48 -0
- package/DESIGN.md +51 -0
- package/LICENSE +21 -0
- package/README.md +155 -2
- package/TESTING.md +83 -0
- package/baseline-policy.json +16 -0
- package/bin/mortar.mjs +258 -0
- package/catalog.json +9703 -0
- package/cli/audit.mjs +943 -0
- package/cli/data.mjs +176 -0
- package/cli/mcp.mjs +216 -0
- package/cli/migrations.mjs +128 -0
- package/cli/packet.mjs +267 -0
- package/cli/search.mjs +246 -0
- package/cli/tools.mjs +229 -0
- package/design-principles.json +30 -0
- package/dist/agent-data.json +1 -0
- package/dist/application.d.ts +340 -0
- package/dist/application.js +240 -0
- package/dist/auth.d.ts +121 -0
- package/dist/auth.js +97 -0
- package/dist/brand.d.ts +69 -0
- package/dist/brand.js +266 -0
- package/dist/calendar.d.ts +75 -0
- package/dist/calendar.js +310 -0
- package/dist/choice.d.ts +36 -0
- package/dist/choice.js +36 -0
- package/dist/composer.d.ts +28 -0
- package/dist/composer.js +47 -0
- package/dist/display.d.ts +136 -0
- package/dist/display.js +75 -0
- package/dist/docs.d.ts +145 -0
- package/dist/docs.js +141 -0
- package/dist/ecommerce.d.ts +261 -0
- package/dist/ecommerce.js +177 -0
- package/dist/feedback.d.ts +80 -0
- package/dist/feedback.js +98 -0
- package/dist/foundation.d.ts +42 -0
- package/dist/foundation.js +121 -0
- package/dist/heading.d.ts +5 -0
- package/dist/heading.js +9 -0
- package/dist/icons.d.ts +50 -0
- package/dist/icons.js +70 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +17 -0
- package/dist/inspection.d.ts +24 -0
- package/dist/inspection.js +14 -0
- package/dist/internal.d.ts +492 -0
- package/dist/internal.js +273 -0
- package/dist/layout.d.ts +107 -0
- package/dist/layout.js +66 -0
- package/dist/marketing-sections.d.ts +161 -0
- package/dist/marketing-sections.js +58 -0
- package/dist/marketing.d.ts +254 -0
- package/dist/marketing.js +150 -0
- package/dist/navigation.d.ts +35 -0
- package/dist/navigation.js +44 -0
- package/dist/primitives.d.ts +258 -0
- package/dist/primitives.js +244 -0
- package/dist/showcase.d.ts +29 -0
- package/dist/showcase.js +10 -0
- package/dist/workspace.d.ts +80 -0
- package/dist/workspace.js +48 -0
- package/docs/agents.md +127 -0
- package/docs/auth-flows.md +71 -0
- package/docs/migrating-to-0.6.md +94 -0
- package/docs/theming.md +170 -0
- package/package.json +94 -4
- package/skills/mortar/SKILL.md +36 -0
- package/src/application.css +1487 -0
- package/src/application.tsx +1413 -0
- package/src/auth.tsx +452 -0
- package/src/brand.ts +335 -0
- package/src/calendar.css +468 -0
- package/src/calendar.tsx +868 -0
- package/src/choice.tsx +240 -0
- package/src/composer.tsx +158 -0
- package/src/data.css +690 -0
- package/src/display.tsx +452 -0
- package/src/docs.css +704 -0
- package/src/docs.tsx +737 -0
- package/src/ecommerce.css +810 -0
- package/src/ecommerce.tsx +1033 -0
- package/src/feedback.tsx +388 -0
- package/src/foundation.css +1867 -0
- package/src/foundation.tsx +227 -0
- package/src/heading.ts +13 -0
- package/src/icons.tsx +237 -0
- package/src/index.ts +230 -0
- package/src/inspection.tsx +91 -0
- package/src/internal.tsx +614 -0
- package/src/layout.tsx +342 -0
- package/src/marketing-sections.tsx +621 -0
- package/src/marketing.css +2400 -0
- package/src/marketing.tsx +1156 -0
- package/src/navigation.tsx +118 -0
- package/src/overlays.css +350 -0
- package/src/primitives.tsx +1135 -0
- package/src/showcase.tsx +120 -0
- package/src/tokens.css +449 -0
- package/src/workspace.tsx +333 -0
- package/styles.css +56 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type InputHTMLAttributes, type SelectHTMLAttributes, type TextareaHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { type HeadingLevel } from "./heading.js";
|
|
3
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
4
|
+
/** accent: the brand's accent fill (its accent gradient when it defines one) with on-accent text. */
|
|
5
|
+
variant?: "primary" | "secondary" | "ghost" | "danger" | "accent";
|
|
6
|
+
/**
|
|
7
|
+
* Busy: shows a spinner, sets aria-busy and aria-disabled and ignores
|
|
8
|
+
* activation. The button stays focusable, so focus is never lost.
|
|
9
|
+
*/
|
|
10
|
+
loading?: boolean;
|
|
11
|
+
size?: "sm" | "md" | "lg";
|
|
12
|
+
/** Decorative icon; the text remains the accessible name. */
|
|
13
|
+
icon?: ReactNode;
|
|
14
|
+
iconPosition?: "start" | "end";
|
|
15
|
+
/** Show only the icon; the children become screen-reader-only text. */
|
|
16
|
+
iconOnly?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
19
|
+
export interface LinkAction {
|
|
20
|
+
label: string;
|
|
21
|
+
href: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function ActionLink({ label, href, variant, className, }: LinkAction & {
|
|
24
|
+
variant?: ButtonProps["variant"];
|
|
25
|
+
className?: string;
|
|
26
|
+
}): import("react").JSX.Element;
|
|
27
|
+
export interface ButtonGroupItem {
|
|
28
|
+
label: string;
|
|
29
|
+
/** Run the item's action. Use href instead for navigation. */
|
|
30
|
+
onSelect?: () => void;
|
|
31
|
+
href?: string;
|
|
32
|
+
icon?: ReactNode;
|
|
33
|
+
/** Show only the icon; the label remains the accessible name. */
|
|
34
|
+
iconOnly?: boolean;
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Joined row of related secondary actions. Use SegmentedControl to choose one value. */
|
|
38
|
+
export declare function ButtonGroup({ label, items, className, }: {
|
|
39
|
+
label: string;
|
|
40
|
+
items: ButtonGroupItem[];
|
|
41
|
+
className?: string;
|
|
42
|
+
}): import("react").JSX.Element;
|
|
43
|
+
export type Tone = "neutral" | "info" | "success" | "warning" | "danger";
|
|
44
|
+
export declare function Badge({ children, tone, className, }: {
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
tone?: Tone;
|
|
47
|
+
className?: string;
|
|
48
|
+
}): import("react").JSX.Element;
|
|
49
|
+
/** Label, hint and error shared by the text fields. Internal: not exported from the package entry. */
|
|
50
|
+
export interface FieldDescription {
|
|
51
|
+
label: string;
|
|
52
|
+
hint?: string;
|
|
53
|
+
/** Marks the field invalid; the message is announced when it appears. */
|
|
54
|
+
error?: string;
|
|
55
|
+
/** On the field's root element. */
|
|
56
|
+
className?: string;
|
|
57
|
+
}
|
|
58
|
+
/** The field label; a required field adds a decorative marker after the text. */
|
|
59
|
+
export declare function FieldLabel({ htmlFor, label, required, }: {
|
|
60
|
+
htmlFor: string;
|
|
61
|
+
label: string;
|
|
62
|
+
required?: boolean;
|
|
63
|
+
}): import("react").JSX.Element;
|
|
64
|
+
export interface InputProps extends InputHTMLAttributes<HTMLInputElement>, FieldDescription {
|
|
65
|
+
prefix?: string;
|
|
66
|
+
suffix?: string;
|
|
67
|
+
/** Decorative icon inside the field, before the text. */
|
|
68
|
+
leadingIcon?: ReactNode;
|
|
69
|
+
/** Decorative icon inside the field, after the text. */
|
|
70
|
+
trailingIcon?: ReactNode;
|
|
71
|
+
/** Button or menu attached to the end of the field, e.g. "Sort" or "Copy". */
|
|
72
|
+
action?: ReactNode;
|
|
73
|
+
}
|
|
74
|
+
export declare const Input: import("react").ForwardRefExoticComponent<InputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
75
|
+
export declare function Select({ label, options, id, hint, error, className, ...props }: SelectHTMLAttributes<HTMLSelectElement> & FieldDescription & {
|
|
76
|
+
options: {
|
|
77
|
+
value: string;
|
|
78
|
+
label: string;
|
|
79
|
+
}[];
|
|
80
|
+
}): import("react").JSX.Element;
|
|
81
|
+
/** Groups related fields under a legend; disabled disables every control inside. */
|
|
82
|
+
export declare function Fieldset({ legend, description, disabled, children, className, }: {
|
|
83
|
+
legend: string;
|
|
84
|
+
description?: string;
|
|
85
|
+
disabled?: boolean;
|
|
86
|
+
children: ReactNode;
|
|
87
|
+
className?: string;
|
|
88
|
+
}): import("react").JSX.Element;
|
|
89
|
+
export declare function TextArea({ label, id, hint, error, className, ...props }: TextareaHTMLAttributes<HTMLTextAreaElement> & FieldDescription): import("react").JSX.Element;
|
|
90
|
+
export declare function Checkbox({ label, description, error, className, ...props }: Omit<InputHTMLAttributes<HTMLInputElement>, "type"> & {
|
|
91
|
+
label: ReactNode;
|
|
92
|
+
/** Secondary line under the label, announced as the description (the checkbox's hint). */
|
|
93
|
+
description?: ReactNode;
|
|
94
|
+
/** Marks the checkbox invalid; the message is announced when it appears. */
|
|
95
|
+
error?: string;
|
|
96
|
+
}): import("react").JSX.Element;
|
|
97
|
+
export declare function Switch({ label, description, className, ...props }: Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "role"> & {
|
|
98
|
+
label: string;
|
|
99
|
+
/** Secondary line under the label, announced as the description. */
|
|
100
|
+
description?: ReactNode;
|
|
101
|
+
}): import("react").JSX.Element;
|
|
102
|
+
export declare function RadioGroup({ label, name, value, onChange, options, layout, hint, error, required, id, className, }: {
|
|
103
|
+
label: string;
|
|
104
|
+
name: string;
|
|
105
|
+
value: string;
|
|
106
|
+
onChange: (value: string) => void;
|
|
107
|
+
options: {
|
|
108
|
+
value: string;
|
|
109
|
+
label: string;
|
|
110
|
+
description?: string;
|
|
111
|
+
/** CSS colour for the swatches layout; the label stays the accessible name. */
|
|
112
|
+
swatch?: string;
|
|
113
|
+
disabled?: boolean;
|
|
114
|
+
}[];
|
|
115
|
+
/** Stacked cards, a card grid, or colour swatches. */
|
|
116
|
+
layout?: "list" | "grid" | "swatches";
|
|
117
|
+
/** Help under the legend, announced as the group's description. */
|
|
118
|
+
hint?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Marks the group invalid: the message under the legend is announced when
|
|
121
|
+
* it appears and describes the group, and each radio is aria-invalid.
|
|
122
|
+
*/
|
|
123
|
+
error?: string;
|
|
124
|
+
/** Marks every radio required and adds a decorative marker to the legend. */
|
|
125
|
+
required?: boolean;
|
|
126
|
+
/** On the fieldset, e.g. the target of an ErrorSummary link. */
|
|
127
|
+
id?: string;
|
|
128
|
+
className?: string;
|
|
129
|
+
}): import("react").JSX.Element;
|
|
130
|
+
export declare function Tabs({ label, value, onChange, items, variant, fullWidth, className, }: {
|
|
131
|
+
label: string;
|
|
132
|
+
/** The selected tab's id. A value matching no tab leaves the first tab in the tab order. */
|
|
133
|
+
value: string;
|
|
134
|
+
onChange: (value: string) => void;
|
|
135
|
+
/** Underlined tabs (default) or filled pills. */
|
|
136
|
+
variant?: "underline" | "pills";
|
|
137
|
+
/** Stretch the tabs to share the full width. */
|
|
138
|
+
fullWidth?: boolean;
|
|
139
|
+
items: {
|
|
140
|
+
id: string;
|
|
141
|
+
label: string;
|
|
142
|
+
content: ReactNode;
|
|
143
|
+
/** Shown after the label whenever it is defined, including 0. */
|
|
144
|
+
count?: number | string;
|
|
145
|
+
icon?: ReactNode;
|
|
146
|
+
}[];
|
|
147
|
+
className?: string;
|
|
148
|
+
}): import("react").JSX.Element;
|
|
149
|
+
export declare function Modal({ open, onClose, title, children, actions, size, className, }: {
|
|
150
|
+
open: boolean;
|
|
151
|
+
onClose: () => void;
|
|
152
|
+
title: string;
|
|
153
|
+
children: ReactNode;
|
|
154
|
+
actions?: ReactNode;
|
|
155
|
+
/** "lg" widens the panel for rich content such as a product quick view. */
|
|
156
|
+
size?: "md" | "lg";
|
|
157
|
+
/** On the dialog panel. */
|
|
158
|
+
className?: string;
|
|
159
|
+
}): import("react").JSX.Element;
|
|
160
|
+
export interface DropdownItem {
|
|
161
|
+
label: string;
|
|
162
|
+
/** Run an action. Use href instead for navigation. */
|
|
163
|
+
onSelect?: () => void;
|
|
164
|
+
href?: string;
|
|
165
|
+
icon?: ReactNode;
|
|
166
|
+
description?: string;
|
|
167
|
+
/** Items sharing a section are grouped under that heading, separated by a divider. */
|
|
168
|
+
section?: string;
|
|
169
|
+
danger?: boolean;
|
|
170
|
+
disabled?: boolean;
|
|
171
|
+
}
|
|
172
|
+
export declare function Dropdown({ label, items, icon, iconOnly, variant, align, header, className, }: {
|
|
173
|
+
/** Trigger text, and its accessible name when iconOnly. */
|
|
174
|
+
label: string;
|
|
175
|
+
items: DropdownItem[];
|
|
176
|
+
/** Trigger icon, e.g. a "more" ellipsis or an avatar. */
|
|
177
|
+
icon?: ReactNode;
|
|
178
|
+
iconOnly?: boolean;
|
|
179
|
+
variant?: "secondary" | "ghost";
|
|
180
|
+
align?: "start" | "end";
|
|
181
|
+
/** Content above the items, e.g. "Signed in as …". */
|
|
182
|
+
header?: ReactNode;
|
|
183
|
+
/** On the trigger button. */
|
|
184
|
+
className?: string;
|
|
185
|
+
}): import("react").JSX.Element;
|
|
186
|
+
export declare function Notice({ title, children, tone, announce, actions, onDismiss, dismissLabel, id, className, }: {
|
|
187
|
+
title: string;
|
|
188
|
+
children?: ReactNode;
|
|
189
|
+
tone?: Tone;
|
|
190
|
+
/**
|
|
191
|
+
* Announce the notice to assistive technology when it appears and when its
|
|
192
|
+
* title changes: politely, or assertively for the danger tone.
|
|
193
|
+
*/
|
|
194
|
+
announce?: boolean;
|
|
195
|
+
/** Links or buttons such as "View details". */
|
|
196
|
+
actions?: ReactNode;
|
|
197
|
+
/** Called by the dismiss button; focus moves to the next control on the page. */
|
|
198
|
+
onDismiss?: () => void;
|
|
199
|
+
/** Accessible name for the dismiss button; defaults to "Dismiss {title}". */
|
|
200
|
+
dismissLabel?: string;
|
|
201
|
+
id?: string;
|
|
202
|
+
className?: string;
|
|
203
|
+
}): import("react").JSX.Element;
|
|
204
|
+
/** One problem in an ErrorSummary. */
|
|
205
|
+
export interface ErrorSummaryItem {
|
|
206
|
+
/** The field's error, worded the same as the message under the field. */
|
|
207
|
+
message: string;
|
|
208
|
+
/** Link to the field; defaults to `#target`. Omit both for a form-level problem without a link. */
|
|
209
|
+
href?: string;
|
|
210
|
+
/**
|
|
211
|
+
* Id of the field the link focuses. Defaults to the id in an in-page
|
|
212
|
+
* `href`. For a group (a RadioGroup's fieldset, a CodeInput) the checked
|
|
213
|
+
* or first enabled control inside takes focus.
|
|
214
|
+
*/
|
|
215
|
+
target?: string;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The problems that stopped a form submitting, above the form, each linking
|
|
219
|
+
* to its field. It is an alert, and it takes focus when it appears and after
|
|
220
|
+
* every failed submit (`submitCount`), so keyboard and screen reader users
|
|
221
|
+
* start from the list. Following a link focuses the field without changing the
|
|
222
|
+
* URL. Renders nothing while `errors` is empty.
|
|
223
|
+
*/
|
|
224
|
+
export declare function ErrorSummary({ errors, title, headingLevel, autoFocus, submitCount, id, className, }: {
|
|
225
|
+
errors: ErrorSummaryItem[];
|
|
226
|
+
title?: string;
|
|
227
|
+
headingLevel?: HeadingLevel;
|
|
228
|
+
/**
|
|
229
|
+
* Move focus to the summary when it appears and whenever `submitCount`
|
|
230
|
+
* changes. Turn off only where focus must stay put; the alert still
|
|
231
|
+
* announces the summary.
|
|
232
|
+
*/
|
|
233
|
+
autoFocus?: boolean;
|
|
234
|
+
/**
|
|
235
|
+
* Count of failed submits. A change moves focus to the summary again even
|
|
236
|
+
* when the errors are the same; clearing a fixed field's error does not.
|
|
237
|
+
*/
|
|
238
|
+
submitCount?: number;
|
|
239
|
+
id?: string;
|
|
240
|
+
className?: string;
|
|
241
|
+
}): import("react").JSX.Element | null;
|
|
242
|
+
export declare function EmptyState({ title, description, action, icon, dashed, align, headingLevel, children, id, className, }: {
|
|
243
|
+
title: string;
|
|
244
|
+
description?: string;
|
|
245
|
+
action?: ReactNode;
|
|
246
|
+
/** Decorative illustration or icon above the title. */
|
|
247
|
+
icon?: ReactNode;
|
|
248
|
+
/** Draws a dashed-border box, e.g. a "create your first" tile. */
|
|
249
|
+
dashed?: boolean;
|
|
250
|
+
/** `start` left-aligns the content for starting-point and template layouts. */
|
|
251
|
+
align?: "center" | "start";
|
|
252
|
+
/** Level of the title heading, so the page keeps a correct outline. */
|
|
253
|
+
headingLevel?: HeadingLevel;
|
|
254
|
+
/** Extra content after the action, e.g. starting points or recommendations. */
|
|
255
|
+
children?: ReactNode;
|
|
256
|
+
id?: string;
|
|
257
|
+
className?: string;
|
|
258
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { KitLink } from "./navigation.js";
|
|
3
|
+
import { Fragment, forwardRef, useEffect, useId, useLayoutEffect, useRef, useState, } from "react";
|
|
4
|
+
import { DialogPanel, DialogTitle, Menu, MenuButton, MenuItems, MenuItem, MenuSection, MenuHeading, MenuSeparator, } from "@headlessui/react";
|
|
5
|
+
import { ChevronDown, Close } from "./icons.js";
|
|
6
|
+
import { headingTag } from "./heading.js";
|
|
7
|
+
import { DialogFrame, FieldFeedback, LinkContent, RequiredMark, Swatch, cx, componentRoot, useComponentName, focusAway, rovingIndex, useDomId, useField, } from "./internal.js";
|
|
8
|
+
export const Button = forwardRef(function Button({ variant = "primary", loading = false, disabled, children, className, type = "button", size = "md", icon, iconPosition = "start", iconOnly = false, onClick, ...props }, ref) {
|
|
9
|
+
const glyph = icon && (_jsx("span", { className: "m-button-icon", "aria-hidden": "true", children: icon }));
|
|
10
|
+
return (_jsxs("button", { ...componentRoot("Button"), ...props, ref: ref, type: type, disabled: disabled, "aria-disabled": (loading && !disabled) || props["aria-disabled"], "aria-busy": loading || undefined, onClick: (event) => {
|
|
11
|
+
// A busy button keeps focus but must not act or submit its form.
|
|
12
|
+
if (loading)
|
|
13
|
+
event.preventDefault();
|
|
14
|
+
else
|
|
15
|
+
onClick?.(event);
|
|
16
|
+
}, className: cx("m-button", `m-button-${variant}`, className), "data-size": size === "md" ? undefined : size, "data-icon-only": iconOnly && icon ? true : undefined, children: [loading && _jsx("span", { className: "m-spinner", "aria-hidden": "true" }), iconPosition === "start" && glyph, iconOnly && icon ? (_jsx("span", { className: "m-sr-only", children: children })) : (children), iconPosition === "end" && glyph] }));
|
|
17
|
+
});
|
|
18
|
+
export function ActionLink({ label, href, variant = "primary", className, }) {
|
|
19
|
+
return (_jsx(KitLink, { ...componentRoot("ActionLink"), className: cx("m-button", `m-button-${variant}`, className), href: href, children: label }));
|
|
20
|
+
}
|
|
21
|
+
/** Joined row of related secondary actions. Use SegmentedControl to choose one value. */
|
|
22
|
+
export function ButtonGroup({ label, items, className, }) {
|
|
23
|
+
return (_jsx("div", { ...componentRoot("ButtonGroup"), className: cx("m-button-group", className), role: "group", "aria-label": label, children: items.map((item) => {
|
|
24
|
+
const content = (_jsxs(_Fragment, { children: [item.icon && (_jsx("span", { "aria-hidden": "true", className: "m-button-group-icon", children: item.icon })), !(item.iconOnly && item.icon) && item.label] }));
|
|
25
|
+
const name = item.iconOnly && item.icon ? item.label : undefined;
|
|
26
|
+
return item.href && !item.disabled ? (_jsx(KitLink, { href: item.href, "aria-label": name, children: content }, item.label)) : (_jsx("button", { type: "button", "aria-label": name, disabled: item.disabled, onClick: item.onSelect, children: content }, item.label));
|
|
27
|
+
}) }));
|
|
28
|
+
}
|
|
29
|
+
export function Badge({ children, tone = "neutral", className, }) {
|
|
30
|
+
return (_jsx("span", { ...componentRoot("Badge"), className: cx("m-badge", className), "data-tone": tone === "neutral" ? undefined : tone, children: children }));
|
|
31
|
+
}
|
|
32
|
+
/** The field label; a required field adds a decorative marker after the text. */
|
|
33
|
+
export function FieldLabel({ htmlFor, label, required, }) {
|
|
34
|
+
return (_jsxs("label", { htmlFor: htmlFor, children: [label, required && _jsx(RequiredMark, {})] }));
|
|
35
|
+
}
|
|
36
|
+
export const Input = forwardRef(function Input({ label, hint, error, id, prefix, suffix, leadingIcon, trailingIcon, action, className, ...props }, ref) {
|
|
37
|
+
const { fieldId, messageId, describedBy } = useField({
|
|
38
|
+
id,
|
|
39
|
+
hint,
|
|
40
|
+
error,
|
|
41
|
+
describedBy: props["aria-describedby"],
|
|
42
|
+
});
|
|
43
|
+
return (_jsxs("div", { ...componentRoot("Input"), className: cx("m-field", className), children: [_jsx(FieldLabel, { htmlFor: fieldId, label: label, required: props.required }), _jsxs("div", { className: "m-input-group", "data-invalid": !!error || undefined, "data-leading": leadingIcon ? true : undefined, "data-trailing": trailingIcon ? true : undefined, children: [prefix && _jsx("span", { children: prefix }), leadingIcon && (_jsx("span", { className: "m-input-icon", "aria-hidden": "true", children: leadingIcon })), _jsx("input", { ...props, ref: ref, id: fieldId, "aria-invalid": !!error || props["aria-invalid"], "aria-describedby": describedBy }), trailingIcon && (_jsx("span", { className: "m-input-icon", "aria-hidden": "true", children: trailingIcon })), suffix && _jsx("span", { children: suffix }), action && _jsx("div", { className: "m-input-action", children: action })] }), _jsx(FieldFeedback, { id: messageId, hint: hint, error: error })] }));
|
|
44
|
+
});
|
|
45
|
+
export function Select({ label, options, id, hint, error, className, ...props }) {
|
|
46
|
+
const { fieldId, messageId, describedBy } = useField({
|
|
47
|
+
id,
|
|
48
|
+
hint,
|
|
49
|
+
error,
|
|
50
|
+
describedBy: props["aria-describedby"],
|
|
51
|
+
});
|
|
52
|
+
const listbox = !!props.multiple || (props.size || 0) > 1;
|
|
53
|
+
return (_jsxs("div", { ...componentRoot("Select"), className: cx("m-field", className), children: [_jsx(FieldLabel, { htmlFor: fieldId, label: label, required: props.required }), _jsxs("div", { className: "m-select-control", "data-listbox": listbox || undefined, children: [_jsx("select", { ...props, id: fieldId, "aria-invalid": !!error || props["aria-invalid"], "aria-describedby": describedBy, children: options.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value))) }), !listbox && _jsx(ChevronDown, {})] }), _jsx(FieldFeedback, { id: messageId, hint: hint, error: error })] }));
|
|
54
|
+
}
|
|
55
|
+
/** Groups related fields under a legend; disabled disables every control inside. */
|
|
56
|
+
export function Fieldset({ legend, description, disabled, children, className, }) {
|
|
57
|
+
const id = useId();
|
|
58
|
+
return (_jsxs("fieldset", { ...componentRoot("Fieldset"), className: cx("m-fieldset", className), disabled: disabled, "aria-describedby": description ? id : undefined, children: [_jsx("legend", { children: legend }), description && _jsx("p", { id: id, children: description }), _jsx("div", { className: "m-fieldset-fields", children: children })] }));
|
|
59
|
+
}
|
|
60
|
+
export function TextArea({ label, id, hint, error, className, ...props }) {
|
|
61
|
+
const { fieldId, messageId, describedBy } = useField({
|
|
62
|
+
id,
|
|
63
|
+
hint,
|
|
64
|
+
error,
|
|
65
|
+
describedBy: props["aria-describedby"],
|
|
66
|
+
});
|
|
67
|
+
return (_jsxs("div", { ...componentRoot("TextArea"), className: cx("m-field", className), children: [_jsx(FieldLabel, { htmlFor: fieldId, label: label, required: props.required }), _jsx("textarea", { ...props, id: fieldId, "aria-invalid": !!error || props["aria-invalid"], "aria-describedby": describedBy }), _jsx(FieldFeedback, { id: messageId, hint: hint, error: error })] }));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Label beside a control, with a description line and an error message under
|
|
71
|
+
* the label, both linked to the control by aria-describedby.
|
|
72
|
+
*/
|
|
73
|
+
function DescribedChoice({ component, input, id, label, description, error, required, describedBy, className, }) {
|
|
74
|
+
const generated = useId();
|
|
75
|
+
const fieldId = id || generated;
|
|
76
|
+
const descriptionId = `${fieldId}-description`;
|
|
77
|
+
const messageId = `${fieldId}-message`;
|
|
78
|
+
const ids = [describedBy, description && descriptionId, error && messageId]
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.join(" ") || undefined;
|
|
81
|
+
return (_jsxs("div", { ...componentRoot(component), className: cx("m-choice", "m-choice-described", className), children: [input({ id: fieldId, "aria-describedby": ids }), _jsxs("div", { className: "m-choice-text", children: [_jsxs("label", { htmlFor: fieldId, children: [label, required && _jsx(RequiredMark, {})] }), description && _jsx("p", { id: descriptionId, children: description }), _jsx(FieldFeedback, { id: messageId, error: error })] })] }));
|
|
82
|
+
}
|
|
83
|
+
export function Checkbox({ label, description, error, className, ...props }) {
|
|
84
|
+
const input = useRef(null);
|
|
85
|
+
// Once an error has shown, keep the described layout: switching back to the
|
|
86
|
+
// plain label would remount the input and drop focus while the person fixes
|
|
87
|
+
// it. If the layout changes while the input has focus, focus follows it.
|
|
88
|
+
const [described, setDescribed] = useState(false);
|
|
89
|
+
const refocus = useRef(false);
|
|
90
|
+
if (error && !described) {
|
|
91
|
+
refocus.current =
|
|
92
|
+
typeof document !== "undefined" &&
|
|
93
|
+
!!input.current &&
|
|
94
|
+
input.current === document.activeElement;
|
|
95
|
+
setDescribed(true);
|
|
96
|
+
}
|
|
97
|
+
useLayoutEffect(() => {
|
|
98
|
+
if (refocus.current)
|
|
99
|
+
input.current?.focus();
|
|
100
|
+
refocus.current = false;
|
|
101
|
+
}, [described]);
|
|
102
|
+
if (description || error || described)
|
|
103
|
+
return (_jsx(DescribedChoice, { component: "Checkbox", id: props.id, label: label, description: description, error: error, required: props.required, describedBy: props["aria-describedby"], className: className, input: (attrs) => (_jsx("input", { ...props, ...attrs, ref: input, "aria-invalid": error ? true : props["aria-invalid"], type: "checkbox" })) }));
|
|
104
|
+
return (_jsxs("label", { ...componentRoot("Checkbox"), className: cx("m-choice", className), children: [_jsx("input", { ...props, ref: input, type: "checkbox" }), label, props.required && _jsx(RequiredMark, {})] }));
|
|
105
|
+
}
|
|
106
|
+
export function Switch({ label, description, className, ...props }) {
|
|
107
|
+
if (description)
|
|
108
|
+
return (_jsx(DescribedChoice, { component: "Switch", id: props.id, label: label, description: description, className: className, input: (attrs) => (_jsx("input", { ...props, ...attrs, type: "checkbox", role: "switch", className: "m-switch" })) }));
|
|
109
|
+
return (_jsxs("label", { ...componentRoot("Switch"), className: cx("m-choice", className), children: [_jsx("input", { ...props, type: "checkbox", role: "switch", className: "m-switch" }), label] }));
|
|
110
|
+
}
|
|
111
|
+
export function RadioGroup({ label, name, value, onChange, options, layout = "list", hint, error, required, id, className, }) {
|
|
112
|
+
const { messageId, describedBy } = useField({ id, hint, error });
|
|
113
|
+
return (_jsxs("fieldset", { ...componentRoot("RadioGroup"), className: cx("m-radio-group", className), "data-layout": layout === "list" ? undefined : layout, id: id, "aria-describedby": describedBy, "data-invalid": error ? true : undefined, children: [_jsxs("legend", { children: [label, required && _jsx(RequiredMark, {})] }), _jsx(FieldFeedback, { id: messageId, hint: hint, error: error }), options.map((o) => (_jsxs("label", { className: "m-radio-card", children: [_jsx("input", { type: "radio", name: name, value: o.value, checked: value === o.value, disabled: o.disabled, required: required, "aria-invalid": error ? true : undefined, onChange: () => onChange(o.value) }), layout === "swatches" ? (_jsx(Swatch, { color: o.swatch, label: o.label })) : (_jsxs("span", { children: [_jsx("strong", { children: o.label }), o.description && _jsx("span", { children: o.description })] }))] }, o.value)))] }));
|
|
114
|
+
}
|
|
115
|
+
/** Arrow keys follow reading direction: in right-to-left text ArrowLeft moves forward. */
|
|
116
|
+
function logicalKey(key, element) {
|
|
117
|
+
if (getComputedStyle(element).direction !== "rtl")
|
|
118
|
+
return key;
|
|
119
|
+
if (key === "ArrowLeft")
|
|
120
|
+
return "ArrowRight";
|
|
121
|
+
if (key === "ArrowRight")
|
|
122
|
+
return "ArrowLeft";
|
|
123
|
+
return key;
|
|
124
|
+
}
|
|
125
|
+
export function Tabs({ label, value, onChange, items, variant = "underline", fullWidth = false, className, }) {
|
|
126
|
+
const base = useId();
|
|
127
|
+
const tabId = (id) => `${base}-${id}-tab`;
|
|
128
|
+
// The tab in the tab order: the selected one, or the first when none matches.
|
|
129
|
+
const focusable = items.some((item) => item.id === value)
|
|
130
|
+
? value
|
|
131
|
+
: items[0]?.id;
|
|
132
|
+
return (_jsxs("div", { ...componentRoot("Tabs"), className: className, children: [_jsx("div", { className: "m-tabs", role: "tablist", "aria-label": label, "data-variant": variant === "underline" ? undefined : variant, "data-full-width": fullWidth || undefined, onKeyDown: (e) => {
|
|
133
|
+
const focused = items.findIndex((item) => tabId(item.id) === e.target.id);
|
|
134
|
+
const next = rovingIndex(logicalKey(e.key, e.currentTarget), focused >= 0
|
|
135
|
+
? focused
|
|
136
|
+
: items.findIndex((item) => item.id === focusable), items.length);
|
|
137
|
+
if (next === null)
|
|
138
|
+
return;
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
onChange(items[next].id);
|
|
141
|
+
document.getElementById(tabId(items[next].id))?.focus();
|
|
142
|
+
}, children: items.map((item) => (_jsx("button", { id: tabId(item.id), type: "button", role: "tab", "aria-selected": item.id === value, "aria-controls": `${base}-${item.id}-panel`, tabIndex: item.id === focusable ? 0 : -1, onClick: () => onChange(item.id), children: _jsx(LinkContent, { prefix: "tab", icon: item.icon, label: item.label, count: item.count }) }, item.id))) }), items.map((item) => (_jsx("div", { hidden: item.id !== value, role: "tabpanel", id: `${base}-${item.id}-panel`, "aria-labelledby": tabId(item.id), tabIndex: 0, children: item.content }, item.id)))] }));
|
|
143
|
+
}
|
|
144
|
+
export function Modal({ open, onClose, title, children, actions, size = "md", className, }) {
|
|
145
|
+
const component = useComponentName("Modal");
|
|
146
|
+
return (_jsx(DialogFrame, { open: open, onClose: onClose, component: component, children: _jsx("div", { className: "m-dialog-position", children: _jsxs(DialogPanel, { className: cx("m-dialog-panel", className), "data-size": size === "md" ? undefined : size, children: [_jsxs("div", { className: "m-dialog-heading", children: [_jsx(DialogTitle, { children: title }), _jsx(Button, { variant: "ghost", onClick: onClose, "aria-label": "Close dialog", children: "Close" })] }), children, actions && _jsx("div", { className: "m-actions", children: actions })] }) }) }));
|
|
147
|
+
}
|
|
148
|
+
export function Dropdown({ label, items, icon, iconOnly = false, variant = "secondary", align = "end", header, className, }) {
|
|
149
|
+
const groups = [];
|
|
150
|
+
for (const item of items) {
|
|
151
|
+
const last = groups[groups.length - 1];
|
|
152
|
+
if (last && last.section === item.section)
|
|
153
|
+
last.items.push(item);
|
|
154
|
+
else
|
|
155
|
+
groups.push({ section: item.section, items: [item] });
|
|
156
|
+
}
|
|
157
|
+
const entry = (item) => {
|
|
158
|
+
const content = (_jsxs(_Fragment, { children: [item.icon && (_jsx("span", { className: "m-menu-icon", "aria-hidden": "true", children: item.icon })), item.description ? (_jsxs("span", { className: "m-menu-text", children: [_jsx("span", { children: item.label }), _jsx("small", { children: item.description })] })) : (item.label)] }));
|
|
159
|
+
return (_jsx(MenuItem, { disabled: item.disabled, children: item.href ? (_jsx(KitLink, { href: item.href, className: item.danger ? "m-error" : "", children: content })) : (_jsx("button", { type: "button", className: item.danger ? "m-error" : "", onClick: item.onSelect, children: content })) }, item.label));
|
|
160
|
+
};
|
|
161
|
+
return (_jsxs(Menu, { children: [_jsxs(MenuButton, { ...componentRoot("Dropdown"), className: cx("m-button", `m-button-${variant}`, className), "aria-label": iconOnly && icon ? label : undefined, "data-icon-only": iconOnly && icon ? true : undefined, children: [icon && (_jsx("span", { className: "m-button-icon", "aria-hidden": "true", children: icon })), !(iconOnly && icon) && label, !(iconOnly && icon) && (_jsx("span", { className: "m-button-icon", "aria-hidden": "true", children: _jsx(ChevronDown, {}) }))] }), _jsxs(MenuItems, { anchor: `bottom ${align}`, className: "m-menu", children: [header && _jsx("div", { className: "m-menu-header", children: header }), groups.map((group, index) => group.section ? (_jsxs(Fragment, { children: [index > 0 && _jsx(MenuSeparator, { className: "m-menu-separator" }), _jsxs(MenuSection, { children: [_jsx(MenuHeading, { className: "m-menu-heading", children: group.section }), group.items.map(entry)] })] }, `${group.section}:${index}`)) : (_jsxs(Fragment, { children: [index > 0 && _jsx(MenuSeparator, { className: "m-menu-separator" }), group.items.map(entry)] }, `items:${index}`)))] })] }));
|
|
162
|
+
}
|
|
163
|
+
export function Notice({ title, children, tone = "neutral", announce = false, actions, onDismiss, dismissLabel, id, className, }) {
|
|
164
|
+
const root = useRef(null);
|
|
165
|
+
// A status region only announces changes made after it is in the document,
|
|
166
|
+
// so a polite notice re-inserts its title once it has mounted; the region is
|
|
167
|
+
// atomic, so the whole notice is read. An alert is announced on insertion.
|
|
168
|
+
const polite = announce && tone !== "danger";
|
|
169
|
+
const [live, setLive] = useState(false);
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (polite)
|
|
172
|
+
setLive(true);
|
|
173
|
+
}, [polite]);
|
|
174
|
+
return (_jsxs("div", { ref: root, id: id, ...componentRoot("Notice"), className: cx("m-notice", className), "data-tone": tone === "neutral" ? undefined : tone, role: announce ? (tone === "danger" ? "alert" : "status") : undefined, "aria-atomic": polite || undefined, "data-dismissible": onDismiss ? true : undefined, children: [_jsx("strong", { children: title }, live ? "live" : "initial"), children && _jsx("div", { children: children }), actions && _jsx("div", { className: "m-notice-actions", children: actions }), onDismiss && (_jsx("button", { type: "button", className: "m-notice-dismiss", "aria-label": dismissLabel ?? `Dismiss ${title}`, onClick: () => {
|
|
175
|
+
onDismiss();
|
|
176
|
+
focusAway(root.current);
|
|
177
|
+
}, children: _jsx(Close, {}) }))] }));
|
|
178
|
+
}
|
|
179
|
+
const FOCUSABLE_CONTROL = 'input:not([type="hidden"]):not(:disabled), select:not(:disabled), textarea:not(:disabled), button:not(:disabled), [tabindex]:not([tabindex="-1"])';
|
|
180
|
+
/**
|
|
181
|
+
* Moves focus to the field an ErrorSummary link names, and scrolls its label
|
|
182
|
+
* or legend into view with it, so the question is visible with the control.
|
|
183
|
+
*/
|
|
184
|
+
function focusField(target) {
|
|
185
|
+
const control = target.matches(FOCUSABLE_CONTROL)
|
|
186
|
+
? target
|
|
187
|
+
: (target.querySelector("input:is([type=radio], [type=checkbox]):checked:not(:disabled)") ?? target.querySelector(FOCUSABLE_CONTROL));
|
|
188
|
+
const focusable = control ?? (target.tabIndex >= -1 ? target : null);
|
|
189
|
+
const anchor = target instanceof HTMLFieldSetElement
|
|
190
|
+
? target.querySelector("legend")
|
|
191
|
+
: control instanceof HTMLInputElement ||
|
|
192
|
+
control instanceof HTMLSelectElement ||
|
|
193
|
+
control instanceof HTMLTextAreaElement
|
|
194
|
+
? control.labels?.[0]
|
|
195
|
+
: null;
|
|
196
|
+
focusable?.focus({ preventScroll: true });
|
|
197
|
+
(anchor ?? focusable ?? target).scrollIntoView({ block: "center" });
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The problems that stopped a form submitting, above the form, each linking
|
|
201
|
+
* to its field. It is an alert, and it takes focus when it appears and after
|
|
202
|
+
* every failed submit (`submitCount`), so keyboard and screen reader users
|
|
203
|
+
* start from the list. Following a link focuses the field without changing the
|
|
204
|
+
* URL. Renders nothing while `errors` is empty.
|
|
205
|
+
*/
|
|
206
|
+
export function ErrorSummary({ errors, title = "There is a problem", headingLevel = 2, autoFocus = true, submitCount, id, className, }) {
|
|
207
|
+
const root = useRef(null);
|
|
208
|
+
const titleId = useDomId("m-error-summary-");
|
|
209
|
+
const shown = errors.length > 0;
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
if (shown && autoFocus)
|
|
212
|
+
root.current?.focus();
|
|
213
|
+
// Focus only on appearing and on a new submit, never while errors clear.
|
|
214
|
+
}, [shown, submitCount]);
|
|
215
|
+
if (!shown)
|
|
216
|
+
return null;
|
|
217
|
+
const Heading = headingTag(headingLevel);
|
|
218
|
+
return (_jsxs("div", { ref: root, id: id, ...componentRoot("ErrorSummary"), className: cx("m-error-summary", className), role: "alert", "aria-labelledby": titleId, tabIndex: -1, children: [_jsx(Heading, { id: titleId, className: "m-error-summary-title", children: title }), _jsx("ul", { children: errors.map((error, index) => {
|
|
219
|
+
const href = error.href ?? (error.target ? `#${error.target}` : undefined);
|
|
220
|
+
const target = error.target ??
|
|
221
|
+
(href?.startsWith("#")
|
|
222
|
+
? decodeURIComponent(href.slice(1))
|
|
223
|
+
: undefined);
|
|
224
|
+
return (_jsx("li", { children: href ? (_jsx("a", { href: href, onClick: (event) => {
|
|
225
|
+
if (!target ||
|
|
226
|
+
event.defaultPrevented ||
|
|
227
|
+
event.button !== 0 ||
|
|
228
|
+
event.metaKey ||
|
|
229
|
+
event.ctrlKey ||
|
|
230
|
+
event.shiftKey ||
|
|
231
|
+
event.altKey)
|
|
232
|
+
return;
|
|
233
|
+
const field = event.currentTarget.ownerDocument.getElementById(target);
|
|
234
|
+
if (!field)
|
|
235
|
+
return;
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
focusField(field);
|
|
238
|
+
}, children: error.message })) : (error.message) }, `${error.message}:${href}:${index}`));
|
|
239
|
+
}) })] }));
|
|
240
|
+
}
|
|
241
|
+
export function EmptyState({ title, description, action, icon, dashed, align = "center", headingLevel = 3, children, id, className, }) {
|
|
242
|
+
const Heading = headingTag(headingLevel);
|
|
243
|
+
return (_jsxs("div", { id: id, ...componentRoot("EmptyState"), className: cx("m-empty", className), "data-dashed": dashed || undefined, "data-align": align === "start" ? "start" : undefined, children: [icon && (_jsx("span", { className: "m-empty-icon", "aria-hidden": "true", children: icon })), _jsx(Heading, { children: title }), description && _jsx("p", { children: description }), action, children && _jsx("div", { className: "m-empty-extra", children: children })] }));
|
|
244
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/** Framed product screenshot or illustration for heroes and feature sections. */
|
|
3
|
+
export declare function MockWindow({ id, className, label, address, variant, children, }: {
|
|
4
|
+
id?: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
/** Accessible description of what the mock shows. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Optional URL or file name shown in the title bar. */
|
|
9
|
+
address?: string;
|
|
10
|
+
/** browser: title bar with dots. app: title bar without dots. plain: no chrome. phone: a portrait handset frame. */
|
|
11
|
+
variant?: "browser" | "app" | "plain" | "phone";
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
export interface ChatMessage {
|
|
15
|
+
from: "user" | "assistant";
|
|
16
|
+
content: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
/** Static, illustrative chat conversation for marketing a conversational product. */
|
|
19
|
+
export declare function ChatPreview({ id, className, title, status, messages, typing, suggestions, placeholder, assistantName, }: {
|
|
20
|
+
id?: string;
|
|
21
|
+
className?: string;
|
|
22
|
+
title: string;
|
|
23
|
+
status?: string;
|
|
24
|
+
messages: ChatMessage[];
|
|
25
|
+
typing?: boolean;
|
|
26
|
+
suggestions?: string[];
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
assistantName?: string;
|
|
29
|
+
}): import("react").JSX.Element;
|
package/dist/showcase.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { componentRoot, cx } from "./internal.js";
|
|
3
|
+
/** Framed product screenshot or illustration for heroes and feature sections. */
|
|
4
|
+
export function MockWindow({ id, className, label, address, variant = "browser", children, }) {
|
|
5
|
+
return (_jsxs("figure", { id: id, className: cx("m-mock-window", className), "data-variant": variant === "browser" ? undefined : variant, "aria-label": label, ...componentRoot("MockWindow"), children: [variant !== "plain" && variant !== "phone" && (_jsxs("div", { className: "m-mock-bar", "aria-hidden": "true", children: [_jsxs("span", { className: "m-mock-dots", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] }), address && _jsx("span", { className: "m-mock-address", children: address })] })), _jsx("div", { className: "m-mock-body", children: children })] }));
|
|
6
|
+
}
|
|
7
|
+
/** Static, illustrative chat conversation for marketing a conversational product. */
|
|
8
|
+
export function ChatPreview({ id, className, title, status, messages, typing = false, suggestions = [], placeholder = "Type a message…", assistantName = "Assistant", }) {
|
|
9
|
+
return (_jsxs("figure", { id: id, className: cx("m-chat-preview", className), "aria-label": `${title} conversation`, ...componentRoot("ChatPreview"), children: [_jsxs("header", { children: [_jsx("span", { className: "m-chat-avatar", "aria-hidden": "true" }), _jsxs("span", { children: [_jsx("strong", { children: title }), status && _jsx("small", { children: status })] })] }), _jsxs("ol", { className: "m-chat-log", children: [messages.map((message, i) => (_jsxs("li", { "data-from": message.from, children: [_jsxs("span", { className: "m-sr-only", children: [message.from === "user" ? "Visitor" : assistantName, ":"] }), _jsx("div", { className: "m-chat-bubble", children: message.content })] }, i))), typing && (_jsx("li", { "data-from": "assistant", children: _jsxs("span", { className: "m-chat-typing", children: [_jsxs("span", { className: "m-sr-only", children: [assistantName, " is typing"] }), _jsx("i", { "aria-hidden": "true" }), _jsx("i", { "aria-hidden": "true" }), _jsx("i", { "aria-hidden": "true" })] }) }))] }), suggestions.length > 0 && (_jsx("ul", { className: "m-chat-suggestions", "aria-label": "Suggested replies", children: suggestions.map((s) => (_jsx("li", { children: s }, s))) })), _jsxs("div", { className: "m-chat-composer", "aria-hidden": "true", children: [_jsx("span", { children: placeholder }), _jsx("span", { className: "m-chat-send" })] })] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type ItemGroup } from "./navigation.js";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { type HeadingLevel } from "./heading.js";
|
|
4
|
+
export interface SlideOverProps {
|
|
5
|
+
open: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
actions?: ReactNode;
|
|
11
|
+
/** Panel width: md (default), lg or xl. */
|
|
12
|
+
size?: "md" | "lg" | "xl";
|
|
13
|
+
/** Class on the sliding panel. */
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function SlideOver({ open, onClose, title, description, children, actions, size, className, }: SlideOverProps): import("react").JSX.Element;
|
|
17
|
+
export interface CommandItem {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
meta?: string;
|
|
22
|
+
shortcut?: string[];
|
|
23
|
+
/** Icon or avatar before the label. */
|
|
24
|
+
icon?: ReactNode;
|
|
25
|
+
/** [start, end) character range of label to emphasise as the query match. */
|
|
26
|
+
match?: [number, number];
|
|
27
|
+
onSelect: () => void;
|
|
28
|
+
}
|
|
29
|
+
export interface CommandPaletteProps {
|
|
30
|
+
open: boolean;
|
|
31
|
+
onClose: () => void;
|
|
32
|
+
query: string;
|
|
33
|
+
onQueryChange: (value: string) => void;
|
|
34
|
+
groups: ItemGroup<CommandItem>[];
|
|
35
|
+
label?: string;
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
/** Announced when no group has items; defaults to "No results for “query”.". */
|
|
38
|
+
empty?: {
|
|
39
|
+
title: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
};
|
|
42
|
+
footer?: ReactNode;
|
|
43
|
+
/** Class on the palette panel. */
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Controlled ⌘K palette. Filtering and ranking stay with the consumer. */
|
|
47
|
+
export declare function CommandPalette({ open, onClose, query, onQueryChange, groups, label, placeholder, empty, footer, className, }: CommandPaletteProps): import("react").JSX.Element;
|
|
48
|
+
export interface StackedListItem {
|
|
49
|
+
id: string;
|
|
50
|
+
title: ReactNode;
|
|
51
|
+
description?: ReactNode;
|
|
52
|
+
leading?: ReactNode;
|
|
53
|
+
trailing?: ReactNode;
|
|
54
|
+
/**
|
|
55
|
+
* Second column at the end of the row, e.g. a role and "Last seen 3h ago";
|
|
56
|
+
* it wraps under the text when narrow. It sits inside the row's link, so it
|
|
57
|
+
* must not contain links or buttons.
|
|
58
|
+
*/
|
|
59
|
+
meta?: ReactNode;
|
|
60
|
+
href?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface StackedListProps {
|
|
63
|
+
/** Accessible name; defaults to the visible `title`. */
|
|
64
|
+
label?: string;
|
|
65
|
+
/** Visible heading above the list. */
|
|
66
|
+
title?: ReactNode;
|
|
67
|
+
/** Level of the title, or of the group headings when there is no title. Group headings sit one level below the title. */
|
|
68
|
+
headingLevel?: HeadingLevel;
|
|
69
|
+
/** Button or link in the header, e.g. "View all". */
|
|
70
|
+
action?: ReactNode;
|
|
71
|
+
/** Rows; with `groups` they render first, ungrouped. */
|
|
72
|
+
items?: StackedListItem[];
|
|
73
|
+
/** Sections with sticky headings, e.g. a directory grouped by letter. */
|
|
74
|
+
groups?: ItemGroup<StackedListItem>[];
|
|
75
|
+
/** Ends each row that has an `href` with a chevron, so rows read as links. */
|
|
76
|
+
chevrons?: boolean;
|
|
77
|
+
id?: string;
|
|
78
|
+
className?: string;
|
|
79
|
+
}
|
|
80
|
+
export declare function StackedList({ label, title, headingLevel, action, items, groups, chevrons, id, className, }: StackedListProps): import("react").JSX.Element;
|