@instruments/materia-ui 0.4.0 → 0.4.1

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.
@@ -10,7 +10,11 @@ export type CommandDialogProps<Item> = Omit<React.ComponentProps<typeof Dialog>,
10
10
  description?: string;
11
11
  /** Extra class names applied to the `DialogContent` wrapper. */
12
12
  className?: string;
13
- /** Whether to show the dialog close button. */
13
+ /**
14
+ * Whether to show the dialog close button. Off by default: the button sits
15
+ * over the search field, and Escape or a click outside closes the palette.
16
+ * @default false
17
+ */
14
18
  showCloseButton?: boolean;
15
19
  /** Extra props applied to the rendered dialog content element. */
16
20
  contentProps?: Omit<React.ComponentProps<typeof DialogContent>, "children"> & React.HTMLAttributes<HTMLDivElement> & {
@@ -14,7 +14,7 @@ import { Command } from "../command/command-root.js";
14
14
  * @bestPractice Pass `filter={null}` to list the entries unfiltered, for example when the application filters them itself.
15
15
  */
16
16
  export const CommandDialog = (props) => {
17
- const { title = "Command Palette", description = "Search for a command to run…", children, className, showCloseButton = true, contentProps, filter, groups: _groups, items: _items, limit, mode, ...dialogProps } = props;
17
+ const { title = "Command Palette", description = "Search for a command to run…", children, className, showCloseButton = false, contentProps, filter, groups: _groups, items: _items, limit, mode, ...dialogProps } = props;
18
18
  // Narrow on `props` so the palette receives exactly one of items or groups.
19
19
  const source = props.groups === undefined
20
20
  ? { items: props.items }
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Autocomplete as AutocompletePrimitive } from "@base-ui/react/autocomplete";
4
- import { focusRing } from "@instruments/materia-ui/tokens/focus-ring";
5
4
  import { cn } from "@instruments/materia-ui/utils/cn";
6
5
  import { Search } from "lucide-react";
7
6
  import { Icon } from "../icon.js";
@@ -9,4 +8,8 @@ import { Icon } from "../icon.js";
9
8
  * CommandInput renders the search field for a Command; typing filters the list.
10
9
  * Import from "@instruments/materia-ui/components/command".
11
10
  */
12
- export const CommandInput = ({ className, ...props }) => (_jsxs(AutocompletePrimitive.InputGroup, { className: "flex h-9 items-center gap-2 border-b px-3", "data-component": "command-input-wrapper", "data-slot": "command-input-wrapper", children: [_jsx(Icon, { className: "shrink-0 opacity-50", icon: Search, size: "xs" }), _jsx(AutocompletePrimitive.Input, { className: cn(focusRing(), "placeholder:text-muted-foreground text-paragraph-sm flex h-10 w-full rounded-lg bg-transparent py-3 disabled:cursor-not-allowed disabled:opacity-50", "focus-visible:ring-inset", className), "data-component": "command-input", "data-slot": "command-input", ...props })] }));
11
+ export const CommandInput = ({ className, ...props }) => (_jsxs(AutocompletePrimitive.InputGroup, { className: "flex h-9 items-center gap-2 border-b px-3", "data-component": "command-input-wrapper", "data-slot": "command-input-wrapper", children: [_jsx(Icon, { className: "shrink-0 opacity-50", icon: Search, size: "xs" }), _jsx(AutocompletePrimitive.Input, { className: cn(
12
+ // No ring: the field is the palette's only input and always holds
13
+ // focus, so the caret marks it; a ring would collide with the
14
+ // dialog's rounded top edge.
15
+ "placeholder:text-muted-foreground text-paragraph-sm flex h-full w-full bg-transparent outline-none disabled:cursor-not-allowed disabled:opacity-50", className), "data-component": "command-input", "data-slot": "command-input", ...props })] }));
@@ -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",
@@ -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) {
@@ -2638,7 +2638,8 @@ export const docsManifest = {
2638
2638
  name: "showCloseButton",
2639
2639
  type: "boolean",
2640
2640
  required: false,
2641
- description: "Whether to show the dialog close button.",
2641
+ defaultValue: "false",
2642
+ description: "Whether to show the dialog close button. Off by default: the button sits\nover the search field, and Escape or a click outside closes the palette.",
2642
2643
  },
2643
2644
  {
2644
2645
  name: "contentProps",
@@ -20,6 +20,14 @@ import type { ComponentSize } from "./size.js";
20
20
  export declare const SEGMENTED_SHELL_BASE = "rounded-full bg-surface-inset text-muted-foreground";
21
21
  /** Shared shell padding scale by size. */
22
22
  export declare const SEGMENTED_SHELL_PADDING: Record<ComponentSize, string>;
23
+ /**
24
+ * The horizontal half of `SEGMENTED_SHELL_PADDING`. SegmentedControl puts this
25
+ * on the shell and the vertical half inside its scroll area, so each item's
26
+ * hit area can fill the shell's full height.
27
+ */
28
+ export declare const SEGMENTED_SHELL_PADDING_X: Record<ComponentSize, string>;
29
+ /** The vertical half of `SEGMENTED_SHELL_PADDING`. */
30
+ export declare const SEGMENTED_SHELL_PADDING_Y: Record<ComponentSize, string>;
23
31
  /**
24
32
  * Shared trigger/item text-size scale by size. `SegmentedControl` items,
25
33
  * `TabsTrigger` and `TabNavigationLink` all read it, so they render identical
@@ -28,6 +28,32 @@ export const SEGMENTED_SHELL_PADDING = {
28
28
  xl: "p-1",
29
29
  xs: "p-0.5",
30
30
  };
31
+ /**
32
+ * The horizontal half of `SEGMENTED_SHELL_PADDING`. SegmentedControl puts this
33
+ * on the shell and the vertical half inside its scroll area, so each item's
34
+ * hit area can fill the shell's full height.
35
+ */
36
+ export const SEGMENTED_SHELL_PADDING_X = {
37
+ "2xl": "px-1.5",
38
+ "2xs": "px-0.5",
39
+ "3xl": "px-1.5",
40
+ base: "px-0.5",
41
+ lg: "px-1",
42
+ sm: "px-0.5",
43
+ xl: "px-1",
44
+ xs: "px-0.5",
45
+ };
46
+ /** The vertical half of `SEGMENTED_SHELL_PADDING`. */
47
+ export const SEGMENTED_SHELL_PADDING_Y = {
48
+ "2xl": "py-1.5",
49
+ "2xs": "py-0.5",
50
+ "3xl": "py-1.5",
51
+ base: "py-0.5",
52
+ lg: "py-1",
53
+ sm: "py-0.5",
54
+ xl: "py-1",
55
+ xs: "py-0.5",
56
+ };
31
57
  /**
32
58
  * Shared trigger/item text-size scale by size. `SegmentedControl` items,
33
59
  * `TabsTrigger` and `TabNavigationLink` all read it, so they render identical
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materia-ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Materia design system: React components, tokens and Tailwind v4 styles.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {