@matthiaskrijgsman/mat-ui 0.0.51 → 0.0.52
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
|
@@ -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__ */
|
|
2413
|
-
|
|
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
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
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__ */
|
|
2590
|
-
|
|
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
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
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,
|