@trackunit/filters-filter-bar 2.4.4 → 2.4.7-alpha-770d9994af7.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/index.cjs.js +67 -35
- package/index.esm.js +68 -36
- package/package.json +12 -12
- package/src/lib/components/AppliedFiltersRenderer.d.ts +8 -6
- package/src/lib/components/FilterTableComponent.d.ts +3 -2
- package/src/lib/components/FilterTooltips/TooltipLabel.d.ts +3 -2
- package/src/lib/components/FiltersMenu.d.ts +3 -3
- package/src/lib/components/FiltersMenuContent.d.ts +8 -5
- package/src/lib/components/FiltersMenuTriggerButton.d.ts +32 -0
- package/src/lib/components/GroupedFiltersList.d.ts +3 -3
- package/src/lib/components/ResetFiltersButton.d.ts +3 -3
package/index.cjs.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var i18nLibraryTranslation = require('@trackunit/i18n-library-translation');
|
|
5
5
|
var react = require('react');
|
|
6
|
+
var tailwindMerge = require('tailwind-merge');
|
|
6
7
|
var reactFilterComponents = require('@trackunit/react-filter-components');
|
|
7
8
|
var reactComponents = require('@trackunit/react-components');
|
|
8
9
|
var reactCoreHooks = require('@trackunit/react-core-hooks');
|
|
9
10
|
var stringTs = require('string-ts');
|
|
10
|
-
var tailwindMerge = require('tailwind-merge');
|
|
11
11
|
var irisAppRuntimeCoreApi = require('@trackunit/iris-app-runtime-core-api');
|
|
12
12
|
var reactFormComponents = require('@trackunit/react-form-components');
|
|
13
13
|
var reactDateAndTimeComponents = require('@trackunit/react-date-and-time-components');
|
|
@@ -451,9 +451,9 @@ const ResetFiltersButtonWithIcon = ({ resetFiltersToInitialState, "data-testid":
|
|
|
451
451
|
/**
|
|
452
452
|
*
|
|
453
453
|
*/
|
|
454
|
-
const AppliedFiltersRenderer = ({ appliedFilters, filterBarConfig, showResetButton = true, style, }) => {
|
|
454
|
+
const AppliedFiltersRenderer = ({ appliedFilters, filterBarConfig, showResetButton = true, className, "data-testid": dataTestId, ref, style, }) => {
|
|
455
455
|
const filtersToRender = react.useMemo(() => appliedFilters.filter(filter => !filter.showDirectly), [appliedFilters]);
|
|
456
|
-
return (jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", style: style, children: [filtersToRender.length > 0 ? (jsxRuntime.jsx("div", { className: "h-4 w-[1px] bg-neutral-300", "data-testid": "applied-filters-buttons" })) : null, jsxRuntime.jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: filtersToRender }), appliedFilters.length > 0 && showResetButton ? (jsxRuntime.jsx(ResetFiltersButtonWithIcon, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] }));
|
|
456
|
+
return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex flex-wrap items-center gap-2", className), "data-testid": dataTestId, ref: ref, style: style, children: [filtersToRender.length > 0 ? (jsxRuntime.jsx("div", { className: "h-4 w-[1px] bg-neutral-300", "data-testid": "applied-filters-buttons" })) : null, jsxRuntime.jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: filtersToRender }), appliedFilters.length > 0 && showResetButton ? (jsxRuntime.jsx(ResetFiltersButtonWithIcon, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] }));
|
|
457
457
|
};
|
|
458
458
|
|
|
459
459
|
/**
|
|
@@ -687,11 +687,11 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
|
|
|
687
687
|
onChange: customSearch?.onChange ?? setSearchText,
|
|
688
688
|
onClear: customSearch?.onClear ?? (() => setSearchText("")),
|
|
689
689
|
count: undefinedCount ? filteredOptions.length - 1 : filteredOptions.length,
|
|
690
|
-
}, children: options.length >= 2 ? (jsxRuntime.jsx(reactComponents.Button, { className: "place-self-start", "data-testid": "selectAllButton", disabled: hasSelectedAll, onClick: handleSelectAll, size: "small", variant: "ghost", children: t("filtersBar.selectAll") })) : null }), jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("grid
|
|
690
|
+
}, children: options.length >= 2 ? (jsxRuntime.jsx(reactComponents.Button, { className: "place-self-start", "data-testid": "selectAllButton", disabled: hasSelectedAll, onClick: handleSelectAll, size: "small", variant: "ghost", children: t("filtersBar.selectAll") })) : null }), jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("grid min-h-0 overflow-hidden", undefinedCount ? "grid-rows-[minmax(0,1fr)_auto]" : "grid-rows-[minmax(0,1fr)]"), children: [jsxRuntime.jsx(FilterResults, { ignoreUndefined: undefinedCount !== null, loading: loading, results: results, children: res => (jsxRuntime.jsx(DynamicFilterList, { checked: index => {
|
|
691
691
|
return filterDefinition.type === "valueNameArray"
|
|
692
692
|
? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, res[index]?.key || "")
|
|
693
693
|
: filterBarActions.arrayIncludesValue(filterDefinition.filterKey, res[index]?.key || "");
|
|
694
|
-
}, className: "
|
|
694
|
+
}, className: "h-full", count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", onChange: index => {
|
|
695
695
|
const result = res[index];
|
|
696
696
|
if (result) {
|
|
697
697
|
handleSetValue(result);
|
|
@@ -933,10 +933,10 @@ const useFiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters =
|
|
|
933
933
|
*
|
|
934
934
|
* @returns {ReactElement} - Returns the FiltersList component.
|
|
935
935
|
*/
|
|
936
|
-
const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, style, "data-testid": dataTestId = "grouped-filters-list", }) => {
|
|
937
|
-
return (jsxRuntime.jsx("div", { className: className, "data-testid": dataTestId,
|
|
936
|
+
const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, ref, style, "data-testid": dataTestId = "grouped-filters-list", }) => {
|
|
937
|
+
return (jsxRuntime.jsx("div", { className: className, "data-testid": dataTestId, ref: ref, style: style, children: filtersGrouped.map((group, idx) => {
|
|
938
938
|
const isLastGroup = idx === filtersGrouped.length - 1;
|
|
939
|
-
return (jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [jsxRuntime.jsxs("div", { children: [filtersGrouped.length > 1 && (jsxRuntime.jsx(reactComponents.Text, { className: "h-7
|
|
939
|
+
return (jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [jsxRuntime.jsxs("div", { children: [filtersGrouped.length > 1 && (jsxRuntime.jsx(reactComponents.Text, { className: "h-7 p-2 text-neutral-400", "data-testid": `${group.key}-group-title`, size: "small", uppercase: true, weight: "bold", children: group.title })), jsxRuntime.jsx("ul", { "aria-labelledby": `${group.key}-group-title`, className: "grid", "data-testid": `${group.key}-group-list`, children: jsxRuntime.jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: group.filters, visualStyle: "list-item" }) })] }), isLastGroup ? null : jsxRuntime.jsx("div", { className: "h-[1px] w-full bg-neutral-200", role: "separator" })] }, group.key));
|
|
940
940
|
}) }));
|
|
941
941
|
};
|
|
942
942
|
|
|
@@ -945,25 +945,38 @@ const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, style,
|
|
|
945
945
|
*
|
|
946
946
|
* @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
|
|
947
947
|
*/
|
|
948
|
-
const ResetFiltersButton = ({ resetFiltersToInitialState, "data-testid": dataTestId, className, }) => {
|
|
948
|
+
const ResetFiltersButton = ({ resetFiltersToInitialState, "data-testid": dataTestId, className, ref, style, }) => {
|
|
949
949
|
const [t] = useTranslation();
|
|
950
950
|
return (jsxRuntime.jsxs(reactComponents.Button, { className: className, "data-testid": dataTestId ?? "reset-filters-button", onClick: () => {
|
|
951
951
|
resetFiltersToInitialState();
|
|
952
|
-
}, size: "small", variant: "ghost", children: [t("filtersBar.resetFilters"), jsxRuntime.jsx("span", { className: "sr-only", children: t("filtersBar.resetFiltersSR") })] }));
|
|
952
|
+
}, ref: ref, size: "small", style: style, variant: "ghost", children: [t("filtersBar.resetFilters"), jsxRuntime.jsx("span", { className: "sr-only", children: t("filtersBar.resetFiltersSR") })] }));
|
|
953
953
|
};
|
|
954
954
|
|
|
955
955
|
/**
|
|
956
956
|
*
|
|
957
957
|
*/
|
|
958
|
-
const FiltersMenuContent = ({ filterBarConfig, setShowCustomFilters, setSearchText, searchText, searchResultsGrouped, filtersToShowGrouped, removeCustomFieldsGroup, hasCustomFields, appliedCustomFields, showCustomFilters, filterBarDefinitionCount, }) => {
|
|
958
|
+
const FiltersMenuContent = ({ filterBarConfig, setShowCustomFilters, setSearchText, searchText, searchResultsGrouped, filtersToShowGrouped, removeCustomFieldsGroup, hasCustomFields, appliedCustomFields, showCustomFilters, filterBarDefinitionCount, className, "data-testid": dataTestId = "starred-filters-menu-popover", ref, style, }) => {
|
|
959
959
|
const [t] = useTranslation();
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
960
|
+
// Chrome-only shell (not `MenuContent`): filter rows are wrapped (`GroupedFiltersList` →
|
|
961
|
+
// `FilterComponent` → `Filter` → `MenuItem`), so `MenuContent`'s direct-child registration would
|
|
962
|
+
// never attach keyboard nav — leaving a `role="menu"` with no owned menuitems. Shadow/layout come
|
|
963
|
+
// from `cvaMenu`/`cvaMenuList`; keyboard ownership returns when MenuItem self-registers (GLU-1644).
|
|
964
|
+
return (jsxRuntime.jsx("div", { className: reactComponents.cvaMenu({
|
|
965
|
+
className: tailwindMerge.twMerge("max-h-[min(600px,calc(100dvh-32px))] grid-rows-[minmax(0,1fr)] overflow-y-hidden p-0", className),
|
|
966
|
+
limitWidth: true,
|
|
967
|
+
}), "data-testid": dataTestId, ref: ref, style: style, children: jsxRuntime.jsxs("div", {
|
|
968
|
+
// `gap-y-0` cancels `cvaMenuList`'s default inter-item gap, which here would otherwise land
|
|
969
|
+
// between the header block (search/count + separator) and the scrollable list below it,
|
|
970
|
+
// showing as a sliver of whitespace above the list's own scrollbar.
|
|
971
|
+
className: reactComponents.cvaMenuList({
|
|
972
|
+
className: "grid-rows-min-fr !max-h-none min-h-0 gap-y-0 !overflow-y-hidden p-0",
|
|
973
|
+
}), children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 p-1", children: [filterBarDefinitionCount > 5 && (jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, "data-testid": "starred-filters-menu-search", fieldSize: "small", id: "search-filters-list", onChange: e => setSearchText(e.currentTarget.value), onClear: () => setSearchText(""), placeholder: t("filtersBar.searchFiltersPlaceholder"), value: searchText })), jsxRuntime.jsxs("div", { className: "flex h-7 items-center justify-between gap-1 pl-3", children: [jsxRuntime.jsx(reactComponents.Text, { className: "text-neutral-400", size: "small", children: jsxRuntime.jsx(FiltersAppliedCountLabel, { filterBarConfig: filterBarConfig }) }), filterBarConfig.appliedFilterKeys().length > 0 ? (jsxRuntime.jsx(ResetFiltersButton, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] })] }), jsxRuntime.jsx(Separator, {})] }), jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-col gap-1 overflow-auto p-1", children: [jsxRuntime.jsx(GroupedFiltersList, { className: "flex flex-col gap-1", filterBarConfig: filterBarConfig, filtersGrouped: searchText
|
|
974
|
+
? searchResultsGrouped
|
|
975
|
+
: showCustomFilters
|
|
976
|
+
? filtersToShowGrouped
|
|
977
|
+
: removeCustomFieldsGroup(filtersToShowGrouped) }), hasCustomFields && !showCustomFilters && !searchText ? (jsxRuntime.jsx(CustomFieldsHiddenGroup, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, onClickShow: () => {
|
|
978
|
+
setShowCustomFilters(true);
|
|
979
|
+
} })) : null] })] }) }));
|
|
967
980
|
};
|
|
968
981
|
const Separator = () => jsxRuntime.jsx("hr", { className: "border-neutral-200", role: "separator" });
|
|
969
982
|
const FiltersAppliedCountLabel = ({ filterBarConfig, }) => {
|
|
@@ -992,6 +1005,26 @@ const CustomFieldsHiddenGroup = ({ appliedCustomFields, filterBarConfig, onClick
|
|
|
992
1005
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Separator, {}), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx(reactComponents.Button, { "aria-controls": "filters-list", className: "text-primary-600 w-full justify-between px-3", onClick: onClickShow, prefix: jsxRuntime.jsx(reactComponents.Text, { className: "text-neutral-400", size: "small", uppercase: true, weight: "bold", children: t("filtersBar.groups.CUSTOM_FIELDS") }), size: "small", variant: "ghost-neutral", children: t("filtersBar.showAll") }), appliedCustomFields.length > 0 ? (jsxRuntime.jsx("ul", { "aria-label": "Visible custom fields", "data-testid": "applied-custom-fields-list", children: jsxRuntime.jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: appliedCustomFields, visualStyle: "list-item" }) })) : null] })] }));
|
|
993
1006
|
};
|
|
994
1007
|
|
|
1008
|
+
const sharedButtonProps = {
|
|
1009
|
+
size: "small",
|
|
1010
|
+
variant: "secondary",
|
|
1011
|
+
};
|
|
1012
|
+
/**
|
|
1013
|
+
* FiltersMenuTriggerButton is the button that opens the filters popover menu.
|
|
1014
|
+
*
|
|
1015
|
+
* On small viewports (`isCompact`) it renders as a square icon-only button with the applied filters count
|
|
1016
|
+
* overlaid as a badge. Otherwise it renders as a regular button with a text label and an inline count badge.
|
|
1017
|
+
*/
|
|
1018
|
+
const FiltersMenuTriggerButton = ({ isCompact, hasAppliedFilters, appliedValuesCount, title, buttonProps, }) => {
|
|
1019
|
+
const [t] = useTranslation();
|
|
1020
|
+
const label = title ?? t("filtersBar.filtersHeading");
|
|
1021
|
+
const icon = jsxRuntime.jsx(reactComponents.Icon, { ariaHidden: true, color: hasAppliedFilters ? "primary" : undefined, name: "Funnel", size: "small" });
|
|
1022
|
+
if (isCompact) {
|
|
1023
|
+
return (jsxRuntime.jsxs(reactComponents.ZStack, { className: "shrink-0", children: [jsxRuntime.jsx(reactComponents.IconButton, { ariaLabel: label, icon: icon, ...sharedButtonProps, ...buttonProps }), appliedValuesCount > 0 ? (jsxRuntime.jsx(reactComponents.Badge, { className: "-translate-y-1/3 translate-x-1/3 self-start justify-self-end", color: "primary", count: appliedValuesCount, hideZero: true, size: "condensed" })) : null] }));
|
|
1024
|
+
}
|
|
1025
|
+
return (jsxRuntime.jsx(reactComponents.Button, { prefix: icon, suffix: appliedValuesCount > 0 ? (jsxRuntime.jsx(reactComponents.Badge, { color: "primary", count: appliedValuesCount, hideZero: true, size: "condensed" })) : null, ...sharedButtonProps, ...buttonProps, children: title !== "" ? label : null }));
|
|
1026
|
+
};
|
|
1027
|
+
|
|
995
1028
|
/**
|
|
996
1029
|
* TooltipValues component that displays formatted tooltip values based on the provided input.
|
|
997
1030
|
*
|
|
@@ -1032,8 +1065,8 @@ const TooltipValues = ({ values }) => {
|
|
|
1032
1065
|
* @param {any} props.displayValues - The filter values to display.
|
|
1033
1066
|
* @returns {ReactElement} The rendered component showing the title and values.
|
|
1034
1067
|
*/
|
|
1035
|
-
const TooltipLabel = ({ title, displayValues }) => {
|
|
1036
|
-
return (jsxRuntime.jsxs(
|
|
1068
|
+
const TooltipLabel = ({ title, displayValues, className, "data-testid": dataTestId, ref, style, }) => {
|
|
1069
|
+
return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("contents", className), "data-testid": dataTestId, ref: ref, style: style, children: [jsxRuntime.jsx("div", { className: "text-xs font-medium", children: title }), jsxRuntime.jsx(TooltipValues, { values: displayValues })] }));
|
|
1037
1070
|
};
|
|
1038
1071
|
|
|
1039
1072
|
/**
|
|
@@ -1095,8 +1128,7 @@ const MultipleFilterTooltipLabel = ({ filterBarConfig, filterKeys, filters, }) =
|
|
|
1095
1128
|
* @template TFilterBarDefinition - The type representing the filter bar definition.
|
|
1096
1129
|
* @returns {ReactElement} - Returns the FilterMenu component.
|
|
1097
1130
|
*/
|
|
1098
|
-
const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact = false, title, "data-testid": dataTestId = "filters-menu", className, style, buttonProps, allowShowFiltersDirectly = true, includeFilterKeys, }) => {
|
|
1099
|
-
const [t] = useTranslation();
|
|
1131
|
+
const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact = false, title, "data-testid": dataTestId = "filters-menu", className, ref, style, buttonProps, allowShowFiltersDirectly = true, includeFilterKeys, }) => {
|
|
1100
1132
|
const [showCustomFilters, setShowCustomFilters] = react.useState(false);
|
|
1101
1133
|
const { isSm } = reactComponents.useViewportBreakpoints();
|
|
1102
1134
|
const filterBarDefinitionCount = react.useMemo(() => sharedUtils.objectValues(filterBarDefinition).length, [filterBarDefinition]);
|
|
@@ -1117,14 +1149,14 @@ const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [],
|
|
|
1117
1149
|
includeFilterKeys,
|
|
1118
1150
|
allowShowFiltersDirectly,
|
|
1119
1151
|
});
|
|
1120
|
-
return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex items-start gap-2", className), "data-testid": dataTestId, style: style, children: [jsxRuntime.jsx(reactComponents.Popover, { onOpenStateChange: open => {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1152
|
+
return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex items-start gap-2", className), "data-testid": dataTestId, ref: ref, style: style, children: [jsxRuntime.jsx(reactComponents.MenuTree, { children: jsxRuntime.jsx(reactComponents.Popover, { onOpenStateChange: open => {
|
|
1153
|
+
if (!open) {
|
|
1154
|
+
setShowCustomFilters(false);
|
|
1155
|
+
setSearchText("");
|
|
1156
|
+
}
|
|
1157
|
+
}, placement: "bottom-start", children: modalState => {
|
|
1158
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx("div", { className: "shrink-0", "data-testid": "starred-filters-menu-trigger", id: "starred-filters-menu-trigger", children: jsxRuntime.jsx(reactComponents.Tooltip, { disabled: modalState.isOpen, label: jsxRuntime.jsx(MultipleFilterTooltipLabel, { filterBarConfig: filterBarConfig, filters: appliedFilters }), placement: "bottom", children: jsxRuntime.jsx(FiltersMenuTriggerButton, { appliedValuesCount: totalAppliedValuesCount, buttonProps: buttonProps, hasAppliedFilters: filterBarConfig.appliedFilterKeys().length > 0, isCompact: !isSm, title: title }) }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { cellPadding: 100, children: jsxRuntime.jsx(FiltersMenuContent, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, filterBarDefinitionCount: filterBarDefinitionCount, filtersToShowGrouped: filtersToShowGrouped, hasCustomFields: hasCustomFields, removeCustomFieldsGroup: removeCustomFieldsGroup, searchResultsGrouped: searchResultsGrouped, searchText: searchText, setSearchText: setSearchText, setShowCustomFilters: setShowCustomFilters, showCustomFilters: showCustomFilters }) })] }));
|
|
1159
|
+
} }) }), showDirectlyFilters.length > 0 ? (jsxRuntime.jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, compact === false ? (jsxRuntime.jsx(AppliedFiltersRenderer, { appliedFilters: appliedFilters, filterBarConfig: filterBarConfig })) : null] }));
|
|
1128
1160
|
};
|
|
1129
1161
|
|
|
1130
1162
|
/**
|
|
@@ -1132,7 +1164,7 @@ const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [],
|
|
|
1132
1164
|
*
|
|
1133
1165
|
* @returns {ReactElement} - Returns the Filter component.
|
|
1134
1166
|
*/
|
|
1135
|
-
const FilterTableComponent = ({ filterKey, filterBarDefinition, filterBarConfig, }) => {
|
|
1167
|
+
const FilterTableComponent = ({ filterKey, filterBarDefinition, filterBarConfig, className, "data-testid": dataTestId, ref, style, }) => {
|
|
1136
1168
|
const [t] = useTranslation();
|
|
1137
1169
|
const ensureFilterKey = react.useCallback((key) => {
|
|
1138
1170
|
return key;
|
|
@@ -1162,16 +1194,16 @@ const FilterTableComponent = ({ filterKey, filterBarDefinition, filterBarConfig,
|
|
|
1162
1194
|
const activeFilterKeys = filterKey.filter(key => appliedFilterKeys.includes(key));
|
|
1163
1195
|
const multipleFiltersAppliedCount = getManuallyAppliedFilterCount(activeFilterKeys);
|
|
1164
1196
|
const multipleFiltersIsActive = multipleFiltersAppliedCount > 0;
|
|
1165
|
-
return (jsxRuntime.jsx(reactComponents.Popover, { placement: "bottom-start", children: modalState => {
|
|
1166
|
-
|
|
1167
|
-
|
|
1197
|
+
return (jsxRuntime.jsx("div", { className: className, "data-testid": dataTestId, ref: ref, style: style, children: jsxRuntime.jsx(reactComponents.MenuTree, { children: jsxRuntime.jsx(reactComponents.Popover, { placement: "bottom-start", children: modalState => {
|
|
1198
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx("div", { className: "relative", children: jsxRuntime.jsx(reactComponents.Tooltip, { disabled: modalState.isOpen || !multipleFiltersIsActive, label: jsxRuntime.jsx(MultipleFilterTooltipLabel, { filterBarConfig: filterBarConfig, filterKeys: filterKey, filters: filters }), placement: "bottom", children: jsxRuntime.jsx(reactComponents.IconButton, { ariaLabel: t("filtersBar.filtersHeading"), className: "h-8 w-5 p-0", icon: jsxRuntime.jsx(reactComponents.Icon, { name: "Funnel", size: "small" }), variant: multipleFiltersIsActive ? "ghost" : "ghost-neutral" }) }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx("div", { className: reactComponents.cvaMenu({ limitWidth: true }), children: jsxRuntime.jsx("div", { className: reactComponents.cvaMenuList(), children: filters.map((value, index) => value && (jsxRuntime.jsx(FilterComponent, { filter: value, filterBarActions: filterBarConfig, filterState: filterBarConfig, visualStyle: "list-item" }, index))) }) }) })] }));
|
|
1199
|
+
} }) }) }));
|
|
1168
1200
|
}
|
|
1169
1201
|
const filter = filterBarDefinition[ensureFilterKey(filterKey)];
|
|
1170
1202
|
if (!filter) {
|
|
1171
1203
|
return null;
|
|
1172
1204
|
}
|
|
1173
1205
|
const isActive = filterBarConfig.appliedFilterKeys().includes(filterKey);
|
|
1174
|
-
return (jsxRuntime.jsx("div", { onClick: event => event.stopPropagation(), children: jsxRuntime.jsx("div", { className: "relative", children: jsxRuntime.jsx(reactComponents.Tooltip, { disabled: !isActive, label: jsxRuntime.jsx(SingleFilterTooltipLabel, { filter: filter, filterBarConfig: filterBarConfig, filterKey: filterKey }), placement: "bottom", children: jsxRuntime.jsx(FilterComponent, { asIcon: "Funnel", className: "h-8 w-5 p-0", filter: filter, filterBarActions: filterBarConfig, filterState: filterBarConfig }) }) }) }));
|
|
1206
|
+
return (jsxRuntime.jsx("div", { className: className, "data-testid": dataTestId, onClick: event => event.stopPropagation(), ref: ref, style: style, children: jsxRuntime.jsx("div", { className: "relative", children: jsxRuntime.jsx(reactComponents.Tooltip, { disabled: !isActive, label: jsxRuntime.jsx(SingleFilterTooltipLabel, { filter: filter, filterBarConfig: filterBarConfig, filterKey: filterKey }), placement: "bottom", children: jsxRuntime.jsx(FilterComponent, { asIcon: "Funnel", className: "h-8 w-5 p-0", filter: filter, filterBarActions: filterBarConfig, filterState: filterBarConfig }) }) }) }));
|
|
1175
1207
|
};
|
|
1176
1208
|
|
|
1177
1209
|
/**
|
package/index.esm.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
|
|
3
3
|
import { useMemo, useState, useEffect, useCallback, Fragment as Fragment$1, useRef } from 'react';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
4
5
|
import { Filter, FilterBody, RadioFilterItem, CheckBoxFilterItem, FilterHeader as FilterHeader$1, FilterFooter } from '@trackunit/react-filter-components';
|
|
5
|
-
import { Button, Icon, useList, List, Text, useTextSearch,
|
|
6
|
+
import { Button, Icon, useList, List, Text, useTextSearch, cvaMenuList, cvaMenu, ZStack, IconButton, Badge, useViewportBreakpoints, MenuTree, Popover, PopoverTrigger, Tooltip, PopoverContent, useCustomEncoding, useSearchParamSync, useStorageKey, storageSerializer, useWatch } from '@trackunit/react-components';
|
|
6
7
|
import { useAnalytics, useCurrentUser } from '@trackunit/react-core-hooks';
|
|
7
8
|
import { capitalize } from 'string-ts';
|
|
8
|
-
import { twMerge } from 'tailwind-merge';
|
|
9
9
|
import { createEvent } from '@trackunit/iris-app-runtime-core-api';
|
|
10
10
|
import { Search, NumberField, RadioGroup } from '@trackunit/react-form-components';
|
|
11
11
|
import { DayRangePicker } from '@trackunit/react-date-and-time-components';
|
|
@@ -449,9 +449,9 @@ const ResetFiltersButtonWithIcon = ({ resetFiltersToInitialState, "data-testid":
|
|
|
449
449
|
/**
|
|
450
450
|
*
|
|
451
451
|
*/
|
|
452
|
-
const AppliedFiltersRenderer = ({ appliedFilters, filterBarConfig, showResetButton = true, style, }) => {
|
|
452
|
+
const AppliedFiltersRenderer = ({ appliedFilters, filterBarConfig, showResetButton = true, className, "data-testid": dataTestId, ref, style, }) => {
|
|
453
453
|
const filtersToRender = useMemo(() => appliedFilters.filter(filter => !filter.showDirectly), [appliedFilters]);
|
|
454
|
-
return (jsxs("div", { className: "flex flex-wrap items-center gap-2", style: style, children: [filtersToRender.length > 0 ? (jsx("div", { className: "h-4 w-[1px] bg-neutral-300", "data-testid": "applied-filters-buttons" })) : null, jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: filtersToRender }), appliedFilters.length > 0 && showResetButton ? (jsx(ResetFiltersButtonWithIcon, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] }));
|
|
454
|
+
return (jsxs("div", { className: twMerge("flex flex-wrap items-center gap-2", className), "data-testid": dataTestId, ref: ref, style: style, children: [filtersToRender.length > 0 ? (jsx("div", { className: "h-4 w-[1px] bg-neutral-300", "data-testid": "applied-filters-buttons" })) : null, jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: filtersToRender }), appliedFilters.length > 0 && showResetButton ? (jsx(ResetFiltersButtonWithIcon, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] }));
|
|
455
455
|
};
|
|
456
456
|
|
|
457
457
|
/**
|
|
@@ -685,11 +685,11 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
|
|
|
685
685
|
onChange: customSearch?.onChange ?? setSearchText,
|
|
686
686
|
onClear: customSearch?.onClear ?? (() => setSearchText("")),
|
|
687
687
|
count: undefinedCount ? filteredOptions.length - 1 : filteredOptions.length,
|
|
688
|
-
}, children: options.length >= 2 ? (jsx(Button, { className: "place-self-start", "data-testid": "selectAllButton", disabled: hasSelectedAll, onClick: handleSelectAll, size: "small", variant: "ghost", children: t("filtersBar.selectAll") })) : null }), jsxs("div", { className: twMerge("grid
|
|
688
|
+
}, children: options.length >= 2 ? (jsx(Button, { className: "place-self-start", "data-testid": "selectAllButton", disabled: hasSelectedAll, onClick: handleSelectAll, size: "small", variant: "ghost", children: t("filtersBar.selectAll") })) : null }), jsxs("div", { className: twMerge("grid min-h-0 overflow-hidden", undefinedCount ? "grid-rows-[minmax(0,1fr)_auto]" : "grid-rows-[minmax(0,1fr)]"), children: [jsx(FilterResults, { ignoreUndefined: undefinedCount !== null, loading: loading, results: results, children: res => (jsx(DynamicFilterList, { checked: index => {
|
|
689
689
|
return filterDefinition.type === "valueNameArray"
|
|
690
690
|
? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, res[index]?.key || "")
|
|
691
691
|
: filterBarActions.arrayIncludesValue(filterDefinition.filterKey, res[index]?.key || "");
|
|
692
|
-
}, className: "
|
|
692
|
+
}, className: "h-full", count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", onChange: index => {
|
|
693
693
|
const result = res[index];
|
|
694
694
|
if (result) {
|
|
695
695
|
handleSetValue(result);
|
|
@@ -931,10 +931,10 @@ const useFiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters =
|
|
|
931
931
|
*
|
|
932
932
|
* @returns {ReactElement} - Returns the FiltersList component.
|
|
933
933
|
*/
|
|
934
|
-
const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, style, "data-testid": dataTestId = "grouped-filters-list", }) => {
|
|
935
|
-
return (jsx("div", { className: className, "data-testid": dataTestId,
|
|
934
|
+
const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, ref, style, "data-testid": dataTestId = "grouped-filters-list", }) => {
|
|
935
|
+
return (jsx("div", { className: className, "data-testid": dataTestId, ref: ref, style: style, children: filtersGrouped.map((group, idx) => {
|
|
936
936
|
const isLastGroup = idx === filtersGrouped.length - 1;
|
|
937
|
-
return (jsxs("div", { className: "flex flex-col gap-1", children: [jsxs("div", { children: [filtersGrouped.length > 1 && (jsx(Text, { className: "h-7
|
|
937
|
+
return (jsxs("div", { className: "flex flex-col gap-1", children: [jsxs("div", { children: [filtersGrouped.length > 1 && (jsx(Text, { className: "h-7 p-2 text-neutral-400", "data-testid": `${group.key}-group-title`, size: "small", uppercase: true, weight: "bold", children: group.title })), jsx("ul", { "aria-labelledby": `${group.key}-group-title`, className: "grid", "data-testid": `${group.key}-group-list`, children: jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: group.filters, visualStyle: "list-item" }) })] }), isLastGroup ? null : jsx("div", { className: "h-[1px] w-full bg-neutral-200", role: "separator" })] }, group.key));
|
|
938
938
|
}) }));
|
|
939
939
|
};
|
|
940
940
|
|
|
@@ -943,25 +943,38 @@ const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, style,
|
|
|
943
943
|
*
|
|
944
944
|
* @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
|
|
945
945
|
*/
|
|
946
|
-
const ResetFiltersButton = ({ resetFiltersToInitialState, "data-testid": dataTestId, className, }) => {
|
|
946
|
+
const ResetFiltersButton = ({ resetFiltersToInitialState, "data-testid": dataTestId, className, ref, style, }) => {
|
|
947
947
|
const [t] = useTranslation();
|
|
948
948
|
return (jsxs(Button, { className: className, "data-testid": dataTestId ?? "reset-filters-button", onClick: () => {
|
|
949
949
|
resetFiltersToInitialState();
|
|
950
|
-
}, size: "small", variant: "ghost", children: [t("filtersBar.resetFilters"), jsx("span", { className: "sr-only", children: t("filtersBar.resetFiltersSR") })] }));
|
|
950
|
+
}, ref: ref, size: "small", style: style, variant: "ghost", children: [t("filtersBar.resetFilters"), jsx("span", { className: "sr-only", children: t("filtersBar.resetFiltersSR") })] }));
|
|
951
951
|
};
|
|
952
952
|
|
|
953
953
|
/**
|
|
954
954
|
*
|
|
955
955
|
*/
|
|
956
|
-
const FiltersMenuContent = ({ filterBarConfig, setShowCustomFilters, setSearchText, searchText, searchResultsGrouped, filtersToShowGrouped, removeCustomFieldsGroup, hasCustomFields, appliedCustomFields, showCustomFilters, filterBarDefinitionCount, }) => {
|
|
956
|
+
const FiltersMenuContent = ({ filterBarConfig, setShowCustomFilters, setSearchText, searchText, searchResultsGrouped, filtersToShowGrouped, removeCustomFieldsGroup, hasCustomFields, appliedCustomFields, showCustomFilters, filterBarDefinitionCount, className, "data-testid": dataTestId = "starred-filters-menu-popover", ref, style, }) => {
|
|
957
957
|
const [t] = useTranslation();
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
958
|
+
// Chrome-only shell (not `MenuContent`): filter rows are wrapped (`GroupedFiltersList` →
|
|
959
|
+
// `FilterComponent` → `Filter` → `MenuItem`), so `MenuContent`'s direct-child registration would
|
|
960
|
+
// never attach keyboard nav — leaving a `role="menu"` with no owned menuitems. Shadow/layout come
|
|
961
|
+
// from `cvaMenu`/`cvaMenuList`; keyboard ownership returns when MenuItem self-registers (GLU-1644).
|
|
962
|
+
return (jsx("div", { className: cvaMenu({
|
|
963
|
+
className: twMerge("max-h-[min(600px,calc(100dvh-32px))] grid-rows-[minmax(0,1fr)] overflow-y-hidden p-0", className),
|
|
964
|
+
limitWidth: true,
|
|
965
|
+
}), "data-testid": dataTestId, ref: ref, style: style, children: jsxs("div", {
|
|
966
|
+
// `gap-y-0` cancels `cvaMenuList`'s default inter-item gap, which here would otherwise land
|
|
967
|
+
// between the header block (search/count + separator) and the scrollable list below it,
|
|
968
|
+
// showing as a sliver of whitespace above the list's own scrollbar.
|
|
969
|
+
className: cvaMenuList({
|
|
970
|
+
className: "grid-rows-min-fr !max-h-none min-h-0 gap-y-0 !overflow-y-hidden p-0",
|
|
971
|
+
}), children: [jsxs("div", { children: [jsxs("div", { className: "flex flex-col gap-1 p-1", children: [filterBarDefinitionCount > 5 && (jsx(Search, { autoFocus: true, "data-testid": "starred-filters-menu-search", fieldSize: "small", id: "search-filters-list", onChange: e => setSearchText(e.currentTarget.value), onClear: () => setSearchText(""), placeholder: t("filtersBar.searchFiltersPlaceholder"), value: searchText })), jsxs("div", { className: "flex h-7 items-center justify-between gap-1 pl-3", children: [jsx(Text, { className: "text-neutral-400", size: "small", children: jsx(FiltersAppliedCountLabel, { filterBarConfig: filterBarConfig }) }), filterBarConfig.appliedFilterKeys().length > 0 ? (jsx(ResetFiltersButton, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] })] }), jsx(Separator, {})] }), jsxs("div", { className: "flex min-h-0 flex-col gap-1 overflow-auto p-1", children: [jsx(GroupedFiltersList, { className: "flex flex-col gap-1", filterBarConfig: filterBarConfig, filtersGrouped: searchText
|
|
972
|
+
? searchResultsGrouped
|
|
973
|
+
: showCustomFilters
|
|
974
|
+
? filtersToShowGrouped
|
|
975
|
+
: removeCustomFieldsGroup(filtersToShowGrouped) }), hasCustomFields && !showCustomFilters && !searchText ? (jsx(CustomFieldsHiddenGroup, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, onClickShow: () => {
|
|
976
|
+
setShowCustomFilters(true);
|
|
977
|
+
} })) : null] })] }) }));
|
|
965
978
|
};
|
|
966
979
|
const Separator = () => jsx("hr", { className: "border-neutral-200", role: "separator" });
|
|
967
980
|
const FiltersAppliedCountLabel = ({ filterBarConfig, }) => {
|
|
@@ -990,6 +1003,26 @@ const CustomFieldsHiddenGroup = ({ appliedCustomFields, filterBarConfig, onClick
|
|
|
990
1003
|
return (jsxs(Fragment, { children: [jsx(Separator, {}), jsxs("div", { children: [jsx(Button, { "aria-controls": "filters-list", className: "text-primary-600 w-full justify-between px-3", onClick: onClickShow, prefix: jsx(Text, { className: "text-neutral-400", size: "small", uppercase: true, weight: "bold", children: t("filtersBar.groups.CUSTOM_FIELDS") }), size: "small", variant: "ghost-neutral", children: t("filtersBar.showAll") }), appliedCustomFields.length > 0 ? (jsx("ul", { "aria-label": "Visible custom fields", "data-testid": "applied-custom-fields-list", children: jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: appliedCustomFields, visualStyle: "list-item" }) })) : null] })] }));
|
|
991
1004
|
};
|
|
992
1005
|
|
|
1006
|
+
const sharedButtonProps = {
|
|
1007
|
+
size: "small",
|
|
1008
|
+
variant: "secondary",
|
|
1009
|
+
};
|
|
1010
|
+
/**
|
|
1011
|
+
* FiltersMenuTriggerButton is the button that opens the filters popover menu.
|
|
1012
|
+
*
|
|
1013
|
+
* On small viewports (`isCompact`) it renders as a square icon-only button with the applied filters count
|
|
1014
|
+
* overlaid as a badge. Otherwise it renders as a regular button with a text label and an inline count badge.
|
|
1015
|
+
*/
|
|
1016
|
+
const FiltersMenuTriggerButton = ({ isCompact, hasAppliedFilters, appliedValuesCount, title, buttonProps, }) => {
|
|
1017
|
+
const [t] = useTranslation();
|
|
1018
|
+
const label = title ?? t("filtersBar.filtersHeading");
|
|
1019
|
+
const icon = jsx(Icon, { ariaHidden: true, color: hasAppliedFilters ? "primary" : undefined, name: "Funnel", size: "small" });
|
|
1020
|
+
if (isCompact) {
|
|
1021
|
+
return (jsxs(ZStack, { className: "shrink-0", children: [jsx(IconButton, { ariaLabel: label, icon: icon, ...sharedButtonProps, ...buttonProps }), appliedValuesCount > 0 ? (jsx(Badge, { className: "-translate-y-1/3 translate-x-1/3 self-start justify-self-end", color: "primary", count: appliedValuesCount, hideZero: true, size: "condensed" })) : null] }));
|
|
1022
|
+
}
|
|
1023
|
+
return (jsx(Button, { prefix: icon, suffix: appliedValuesCount > 0 ? (jsx(Badge, { color: "primary", count: appliedValuesCount, hideZero: true, size: "condensed" })) : null, ...sharedButtonProps, ...buttonProps, children: title !== "" ? label : null }));
|
|
1024
|
+
};
|
|
1025
|
+
|
|
993
1026
|
/**
|
|
994
1027
|
* TooltipValues component that displays formatted tooltip values based on the provided input.
|
|
995
1028
|
*
|
|
@@ -1030,8 +1063,8 @@ const TooltipValues = ({ values }) => {
|
|
|
1030
1063
|
* @param {any} props.displayValues - The filter values to display.
|
|
1031
1064
|
* @returns {ReactElement} The rendered component showing the title and values.
|
|
1032
1065
|
*/
|
|
1033
|
-
const TooltipLabel = ({ title, displayValues }) => {
|
|
1034
|
-
return (jsxs(
|
|
1066
|
+
const TooltipLabel = ({ title, displayValues, className, "data-testid": dataTestId, ref, style, }) => {
|
|
1067
|
+
return (jsxs("div", { className: twMerge("contents", className), "data-testid": dataTestId, ref: ref, style: style, children: [jsx("div", { className: "text-xs font-medium", children: title }), jsx(TooltipValues, { values: displayValues })] }));
|
|
1035
1068
|
};
|
|
1036
1069
|
|
|
1037
1070
|
/**
|
|
@@ -1093,8 +1126,7 @@ const MultipleFilterTooltipLabel = ({ filterBarConfig, filterKeys, filters, }) =
|
|
|
1093
1126
|
* @template TFilterBarDefinition - The type representing the filter bar definition.
|
|
1094
1127
|
* @returns {ReactElement} - Returns the FilterMenu component.
|
|
1095
1128
|
*/
|
|
1096
|
-
const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact = false, title, "data-testid": dataTestId = "filters-menu", className, style, buttonProps, allowShowFiltersDirectly = true, includeFilterKeys, }) => {
|
|
1097
|
-
const [t] = useTranslation();
|
|
1129
|
+
const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact = false, title, "data-testid": dataTestId = "filters-menu", className, ref, style, buttonProps, allowShowFiltersDirectly = true, includeFilterKeys, }) => {
|
|
1098
1130
|
const [showCustomFilters, setShowCustomFilters] = useState(false);
|
|
1099
1131
|
const { isSm } = useViewportBreakpoints();
|
|
1100
1132
|
const filterBarDefinitionCount = useMemo(() => objectValues(filterBarDefinition).length, [filterBarDefinition]);
|
|
@@ -1115,14 +1147,14 @@ const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [],
|
|
|
1115
1147
|
includeFilterKeys,
|
|
1116
1148
|
allowShowFiltersDirectly,
|
|
1117
1149
|
});
|
|
1118
|
-
return (jsxs("div", { className: twMerge("flex items-start gap-2", className), "data-testid": dataTestId, style: style, children: [jsx(Popover, { onOpenStateChange: open => {
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1150
|
+
return (jsxs("div", { className: twMerge("flex items-start gap-2", className), "data-testid": dataTestId, ref: ref, style: style, children: [jsx(MenuTree, { children: jsx(Popover, { onOpenStateChange: open => {
|
|
1151
|
+
if (!open) {
|
|
1152
|
+
setShowCustomFilters(false);
|
|
1153
|
+
setSearchText("");
|
|
1154
|
+
}
|
|
1155
|
+
}, placement: "bottom-start", children: modalState => {
|
|
1156
|
+
return (jsxs(Fragment, { children: [jsx(PopoverTrigger, { children: jsx("div", { className: "shrink-0", "data-testid": "starred-filters-menu-trigger", id: "starred-filters-menu-trigger", children: jsx(Tooltip, { disabled: modalState.isOpen, label: jsx(MultipleFilterTooltipLabel, { filterBarConfig: filterBarConfig, filters: appliedFilters }), placement: "bottom", children: jsx(FiltersMenuTriggerButton, { appliedValuesCount: totalAppliedValuesCount, buttonProps: buttonProps, hasAppliedFilters: filterBarConfig.appliedFilterKeys().length > 0, isCompact: !isSm, title: title }) }) }) }), jsx(PopoverContent, { cellPadding: 100, children: jsx(FiltersMenuContent, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, filterBarDefinitionCount: filterBarDefinitionCount, filtersToShowGrouped: filtersToShowGrouped, hasCustomFields: hasCustomFields, removeCustomFieldsGroup: removeCustomFieldsGroup, searchResultsGrouped: searchResultsGrouped, searchText: searchText, setSearchText: setSearchText, setShowCustomFilters: setShowCustomFilters, showCustomFilters: showCustomFilters }) })] }));
|
|
1157
|
+
} }) }), showDirectlyFilters.length > 0 ? (jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, compact === false ? (jsx(AppliedFiltersRenderer, { appliedFilters: appliedFilters, filterBarConfig: filterBarConfig })) : null] }));
|
|
1126
1158
|
};
|
|
1127
1159
|
|
|
1128
1160
|
/**
|
|
@@ -1130,7 +1162,7 @@ const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [],
|
|
|
1130
1162
|
*
|
|
1131
1163
|
* @returns {ReactElement} - Returns the Filter component.
|
|
1132
1164
|
*/
|
|
1133
|
-
const FilterTableComponent = ({ filterKey, filterBarDefinition, filterBarConfig, }) => {
|
|
1165
|
+
const FilterTableComponent = ({ filterKey, filterBarDefinition, filterBarConfig, className, "data-testid": dataTestId, ref, style, }) => {
|
|
1134
1166
|
const [t] = useTranslation();
|
|
1135
1167
|
const ensureFilterKey = useCallback((key) => {
|
|
1136
1168
|
return key;
|
|
@@ -1160,16 +1192,16 @@ const FilterTableComponent = ({ filterKey, filterBarDefinition, filterBarConfig,
|
|
|
1160
1192
|
const activeFilterKeys = filterKey.filter(key => appliedFilterKeys.includes(key));
|
|
1161
1193
|
const multipleFiltersAppliedCount = getManuallyAppliedFilterCount(activeFilterKeys);
|
|
1162
1194
|
const multipleFiltersIsActive = multipleFiltersAppliedCount > 0;
|
|
1163
|
-
return (jsx(Popover, { placement: "bottom-start", children: modalState => {
|
|
1164
|
-
|
|
1165
|
-
|
|
1195
|
+
return (jsx("div", { className: className, "data-testid": dataTestId, ref: ref, style: style, children: jsx(MenuTree, { children: jsx(Popover, { placement: "bottom-start", children: modalState => {
|
|
1196
|
+
return (jsxs(Fragment, { children: [jsx(PopoverTrigger, { children: jsx("div", { className: "relative", children: jsx(Tooltip, { disabled: modalState.isOpen || !multipleFiltersIsActive, label: jsx(MultipleFilterTooltipLabel, { filterBarConfig: filterBarConfig, filterKeys: filterKey, filters: filters }), placement: "bottom", children: jsx(IconButton, { ariaLabel: t("filtersBar.filtersHeading"), className: "h-8 w-5 p-0", icon: jsx(Icon, { name: "Funnel", size: "small" }), variant: multipleFiltersIsActive ? "ghost" : "ghost-neutral" }) }) }) }), jsx(PopoverContent, { children: jsx("div", { className: cvaMenu({ limitWidth: true }), children: jsx("div", { className: cvaMenuList(), children: filters.map((value, index) => value && (jsx(FilterComponent, { filter: value, filterBarActions: filterBarConfig, filterState: filterBarConfig, visualStyle: "list-item" }, index))) }) }) })] }));
|
|
1197
|
+
} }) }) }));
|
|
1166
1198
|
}
|
|
1167
1199
|
const filter = filterBarDefinition[ensureFilterKey(filterKey)];
|
|
1168
1200
|
if (!filter) {
|
|
1169
1201
|
return null;
|
|
1170
1202
|
}
|
|
1171
1203
|
const isActive = filterBarConfig.appliedFilterKeys().includes(filterKey);
|
|
1172
|
-
return (jsx("div", { onClick: event => event.stopPropagation(), children: jsx("div", { className: "relative", children: jsx(Tooltip, { disabled: !isActive, label: jsx(SingleFilterTooltipLabel, { filter: filter, filterBarConfig: filterBarConfig, filterKey: filterKey }), placement: "bottom", children: jsx(FilterComponent, { asIcon: "Funnel", className: "h-8 w-5 p-0", filter: filter, filterBarActions: filterBarConfig, filterState: filterBarConfig }) }) }) }));
|
|
1204
|
+
return (jsx("div", { className: className, "data-testid": dataTestId, onClick: event => event.stopPropagation(), ref: ref, style: style, children: jsx("div", { className: "relative", children: jsx(Tooltip, { disabled: !isActive, label: jsx(SingleFilterTooltipLabel, { filter: filter, filterBarConfig: filterBarConfig, filterKey: filterKey }), placement: "bottom", children: jsx(FilterComponent, { asIcon: "Funnel", className: "h-8 w-5 p-0", filter: filter, filterBarActions: filterBarConfig, filterState: filterBarConfig }) }) }) }));
|
|
1173
1205
|
};
|
|
1174
1206
|
|
|
1175
1207
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/filters-filter-bar",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.7-alpha-770d9994af7.0",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
"tailwind-merge": "^2.0.0",
|
|
12
12
|
"string-ts": "^2.0.0",
|
|
13
13
|
"zod": "^3.25.76",
|
|
14
|
-
"@trackunit/iris-app-api": "2.3.
|
|
15
|
-
"@trackunit/react-core-hooks": "1.20.
|
|
16
|
-
"@trackunit/react-filter-components": "2.4.
|
|
17
|
-
"@trackunit/react-date-and-time-components": "2.4.
|
|
18
|
-
"@trackunit/shared-utils": "1.16.
|
|
19
|
-
"@trackunit/react-form-components": "2.4.
|
|
20
|
-
"@trackunit/iris-app-runtime-core-api": "1.17.
|
|
21
|
-
"@trackunit/geo-json-utils": "1.15.
|
|
22
|
-
"@trackunit/i18n-library-translation": "2.3.
|
|
23
|
-
"@trackunit/css-class-variance-utilities": "1.14.
|
|
24
|
-
"@trackunit/react-components": "2.
|
|
14
|
+
"@trackunit/iris-app-api": "2.3.4-alpha-770d9994af7.0",
|
|
15
|
+
"@trackunit/react-core-hooks": "1.20.4-alpha-770d9994af7.0",
|
|
16
|
+
"@trackunit/react-filter-components": "2.4.7-alpha-770d9994af7.0",
|
|
17
|
+
"@trackunit/react-date-and-time-components": "2.4.7-alpha-770d9994af7.0",
|
|
18
|
+
"@trackunit/shared-utils": "1.16.3-alpha-770d9994af7.0",
|
|
19
|
+
"@trackunit/react-form-components": "2.4.7-alpha-770d9994af7.0",
|
|
20
|
+
"@trackunit/iris-app-runtime-core-api": "1.17.3-alpha-770d9994af7.0",
|
|
21
|
+
"@trackunit/geo-json-utils": "1.15.3-alpha-770d9994af7.0",
|
|
22
|
+
"@trackunit/i18n-library-translation": "2.3.4-alpha-770d9994af7.0",
|
|
23
|
+
"@trackunit/css-class-variance-utilities": "1.14.3-alpha-770d9994af7.0",
|
|
24
|
+
"@trackunit/react-components": "2.6.2-alpha-770d9994af7.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"@apollo/client": "^3.13.8",
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { CommonProps, Refable, type Styleable } from "@trackunit/react-components";
|
|
2
2
|
import { ReactElement } from "react";
|
|
3
3
|
import { FilterBarConfig, FilterBarDefinition, FilterDefinition, FilterMapActions, FilterMapGetter } from "../types/FilterTypes";
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
export declare const AppliedFiltersRenderer: <TFilterBarDefinition extends FilterBarDefinition>({ appliedFilters, filterBarConfig, showResetButton, style, }: {
|
|
4
|
+
interface AppliedFiltersRendererProps<TFilterBarDefinition extends FilterBarDefinition> extends CommonProps, Refable<HTMLDivElement>, Styleable {
|
|
8
5
|
appliedFilters: Array<FilterDefinition>;
|
|
9
6
|
filterBarConfig: FilterBarConfig<TFilterBarDefinition> & FilterMapActions & FilterMapGetter;
|
|
10
7
|
showResetButton?: boolean;
|
|
11
|
-
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
export declare const AppliedFiltersRenderer: <TFilterBarDefinition extends FilterBarDefinition>({ appliedFilters, filterBarConfig, showResetButton, className, "data-testid": dataTestId, ref, style, }: AppliedFiltersRendererProps<TFilterBarDefinition>) => ReactElement;
|
|
13
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { CommonProps, Refable, type Styleable } from "@trackunit/react-components";
|
|
1
2
|
import { ReactElement } from "react";
|
|
2
3
|
import { FilterBarDefinition, FilterMapActions, FilterMapGetter, FilterState } from "../types/FilterTypes";
|
|
3
|
-
interface FilterComponentProps<TFilterBarDefinition extends FilterBarDefinition> {
|
|
4
|
+
interface FilterComponentProps<TFilterBarDefinition extends FilterBarDefinition> extends CommonProps, Refable<HTMLDivElement>, Styleable {
|
|
4
5
|
/**
|
|
5
6
|
* The definition of the filter.
|
|
6
7
|
*/
|
|
@@ -19,5 +20,5 @@ interface FilterComponentProps<TFilterBarDefinition extends FilterBarDefinition>
|
|
|
19
20
|
*
|
|
20
21
|
* @returns {ReactElement} - Returns the Filter component.
|
|
21
22
|
*/
|
|
22
|
-
export declare const FilterTableComponent: <TFilterBarDefinition extends FilterBarDefinition>({ filterKey, filterBarDefinition, filterBarConfig, }: FilterComponentProps<TFilterBarDefinition>) => ReactElement | null;
|
|
23
|
+
export declare const FilterTableComponent: <TFilterBarDefinition extends FilterBarDefinition>({ filterKey, filterBarDefinition, filterBarConfig, className, "data-testid": dataTestId, ref, style, }: FilterComponentProps<TFilterBarDefinition>) => ReactElement | null;
|
|
23
24
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { CommonProps, Refable, type Styleable } from "@trackunit/react-components";
|
|
1
2
|
import { ReactElement } from "react";
|
|
2
3
|
import { FilterValueType } from "../../types/FilterTypes";
|
|
3
|
-
interface TooltipLabelProps {
|
|
4
|
+
interface TooltipLabelProps extends CommonProps, Refable<HTMLDivElement>, Styleable {
|
|
4
5
|
title: string;
|
|
5
6
|
displayValues: FilterValueType;
|
|
6
7
|
}
|
|
@@ -12,5 +13,5 @@ interface TooltipLabelProps {
|
|
|
12
13
|
* @param {any} props.displayValues - The filter values to display.
|
|
13
14
|
* @returns {ReactElement} The rendered component showing the title and values.
|
|
14
15
|
*/
|
|
15
|
-
export declare const TooltipLabel: ({ title, displayValues }: TooltipLabelProps) => ReactElement;
|
|
16
|
+
export declare const TooltipLabel: ({ title, displayValues, className, "data-testid": dataTestId, ref, style, }: TooltipLabelProps) => ReactElement;
|
|
16
17
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ButtonProps, CommonProps, type Styleable } from "@trackunit/react-components";
|
|
1
|
+
import { ButtonProps, CommonProps, Refable, type Styleable } from "@trackunit/react-components";
|
|
2
2
|
import { ReactElement } from "react";
|
|
3
3
|
import { FilterBarConfig, FilterBarDefinition, FilterMapActions, FilterMapGetter } from "../types/FilterTypes";
|
|
4
|
-
interface FiltersMenuProps<TFilterBarDefinition extends FilterBarDefinition> extends CommonProps, Styleable {
|
|
4
|
+
interface FiltersMenuProps<TFilterBarDefinition extends FilterBarDefinition> extends CommonProps, Refable<HTMLDivElement>, Styleable {
|
|
5
5
|
/**
|
|
6
6
|
* If true, the filters marked as showDirectly will not be shown directly in the filter bar
|
|
7
7
|
*/
|
|
@@ -41,5 +41,5 @@ interface FiltersMenuProps<TFilterBarDefinition extends FilterBarDefinition> ext
|
|
|
41
41
|
* @template TFilterBarDefinition - The type representing the filter bar definition.
|
|
42
42
|
* @returns {ReactElement} - Returns the FilterMenu component.
|
|
43
43
|
*/
|
|
44
|
-
export declare const FiltersMenu: <TFilterBarDefinition extends FilterBarDefinition>({ filterBarDefinition, filterBarConfig, hiddenFilters, compact, title, "data-testid": dataTestId, className, style, buttonProps, allowShowFiltersDirectly, includeFilterKeys, }: FiltersMenuProps<TFilterBarDefinition>) => ReactElement;
|
|
44
|
+
export declare const FiltersMenu: <TFilterBarDefinition extends FilterBarDefinition>({ filterBarDefinition, filterBarConfig, hiddenFilters, compact, title, "data-testid": dataTestId, className, ref, style, buttonProps, allowShowFiltersDirectly, includeFilterKeys, }: FiltersMenuProps<TFilterBarDefinition>) => ReactElement;
|
|
45
45
|
export {};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import { CommonProps, Refable, type Styleable } from "@trackunit/react-components";
|
|
1
2
|
import { ReactElement } from "react";
|
|
2
3
|
import { GroupOfFilters } from "../hooks/useGroupFilters";
|
|
3
4
|
import { FilterBarConfig, FilterBarDefinition, FilterDefinition, FilterMapActions, FilterMapGetter } from "../types/FilterTypes";
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
7
|
-
export declare const FiltersMenuContent: ({ filterBarConfig, setShowCustomFilters, setSearchText, searchText, searchResultsGrouped, filtersToShowGrouped, removeCustomFieldsGroup, hasCustomFields, appliedCustomFields, showCustomFilters, filterBarDefinitionCount, }: {
|
|
5
|
+
interface FiltersMenuContentProps extends CommonProps, Refable<HTMLDivElement>, Styleable {
|
|
8
6
|
filterBarConfig: FilterBarConfig<FilterBarDefinition> & FilterMapActions & FilterMapGetter;
|
|
9
7
|
setShowCustomFilters: (show: boolean) => void;
|
|
10
8
|
setSearchText: (text: string) => void;
|
|
@@ -16,4 +14,9 @@ export declare const FiltersMenuContent: ({ filterBarConfig, setShowCustomFilter
|
|
|
16
14
|
appliedCustomFields: Array<FilterDefinition>;
|
|
17
15
|
showCustomFilters: boolean;
|
|
18
16
|
filterBarDefinitionCount: number;
|
|
19
|
-
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
export declare const FiltersMenuContent: ({ filterBarConfig, setShowCustomFilters, setSearchText, searchText, searchResultsGrouped, filtersToShowGrouped, removeCustomFieldsGroup, hasCustomFields, appliedCustomFields, showCustomFilters, filterBarDefinitionCount, className, "data-testid": dataTestId, ref, style, }: FiltersMenuContentProps) => ReactElement;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ButtonProps } from "@trackunit/react-components";
|
|
2
|
+
import { ReactElement } from "react";
|
|
3
|
+
interface FiltersMenuTriggerButtonProps {
|
|
4
|
+
/**
|
|
5
|
+
* If true, renders a square icon-only button (used on small viewports) instead of the full button with a text label.
|
|
6
|
+
*/
|
|
7
|
+
isCompact: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Whether any filters are currently applied. Highlights the funnel icon when true.
|
|
10
|
+
*/
|
|
11
|
+
hasAppliedFilters: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* The number of applied filter values, shown as a count badge on the button.
|
|
14
|
+
*/
|
|
15
|
+
appliedValuesCount: number;
|
|
16
|
+
/**
|
|
17
|
+
* The title of the filter bar, default is "Filters" (translated). Pass an empty string to render no label.
|
|
18
|
+
*/
|
|
19
|
+
title?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Props to override the trigger button's look and feel.
|
|
22
|
+
*/
|
|
23
|
+
buttonProps?: ButtonProps;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* FiltersMenuTriggerButton is the button that opens the filters popover menu.
|
|
27
|
+
*
|
|
28
|
+
* On small viewports (`isCompact`) it renders as a square icon-only button with the applied filters count
|
|
29
|
+
* overlaid as a badge. Otherwise it renders as a regular button with a text label and an inline count badge.
|
|
30
|
+
*/
|
|
31
|
+
export declare const FiltersMenuTriggerButton: ({ isCompact, hasAppliedFilters, appliedValuesCount, title, buttonProps, }: FiltersMenuTriggerButtonProps) => ReactElement;
|
|
32
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CommonProps, type Styleable } from "@trackunit/react-components";
|
|
1
|
+
import { CommonProps, Refable, type Styleable } from "@trackunit/react-components";
|
|
2
2
|
import { ReactElement } from "react";
|
|
3
3
|
import { GroupOfFilters } from "../hooks/useGroupFilters";
|
|
4
4
|
import { FilterBarConfig, FilterBarDefinition, FilterMapActions, FilterMapGetter } from "../types/FilterTypes";
|
|
5
|
-
interface FiltersListProps extends CommonProps, Styleable {
|
|
5
|
+
interface FiltersListProps extends CommonProps, Refable<HTMLDivElement>, Styleable {
|
|
6
6
|
/**
|
|
7
7
|
* The configuration of the filter bar, including its actions and state.
|
|
8
8
|
*/
|
|
@@ -14,5 +14,5 @@ interface FiltersListProps extends CommonProps, Styleable {
|
|
|
14
14
|
*
|
|
15
15
|
* @returns {ReactElement} - Returns the FiltersList component.
|
|
16
16
|
*/
|
|
17
|
-
export declare const GroupedFiltersList: ({ filterBarConfig, filtersGrouped, className, style, "data-testid": dataTestId, }: FiltersListProps) => ReactElement;
|
|
17
|
+
export declare const GroupedFiltersList: ({ filterBarConfig, filtersGrouped, className, ref, style, "data-testid": dataTestId, }: FiltersListProps) => ReactElement;
|
|
18
18
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CommonProps } from "@trackunit/react-components";
|
|
1
|
+
import { CommonProps, Refable, type Styleable } from "@trackunit/react-components";
|
|
2
2
|
import { ReactElement } from "react";
|
|
3
|
-
interface ResetFiltersProps extends CommonProps {
|
|
3
|
+
interface ResetFiltersProps extends CommonProps, Refable<HTMLButtonElement>, Styleable {
|
|
4
4
|
/**
|
|
5
5
|
* A function that resets the filters to their initial state.
|
|
6
6
|
*/
|
|
@@ -11,5 +11,5 @@ interface ResetFiltersProps extends CommonProps {
|
|
|
11
11
|
*
|
|
12
12
|
* @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
|
|
13
13
|
*/
|
|
14
|
-
export declare const ResetFiltersButton: ({ resetFiltersToInitialState, "data-testid": dataTestId, className, }: ResetFiltersProps) => ReactElement | null;
|
|
14
|
+
export declare const ResetFiltersButton: ({ resetFiltersToInitialState, "data-testid": dataTestId, className, ref, style, }: ResetFiltersProps) => ReactElement | null;
|
|
15
15
|
export {};
|