@getgreenline/blaze-ui 1.0.36 → 1.0.37
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.
|
@@ -4,10 +4,8 @@ interface HierarchicalSelectItem {
|
|
|
4
4
|
label: string;
|
|
5
5
|
parentId?: string | null;
|
|
6
6
|
}
|
|
7
|
-
interface
|
|
7
|
+
interface HierarchicalSelectBaseProps {
|
|
8
8
|
items: HierarchicalSelectItem[];
|
|
9
|
-
value?: string;
|
|
10
|
-
onValueChange?: (value: string) => void;
|
|
11
9
|
placeholder?: string;
|
|
12
10
|
searchPlaceholder?: string;
|
|
13
11
|
emptyMessage?: string;
|
|
@@ -16,7 +14,18 @@ interface HierarchicalSelectProps {
|
|
|
16
14
|
disabled?: boolean;
|
|
17
15
|
label?: string;
|
|
18
16
|
}
|
|
19
|
-
|
|
17
|
+
interface HierarchicalSelectSingleProps extends HierarchicalSelectBaseProps {
|
|
18
|
+
multi?: false;
|
|
19
|
+
value?: string;
|
|
20
|
+
onValueChange?: (value: string) => void;
|
|
21
|
+
}
|
|
22
|
+
interface HierarchicalSelectMultiProps extends HierarchicalSelectBaseProps {
|
|
23
|
+
multi: true;
|
|
24
|
+
value?: string[];
|
|
25
|
+
onValueChange?: (values: string[]) => void;
|
|
26
|
+
}
|
|
27
|
+
type HierarchicalSelectProps = HierarchicalSelectSingleProps | HierarchicalSelectMultiProps;
|
|
28
|
+
declare function HierarchicalSelect(props: HierarchicalSelectProps): import("react/jsx-runtime").JSX.Element;
|
|
20
29
|
export { HierarchicalSelect };
|
|
21
30
|
export type { HierarchicalSelectItem, HierarchicalSelectProps };
|
|
22
31
|
//# sourceMappingURL=hierarchical-select.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hierarchical-select.d.ts","sourceRoot":"","sources":["../../src/components/hierarchical-select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"hierarchical-select.d.ts","sourceRoot":"","sources":["../../src/components/hierarchical-select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAY9B,UAAU,sBAAsB;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,UAAU,2BAA2B;IACnC,KAAK,EAAE,sBAAsB,EAAE,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,UAAU,6BAA8B,SAAQ,2BAA2B;IACzE,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED,UAAU,4BAA6B,SAAQ,2BAA2B;IACxE,KAAK,EAAE,IAAI,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CAC3C;AAED,KAAK,uBAAuB,GACxB,6BAA6B,GAC7B,4BAA4B,CAAA;AAgFhC,iBAAS,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,2CA+UzD;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAC7B,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
4
|
-
import { ChevronDownIcon, SearchIcon, FolderTreeIcon, CheckIcon } from 'lucide-react';
|
|
4
|
+
import { XIcon, ChevronDownIcon, SearchIcon, FolderTreeIcon, CheckIcon } from 'lucide-react';
|
|
5
5
|
import { cn } from '../lib/utils.js';
|
|
6
6
|
|
|
7
7
|
function buildHierarchy(items) {
|
|
@@ -48,10 +48,21 @@ function getDisplayValue(items, value) {
|
|
|
48
48
|
}
|
|
49
49
|
return selected.label;
|
|
50
50
|
}
|
|
51
|
+
function getDisplayValueMulti(items, values) {
|
|
52
|
+
if (values.length === 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
if (values.length === 1)
|
|
55
|
+
return getDisplayValue(items, values[0]);
|
|
56
|
+
return `${values.length} categories selected`;
|
|
57
|
+
}
|
|
51
58
|
function countVisibleCategories(groups) {
|
|
52
59
|
return groups.reduce((count, group) => count + 1 + group.children.length, 0);
|
|
53
60
|
}
|
|
54
|
-
function HierarchicalSelect(
|
|
61
|
+
function HierarchicalSelect(props) {
|
|
62
|
+
const { items, placeholder = "Select an option...", searchPlaceholder = "Search...", emptyMessage = "No results found", className, triggerStyle, disabled = false, label, } = props;
|
|
63
|
+
const isMulti = props.multi === true;
|
|
64
|
+
const multiValues = isMulti ? (props.value ?? []) : [];
|
|
65
|
+
const singleValue = !isMulti ? props.value : undefined;
|
|
55
66
|
const [open, setOpen] = React.useState(false);
|
|
56
67
|
const [search, setSearch] = React.useState("");
|
|
57
68
|
const searchInputRef = React.useRef(null);
|
|
@@ -69,7 +80,12 @@ function HierarchicalSelect({ items, value, onValueChange, placeholder = "Select
|
|
|
69
80
|
}, []);
|
|
70
81
|
const hierarchy = React.useMemo(() => buildHierarchy(items), [items]);
|
|
71
82
|
const filtered = React.useMemo(() => filterHierarchy(hierarchy, search), [hierarchy, search]);
|
|
72
|
-
const displayValue = React.useMemo(() =>
|
|
83
|
+
const displayValue = React.useMemo(() => {
|
|
84
|
+
if (isMulti)
|
|
85
|
+
return getDisplayValueMulti(items, multiValues);
|
|
86
|
+
return getDisplayValue(items, singleValue);
|
|
87
|
+
}, [isMulti, items, multiValues, singleValue]);
|
|
88
|
+
const hasSelection = isMulti ? multiValues.length > 0 : !!singleValue;
|
|
73
89
|
const visibleCount = React.useMemo(() => countVisibleCategories(filtered), [filtered]);
|
|
74
90
|
const handleOpenChange = React.useCallback((nextOpen) => {
|
|
75
91
|
setOpen(nextOpen);
|
|
@@ -77,11 +93,34 @@ function HierarchicalSelect({ items, value, onValueChange, placeholder = "Select
|
|
|
77
93
|
setSearch("");
|
|
78
94
|
}
|
|
79
95
|
}, []);
|
|
96
|
+
const selectedIds = React.useMemo(() => {
|
|
97
|
+
if (isMulti)
|
|
98
|
+
return new Set(multiValues);
|
|
99
|
+
return new Set(singleValue ? [singleValue] : []);
|
|
100
|
+
}, [isMulti, multiValues, singleValue]);
|
|
101
|
+
const isSelected = React.useCallback((id) => selectedIds.has(id), [selectedIds]);
|
|
80
102
|
const handleSelect = React.useCallback((id) => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
if (isMulti) {
|
|
104
|
+
const next = selectedIds.has(id)
|
|
105
|
+
? multiValues.filter((v) => v !== id)
|
|
106
|
+
: [...multiValues, id];
|
|
107
|
+
props.onValueChange?.(next);
|
|
108
|
+
// keep popover open in multi mode
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
props.onValueChange?.(id);
|
|
112
|
+
setOpen(false);
|
|
113
|
+
setSearch("");
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
117
|
+
[isMulti, selectedIds, multiValues, props.onValueChange]);
|
|
118
|
+
const handleClear = React.useCallback((e) => {
|
|
119
|
+
e.stopPropagation();
|
|
120
|
+
props.onValueChange?.([]);
|
|
121
|
+
},
|
|
122
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
123
|
+
[props.onValueChange]);
|
|
85
124
|
React.useEffect(() => {
|
|
86
125
|
if (open) {
|
|
87
126
|
const frame = requestAnimationFrame(() => {
|
|
@@ -90,9 +129,15 @@ function HierarchicalSelect({ items, value, onValueChange, placeholder = "Select
|
|
|
90
129
|
return () => cancelAnimationFrame(frame);
|
|
91
130
|
}
|
|
92
131
|
}, [open]);
|
|
93
|
-
return (jsxs(PopoverPrimitive.Root, { open: open, onOpenChange: handleOpenChange, children: [
|
|
94
|
-
|
|
95
|
-
|
|
132
|
+
return (jsxs(PopoverPrimitive.Root, { open: open, onOpenChange: handleOpenChange, children: [jsx(PopoverPrimitive.Trigger, { asChild: true, children: jsxs("div", { "data-slot": "hierarchical-select-trigger", role: "combobox", tabIndex: disabled ? -1 : 0, "aria-label": label ?? placeholder, "aria-disabled": disabled || undefined, onKeyDown: (e) => {
|
|
133
|
+
if (!disabled && (e.key === "Enter" || e.key === " ")) {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
e.currentTarget.click();
|
|
136
|
+
}
|
|
137
|
+
}, className: cn("tw:!flex tw:!w-full tw:!items-center tw:!justify-between tw:!gap-2 tw:!rounded-md tw:!border tw:!border-input tw:!bg-transparent tw:px-3 tw:py-2 tw:!text-[length:var(--hs-font-size,14px)] tw:!whitespace-nowrap tw:!shadow-xs tw:!transition-[color,box-shadow] tw:!outline-none tw:h-9 tw:!cursor-default", "focus-visible:tw:!border-ring focus-visible:tw:!ring-ring/50 focus-visible:tw:!ring-[3px]", disabled &&
|
|
138
|
+
"tw:!cursor-not-allowed tw:!opacity-50 tw:!pointer-events-none", "dark:tw:!bg-input/30 dark:hover:tw:!bg-input/50", !displayValue && "tw:!text-muted-foreground", className), style: triggerStyle, children: [jsx("span", { "data-slot": "hierarchical-select-value", className: "tw:!truncate tw:!flex-1 tw:!text-left", children: displayValue ?? placeholder }), isMulti && hasSelection && (jsx("button", { type: "button", "data-slot": "hierarchical-select-clear", "aria-label": "Clear selection", onClick: handleClear, className: "tw:!shrink-0 tw:!flex tw:!items-center tw:!justify-center tw:!size-4 tw:!rounded-sm tw:!text-muted-foreground hover:tw:!text-foreground tw:!transition-colors tw:!pointer-events-auto", children: jsx(XIcon, { className: "tw:!size-3" }) })), jsx(ChevronDownIcon, { "data-slot": "hierarchical-select-chevron", "aria-hidden": true, className: cn("tw:!size-4 tw:!shrink-0 tw:!opacity-50 tw:!transition-transform tw:!duration-200", open && "tw:!rotate-180") })] }) }), jsx(PopoverPrimitive.Portal, { container: portalContainer, children: jsxs(PopoverPrimitive.Content, { "data-slot": "hierarchical-select-content", align: "start", sideOffset: 4, className: cn("tw:!bg-popover tw:!text-popover-foreground tw:!z-[9999] tw:!w-[var(--radix-popover-trigger-width)] tw:!origin-(--radix-popover-content-transform-origin) tw:!rounded-md tw:!border tw:!shadow-md tw:!outline-hidden", "data-[state=open]:tw:!animate-in data-[state=closed]:tw:!animate-out data-[state=closed]:tw:!fade-out-0 data-[state=open]:tw:!fade-in-0 data-[state=closed]:tw:!zoom-out-95 data-[state=open]:tw:!zoom-in-95", "data-[side=bottom]:tw:!slide-in-from-top-2 data-[side=left]:tw:!slide-in-from-right-2 data-[side=right]:tw:!slide-in-from-left-2 data-[side=top]:tw:!slide-in-from-bottom-2"), children: [jsxs("div", { "data-slot": "hierarchical-select-search", className: "tw:!flex tw:!items-center tw:!gap-2 tw:!border-b tw:!px-3 tw:!sticky tw:!top-0 tw:!bg-popover tw:!z-10", children: [jsx(SearchIcon, { className: "tw:!size-4 tw:!shrink-0 tw:!text-muted-foreground" }), jsx("input", { ref: searchInputRef, id: searchId, "data-slot": "hierarchical-select-search-input", type: "text", role: "searchbox", "aria-label": searchPlaceholder, "aria-autocomplete": "list", value: search, onChange: (e) => setSearch(e.target.value), placeholder: searchPlaceholder, className: "tw:!flex tw:!h-9 tw:!w-full tw:!bg-transparent tw:!py-2 tw:!text-[14px] tw:!outline-none placeholder:tw:!text-muted-foreground" })] }), jsx("div", { id: listboxId, "data-slot": "hierarchical-select-list", role: "listbox", "aria-multiselectable": isMulti, "aria-label": label ?? placeholder, className: "tw:!max-h-[320px] tw:!overflow-y-auto tw:!overflow-x-hidden tw:!p-1", children: filtered.length === 0 ? (jsxs("div", { "data-slot": "hierarchical-select-empty", role: "status", "aria-live": "polite", className: "tw:!flex tw:!flex-col tw:!items-center tw:!justify-center tw:!gap-2 tw:!py-8 tw:!text-center", children: [jsx("div", { className: "tw:!flex tw:!size-10 tw:!items-center tw:!justify-center tw:!rounded-full tw:!bg-muted", children: jsx(SearchIcon, { "aria-hidden": true, className: "tw:!size-4 tw:!text-muted-foreground" }) }), jsx("div", { className: "tw:!text-[14px] tw:!font-medium", children: emptyMessage }), jsx("p", { className: "tw:!text-[12px] tw:!text-muted-foreground", children: "Try adjusting your search query" })] })) : (filtered.map((group) => (jsxs("div", { "data-slot": "hierarchical-select-group", role: "group", "aria-label": group.parent.label, className: "tw:!mb-1", children: [jsxs("button", { type: "button", "data-slot": "hierarchical-select-parent", role: "option", "aria-selected": isSelected(group.parent.id), onClick: () => handleSelect(group.parent.id), className: cn("tw:!relative tw:!flex tw:!w-full tw:!cursor-default tw:!items-center tw:!gap-2 tw:!rounded-sm tw:!px-2 tw:!py-1.5 tw:!text-[14px] tw:!font-semibold tw:!outline-hidden tw:!select-none tw:!transition-colors", "hover:tw:!bg-accent hover:tw:!text-accent-foreground", isSelected(group.parent.id) &&
|
|
139
|
+
"tw:!bg-accent tw:!text-accent-foreground"), children: [jsx(FolderTreeIcon, { "aria-hidden": true, className: "tw:!size-4 tw:!shrink-0 tw:!text-muted-foreground" }), jsx("span", { className: "tw:!flex-1 tw:!text-left tw:!truncate", children: group.parent.label }), group.children.length > 0 && (jsx("span", { "data-slot": "hierarchical-select-badge", className: "tw:!inline-flex tw:!items-center tw:!justify-center tw:!rounded-full tw:!bg-muted tw:!px-1.5 tw:!py-0.5 tw:!text-[10px] tw:!font-medium tw:!text-muted-foreground tw:!leading-none", children: group.children.length })), isSelected(group.parent.id) && (jsx(CheckIcon, { "aria-hidden": true, className: "tw:!size-4 tw:!shrink-0" }))] }), group.children.length > 0 && (jsx("div", { "data-slot": "hierarchical-select-children", role: "group", "aria-label": `${group.parent.label} subcategories`, className: "tw:!ml-4 tw:!border-l tw:!border-border tw:!pl-2", children: group.children.map((child) => (jsxs("button", { type: "button", "data-slot": "hierarchical-select-child", role: "option", "aria-selected": isSelected(child.id), onClick: () => handleSelect(child.id), className: cn("tw:!relative tw:!flex tw:!w-full tw:!cursor-default tw:!items-center tw:!gap-2 tw:!rounded-sm tw:!px-2 tw:!py-1.5 tw:!text-[14px] tw:!outline-hidden tw:!select-none tw:!transition-colors", "hover:tw:!bg-accent hover:tw:!text-accent-foreground", "tw:!text-muted-foreground", isSelected(child.id) &&
|
|
140
|
+
"tw:!bg-accent tw:!text-accent-foreground"), children: [jsx("span", { "aria-hidden": true, className: "tw:!size-1.5 tw:!rounded-full tw:!bg-muted-foreground/50 tw:!shrink-0" }), jsx("span", { className: "tw:!flex-1 tw:!text-left tw:!truncate", children: child.label }), isSelected(child.id) && (jsx(CheckIcon, { "aria-hidden": true, className: "tw:!size-4 tw:!shrink-0" }))] }, child.id))) }))] }, group.parent.id)))) }), jsx("div", { "data-slot": "hierarchical-select-footer", "aria-live": "polite", "aria-atomic": "true", className: "tw:!border-t tw:!px-3 tw:!py-2 tw:!text-[12px] tw:!text-muted-foreground", children: search ? (jsxs("span", { children: [visibleCount, " result", visibleCount !== 1 ? "s" : "", " for \u201C", search, "\u201D"] })) : (jsxs("span", { children: [visibleCount, " categor", visibleCount !== 1 ? "ies" : "y"] })) })] }) })] }));
|
|
96
141
|
}
|
|
97
142
|
|
|
98
143
|
export { HierarchicalSelect };
|