@flood-md/react 0.2.3 → 0.2.5
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/README.md +398 -3
- package/dist/alert-dialog.d.ts +2 -0
- package/dist/alert-dialog.js +23 -0
- package/dist/avatar.d.ts +5 -0
- package/dist/avatar.js +8 -0
- package/dist/checkbox.d.ts +4 -0
- package/dist/checkbox.js +15 -0
- package/dist/combobox.d.ts +11 -0
- package/dist/combobox.js +301 -0
- package/dist/content-elements.d.ts +8 -0
- package/dist/content-elements.js +47 -0
- package/dist/data-display.d.ts +3 -0
- package/dist/data-display.js +11 -0
- package/dist/disclosure.js +2 -1
- package/dist/feedback-actions.d.ts +3 -0
- package/dist/feedback-actions.js +106 -0
- package/dist/feedback.d.ts +8 -0
- package/dist/feedback.js +29 -0
- package/dist/file-input.d.ts +8 -0
- package/dist/file-input.js +86 -0
- package/dist/focusable-hidden.d.ts +3 -0
- package/dist/focusable-hidden.js +99 -0
- package/dist/fonts/InterVariable-Italic.woff2 +0 -0
- package/dist/fonts/README.md +9 -5
- package/dist/form-controls.d.ts +6 -0
- package/dist/form-controls.js +250 -0
- package/dist/foundations.css +543 -12
- package/dist/index.d.ts +513 -2
- package/dist/index.js +223 -36
- package/dist/menu.d.ts +2 -0
- package/dist/menu.js +130 -0
- package/dist/navigation-layout.d.ts +7 -0
- package/dist/navigation-layout.js +77 -0
- package/dist/navigation.js +8 -8
- package/dist/numeric-input.d.ts +8 -0
- package/dist/numeric-input.js +68 -0
- package/dist/overlay-runtime.js +43 -16
- package/dist/overlays.d.ts +7 -1
- package/dist/overlays.js +17 -6
- package/dist/scroll-area.js +17 -2
- package/dist/select.js +96 -47
- package/dist/slider.d.ts +7 -0
- package/dist/slider.js +89 -0
- package/dist/styles.css +9 -0
- package/dist/text-inputs.d.ts +9 -0
- package/dist/text-inputs.js +45 -0
- package/dist/toolbar.d.ts +2 -0
- package/dist/toolbar.js +80 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,23 +1,104 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef, createElement, useId, useState, } from "react";
|
|
4
|
+
import { useFocusableHidden } from "./focusable-hidden.js";
|
|
4
5
|
import { resolveFieldStatus, hasFieldContent, mergeDescriptions } from "./field-state.js";
|
|
5
6
|
import { SingleSelect } from "./select.js";
|
|
7
|
+
import { MixedCheckbox } from "./checkbox.js";
|
|
6
8
|
import { OverlayScrollArea } from "./scroll-area.js";
|
|
7
9
|
import { ChoiceGroup } from "./choice-group.js";
|
|
8
10
|
import { Disclosure } from "./disclosure.js";
|
|
9
11
|
import { AnchoredOverlay, ModalOverlay } from "./overlays.js";
|
|
10
12
|
import { TabNavigation, BreadcrumbNavigation, PageNavigation } from "./navigation.js";
|
|
13
|
+
import { AvatarArtwork } from "./avatar.js";
|
|
14
|
+
import { ValueIndicator, FeedbackMessage } from "./feedback.js";
|
|
15
|
+
import { DescribedData, TabularData } from "./data-display.js";
|
|
16
|
+
import { ActionMenu } from "./menu.js";
|
|
17
|
+
import { RovingToolbar } from "./toolbar.js";
|
|
18
|
+
import { NumericStepper } from "./numeric-input.js";
|
|
19
|
+
import { RangeControl } from "./slider.js";
|
|
20
|
+
import { GroupedAvatars, NamedStatus, CaptionedFigure, AttributedQuote, InlineCodeText, MeasuredValue, EventTimeline } from "./content-elements.js";
|
|
21
|
+
import { NavigationMenu, StepSequence, ContentSection, SplitLayout, CenterLayout, SkipNavigation } from "./navigation-layout.js";
|
|
22
|
+
import { CollectionListBox, SegmentedPinInput, StarRating, FileDropzone, SupportingCharacterCount } from "./form-controls.js";
|
|
23
|
+
import { ClipboardAction, NotificationRegion } from "./feedback-actions.js";
|
|
24
|
+
import { ConfirmationDialog } from "./alert-dialog.js";
|
|
25
|
+
import { FilePicker } from "./file-input.js";
|
|
26
|
+
import { SearchField, PasswordField } from "./text-inputs.js";
|
|
27
|
+
import { SearchSelection } from "./combobox.js";
|
|
11
28
|
const classes = (...values) => values.filter(Boolean).join(" ");
|
|
12
29
|
export const Button = forwardRef(function Button({ variant = "solid", size = "md", loading = false, disabled, type = "button", className, children, ...props }, ref) {
|
|
13
|
-
return (_jsxs("button", { ...props, ref: ref, type: type, disabled: disabled || loading, "aria-busy": loading || undefined, className: classes("flood-button", `flood-button--${variant}`, `flood-button--${size}`, className), children: [loading && _jsx(
|
|
30
|
+
return (_jsxs("button", { ...props, ref: ref, type: type, disabled: disabled || loading, "aria-busy": loading || undefined, className: classes("flood-button", `flood-button--${variant}`, `flood-button--${size}`, className), children: [loading && _jsx(Spinner, { decorative: true }), children] }));
|
|
31
|
+
});
|
|
32
|
+
/** @since 0.2.5 */
|
|
33
|
+
export const Tag = forwardRef(function Tag({ size = "md", icon, disabled = false, onRemove, removeLabel, removeButtonProps, className, children, ...props }, ref) {
|
|
34
|
+
const label = removeLabel ?? (typeof children === "string" || typeof children === "number" ? `Remove ${children}` : "");
|
|
35
|
+
if (onRemove && !label.trim())
|
|
36
|
+
throw new Error("Flood Tag removal needs an accessible removeLabel for rich content");
|
|
37
|
+
const iconSize = size === "sm" ? "2xs" : size === "md" ? "xs" : "sm";
|
|
38
|
+
return _jsxs("span", { ...props, ref: ref, className: classes("flood-tag", className), "data-size": size, "data-disabled": disabled || undefined, children: [hasFieldContent(icon) && _jsx(IconSlot, { size: iconSize, children: icon }), _jsx("span", { className: "flood-tag__label", children: children }), onRemove && _jsx(IconButton, { ...removeButtonProps, type: "button", className: classes("flood-tag__remove", removeButtonProps?.className), label: label, variant: "ghost", size: iconSize, disabled: disabled, onClick: () => { if (!disabled)
|
|
39
|
+
onRemove(); }, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", children: _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) }) })] });
|
|
40
|
+
});
|
|
41
|
+
/** @since 0.2.4 */
|
|
42
|
+
export const Kbd = forwardRef(function Kbd({ size = "sm", className, ...props }, ref) {
|
|
43
|
+
return _jsx("kbd", { ...props, ref: ref, className: classes("flood-kbd", className), "data-size": size });
|
|
44
|
+
});
|
|
45
|
+
/** @since 0.2.4 */
|
|
46
|
+
export const Toolbar = forwardRef(function Toolbar({ label, orientation = "horizontal", loop = true, ...props }, ref) {
|
|
47
|
+
return _jsx(RovingToolbar, { ...props, ref: ref, label: label, orientation: orientation, loop: loop });
|
|
48
|
+
});
|
|
49
|
+
/** @since 0.2.4 */
|
|
50
|
+
export const DescriptionList = forwardRef(function DescriptionList({ items, layout = "inline", density = "compact", dividers = false, ...props }, ref) {
|
|
51
|
+
return _jsx(DescribedData, { ...props, ref: ref, items: items, layout: layout, density: density, dividers: dividers });
|
|
52
|
+
});
|
|
53
|
+
/** @since 0.2.4 */
|
|
54
|
+
export const Table = forwardRef(function Table({ caption, children, hideCaption = false, density = "comfortable", striped = false, scrollLabel, ...props }, ref) {
|
|
55
|
+
return _jsx(TabularData, { ...props, ref: ref, caption: caption, hideCaption: hideCaption, density: density, striped: striped, scrollLabel: scrollLabel, children: children });
|
|
56
|
+
});
|
|
57
|
+
/** @since 0.2.4 */
|
|
58
|
+
export const Badge = forwardRef(function Badge({ size = "md", tone = "neutral", variant = "subtle", className, ...props }, ref) {
|
|
59
|
+
return _jsx("span", { ...props, ref: ref, className: classes("flood-badge", className), "data-size": size, "data-tone": tone, "data-variant": variant });
|
|
60
|
+
});
|
|
61
|
+
/** @since 0.2.4 */
|
|
62
|
+
export const Spinner = forwardRef(function Spinner({ size = "md", label = "Loading", decorative = false, className, ...props }, ref) {
|
|
63
|
+
return _jsxs("span", { ...props, ref: ref, className: classes("flood-loading", className), "data-size": size, role: decorative ? undefined : "status", "aria-hidden": decorative || undefined, children: [_jsx("span", { className: "flood-spinner", "aria-hidden": "true" }), !decorative && _jsx(VisuallyHidden, { children: label })] });
|
|
64
|
+
});
|
|
65
|
+
/** @since 0.2.4 */
|
|
66
|
+
export const Skeleton = forwardRef(function Skeleton({ shape = "text", width, height, animate = true, className, style, ...props }, ref) {
|
|
67
|
+
return _jsx("span", { ...props, ref: ref, className: classes("flood-skeleton", className), "data-shape": shape, "data-animate": animate, "aria-hidden": "true", style: { width, height, ...style } });
|
|
68
|
+
});
|
|
69
|
+
/** @since 0.2.4 */
|
|
70
|
+
export const Avatar = forwardRef(function Avatar({ name, src, fallback, size = "md", shape = "circle", decorative = false, className, ...props }, ref) {
|
|
71
|
+
const initials = name.trim().split(/\s+/u).filter(Boolean).slice(0, 2).map(word => Array.from(word)[0]).join("").toUpperCase();
|
|
72
|
+
return _jsx("span", { ...props, ref: ref, className: classes("flood-avatar", className), "data-size": size, "data-shape": shape, role: decorative ? undefined : "img", "aria-label": decorative ? undefined : name, "aria-hidden": decorative || undefined, children: _jsx(AvatarArtwork, { src: src, fallback: fallback ?? (initials || "?") }, src ?? "") });
|
|
73
|
+
});
|
|
74
|
+
/** @since 0.2.4 */
|
|
75
|
+
export const Progress = forwardRef(function Progress({ label, value, max = 100, valueText, size = "md", tone = "accent", hideLabel = false, ...props }, ref) {
|
|
76
|
+
return _jsx(ValueIndicator, { ...props, ref: ref, kind: "progress", label: label, value: value, min: 0, max: max, valueText: valueText, size: size, tone: tone, hideLabel: hideLabel });
|
|
77
|
+
});
|
|
78
|
+
/** @since 0.2.4 */
|
|
79
|
+
export const Meter = forwardRef(function Meter({ label, value, min = 0, max = 100, valueText, size = "md", tone = "neutral", hideLabel = false, ...props }, ref) {
|
|
80
|
+
return _jsx(ValueIndicator, { ...props, ref: ref, kind: "meter", label: label, value: value, min: min, max: max, valueText: valueText, size: size, tone: tone, hideLabel: hideLabel });
|
|
81
|
+
});
|
|
82
|
+
/** @since 0.2.4 */
|
|
83
|
+
export const Alert = forwardRef(function Alert({ title, tone = "info", icon, announcement = "off", ...props }, ref) {
|
|
84
|
+
return _jsx(FeedbackMessage, { ...props, ref: ref, title: title, tone: tone, icon: icon, announcement: announcement });
|
|
14
85
|
});
|
|
15
86
|
export const Tabs = forwardRef(function Tabs({ activationMode = "automatic", orientation = "horizontal", controlSize = "md", disabled = false, iconOnly = false, ...props }, ref) { return _jsx(TabNavigation, { ...props, ref: ref, activationMode: activationMode, orientation: orientation, controlSize: controlSize, disabled: disabled, iconOnly: iconOnly }); });
|
|
16
87
|
export const Breadcrumb = forwardRef(function Breadcrumb({ label = "Breadcrumb", separator = "/", ...props }, ref) { return _jsx(BreadcrumbNavigation, { ...props, ref: ref, label: label, separator: separator }); });
|
|
17
88
|
export const Pagination = forwardRef(function Pagination({ siblingCount = 1, controlSize = "sm", disabled = false, label = "Pagination", previousLabel = "Previous page", nextLabel = "Next page", ...props }, ref) { return _jsx(PageNavigation, { ...props, ref: ref, siblingCount: siblingCount, controlSize: controlSize, disabled: disabled, label: label, previousLabel: previousLabel, nextLabel: nextLabel }); });
|
|
89
|
+
/** @since 0.2.4 */
|
|
90
|
+
export const Menu = forwardRef(function Menu({ defaultOpen = false, disabled = false, side = "bottom", align = "start", controlSize = "sm", ...props }, ref) {
|
|
91
|
+
return _jsx(ActionMenu, { ...props, ref: ref, defaultOpen: defaultOpen, disabled: disabled, side: side, align: align, controlSize: controlSize });
|
|
92
|
+
});
|
|
18
93
|
export const Tooltip = forwardRef(function Tooltip({ side = "top", align = "center", delay = 300, disabled = false, ...props }, ref) { return _jsx(AnchoredOverlay, { ...props, ref: ref, kind: "tooltip", side: side, align: align, delay: delay, disabled: disabled }); });
|
|
19
94
|
export const Popover = forwardRef(function Popover({ side = "bottom", align = "start", defaultOpen = false, disabled = false, closeLabel = "Close", ...props }, ref) { return _jsx(AnchoredOverlay, { ...props, ref: ref, kind: "popover", side: side, align: align, defaultOpen: defaultOpen, disabled: disabled, closeLabel: closeLabel }); });
|
|
20
95
|
export const Dialog = forwardRef(function Dialog({ defaultOpen = false, disabled = false, closeLabel = "Close", closeOnOutsideClick = true, ...props }, ref) { return _jsx(ModalOverlay, { ...props, ref: ref, defaultOpen: defaultOpen, disabled: disabled, closeLabel: closeLabel, closeOnOutsideClick: closeOnOutsideClick }); });
|
|
96
|
+
export const Drawer = forwardRef(function Drawer({ side = "end", size = "md", defaultOpen = false, closeLabel = "Close", closeOnOutsideClick = true, className, footer, ...props }, ref) {
|
|
97
|
+
return _jsx(ModalOverlay, { ...props, ref: ref, defaultOpen: defaultOpen, closeLabel: closeLabel, closeOnOutsideClick: closeOnOutsideClick, className: classes("flood-drawer", className), "data-side": side, "data-size": size, scrollBody: true, footer: footer });
|
|
98
|
+
});
|
|
99
|
+
export const AlertDialog = forwardRef(function AlertDialog({ defaultOpen = false, confirmLabel = "Continue", cancelLabel = "Cancel", confirmDisabled = false, ...props }, ref) {
|
|
100
|
+
return _jsx(ConfirmationDialog, { ...props, ref: ref, defaultOpen: defaultOpen, confirmLabel: confirmLabel, cancelLabel: cancelLabel, confirmDisabled: confirmDisabled });
|
|
101
|
+
});
|
|
21
102
|
export const Collapsible = forwardRef(function Collapsible({ open, defaultOpen = false, onOpenChange, disabled = false, controlSize = "md", ...props }, ref) {
|
|
22
103
|
const [local, setLocal] = useState(defaultOpen);
|
|
23
104
|
return _jsx(Disclosure, { ...props, ref: ref, expanded: open ?? local, disabled: disabled, controlSize: controlSize, onExpandedChange: next => {
|
|
@@ -35,7 +116,7 @@ export const Accordion = forwardRef(function Accordion({ label, items, expansion
|
|
|
35
116
|
throw new Error("Flood single Accordion accepts at most one value");
|
|
36
117
|
return _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, className: classes("flood-accordion", className), onKeyDown: event => {
|
|
37
118
|
onKeyDown?.(event);
|
|
38
|
-
if (event.defaultPrevented)
|
|
119
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
|
|
39
120
|
return;
|
|
40
121
|
const buttons = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-disclosure > .flood-disclosure__heading > .flood-disclosure__trigger:not(:disabled)"));
|
|
41
122
|
const index = buttons.indexOf(event.target);
|
|
@@ -76,39 +157,64 @@ export const ToggleGroup = forwardRef(function ToggleGroup({ label, options, sel
|
|
|
76
157
|
throw new Error("Flood single ToggleGroup accepts at most one value");
|
|
77
158
|
if (iconOnly && options.some(option => !option.icon))
|
|
78
159
|
throw new Error("Flood icon-only toggle options require icons");
|
|
79
|
-
return _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, "data-selection-mode": selectionMode, className: classes("flood-button-group", "flood-toggle-group", className), onKeyDown: event => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
160
|
+
return _jsx(ScrollArea, { orientation: "horizontal", tabIndex: -1, role: "group", "aria-label": `${label} scrolling`, className: "flood-connected-scroll", hidden: props.hidden, dir: props.dir, lang: props.lang, style: { maxInlineSize: "100%" }, children: _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, "data-selection-mode": selectionMode, className: classes("flood-button-group", "flood-toggle-group", className), onKeyDown: event => {
|
|
161
|
+
onKeyDown?.(event);
|
|
162
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
|
|
163
|
+
return;
|
|
164
|
+
const buttons = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-toggle:not(:disabled)"));
|
|
165
|
+
const index = buttons.indexOf(event.target);
|
|
166
|
+
if (index < 0)
|
|
167
|
+
return;
|
|
168
|
+
const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
|
|
169
|
+
const nextKey = orientation === "vertical" ? "ArrowDown" : rtl ? "ArrowLeft" : "ArrowRight";
|
|
170
|
+
const previousKey = orientation === "vertical" ? "ArrowUp" : rtl ? "ArrowRight" : "ArrowLeft";
|
|
171
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : event.key === nextKey ? (index + 1) % buttons.length : event.key === previousKey ? (index - 1 + buttons.length) % buttons.length : -1;
|
|
172
|
+
if (next >= 0) {
|
|
173
|
+
event.preventDefault();
|
|
174
|
+
buttons[next].focus();
|
|
175
|
+
}
|
|
176
|
+
}, children: options.map(option => _jsx(Toggle, { size: size, variant: variant, disabled: disabled || option.disabled, pressed: selected.includes(option.value), iconOnly: iconOnly, "aria-label": iconOnly ? option.label : undefined, onPressedChange: active => {
|
|
177
|
+
const next = active ? selectionMode === "single" ? [option.value] : [...new Set([...selected, option.value])] : selected.filter(item => item !== option.value);
|
|
178
|
+
if (value === undefined)
|
|
179
|
+
setLocal(next);
|
|
180
|
+
onValueChange?.([...next]);
|
|
181
|
+
}, children: iconOnly ? option.icon : _jsxs(_Fragment, { children: [option.icon && _jsx(IconSlot, { size: size, children: option.icon }), option.label] }) }, option.value)) }) });
|
|
101
182
|
});
|
|
102
183
|
/** Connected actions keep their ordinary Tab order; this is not a toolbar. */
|
|
103
184
|
export const ButtonGroup = forwardRef(function ButtonGroup({ label, orientation = "horizontal", className, ...props }, ref) {
|
|
104
|
-
return _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, className: classes("flood-button-group", className) });
|
|
185
|
+
return _jsx(ScrollArea, { orientation: "horizontal", tabIndex: -1, role: "group", "aria-label": `${label} scrolling`, className: "flood-connected-scroll", hidden: props.hidden, dir: props.dir, lang: props.lang, style: { maxInlineSize: "100%" }, children: _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, className: classes("flood-button-group", className) }) });
|
|
105
186
|
});
|
|
106
187
|
export const Input = forwardRef(function Input({ invalid, status, controlSize = "md", className, ...props }, ref) {
|
|
107
188
|
return (_jsx("input", { ...props, ref: ref, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", `flood-control--${controlSize}`, className) }));
|
|
108
189
|
});
|
|
109
190
|
/** One real Input with host-owned logical start/end adornments. Ref and native attributes target the input. */
|
|
110
191
|
export const InputGroup = forwardRef(function InputGroup({ start, end, controlSize = "md", className, style, invalid, status, disabled, readOnly, ...props }, ref) {
|
|
111
|
-
return _jsxs("div", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-input-group", `flood-control--${controlSize}`, className), style: style, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, children: [hasFieldContent(start) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--start", children: start }), _jsx(Input, { ...props, ref: ref, controlSize: controlSize, invalid: invalid, status: status, disabled: disabled, readOnly: readOnly }), hasFieldContent(end) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--end", children: end })] });
|
|
192
|
+
return _jsxs("div", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-input-group", `flood-control--${controlSize}`, className), style: style, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, children: [hasFieldContent(start) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--start", children: typeof start === "string" ? _jsx("bdi", { dir: "auto", children: start }) : start }), _jsx(Input, { ...props, ref: ref, controlSize: controlSize, invalid: invalid, status: status, disabled: disabled, readOnly: readOnly }), hasFieldContent(end) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--end", children: typeof end === "string" ? _jsx("bdi", { dir: "auto", children: end }) : end })] });
|
|
193
|
+
});
|
|
194
|
+
export const FileInput = forwardRef(function FileInput({ clearable = true, clearLabel = "Clear files", controlSize = "md", ...props }, ref) {
|
|
195
|
+
return _jsx(FilePicker, { ...props, ref: ref, clearable: clearable, clearLabel: clearLabel, controlSize: controlSize, actionComponent: IconButton, iconComponent: IconSlot });
|
|
196
|
+
});
|
|
197
|
+
export const SearchInput = forwardRef(function SearchInput({ clearable = true, clearLabel = "Clear search", controlSize = "md", ...props }, ref) {
|
|
198
|
+
return _jsx(SearchField, { ...props, ref: ref, clearable: clearable, clearLabel: clearLabel, controlSize: controlSize, group: InputGroup, action: IconButton, icon: IconSlot });
|
|
199
|
+
});
|
|
200
|
+
export const PasswordInput = forwardRef(function PasswordInput({ defaultVisible = false, showPasswordLabel = "Show password", hidePasswordLabel = "Hide password", controlSize = "md", ...props }, ref) {
|
|
201
|
+
return _jsx(PasswordField, { ...props, ref: ref, defaultVisible: defaultVisible, showPasswordLabel: showPasswordLabel, hidePasswordLabel: hidePasswordLabel, controlSize: controlSize, group: InputGroup, action: IconButton, icon: IconSlot });
|
|
202
|
+
});
|
|
203
|
+
/** @since 0.2.5 */
|
|
204
|
+
export const Combobox = forwardRef(function Combobox({ value, defaultValue = "", onValueChange, clearable = false, emptyMessage = "No results found.", listLabel = "Options", toggleLabel = "Show options", clearLabel = "Clear selection", invalidSelectionMessage = "Select an option.", controlSize = "md", ...props }, ref) {
|
|
205
|
+
return _jsx(SearchSelection, { ...props, ref: ref, multiple: false, selectedValues: value === undefined ? undefined : value ? [value] : [], initialValues: defaultValue ? [defaultValue] : [], onSelectionChange: next => onValueChange?.(next[0] ?? ""), clearable: clearable, emptyMessage: emptyMessage, listLabel: listLabel, toggleLabel: toggleLabel, clearLabel: clearLabel, invalidSelectionMessage: invalidSelectionMessage, controlSize: controlSize });
|
|
206
|
+
});
|
|
207
|
+
/** @since 0.2.5 */
|
|
208
|
+
export const MultiSelect = forwardRef(function MultiSelect({ value, defaultValue = [], onValueChange, removeItemLabel = "Remove", clearable = false, emptyMessage = "No results found.", listLabel = "Options", toggleLabel = "Show options", clearLabel = "Clear selection", invalidSelectionMessage = "Select at least one option.", controlSize = "md", ...props }, ref) {
|
|
209
|
+
return _jsx(SearchSelection, { ...props, ref: ref, multiple: true, selectedValues: value, initialValues: defaultValue, onSelectionChange: onValueChange, removeItemLabel: removeItemLabel, clearable: clearable, emptyMessage: emptyMessage, listLabel: listLabel, toggleLabel: toggleLabel, clearLabel: clearLabel, invalidSelectionMessage: invalidSelectionMessage, controlSize: controlSize });
|
|
210
|
+
});
|
|
211
|
+
/** @since 0.2.5 */
|
|
212
|
+
export const NumberInput = forwardRef(function NumberInput({ defaultValue = 0, step = 1, controlSize = "md", decreaseLabel = "Decrease value", increaseLabel = "Increase value", ...props }, ref) {
|
|
213
|
+
return _jsx(NumericStepper, { ...props, ref: ref, defaultValue: defaultValue, step: step, controlSize: controlSize, decreaseLabel: decreaseLabel, increaseLabel: increaseLabel, inputComponent: InputGroup, buttonComponent: IconButton });
|
|
214
|
+
});
|
|
215
|
+
/** @since 0.2.5 */
|
|
216
|
+
export const Slider = forwardRef(function Slider({ defaultValue = [50], min = 0, max = 100, step = 1, showValue = true, readOnly = false, controlSize = "md", ...props }, ref) {
|
|
217
|
+
return _jsx(RangeControl, { ...props, ref: ref, defaultValue: defaultValue, min: min, max: max, step: step, showValue: showValue, readOnly: readOnly, controlSize: controlSize, fieldsetComponent: Fieldset });
|
|
112
218
|
});
|
|
113
219
|
export const Textarea = forwardRef(function Textarea({ invalid, status, controlSize = "md", className, ...props }, ref) {
|
|
114
220
|
return (_jsx("textarea", { ...props, ref: ref, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", "flood-textarea", `flood-control--${controlSize}`, className) }));
|
|
@@ -118,16 +224,16 @@ export const Select = forwardRef(function Select({ invalid, status, controlSize
|
|
|
118
224
|
const listbox = multiple || (size !== undefined && size > 1);
|
|
119
225
|
if (!listbox)
|
|
120
226
|
return _jsx(SingleSelect, { ...props, status: status, invalid: invalid, controlSize: controlSize, className: className, size: size, ref: ref });
|
|
121
|
-
return (_jsx("span", { className: classes("flood-select", `flood-control--${controlSize}`, listbox && "flood-select--listbox"), children: _jsx("select", { ...props, ref: ref, multiple: multiple, size: size, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", "flood-select__control", `flood-control--${controlSize}`, className) }) }));
|
|
227
|
+
return (_jsx("span", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-select", `flood-control--${controlSize}`, listbox && "flood-select--listbox"), children: _jsx("select", { ...props, ref: ref, multiple: multiple, size: size, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", "flood-select__control", `flood-control--${controlSize}`, className) }) }));
|
|
122
228
|
});
|
|
123
|
-
export const Checkbox = forwardRef(function Checkbox({ label, className, ...props }, ref) {
|
|
124
|
-
return (_jsxs("label", { className: classes("flood-choice", className), children: [_jsx(
|
|
229
|
+
export const Checkbox = forwardRef(function Checkbox({ label, indeterminate = false, className, ...props }, ref) {
|
|
230
|
+
return (_jsxs("label", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-choice", className), children: [_jsx(MixedCheckbox, { ...props, ref: ref, indeterminate: indeterminate }), _jsx("span", { children: label })] }));
|
|
125
231
|
});
|
|
126
232
|
export const Radio = forwardRef(function Radio({ label, className, ...props }, ref) {
|
|
127
|
-
return (_jsxs("label", { className: classes("flood-choice", className), children: [_jsx("input", { ...props, ref: ref, type: "radio", className: "flood-check" }), _jsx("span", { children: label })] }));
|
|
233
|
+
return (_jsxs("label", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-choice", className), children: [_jsx("input", { ...props, ref: ref, type: "radio", className: "flood-check" }), _jsx("span", { children: label })] }));
|
|
128
234
|
});
|
|
129
235
|
export const Switch = forwardRef(function Switch({ label, className, ...props }, ref) {
|
|
130
|
-
return (_jsxs("label", { className: classes("flood-choice", "flood-switch", className), children: [_jsx("span", { children: label }), _jsx("input", { ...props, ref: ref, type: "checkbox", role: "switch", className: "flood-switch-input" })] }));
|
|
236
|
+
return (_jsxs("label", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-choice", "flood-switch", className), children: [_jsx("span", { children: label }), _jsx("input", { ...props, ref: ref, type: "checkbox", role: "switch", className: "flood-switch-input" })] }));
|
|
131
237
|
});
|
|
132
238
|
/** A native label; the required marker never substitutes for the control's required prop. */
|
|
133
239
|
export const Label = forwardRef(function Label({ required, children, className, ...props }, ref) {
|
|
@@ -137,10 +243,11 @@ export const Label = forwardRef(function Label({ required, children, className,
|
|
|
137
243
|
export const Fieldset = forwardRef(function Fieldset({ legend, hideLabel = false, hint, error, message, status, children, className, id: explicitId, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...props }, ref) {
|
|
138
244
|
const generatedId = useId();
|
|
139
245
|
const id = explicitId ?? `flood-${generatedId}`;
|
|
246
|
+
const legendRef = useFocusableHidden(hideLabel);
|
|
140
247
|
const hasHint = hasFieldContent(hint), hasError = hasFieldContent(error), hasMessage = hasFieldContent(message) && !hasError;
|
|
141
248
|
const resolvedStatus = resolveFieldStatus(status, hasError, ariaInvalid);
|
|
142
249
|
const description = mergeDescriptions(describedBy, hasHint && `${id}-hint`, hasError && `${id}-error`, hasMessage && `${id}-message`);
|
|
143
|
-
return _jsxs("fieldset", { ...props, ref: ref, id: id, className: classes("flood-fieldset", className), "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, "aria-describedby": description, "aria-invalid": resolvedStatus === "error" || ariaInvalid || undefined, children: [_jsx("legend", { className: classes("flood-legend", hideLabel && "flood-visually-hidden"), children: legend }), _jsxs("div", { className: "flood-fieldset__body", children: [hasHint && _jsx("div", { id: `${id}-hint`, className: "flood-hint", children: hint }), children, hasMessage && _jsx("div", { id: `${id}-message`, className: "flood-message", role: resolvedStatus === "error" ? "alert" : undefined, children: message }), hasError && _jsx("div", { id: `${id}-error`, className: "flood-error", role: "alert", children: error })] })] });
|
|
250
|
+
return _jsxs("fieldset", { ...props, ref: ref, id: id, className: classes("flood-fieldset", className), "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, "aria-describedby": description, "aria-invalid": resolvedStatus === "error" || ariaInvalid || undefined, children: [_jsx("legend", { ref: legendRef, className: classes("flood-legend", hideLabel && "flood-visually-hidden--focusable"), children: legend }), _jsxs("div", { className: "flood-fieldset__body", children: [hasHint && _jsx("div", { id: `${id}-hint`, className: "flood-hint", children: hint }), children, hasMessage && _jsx("div", { id: `${id}-message`, className: "flood-message", role: resolvedStatus === "error" ? "alert" : undefined, children: message }), hasError && _jsx("div", { id: `${id}-error`, className: "flood-error", role: "alert", children: error })] })] });
|
|
144
251
|
});
|
|
145
252
|
export const RadioGroup = forwardRef(function RadioGroup({ value, defaultValue, onValueChange, orientation = "vertical", ...props }, ref) {
|
|
146
253
|
return _jsx(ChoiceGroup, { ...props, ref: ref, kind: "radio", orientation: orientation, selected: value === undefined ? undefined : [value], initial: defaultValue === undefined ? [] : [defaultValue], onSelectionChange: next => onValueChange?.(next[0] ?? ""), fieldsetComponent: Fieldset, choiceComponent: Radio });
|
|
@@ -151,7 +258,7 @@ export const SegmentedControl = forwardRef(function SegmentedControl({ label, op
|
|
|
151
258
|
throw new Error("Flood icon-only segments require icons");
|
|
152
259
|
return _jsx(RadioGroup, { ...props, ref: ref, legend: label, orientation: orientation, className: classes("flood-segmented", `flood-control--${controlSize}`, iconOnly && "flood-segmented--icons", fullWidth && "flood-segmented--full", className), options: options.map(option => ({ ...option, label: _jsxs(_Fragment, { children: [option.icon && _jsx(IconSlot, { size: controlSize, children: option.icon }), iconOnly ? _jsx("span", { className: "flood-segmented__name", children: option.label }) : option.label] }) })), onKeyDown: event => {
|
|
153
260
|
onKeyDown?.(event);
|
|
154
|
-
if (event.defaultPrevented)
|
|
261
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
|
|
155
262
|
return;
|
|
156
263
|
const inputs = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-fieldset__body > .flood-choice-group > .flood-choice > input[type=radio]:not(:disabled)"));
|
|
157
264
|
const index = inputs.indexOf(event.target);
|
|
@@ -174,12 +281,13 @@ export const CheckboxGroup = forwardRef(function CheckboxGroup({ value, defaultV
|
|
|
174
281
|
export function Field({ status, message, id: explicitId, label, hideLabel = false, hint, error, required, describedBy, children, }) {
|
|
175
282
|
const generatedId = useId();
|
|
176
283
|
const id = explicitId ?? `flood-${generatedId}`;
|
|
284
|
+
const labelRef = useFocusableHidden(hideLabel);
|
|
177
285
|
const hasMessage = hasFieldContent(message);
|
|
178
286
|
const hasHint = hasFieldContent(hint);
|
|
179
287
|
const hasError = hasFieldContent(error);
|
|
180
288
|
const resolvedStatus = hasError ? "error" : status;
|
|
181
289
|
const description = mergeDescriptions(describedBy, hasHint && `${id}-hint`, hasError && `${id}-error`, hasMessage && !hasError && `${id}-message`);
|
|
182
|
-
return (_jsxs("div", { className: "flood-field", "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, children: [_jsx(Label, { htmlFor: id, required: required, className: hideLabel ? "flood-visually-hidden" : undefined, children: label }), children({
|
|
290
|
+
return (_jsxs("div", { className: "flood-field", "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, children: [_jsx(Label, { ref: labelRef, htmlFor: id, required: required, className: hideLabel ? "flood-visually-hidden--focusable" : undefined, children: label }), children({
|
|
183
291
|
id,
|
|
184
292
|
"aria-describedby": description,
|
|
185
293
|
"aria-invalid": hasError || resolvedStatus === "error" ? true : undefined,
|
|
@@ -209,6 +317,57 @@ export const Inline = forwardRef(function Inline({ gap = "2", align = "center",
|
|
|
209
317
|
export const Surface = forwardRef(function Surface({ elevation = "default", padding = "6", style, className, ...props }, ref) {
|
|
210
318
|
return _jsx("div", { ...props, ref: ref, "data-flood-elevation": elevation, className: classes("flood-surface", className), style: { background: `var(--flood-sys-color-surface-${elevation})`, padding: `var(--flood-sys-space-${padding})`, ...style } });
|
|
211
319
|
});
|
|
320
|
+
/** Centered content with logical gutters; max width includes padding. @since 0.2.4 */
|
|
321
|
+
export const Container = forwardRef(function Container({ size = "lg", gutter = "6", className, style, ...props }, ref) {
|
|
322
|
+
return _jsx("div", { ...props, ref: ref, className: classes("flood-container", className), style: {
|
|
323
|
+
"--flood-container-max": size === "full" ? "none" : `var(--flood-sys-size-container-${size})`,
|
|
324
|
+
"--flood-container-gutter": `var(--flood-sys-space-${gutter})`, ...style,
|
|
325
|
+
} });
|
|
326
|
+
});
|
|
327
|
+
/** Auto-fit by default; explicit columns retain their count at every width. @since 0.2.4 */
|
|
328
|
+
export const Grid = forwardRef(function Grid({ columns, minItemWidth, gap = "4", className, style, ...props }, ref) {
|
|
329
|
+
return _jsx("div", { ...props, ref: ref, className: classes("flood-grid", className), style: {
|
|
330
|
+
gridTemplateColumns: columns ? `repeat(${columns}, minmax(0, 1fr))` : undefined,
|
|
331
|
+
"--flood-grid-min": minItemWidth, "--flood-layout-gap": `var(--flood-sys-space-${gap})`, ...style,
|
|
332
|
+
} });
|
|
333
|
+
});
|
|
334
|
+
/** Nonvisual content, with optional reveal when a descendant receives focus. @since 0.2.4 */
|
|
335
|
+
export const VisuallyHidden = forwardRef(function VisuallyHidden({ as = "span", focusable = false, className, ...props }, ref) {
|
|
336
|
+
const hiddenRef = useFocusableHidden(focusable, ref);
|
|
337
|
+
return createElement(as, { ...props, ref: hiddenRef, className: classes(focusable ? "flood-visually-hidden--focusable" : "flood-visually-hidden", className) });
|
|
338
|
+
});
|
|
339
|
+
/** Reserve media geometry. Child content may overflow rather than being clipped. @since 0.2.4 */
|
|
340
|
+
export const AspectRatio = forwardRef(function AspectRatio({ ratio = 16 / 9, className, style, children, ...props }, ref) {
|
|
341
|
+
if (!Number.isFinite(ratio) || ratio <= 0)
|
|
342
|
+
throw new RangeError("Flood AspectRatio requires a finite positive ratio");
|
|
343
|
+
return _jsx("div", { ...props, ref: ref, className: classes("flood-aspect-ratio", className), style: { aspectRatio: String(ratio), ...style }, children: _jsx("div", { className: "flood-aspect-ratio__content", children: children }) });
|
|
344
|
+
});
|
|
345
|
+
/** Native image semantics, explicit alternative text and bounded media fitting. @since 0.2.4 */
|
|
346
|
+
export const Image = forwardRef(function Image({ alt, fit = "cover", className, ...props }, ref) {
|
|
347
|
+
if (typeof alt !== "string")
|
|
348
|
+
throw new TypeError("Flood Image requires explicit alt text (empty for decorative images)");
|
|
349
|
+
return _jsx("img", { ...props, ref: ref, alt: alt, "data-fit": fit, className: classes("flood-image", className) });
|
|
350
|
+
});
|
|
351
|
+
/** Native list items supplied by the host; hidden markers retain list semantics. @since 0.2.4 */
|
|
352
|
+
export const List = forwardRef(function List({ ordered = false, start, reversed, marker = true, gap = "2", className, style, ...props }, ref) {
|
|
353
|
+
if (start !== undefined && !Number.isSafeInteger(start))
|
|
354
|
+
throw new RangeError("Flood List start requires a safe integer");
|
|
355
|
+
return createElement(ordered ? "ol" : "ul", { ...props, ref, start: ordered ? start : undefined,
|
|
356
|
+
reversed: ordered ? reversed : undefined, role: marker ? undefined : "list", "data-marker": marker,
|
|
357
|
+
className: classes("flood-list", className), style: { "--flood-layout-gap": `var(--flood-sys-space-${gap})`, ...style } });
|
|
358
|
+
});
|
|
359
|
+
/** Static empty content, not loading, an error boundary or a live announcement. @since 0.2.4 */
|
|
360
|
+
export const EmptyState = forwardRef(function EmptyState({ title, description, icon, actions, headingLevel = 2, density = "comfortable", className, ...props }, ref) {
|
|
361
|
+
if (typeof title !== "string" || !title.trim())
|
|
362
|
+
throw new Error("Flood EmptyState requires a nonempty title");
|
|
363
|
+
return _jsxs(Stack, { ...props, ref: ref, gap: "4", "data-density": density, className: classes("flood-empty-state", className), children: [hasFieldContent(icon) && _jsx("div", { className: "flood-empty-state__icon", children: _jsx(IconSlot, { size: "2xl", children: icon }) }), _jsxs(Stack, { gap: "1", children: [_jsx(Heading, { level: headingLevel, size: "section", children: title }), hasFieldContent(description) && _jsx(Text, { as: "div", size: "compact", tone: "subtle", children: description })] }), hasFieldContent(actions) && _jsx(Inline, { gap: "2", className: "flood-empty-state__actions", children: actions })] });
|
|
364
|
+
});
|
|
365
|
+
/** A structured content surface, not an interactive wrapper. @since 0.2.4 */
|
|
366
|
+
export const Card = forwardRef(function Card({ title, description, actions, footer, headingLevel = 2, density = "comfortable", elevation = "default", children, className, ...props }, ref) {
|
|
367
|
+
const present = hasFieldContent;
|
|
368
|
+
const heading = present(title), hint = present(description), action = present(actions);
|
|
369
|
+
return _jsxs(Surface, { ...props, ref: ref, elevation: elevation, padding: density === "compact" ? "4" : "6", "data-density": density, className: classes("flood-card", className), children: [(heading || hint || action) && _jsxs("div", { className: "flood-card__header", children: [(heading || hint) && _jsxs("div", { className: "flood-card__intro", children: [heading && _jsx(Heading, { level: headingLevel, size: "section", children: title }), hint && _jsx(Text, { as: "div", size: "compact", tone: "subtle", children: description })] }), action && _jsx("div", { className: "flood-card__actions", children: actions })] }), present(children) && _jsx("div", { className: "flood-card__body", children: children }), present(footer) && _jsx("div", { className: "flood-card__footer", children: footer })] });
|
|
370
|
+
});
|
|
212
371
|
/** Adapter for the host's SVG icon package; glyphs are not bundled. */
|
|
213
372
|
export const IconSlot = forwardRef(function IconSlot({ size = "md", label, className, children, ...props }, ref) {
|
|
214
373
|
return _jsx("span", { ...props, ref: ref, role: label ? "img" : undefined, "aria-label": label, "aria-hidden": label ? undefined : true, className: classes("flood-icon", `flood-icon--${size}`, className), children: children });
|
|
@@ -220,10 +379,38 @@ export const Divider = forwardRef(function Divider({ className, ...props }, ref)
|
|
|
220
379
|
return _jsx("hr", { ...props, ref: ref, className: classes("flood-divider", className) });
|
|
221
380
|
});
|
|
222
381
|
/** Selectable code with an optional host-owned action; no clipboard side effects. */
|
|
223
|
-
export const CodeSnippet = forwardRef(function CodeSnippet({ code, action, className, ...props }, ref) {
|
|
224
|
-
return (_jsxs("div", { ...props, ref: ref, className: classes("flood-code-snippet", className), children: [_jsx("code", { className: "flood-code-snippet__code", children: code }), action != null && _jsx("div", { className: "flood-code-snippet__action", children: action })] }));
|
|
382
|
+
export const CodeSnippet = forwardRef(function CodeSnippet({ code, codeDirection = "ltr", action, className, ...props }, ref) {
|
|
383
|
+
return (_jsxs("div", { ...props, ref: ref, className: classes("flood-code-snippet", className), children: [_jsx("code", { dir: codeDirection, className: "flood-code-snippet__code", children: code }), action != null && _jsx("div", { className: "flood-code-snippet__action", children: action })] }));
|
|
225
384
|
});
|
|
226
385
|
/** Native scrolling with inset overlay tracks; the ref targets the scrollable viewport. */
|
|
227
386
|
export const ScrollArea = forwardRef(function ScrollArea({ orientation = "vertical", contentInset = "scrollbar", ...props }, ref) {
|
|
228
387
|
return _jsx(OverlayScrollArea, { ...props, orientation: orientation, contentInset: contentInset, ref: ref });
|
|
229
388
|
});
|
|
389
|
+
export const CopyButton = forwardRef(function CopyButton({ copyLabel = "Copy", copiedLabel = "Copied", errorLabel = "Failed", resetDelay = 2000, size = "md", variant = "subtle", ...props }, ref) {
|
|
390
|
+
return _jsx(ClipboardAction, { ...props, ref: ref, copyLabel: copyLabel, copiedLabel: copiedLabel, errorLabel: errorLabel, resetDelay: resetDelay, size: size, variant: variant });
|
|
391
|
+
});
|
|
392
|
+
export const ToastRegion = forwardRef(function ToastRegion({ label = "Notifications", closeLabel = "Dismiss notification", placement = "bottom-end", limit = 3, inline = false, hotkey = true, ...props }, ref) {
|
|
393
|
+
return _jsx(NotificationRegion, { ...props, ref: ref, label: label, closeLabel: closeLabel, placement: placement, limit: limit, inline: inline, hotkey: hotkey });
|
|
394
|
+
});
|
|
395
|
+
export const AvatarGroup = forwardRef(function AvatarGroup({ max = 4, size = "md", ...props }, ref) { return _jsx(GroupedAvatars, { ...props, ref: ref, max: max, size: size }); });
|
|
396
|
+
export const StatusIndicator = forwardRef(function StatusIndicator({ tone = "neutral", size = "md", announce = false, ...props }, ref) { return _jsx(NamedStatus, { ...props, ref: ref, tone: tone, size: size, announce: announce }); });
|
|
397
|
+
export const Figure = forwardRef(function Figure({ captionPosition = "after", ...props }, ref) { return _jsx(CaptionedFigure, { ...props, ref: ref, captionPosition: captionPosition }); });
|
|
398
|
+
export const Blockquote = forwardRef(function Blockquote(props, ref) {
|
|
399
|
+
return _jsx(AttributedQuote, { ...props, ref: ref });
|
|
400
|
+
});
|
|
401
|
+
export const InlineCode = forwardRef(function InlineCode({ size = "md", wrap = true, ...props }, ref) { return _jsx(InlineCodeText, { ...props, ref: ref, size: size, wrap: wrap }); });
|
|
402
|
+
export const Metric = forwardRef(function Metric({ size = "md", ...props }, ref) { return _jsx(MeasuredValue, { ...props, ref: ref, size: size }); });
|
|
403
|
+
export const Timeline = forwardRef(function Timeline(props, ref) {
|
|
404
|
+
return _jsx(EventTimeline, { ...props, ref: ref });
|
|
405
|
+
});
|
|
406
|
+
export const NavigationList = forwardRef(function NavigationList({ size = "md", ...props }, ref) { return _jsx(NavigationMenu, { ...props, ref: ref, size: size }); });
|
|
407
|
+
export const Steps = forwardRef(function Steps({ orientation = "horizontal", ...props }, ref) { return _jsx(StepSequence, { ...props, ref: ref, orientation: orientation }); });
|
|
408
|
+
export const Section = forwardRef(function Section({ headingLevel = 2, headingSize = "section", ...props }, ref) { return _jsx(ContentSection, { ...props, ref: ref, headingLevel: headingLevel, headingSize: headingSize }); });
|
|
409
|
+
export const Split = forwardRef(function Split({ side = "end", ratio = "main", collapseAt = "md", gap = "6", ...props }, ref) { return _jsx(SplitLayout, { ...props, ref: ref, side: side, ratio: ratio, collapseAt: collapseAt, gap: gap }); });
|
|
410
|
+
export const Center = forwardRef(function Center({ inline = false, ...props }, ref) { return _jsx(CenterLayout, { ...props, ref: ref, inline: inline }); });
|
|
411
|
+
export const SkipLink = forwardRef(function SkipLink({ children = "Skip to content", ...props }, ref) { return _jsx(SkipNavigation, { ...props, ref: ref, children: children }); });
|
|
412
|
+
export const ListBox = forwardRef(function ListBox({ multiple = false, disabled = false, readOnly = false, hideLabel = false, controlSize = "md", ...props }, ref) { return _jsx(CollectionListBox, { ...props, ref: ref, multiple: multiple, disabled: disabled, readOnly: readOnly, hideLabel: hideLabel, controlSize: controlSize }); });
|
|
413
|
+
export const PinInput = forwardRef(function PinInput({ length = 6, mode = "numeric", mask = false, hideLabel = false, controlSize = "md", ...props }, ref) { return _jsx(SegmentedPinInput, { ...props, ref: ref, length: length, mode: mode, mask: mask, hideLabel: hideLabel, controlSize: controlSize }); });
|
|
414
|
+
export const Rating = forwardRef(function Rating({ max = 5, defaultValue = 0, hideLabel = false, readOnly = false, size = "md", ...props }, ref) { return _jsx(StarRating, { ...props, ref: ref, max: max, defaultValue: defaultValue, hideLabel: hideLabel, readOnly: readOnly, size: size }); });
|
|
415
|
+
export const Dropzone = forwardRef(function Dropzone({ description = "Drop files here or choose from your device.", multiple = true, ...props }, ref) { return _jsx(FileDropzone, { ...props, ref: ref, description: description, multiple: multiple }); });
|
|
416
|
+
export const CharacterCount = forwardRef(function CharacterCount({ mode = "count", label = "Characters", overLimitLabel = "Over limit", ...props }, ref) { return _jsx(SupportingCharacterCount, { ...props, ref: ref, mode: mode, label: label, overLimitLabel: overLimitLabel }); });
|
package/dist/menu.d.ts
ADDED
package/dist/menu.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { Button, Kbd, ScrollArea } from "./index.js";
|
|
4
|
+
import { positionLayer, registerLayer, returnFocus, tabbables } from "./overlay-runtime.js";
|
|
5
|
+
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
6
|
+
export const ActionMenu = forwardRef(function ActionMenu({ label, trigger, items, onAction, open: controlled, defaultOpen, onOpenChange, disabled, side = "bottom", align = "start", controlSize = "sm", id: explicitId, className, onKeyDown, ...props }, ref) {
|
|
7
|
+
const [local, setLocal] = useState(Boolean(defaultOpen)), open = controlled ?? local;
|
|
8
|
+
const anchor = useRef(null), panel = useRef(null);
|
|
9
|
+
const generated = useId(), id = explicitId ?? `flood-menu-${generated}`;
|
|
10
|
+
const pending = useRef(undefined);
|
|
11
|
+
const edge = useRef("first"), search = useRef({ text: "", time: 0 });
|
|
12
|
+
if (!items.some(item => item.type !== "separator"))
|
|
13
|
+
throw new Error("Flood Menu requires at least one action item");
|
|
14
|
+
if (new Set(items.map(item => item.id)).size !== items.length || items.some(item => !item.id.trim() || (item.type !== "separator" && !item.label.trim())))
|
|
15
|
+
throw new Error("Flood menu items need unique nonempty IDs and action labels");
|
|
16
|
+
const request = (next) => {
|
|
17
|
+
if (pending.current === next)
|
|
18
|
+
return;
|
|
19
|
+
pending.current = next;
|
|
20
|
+
queueMicrotask(() => { pending.current = undefined; });
|
|
21
|
+
if (controlled === undefined)
|
|
22
|
+
setLocal(next);
|
|
23
|
+
onOpenChange?.(next);
|
|
24
|
+
};
|
|
25
|
+
const current = useRef({ open, request });
|
|
26
|
+
useBrowserLayoutEffect(() => { current.current = { open, request }; });
|
|
27
|
+
const setAnchor = useCallback((node) => { anchor.current = node; }, []);
|
|
28
|
+
useImperativeHandle(ref, () => panel.current, []);
|
|
29
|
+
const buttons = () => Array.from(panel.current?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
30
|
+
const focusEdge = () => { const all = buttons(); ((edge.current === "last" ? all.at(-1) : all[0]) ?? panel.current)?.focus({ preventScroll: true }); };
|
|
31
|
+
useBrowserLayoutEffect(() => {
|
|
32
|
+
if (!open || !panel.current)
|
|
33
|
+
return;
|
|
34
|
+
const element = panel.current, document = element.ownerDocument, view = document.defaultView;
|
|
35
|
+
let alive = true, frame = 0, unregister = () => { };
|
|
36
|
+
const start = () => {
|
|
37
|
+
if (!alive || !anchor.current?.isConnected)
|
|
38
|
+
return;
|
|
39
|
+
const parent = anchor.current.closest("dialog");
|
|
40
|
+
if (parent && !parent.open) {
|
|
41
|
+
frame = view.requestAnimationFrame(start);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (typeof element.showPopover !== "function")
|
|
45
|
+
throw new Error("Flood Menu requires the native Popover API");
|
|
46
|
+
element.style.visibility = "hidden";
|
|
47
|
+
element.showPopover();
|
|
48
|
+
positionLayer(anchor.current, element, side, align);
|
|
49
|
+
unregister = registerLayer(document, { panel: element, anchor: () => anchor.current, close: () => current.current.request(false) });
|
|
50
|
+
search.current = { text: "", time: 0 };
|
|
51
|
+
focusEdge();
|
|
52
|
+
const update = () => { if (!alive)
|
|
53
|
+
return; if (anchor.current?.isConnected)
|
|
54
|
+
positionLayer(anchor.current, element, side, align); frame = view.requestAnimationFrame(update); };
|
|
55
|
+
frame = view.requestAnimationFrame(update);
|
|
56
|
+
};
|
|
57
|
+
start();
|
|
58
|
+
const toggle = (event) => {
|
|
59
|
+
if (!alive || event.newState !== "closed" || element.matches(":popover-open") || !current.current.open)
|
|
60
|
+
return;
|
|
61
|
+
current.current.request(false);
|
|
62
|
+
queueMicrotask(() => { if (alive && current.current.open && element.isConnected && !element.matches(":popover-open") && anchor.current?.isConnected) {
|
|
63
|
+
element.showPopover();
|
|
64
|
+
positionLayer(anchor.current, element, side, align);
|
|
65
|
+
focusEdge();
|
|
66
|
+
} });
|
|
67
|
+
};
|
|
68
|
+
element.addEventListener("toggle", toggle);
|
|
69
|
+
return () => { alive = false; view.cancelAnimationFrame(frame); unregister(); element.removeEventListener("toggle", toggle); returnFocus(element, anchor.current); if (element.matches(":popover-open"))
|
|
70
|
+
element.hidePopover(); };
|
|
71
|
+
}, [open, side, align]);
|
|
72
|
+
useBrowserLayoutEffect(() => {
|
|
73
|
+
if (open && panel.current?.matches(":popover-open") && panel.current.ownerDocument.activeElement === panel.current.ownerDocument.body)
|
|
74
|
+
focusEdge();
|
|
75
|
+
}, [items, open]);
|
|
76
|
+
const triggerProps = { ref: setAnchor, type: "button", disabled, "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": id,
|
|
77
|
+
onClick: () => { edge.current = "first"; request(!open); },
|
|
78
|
+
onKeyDown: event => { if (event.defaultPrevented || disabled || event.altKey || event.ctrlKey || event.metaKey || !["ArrowDown", "ArrowUp"].includes(event.key))
|
|
79
|
+
return; event.preventDefault(); edge.current = event.key === "ArrowUp" ? "last" : "first"; if (open && panel.current?.matches(":popover-open"))
|
|
80
|
+
focusEdge();
|
|
81
|
+
else
|
|
82
|
+
request(true); },
|
|
83
|
+
};
|
|
84
|
+
const hasIcons = items.some(item => item.type !== "separator" && item.icon != null);
|
|
85
|
+
return _jsxs(_Fragment, { children: [trigger(triggerProps), _jsx("div", { ...props, ref: panel, id: id, popover: "manual", role: "menu", "aria-label": label, "aria-orientation": "vertical", tabIndex: -1, className: ["flood-overlay-panel", "flood-menu", className].filter(Boolean).join(" "), onKeyDown: event => {
|
|
86
|
+
onKeyDown?.(event);
|
|
87
|
+
if (event.defaultPrevented)
|
|
88
|
+
return;
|
|
89
|
+
const all = buttons(), index = all.indexOf(event.target);
|
|
90
|
+
if (event.key === "Tab") {
|
|
91
|
+
const button = anchor.current;
|
|
92
|
+
if (button) {
|
|
93
|
+
const scope = button.closest("dialog[open]") ?? button.ownerDocument.body;
|
|
94
|
+
const stops = tabbables(scope).filter(node => !panel.current?.contains(node));
|
|
95
|
+
const at = stops.indexOf(button), target = event.shiftKey ? button : at >= 0 ? stops[at + 1] : undefined;
|
|
96
|
+
if (target) {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
target.focus();
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
button.focus();
|
|
102
|
+
}
|
|
103
|
+
request(false);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
|
|
107
|
+
return;
|
|
108
|
+
let next = event.key === "Home" ? 0 : event.key === "End" ? all.length - 1 : event.key === "ArrowDown" ? (index + 1) % all.length : event.key === "ArrowUp" ? (index - 1 + all.length) % all.length : -1;
|
|
109
|
+
if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key))
|
|
110
|
+
search.current = { text: "", time: 0 };
|
|
111
|
+
if (event.key.length === 1 && event.key !== " ") {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
search.current = { text: (now - search.current.time < 700 ? search.current.text : "") + event.key.toLocaleLowerCase(), time: now };
|
|
114
|
+
const text = Array.from(search.current.text).every(char => char === search.current.text[0]) ? search.current.text[0] : search.current.text;
|
|
115
|
+
const start = text.length === 1 ? index + 1 : Math.max(0, index);
|
|
116
|
+
next = Array.from({ length: all.length }, (_, offset) => (start + offset) % all.length).find(candidate => all[candidate].dataset.label?.toLocaleLowerCase().startsWith(text)) ?? -1;
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
}
|
|
119
|
+
if (next >= 0 && all[next]) {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
all[next].focus({ preventScroll: true });
|
|
122
|
+
all[next].scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
123
|
+
}
|
|
124
|
+
}, children: open && _jsx(ScrollArea, { orientation: "vertical", role: "group", tabIndex: -1, className: "flood-menu__viewport", children: items.map(item => item.type === "separator" ? _jsx("div", { role: "separator", className: "flood-menu__separator" }, item.id) :
|
|
125
|
+
_jsxs(Button, { role: "menuitem", tabIndex: -1, variant: "ghost", size: controlSize, "aria-label": item.label, "aria-disabled": item.disabled || undefined, "data-label": item.label, "data-tone": item.tone, className: "flood-menu__item", onPointerMove: event => { if (event.pointerType !== "touch" && event.currentTarget.ownerDocument.activeElement !== event.currentTarget)
|
|
126
|
+
event.currentTarget.focus({ preventScroll: true }); }, onClick: event => { if (item.disabled) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
return;
|
|
129
|
+
} request(false); onAction?.(item.id); }, children: [hasIcons && _jsx("span", { className: "flood-menu__icon", "aria-hidden": "true", children: item.icon }), _jsx("span", { className: "flood-menu__label", children: item.label }), item.shortcut && _jsx("span", { className: "flood-menu__shortcut", "aria-hidden": "true", children: _jsx(Kbd, { children: item.shortcut }) })] }, item.id)) }) })] });
|
|
130
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NavigationListProps, StepsProps, SectionProps, SplitProps, CenterProps, SkipLinkProps } from "./index.js";
|
|
2
|
+
export declare const NavigationMenu: import("react").ForwardRefExoticComponent<NavigationListProps & import("react").RefAttributes<HTMLElement>>;
|
|
3
|
+
export declare const StepSequence: import("react").ForwardRefExoticComponent<StepsProps & import("react").RefAttributes<HTMLOListElement>>;
|
|
4
|
+
export declare const ContentSection: import("react").ForwardRefExoticComponent<SectionProps & import("react").RefAttributes<HTMLElement>>;
|
|
5
|
+
export declare const SplitLayout: import("react").ForwardRefExoticComponent<SplitProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
6
|
+
export declare const CenterLayout: import("react").ForwardRefExoticComponent<CenterProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
7
|
+
export declare const SkipNavigation: import("react").ForwardRefExoticComponent<SkipLinkProps & import("react").RefAttributes<HTMLAnchorElement>>;
|