@abuzareal/contour-ui 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/COMPOSITION.md +28 -28
- package/CONTRACTS.md +9 -3
- package/FORMS.md +94 -0
- package/NEXTJS.md +6 -2
- package/OVERLAYS.md +52 -0
- package/PUBLIC-API.md +181 -162
- package/README.md +44 -28
- package/RELEASING.md +1 -1
- package/SIZE.md +45 -0
- package/STYLING.md +68 -0
- package/dist/actions.css +7 -0
- package/dist/components/hero/Sculpture.js +2 -1
- package/dist/components/three/ObjectStudy.js +2 -1
- package/dist/components/ui/Alert.d.ts +2 -1
- package/dist/components/ui/Alert.js +3 -3
- package/dist/components/ui/Avatar.d.ts +3 -1
- package/dist/components/ui/Avatar.js +3 -3
- package/dist/components/ui/Badge.js +2 -2
- package/dist/components/ui/BrandMark.js +2 -1
- package/dist/components/ui/Breadcrumbs.d.ts +2 -1
- package/dist/components/ui/Breadcrumbs.js +3 -2
- package/dist/components/ui/Button.d.ts +2 -0
- package/dist/components/ui/Button.js +3 -2
- package/dist/components/ui/Card.js +2 -2
- package/dist/components/ui/Checkbox.d.ts +2 -2
- package/dist/components/ui/Checkbox.js +14 -5
- package/dist/components/ui/CountUp.d.ts +2 -1
- package/dist/components/ui/CountUp.js +6 -3
- package/dist/components/ui/DataTable.d.ts +1 -0
- package/dist/components/ui/DataTable.js +12 -8
- package/dist/components/ui/DepthButton.js +2 -2
- package/dist/components/ui/Dialog.d.ts +5 -1
- package/dist/components/ui/Dialog.js +49 -18
- package/dist/components/ui/Disclosure.js +2 -1
- package/dist/components/ui/Divider.js +3 -2
- package/dist/components/ui/DropdownMenu.d.ts +11 -4
- package/dist/components/ui/DropdownMenu.js +109 -66
- package/dist/components/ui/EmptyState.js +2 -1
- package/dist/components/ui/Field.js +2 -2
- package/dist/components/ui/FlipCard.js +2 -2
- package/dist/components/ui/FloatingPortal.d.ts +10 -0
- package/dist/components/ui/FloatingPortal.js +51 -0
- package/dist/components/ui/IconButton.js +2 -2
- package/dist/components/ui/Kbd.js +2 -1
- package/dist/components/ui/Magnetic.js +2 -1
- package/dist/components/ui/Marquee.js +3 -2
- package/dist/components/ui/OverlayTrigger.d.ts +11 -0
- package/dist/components/ui/OverlayTrigger.js +5 -0
- package/dist/components/ui/Pagination.d.ts +5 -1
- package/dist/components/ui/Pagination.js +3 -2
- package/dist/components/ui/Popover.d.ts +18 -8
- package/dist/components/ui/Popover.js +58 -17
- package/dist/components/ui/ProgressBar.js +2 -2
- package/dist/components/ui/RadioGroup.d.ts +13 -5
- package/dist/components/ui/RadioGroup.js +36 -9
- package/dist/components/ui/Reveal.d.ts +0 -1
- package/dist/components/ui/Reveal.js +4 -3
- package/dist/components/ui/RevealHeading.js +2 -1
- package/dist/components/ui/ScrollProgress.js +2 -1
- package/dist/components/ui/SectionLabel.js +2 -1
- package/dist/components/ui/SegmentedControl.js +6 -3
- package/dist/components/ui/SelectField.js +18 -3
- package/dist/components/ui/Skeleton.js +2 -2
- package/dist/components/ui/Slider.d.ts +23 -7
- package/dist/components/ui/Slider.js +43 -6
- package/dist/components/ui/Spinner.js +2 -2
- package/dist/components/ui/Stat.js +2 -1
- package/dist/components/ui/Stepper.js +3 -3
- package/dist/components/ui/Switch.d.ts +27 -5
- package/dist/components/ui/Switch.js +51 -5
- package/dist/components/ui/Tabs.js +7 -5
- package/dist/components/ui/Tag.d.ts +2 -1
- package/dist/components/ui/Tag.js +4 -3
- package/dist/components/ui/TextArea.js +4 -1
- package/dist/components/ui/TextField.js +8 -3
- package/dist/components/ui/ThemeScope.d.ts +13 -0
- package/dist/components/ui/ThemeScope.js +10 -0
- package/dist/components/ui/ThemeToggle.d.ts +3 -0
- package/dist/components/ui/ThemeToggle.js +4 -3
- package/dist/components/ui/TiltCard.js +3 -2
- package/dist/components/ui/Timeline.js +2 -2
- package/dist/components/ui/ToastProvider.d.ts +5 -2
- package/dist/components/ui/ToastProvider.js +5 -5
- package/dist/components/ui/Tooltip.d.ts +4 -4
- package/dist/components/ui/Tooltip.js +70 -10
- package/dist/data-display.css +5 -0
- package/dist/effects.css +6 -0
- package/dist/feedback.css +5 -0
- package/dist/fonts-latin.css +10 -4
- package/dist/fonts.css +8 -0
- package/dist/forms.css +5 -0
- package/dist/hooks/useDismiss.d.ts +12 -3
- package/dist/hooks/useDismiss.js +5 -21
- package/dist/hooks/useFloatingPanel.d.ts +14 -0
- package/dist/hooks/useFloatingPanel.js +157 -0
- package/dist/hooks/useFormReset.d.ts +1 -1
- package/dist/hooks/useLayerDismiss.d.ts +4 -0
- package/dist/hooks/useLayerDismiss.js +48 -0
- package/dist/hooks/useMobileMenu.d.ts +0 -1
- package/dist/hooks/useMobileMenu.js +11 -16
- package/dist/hooks/usePanelFocus.d.ts +10 -0
- package/dist/hooks/usePanelFocus.js +60 -0
- package/dist/hooks/useTheme.d.ts +1 -0
- package/dist/hooks/useTheme.js +18 -5
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -0
- package/dist/kit/forms.css +11 -2
- package/dist/kit/navigation.css +4 -0
- package/dist/kit/overlays.css +8 -4
- package/dist/lib/classNames.d.ts +2 -0
- package/dist/lib/classNames.js +16 -1
- package/dist/lib/componentClasses.d.ts +1 -0
- package/dist/lib/componentClasses.js +2 -0
- package/dist/lib/keyboard.d.ts +1 -1
- package/dist/lib/keyboard.js +5 -2
- package/dist/lib/layers.d.ts +18 -0
- package/dist/lib/layers.js +128 -0
- package/dist/lib/themeScope.d.ts +5 -0
- package/dist/lib/themeScope.js +3 -0
- package/dist/minimal-reset.css +6 -0
- package/dist/navigation.css +5 -0
- package/dist/overlays.css +5 -0
- package/dist/scoped/base.css +183 -0
- package/dist/scoped/kit/actions.css +209 -0
- package/dist/scoped/kit/data-display.css +492 -0
- package/dist/scoped/kit/effects.css +140 -0
- package/dist/scoped/kit/feedback.css +309 -0
- package/dist/scoped/kit/forms.css +370 -0
- package/dist/scoped/kit/navigation.css +266 -0
- package/dist/scoped/kit/overlays.css +299 -0
- package/dist/scoped/marquee.css +58 -0
- package/dist/scoped/spinner.css +20 -0
- package/dist/scoped/theme-toggle.css +38 -0
- package/dist/scoped/tokens.css +119 -0
- package/dist/scoped/ui.css +127 -0
- package/dist/scoped/utilities.css +17 -0
- package/dist/scoped-tokens.css +1 -0
- package/dist/scoped.css +12 -0
- package/dist/theme-toggle.css +2 -2
- package/dist/tokens.json +710 -0
- package/examples/forms/HookFormSettingsForm.tsx +176 -0
- package/examples/forms/ManualSettingsForm-0.3.0.tsx +102 -0
- package/examples/forms/NativeSettingsForm.tsx +119 -0
- package/examples/forms/settings.ts +61 -0
- package/examples/forms/useSubmission.ts +77 -0
- package/package.json +30 -6
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ccx } from "../../lib/classNames.js";
|
|
3
4
|
/** Page navigation with previous/next controls and a condensed list of two-digit page numbers. */
|
|
4
5
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
|
5
6
|
import { formatIndex } from "../../lib/format.js";
|
|
@@ -11,6 +12,6 @@ function visiblePages(page, pageCount) {
|
|
|
11
12
|
.sort((a, b) => a - b);
|
|
12
13
|
return sorted.flatMap((value, index) => index > 0 && value - sorted[index - 1] > 1 ? ["gap", value] : [value]);
|
|
13
14
|
}
|
|
14
|
-
export default function Pagination({ page, pageCount, onChange, label = "Pagination", }) {
|
|
15
|
-
return (_jsxs("nav", { "aria-label": label, className: "pagination", children: [_jsx("button", { type: "button", className: "pagination-step", "aria-label":
|
|
15
|
+
export default function Pagination({ page, pageCount, onChange, label = "Pagination", previousLabel = "Previous page", nextLabel = "Next page", pageLabel = (value) => `Page ${value}`, formatPage = formatIndex, }) {
|
|
16
|
+
return (_jsxs("nav", { "aria-label": label, className: ccx("pagination"), children: [_jsx("button", { type: "button", className: ccx("pagination-step"), "aria-label": previousLabel, disabled: page === 1, onClick: () => onChange(page - 1), children: _jsx(ArrowLeft, { size: 16 }) }), _jsx("ol", { className: ccx("mono"), children: visiblePages(page, pageCount).map((item, index) => item === "gap" ? (_jsx("li", { "aria-hidden": "true", className: ccx("pagination-gap"), children: "\u2026" }, `gap-${index}`)) : (_jsx("li", { children: _jsx("button", { type: "button", className: ccx("pagination-page"), "aria-label": pageLabel(Number(item)), "aria-current": item === page ? "page" : undefined, onClick: () => onChange(Number(item)), children: formatPage(Number(item)) }) }, item))) }), _jsx("button", { type: "button", className: ccx("pagination-step"), "aria-label": nextLabel, disabled: page === pageCount, onClick: () => onChange(page + 1), children: _jsx(ArrowRight, { size: 16 }) })] }));
|
|
16
17
|
}
|
|
@@ -1,28 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
import { type
|
|
1
|
+
import { type ReactNode, type ReactElement } from "react";
|
|
2
|
+
import { type PanelFocusOptions } from "../../hooks/usePanelFocus.js";
|
|
3
|
+
import type { FloatingOptions } from "../../hooks/useFloatingPanel.js";
|
|
4
|
+
import { type DismissReason } from "../../lib/layers.js";
|
|
3
5
|
import type { NativePartProps } from "../../lib/componentProps.js";
|
|
4
|
-
export type PopoverProps = Omit<NativePartProps<"div">, "ref" | "title"> & {
|
|
5
|
-
/** Content inside the owned trigger button, not a replacement interactive element. */
|
|
6
|
+
export type PopoverProps = Omit<NativePartProps<"div">, "ref" | "title"> & FloatingOptions & PanelFocusOptions & {
|
|
6
7
|
trigger: ReactNode;
|
|
7
8
|
title: string;
|
|
8
9
|
children: ReactNode;
|
|
9
|
-
align?: "start" | "end";
|
|
10
10
|
open?: boolean;
|
|
11
11
|
defaultOpen?: boolean;
|
|
12
12
|
onOpenChange?: (open: boolean) => void;
|
|
13
13
|
triggerProps?: NativePartProps<"button">;
|
|
14
14
|
panelProps?: NativePartProps<"div">;
|
|
15
|
+
/** Render one ref-forwarding native button with the supplied owned props. */
|
|
16
|
+
renderTrigger?: (props: NativePartProps<"button"> & {
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}) => ReactElement;
|
|
19
|
+
/** preventDefault vetoes dismissal. Native document events still propagate. */
|
|
20
|
+
onDismiss?: (reason: DismissReason, event: KeyboardEvent | PointerEvent) => void;
|
|
15
21
|
};
|
|
16
|
-
declare const Popover: import("react").ForwardRefExoticComponent<Omit<NativePartProps<"div">, "title" | "ref"> & {
|
|
17
|
-
/** Content inside the owned trigger button, not a replacement interactive element. */
|
|
22
|
+
declare const Popover: import("react").ForwardRefExoticComponent<Omit<NativePartProps<"div">, "title" | "ref"> & FloatingOptions & PanelFocusOptions & {
|
|
18
23
|
trigger: ReactNode;
|
|
19
24
|
title: string;
|
|
20
25
|
children: ReactNode;
|
|
21
|
-
align?: "start" | "end";
|
|
22
26
|
open?: boolean;
|
|
23
27
|
defaultOpen?: boolean;
|
|
24
28
|
onOpenChange?: (open: boolean) => void;
|
|
25
29
|
triggerProps?: NativePartProps<"button">;
|
|
26
30
|
panelProps?: NativePartProps<"div">;
|
|
31
|
+
/** Render one ref-forwarding native button with the supplied owned props. */
|
|
32
|
+
renderTrigger?: (props: NativePartProps<"button"> & {
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}) => ReactElement;
|
|
35
|
+
/** preventDefault vetoes dismissal. Native document events still propagate. */
|
|
36
|
+
onDismiss?: (reason: DismissReason, event: KeyboardEvent | PointerEvent) => void;
|
|
27
37
|
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
28
38
|
export default Popover;
|
|
@@ -1,27 +1,68 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
|
|
4
|
-
import { forwardRef, useCallback, useId } from "react";
|
|
3
|
+
import { useContext, useLayoutEffect, forwardRef, useId, useMemo, useRef, useState, } from "react";
|
|
5
4
|
import useOpenState from "../../hooks/useOpenState.js";
|
|
6
|
-
import useDismiss from "../../hooks/
|
|
5
|
+
import useDismiss from "../../hooks/useLayerDismiss.js";
|
|
7
6
|
import useControlRef from "../../hooks/useControlRef.js";
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import usePanelFocus from "../../hooks/usePanelFocus.js";
|
|
8
|
+
import { cx, ccx } from "../../lib/classNames.js";
|
|
9
|
+
import { composeRefs } from "../../lib/composeRefs.js";
|
|
10
|
+
import { LayerVisibility, LayerParent, } from "../../lib/layers.js";
|
|
11
|
+
import OverlayTrigger from "./OverlayTrigger.js";
|
|
12
|
+
import FloatingPortal from "./FloatingPortal.js";
|
|
13
|
+
const Popover = forwardRef(function Popover({ trigger, title, children, align = "start", side = "bottom", sideOffset = 8, collisionPadding = 8, portal = true, container: portalContainer, initialFocus, onOpenAutoFocus, onCloseAutoFocus, renderTrigger, onDismiss, open: controlledOpen, defaultOpen = false, onOpenChange, className, triggerProps, panelProps, ...rest }, forwardedRef) {
|
|
14
|
+
const generatedPanelId = useId(), titleId = useId();
|
|
12
15
|
const panelId = panelProps?.id ?? generatedPanelId;
|
|
13
|
-
const titleId = useId();
|
|
14
16
|
const [open, setOpen] = useOpenState(controlledOpen, defaultOpen, onOpenChange);
|
|
15
|
-
const
|
|
17
|
+
const parentVisible = useContext(LayerVisibility);
|
|
18
|
+
const visibleOpen = open && parentVisible;
|
|
19
|
+
const { ref: root, attach: attachRoot } = useControlRef(forwardedRef);
|
|
16
20
|
const { ref: button, attach: attachButton } = useControlRef(triggerProps?.ref ?? null);
|
|
17
|
-
|
|
21
|
+
const [anchorElement, setAnchorElement] = useState(null);
|
|
22
|
+
const attachAnchor = useMemo(() => composeRefs(attachButton, setAnchorElement), [attachButton]);
|
|
23
|
+
const panel = useRef(null);
|
|
24
|
+
const attachPanel = useMemo(() => composeRefs(panel, panelProps?.ref), [panelProps?.ref]);
|
|
25
|
+
const reason = useRef({ value: undefined });
|
|
26
|
+
const layer = useDismiss(root, visibleOpen, (why, event) => {
|
|
27
|
+
onDismiss?.(why, event);
|
|
28
|
+
if (event.defaultPrevented)
|
|
29
|
+
return;
|
|
30
|
+
reason.current.value = why;
|
|
18
31
|
setOpen(false);
|
|
19
|
-
if (
|
|
20
|
-
button.current
|
|
21
|
-
}, [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
if (why === "escape" && !onCloseAutoFocus && button.current?.isConnected)
|
|
33
|
+
button.current.focus();
|
|
34
|
+
}, { branches: [panel, button], layerElement: panel });
|
|
35
|
+
useLayoutEffect(() => {
|
|
36
|
+
if (visibleOpen)
|
|
37
|
+
reason.current.value = undefined;
|
|
38
|
+
}, [visibleOpen, anchorElement]);
|
|
39
|
+
usePanelFocus(visibleOpen, button, panel, reason, { initialFocus, onOpenAutoFocus, onCloseAutoFocus }, anchorElement, layer);
|
|
40
|
+
const ownedTrigger = {
|
|
41
|
+
...triggerProps,
|
|
42
|
+
ref: attachAnchor,
|
|
43
|
+
type: "button",
|
|
44
|
+
className: ccx("menu-trigger", triggerProps?.className),
|
|
45
|
+
"aria-haspopup": "dialog",
|
|
46
|
+
"aria-expanded": visibleOpen,
|
|
47
|
+
"aria-controls": panelId,
|
|
48
|
+
children: trigger,
|
|
49
|
+
onClick: (event) => {
|
|
50
|
+
triggerProps?.onClick?.(event);
|
|
51
|
+
if (event.defaultPrevented)
|
|
52
|
+
return;
|
|
53
|
+
if (!triggerProps?.disabled) {
|
|
54
|
+
reason.current.value = "trigger";
|
|
55
|
+
setOpen(!open);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
return (_jsx(LayerVisibility.Provider, { value: visibleOpen, children: _jsx(LayerParent.Provider, { value: layer, children: _jsxs("div", { ...rest, ref: attachRoot, className: ccx("popover", `popover-${align}`, className), children: [_jsx(OverlayTrigger, { renderer: renderTrigger, props: ownedTrigger }), _jsx(FloatingPortal, { anchorElement: anchorElement, anchor: button, panel: panel, open: visibleOpen, options: {
|
|
60
|
+
side,
|
|
61
|
+
align,
|
|
62
|
+
sideOffset,
|
|
63
|
+
collisionPadding,
|
|
64
|
+
portal,
|
|
65
|
+
container: portalContainer,
|
|
66
|
+
}, children: _jsxs("div", { ...panelProps, ref: attachPanel, id: panelId, role: "dialog", tabIndex: panelProps?.tabIndex ?? -1, "aria-labelledby": cx(titleId, panelProps?.["aria-labelledby"]), className: ccx("popover-panel", panelProps?.className), hidden: !visibleOpen, children: [_jsx("strong", { id: titleId, className: ccx("popover-title"), children: title }), children] }) })] }) }) }));
|
|
26
67
|
});
|
|
27
68
|
export default Popover;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { ccx } from "../../lib/classNames.js";
|
|
3
3
|
export default function ProgressBar({ label, value, tone = "accent", }) {
|
|
4
4
|
const indeterminate = value === undefined;
|
|
5
|
-
return (_jsxs("div", { className:
|
|
5
|
+
return (_jsxs("div", { className: ccx("progress", `progress-${tone}`), children: [_jsxs("div", { className: ccx("progress-header mono"), children: [_jsx("span", { children: label }), !indeterminate && _jsxs("span", { children: [Math.round(value), "%"] })] }), _jsx("div", { role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": indeterminate ? undefined : value, className: ccx("progress-track", indeterminate && "progress-indeterminate"), style: { "--progress": `${value ?? 0}%` }, children: _jsx("span", { className: ccx("progress-fill") }) })] }));
|
|
6
6
|
}
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
/** Native radios with caller form/name, controlled/default selection and option input refs. */
|
|
2
|
+
import { type FieldsetHTMLAttributes } from "react";
|
|
3
|
+
import type { NativePartProps } from "../../lib/componentProps.js";
|
|
4
|
+
export type RadioGroupProps<Value extends string> = Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, "onChange" | "defaultValue" | "children" | "dangerouslySetInnerHTML"> & {
|
|
2
5
|
legend: string;
|
|
3
6
|
options: {
|
|
4
7
|
value: Value;
|
|
5
8
|
label: string;
|
|
6
9
|
description?: string;
|
|
7
10
|
disabled?: boolean;
|
|
11
|
+
/** Native focus/blur/ref/ID access for form controllers; selection stays group-owned. */
|
|
12
|
+
inputProps?: Omit<NativePartProps<"input">, "type" | "name" | "form" | "value" | "defaultValue" | "checked" | "defaultChecked" | "disabled" | "required" | "onChange">;
|
|
8
13
|
}[];
|
|
9
|
-
value
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
value?: Value;
|
|
15
|
+
defaultValue?: Value;
|
|
16
|
+
onChange?: (value: Value) => void;
|
|
12
17
|
inline?: boolean;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
description?: string;
|
|
20
|
+
error?: string;
|
|
13
21
|
};
|
|
14
|
-
export default function RadioGroup<Value extends string>({ legend, options, value, onChange, inline, }: RadioGroupProps<Value>): import("react").JSX.Element;
|
|
22
|
+
export default function RadioGroup<Value extends string>({ legend, options, value, defaultValue, onChange, inline, name: providedName, form, id: providedId, disabled, required, description, error, className, "aria-describedby": describedBy, ...rest }: RadioGroupProps<Value>): import("react").JSX.Element;
|
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
import { useId } from "react";
|
|
5
|
-
import { cx } from "../../lib/classNames.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
/** Native radios with caller form/name, controlled/default selection and option input refs. */
|
|
4
|
+
import { useId, useLayoutEffect, useRef, useState, } from "react";
|
|
5
|
+
import { cx, ccx } from "../../lib/classNames.js";
|
|
6
|
+
import useFormReset from "../../hooks/useFormReset.js";
|
|
7
|
+
export default function RadioGroup({ legend, options, value, defaultValue, onChange, inline = false, name: providedName, form, id: providedId, disabled, required, description, error, className, "aria-describedby": describedBy, ...rest }) {
|
|
8
|
+
const generatedId = useId();
|
|
9
|
+
const id = providedId ?? generatedId;
|
|
10
|
+
const name = providedName ?? generatedId;
|
|
11
|
+
const [initial] = useState(defaultValue ?? value);
|
|
12
|
+
const ref = useRef(null);
|
|
13
|
+
const inputs = () => ref.current?.querySelectorAll('input[type="radio"]');
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
inputs()?.forEach((input) => {
|
|
16
|
+
input.defaultChecked = input.value === initial;
|
|
17
|
+
if (value !== undefined)
|
|
18
|
+
input.checked = input.value === value;
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
useFormReset(ref, () => {
|
|
22
|
+
if (value !== undefined)
|
|
23
|
+
inputs()?.forEach((input) => {
|
|
24
|
+
input.checked = input.value === value;
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
const context = cx(describedBy, description && `${id}-description`, error && `${id}-error`) || undefined;
|
|
28
|
+
return (_jsxs("fieldset", { ...rest, ref: ref, id: id, name: name, form: form, disabled: disabled, "aria-invalid": error ? true : rest["aria-invalid"], "aria-describedby": context, className: ccx("radio-group", inline && "radio-group-inline", className), children: [_jsx("legend", { className: ccx("field-label mono"), children: legend }), description && (_jsx("span", { id: `${id}-description`, className: ccx("choice-description"), children: description })), options.map((option) => {
|
|
29
|
+
const optionId = option.inputProps?.id ?? `${id}-${option.value}`;
|
|
30
|
+
return (_jsxs("div", { className: ccx("choice choice-radio"), children: [_jsxs("span", { className: ccx("choice-control"), children: [_jsx("input", { ...option.inputProps, id: optionId, type: "radio", name: name, form: form, value: option.value, checked: value === undefined ? undefined : option.value === value, defaultChecked: value === undefined ? option.value === initial : undefined, required: required, disabled: disabled || option.disabled, className: ccx("choice-input", option.inputProps?.className), "aria-invalid": error
|
|
31
|
+
? true
|
|
32
|
+
: (option.inputProps?.["aria-invalid"] ??
|
|
33
|
+
rest["aria-invalid"]), "aria-describedby": cx(option.inputProps?.["aria-describedby"], context, option.description && `${optionId}-description`) || undefined, onChange: (event) => {
|
|
34
|
+
if (!event.currentTarget.checked)
|
|
35
|
+
return;
|
|
36
|
+
onChange?.(option.value);
|
|
37
|
+
} }), _jsx("span", { className: ccx("choice-box"), "aria-hidden": "true" })] }), _jsxs("label", { htmlFor: optionId, className: ccx("choice-text"), children: [_jsx("span", { className: ccx("choice-label"), children: option.label }), option.description && (_jsx("span", { id: `${optionId}-description`, className: ccx("choice-description"), children: option.description }))] })] }, option.value));
|
|
38
|
+
}), error && (_jsx("span", { id: `${id}-error`, className: ccx("choice-error mono"), children: error }))] }));
|
|
12
39
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
/** Fades and lifts its children into view the first time they enter the viewport. */
|
|
4
|
+
import { ccx } from "../../lib/classNames.js";
|
|
4
5
|
import { useEffect, useRef } from "react";
|
|
5
6
|
export default function Reveal({ children, className = "", delay = 0, }) {
|
|
6
7
|
const ref = useRef(null);
|
|
@@ -9,17 +10,17 @@ export default function Reveal({ children, className = "", delay = 0, }) {
|
|
|
9
10
|
if (!element)
|
|
10
11
|
return;
|
|
11
12
|
if (typeof IntersectionObserver === "undefined") {
|
|
12
|
-
element.classList.add("is-visible");
|
|
13
|
+
element.classList.add("is-visible", "contour-is-visible");
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
15
16
|
const observer = new IntersectionObserver(([entry]) => {
|
|
16
17
|
if (!entry.isIntersecting)
|
|
17
18
|
return;
|
|
18
|
-
element.classList.add("is-visible");
|
|
19
|
+
element.classList.add("is-visible", "contour-is-visible");
|
|
19
20
|
observer.disconnect();
|
|
20
21
|
}, { threshold: 0, rootMargin: "0px 0px -24px 0px" });
|
|
21
22
|
observer.observe(element);
|
|
22
23
|
return () => observer.disconnect();
|
|
23
24
|
}, []);
|
|
24
|
-
return (_jsx("div", { ref: ref, className:
|
|
25
|
+
return (_jsx("div", { ref: ref, className: ccx("reveal", className), style: { transitionDelay: `${delay}ms` }, children: children }));
|
|
25
26
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ccx } from "../../lib/classNames.js";
|
|
2
3
|
export default function RevealHeading({ id, lines }) {
|
|
3
|
-
return (_jsx("h2", { id: id, children: lines.map((line, index) => (_jsx("span", { className: line.accent ? "type-line type-accent" : "type-line", children: _jsx("span", { className: "type-run", children: line.text }) }, index))) }));
|
|
4
|
+
return (_jsx("h2", { id: id, children: lines.map((line, index) => (_jsx("span", { className: ccx(line.accent ? "type-line type-accent" : "type-line"), children: _jsx("span", { className: ccx("type-run"), children: line.text }) }, index))) }));
|
|
4
5
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ccx } from "../../lib/classNames.js";
|
|
3
4
|
/** Thin acid bar fixed to the top of the viewport that tracks page scroll progress. */
|
|
4
5
|
import { useEffect, useRef } from "react";
|
|
5
6
|
export default function ScrollProgress() {
|
|
@@ -24,5 +25,5 @@ export default function ScrollProgress() {
|
|
|
24
25
|
window.removeEventListener("resize", update);
|
|
25
26
|
};
|
|
26
27
|
}, []);
|
|
27
|
-
return _jsx("div", { ref: ref, className: "scroll-progress", "aria-hidden": "true" });
|
|
28
|
+
return (_jsx("div", { ref: ref, className: ccx("scroll-progress"), "aria-hidden": "true" }));
|
|
28
29
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ccx } from "../../lib/classNames.js";
|
|
2
3
|
import { formatIndex } from "../../lib/format.js";
|
|
3
4
|
export default function SectionLabel({ section, aside }) {
|
|
4
|
-
return (_jsxs("div", { className: "section-label mono", children: [_jsxs("span", { children: [formatIndex(section.number), " / ", section.title] }), _jsx("span", { children: aside })] }));
|
|
5
|
+
return (_jsxs("div", { className: ccx("section-label mono"), children: [_jsxs("span", { children: [formatIndex(section.number), " / ", section.title] }), _jsx("span", { children: aside })] }));
|
|
5
6
|
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ccx } from "../../lib/classNames.js";
|
|
3
4
|
/** Single-choice pill group (filter buttons) with arrow-key navigation. */
|
|
4
5
|
import { useRef } from "react";
|
|
5
6
|
import { nextIndexForKey } from "../../lib/keyboard.js";
|
|
6
7
|
export default function SegmentedControl({ label, options, value, onChange, }) {
|
|
7
8
|
const buttons = useRef([]);
|
|
8
|
-
return (_jsx("div", { className: "segmented", role: "radiogroup", "aria-label": label, children: options.map((option, index) => {
|
|
9
|
+
return (_jsx("div", { className: ccx("segmented"), role: "radiogroup", "aria-label": label, children: options.map((option, index) => {
|
|
9
10
|
const selected = option.value === value;
|
|
10
11
|
return (_jsx("button", { ref: (element) => {
|
|
11
12
|
buttons.current[index] = element;
|
|
12
|
-
}, type: "button", role: "radio", "aria-checked": selected, tabIndex: selected ? 0 : -1, className: "segmented-option", onClick: () => onChange(option.value), onKeyDown: (event) => {
|
|
13
|
-
const next = nextIndexForKey(event.key, index, options.length)
|
|
13
|
+
}, type: "button", role: "radio", "aria-checked": selected, tabIndex: selected ? 0 : -1, className: ccx("segmented-option"), onClick: () => onChange(option.value), onKeyDown: (event) => {
|
|
14
|
+
const next = nextIndexForKey(event.key, index, options.length, "horizontal", event.currentTarget.ownerDocument.defaultView?.getComputedStyle(event.currentTarget).direction === "rtl"
|
|
15
|
+
? "rtl"
|
|
16
|
+
: "ltr");
|
|
14
17
|
if (next === null)
|
|
15
18
|
return;
|
|
16
19
|
event.preventDefault();
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ccx } from "../../lib/classNames.js";
|
|
3
4
|
/** Native select styled as an underlined field with a chevron. */
|
|
4
5
|
import { forwardRef, useId } from "react";
|
|
5
6
|
import { ChevronDown } from "lucide-react";
|
|
6
7
|
import { fieldDescribedBy } from "../../lib/a11y.js";
|
|
7
8
|
import Field from "./Field.js";
|
|
8
|
-
|
|
9
|
+
import useControlRef from "../../hooks/useControlRef.js";
|
|
10
|
+
import useFormReset from "../../hooks/useFormReset.js";
|
|
11
|
+
/** Native reset does not preserve React's controlled selection on its own. */
|
|
12
|
+
function restoreSelection(control, value) {
|
|
13
|
+
const values = new Set(Array.isArray(value) ? value.map(String) : [String(value)]);
|
|
14
|
+
for (const option of control.options)
|
|
15
|
+
option.selected = values.has(option.value);
|
|
16
|
+
}
|
|
17
|
+
const SelectField = forwardRef(function SelectField({ id: providedId, "aria-describedby": describedBy, label, options, hint, error, placeholder, ...rest }, forwardedRef) {
|
|
9
18
|
const generatedId = useId();
|
|
10
19
|
const id = providedId ?? generatedId;
|
|
11
|
-
|
|
20
|
+
const { ref, attach } = useControlRef(forwardedRef);
|
|
21
|
+
useFormReset(ref, () => {
|
|
22
|
+
if (!ref.current || rest.value === undefined)
|
|
23
|
+
return;
|
|
24
|
+
restoreSelection(ref.current, rest.value);
|
|
25
|
+
});
|
|
26
|
+
return (_jsx(Field, { id: id, label: label, hint: hint, error: error, required: rest.required, disabled: rest.disabled, children: _jsxs("div", { className: ccx("field-control field-select"), children: [_jsxs("select", { ...rest, ref: attach, id: id, "aria-invalid": error ? true : rest["aria-invalid"], "aria-describedby": [describedBy, fieldDescribedBy(id, hint, error)]
|
|
12
27
|
.filter(Boolean)
|
|
13
|
-
.join(" ") || undefined, children: [placeholder && (_jsx("option", { value: "", disabled: true, children: placeholder })), options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value)))] }), _jsx(ChevronDown, { className: "field-chevron", size: 16, "aria-hidden": "true" })] }) }));
|
|
28
|
+
.join(" ") || undefined, children: [placeholder && (_jsx("option", { value: "", disabled: true, children: placeholder })), options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value)))] }), _jsx(ChevronDown, { className: ccx("field-chevron"), size: 16, "aria-hidden": "true" })] }) }));
|
|
14
29
|
});
|
|
15
30
|
export default SelectField;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { ccx } from "../../lib/classNames.js";
|
|
3
3
|
export default function Skeleton({ shape = "line", width, height, }) {
|
|
4
|
-
return (_jsx("span", { "aria-hidden": "true", className:
|
|
4
|
+
return (_jsx("span", { "aria-hidden": "true", className: ccx("skeleton", `skeleton-${shape}`), style: { width, height } }));
|
|
5
5
|
}
|
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
/** Native range submission with controlled/default state, reset and a formatted readout. */
|
|
2
|
+
import { type InputHTMLAttributes } from "react";
|
|
3
|
+
export type SliderProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "defaultValue" | "onChange" | "required" | "dangerouslySetInnerHTML" | "children" | "min" | "max" | "step"> & {
|
|
2
4
|
label: string;
|
|
3
|
-
value
|
|
4
|
-
|
|
5
|
+
value?: number;
|
|
6
|
+
defaultValue?: number;
|
|
7
|
+
onChange?: (value: number) => void;
|
|
5
8
|
min?: number;
|
|
6
9
|
max?: number;
|
|
7
|
-
step?: number;
|
|
8
|
-
/** Formats the readout
|
|
10
|
+
step?: number | "any";
|
|
11
|
+
/** Formats the readout and accessible value text. */
|
|
9
12
|
format?: (value: number) => string;
|
|
10
|
-
|
|
13
|
+
hint?: string;
|
|
14
|
+
error?: string;
|
|
11
15
|
};
|
|
12
|
-
declare const Slider: import("react").ForwardRefExoticComponent<
|
|
16
|
+
declare const Slider: import("react").ForwardRefExoticComponent<Omit<InputHTMLAttributes<HTMLInputElement>, "children" | "max" | "min" | "type" | "dangerouslySetInnerHTML" | "onChange" | "step" | "value" | "defaultValue" | "required"> & {
|
|
17
|
+
label: string;
|
|
18
|
+
value?: number;
|
|
19
|
+
defaultValue?: number;
|
|
20
|
+
onChange?: (value: number) => void;
|
|
21
|
+
min?: number;
|
|
22
|
+
max?: number;
|
|
23
|
+
step?: number | "any";
|
|
24
|
+
/** Formats the readout and accessible value text. */
|
|
25
|
+
format?: (value: number) => string;
|
|
26
|
+
hint?: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
} & import("react").RefAttributes<HTMLInputElement>>;
|
|
13
29
|
export default Slider;
|
|
@@ -1,12 +1,49 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
import { forwardRef, useId } from "react";
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/** Native range submission with controlled/default state, reset and a formatted readout. */
|
|
4
|
+
import { forwardRef, useId, useLayoutEffect, useState, } from "react";
|
|
5
|
+
import { cx, ccx } from "../../lib/classNames.js";
|
|
6
|
+
import useControlRef from "../../hooks/useControlRef.js";
|
|
7
|
+
import useFormReset from "../../hooks/useFormReset.js";
|
|
8
|
+
const Slider = forwardRef(function Slider({ label, value, defaultValue, onChange, min = 0, max = 100, step = 1, format = String, disabled, id: providedId, className, style, onInput, hint, error, "aria-describedby": describedBy, ...rest }, forwardedRef) {
|
|
9
|
+
const generatedId = useId();
|
|
10
|
+
const id = providedId ?? generatedId;
|
|
11
|
+
const [initial] = useState(defaultValue ?? value ?? (min + Math.max(min, max)) / 2);
|
|
12
|
+
const [readout, setReadout] = useState(value ?? initial);
|
|
13
|
+
const { ref, attach } = useControlRef(forwardedRef);
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
const input = ref.current;
|
|
16
|
+
if (!input)
|
|
17
|
+
return;
|
|
18
|
+
input.defaultValue = String(initial);
|
|
19
|
+
// Read the browser's sanitized value; clamp/step behavior stays native.
|
|
20
|
+
if (value !== undefined)
|
|
21
|
+
input.value = String(value);
|
|
22
|
+
const actual = input.valueAsNumber;
|
|
23
|
+
if (Number.isFinite(actual))
|
|
24
|
+
setReadout(actual);
|
|
25
|
+
}, [readout, initial, min, max, step, ref, value]);
|
|
26
|
+
useFormReset(ref, () => {
|
|
27
|
+
const input = ref.current;
|
|
28
|
+
if (!input)
|
|
29
|
+
return;
|
|
30
|
+
if (value !== undefined)
|
|
31
|
+
input.value = String(value);
|
|
32
|
+
setReadout(input.valueAsNumber);
|
|
33
|
+
});
|
|
7
34
|
const progress = max > min
|
|
8
|
-
? Math.min(100, Math.max(0, ((
|
|
35
|
+
? Math.min(100, Math.max(0, ((readout - min) / (max - min)) * 100))
|
|
9
36
|
: 0;
|
|
10
|
-
return (_jsxs("div", { className: "slider", children: [_jsxs("div", { className: "slider-header mono", children: [_jsx("label", { htmlFor: id, className: "field-label", children: label }), _jsx("output", { htmlFor: id, children: format(
|
|
37
|
+
return (_jsxs("div", { className: ccx("slider"), children: [_jsxs("div", { className: ccx("slider-header mono"), children: [_jsx("label", { htmlFor: id, className: ccx("field-label"), children: label }), _jsx("output", { htmlFor: id, children: format(readout) })] }), _jsx("input", { ...rest, ref: attach, id: id, type: "range", min: min, max: max, step: step, value: value, defaultValue: value === undefined ? initial : undefined, disabled: disabled, className: className, "aria-invalid": error ? true : rest["aria-invalid"], "aria-describedby": cx(describedBy, hint && `${id}-hint`, error && `${id}-error`) ||
|
|
38
|
+
undefined, "aria-valuetext": rest["aria-valuetext"] ?? format(readout), style: { ...style, "--progress": `${progress}%` }, onInput: (event) => {
|
|
39
|
+
if (value === undefined)
|
|
40
|
+
setReadout(event.currentTarget.valueAsNumber);
|
|
41
|
+
onInput?.(event);
|
|
42
|
+
}, onChange: (event) => {
|
|
43
|
+
const next = event.currentTarget.valueAsNumber;
|
|
44
|
+
if (value === undefined)
|
|
45
|
+
setReadout(next);
|
|
46
|
+
onChange?.(next);
|
|
47
|
+
} }), hint && (_jsx("span", { id: `${id}-hint`, className: ccx("choice-description"), children: hint })), error && (_jsx("span", { id: `${id}-error`, className: ccx("choice-error mono"), children: error }))] }));
|
|
11
48
|
});
|
|
12
49
|
export default Slider;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/** Brand asterisk that spins as a loading indicator; announces its label to assistive tech. */
|
|
3
3
|
import { Asterisk } from "lucide-react";
|
|
4
|
-
import {
|
|
4
|
+
import { ccx } from "../../lib/classNames.js";
|
|
5
5
|
const iconSizes = { sm: 16, md: 24, lg: 40 };
|
|
6
6
|
export default function Spinner({ size = "md", label = "Loading", className, }) {
|
|
7
|
-
return (_jsxs("span", { role: "status", className:
|
|
7
|
+
return (_jsxs("span", { role: "status", className: ccx("spinner", `spinner-${size}`, className), children: [_jsx(Asterisk, { size: iconSizes[size], strokeWidth: 1.6, "aria-hidden": "true" }), _jsx("span", { className: ccx("visually-hidden"), children: label })] }));
|
|
8
8
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ccx } from "../../lib/classNames.js";
|
|
2
3
|
export default function Stat({ value, label, suffix }) {
|
|
3
|
-
return (_jsxs("div", { className: "stat", children: [_jsxs("strong", { children: [value, suffix && _jsx("span", { className: "stat-suffix", children: suffix })] }), _jsx("span", { className: "mono", children: label })] }));
|
|
4
|
+
return (_jsxs("div", { className: ccx("stat"), children: [_jsxs("strong", { children: [value, suffix && _jsx("span", { className: ccx("stat-suffix"), children: suffix })] }), _jsx("span", { className: ccx("mono"), children: label })] }));
|
|
4
5
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/** Numbered progress steps joined by a rule; completed steps show a check. */
|
|
3
3
|
import { Check } from "lucide-react";
|
|
4
|
-
import {
|
|
4
|
+
import { ccx } from "../../lib/classNames.js";
|
|
5
5
|
import { formatIndex } from "../../lib/format.js";
|
|
6
6
|
export default function Stepper({ steps, current, label = "Progress", }) {
|
|
7
|
-
return (_jsx("ol", { className: "stepper", "aria-label": label, children: steps.map((step, index) => {
|
|
7
|
+
return (_jsx("ol", { className: ccx("stepper"), "aria-label": label, children: steps.map((step, index) => {
|
|
8
8
|
const state = index < current
|
|
9
9
|
? "complete"
|
|
10
10
|
: index === current
|
|
11
11
|
? "current"
|
|
12
12
|
: "upcoming";
|
|
13
|
-
return (_jsxs("li", { className:
|
|
13
|
+
return (_jsxs("li", { className: ccx("stepper-step", `stepper-${state}`), "aria-current": state === "current" ? "step" : undefined, children: [_jsx("span", { className: ccx("stepper-marker mono"), "aria-hidden": "true", children: state === "complete" ? (_jsx(Check, { size: 14, strokeWidth: 2.5 })) : (formatIndex(index + 1)) }), _jsxs("span", { className: ccx("stepper-text"), children: [_jsx("span", { className: ccx("stepper-label"), children: step.label }), step.description && (_jsx("span", { className: ccx("stepper-description mono"), children: step.description })), _jsxs("span", { className: ccx("visually-hidden"), children: ["(", state, ")"] })] })] }, step.label));
|
|
14
14
|
}) }));
|
|
15
15
|
}
|
|
@@ -1,9 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
/** Button switch with a native checkbox backing submission, validation and reset. */
|
|
2
|
+
import { type ButtonHTMLAttributes, type FormEventHandler } from "react";
|
|
3
|
+
export type SwitchProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onChange" | "value" | "defaultValue" | "onInvalid" | "children" | "dangerouslySetInnerHTML"> & {
|
|
2
4
|
label: string;
|
|
3
|
-
checked
|
|
4
|
-
|
|
5
|
+
checked?: boolean;
|
|
6
|
+
defaultChecked?: boolean;
|
|
7
|
+
onChange?: (checked: boolean) => void;
|
|
8
|
+
/** Submitted only when checked; defaults to the native checkbox value "on". */
|
|
9
|
+
value?: string;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
requiredMessage?: string;
|
|
5
12
|
description?: string;
|
|
6
|
-
|
|
13
|
+
error?: string;
|
|
14
|
+
/** Native invalid event from the backing checkbox; preventDefault owns feedback/focus. */
|
|
15
|
+
onInvalid?: FormEventHandler<HTMLInputElement>;
|
|
7
16
|
};
|
|
8
|
-
declare const Switch: import("react").ForwardRefExoticComponent<
|
|
17
|
+
declare const Switch: import("react").ForwardRefExoticComponent<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "dangerouslySetInnerHTML" | "onChange" | "onInvalid" | "value" | "defaultValue"> & {
|
|
18
|
+
label: string;
|
|
19
|
+
checked?: boolean;
|
|
20
|
+
defaultChecked?: boolean;
|
|
21
|
+
onChange?: (checked: boolean) => void;
|
|
22
|
+
/** Submitted only when checked; defaults to the native checkbox value "on". */
|
|
23
|
+
value?: string;
|
|
24
|
+
required?: boolean;
|
|
25
|
+
requiredMessage?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
/** Native invalid event from the backing checkbox; preventDefault owns feedback/focus. */
|
|
29
|
+
onInvalid?: FormEventHandler<HTMLInputElement>;
|
|
30
|
+
} & import("react").RefAttributes<HTMLButtonElement>>;
|
|
9
31
|
export default Switch;
|