@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.
Files changed (117) hide show
  1. package/README.md +2 -4
  2. package/agent-docs.md +9 -9
  3. package/dist/components/accordion/accordion-root.d.ts +1 -0
  4. package/dist/components/accordion/accordion-root.js +1 -0
  5. package/dist/components/alert/alert-root.d.ts +1 -0
  6. package/dist/components/alert/alert-root.js +1 -0
  7. package/dist/components/alert-dialog/alert-dialog-root.d.ts +1 -0
  8. package/dist/components/alert-dialog/alert-dialog-root.js +1 -0
  9. package/dist/components/avatar/avatar-root.d.ts +1 -0
  10. package/dist/components/avatar/avatar-root.js +1 -0
  11. package/dist/components/badge/badge-root.d.ts +1 -0
  12. package/dist/components/badge/badge-root.js +1 -0
  13. package/dist/components/calendar/calendar-root.js +1 -0
  14. package/dist/components/carousel/carousel-root.d.ts +1 -0
  15. package/dist/components/carousel/carousel-root.js +1 -0
  16. package/dist/components/chart/chart-root.d.ts +1 -0
  17. package/dist/components/chart/chart-root.js +1 -0
  18. package/dist/components/collapsible/collapsible-root.d.ts +1 -0
  19. package/dist/components/collapsible/collapsible-root.js +1 -0
  20. package/dist/components/combobox/combobox-root.d.ts +1 -0
  21. package/dist/components/combobox/combobox-root.js +1 -0
  22. package/dist/components/command/command-dialog.d.ts +5 -1
  23. package/dist/components/command/command-dialog.js +1 -1
  24. package/dist/components/command/command-input.js +5 -2
  25. package/dist/components/command/command-root.d.ts +1 -0
  26. package/dist/components/command/command-root.js +1 -0
  27. package/dist/components/dialog/dialog-content.d.ts +2 -0
  28. package/dist/components/dialog/dialog-content.js +3 -2
  29. package/dist/components/dialog/dialog-footer.d.ts +2 -0
  30. package/dist/components/dialog/dialog-footer.js +8 -8
  31. package/dist/components/dialog/dialog-root.d.ts +1 -0
  32. package/dist/components/dialog/dialog-root.js +1 -0
  33. package/dist/components/dialog/dialog-title.d.ts +2 -0
  34. package/dist/components/dialog/dialog-title.js +3 -1
  35. package/dist/components/drawer/drawer-root.d.ts +1 -0
  36. package/dist/components/drawer/drawer-root.js +1 -0
  37. package/dist/components/heading/heading-root.d.ts +1 -0
  38. package/dist/components/heading/heading-root.js +1 -0
  39. package/dist/components/input/input-root.d.ts +1 -0
  40. package/dist/components/input/input-root.js +1 -0
  41. package/dist/components/kbd/kbd-root.d.ts +1 -0
  42. package/dist/components/kbd/kbd-root.js +1 -0
  43. package/dist/components/logo/logo-mark.d.ts +1 -1
  44. package/dist/components/logo/logo-mark.js +1 -1
  45. package/dist/components/logotype/logotype-paths.d.ts +1 -1
  46. package/dist/components/logotype/logotype-paths.js +1 -1
  47. package/dist/components/month-year-picker/month-year-picker-root.d.ts +1 -0
  48. package/dist/components/month-year-picker/month-year-picker-root.js +1 -0
  49. package/dist/components/native-select/native-select-root.d.ts +1 -0
  50. package/dist/components/native-select/native-select-root.js +1 -0
  51. package/dist/components/pill/pill-root.d.ts +1 -0
  52. package/dist/components/pill/pill-root.js +1 -0
  53. package/dist/components/popover/popover-root.d.ts +1 -0
  54. package/dist/components/popover/popover-root.js +1 -0
  55. package/dist/components/quantity-control/quantity-control-root.d.ts +1 -0
  56. package/dist/components/quantity-control/quantity-control-root.js +1 -0
  57. package/dist/components/segmented-control/segmented-control-root.js +6 -3
  58. package/dist/components/segmented-control/segmented-control-variants.js +4 -1
  59. package/dist/components/select/select-root.d.ts +1 -0
  60. package/dist/components/select/select-root.js +1 -0
  61. package/dist/components/separator/separator-root.d.ts +1 -0
  62. package/dist/components/separator/separator-root.js +1 -0
  63. package/dist/components/sheet/sheet-root.d.ts +1 -0
  64. package/dist/components/sheet/sheet-root.js +1 -0
  65. package/dist/components/spinner/spinner-root.d.ts +1 -0
  66. package/dist/components/spinner/spinner-root.js +1 -0
  67. package/dist/components/stepper/stepper-root.d.ts +1 -0
  68. package/dist/components/stepper/stepper-root.js +1 -0
  69. package/dist/components/success-tick-ripple/success-tick-ripple-root.d.ts +1 -0
  70. package/dist/components/success-tick-ripple/success-tick-ripple-root.js +1 -0
  71. package/dist/components/swatch-selector/swatch-selector-item.js +4 -1
  72. package/dist/components/switch/switch-root.d.ts +1 -0
  73. package/dist/components/switch/switch-root.js +1 -0
  74. package/dist/components/tag/tag-root.d.ts +1 -0
  75. package/dist/components/tag/tag-root.js +1 -0
  76. package/dist/components/textarea/textarea-root.d.ts +1 -0
  77. package/dist/components/textarea/textarea-root.js +1 -0
  78. package/dist/components/time-slot-picker/time-slot-picker-root.d.ts +1 -0
  79. package/dist/components/time-slot-picker/time-slot-picker-root.js +1 -0
  80. package/dist/components/toast/toast-root.d.ts +1 -0
  81. package/dist/components/toast/toast-root.js +1 -0
  82. package/dist/components/tooltip/tooltip-root.d.ts +1 -0
  83. package/dist/components/tooltip/tooltip-root.js +1 -0
  84. package/dist/compositions/empty-state/empty-state-root.d.ts +1 -0
  85. package/dist/compositions/empty-state/empty-state-root.js +1 -0
  86. package/dist/compositions/file-upload/file-upload-dropzone.d.ts +1 -0
  87. package/dist/compositions/file-upload/file-upload-dropzone.js +1 -0
  88. package/dist/data/decision-trees.d.ts +1 -1
  89. package/dist/data/decision-trees.js +1 -1
  90. package/dist/data/docs-manifest.d.ts +2 -0
  91. package/dist/data/docs-manifest.js +235 -68
  92. package/dist/styles/index.css +0 -1
  93. package/dist/tokens/segmented.d.ts +8 -0
  94. package/dist/tokens/segmented.js +26 -0
  95. package/dist/utils/cn-theme.js +1 -1
  96. package/lint/README.md +6 -2
  97. package/lint/doctrine-rules.mjs +4 -4
  98. package/lint/oxlint/index.d.mts +13 -5
  99. package/lint/oxlint/index.mjs +26 -0
  100. package/lint/oxlint/no-naked-element.mjs +123 -65
  101. package/lint/oxlint/no-render-layout-as.mjs +61 -56
  102. package/lint/scan-classes.d.mts +58 -5
  103. package/lint/scan-classes.mjs +274 -38
  104. package/lint/tokens.d.mts +16 -8
  105. package/lint/tokens.mjs +13 -3
  106. package/lint/type-case.d.mts +16 -8
  107. package/lint/type-case.mjs +17 -0
  108. package/package.json +27 -14
  109. package/lint/oxlint/fixtures/allowed.tsx +0 -12
  110. package/lint/oxlint/fixtures/fixture.config.mjs +0 -29
  111. package/lint/oxlint/fixtures/invalid.tsx +0 -24
  112. package/lint/oxlint/fixtures/layout-only.tsx +0 -26
  113. package/lint/oxlint/fixtures/render-as-invalid.tsx +0 -11
  114. package/lint/oxlint/fixtures/render-as-valid.tsx +0 -14
  115. package/lint/oxlint/fixtures/valid.tsx +0 -18
  116. package/lint/oxlint/no-naked-element.test.ts +0 -155
  117. 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, SEGMENTED_SHELL_PADDING, } from "../../tokens/segmented.js";
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", SEGMENTED_SHELL_PADDING[resolvedSize], getResponsiveSizeOverrides(size, SEGMENTED_SHELL_PADDING), fullWidth && "flex w-full", className), "data-component": "segmented-control", "data-size": resolvedSize, "data-slot": "segmented-control", onValueChange: (groupValue) => {
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(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" }))] }) }) }));
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", `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"), {
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", focusRing(), "disabled:cursor-not-allowed disabled:opacity-50", className), "data-component": "swatch-selector-item", "data-slot": "swatch-selector-item", onClick: (event) => {
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.mjs`), so a component rename
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.mjs`), so a component rename
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. */