@abuzareal/contour-ui 0.4.0 → 0.6.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 +24 -0
- package/COMBOBOX.md +87 -0
- package/CONTRACTS.md +23 -17
- package/DOCS.md +80 -0
- package/FOUNDATION.md +35 -0
- package/GETTING-STARTED.md +63 -0
- package/NEXTJS.md +5 -1
- package/OVERLAYS.md +52 -0
- package/PUBLIC-API.md +189 -162
- package/README.md +44 -28
- package/RECIPES.md +43 -0
- package/RELEASING.md +2 -2
- package/SIZE.md +51 -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.js +3 -3
- package/dist/components/ui/Combobox.d.ts +325 -0
- package/dist/components/ui/Combobox.js +120 -0
- package/dist/components/ui/Combobox.types.d.ts +46 -0
- package/dist/components/ui/Combobox.types.js +1 -0
- 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 +15 -10
- 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 +13 -0
- package/dist/components/ui/FloatingScope.d.ts +9 -0
- package/dist/components/ui/FloatingScope.js +43 -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.js +5 -5
- 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 +3 -2
- package/dist/components/ui/Skeleton.js +2 -2
- package/dist/components/ui/Slider.js +3 -3
- 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 +2 -0
- package/dist/components/ui/Switch.js +6 -7
- package/dist/components/ui/Table.d.ts +20 -0
- package/dist/components/ui/Table.js +9 -0
- 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 +2 -1
- package/dist/components/ui/TextField.js +3 -2
- 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/useComboboxAnchor.d.ts +17 -0
- package/dist/hooks/useComboboxAnchor.js +61 -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/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 +7 -0
- package/dist/index.js +3 -0
- package/dist/kit/data-display.css +6 -0
- package/dist/kit/forms.css +45 -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 +498 -0
- package/dist/scoped/kit/effects.css +140 -0
- package/dist/scoped/kit/feedback.css +309 -0
- package/dist/scoped/kit/forms.css +404 -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/optional/LazyScene.tsx +78 -0
- package/examples/optional/OptionalSmoothScroll.tsx +25 -0
- package/examples/optional/SmoothScrollOwner.tsx +7 -0
- package/examples/optional/optional.css +25 -0
- package/examples/recipes/AsyncProjectPicker.tsx +82 -0
- package/examples/recipes/DeleteProjectConfirmation.tsx +97 -0
- package/examples/recipes/InputCompositions.tsx +49 -0
- package/examples/recipes/RemoteRecordsTable.tsx +256 -0
- package/examples/recipes/useRemoteResource.ts +81 -0
- package/examples/reproduction/README.md +9 -0
- package/examples/reproduction/index.html +14 -0
- package/examples/reproduction/package.json +27 -0
- package/examples/reproduction/src/main.tsx +33 -0
- package/examples/reproduction/src/style.css +19 -0
- package/examples/reproduction/tsconfig.json +18 -0
- package/examples/reproduction/vite.config.ts +19 -0
- package/examples/starters/next/README.md +18 -0
- package/examples/starters/next/app/Demo.tsx +65 -0
- package/examples/starters/next/app/layout.tsx +25 -0
- package/examples/starters/next/app/next-page/page.tsx +9 -0
- package/examples/starters/next/app/page.tsx +11 -0
- package/examples/starters/next/app/style.css +19 -0
- package/examples/starters/next/next-env.d.ts +2 -0
- package/examples/starters/next/next.config.mjs +1 -0
- package/examples/starters/next/package.json +27 -0
- package/examples/starters/next/proxy.ts +16 -0
- package/examples/starters/next/tsconfig.json +30 -0
- package/examples/starters/vite/README.md +16 -0
- package/examples/starters/vite/index.html +14 -0
- package/examples/starters/vite/package.json +27 -0
- package/examples/starters/vite/src/main.tsx +81 -0
- package/examples/starters/vite/src/style.css +19 -0
- package/examples/starters/vite/tsconfig.json +18 -0
- package/examples/starters/vite/vite.config.ts +19 -0
- package/package.json +41 -5
|
@@ -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
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
/** Native radios with caller form/name, controlled/default selection and option input refs. */
|
|
4
4
|
import { useId, useLayoutEffect, useRef, useState, } from "react";
|
|
5
|
-
import { cx } from "../../lib/classNames.js";
|
|
5
|
+
import { cx, ccx } from "../../lib/classNames.js";
|
|
6
6
|
import useFormReset from "../../hooks/useFormReset.js";
|
|
7
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
8
|
const generatedId = useId();
|
|
@@ -25,15 +25,15 @@ export default function RadioGroup({ legend, options, value, defaultValue, onCha
|
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
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:
|
|
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
29
|
const optionId = option.inputProps?.id ?? `${id}-${option.value}`;
|
|
30
|
-
return (_jsxs("div", { className: "choice choice-radio", children: [_jsxs("span", { className: "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:
|
|
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
31
|
? true
|
|
32
32
|
: (option.inputProps?.["aria-invalid"] ??
|
|
33
33
|
rest["aria-invalid"]), "aria-describedby": cx(option.inputProps?.["aria-describedby"], context, option.description && `${optionId}-description`) || undefined, onChange: (event) => {
|
|
34
34
|
if (!event.currentTarget.checked)
|
|
35
35
|
return;
|
|
36
36
|
onChange?.(option.value);
|
|
37
|
-
} }), _jsx("span", { className: "choice-box", "aria-hidden": "true" })] }), _jsxs("label", { htmlFor: optionId, className: "choice-text", children: [_jsx("span", { className: "choice-label", children: option.label }), option.description && (_jsx("span", { id: `${optionId}-description`, className: "choice-description", children: option.description }))] })] }, option.value));
|
|
38
|
-
}), error && (_jsx("span", { id: `${id}-error`, className: "choice-error mono", children: error }))] }));
|
|
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 }))] }));
|
|
39
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,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
|
/** Native select styled as an underlined field with a chevron. */
|
|
4
5
|
import { forwardRef, useId } from "react";
|
|
5
6
|
import { ChevronDown } from "lucide-react";
|
|
@@ -22,8 +23,8 @@ const SelectField = forwardRef(function SelectField({ id: providedId, "aria-desc
|
|
|
22
23
|
return;
|
|
23
24
|
restoreSelection(ref.current, rest.value);
|
|
24
25
|
});
|
|
25
|
-
return (_jsx(Field, { id: id, label: label, hint: hint, error: error, required: rest.required, disabled: rest.disabled, children: _jsxs("div", { className: "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)]
|
|
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)]
|
|
26
27
|
.filter(Boolean)
|
|
27
|
-
.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" })] }) }));
|
|
28
29
|
});
|
|
29
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
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
/** Native range submission with controlled/default state, reset and a formatted readout. */
|
|
4
4
|
import { forwardRef, useId, useLayoutEffect, useState, } from "react";
|
|
5
|
-
import { cx } from "../../lib/classNames.js";
|
|
5
|
+
import { cx, ccx } from "../../lib/classNames.js";
|
|
6
6
|
import useControlRef from "../../hooks/useControlRef.js";
|
|
7
7
|
import useFormReset from "../../hooks/useFormReset.js";
|
|
8
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) {
|
|
@@ -34,7 +34,7 @@ const Slider = forwardRef(function Slider({ label, value, defaultValue, onChange
|
|
|
34
34
|
const progress = max > min
|
|
35
35
|
? Math.min(100, Math.max(0, ((readout - min) / (max - min)) * 100))
|
|
36
36
|
: 0;
|
|
37
|
-
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(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`) ||
|
|
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
38
|
undefined, "aria-valuetext": rest["aria-valuetext"] ?? format(readout), style: { ...style, "--progress": `${progress}%` }, onInput: (event) => {
|
|
39
39
|
if (value === undefined)
|
|
40
40
|
setReadout(event.currentTarget.valueAsNumber);
|
|
@@ -44,6 +44,6 @@ const Slider = forwardRef(function Slider({ label, value, defaultValue, onChange
|
|
|
44
44
|
if (value === undefined)
|
|
45
45
|
setReadout(next);
|
|
46
46
|
onChange?.(next);
|
|
47
|
-
} }), hint && (_jsx("span", { id: `${id}-hint`, className: "choice-description", children: hint })), error && (_jsx("span", { id: `${id}-error`, className: "choice-error mono", children: error }))] }));
|
|
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 }))] }));
|
|
48
48
|
});
|
|
49
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
|
}
|
|
@@ -8,6 +8,7 @@ export type SwitchProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onChang
|
|
|
8
8
|
/** Submitted only when checked; defaults to the native checkbox value "on". */
|
|
9
9
|
value?: string;
|
|
10
10
|
required?: boolean;
|
|
11
|
+
requiredMessage?: string;
|
|
11
12
|
description?: string;
|
|
12
13
|
error?: string;
|
|
13
14
|
/** Native invalid event from the backing checkbox; preventDefault owns feedback/focus. */
|
|
@@ -21,6 +22,7 @@ declare const Switch: import("react").ForwardRefExoticComponent<Omit<ButtonHTMLA
|
|
|
21
22
|
/** Submitted only when checked; defaults to the native checkbox value "on". */
|
|
22
23
|
value?: string;
|
|
23
24
|
required?: boolean;
|
|
25
|
+
requiredMessage?: string;
|
|
24
26
|
description?: string;
|
|
25
27
|
error?: string;
|
|
26
28
|
/** Native invalid event from the backing checkbox; preventDefault owns feedback/focus. */
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
/** Button switch with a native checkbox backing submission, validation and reset. */
|
|
4
4
|
import { forwardRef, useId, useLayoutEffect, useRef, useState, } from "react";
|
|
5
|
-
import { cx } from "../../lib/classNames.js";
|
|
5
|
+
import { cx, ccx } from "../../lib/classNames.js";
|
|
6
6
|
import useControlRef from "../../hooks/useControlRef.js";
|
|
7
7
|
import useFormReset from "../../hooks/useFormReset.js";
|
|
8
|
-
const Switch = forwardRef(function Switch({ label, checked, defaultChecked, onChange, description, error, disabled, required, name, form, value = "on", id: providedId, className, onClick, onInvalid, "aria-describedby": describedBy, ...rest }, forwardedRef) {
|
|
8
|
+
const Switch = forwardRef(function Switch({ label, checked, defaultChecked, onChange, description, error, disabled, required, requiredMessage = "Turn on this option to continue.", name, form, value = "on", id: providedId, className, onClick, onInvalid, "aria-describedby": describedBy, ...rest }, forwardedRef) {
|
|
9
9
|
const generatedId = useId();
|
|
10
10
|
const id = providedId ?? generatedId;
|
|
11
11
|
const [initial] = useState(defaultChecked ?? checked ?? false);
|
|
@@ -14,8 +14,7 @@ const Switch = forwardRef(function Switch({ label, checked, defaultChecked, onCh
|
|
|
14
14
|
const current = checked ?? localChecked;
|
|
15
15
|
const { ref: button, attach } = useControlRef(forwardedRef);
|
|
16
16
|
const input = useRef(null);
|
|
17
|
-
const message = error ??
|
|
18
|
-
(invalid && !current ? "Turn on this option to continue." : undefined);
|
|
17
|
+
const message = error ?? (invalid && !current ? requiredMessage : undefined);
|
|
19
18
|
useLayoutEffect(() => {
|
|
20
19
|
if (input.current) {
|
|
21
20
|
input.current.defaultChecked = initial;
|
|
@@ -29,7 +28,7 @@ const Switch = forwardRef(function Switch({ label, checked, defaultChecked, onCh
|
|
|
29
28
|
input.current.checked = checked;
|
|
30
29
|
setInvalid(false);
|
|
31
30
|
});
|
|
32
|
-
return (_jsxs("div", { className: "switch", children: [_jsx("button", { ...rest, ref: attach, id: id, name: undefined, form: form, type: "button", role: "switch", "aria-labelledby": cx(`${id}-label`, rest["aria-labelledby"]), "aria-checked": current, "aria-required": required || undefined, "aria-invalid": message ? true : rest["aria-invalid"], "aria-describedby": cx(describedBy, description && `${id}-description`, message && `${id}-error`) || undefined, disabled: disabled, className:
|
|
31
|
+
return (_jsxs("div", { className: ccx("switch"), children: [_jsx("button", { ...rest, ref: attach, id: id, name: undefined, form: form, type: "button", role: "switch", "aria-labelledby": cx(`${id}-label`, rest["aria-labelledby"]), "aria-checked": current, "aria-required": required || undefined, "aria-invalid": message ? true : rest["aria-invalid"], "aria-describedby": cx(describedBy, description && `${id}-description`, message && `${id}-error`) || undefined, disabled: disabled, className: ccx("switch-track", className), onClick: (event) => {
|
|
33
32
|
onClick?.(event);
|
|
34
33
|
if (event.defaultPrevented)
|
|
35
34
|
return;
|
|
@@ -42,7 +41,7 @@ const Switch = forwardRef(function Switch({ label, checked, defaultChecked, onCh
|
|
|
42
41
|
}
|
|
43
42
|
setInvalid(false);
|
|
44
43
|
onChange?.(!current);
|
|
45
|
-
}, children: _jsx("span", { className: "switch-thumb" }) }), _jsx("input", { ref: input, type: "checkbox", hidden: true, "aria-hidden": "true", tabIndex: -1, name: name, form: form, value: value, disabled: disabled, required: required, checked: checked, defaultChecked: checked === undefined ? initial : undefined, onChange: () => { }, onInvalid: (event) => {
|
|
44
|
+
}, children: _jsx("span", { className: ccx("switch-thumb") }) }), _jsx("input", { ref: input, type: "checkbox", hidden: true, "aria-hidden": "true", tabIndex: -1, name: name, form: form, value: value, disabled: disabled, required: required, checked: checked, defaultChecked: checked === undefined ? initial : undefined, onChange: () => { }, onInvalid: (event) => {
|
|
46
45
|
onInvalid?.(event);
|
|
47
46
|
const canceled = event.defaultPrevented;
|
|
48
47
|
// Native validation must never try to focus the noninteractive backing control.
|
|
@@ -51,6 +50,6 @@ const Switch = forwardRef(function Switch({ label, checked, defaultChecked, onCh
|
|
|
51
50
|
setInvalid(true);
|
|
52
51
|
button.current?.focus();
|
|
53
52
|
}
|
|
54
|
-
} }), _jsxs("label", { htmlFor: id, className: "choice-text", children: [_jsx("span", { id: `${id}-label`, className: "choice-label", children: label }), description && (_jsx("span", { id: `${id}-description`, className: "choice-description", children: description })), message && (_jsx("span", { id: `${id}-error`, className: "choice-error mono", children: message }))] })] }));
|
|
53
|
+
} }), _jsxs("label", { htmlFor: id, className: ccx("choice-text"), children: [_jsx("span", { id: `${id}-label`, className: ccx("choice-label"), children: label }), description && (_jsx("span", { id: `${id}-description`, className: ccx("choice-description"), children: description })), message && (_jsx("span", { id: `${id}-error`, className: ccx("choice-error mono"), children: message }))] })] }));
|
|
55
54
|
});
|
|
56
55
|
export default Switch;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type TableHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import type { NativePartProps } from "../../lib/componentProps.js";
|
|
3
|
+
export type TableProps = Omit<TableHTMLAttributes<HTMLTableElement>, "children"> & {
|
|
4
|
+
/** Accessible table name. The component owns the caption element. */
|
|
5
|
+
caption: string;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
/** Show the caption visually; it remains accessible when false. @default false */
|
|
8
|
+
showCaption?: boolean;
|
|
9
|
+
scrollProps?: NativePartProps<"div">;
|
|
10
|
+
};
|
|
11
|
+
/** Semantic table styling for application-owned rows, cells and table engines. */
|
|
12
|
+
declare const Table: import("react").ForwardRefExoticComponent<Omit<TableHTMLAttributes<HTMLTableElement>, "children"> & {
|
|
13
|
+
/** Accessible table name. The component owns the caption element. */
|
|
14
|
+
caption: string;
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
/** Show the caption visually; it remains accessible when false. @default false */
|
|
17
|
+
showCaption?: boolean;
|
|
18
|
+
scrollProps?: NativePartProps<"div">;
|
|
19
|
+
} & import("react").RefAttributes<HTMLTableElement>>;
|
|
20
|
+
export default Table;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import { ccx } from "../../lib/classNames.js";
|
|
5
|
+
/** Semantic table styling for application-owned rows, cells and table engines. */
|
|
6
|
+
const Table = forwardRef(function Table({ caption, children, showCaption = false, scrollProps, className, ...rest }, ref) {
|
|
7
|
+
return (_jsx("div", { ...scrollProps, className: ccx("table-scroll", scrollProps?.className), tabIndex: scrollProps?.tabIndex ?? 0, role: scrollProps?.role ?? "region", "aria-label": scrollProps?.["aria-label"] ?? caption, children: _jsxs("table", { ...rest, ref: ref, className: ccx("data-table", className), children: [_jsx("caption", { className: showCaption ? undefined : ccx("visually-hidden"), children: caption }), children] }) }));
|
|
8
|
+
});
|
|
9
|
+
export default Table;
|
|
@@ -4,7 +4,7 @@ import { createElement as _createElement } from "react";
|
|
|
4
4
|
/** Controlled/default tabs, with props and native refs for root/list/buttons/panels. */
|
|
5
5
|
import { forwardRef, useId, useLayoutEffect, useRef, useState, } from "react";
|
|
6
6
|
import { nextIndexForKey } from "../../lib/keyboard.js";
|
|
7
|
-
import { cx } from "../../lib/classNames.js";
|
|
7
|
+
import { cx, ccx } from "../../lib/classNames.js";
|
|
8
8
|
import useTabRefs from "../../hooks/useTabRefs.js";
|
|
9
9
|
const Tabs = forwardRef(function Tabs({ label, tabs, defaultTabId, activeTabId, onTabChange, listProps, className, id, ...rest }, ref) {
|
|
10
10
|
const generatedId = useId();
|
|
@@ -45,13 +45,13 @@ const Tabs = forwardRef(function Tabs({ label, tabs, defaultTabId, activeTabId,
|
|
|
45
45
|
};
|
|
46
46
|
const buttonId = (tab) => tab.buttonProps?.id ?? `${baseId}-tab-${tab.id}`;
|
|
47
47
|
const panelId = (tab) => tab.panelProps?.id ?? `${baseId}-panel-${tab.id}`;
|
|
48
|
-
return (_jsxs("div", { ...rest, ref: ref, id: id, className:
|
|
48
|
+
return (_jsxs("div", { ...rest, ref: ref, id: id, className: ccx("tabs", className), children: [_jsx("div", { ...listProps, className: ccx("tabs-list", listProps?.className), role: "tablist", "aria-label": label, style: {
|
|
49
49
|
...listProps?.style,
|
|
50
50
|
"--indicator-left": `${indicator.left}px`,
|
|
51
51
|
"--indicator-width": `${indicator.width}px`,
|
|
52
52
|
}, children: tabs.map((tab, index) => {
|
|
53
53
|
const selected = tab.id === selectedId;
|
|
54
|
-
return (_createElement("button", { ...tab.buttonProps, key: tab.id, ref: buttonRefs[index], id: buttonId(tab), type: "button", role: "tab", "aria-selected": selected, "aria-controls": panelId(tab), tabIndex: selected ? 0 : -1, className:
|
|
54
|
+
return (_createElement("button", { ...tab.buttonProps, key: tab.id, ref: buttonRefs[index], id: buttonId(tab), type: "button", role: "tab", "aria-selected": selected, "aria-controls": panelId(tab), tabIndex: selected ? 0 : -1, className: ccx("tabs-tab", tab.buttonProps?.className), onClick: (event) => {
|
|
55
55
|
tab.buttonProps?.onClick?.(event);
|
|
56
56
|
if (!event.defaultPrevented)
|
|
57
57
|
select(tab);
|
|
@@ -59,12 +59,14 @@ const Tabs = forwardRef(function Tabs({ label, tabs, defaultTabId, activeTabId,
|
|
|
59
59
|
tab.buttonProps?.onKeyDown?.(event);
|
|
60
60
|
if (event.defaultPrevented)
|
|
61
61
|
return;
|
|
62
|
-
const next = nextIndexForKey(event.key, index, tabs.length)
|
|
62
|
+
const next = nextIndexForKey(event.key, index, tabs.length, "horizontal", event.currentTarget.ownerDocument.defaultView?.getComputedStyle(event.currentTarget).direction === "rtl"
|
|
63
|
+
? "rtl"
|
|
64
|
+
: "ltr");
|
|
63
65
|
if (next === null)
|
|
64
66
|
return;
|
|
65
67
|
event.preventDefault();
|
|
66
68
|
select(tabs[next], true);
|
|
67
69
|
} }, tab.label));
|
|
68
|
-
}) }), tabs.map((tab) => (_createElement("div", { ...tab.panelProps, key: tab.id, id: panelId(tab), role: "tabpanel", "aria-labelledby": cx(buttonId(tab), tab.panelProps?.["aria-labelledby"]), hidden: tab.id !== selectedId, tabIndex: 0, className:
|
|
70
|
+
}) }), tabs.map((tab) => (_createElement("div", { ...tab.panelProps, key: tab.id, id: panelId(tab), role: "tabpanel", "aria-labelledby": cx(buttonId(tab), tab.panelProps?.["aria-labelledby"]), hidden: tab.id !== selectedId, tabIndex: 0, className: ccx("tabs-panel", tab.panelProps?.className) }, tab.content)))] }));
|
|
69
71
|
});
|
|
70
72
|
export default Tabs;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type TagProps = {
|
|
2
2
|
children: string;
|
|
3
|
+
removeLabel?: string;
|
|
3
4
|
onRemove?: () => void;
|
|
4
5
|
};
|
|
5
|
-
export default function Tag({ children, onRemove }: TagProps): import("react").JSX.Element;
|
|
6
|
+
export default function Tag({ children, onRemove, removeLabel }: TagProps): import("react").JSX.Element;
|
|
@@ -1,9 +1,10 @@
|
|
|
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
|
/** Mono "+ tag" label (as in project notes); becomes a removable chip when given onRemove. */
|
|
4
5
|
import { X } from "lucide-react";
|
|
5
|
-
export default function Tag({ children, onRemove }) {
|
|
6
|
+
export default function Tag({ children, onRemove, removeLabel }) {
|
|
6
7
|
if (!onRemove)
|
|
7
|
-
return _jsx("span", { className: "tag mono", children: children });
|
|
8
|
-
return (_jsxs("span", { className: "tag tag-removable mono", children: [children, _jsx("button", { type: "button", className: "tag-remove", "aria-label": `Remove ${children}`, onClick: onRemove, children: _jsx(X, { size: 12 }) })] }));
|
|
8
|
+
return _jsx("span", { className: ccx("tag mono"), children: children });
|
|
9
|
+
return (_jsxs("span", { className: ccx("tag tag-removable mono"), children: [children, _jsx("button", { type: "button", className: ccx("tag-remove"), "aria-label": removeLabel ?? `Remove ${children}`, onClick: onRemove, children: _jsx(X, { size: 12 }) })] }));
|
|
9
10
|
}
|
|
@@ -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
|
/** Multi-line text input with optional auto-resize and character counter. */
|
|
4
5
|
import { forwardRef, useId, useLayoutEffect, useState, } from "react";
|
|
5
6
|
import { fieldDescribedBy } from "../../lib/a11y.js";
|
|
@@ -27,7 +28,7 @@ const TextArea = forwardRef(function TextArea({ id: providedId, "aria-describedb
|
|
|
27
28
|
});
|
|
28
29
|
return (_jsx(Field, { id: id, label: label, hint: hint, error: error, required: rest.required, disabled: rest.disabled, counter: maxLength !== undefined
|
|
29
30
|
? `${rest.value !== undefined ? String(rest.value).length : length}/${maxLength}`
|
|
30
|
-
: undefined, children: _jsx("div", { className: "field-control", children: _jsx("textarea", { ...rest, ref: attach, id: id, rows: rows, maxLength: maxLength, "data-auto-resize": autoResize || undefined, "aria-invalid": error ? true : rest["aria-invalid"], "aria-describedby": [describedBy, fieldDescribedBy(id, hint, error)]
|
|
31
|
+
: undefined, children: _jsx("div", { className: ccx("field-control"), children: _jsx("textarea", { ...rest, ref: attach, id: id, rows: rows, maxLength: maxLength, "data-auto-resize": autoResize || undefined, "aria-invalid": error ? true : rest["aria-invalid"], "aria-describedby": [describedBy, fieldDescribedBy(id, hint, error)]
|
|
31
32
|
.filter(Boolean)
|
|
32
33
|
.join(" ") || undefined,
|
|
33
34
|
// Native input also runs when React deduplicates a repeated value after form.reset().
|
|
@@ -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
|
/** Single-line text input with underline styling, optional icon/adornment, and character counter. */
|
|
4
5
|
import { forwardRef, useId, useState, } from "react";
|
|
5
6
|
import { fieldDescribedBy } from "../../lib/a11y.js";
|
|
@@ -18,7 +19,7 @@ const TextField = forwardRef(function TextField({ id: providedId, "aria-describe
|
|
|
18
19
|
});
|
|
19
20
|
return (_jsx(Field, { id: id, label: label, hint: hint, error: error, required: rest.required, hideLabel: hideLabel, disabled: rest.disabled, counter: maxLength !== undefined
|
|
20
21
|
? `${rest.value !== undefined ? String(rest.value).length : length}/${maxLength}`
|
|
21
|
-
: undefined, children: _jsxs("div", { className: "field-control", children: [leadingIcon && _jsx("span", { className: "field-icon", children: leadingIcon }), _jsx("input", { ...rest, ref: attach, id: id, maxLength: maxLength, "aria-invalid": error ? true : rest["aria-invalid"], "aria-describedby": [describedBy, fieldDescribedBy(id, hint, error)]
|
|
22
|
+
: undefined, children: _jsxs("div", { className: ccx("field-control"), children: [leadingIcon && (_jsx("span", { className: ccx("field-icon"), children: leadingIcon })), _jsx("input", { ...rest, ref: attach, id: id, maxLength: maxLength, "aria-invalid": error ? true : rest["aria-invalid"], "aria-describedby": [describedBy, fieldDescribedBy(id, hint, error)]
|
|
22
23
|
.filter(Boolean)
|
|
23
24
|
.join(" ") || undefined,
|
|
24
25
|
// Native input also runs when React deduplicates a repeated value after form.reset().
|
|
@@ -28,6 +29,6 @@ const TextField = forwardRef(function TextField({ id: providedId, "aria-describe
|
|
|
28
29
|
}, onChange: (event) => {
|
|
29
30
|
setLength(event.target.value.length);
|
|
30
31
|
onChange?.(event);
|
|
31
|
-
} }), trailing && _jsx("span", { className: "field-trailing", children: trailing })] }) }));
|
|
32
|
+
} }), trailing && (_jsx("span", { className: ccx("field-trailing"), children: trailing }))] }) }));
|
|
32
33
|
});
|
|
33
34
|
export default TextField;
|