@matthiaskrijgsman/mat-ui 0.0.51 → 0.0.53

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.
@@ -0,0 +1,40 @@
1
+ import * as React from "react";
2
+ import type { ControlSize } from "@/control-size/control-size.util.ts";
3
+ export type SelectDrilldownApi = {
4
+ /** Dismisses the popover — call after committing a leaf selection. */
5
+ close: () => void;
6
+ };
7
+ export type InputSelectDrilldownProps = {
8
+ id?: string;
9
+ className?: string;
10
+ label?: string | React.ReactNode;
11
+ description?: string | React.ReactNode;
12
+ error?: string | React.ReactNode;
13
+ size?: ControlSize;
14
+ disabled?: boolean;
15
+ clearable?: boolean;
16
+ placeholder?: string | React.ReactNode;
17
+ /** Whether a value is selected — drives the clear button + trigger padding. */
18
+ hasValue?: boolean;
19
+ /** Rendered inside the trigger for the current selection. */
20
+ valueLabel?: React.ReactNode;
21
+ onClear?: () => void;
22
+ minWidth?: number;
23
+ maxWidth?: number;
24
+ maxHeight?: number;
25
+ /**
26
+ * Root-level menu content. Rendered inside the drilldown navigator, so it (and
27
+ * anything it drills into via {@link DropdownDrilldown}) slides between levels.
28
+ * Pass a function to receive `{ close }` for dismissing after a leaf pick.
29
+ */
30
+ children: React.ReactNode | ((api: SelectDrilldownApi) => React.ReactNode);
31
+ };
32
+ /**
33
+ * A single-select control whose menu is a sliding drilldown rather than a flat
34
+ * option list — for hierarchies too large or too lazy to flatten up front. It
35
+ * owns the (shared) {@link SelectTrigger}, popover, and {@link DropdownNavigator};
36
+ * the consumer supplies the level content and uses {@link DropdownDrilldown} for
37
+ * rows that descend a level. The current value is opaque to this component — pass
38
+ * `valueLabel` for the trigger and `hasValue`/`onClear` for clearing.
39
+ */
40
+ export declare const InputSelectDrilldown: (props: InputSelectDrilldownProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,43 @@
1
+ import * as React from "react";
2
+ import type { ControlSize } from "@/control-size/control-size.util.ts";
3
+ export type SelectTriggerProps = {
4
+ /** Overrides the size from `ControlSizeContext` when set. */
5
+ size?: ControlSize;
6
+ open?: boolean;
7
+ disabled?: boolean;
8
+ error?: boolean;
9
+ clearable?: boolean;
10
+ /** Whether a value is selected — drives the clear button and right padding. */
11
+ hasValue?: boolean;
12
+ /** Rendered when a value is selected. */
13
+ selectedLabel?: React.ReactNode;
14
+ placeholder?: React.ReactNode;
15
+ onClear?: () => void;
16
+ showChevron?: boolean;
17
+ className?: string;
18
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, 'className'>;
19
+ /**
20
+ * The shared trigger surface for the select family (`InputSelect`,
21
+ * `InputSelectSearchable`, `InputSelectDrilldown`): the bordered control box that
22
+ * shows the current selection (or placeholder) plus the icon-button tray (error
23
+ * icon, optional clear, chevron). Extracted so every select-like control shares
24
+ * exactly the same sizing and styling. Interaction props (role, tabIndex,
25
+ * onClick, onKeyDown, floating-ui reference props) are spread onto the box, and
26
+ * the forwarded ref lands on it.
27
+ */
28
+ export declare const SelectTrigger: React.ForwardRefExoticComponent<{
29
+ /** Overrides the size from `ControlSizeContext` when set. */
30
+ size?: ControlSize;
31
+ open?: boolean;
32
+ disabled?: boolean;
33
+ error?: boolean;
34
+ clearable?: boolean;
35
+ /** Whether a value is selected — drives the clear button and right padding. */
36
+ hasValue?: boolean;
37
+ /** Rendered when a value is selected. */
38
+ selectedLabel?: React.ReactNode;
39
+ placeholder?: React.ReactNode;
40
+ onClear?: () => void;
41
+ showChevron?: boolean;
42
+ className?: string;
43
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, "className"> & React.RefAttributes<HTMLDivElement>>;
package/dist/index.d.ts CHANGED
@@ -34,6 +34,8 @@ export { lexicalTheme, LEXICAL_NODES } from "./components/inputs/input-lexical/l
34
34
  export { InputSelectNative } from "./components/inputs/InputSelectNative.tsx";
35
35
  export { InputSelect } from "./components/inputs/InputSelect.tsx";
36
36
  export { InputSelectSearchable } from "./components/inputs/InputSelectSearchable.tsx";
37
+ export { InputSelectDrilldown } from "./components/inputs/InputSelectDrilldown.tsx";
38
+ export { SelectTrigger } from "./components/inputs/SelectTrigger.tsx";
37
39
  export { InputSelectSearchableAsync } from "./components/inputs/InputSelectSearchableAsync.tsx";
38
40
  export { InputSelectMultiple } from "./components/inputs/InputSelectMultiple.tsx";
39
41
  export { InputSelectOption } from "./components/inputs/InputSelectOption.tsx";
package/dist/index.js CHANGED
@@ -1580,7 +1580,7 @@ const DropdownNavigator = (props) => {
1580
1580
  const current = stack[stack.length - 1];
1581
1581
  const levelKey = current ? current.id : "__root__";
1582
1582
  const content = current ? current.content : children;
1583
- return /* @__PURE__ */ jsx(DropdownDrilldownContext.Provider, { value: { push, pop, reset, depth: stack.length }, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col overflow-hidden", children: /* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", custom: direction, children: /* @__PURE__ */ jsxs(
1583
+ return /* @__PURE__ */ jsx(DropdownDrilldownContext.Provider, { value: { push, pop, reset, depth: stack.length }, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col overflow-x-hidden overflow-y-auto", children: /* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", custom: direction, children: /* @__PURE__ */ jsxs(
1584
1584
  motion.div,
1585
1585
  {
1586
1586
  custom: direction,
@@ -2342,6 +2342,53 @@ const useSelectPopover = (props) => {
2342
2342
  getItemProps
2343
2343
  };
2344
2344
  };
2345
+ const SelectTrigger = React.forwardRef((props, ref) => {
2346
+ const {
2347
+ size: sizeProp,
2348
+ open = false,
2349
+ disabled = false,
2350
+ error = false,
2351
+ clearable = false,
2352
+ hasValue = false,
2353
+ selectedLabel,
2354
+ placeholder,
2355
+ onClear,
2356
+ showChevron = true,
2357
+ className,
2358
+ ...rest
2359
+ } = props;
2360
+ const contextSize = useControlSize();
2361
+ const size2 = sizeProp ?? contextSize;
2362
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2363
+ /* @__PURE__ */ jsxs(
2364
+ "div",
2365
+ {
2366
+ ref,
2367
+ className: classNames(
2368
+ "flex flex-row items-center border-[length:var(--border-width-input)] select-trigger transition-all duration-[var(--control-transition-duration)] rounded-[var(--border-radius-input)] shadow-[var(--shadow-control)] ring-0 focus:ring-[length:var(--control-ring-width)] focus:outline-none select-none font-[number:var(--font-weight-input-text)] font-[family-name:var(--font-family-base)]",
2369
+ sizeHeightClasses[size2],
2370
+ sizeFontClasses[size2],
2371
+ sizePaddingLeftClasses[size2],
2372
+ clearable && hasValue ? sizePaddingRightWithTrayTwoClasses[size2] : sizePaddingRightWithTrayClasses[size2],
2373
+ disabled ? "select-trigger-disabled" : error && "select-trigger-error",
2374
+ !disabled && open && "ring-[length:var(--control-ring-width)]",
2375
+ className
2376
+ ),
2377
+ ...rest,
2378
+ children: [
2379
+ selectedLabel && /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 break-all line-clamp-1 text-left", children: selectedLabel }),
2380
+ !selectedLabel && placeholder && /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 break-all line-clamp-1 text-left select-placeholder", children: placeholder })
2381
+ ]
2382
+ }
2383
+ ),
2384
+ /* @__PURE__ */ jsxs(InputIconButtonTray, { children: [
2385
+ !disabled && error && /* @__PURE__ */ jsx(InputErrorIcon, {}),
2386
+ clearable && hasValue && !disabled && /* @__PURE__ */ jsx(InputIconButton, { Icon: IconX, onClick: onClear }),
2387
+ showChevron && /* @__PURE__ */ jsx(InputIconButton, { Icon: IconChevronDown })
2388
+ ] })
2389
+ ] });
2390
+ });
2391
+ SelectTrigger.displayName = "SelectTrigger";
2345
2392
  const InputSelect = (props) => {
2346
2393
  const {
2347
2394
  className,
@@ -2409,8 +2456,8 @@ const InputSelect = (props) => {
2409
2456
  children: [
2410
2457
  /* @__PURE__ */ jsx(InputLabel, { children: label }),
2411
2458
  /* @__PURE__ */ jsxs("div", { className: "relative flex w-full flex-col", ref: anchorRef, children: [
2412
- /* @__PURE__ */ jsxs(
2413
- "div",
2459
+ /* @__PURE__ */ jsx(
2460
+ SelectTrigger,
2414
2461
  {
2415
2462
  ...getReferenceProps({
2416
2463
  ref,
@@ -2435,26 +2482,17 @@ const InputSelect = (props) => {
2435
2482
  }
2436
2483
  }
2437
2484
  }),
2438
- className: classNames(
2439
- "flex flex-row items-center border-[length:var(--border-width-input)] select-trigger transition-all duration-[var(--control-transition-duration)] rounded-[var(--border-radius-input)] shadow-[var(--shadow-control)] ring-0 focus:ring-[length:var(--control-ring-width)] focus:outline-none select-none font-[number:var(--font-weight-input-text)] font-[family-name:var(--font-family-base)]",
2440
- sizeHeightClasses[size2],
2441
- sizeFontClasses[size2],
2442
- sizePaddingLeftClasses[size2],
2443
- clearable && value ? sizePaddingRightWithTrayTwoClasses[size2] : sizePaddingRightWithTrayClasses[size2],
2444
- disabled ? "select-trigger-disabled" : error && "select-trigger-error",
2445
- !disabled && open && "ring-[length:var(--control-ring-width)]"
2446
- ),
2447
- children: [
2448
- selectedOption && /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 break-all line-clamp-1 text-left", children: selectedOption.label }),
2449
- !selectedOption && placeholder && /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 break-all line-clamp-1 text-left select-placeholder", children: placeholder })
2450
- ]
2485
+ size: size2,
2486
+ open,
2487
+ disabled,
2488
+ error: !!error,
2489
+ clearable,
2490
+ hasValue: !!value,
2491
+ selectedLabel: selectedOption?.label,
2492
+ placeholder,
2493
+ onClear: () => onChange(null)
2451
2494
  }
2452
2495
  ),
2453
- /* @__PURE__ */ jsxs(InputIconButtonTray, { children: [
2454
- !disabled && error && /* @__PURE__ */ jsx(InputErrorIcon, {}),
2455
- clearable && !!value && !disabled && /* @__PURE__ */ jsx(InputIconButton, { Icon: IconX, onClick: () => onChange(null) }),
2456
- /* @__PURE__ */ jsx(InputIconButton, { Icon: IconChevronDown })
2457
- ] }),
2458
2496
  /* @__PURE__ */ jsx(Popover, { open, children: /* @__PURE__ */ jsx(DropdownPanel, { className: "!p-0", style: { maxHeight }, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col p-2 gap-1", children: options.map((item, i) => {
2459
2497
  if (!isSelectOption(item)) {
2460
2498
  if (item.kind === "header") {
@@ -2586,8 +2624,8 @@ const InputSelectSearchable = (props) => {
2586
2624
  children: [
2587
2625
  /* @__PURE__ */ jsx(InputLabel, { children: label }),
2588
2626
  /* @__PURE__ */ jsxs("div", { className: "relative flex w-full flex-col", ref: anchorRef, children: [
2589
- /* @__PURE__ */ jsxs(
2590
- "div",
2627
+ /* @__PURE__ */ jsx(
2628
+ SelectTrigger,
2591
2629
  {
2592
2630
  ...getReferenceProps({
2593
2631
  ref,
@@ -2608,26 +2646,17 @@ const InputSelectSearchable = (props) => {
2608
2646
  }
2609
2647
  }
2610
2648
  }),
2611
- className: classNames(
2612
- "flex flex-row items-center border-[length:var(--border-width-input)] select-trigger transition-all duration-[var(--control-transition-duration)] rounded-[var(--border-radius-input)] shadow-[var(--shadow-control)] ring-0 focus:ring-[length:var(--control-ring-width)] focus:outline-none select-none font-[number:var(--font-weight-input-text)] font-[family-name:var(--font-family-base)]",
2613
- sizeHeightClasses[size2],
2614
- sizeFontClasses[size2],
2615
- sizePaddingLeftClasses[size2],
2616
- clearable && value ? sizePaddingRightWithTrayTwoClasses[size2] : sizePaddingRightWithTrayClasses[size2],
2617
- disabled ? "select-trigger-disabled" : error && "select-trigger-error",
2618
- !disabled && open && "ring-[length:var(--control-ring-width)]"
2619
- ),
2620
- children: [
2621
- selectedOption && /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 break-all line-clamp-1 text-left", children: selectedOption.label }),
2622
- !selectedOption && placeholder && /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 break-all line-clamp-1 text-left select-placeholder", children: placeholder })
2623
- ]
2649
+ size: size2,
2650
+ open,
2651
+ disabled,
2652
+ error: !!error,
2653
+ clearable,
2654
+ hasValue: !!value,
2655
+ selectedLabel: selectedOption?.label,
2656
+ placeholder,
2657
+ onClear: () => onChange(null)
2624
2658
  }
2625
2659
  ),
2626
- /* @__PURE__ */ jsxs(InputIconButtonTray, { children: [
2627
- !disabled && error && /* @__PURE__ */ jsx(InputErrorIcon, {}),
2628
- clearable && !!value && !disabled && /* @__PURE__ */ jsx(InputIconButton, { Icon: IconX, onClick: () => onChange(null) }),
2629
- /* @__PURE__ */ jsx(InputIconButton, { Icon: IconChevronDown })
2630
- ] }),
2631
2660
  /* @__PURE__ */ jsx(Popover, { open, children: /* @__PURE__ */ jsxs(DropdownPanel, { className: "gap-0 !p-0", style: { maxHeight }, children: [
2632
2661
  /* @__PURE__ */ jsxs("div", { className: "sticky top-0 border-b select-search-bar py-1 backdrop-blur-sm", children: [
2633
2662
  /* @__PURE__ */ jsx(
@@ -2702,6 +2731,75 @@ const InputSelectSearchable = (props) => {
2702
2731
  }
2703
2732
  ) });
2704
2733
  };
2734
+ const InputSelectDrilldown = (props) => {
2735
+ const {
2736
+ id,
2737
+ className,
2738
+ label,
2739
+ description,
2740
+ error,
2741
+ size: size2 = "md",
2742
+ disabled = false,
2743
+ clearable = false,
2744
+ placeholder,
2745
+ hasValue = false,
2746
+ valueLabel,
2747
+ onClear,
2748
+ minWidth = 200,
2749
+ maxWidth,
2750
+ maxHeight = 300,
2751
+ children
2752
+ } = props;
2753
+ const [open, setOpen] = useState(false);
2754
+ const { anchorRef, Popover } = usePopover({
2755
+ placement: "bottom-start",
2756
+ fullWidth: true,
2757
+ minWidth,
2758
+ maxWidth,
2759
+ open,
2760
+ onOpenChange: setOpen
2761
+ });
2762
+ const content = typeof children === "function" ? children({ close: () => setOpen(false) }) : children;
2763
+ return /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: size2, children: /* @__PURE__ */ jsxs("div", { className: classNames("flex flex-col", className), children: [
2764
+ /* @__PURE__ */ jsx(InputLabel, { children: label }),
2765
+ /* @__PURE__ */ jsxs("div", { className: "relative flex w-full flex-col", ref: anchorRef, children: [
2766
+ /* @__PURE__ */ jsx(
2767
+ SelectTrigger,
2768
+ {
2769
+ id,
2770
+ role: "button",
2771
+ tabIndex: disabled ? -1 : 0,
2772
+ "aria-disabled": disabled,
2773
+ size: size2,
2774
+ open,
2775
+ disabled,
2776
+ error: !!error,
2777
+ clearable,
2778
+ hasValue,
2779
+ selectedLabel: valueLabel,
2780
+ placeholder,
2781
+ onClear,
2782
+ onClick: () => {
2783
+ if (!disabled) setOpen(!open);
2784
+ },
2785
+ onKeyDown: (e) => {
2786
+ if (disabled) return;
2787
+ if (e.key === " ") {
2788
+ e.preventDefault();
2789
+ setOpen((o) => !o);
2790
+ } else if (e.key === "Enter" && !open) {
2791
+ e.preventDefault();
2792
+ setOpen(true);
2793
+ }
2794
+ }
2795
+ }
2796
+ ),
2797
+ /* @__PURE__ */ jsx(Popover, { open, children: /* @__PURE__ */ jsx(DropdownPanel, { padding: "sm", style: { maxHeight }, children: /* @__PURE__ */ jsx(DropdownNavigator, { open, children: content }) }) })
2798
+ ] }),
2799
+ /* @__PURE__ */ jsx(InputDescription, { children: description }),
2800
+ /* @__PURE__ */ jsx(InputError, { children: error })
2801
+ ] }) });
2802
+ };
2705
2803
  const useDebounce = (value, delay) => {
2706
2804
  const [debouncedValue, setDebouncedValue] = useState(value);
2707
2805
  useEffect(() => {
@@ -4357,6 +4455,7 @@ export {
4357
4455
  InputRadio,
4358
4456
  InputSelect,
4359
4457
  InputSelectDivider,
4458
+ InputSelectDrilldown,
4360
4459
  InputSelectGroupHeader,
4361
4460
  InputSelectMultiple,
4362
4461
  InputSelectNative,
@@ -4383,6 +4482,7 @@ export {
4383
4482
  PanelLink,
4384
4483
  PanelStack,
4385
4484
  PopoverBase,
4485
+ SelectTrigger,
4386
4486
  SidebarModal,
4387
4487
  Spinner,
4388
4488
  TabButtons,