@wallarm-org/design-system 0.78.1 → 0.79.0
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.
- package/dist/components/FilterInput/FilterInputErrors/parseFilterInputErrors.js +2 -2
- package/dist/components/FilterInput/FilterInputField/ChipsWithGaps.js +1 -1
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.d.ts +21 -3
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.js +10 -75
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FlatValueMenu.d.ts +8 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FlatValueMenu.js +81 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/NestedValueMenu.d.ts +10 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/NestedValueMenu.js +145 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/NestedValueMenuItem.d.ts +26 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/NestedValueMenuItem.js +54 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuFooterHints.d.ts +12 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuFooterHints.js +69 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useNestedValueMenuState.d.ts +65 -0
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/useNestedValueMenuState.js +293 -0
- package/dist/components/FilterInput/FilterInputMenu/hooks/useKeyboardNav.d.ts +3 -1
- package/dist/components/FilterInput/FilterInputMenu/hooks/useKeyboardNav.js +20 -1
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/lib/deriveAutocompleteValues.js +3 -3
- package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/lib/valueResolution.js +5 -5
- package/dist/components/FilterInput/hooks/useFilterInputExpression/buildChips.js +2 -2
- package/dist/components/FilterInput/lib/buildValueMenuSections.d.ts +33 -0
- package/dist/components/FilterInput/lib/buildValueMenuSections.js +68 -0
- package/dist/components/FilterInput/lib/collapseValuesToLabels.d.ts +32 -0
- package/dist/components/FilterInput/lib/collapseValuesToLabels.js +55 -0
- package/dist/components/FilterInput/lib/fields.d.ts +19 -7
- package/dist/components/FilterInput/lib/fields.js +18 -4
- package/dist/components/FilterInput/lib/index.d.ts +3 -1
- package/dist/components/FilterInput/lib/index.js +3 -1
- package/dist/components/FilterInput/lib/validation.js +2 -2
- package/dist/components/FilterInput/types.d.ts +31 -2
- package/dist/metadata/components.json +2 -2
- package/package.json +1 -1
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuFooter.d.ts +0 -6
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuFooter.js +0 -72
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SEGMENT_VARIANT } from "../FilterInputField/FilterInputChip/index.js";
|
|
2
|
-
import { getFieldValues, hasStaticAllowlist, incompleteTripletError, isEmptyFilterValue, isNoValueOperator, isValidFieldValue } from "../lib/index.js";
|
|
2
|
+
import { collectLeaves, getFieldValues, hasStaticAllowlist, incompleteTripletError, isEmptyFilterValue, isNoValueOperator, isValidFieldValue } from "../lib/index.js";
|
|
3
3
|
const isPairValueMissing = (condition)=>{
|
|
4
4
|
const pair = condition.pair;
|
|
5
5
|
if (!pair) return true;
|
|
@@ -34,7 +34,7 @@ const parseFilterInputErrors = (conditions, fields)=>{
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
if (field && hasStaticAllowlist(field) && Array.isArray(condition.value)) {
|
|
37
|
-
const fv = getFieldValues(field);
|
|
37
|
+
const fv = collectLeaves(getFieldValues(field));
|
|
38
38
|
if (fv.length > 0) {
|
|
39
39
|
const invalidValues = condition.value.filter((v)=>!isValidFieldValue(fv, v));
|
|
40
40
|
if (invalidValues.length > 0) {
|
|
@@ -18,7 +18,7 @@ const ChipsWithGaps = ({ chips, hideLeadingGap, hideTrailingGap, onChipClick, on
|
|
|
18
18
|
const isConnector = 'and' === chip.variant || 'or' === chip.variant;
|
|
19
19
|
if (isCondition) elements.push(/*#__PURE__*/ jsx("div", {
|
|
20
20
|
ref: chipRef(chip.id),
|
|
21
|
-
className: "shrink-0 cursor-pointer hover:z-
|
|
21
|
+
className: "shrink-0 cursor-pointer hover:z-30",
|
|
22
22
|
children: /*#__PURE__*/ jsx(FilterInputChip, {
|
|
23
23
|
chipId: chip.id,
|
|
24
24
|
attribute: chip.attribute ?? '',
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { FC, RefObject } from 'react';
|
|
2
2
|
import type { BadgeColor } from '../../../Badge';
|
|
3
3
|
export interface ValueOption {
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Committable value — present on **leaf** options only. Group options (those
|
|
6
|
+
* with `children`) omit it; they are presentational and never committed.
|
|
7
|
+
*/
|
|
8
|
+
value?: string | number | boolean;
|
|
5
9
|
label: string;
|
|
6
10
|
badge?: {
|
|
7
11
|
color: BadgeColor;
|
|
@@ -9,12 +13,20 @@ export interface ValueOption {
|
|
|
9
13
|
};
|
|
10
14
|
/** Muted secondary line rendered beneath the bold `label`. Display-only. */
|
|
11
15
|
description?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Nested sub-values. When present this option is a group: a section header at
|
|
18
|
+
* the top level, or a submenu trigger when nested. Only leaves are committed.
|
|
19
|
+
*/
|
|
20
|
+
children?: ValueOption[];
|
|
21
|
+
/** Optional section-header label to bucket top-level options under a heading. */
|
|
22
|
+
section?: string;
|
|
23
|
+
/** @deprecated superseded by `children`; kept for back-compat. */
|
|
12
24
|
hasSubmenu?: boolean;
|
|
13
25
|
}
|
|
14
26
|
type ConditionValue = string | number | boolean;
|
|
15
27
|
export interface FilterInputValueMenuProps {
|
|
16
28
|
values: ValueOption[];
|
|
17
|
-
onSelect: (value:
|
|
29
|
+
onSelect: (value: ConditionValue) => void;
|
|
18
30
|
onCommit?: (values: ConditionValue[]) => void;
|
|
19
31
|
open?: boolean;
|
|
20
32
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -37,5 +49,11 @@ export interface FilterInputValueMenuProps {
|
|
|
37
49
|
blurCommitRef?: RefObject<(() => boolean) | null>;
|
|
38
50
|
className?: string;
|
|
39
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Value dropdown (third autocomplete step). Dispatches to the nested menu
|
|
54
|
+
* (sections + submenu) when the field's values contain groups or `section`
|
|
55
|
+
* tags, otherwise to the flat single-list menu. Owns no hooks itself so each
|
|
56
|
+
* variant's hooks mount/unmount cleanly.
|
|
57
|
+
*/
|
|
40
58
|
export declare const FilterInputValueMenu: FC<FilterInputValueMenuProps>;
|
|
41
59
|
export {};
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/FilterInputValueMenu.js
CHANGED
|
@@ -1,78 +1,13 @@
|
|
|
1
|
-
import { jsx
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { valueOptionSearchText } from "./valueOptionSearchText.js";
|
|
12
|
-
const FilterInputValueMenu = ({ values, onSelect, onCommit, open = false, onOpenChange, onEscape, multiSelect = false, initialValues = [], highlightValue, width = 'standard', positioning, onBuildingValueChange, onItemToggle, inputRef, filterText = '', menuRef, blurCommitRef, className })=>{
|
|
13
|
-
const filteredValues = useMemo(()=>filterAndSort(values, filterText, valueOptionSearchText), [
|
|
14
|
-
values,
|
|
15
|
-
filterText
|
|
16
|
-
]);
|
|
17
|
-
const { selectedValues, checkedValues, highlightedValue, onHighlightChange, pendingIds, registerItem, handleItemSelect } = useValueMenuState({
|
|
18
|
-
values: filteredValues,
|
|
19
|
-
open,
|
|
20
|
-
multiSelect,
|
|
21
|
-
initialValues,
|
|
22
|
-
highlightValue,
|
|
23
|
-
onSelect,
|
|
24
|
-
onCommit,
|
|
25
|
-
onEscape,
|
|
26
|
-
onOpenChange,
|
|
27
|
-
onBuildingValueChange,
|
|
28
|
-
onItemToggle,
|
|
29
|
-
inputRef,
|
|
30
|
-
menuRef,
|
|
31
|
-
blurCommitRef
|
|
32
|
-
});
|
|
33
|
-
const displayValues = useValueMenuDisplayValues({
|
|
34
|
-
values,
|
|
35
|
-
filteredValues,
|
|
36
|
-
multiSelect,
|
|
37
|
-
checkedValues,
|
|
38
|
-
highlightValue
|
|
39
|
-
});
|
|
40
|
-
const widthClass = 'compact' === width ? 'w-[172px]' : 'w-[300px]';
|
|
41
|
-
const widthStyle = 'number' == typeof width ? {
|
|
42
|
-
width: `${width}px`
|
|
43
|
-
} : void 0;
|
|
44
|
-
return /*#__PURE__*/ jsx(DropdownMenu, {
|
|
45
|
-
open: open,
|
|
46
|
-
onOpenChange: onOpenChange,
|
|
47
|
-
closeOnSelect: false,
|
|
48
|
-
positioning: positioning,
|
|
49
|
-
highlightedValue: highlightedValue,
|
|
50
|
-
onHighlightChange: onHighlightChange,
|
|
51
|
-
children: /*#__PURE__*/ jsxs(DropdownMenuContent, {
|
|
52
|
-
ref: menuRef,
|
|
53
|
-
className: cn(widthClass, 'max-h-[430px]', className),
|
|
54
|
-
style: widthStyle,
|
|
55
|
-
"data-filter-input-menu": "true",
|
|
56
|
-
children: [
|
|
57
|
-
displayValues.length > 0 ? /*#__PURE__*/ jsx(DropdownMenuGroup, {
|
|
58
|
-
children: displayValues.map((option)=>/*#__PURE__*/ jsx(ValueMenuItem, {
|
|
59
|
-
option: option,
|
|
60
|
-
isChecked: selectedValues.some((v)=>String(v) === String(option.value)),
|
|
61
|
-
isPending: pendingIds.has(String(option.value)),
|
|
62
|
-
multiSelect: multiSelect,
|
|
63
|
-
registerItem: registerItem,
|
|
64
|
-
onSelect: ()=>handleItemSelect({
|
|
65
|
-
id: String(option.value),
|
|
66
|
-
label: option.label,
|
|
67
|
-
value: option.value
|
|
68
|
-
})
|
|
69
|
-
}, String(option.value)))
|
|
70
|
-
}) : /*#__PURE__*/ jsx(MenuEmptyState, {}),
|
|
71
|
-
/*#__PURE__*/ jsx(ValueMenuFooter, {
|
|
72
|
-
multiSelect: multiSelect
|
|
73
|
-
})
|
|
74
|
-
]
|
|
75
|
-
})
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { isValueGroup } from "../../lib/index.js";
|
|
3
|
+
import { FlatValueMenu } from "./FlatValueMenu.js";
|
|
4
|
+
import { NestedValueMenu } from "./NestedValueMenu.js";
|
|
5
|
+
const FilterInputValueMenu = (props)=>{
|
|
6
|
+
const isNested = props.values.some((v)=>isValueGroup(v) || null != v.section);
|
|
7
|
+
return isNested ? /*#__PURE__*/ jsx(NestedValueMenu, {
|
|
8
|
+
...props
|
|
9
|
+
}) : /*#__PURE__*/ jsx(FlatValueMenu, {
|
|
10
|
+
...props
|
|
76
11
|
});
|
|
77
12
|
};
|
|
78
13
|
FilterInputValueMenu.displayName = 'FilterInputValueMenu';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import type { FilterInputValueMenuProps } from './FilterInputValueMenu';
|
|
3
|
+
/**
|
|
4
|
+
* Flat (non-nested) value menu — the original single-list implementation.
|
|
5
|
+
* Rendered by `FilterInputValueMenu` for fields whose values have no groups or
|
|
6
|
+
* sections. Behavior is unchanged from before nesting was introduced.
|
|
7
|
+
*/
|
|
8
|
+
export declare const FlatValueMenu: FC<FilterInputValueMenuProps>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { cn } from "../../../../utils/cn.js";
|
|
4
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuFooter, DropdownMenuGroup } from "../../../DropdownMenu/index.js";
|
|
5
|
+
import { filterAndSort } from "../../lib/index.js";
|
|
6
|
+
import { MenuEmptyState } from "../MenuEmptyState.js";
|
|
7
|
+
import { useValueMenuDisplayValues } from "./useValueMenuDisplayValues.js";
|
|
8
|
+
import { useValueMenuState } from "./useValueMenuState.js";
|
|
9
|
+
import { ValueMenuFooterHints } from "./ValueMenuFooterHints.js";
|
|
10
|
+
import { ValueMenuItem } from "./ValueMenuItem.js";
|
|
11
|
+
import { valueOptionSearchText } from "./valueOptionSearchText.js";
|
|
12
|
+
const FlatValueMenu = ({ values, onSelect, onCommit, open = false, onOpenChange, onEscape, multiSelect = false, initialValues = [], highlightValue, width = 'standard', positioning, onBuildingValueChange, onItemToggle, inputRef, filterText = '', menuRef, blurCommitRef, className })=>{
|
|
13
|
+
const filteredValues = useMemo(()=>filterAndSort(values, filterText, valueOptionSearchText), [
|
|
14
|
+
values,
|
|
15
|
+
filterText
|
|
16
|
+
]);
|
|
17
|
+
const { selectedValues, checkedValues, highlightedValue, onHighlightChange, pendingIds, registerItem, handleItemSelect } = useValueMenuState({
|
|
18
|
+
values: filteredValues,
|
|
19
|
+
open,
|
|
20
|
+
multiSelect,
|
|
21
|
+
initialValues,
|
|
22
|
+
highlightValue,
|
|
23
|
+
onSelect,
|
|
24
|
+
onCommit,
|
|
25
|
+
onEscape,
|
|
26
|
+
onOpenChange,
|
|
27
|
+
onBuildingValueChange,
|
|
28
|
+
onItemToggle,
|
|
29
|
+
inputRef,
|
|
30
|
+
menuRef,
|
|
31
|
+
blurCommitRef
|
|
32
|
+
});
|
|
33
|
+
const displayValues = useValueMenuDisplayValues({
|
|
34
|
+
values,
|
|
35
|
+
filteredValues,
|
|
36
|
+
multiSelect,
|
|
37
|
+
checkedValues,
|
|
38
|
+
highlightValue
|
|
39
|
+
});
|
|
40
|
+
const widthClass = 'compact' === width ? 'w-[172px]' : 'w-[300px]';
|
|
41
|
+
const widthStyle = 'number' == typeof width ? {
|
|
42
|
+
width: `${width}px`
|
|
43
|
+
} : void 0;
|
|
44
|
+
return /*#__PURE__*/ jsx(DropdownMenu, {
|
|
45
|
+
open: open,
|
|
46
|
+
onOpenChange: onOpenChange,
|
|
47
|
+
closeOnSelect: false,
|
|
48
|
+
positioning: positioning,
|
|
49
|
+
highlightedValue: highlightedValue,
|
|
50
|
+
onHighlightChange: onHighlightChange,
|
|
51
|
+
children: /*#__PURE__*/ jsxs(DropdownMenuContent, {
|
|
52
|
+
ref: menuRef,
|
|
53
|
+
className: cn(widthClass, 'max-h-[430px]', className),
|
|
54
|
+
style: widthStyle,
|
|
55
|
+
"data-filter-input-menu": "true",
|
|
56
|
+
children: [
|
|
57
|
+
displayValues.length > 0 ? /*#__PURE__*/ jsx(DropdownMenuGroup, {
|
|
58
|
+
children: displayValues.map((option)=>/*#__PURE__*/ jsx(ValueMenuItem, {
|
|
59
|
+
option: option,
|
|
60
|
+
isChecked: selectedValues.some((v)=>String(v) === String(option.value)),
|
|
61
|
+
isPending: pendingIds.has(String(option.value)),
|
|
62
|
+
multiSelect: multiSelect,
|
|
63
|
+
registerItem: registerItem,
|
|
64
|
+
onSelect: ()=>handleItemSelect({
|
|
65
|
+
id: String(option.value),
|
|
66
|
+
label: option.label,
|
|
67
|
+
value: option.value
|
|
68
|
+
})
|
|
69
|
+
}, String(option.value)))
|
|
70
|
+
}) : /*#__PURE__*/ jsx(MenuEmptyState, {}),
|
|
71
|
+
/*#__PURE__*/ jsx(DropdownMenuFooter, {
|
|
72
|
+
children: /*#__PURE__*/ jsx(ValueMenuFooterHints, {
|
|
73
|
+
multiSelect: multiSelect
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
]
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
FlatValueMenu.displayName = 'FlatValueMenu';
|
|
81
|
+
export { FlatValueMenu };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import type { FilterInputValueMenuProps } from './FilterInputValueMenu';
|
|
3
|
+
/**
|
|
4
|
+
* Value menu with nesting: top-level options are grouped under section headers
|
|
5
|
+
* and a parent category opens a right-side submenu of its leaf sub-values
|
|
6
|
+
* (multi-select). Only committable leaves reach the expression — sections and
|
|
7
|
+
* parent categories are presentational. Rendered by `FilterInputValueMenu` when
|
|
8
|
+
* the field's values contain groups/sections; the flat menu handles the rest.
|
|
9
|
+
*/
|
|
10
|
+
export declare const NestedValueMenu: FC<FilterInputValueMenuProps>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment as external_react_Fragment, useMemo, useRef } from "react";
|
|
3
|
+
import { cn } from "../../../../utils/cn.js";
|
|
4
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuFooter, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from "../../../DropdownMenu/index.js";
|
|
5
|
+
import { Text } from "../../../Text/index.js";
|
|
6
|
+
import { buildValueMenuSections } from "../../lib/buildValueMenuSections.js";
|
|
7
|
+
import { MenuEmptyState } from "../MenuEmptyState.js";
|
|
8
|
+
import { NestedValueMenuItem } from "./NestedValueMenuItem.js";
|
|
9
|
+
import { SELECT_ALL_ID, useNestedValueMenuState } from "./useNestedValueMenuState.js";
|
|
10
|
+
import { ValueMenuFooterHints } from "./ValueMenuFooterHints.js";
|
|
11
|
+
const NestedValueMenu = ({ values, onSelect, onCommit, open = false, onOpenChange, onEscape, multiSelect = false, initialValues = [], width = 'standard', positioning, onBuildingValueChange, onItemToggle, inputRef, filterText = '', menuRef, blurCommitRef, className })=>{
|
|
12
|
+
const submenuRef = useRef(null);
|
|
13
|
+
const rowElsRef = useRef(new Map());
|
|
14
|
+
const sections = useMemo(()=>buildValueMenuSections(values, filterText), [
|
|
15
|
+
values,
|
|
16
|
+
filterText
|
|
17
|
+
]);
|
|
18
|
+
const state = useNestedValueMenuState({
|
|
19
|
+
sections,
|
|
20
|
+
allValues: values,
|
|
21
|
+
open,
|
|
22
|
+
multiSelect,
|
|
23
|
+
initialValues,
|
|
24
|
+
onSelect,
|
|
25
|
+
onCommit,
|
|
26
|
+
onEscape,
|
|
27
|
+
onOpenChange,
|
|
28
|
+
onBuildingValueChange,
|
|
29
|
+
onItemToggle,
|
|
30
|
+
inputRef,
|
|
31
|
+
menuRef,
|
|
32
|
+
submenuRef,
|
|
33
|
+
blurCommitRef
|
|
34
|
+
});
|
|
35
|
+
const widthClass = 'compact' === width ? 'w-[172px]' : 'w-[300px]';
|
|
36
|
+
const widthStyle = 'number' == typeof width ? {
|
|
37
|
+
width: `${width}px`
|
|
38
|
+
} : void 0;
|
|
39
|
+
const setRowRef = (id)=>(el)=>{
|
|
40
|
+
state.registerTopItem(id)(el);
|
|
41
|
+
if (el) rowElsRef.current.set(id, el);
|
|
42
|
+
else rowElsRef.current.delete(id);
|
|
43
|
+
};
|
|
44
|
+
const submenuPositioning = useMemo(()=>({
|
|
45
|
+
placement: 'right-start',
|
|
46
|
+
gutter: 8,
|
|
47
|
+
getAnchorRect: ()=>state.openParentId && rowElsRef.current.get(state.openParentId)?.getBoundingClientRect() || null
|
|
48
|
+
}), [
|
|
49
|
+
state.openParentId
|
|
50
|
+
]);
|
|
51
|
+
const openParentRow = state.openParentRow;
|
|
52
|
+
const allChecked = openParentRow ? state.allLeavesChecked(openParentRow.option) : false;
|
|
53
|
+
const hasRows = sections.some((section)=>section.rows.length > 0);
|
|
54
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
55
|
+
children: [
|
|
56
|
+
/*#__PURE__*/ jsx(DropdownMenu, {
|
|
57
|
+
open: open,
|
|
58
|
+
onOpenChange: onOpenChange,
|
|
59
|
+
closeOnSelect: false,
|
|
60
|
+
positioning: positioning,
|
|
61
|
+
highlightedValue: state.topHighlightedValue,
|
|
62
|
+
onHighlightChange: state.onTopHighlightChange,
|
|
63
|
+
children: /*#__PURE__*/ jsxs(DropdownMenuContent, {
|
|
64
|
+
ref: menuRef,
|
|
65
|
+
className: cn(widthClass, 'max-h-[430px]', className),
|
|
66
|
+
style: widthStyle,
|
|
67
|
+
"data-filter-input-menu": "true",
|
|
68
|
+
children: [
|
|
69
|
+
hasRows ? sections.map((section, index)=>/*#__PURE__*/ jsxs(external_react_Fragment, {
|
|
70
|
+
children: [
|
|
71
|
+
index > 0 && /*#__PURE__*/ jsx(DropdownMenuSeparator, {}),
|
|
72
|
+
section.label && /*#__PURE__*/ jsx(DropdownMenuLabel, {
|
|
73
|
+
children: section.label
|
|
74
|
+
}),
|
|
75
|
+
/*#__PURE__*/ jsx(DropdownMenuGroup, {
|
|
76
|
+
children: section.rows.map((row)=>/*#__PURE__*/ jsx(NestedValueMenuItem, {
|
|
77
|
+
id: row.id,
|
|
78
|
+
option: row.option,
|
|
79
|
+
isGroup: row.isGroup,
|
|
80
|
+
checkedState: state.getRowCheckState(row),
|
|
81
|
+
isPending: state.topPendingIds.has(row.id),
|
|
82
|
+
multiSelect: multiSelect,
|
|
83
|
+
onSelect: ()=>state.selectRow(row, false),
|
|
84
|
+
onMouseEnter: row.isGroup ? ()=>state.openParent(row.id) : void 0,
|
|
85
|
+
onMouseLeave: row.isGroup ? ()=>state.scheduleClose() : void 0,
|
|
86
|
+
registerRef: setRowRef(row.id),
|
|
87
|
+
expanded: state.openParentId === row.id
|
|
88
|
+
}, row.id))
|
|
89
|
+
})
|
|
90
|
+
]
|
|
91
|
+
}, section.label ?? `section-${index}`)) : /*#__PURE__*/ jsx(MenuEmptyState, {}),
|
|
92
|
+
/*#__PURE__*/ jsx(DropdownMenuFooter, {
|
|
93
|
+
children: /*#__PURE__*/ jsx(ValueMenuFooterHints, {
|
|
94
|
+
multiSelect: multiSelect
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
]
|
|
98
|
+
})
|
|
99
|
+
}),
|
|
100
|
+
openParentRow && /*#__PURE__*/ jsx(DropdownMenu, {
|
|
101
|
+
open: null != state.openParentId,
|
|
102
|
+
closeOnSelect: false,
|
|
103
|
+
positioning: submenuPositioning,
|
|
104
|
+
highlightedValue: state.submenuHighlightedValue,
|
|
105
|
+
onHighlightChange: state.onSubmenuHighlightChange,
|
|
106
|
+
children: /*#__PURE__*/ jsx(DropdownMenuContent, {
|
|
107
|
+
ref: submenuRef,
|
|
108
|
+
className: cn(widthClass, 'max-h-[430px]'),
|
|
109
|
+
style: widthStyle,
|
|
110
|
+
"data-filter-input-menu": "true",
|
|
111
|
+
onMouseEnter: state.cancelClose,
|
|
112
|
+
onMouseLeave: state.scheduleClose,
|
|
113
|
+
children: /*#__PURE__*/ jsxs(DropdownMenuGroup, {
|
|
114
|
+
children: [
|
|
115
|
+
/*#__PURE__*/ jsx(DropdownMenuItem, {
|
|
116
|
+
value: SELECT_ALL_ID,
|
|
117
|
+
ref: state.registerSubmenuItem(SELECT_ALL_ID),
|
|
118
|
+
onSelect: ()=>state.toggleGroup(openParentRow.option),
|
|
119
|
+
className: state.submenuPendingIds.has(SELECT_ALL_ID) ? 'bg-states-primary-hover' : void 0,
|
|
120
|
+
children: /*#__PURE__*/ jsx(Text, {
|
|
121
|
+
size: "sm",
|
|
122
|
+
color: "secondary",
|
|
123
|
+
children: allChecked ? 'Unselect all' : 'Select all'
|
|
124
|
+
})
|
|
125
|
+
}),
|
|
126
|
+
/*#__PURE__*/ jsx(DropdownMenuSeparator, {}),
|
|
127
|
+
state.submenuRows.map((row)=>/*#__PURE__*/ jsx(NestedValueMenuItem, {
|
|
128
|
+
id: row.id,
|
|
129
|
+
option: row.option,
|
|
130
|
+
isGroup: row.isGroup,
|
|
131
|
+
checkedState: state.getRowCheckState(row),
|
|
132
|
+
isPending: state.submenuPendingIds.has(row.id),
|
|
133
|
+
multiSelect: multiSelect,
|
|
134
|
+
onSelect: ()=>state.selectRow(row, true),
|
|
135
|
+
registerRef: state.registerSubmenuItem(row.id)
|
|
136
|
+
}, row.id))
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
]
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
NestedValueMenu.displayName = 'NestedValueMenu';
|
|
145
|
+
export { NestedValueMenu };
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/NestedValueMenuItem.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { CheckboxCheckedState } from '@ark-ui/react/checkbox';
|
|
3
|
+
import type { ValueOption } from './FilterInputValueMenu';
|
|
4
|
+
interface NestedValueMenuItemProps {
|
|
5
|
+
id: string;
|
|
6
|
+
option: ValueOption;
|
|
7
|
+
/** Whether this row is a group (parent category → opens a submenu on hover). */
|
|
8
|
+
isGroup: boolean;
|
|
9
|
+
checkedState: CheckboxCheckedState;
|
|
10
|
+
isPending: boolean;
|
|
11
|
+
multiSelect: boolean;
|
|
12
|
+
onSelect: () => void;
|
|
13
|
+
onMouseEnter?: () => void;
|
|
14
|
+
onMouseLeave?: () => void;
|
|
15
|
+
registerRef?: (el: HTMLElement | null) => void;
|
|
16
|
+
/** For group rows: whether its submenu is currently open. */
|
|
17
|
+
expanded?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A single row in the nested value menu — a committable leaf or a group
|
|
21
|
+
* (parent category). Both render a (tri-state, for groups) checkbox; a group's
|
|
22
|
+
* submenu opens on hover. No chevron: selection and bulk-selection are driven
|
|
23
|
+
* entirely by the checkbox.
|
|
24
|
+
*/
|
|
25
|
+
export declare const NestedValueMenuItem: FC<NestedValueMenuItemProps>;
|
|
26
|
+
export {};
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/NestedValueMenuItem.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from "../../../Badge/index.js";
|
|
3
|
+
import { Checkmark } from "../../../Checkmark/index.js";
|
|
4
|
+
import { DropdownMenuItem } from "../../../DropdownMenu/index.js";
|
|
5
|
+
import { Text } from "../../../Text/index.js";
|
|
6
|
+
const NestedValueMenuItem = ({ id, option, isGroup, checkedState, isPending, multiSelect, onSelect, onMouseEnter, onMouseLeave, registerRef, expanded })=>/*#__PURE__*/ jsxs(DropdownMenuItem, {
|
|
7
|
+
value: id,
|
|
8
|
+
ref: registerRef,
|
|
9
|
+
onSelect: onSelect,
|
|
10
|
+
onMouseEnter: onMouseEnter,
|
|
11
|
+
onMouseLeave: onMouseLeave,
|
|
12
|
+
className: isPending ? 'bg-states-primary-hover' : void 0,
|
|
13
|
+
"aria-checked": multiSelect ? 'indeterminate' === checkedState ? 'mixed' : !!checkedState : void 0,
|
|
14
|
+
...isGroup && {
|
|
15
|
+
'aria-haspopup': 'menu',
|
|
16
|
+
'aria-expanded': !!expanded
|
|
17
|
+
},
|
|
18
|
+
children: [
|
|
19
|
+
option.badge ? /*#__PURE__*/ jsx(Badge, {
|
|
20
|
+
color: option.badge.color,
|
|
21
|
+
type: "secondary",
|
|
22
|
+
variant: "default",
|
|
23
|
+
children: option.badge.text
|
|
24
|
+
}) : /*#__PURE__*/ jsxs("div", {
|
|
25
|
+
className: "min-w-0",
|
|
26
|
+
children: [
|
|
27
|
+
/*#__PURE__*/ jsx(Text, {
|
|
28
|
+
size: "sm",
|
|
29
|
+
truncate: true,
|
|
30
|
+
children: option.label
|
|
31
|
+
}),
|
|
32
|
+
option.description && /*#__PURE__*/ jsx(Text, {
|
|
33
|
+
size: "xs",
|
|
34
|
+
color: "secondary",
|
|
35
|
+
truncate: true,
|
|
36
|
+
children: option.description
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
}),
|
|
40
|
+
multiSelect ? /*#__PURE__*/ jsx("div", {
|
|
41
|
+
className: "flex shrink-0 items-start justify-end py-2 ml-auto",
|
|
42
|
+
children: /*#__PURE__*/ jsx(Checkmark, {
|
|
43
|
+
checkedState: checkedState
|
|
44
|
+
})
|
|
45
|
+
}) : !isGroup && checkedState ? /*#__PURE__*/ jsx("div", {
|
|
46
|
+
className: "flex shrink-0 items-start justify-end py-2 ml-auto",
|
|
47
|
+
children: /*#__PURE__*/ jsx(Checkmark, {
|
|
48
|
+
checkedState: true
|
|
49
|
+
})
|
|
50
|
+
}) : null
|
|
51
|
+
]
|
|
52
|
+
});
|
|
53
|
+
NestedValueMenuItem.displayName = 'NestedValueMenuItem';
|
|
54
|
+
export { NestedValueMenuItem };
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuFooterHints.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
interface ValueMenuFooterHintsProps {
|
|
3
|
+
multiSelect: boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Keyboard-hint content for the value menu footer. Rendered *inside* a
|
|
7
|
+
* `DropdownMenuFooter` at the call site (not a footer itself) so the footer
|
|
8
|
+
* element stays a direct child of `DropdownMenuContent` and is pinned outside
|
|
9
|
+
* the scrolling list instead of scrolling away with the options.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ValueMenuFooterHints: FC<ValueMenuFooterHintsProps>;
|
|
12
|
+
export {};
|
package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuFooterHints.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Kbd } from "../../../Kbd/Kbd.js";
|
|
3
|
+
import { KbdGroup } from "../../../Kbd/KbdGroup.js";
|
|
4
|
+
const ValueMenuFooterHints = ({ multiSelect })=>multiSelect ? /*#__PURE__*/ jsxs(Fragment, {
|
|
5
|
+
children: [
|
|
6
|
+
/*#__PURE__*/ jsxs("span", {
|
|
7
|
+
className: "flex items-center gap-4",
|
|
8
|
+
children: [
|
|
9
|
+
/*#__PURE__*/ jsx(KbdGroup, {
|
|
10
|
+
children: /*#__PURE__*/ jsx(Kbd, {
|
|
11
|
+
children: "↵"
|
|
12
|
+
})
|
|
13
|
+
}),
|
|
14
|
+
"to select"
|
|
15
|
+
]
|
|
16
|
+
}),
|
|
17
|
+
/*#__PURE__*/ jsxs("span", {
|
|
18
|
+
className: "flex items-center gap-4",
|
|
19
|
+
children: [
|
|
20
|
+
/*#__PURE__*/ jsxs(KbdGroup, {
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
23
|
+
children: "⌘"
|
|
24
|
+
}),
|
|
25
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
26
|
+
children: "↑"
|
|
27
|
+
}),
|
|
28
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
29
|
+
children: "↓"
|
|
30
|
+
})
|
|
31
|
+
]
|
|
32
|
+
}),
|
|
33
|
+
"to multi-select"
|
|
34
|
+
]
|
|
35
|
+
})
|
|
36
|
+
]
|
|
37
|
+
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
38
|
+
children: [
|
|
39
|
+
/*#__PURE__*/ jsxs("span", {
|
|
40
|
+
className: "flex items-center gap-4",
|
|
41
|
+
children: [
|
|
42
|
+
/*#__PURE__*/ jsxs(KbdGroup, {
|
|
43
|
+
children: [
|
|
44
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
45
|
+
children: "↑"
|
|
46
|
+
}),
|
|
47
|
+
/*#__PURE__*/ jsx(Kbd, {
|
|
48
|
+
children: "↓"
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
}),
|
|
52
|
+
"to navigate"
|
|
53
|
+
]
|
|
54
|
+
}),
|
|
55
|
+
/*#__PURE__*/ jsxs("span", {
|
|
56
|
+
className: "flex items-center gap-4",
|
|
57
|
+
children: [
|
|
58
|
+
/*#__PURE__*/ jsx(KbdGroup, {
|
|
59
|
+
children: /*#__PURE__*/ jsx(Kbd, {
|
|
60
|
+
children: "↵"
|
|
61
|
+
})
|
|
62
|
+
}),
|
|
63
|
+
"to select"
|
|
64
|
+
]
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
ValueMenuFooterHints.displayName = 'ValueMenuFooterHints';
|
|
69
|
+
export { ValueMenuFooterHints };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { CheckboxCheckedState } from '@ark-ui/react/checkbox';
|
|
3
|
+
import type { ValueMenuRow, ValueMenuSection } from '../../lib/buildValueMenuSections';
|
|
4
|
+
import type { FieldValueOption } from '../../types';
|
|
5
|
+
type ConditionValue = string | number | boolean;
|
|
6
|
+
/** Sentinel id for the submenu's "Select all / Unselect all" toggle row. */
|
|
7
|
+
export declare const SELECT_ALL_ID = "__filter_value_select_all__";
|
|
8
|
+
interface UseNestedValueMenuStateOptions {
|
|
9
|
+
/** Filtered, ordered top-level sections. */
|
|
10
|
+
sections: ValueMenuSection[];
|
|
11
|
+
/** Full (unfiltered) value tree — used for leaf-label resolution / preview. */
|
|
12
|
+
allValues: FieldValueOption[];
|
|
13
|
+
open: boolean;
|
|
14
|
+
multiSelect: boolean;
|
|
15
|
+
initialValues: ConditionValue[];
|
|
16
|
+
onSelect: (value: ConditionValue) => void;
|
|
17
|
+
onCommit?: (values: ConditionValue[]) => void;
|
|
18
|
+
onEscape?: () => void;
|
|
19
|
+
onOpenChange?: (open: boolean) => void;
|
|
20
|
+
onBuildingValueChange?: (preview: string | undefined) => void;
|
|
21
|
+
onItemToggle?: () => void;
|
|
22
|
+
inputRef?: RefObject<HTMLInputElement | null>;
|
|
23
|
+
menuRef?: RefObject<HTMLDivElement | null>;
|
|
24
|
+
submenuRef?: RefObject<HTMLDivElement | null>;
|
|
25
|
+
blurCommitRef?: RefObject<(() => boolean) | null>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Selection + two-level keyboard navigation for the **nested** value menu
|
|
29
|
+
* (sections + a right-side submenu of leaf sub-values). Selection state is a
|
|
30
|
+
* single flat array of committed **leaf** values — group rows are never stored;
|
|
31
|
+
* toggling one is a bulk shortcut for its descendant leaves.
|
|
32
|
+
*
|
|
33
|
+
* Only one level listens for keys at a time: the two `useKeyboardNav` instances
|
|
34
|
+
* are gated on `open` so the top-level listener detaches while the submenu is
|
|
35
|
+
* open and vice-versa (no double navigation).
|
|
36
|
+
*/
|
|
37
|
+
export declare const useNestedValueMenuState: ({ sections, allValues, open, multiSelect, initialValues, onSelect, onCommit, onEscape, onOpenChange, onBuildingValueChange, onItemToggle, inputRef, menuRef, submenuRef, blurCommitRef, }: UseNestedValueMenuStateOptions) => {
|
|
38
|
+
checkedValues: ConditionValue[];
|
|
39
|
+
openParentId: string | null;
|
|
40
|
+
openParent: (id: string) => void;
|
|
41
|
+
closeParent: () => void;
|
|
42
|
+
cancelClose: () => void;
|
|
43
|
+
scheduleClose: () => void;
|
|
44
|
+
openParentRow: ValueMenuRow | undefined;
|
|
45
|
+
submenuRows: ValueMenuRow[];
|
|
46
|
+
selectRow: (row: ValueMenuRow, fromSubmenu: boolean) => void;
|
|
47
|
+
toggleGroup: (option: FieldValueOption) => void;
|
|
48
|
+
isLeafChecked: (value: ConditionValue) => boolean;
|
|
49
|
+
getRowCheckState: (row: ValueMenuRow) => CheckboxCheckedState;
|
|
50
|
+
allLeavesChecked: (option: FieldValueOption) => boolean;
|
|
51
|
+
commitChecked: () => boolean;
|
|
52
|
+
topHighlightedValue: string;
|
|
53
|
+
onTopHighlightChange: (details: {
|
|
54
|
+
highlightedValue: string | null;
|
|
55
|
+
}) => void;
|
|
56
|
+
topPendingIds: Set<string>;
|
|
57
|
+
registerTopItem: (id: string) => (el: HTMLElement | null) => void;
|
|
58
|
+
submenuHighlightedValue: string;
|
|
59
|
+
onSubmenuHighlightChange: (details: {
|
|
60
|
+
highlightedValue: string | null;
|
|
61
|
+
}) => void;
|
|
62
|
+
submenuPendingIds: Set<string>;
|
|
63
|
+
registerSubmenuItem: (id: string) => (el: HTMLElement | null) => void;
|
|
64
|
+
};
|
|
65
|
+
export {};
|