@instruments/materia-ui 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -4
- package/agent-docs.md +9 -9
- package/dist/components/accordion/accordion-root.d.ts +1 -0
- package/dist/components/accordion/accordion-root.js +1 -0
- package/dist/components/alert/alert-root.d.ts +1 -0
- package/dist/components/alert/alert-root.js +1 -0
- package/dist/components/alert-dialog/alert-dialog-root.d.ts +1 -0
- package/dist/components/alert-dialog/alert-dialog-root.js +1 -0
- package/dist/components/avatar/avatar-root.d.ts +1 -0
- package/dist/components/avatar/avatar-root.js +1 -0
- package/dist/components/badge/badge-root.d.ts +1 -0
- package/dist/components/badge/badge-root.js +1 -0
- package/dist/components/calendar/calendar-root.js +1 -0
- package/dist/components/carousel/carousel-root.d.ts +1 -0
- package/dist/components/carousel/carousel-root.js +1 -0
- package/dist/components/chart/chart-root.d.ts +1 -0
- package/dist/components/chart/chart-root.js +1 -0
- package/dist/components/collapsible/collapsible-root.d.ts +1 -0
- package/dist/components/collapsible/collapsible-root.js +1 -0
- package/dist/components/combobox/combobox-root.d.ts +1 -0
- package/dist/components/combobox/combobox-root.js +1 -0
- package/dist/components/command/command-dialog.d.ts +5 -1
- package/dist/components/command/command-dialog.js +1 -1
- package/dist/components/command/command-input.js +5 -2
- package/dist/components/command/command-root.d.ts +1 -0
- package/dist/components/command/command-root.js +1 -0
- package/dist/components/dialog/dialog-content.d.ts +2 -0
- package/dist/components/dialog/dialog-content.js +3 -2
- package/dist/components/dialog/dialog-footer.d.ts +2 -0
- package/dist/components/dialog/dialog-footer.js +8 -8
- package/dist/components/dialog/dialog-root.d.ts +1 -0
- package/dist/components/dialog/dialog-root.js +1 -0
- package/dist/components/dialog/dialog-title.d.ts +2 -0
- package/dist/components/dialog/dialog-title.js +3 -1
- package/dist/components/drawer/drawer-root.d.ts +1 -0
- package/dist/components/drawer/drawer-root.js +1 -0
- package/dist/components/heading/heading-root.d.ts +1 -0
- package/dist/components/heading/heading-root.js +1 -0
- package/dist/components/input/input-root.d.ts +1 -0
- package/dist/components/input/input-root.js +1 -0
- package/dist/components/kbd/kbd-root.d.ts +1 -0
- package/dist/components/kbd/kbd-root.js +1 -0
- package/dist/components/logo/logo-mark.d.ts +1 -1
- package/dist/components/logo/logo-mark.js +1 -1
- package/dist/components/logotype/logotype-paths.d.ts +1 -1
- package/dist/components/logotype/logotype-paths.js +1 -1
- package/dist/components/month-year-picker/month-year-picker-root.d.ts +1 -0
- package/dist/components/month-year-picker/month-year-picker-root.js +1 -0
- package/dist/components/native-select/native-select-root.d.ts +1 -0
- package/dist/components/native-select/native-select-root.js +1 -0
- package/dist/components/pill/pill-root.d.ts +1 -0
- package/dist/components/pill/pill-root.js +1 -0
- package/dist/components/popover/popover-root.d.ts +1 -0
- package/dist/components/popover/popover-root.js +1 -0
- package/dist/components/quantity-control/quantity-control-root.d.ts +1 -0
- package/dist/components/quantity-control/quantity-control-root.js +1 -0
- package/dist/components/segmented-control/segmented-control-root.js +6 -3
- package/dist/components/segmented-control/segmented-control-variants.js +4 -1
- package/dist/components/select/select-root.d.ts +1 -0
- package/dist/components/select/select-root.js +1 -0
- package/dist/components/separator/separator-root.d.ts +1 -0
- package/dist/components/separator/separator-root.js +1 -0
- package/dist/components/sheet/sheet-root.d.ts +1 -0
- package/dist/components/sheet/sheet-root.js +1 -0
- package/dist/components/spinner/spinner-root.d.ts +1 -0
- package/dist/components/spinner/spinner-root.js +1 -0
- package/dist/components/stepper/stepper-root.d.ts +1 -0
- package/dist/components/stepper/stepper-root.js +1 -0
- package/dist/components/success-tick-ripple/success-tick-ripple-root.d.ts +1 -0
- package/dist/components/success-tick-ripple/success-tick-ripple-root.js +1 -0
- package/dist/components/swatch-selector/swatch-selector-item.js +4 -1
- package/dist/components/switch/switch-root.d.ts +1 -0
- package/dist/components/switch/switch-root.js +1 -0
- package/dist/components/tag/tag-root.d.ts +1 -0
- package/dist/components/tag/tag-root.js +1 -0
- package/dist/components/textarea/textarea-root.d.ts +1 -0
- package/dist/components/textarea/textarea-root.js +1 -0
- package/dist/components/time-slot-picker/time-slot-picker-root.d.ts +1 -0
- package/dist/components/time-slot-picker/time-slot-picker-root.js +1 -0
- package/dist/components/toast/toast-root.d.ts +1 -0
- package/dist/components/toast/toast-root.js +1 -0
- package/dist/components/tooltip/tooltip-root.d.ts +1 -0
- package/dist/components/tooltip/tooltip-root.js +1 -0
- package/dist/compositions/empty-state/empty-state-root.d.ts +1 -0
- package/dist/compositions/empty-state/empty-state-root.js +1 -0
- package/dist/compositions/file-upload/file-upload-dropzone.d.ts +1 -0
- package/dist/compositions/file-upload/file-upload-dropzone.js +1 -0
- package/dist/data/decision-trees.d.ts +1 -1
- package/dist/data/decision-trees.js +1 -1
- package/dist/data/docs-manifest.d.ts +2 -0
- package/dist/data/docs-manifest.js +235 -68
- package/dist/styles/index.css +0 -1
- package/dist/tokens/segmented.d.ts +8 -0
- package/dist/tokens/segmented.js +26 -0
- package/dist/utils/cn-theme.js +1 -1
- package/lint/README.md +6 -2
- package/lint/doctrine-rules.mjs +4 -4
- package/lint/oxlint/index.d.mts +13 -5
- package/lint/oxlint/index.mjs +26 -0
- package/lint/oxlint/no-naked-element.mjs +123 -65
- package/lint/oxlint/no-render-layout-as.mjs +61 -56
- package/lint/scan-classes.d.mts +58 -5
- package/lint/scan-classes.mjs +274 -38
- package/lint/tokens.d.mts +16 -8
- package/lint/tokens.mjs +13 -3
- package/lint/type-case.d.mts +16 -8
- package/lint/type-case.mjs +17 -0
- package/package.json +27 -14
- package/lint/oxlint/fixtures/allowed.tsx +0 -12
- package/lint/oxlint/fixtures/fixture.config.mjs +0 -29
- package/lint/oxlint/fixtures/invalid.tsx +0 -24
- package/lint/oxlint/fixtures/layout-only.tsx +0 -26
- package/lint/oxlint/fixtures/render-as-invalid.tsx +0 -11
- package/lint/oxlint/fixtures/render-as-valid.tsx +0 -14
- package/lint/oxlint/fixtures/valid.tsx +0 -18
- package/lint/oxlint/no-naked-element.test.ts +0 -155
- package/lint/oxlint/no-render-layout-as.test.ts +0 -81
|
@@ -7,6 +7,7 @@ import type * as React from "react";
|
|
|
7
7
|
* @bestPractice `NativeSelect` wraps a real `<select>` - use standard `value`/`defaultValue`/`onChange` and `NativeSelectOption`/`NativeSelectOptGroup` children, not `Select` parts.
|
|
8
8
|
* @bestPractice Reach for `NativeSelect` when the native mobile picker and zero-JS behaviour matter; use `Select` when you need custom-rendered option content or styling.
|
|
9
9
|
* @bestPractice It has no built-in label - associate a `Label` via `htmlFor`/`id` for an accessible name.
|
|
10
|
+
* @keywords dropdown, picker
|
|
10
11
|
* @category forms
|
|
11
12
|
*/
|
|
12
13
|
declare const NativeSelect: ({ className, ...props }: React.ComponentProps<"select">) => React.JSX.Element;
|
|
@@ -13,6 +13,7 @@ import { Icon } from "../icon.js";
|
|
|
13
13
|
* @bestPractice `NativeSelect` wraps a real `<select>` - use standard `value`/`defaultValue`/`onChange` and `NativeSelectOption`/`NativeSelectOptGroup` children, not `Select` parts.
|
|
14
14
|
* @bestPractice Reach for `NativeSelect` when the native mobile picker and zero-JS behaviour matter; use `Select` when you need custom-rendered option content or styling.
|
|
15
15
|
* @bestPractice It has no built-in label - associate a `Label` via `htmlFor`/`id` for an accessible name.
|
|
16
|
+
* @keywords dropdown, picker
|
|
16
17
|
* @category forms
|
|
17
18
|
*/
|
|
18
19
|
const NativeSelect = ({ className, ...props }) => (_jsxs("div", { className: "group/native-select relative w-fit has-[select:disabled]:opacity-50", "data-component": "native-select-wrapper", "data-slot": "native-select-wrapper", children: [_jsx("select", { className: cn(focusInput(), `h-12 w-full min-w-0 appearance-none ${INPUT_SHELL} selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground transition-color-shadow text-paragraph-sm px-4 py-3 pr-9 shadow-xs disabled:pointer-events-none disabled:cursor-not-allowed`, hasErrorInput, className), "data-component": "native-select", "data-slot": "native-select", ...props }), _jsx(Icon, { "aria-hidden": "true", className: "pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 opacity-50 select-none", "data-component": "native-select-icon", "data-slot": "native-select-icon", icon: ChevronDown, size: "xs" })] }));
|
|
@@ -26,6 +26,7 @@ export interface PillProps extends React.HTMLAttributes<HTMLElement>, Omit<Varia
|
|
|
26
26
|
*
|
|
27
27
|
* @bestPractice Don't reach for `Pill` in app code - it's the shared styling base; use `Badge` for display-only labels/counts and `Tag` for interactive (clickable/dismissible) chips.
|
|
28
28
|
* @bestPractice `Pill` is presentation-only and has no button semantics or dismiss/click handling - those live on `Tag`; adding `onClick` here yields a non-accessible pseudo-button.
|
|
29
|
+
* @keywords chip
|
|
29
30
|
* @category data
|
|
30
31
|
*/
|
|
31
32
|
export declare const Pill: ({ as, className, tone, size, emphasis, radius, disabled, asInput, children, ref, render, ...props }: PillProps) => React.ReactElement<unknown, string | React.JSXElementConstructor<any>>;
|
|
@@ -11,6 +11,7 @@ import { DEFAULT_PILL_SIZE, PILL_SIZE_CLASSES, pillVariants, } from "../pill/pil
|
|
|
11
11
|
*
|
|
12
12
|
* @bestPractice Don't reach for `Pill` in app code - it's the shared styling base; use `Badge` for display-only labels/counts and `Tag` for interactive (clickable/dismissible) chips.
|
|
13
13
|
* @bestPractice `Pill` is presentation-only and has no button semantics or dismiss/click handling - those live on `Tag`; adding `onClick` here yields a non-accessible pseudo-button.
|
|
14
|
+
* @keywords chip
|
|
14
15
|
* @category data
|
|
15
16
|
*/
|
|
16
17
|
export const Pill = ({ as = "span", className, tone, size, emphasis, radius, disabled, asInput, children, ref, render, ...props }) => {
|
|
@@ -21,6 +21,7 @@ import type { ComponentSize } from "../../tokens/size.js";
|
|
|
21
21
|
* @bestPractice Use `Popover` for an anchored non-blocking panel dismissed on outside-click / Esc; a page-blocking modal is a `Dialog`, a menu of actions is a `DropdownMenu`.
|
|
22
22
|
* @bestPractice It's non-modal by default (`modal={false}`) so the page stays interactive - pass `modal` only when you must trap focus and block the background.
|
|
23
23
|
* @bestPractice `PopoverContent` portals to the body; anchor to a non-trigger element with its `anchor` prop.
|
|
24
|
+
* @keywords popup, flyout, popout
|
|
24
25
|
* @category overlays
|
|
25
26
|
*/
|
|
26
27
|
declare const Popover: <Payload>({ ...props }: PopoverPrimitive.Root.Props<Payload>) => import("react").JSX.Element;
|
|
@@ -24,6 +24,7 @@ import { OVERLAY_SCRIM_DARK, POPOVER_CONTENT_ANIMATION, POPOVER_SURFACE, } from
|
|
|
24
24
|
* @bestPractice Use `Popover` for an anchored non-blocking panel dismissed on outside-click / Esc; a page-blocking modal is a `Dialog`, a menu of actions is a `DropdownMenu`.
|
|
25
25
|
* @bestPractice It's non-modal by default (`modal={false}`) so the page stays interactive - pass `modal` only when you must trap focus and block the background.
|
|
26
26
|
* @bestPractice `PopoverContent` portals to the body; anchor to a non-trigger element with its `anchor` prop.
|
|
27
|
+
* @keywords popup, flyout, popout
|
|
27
28
|
* @category overlays
|
|
28
29
|
*/
|
|
29
30
|
const Popover = ({ ...props }) => (_jsx(PopoverPrimitive.Root, { "data-component": "popover", "data-slot": "popover", ...props }));
|
|
@@ -44,6 +44,7 @@ interface QuantityControlProps {
|
|
|
44
44
|
* @bestPractice Pass `onRemove` when hitting the minimum should delete the item - the decrement button then swaps to a trash icon; without it, decrement just stops at `min`.
|
|
45
45
|
* @bestPractice It's controlled - pass the current `value` and update it in `onIncrement`/`onDecrement`, which fire once per step for clicks, arrow keys and each repeat of a held button. Typing a number is not supported.
|
|
46
46
|
* @bestPractice Tab focuses the quantity itself (the stepper buttons are skipped, as in any number field), announced by its `label`. In a list of lines, pass a `label` naming the item so each field is told apart.
|
|
47
|
+
* @keywords counter, number input, increment, spinbutton
|
|
47
48
|
* @category actions
|
|
48
49
|
*/
|
|
49
50
|
export declare const QuantityControl: ({ value, label, onIncrement, onDecrement, onRemove, min, max, disabled, radius, testId, }: QuantityControlProps) => React.JSX.Element;
|
|
@@ -47,6 +47,7 @@ const stepperProps = {
|
|
|
47
47
|
* @bestPractice Pass `onRemove` when hitting the minimum should delete the item - the decrement button then swaps to a trash icon; without it, decrement just stops at `min`.
|
|
48
48
|
* @bestPractice It's controlled - pass the current `value` and update it in `onIncrement`/`onDecrement`, which fire once per step for clicks, arrow keys and each repeat of a held button. Typing a number is not supported.
|
|
49
49
|
* @bestPractice Tab focuses the quantity itself (the stepper buttons are skipped, as in any number field), announced by its `label`. In a list of lines, pass a `label` naming the item so each field is told apart.
|
|
50
|
+
* @keywords counter, number input, increment, spinbutton
|
|
50
51
|
* @category actions
|
|
51
52
|
*/
|
|
52
53
|
export const QuantityControl = ({ value, label = "Quantity", onIncrement, onDecrement, onRemove, min = 1, max = 99, disabled = false, radius = "rounded", testId, }) => {
|
|
@@ -6,7 +6,7 @@ import { ScrollFade } from "@instruments/materia-ui/components/scroll-fade";
|
|
|
6
6
|
import { cn } from "@instruments/materia-ui/utils/cn";
|
|
7
7
|
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { getResponsiveSizeOverrides, resolveResponsiveBase, } from "../../tokens/responsive.js";
|
|
9
|
-
import { SEGMENTED_GAP, SEGMENTED_SHELL_BASE,
|
|
9
|
+
import { SEGMENTED_GAP, SEGMENTED_SHELL_BASE, SEGMENTED_SHELL_PADDING_X, SEGMENTED_SHELL_PADDING_Y, } from "../../tokens/segmented.js";
|
|
10
10
|
import { Flex } from "../flex.js";
|
|
11
11
|
import { SegmentedControlContext } from "./segmented-control-context.js";
|
|
12
12
|
import { useSingleSelection } from "./use-single-selection.js";
|
|
@@ -65,10 +65,13 @@ const SegmentedControl = ({ className, size = "sm", fullWidth = false, value, de
|
|
|
65
65
|
viewport?.removeEventListener("scroll", checkScroll);
|
|
66
66
|
};
|
|
67
67
|
}, []);
|
|
68
|
-
return (_jsx(ToggleGroup, { className: cn(SEGMENTED_SHELL_BASE, "group/segmented-control inline-flex h-auto max-w-full items-center overflow-hidden",
|
|
68
|
+
return (_jsx(ToggleGroup, { className: cn(SEGMENTED_SHELL_BASE, "group/segmented-control inline-flex h-auto max-w-full items-center overflow-hidden", SEGMENTED_SHELL_PADDING_X[resolvedSize], getResponsiveSizeOverrides(size, SEGMENTED_SHELL_PADDING_X), fullWidth && "flex w-full", className), "data-component": "segmented-control", "data-size": resolvedSize, "data-slot": "segmented-control", onValueChange: (groupValue) => {
|
|
69
69
|
// Single-select group: Base UI sends `[]` when the active item is
|
|
70
70
|
// re-clicked; the selection hook swallows that deselect.
|
|
71
71
|
selection.onValueChange(groupValue[0] ?? "");
|
|
72
|
-
}, value: [selection.value], ...props, children: _jsx(SegmentedControlContext.Provider, { value: contextValue, children: _jsxs("div", { className: "relative min-w-0 flex-1", ref: containerRef, children: [_jsx(ScrollArea, { hideScrollbar: true, orientation: "horizontal", children: _jsx(Flex, { align: "center", className: cn(
|
|
72
|
+
}, value: [selection.value], ...props, children: _jsx(SegmentedControlContext.Provider, { value: contextValue, children: _jsxs("div", { className: "relative min-w-0 flex-1", ref: containerRef, children: [_jsx(ScrollArea, { hideScrollbar: true, orientation: "horizontal", children: _jsx(Flex, { align: "center", className: cn(
|
|
73
|
+
// Vertical padding sits inside the scroll area, which clips,
|
|
74
|
+
// so each item's hit area reaches the shell's full height.
|
|
75
|
+
SEGMENTED_SHELL_PADDING_Y[resolvedSize], getResponsiveSizeOverrides(size, SEGMENTED_SHELL_PADDING_Y), SEGMENTED_GAP[resolvedSize], getResponsiveSizeOverrides(size, SEGMENTED_GAP), fullWidth && "w-full"), inline: true, children: children }) }), showEndFade && (_jsx(ScrollFade, { className: "rounded-r-full", orientation: "horizontal", position: "end", size: "sm", theme: "gray" }))] }) }) }));
|
|
73
76
|
};
|
|
74
77
|
export { SegmentedControl };
|
|
@@ -20,7 +20,10 @@ const triggerPaddingCompoundVariants = typedEntries(SEGMENTED_TRIGGER_PADDING).m
|
|
|
20
20
|
* Structure mirrors `tabsTriggerVariants` (pill): the `size` variant only sets
|
|
21
21
|
* text size; ALL padding is applied through size-keyed compound variants.
|
|
22
22
|
*/
|
|
23
|
-
export const segmentedControlItemVariants = cva(cn(focusRing(), "text-muted-foreground relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full font-medium whitespace-nowrap",
|
|
23
|
+
export const segmentedControlItemVariants = cva(cn(focusRing(), "text-muted-foreground relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full font-medium whitespace-nowrap",
|
|
24
|
+
// An invisible hit area overhanging the segment; the root's scroll area
|
|
25
|
+
// trims it to the track, so a tap anywhere in the track's height lands.
|
|
26
|
+
"after:absolute after:inset-x-0 after:-inset-y-3", `transition-color-shadow-border ${DISABLED}`, "data-pressed:text-foreground dark:text-gray-500 dark:data-pressed:text-gray-100", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"), {
|
|
24
27
|
compoundVariants: triggerPaddingCompoundVariants,
|
|
25
28
|
defaultVariants: {
|
|
26
29
|
size: "sm",
|
|
@@ -9,6 +9,7 @@ import type { WithTestId } from "../../utils/with-test-id.js";
|
|
|
9
9
|
* @bestPractice Use `Select` to pick one option from a short closed list; when users need type-to-filter, use `Combobox` instead.
|
|
10
10
|
* @bestPractice Drive it with `value` + `onValueChange` (controlled) or `defaultValue` (uncontrolled), and give it an accessible name via the trigger (`Field`/`Label`).
|
|
11
11
|
* @bestPractice Pass `items` (a value-to-label map or `{ value, label }[]`) so the trigger shows the selected label even before the popup has mounted.
|
|
12
|
+
* @keywords dropdown, picker
|
|
12
13
|
* @category forms
|
|
13
14
|
*/
|
|
14
15
|
declare const Select: <Value, Multiple extends boolean | undefined = false>({ testId, ...props }: WithTestId<SelectPrimitive.Root.Props<Value, Multiple>>) => import("react").JSX.Element;
|
|
@@ -10,6 +10,7 @@ import { Select as SelectPrimitive } from "@base-ui/react/select";
|
|
|
10
10
|
* @bestPractice Use `Select` to pick one option from a short closed list; when users need type-to-filter, use `Combobox` instead.
|
|
11
11
|
* @bestPractice Drive it with `value` + `onValueChange` (controlled) or `defaultValue` (uncontrolled), and give it an accessible name via the trigger (`Field`/`Label`).
|
|
12
12
|
* @bestPractice Pass `items` (a value-to-label map or `{ value, label }[]`) so the trigger shows the selected label even before the popup has mounted.
|
|
13
|
+
* @keywords dropdown, picker
|
|
13
14
|
* @category forms
|
|
14
15
|
*/
|
|
15
16
|
const Select = ({ testId, ...props }) => (_jsx(SelectPrimitive.Root, { "data-component": "select", "data-slot": "select", "data-testid": testId, ...props }));
|
|
@@ -24,6 +24,7 @@ interface SeparatorProps extends SeparatorPrimitive.Props {
|
|
|
24
24
|
*
|
|
25
25
|
* @bestPractice It is announced as a separator by default - pass `aria-hidden` when the rule is purely decorative.
|
|
26
26
|
* @bestPractice For an inline rule set `orientation="vertical"` and give it an explicit height (e.g. `className="h-6"`) - a vertical separator has no intrinsic length.
|
|
27
|
+
* @keywords divider, rule, hr
|
|
27
28
|
* @category layout
|
|
28
29
|
*/
|
|
29
30
|
declare const Separator: ({ className, orientation, variant, ...props }: SeparatorProps) => import("react").JSX.Element;
|
|
@@ -22,6 +22,7 @@ const VARIANT_CLASSES = {
|
|
|
22
22
|
*
|
|
23
23
|
* @bestPractice It is announced as a separator by default - pass `aria-hidden` when the rule is purely decorative.
|
|
24
24
|
* @bestPractice For an inline rule set `orientation="vertical"` and give it an explicit height (e.g. `className="h-6"`) - a vertical separator has no intrinsic length.
|
|
25
|
+
* @keywords divider, rule, hr
|
|
25
26
|
* @category layout
|
|
26
27
|
*/
|
|
27
28
|
const Separator = ({ className, orientation = "horizontal", variant = "default", ...props }) => (_jsx(SeparatorPrimitive, { className: cn("shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", VARIANT_CLASSES[variant], className), "data-component": "separator", "data-slot": "separator", orientation: orientation, ...props }));
|
|
@@ -5,6 +5,7 @@ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
|
5
5
|
*
|
|
6
6
|
* @bestPractice Use `Sheet` for a simple edge panel holding a focused form / settings; a drag-to-dismiss bottom sheet is a `Drawer`, and nested panels are a `Sidepanel`.
|
|
7
7
|
* @bestPractice Set the entry edge via `SheetContent` `side`; always render a `SheetTitle` (wrap in `sr-only` if hidden) so the panel is announced.
|
|
8
|
+
* @keywords drawer, side sheet, slide-over
|
|
8
9
|
* @category overlays
|
|
9
10
|
*/
|
|
10
11
|
export declare const Sheet: (props: DialogPrimitive.Root.Props<unknown>) => import("react").ReactElement;
|
|
@@ -6,6 +6,7 @@ import { makeOverlayRoot } from "../overlay-primitives.js";
|
|
|
6
6
|
*
|
|
7
7
|
* @bestPractice Use `Sheet` for a simple edge panel holding a focused form / settings; a drag-to-dismiss bottom sheet is a `Drawer`, and nested panels are a `Sidepanel`.
|
|
8
8
|
* @bestPractice Set the entry edge via `SheetContent` `side`; always render a `SheetTitle` (wrap in `sr-only` if hidden) so the panel is announced.
|
|
9
|
+
* @keywords drawer, side sheet, slide-over
|
|
9
10
|
* @category overlays
|
|
10
11
|
*/
|
|
11
12
|
export const Sheet = makeOverlayRoot(DialogPrimitive.Root, {
|
|
@@ -20,6 +20,7 @@ export type SpinnerProps = LucideProps & {
|
|
|
20
20
|
*
|
|
21
21
|
* @bestPractice Use `Spinner` for small inline / action loading (inside a `Button`, next to text); a blocking full region is `LoadingState`, a layout placeholder is `Skeleton`.
|
|
22
22
|
* @bestPractice A bare `Spinner` carries no accessible name - wrap it in a labelled live region (e.g. `LoadingState`) when it's the sole loading signal.
|
|
23
|
+
* @keywords loader, loading, activity indicator
|
|
23
24
|
* @category feedback
|
|
24
25
|
*/
|
|
25
26
|
export declare const Spinner: ({ size, className, ...props }: SpinnerProps) => import("react").JSX.Element;
|
|
@@ -41,6 +41,7 @@ const SPINNER_STROKE = {
|
|
|
41
41
|
*
|
|
42
42
|
* @bestPractice Use `Spinner` for small inline / action loading (inside a `Button`, next to text); a blocking full region is `LoadingState`, a layout placeholder is `Skeleton`.
|
|
43
43
|
* @bestPractice A bare `Spinner` carries no accessible name - wrap it in a labelled live region (e.g. `LoadingState`) when it's the sole loading signal.
|
|
44
|
+
* @keywords loader, loading, activity indicator
|
|
44
45
|
* @category feedback
|
|
45
46
|
*/
|
|
46
47
|
export const Spinner = ({ size = "base", className, ...props }) => {
|
|
@@ -26,6 +26,7 @@ export type StepperProps = {
|
|
|
26
26
|
*
|
|
27
27
|
* @bestPractice Give each step a `StepperItem` child in order - progress is derived from child index, so conditionally rendering steps shifts the fill.
|
|
28
28
|
* @bestPractice Control completion via `value`/`onValueChange` (or uncontrolled `defaultValue`); the fill reaches only the last *completed* step, not the current one.
|
|
29
|
+
* @keywords wizard, steps, multi-step
|
|
29
30
|
* @category navigation
|
|
30
31
|
*/
|
|
31
32
|
export declare const Stepper: ({ value, defaultValue, onValueChange, size, className, children, ...rest }: StepperProps) => React.JSX.Element;
|
|
@@ -13,6 +13,7 @@ import { DEFAULT_STEPPER_SIZE, STEPPER_TRACK_SIZE_CLASSES, stepperRootVariants,
|
|
|
13
13
|
*
|
|
14
14
|
* @bestPractice Give each step a `StepperItem` child in order - progress is derived from child index, so conditionally rendering steps shifts the fill.
|
|
15
15
|
* @bestPractice Control completion via `value`/`onValueChange` (or uncontrolled `defaultValue`); the fill reaches only the last *completed* step, not the current one.
|
|
16
|
+
* @keywords wizard, steps, multi-step
|
|
16
17
|
* @category navigation
|
|
17
18
|
*/
|
|
18
19
|
export const Stepper = ({ value, defaultValue = 0, onValueChange, size = "base", className, children, ...rest }) => {
|
|
@@ -30,6 +30,7 @@ export interface SuccessTickRippleProps {
|
|
|
30
30
|
*
|
|
31
31
|
* @bestPractice Wrap `SuccessTickRipple` in a `position: relative; overflow: hidden` parent - the ripple field is absolutely positioned and bleeds past an unbounded container.
|
|
32
32
|
* @bestPractice It's decorative (`aria-hidden`) unless you pass `aria-label`, which exposes it as an `img`; animations are CSS-only and already collapse under `prefers-reduced-motion`.
|
|
33
|
+
* @keywords celebrate, success, confirmation
|
|
33
34
|
* @category feedback
|
|
34
35
|
*/
|
|
35
36
|
export declare const SuccessTickRipple: ({ size, className, "aria-label": ariaLabel, }: SuccessTickRippleProps) => import("react").JSX.Element;
|
|
@@ -23,6 +23,7 @@ const CHECK_PATH = "M5 13.5 9 17.5 19 6.5";
|
|
|
23
23
|
*
|
|
24
24
|
* @bestPractice Wrap `SuccessTickRipple` in a `position: relative; overflow: hidden` parent - the ripple field is absolutely positioned and bleeds past an unbounded container.
|
|
25
25
|
* @bestPractice It's decorative (`aria-hidden`) unless you pass `aria-label`, which exposes it as an `img`; animations are CSS-only and already collapse under `prefers-reduced-motion`.
|
|
26
|
+
* @keywords celebrate, success, confirmation
|
|
26
27
|
* @category feedback
|
|
27
28
|
*/
|
|
28
29
|
export const SuccessTickRipple = ({ size = 64, className, "aria-label": ariaLabel, }) => {
|
|
@@ -24,7 +24,10 @@ export const SwatchSelectorItem = ({ value, color, children, size: sizeOverride,
|
|
|
24
24
|
const isSelected = selectedValue === value;
|
|
25
25
|
const button = (_jsx(TogglePrimitive, { "aria-label": props["aria-label"] ?? `Select ${value}`, className: cn(
|
|
26
26
|
// Focus styles only - visual handled by Swatch
|
|
27
|
-
"inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full",
|
|
27
|
+
"relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full",
|
|
28
|
+
// An invisible hit area at least 44px tall and 5px wider each side, so
|
|
29
|
+
// a small swatch fills its slot in a 10px-gapped row.
|
|
30
|
+
"after:absolute after:-inset-x-[5px] after:top-1/2 after:h-full after:min-h-11 after:-translate-y-1/2", focusRing(), "disabled:cursor-not-allowed disabled:opacity-50", className), "data-component": "swatch-selector-item", "data-slot": "swatch-selector-item", onClick: (event) => {
|
|
28
31
|
event.stopPropagation();
|
|
29
32
|
}, value: value, ...props, children: _jsx(Swatch, { ...(color === undefined ? {} : { color }), selected: isSelected, size: resolvedSize, unavailable: unavailable, children: children }) }));
|
|
30
33
|
if (unavailable) {
|
|
@@ -42,6 +42,7 @@ type SwitchProps = Omit<React.ComponentProps<typeof SwitchPrimitive.Root>, "size
|
|
|
42
42
|
* @bestPractice Use `Switch` for an instantly-applied on/off setting; use `Checkbox` for opt-ins confirmed by a submit, and `RadioGroup` for mutually-exclusive choices.
|
|
43
43
|
* @bestPractice Controlled use needs `checked` + `onCheckedChange`; with only `checked` the switch won't move - use `defaultChecked` for uncontrolled.
|
|
44
44
|
* @bestPractice `Switch` has no built-in label - associate a `Label` via `htmlFor`/`id` so the toggle has an accessible name.
|
|
45
|
+
* @keywords toggle, on off
|
|
45
46
|
* @category forms
|
|
46
47
|
*/
|
|
47
48
|
declare const Switch: ({ className, size, ...props }: SwitchProps) => React.JSX.Element;
|
|
@@ -57,6 +57,7 @@ export const SWITCH_THUMB_CLASSES = {
|
|
|
57
57
|
* @bestPractice Use `Switch` for an instantly-applied on/off setting; use `Checkbox` for opt-ins confirmed by a submit, and `RadioGroup` for mutually-exclusive choices.
|
|
58
58
|
* @bestPractice Controlled use needs `checked` + `onCheckedChange`; with only `checked` the switch won't move - use `defaultChecked` for uncontrolled.
|
|
59
59
|
* @bestPractice `Switch` has no built-in label - associate a `Label` via `htmlFor`/`id` so the toggle has an accessible name.
|
|
60
|
+
* @keywords toggle, on off
|
|
60
61
|
* @category forms
|
|
61
62
|
*/
|
|
62
63
|
const Switch = ({ className, size, ...props }) => (_jsx(SwitchPrimitive.Root, { nativeButton: true, render: (buttonProps) => _jsx("button", { type: "button", ...buttonProps }), className: cn("peer data-checked:bg-primary data-unchecked:bg-muted dark:data-checked:bg-muted-foreground dark:data-unchecked:bg-secondary inline-flex shrink-0 items-center rounded-full border-transparent outline-none disabled:cursor-not-allowed disabled:opacity-50 data-checked:shadow-xs", getResponsiveSizeClasses(size, SWITCH_TRACK_CLASSES, DEFAULT_SWITCH_SIZE), focusInput(), hasErrorInput,
|
|
@@ -53,6 +53,7 @@ export interface TagProps extends Omit<React.HTMLAttributes<HTMLElement>, "child
|
|
|
53
53
|
* @bestPractice With both actions, HTML attributes (including the accessible name and selected state) apply to the selection button; `className` and `style` style the outer chip.
|
|
54
54
|
* @bestPractice Set at most one leading visual - `loading` > `imageUrl` > `dot` > `icon` (in that precedence); pass a Lucide component to `icon` (not `<Icon/>`), and `imageUrl` renders a circular `<img alt="">`, so keep it decorative.
|
|
55
55
|
* @bestPractice When `onDismiss` is set, Backspace/Delete also removes the tag while it's focused - don't bind your own conflicting key handler.
|
|
56
|
+
* @keywords chip, token
|
|
56
57
|
* @category data
|
|
57
58
|
*/
|
|
58
59
|
export declare const Tag: ({ className, style, tone, size, emphasis, asInput, radius, disabled, children, icon, dot, imageUrl, loading, onClick, onDismiss, dismissIcon, hideDismiss, ...props }: TagProps) => React.JSX.Element;
|
|
@@ -161,6 +161,7 @@ const getTagAppearanceProps = ({ emphasis, asInput, disabled, radius, }) => {
|
|
|
161
161
|
* @bestPractice With both actions, HTML attributes (including the accessible name and selected state) apply to the selection button; `className` and `style` style the outer chip.
|
|
162
162
|
* @bestPractice Set at most one leading visual - `loading` > `imageUrl` > `dot` > `icon` (in that precedence); pass a Lucide component to `icon` (not `<Icon/>`), and `imageUrl` renders a circular `<img alt="">`, so keep it decorative.
|
|
163
163
|
* @bestPractice When `onDismiss` is set, Backspace/Delete also removes the tag while it's focused - don't bind your own conflicting key handler.
|
|
164
|
+
* @keywords chip, token
|
|
164
165
|
* @category data
|
|
165
166
|
*/
|
|
166
167
|
export const Tag = ({ className, style, tone, size = "base", emphasis, asInput, radius, disabled, children, icon, dot, imageUrl, loading = false, onClick, onDismiss, dismissIcon, hideDismiss = false, ...props }) => {
|
|
@@ -26,6 +26,7 @@ interface TextareaProps extends WithTestId<React.ComponentProps<"textarea">> {
|
|
|
26
26
|
* @bestPractice Inside a `Field`, let invalid state cascade like `Input` - omit `hasError`; pass it only to force error styling on a standalone `Textarea`.
|
|
27
27
|
* @bestPractice It auto-grows with content (`field-sizing-content`); set `rows` for a minimum height in lines rather than pinning height via CSS.
|
|
28
28
|
* @bestPractice Give it a label - inside a `Field` a `FieldLabel` targets it with no ids to wire; standalone, point a `Label`'s `htmlFor` at its `id`. A placeholder is not an accessible name.
|
|
29
|
+
* @keywords multiline, text area
|
|
29
30
|
* @category forms
|
|
30
31
|
*/
|
|
31
32
|
declare const Textarea: ({ className, testId, hasError, id, name, disabled, rows, style, ...props }: TextareaProps) => React.JSX.Element;
|
|
@@ -29,6 +29,7 @@ import { useTextareaRows } from "./use-textarea-rows.js";
|
|
|
29
29
|
* @bestPractice Inside a `Field`, let invalid state cascade like `Input` - omit `hasError`; pass it only to force error styling on a standalone `Textarea`.
|
|
30
30
|
* @bestPractice It auto-grows with content (`field-sizing-content`); set `rows` for a minimum height in lines rather than pinning height via CSS.
|
|
31
31
|
* @bestPractice Give it a label - inside a `Field` a `FieldLabel` targets it with no ids to wire; standalone, point a `Label`'s `htmlFor` at its `id`. A placeholder is not an accessible name.
|
|
32
|
+
* @keywords multiline, text area
|
|
32
33
|
* @category forms
|
|
33
34
|
*/
|
|
34
35
|
const Textarea = ({ className, testId, hasError, id, name, disabled, rows, style, ...props }) => {
|
|
@@ -34,6 +34,7 @@ export interface TimeSlotPickerProps extends Omit<React.ComponentProps<"div">, "
|
|
|
34
34
|
* @bestPractice Pass slot values already formatted for display, or give each slot a `label` - the picker does no time parsing or 12/24-hour formatting.
|
|
35
35
|
* @bestPractice Name the group with `aria-label` or `aria-labelledby`; screen readers announce it before the first slot.
|
|
36
36
|
* @bestPractice Use `TimeSlotPicker` for a fixed set of offered times; for any time the user types, use `Input type="time"`.
|
|
37
|
+
* @keywords date, appointment, booking, schedule
|
|
37
38
|
* @category forms
|
|
38
39
|
*/
|
|
39
40
|
export declare const TimeSlotPicker: ({ className, defaultValue, disabled, name, onValueChange, slots, value, ...props }: TimeSlotPickerProps) => React.JSX.Element;
|
|
@@ -19,6 +19,7 @@ focusRing(), "inline-flex h-9 cursor-pointer items-center justify-center px-4 se
|
|
|
19
19
|
* @bestPractice Pass slot values already formatted for display, or give each slot a `label` - the picker does no time parsing or 12/24-hour formatting.
|
|
20
20
|
* @bestPractice Name the group with `aria-label` or `aria-labelledby`; screen readers announce it before the first slot.
|
|
21
21
|
* @bestPractice Use `TimeSlotPicker` for a fixed set of offered times; for any time the user types, use `Input type="time"`.
|
|
22
|
+
* @keywords date, appointment, booking, schedule
|
|
22
23
|
* @category forms
|
|
23
24
|
*/
|
|
24
25
|
export const TimeSlotPicker = ({ className, defaultValue, disabled, name, onValueChange, slots, value, ...props }) => {
|
|
@@ -34,6 +34,7 @@ export interface ToasterProps {
|
|
|
34
34
|
* @bestPractice Use toasts for transient, auto-dismissing notifications; a persistent inline message is an `Alert`.
|
|
35
35
|
* @bestPractice Mount `<Toaster />` exactly once near the app root, then fire toasts with `toast()` from `@instruments/materia-ui/components/toast` - don't render a `Toaster` per view.
|
|
36
36
|
* @bestPractice Press F6 to move focus into the toasts; hovering or focusing the stack pauses dismissal and fans it open.
|
|
37
|
+
* @keywords notification, snackbar, flash message
|
|
37
38
|
* @category feedback
|
|
38
39
|
*/
|
|
39
40
|
export declare const Toaster: ({ position, duration, limit, className, }: ToasterProps) => React.JSX.Element;
|
|
@@ -101,6 +101,7 @@ const ToastList = ({ edge }) => {
|
|
|
101
101
|
* @bestPractice Use toasts for transient, auto-dismissing notifications; a persistent inline message is an `Alert`.
|
|
102
102
|
* @bestPractice Mount `<Toaster />` exactly once near the app root, then fire toasts with `toast()` from `@instruments/materia-ui/components/toast` - don't render a `Toaster` per view.
|
|
103
103
|
* @bestPractice Press F6 to move focus into the toasts; hovering or focusing the stack pauses dismissal and fans it open.
|
|
104
|
+
* @keywords notification, snackbar, flash message
|
|
104
105
|
* @category feedback
|
|
105
106
|
*/
|
|
106
107
|
export const Toaster = ({ position = "bottom-center", duration = 4000, limit = 3, className, }) => {
|
|
@@ -28,6 +28,7 @@ declare const TooltipProvider: ({ delay, ...props }: TooltipPrimitive.Provider.P
|
|
|
28
28
|
* @bestPractice Keep `TooltipContent` a short, non-interactive label - no buttons or links inside; for rich or actionable hover content use `HoverCard`.
|
|
29
29
|
* @bestPractice The trigger must be focusable (a button / link, or `render` onto one) or keyboard and AT users never see the tip.
|
|
30
30
|
* @bestPractice Share one `TooltipProvider` to tune `delay` / skip-delay for a group; `Tooltip` wraps its own provider, so you only need it for grouping.
|
|
31
|
+
* @keywords hint, title
|
|
31
32
|
* @category overlays
|
|
32
33
|
*/
|
|
33
34
|
declare const Tooltip: <Payload>({ delay, ...props }: TooltipPrimitive.Root.Props<Payload> & {
|
|
@@ -32,6 +32,7 @@ const TooltipProvider = ({ delay = 0, ...props }) => (_jsx(TooltipPrimitive.Prov
|
|
|
32
32
|
* @bestPractice Keep `TooltipContent` a short, non-interactive label - no buttons or links inside; for rich or actionable hover content use `HoverCard`.
|
|
33
33
|
* @bestPractice The trigger must be focusable (a button / link, or `render` onto one) or keyboard and AT users never see the tip.
|
|
34
34
|
* @bestPractice Share one `TooltipProvider` to tune `delay` / skip-delay for a group; `Tooltip` wraps its own provider, so you only need it for grouping.
|
|
35
|
+
* @keywords hint, title
|
|
35
36
|
* @category overlays
|
|
36
37
|
*/
|
|
37
38
|
const Tooltip = ({ delay, ...props }) => (_jsx(TooltipProvider, { ...(delay === undefined ? {} : { delay }), children: _jsx(TooltipPrimitive.Root, { "data-component": "tooltip", "data-slot": "tooltip", ...props }) }));
|
|
@@ -41,6 +41,7 @@ export interface EmptyStateProps extends Omit<EmptyProps, "children" | "title">
|
|
|
41
41
|
*
|
|
42
42
|
* @bestPractice Use `EmptyState` for "no data / nothing here"; a failure the user can retry is an `ErrorState` (which also composes `Empty`), so don't hand-roll a retry empty.
|
|
43
43
|
* @bestPractice Set `level` to keep the document outline gapless where you place it - a full-page empty near an `h2` wants `level="3"`, but a nested one may need `"4"`.
|
|
44
|
+
* @keywords no results, zero state, blank slate
|
|
44
45
|
* @category states
|
|
45
46
|
*/
|
|
46
47
|
export declare const EmptyState: ({ actions, description, icon, iconPlain, iconTone, level, media, title, ...emptyProps }: EmptyStateProps) => React.JSX.Element;
|
|
@@ -23,6 +23,7 @@ import { EmptyTitle } from "../../components/empty/empty-title.js";
|
|
|
23
23
|
*
|
|
24
24
|
* @bestPractice Use `EmptyState` for "no data / nothing here"; a failure the user can retry is an `ErrorState` (which also composes `Empty`), so don't hand-roll a retry empty.
|
|
25
25
|
* @bestPractice Set `level` to keep the document outline gapless where you place it - a full-page empty near an `h2` wants `level="3"`, but a nested one may need `"4"`.
|
|
26
|
+
* @keywords no results, zero state, blank slate
|
|
26
27
|
* @category states
|
|
27
28
|
*/
|
|
28
29
|
export const EmptyState = ({ actions, description, icon, iconPlain, iconTone, level = "3", media, title, ...emptyProps }) => {
|
|
@@ -46,6 +46,7 @@ interface FileUploadDropzoneProps {
|
|
|
46
46
|
* @bestPractice `FileUploadDropzone` is presentational - wire it to the `useFileUpload` hook for the actual file input, drag state, and file list; it stores nothing itself.
|
|
47
47
|
* @bestPractice Forward the hook's `handleDragEnter/Leave/Over/Drop` and drag state into `dragging` so the drag styling matches the real drop-target state.
|
|
48
48
|
* @bestPractice Keep `title`/`description` (accepted types, size limit) accurate - they are the zone's only affordance copy; override the whole body via `children` when needed.
|
|
49
|
+
* @keywords dropzone, attachment, file input
|
|
49
50
|
* @category inputs
|
|
50
51
|
*/
|
|
51
52
|
export declare const FileUploadDropzone: ({ ref, dragging, disabled, icon, title, description, onDragEnter, onDragLeave, onDragOver, onDrop, onClick, className, children, }: FileUploadDropzoneProps) => import("react").JSX.Element;
|
|
@@ -28,6 +28,7 @@ import { Text } from "../../components/text.js";
|
|
|
28
28
|
* @bestPractice `FileUploadDropzone` is presentational - wire it to the `useFileUpload` hook for the actual file input, drag state, and file list; it stores nothing itself.
|
|
29
29
|
* @bestPractice Forward the hook's `handleDragEnter/Leave/Over/Drop` and drag state into `dragging` so the drag styling matches the real drop-target state.
|
|
30
30
|
* @bestPractice Keep `title`/`description` (accepted types, size limit) accurate - they are the zone's only affordance copy; override the whole body via `children` when needed.
|
|
31
|
+
* @keywords dropzone, attachment, file input
|
|
31
32
|
* @category inputs
|
|
32
33
|
*/
|
|
33
34
|
export const FileUploadDropzone = ({ ref, dragging = false, disabled = false, icon, title = "Drop files here or click to upload", description = "PNG, JPG, GIF up to 10MB", onDragEnter, onDragLeave, onDragOver, onDrop, onClick, className, children, }) => {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* unit's page (`/components/<slug>` or `/compositions/<slug>`).
|
|
11
11
|
*
|
|
12
12
|
* Every leaf slug is validated against the generated docs manifest at
|
|
13
|
-
* generation time (`scripts/generate-docs-manifest.
|
|
13
|
+
* generation time (`scripts/generate-docs-manifest.ts`), so a component rename
|
|
14
14
|
* fails the manifest freshness test until the tree is updated - the drift gate. Family `id`s
|
|
15
15
|
* are validated against `COMPONENT_CATEGORIES`; a family renders on the shelf of
|
|
16
16
|
* its `id`, and several families may share a shelf (three feedback families
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* unit's page (`/components/<slug>` or `/compositions/<slug>`).
|
|
11
11
|
*
|
|
12
12
|
* Every leaf slug is validated against the generated docs manifest at
|
|
13
|
-
* generation time (`scripts/generate-docs-manifest.
|
|
13
|
+
* generation time (`scripts/generate-docs-manifest.ts`), so a component rename
|
|
14
14
|
* fails the manifest freshness test until the tree is updated - the drift gate. Family `id`s
|
|
15
15
|
* are validated against `COMPONENT_CATEGORIES`; a family renders on the shelf of
|
|
16
16
|
* its `id`, and several families may share a shelf (three feedback families
|
|
@@ -26,6 +26,8 @@ export interface DocsComponentEntry {
|
|
|
26
26
|
description: string;
|
|
27
27
|
exports: DocsExportEntry[];
|
|
28
28
|
bestPractices: string[];
|
|
29
|
+
/** Words an agent might search for that the title and description don't use, from `@keywords`. */
|
|
30
|
+
keywords: string[];
|
|
29
31
|
/** Same-layer component/composition slugs this unit's implementation imports. */
|
|
30
32
|
uses: string[];
|
|
31
33
|
/** Same-layer component/composition slugs that import this unit. */
|