@trackunit/filters-filter-bar 1.3.198 → 1.3.201

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.
Files changed (46) hide show
  1. package/index.cjs.js +146 -80
  2. package/index.esm.js +145 -82
  3. package/package.json +7 -7
  4. package/src/lib/FilterBar.d.ts +5 -1
  5. package/src/lib/components/FilterButtonTooltipLabel.d.ts +7 -0
  6. package/src/lib/components/FiltersMenu.d.ts +15 -3
  7. package/src/lib/components/FiltersMenuContent.d.ts +17 -0
  8. package/src/lib/components/index.d.ts +2 -0
  9. package/src/lib/hooks/useFiltersMenu.d.ts +25 -0
  10. package/src/lib/index.d.ts +1 -0
  11. package/src/lib/types/FilterTypes.d.ts +1 -1
  12. package/src/translation.d.ts +2 -2
  13. package/translation.cjs.js +1 -0
  14. package/translation.cjs10.js +1 -0
  15. package/translation.cjs11.js +1 -0
  16. package/translation.cjs12.js +1 -0
  17. package/translation.cjs13.js +1 -0
  18. package/translation.cjs14.js +1 -0
  19. package/translation.cjs15.js +1 -0
  20. package/translation.cjs16.js +1 -0
  21. package/translation.cjs17.js +1 -0
  22. package/translation.cjs2.js +1 -0
  23. package/translation.cjs3.js +1 -0
  24. package/translation.cjs4.js +1 -0
  25. package/translation.cjs5.js +1 -0
  26. package/translation.cjs6.js +1 -0
  27. package/translation.cjs7.js +1 -0
  28. package/translation.cjs8.js +1 -0
  29. package/translation.cjs9.js +1 -0
  30. package/translation.esm.js +1 -0
  31. package/translation.esm10.js +1 -0
  32. package/translation.esm11.js +1 -0
  33. package/translation.esm12.js +1 -0
  34. package/translation.esm13.js +1 -0
  35. package/translation.esm14.js +1 -0
  36. package/translation.esm15.js +1 -0
  37. package/translation.esm16.js +1 -0
  38. package/translation.esm17.js +1 -0
  39. package/translation.esm2.js +1 -0
  40. package/translation.esm3.js +1 -0
  41. package/translation.esm4.js +1 -0
  42. package/translation.esm5.js +1 -0
  43. package/translation.esm6.js +1 -0
  44. package/translation.esm7.js +1 -0
  45. package/translation.esm8.js +1 -0
  46. package/translation.esm9.js +1 -0
package/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
3
- import { VirtualizedList, Text, Button, useViewportBreakpoints, Popover, PopoverTrigger, Tooltip, Icon, PopoverContent, Card, CardBody, IconButton, MenuList } from '@trackunit/react-components';
3
+ import { VirtualizedList, Text, Button, Card, CardBody, useViewportBreakpoints, Popover, PopoverTrigger, Tooltip, Icon, PopoverContent, IconButton, MenuList } from '@trackunit/react-components';
4
4
  import { useAnalytics, useTextSearch, useCurrentUser } from '@trackunit/react-core-hooks';
5
5
  import { FilterBody, RadioFilterItem, CheckBoxFilterItem, FilterHeader as FilterHeader$1, FilterFooter, Filter } from '@trackunit/react-filter-components';
6
6
  import { useRef, useMemo, useState, useEffect, useCallback, Fragment as Fragment$1 } from 'react';
@@ -130,6 +130,7 @@ var defaultTranslations = {
130
130
  "filtersBar.groups.ASSET": "Asset",
131
131
  "filtersBar.groups.CUSTOM_FIELDS": "Custom Fields",
132
132
  "filtersBar.groups.CUSTOMERS": "Customers",
133
+ "filtersBar.groups.FLEET_HEALTH": "Fleet Health",
133
134
  "filtersBar.groups.INTEGRATION": "Integration",
134
135
  "filtersBar.groups.METADATA": "Metadata",
135
136
  "filtersBar.groups.MY_NETWORK": "My Network",
@@ -642,6 +643,25 @@ const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loadi
642
643
  }, value: selectedRadioId?.key || "", children: jsx(DynamicFilterList, { checked: index => filterBarActions.objectIncludesValue(filterDefinition.filterKey, res[index]?.key || ""), className: "m-1 mt-0", count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
643
644
  };
644
645
 
646
+ /**
647
+ * Tooltip label for the filter button
648
+ */
649
+ const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
650
+ const [t] = useTranslation();
651
+ switch (filterBarConfig.appliedFilterKeys().length) {
652
+ case 0:
653
+ return t("filtersBar.appliedFiltersTooltip.none");
654
+ case 1:
655
+ return filterBarConfig.appliedFilterKeys()[0]
656
+ ? t("filtersBar.appliedFiltersTooltip.singular", {
657
+ filterName: filterBarConfig.getFilterTitle(filterBarConfig.appliedFilterKeys()[0]),
658
+ })
659
+ : null; // should never happen though
660
+ default:
661
+ return (jsxs(Fragment, { children: [t("filtersBar.appliedFiltersTooltip.plural", { count: filterBarConfig.appliedFilterKeys().length }), jsx("ul", { className: "list-inside", children: filterBarConfig.appliedFilterKeys().map((appliedFilterKey, index) => (jsx("li", { className: "list-disc", children: filterBarConfig.getFilterTitle(appliedFilterKey) }, index))) })] }));
662
+ }
663
+ };
664
+
645
665
  /**
646
666
  * Returns the two first values, appends counter if more.
647
667
  *
@@ -757,6 +777,64 @@ const useGroupFilters = (filterDefinitions, hiddenFilters) => {
757
777
  };
758
778
  const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
759
779
 
780
+ /**
781
+ * This hook is used to manage the filters menu.
782
+ * It returns the filters that should be shown in the menu, the filters that should be shown directly in the filter bar,
783
+ * and the filters that should be shown in the search results.
784
+ */
785
+ const useFiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], allowShowFiltersDirectly = true, }) => {
786
+ const hideInMenu = useMemo(() => {
787
+ return objectValues(filterBarDefinition)
788
+ .map(filter => {
789
+ const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
790
+ const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
791
+ const showMenuAnywayBecauseFilterHasChanged = filterBarConfig.appliedFilterKeys().includes(filter.filterKey);
792
+ return (!showInFilterBar || !showInStarredMenu) && !showMenuAnywayBecauseFilterHasChanged
793
+ ? filter.filterKey
794
+ : null;
795
+ })
796
+ .filter(truthy);
797
+ }, [filterBarConfig, filterBarDefinition]);
798
+ const removeCustomFieldsGroup = useCallback((groupOfFilters) => groupOfFilters.filter(group => group.key !== "CUSTOM_FIELDS"), []);
799
+ const { filtersGrouped } = useGroupFilters(objectValues(filterBarDefinition), [...hideInMenu, ...hiddenFilters]);
800
+ const { appliedFilters, filtersToShow, showDirectlyFilters, hasCustomFields } = useMemo(() => {
801
+ const allFilters = filtersGrouped.map(group => group.filters).flat();
802
+ return {
803
+ appliedFilters: allFilters.filter(filter => filterBarConfig.appliedFilterKeys().includes(filter.filterKey)),
804
+ filtersToShow: allFilters.filter(filter => !filter.showDirectly),
805
+ showDirectlyFilters: allFilters.filter(filter => filter.showDirectly && allowShowFiltersDirectly),
806
+ hasCustomFields: allFilters.some(filter => filter.group === "CUSTOM_FIELDS"),
807
+ };
808
+ }, [filterBarConfig, filtersGrouped, allowShowFiltersDirectly]);
809
+ const [searchResults, searchText, setSearchText] = useTextSearch(filtersToShow, item => [item.title]);
810
+ const { filtersGrouped: searchResultsGrouped } = useGroupFilters(searchResults, []);
811
+ const { filtersGrouped: filtersToShowGrouped } = useGroupFilters(filtersToShow, []);
812
+ const appliedCustomFields = useMemo(() => appliedFilters.filter(filter => filter.group === "CUSTOM_FIELDS"), [appliedFilters]);
813
+ return useMemo(() => {
814
+ return {
815
+ appliedFilters,
816
+ hasCustomFields,
817
+ showDirectlyFilters,
818
+ appliedCustomFields,
819
+ searchText,
820
+ setSearchText,
821
+ filtersToShowGrouped,
822
+ searchResultsGrouped,
823
+ removeCustomFieldsGroup,
824
+ };
825
+ }, [
826
+ appliedFilters,
827
+ hasCustomFields,
828
+ showDirectlyFilters,
829
+ appliedCustomFields,
830
+ searchText,
831
+ setSearchText,
832
+ filtersToShowGrouped,
833
+ searchResultsGrouped,
834
+ removeCustomFieldsGroup,
835
+ ]);
836
+ };
837
+
760
838
  /**
761
839
  * FiltersRenderer renders an array of Filter components from filter definitions
762
840
  * It ignores hidden filters.
@@ -777,6 +855,60 @@ const FiltersRenderer = ({ filters, filterBarConfig, visualStyle, }) => {
777
855
  .map(filter => (jsx(FilterComponent, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters }, visualStyle: visualStyle }, `filter-${filter.filterKey}`)));
778
856
  };
779
857
 
858
+ /**
859
+ * FiltersList is a React component that displays a list of filters within a filter bar.
860
+ *
861
+ * @returns {ReactElement} - Returns the FiltersList component.
862
+ */
863
+ const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, dataTestId = "grouped-filters-list", }) => {
864
+ return (jsx("div", { className: className, "data-testid": dataTestId, role: "menu", children: filtersGrouped.map((group, idx) => {
865
+ const isLastGroup = idx === filtersGrouped.length - 1;
866
+ return (jsxs("div", { className: "flex flex-col gap-1", children: [jsxs("div", { children: [jsx(Text, { className: "text-secondary-400 h-7 px-3 py-2", dataTestId: `${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: "bg-secondary-200 h-[1px] w-full", role: "separator" })] }, group.key));
867
+ }) }));
868
+ };
869
+
870
+ /**
871
+ * ResetFiltersButton is a React component that provides a button for resetting filters.
872
+ *
873
+ * @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
874
+ */
875
+ const ResetFiltersButton = ({ resetFiltersToInitialState, dataTestId, className, }) => {
876
+ const [t] = useTranslation();
877
+ return (jsxs(Button, { className: className, dataTestId: dataTestId ?? "reset-filters-button", onClick: () => {
878
+ resetFiltersToInitialState();
879
+ }, size: "small", variant: "ghost", children: [t("filtersBar.resetFilters"), jsx("span", { className: "sr-only", children: "Resets all applied filters" })] }));
880
+ };
881
+
882
+ /**
883
+ *
884
+ */
885
+ const FiltersMenuContent = ({ filterBarConfig, setShowCustomFilters, setSearchText, searchText, searchResultsGrouped, filtersToShowGrouped, removeCustomFieldsGroup, hasCustomFields, appliedCustomFields, showCustomFilters, }) => {
886
+ const [t] = useTranslation();
887
+ return (jsxs(Card, { className: "max-h-[min(600px,_calc(100dvh-32px))] w-[300px] overflow-y-hidden", dataTestId: "starred-filters-menu-popover", children: [jsxs("div", { className: " flex-col gap-1 p-1", children: [jsx(Search, { autoFocus: true, dataTestId: "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 px-3", children: [jsx(Text, { className: "text-secondary-400", size: "small", children: jsx(FiltersAppliedCountLabel, { filterBarConfig: filterBarConfig }) }), filterBarConfig.appliedFilterKeys().length > 0 ? (jsx(ResetFiltersButton, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] })] }), jsx(Separator, {}), jsxs(CardBody, { className: "gap-1 p-1", density: "none", disableGap: true, children: [jsx(GroupedFiltersList, { className: "flex flex-col gap-1", filterBarConfig: filterBarConfig, filtersGrouped: searchText
888
+ ? searchResultsGrouped
889
+ : showCustomFilters
890
+ ? filtersToShowGrouped
891
+ : removeCustomFieldsGroup(filtersToShowGrouped) }), hasCustomFields && !showCustomFilters && !searchText ? (jsx(CustomFieldsHiddenGroup, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, onShow: () => {
892
+ setShowCustomFilters(true);
893
+ } })) : null] })] }));
894
+ };
895
+ const Separator = () => jsx("hr", { className: "border-secondary-200", role: "separator" });
896
+ const FiltersAppliedCountLabel = ({ filterBarConfig, }) => {
897
+ const [t] = useTranslation();
898
+ switch (filterBarConfig.appliedFilterKeys().length) {
899
+ case 0:
900
+ return t("filtersBar.appliedFiltersTooltip.none");
901
+ case 1:
902
+ return filterBarConfig.appliedFilterKeys()[0] ? t("filtersMenu.appliedFiltersLabel.singular") : null;
903
+ default:
904
+ return jsx(Fragment, { children: t("filtersMenu.appliedFiltersLabel.plural", { count: filterBarConfig.appliedFilterKeys().length }) });
905
+ }
906
+ };
907
+ const CustomFieldsHiddenGroup = ({ appliedCustomFields, filterBarConfig, onShow, }) => {
908
+ const [t] = useTranslation();
909
+ 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: onShow, prefix: jsx(Text, { className: "text-secondary-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] })] }));
910
+ };
911
+
780
912
  /**
781
913
  * TooltipValues component that displays formatted tooltip values based on the provided input.
782
914
  *
@@ -870,100 +1002,31 @@ const MultipleFilterTooltipLabel = ({ filterBarConfig, filterKeys, }) => {
870
1002
  }
871
1003
  };
872
1004
 
873
- /**
874
- * FiltersList is a React component that displays a list of filters within a filter bar.
875
- *
876
- * @returns {ReactElement} - Returns the FiltersList component.
877
- */
878
- const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, dataTestId = "grouped-filters-list", }) => {
879
- return (jsx("div", { className: className, "data-testid": dataTestId, role: "menu", children: filtersGrouped.map((group, idx) => {
880
- const isLastGroup = idx === filtersGrouped.length - 1;
881
- return (jsxs("div", { className: "flex flex-col gap-1", children: [jsxs("div", { children: [jsx(Text, { className: "text-secondary-400 h-7 px-3 py-2", dataTestId: `${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: "bg-secondary-200 h-[1px] w-full", role: "separator" })] }, group.key));
882
- }) }));
883
- };
884
-
885
- /**
886
- * ResetFiltersButton is a React component that provides a button for resetting filters.
887
- *
888
- * @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
889
- */
890
- const ResetFiltersButton = ({ resetFiltersToInitialState, dataTestId, className, }) => {
891
- const [t] = useTranslation();
892
- return (jsxs(Button, { className: className, dataTestId: dataTestId ?? "reset-filters-button", onClick: () => {
893
- resetFiltersToInitialState();
894
- }, size: "small", variant: "ghost", children: [t("filtersBar.resetFilters"), jsx("span", { className: "sr-only", children: "Resets all applied filters" })] }));
895
- };
896
-
897
1005
  /**
898
1006
  * FilterMenu is a React component that displays a list of filters in a popover menu based on the provided filter bar configuration.
899
1007
  *
900
1008
  * @template TFilterBarDefinition - The type representing the filter bar definition.
901
1009
  * @returns {ReactElement} - Returns the FilterMenu component.
902
1010
  */
903
- const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact, title, dataTestId = "filters-menu", className, }) => {
1011
+ const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact, title, dataTestId = "filters-menu", className, showAppliedFiltersCount = true, buttonProps, allowShowFiltersDirectly = true, }) => {
904
1012
  const [t] = useTranslation();
905
1013
  const { isSm } = useViewportBreakpoints();
906
1014
  const [showCustomFilters, setShowCustomFilters] = useState(false);
907
- // TODO: Add analytics if event requirements are defined
908
- // const { logEvent } = useAnalytics(FilterEvents);
909
- const hideInMenu = useMemo(() => {
910
- return objectValues(filterBarDefinition)
911
- .map(filter => {
912
- const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
913
- const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true; // TODO: Starred menu concept should be completely removed everywhere
914
- const showMenuAnywayBecauseFilterHasChanged = filterBarConfig.appliedFilterKeys().includes(filter.filterKey);
915
- return (!showInFilterBar || !showInStarredMenu) && !showMenuAnywayBecauseFilterHasChanged
916
- ? filter.filterKey
917
- : null;
918
- })
919
- .filter(truthy);
920
- }, [filterBarConfig, filterBarDefinition]);
921
- const { filtersGrouped } = useGroupFilters(objectValues(filterBarDefinition), [...hideInMenu, ...hiddenFilters]);
922
- const { appliedFilters, filtersToShow, showDirectlyFilters, hasCustomFields } = useMemo(() => {
923
- const allFilters = filtersGrouped.map(group => group.filters).flat();
924
- return {
925
- appliedFilters: allFilters.filter(filter => filterBarConfig.appliedFilterKeys().includes(filter.filterKey)),
926
- filtersToShow: allFilters.filter(filter => !filter.showDirectly),
927
- showDirectlyFilters: allFilters.filter(filter => filter.showDirectly),
928
- hasCustomFields: allFilters.some(filter => filter.group === "CUSTOM_FIELDS"),
929
- };
930
- }, [filterBarConfig, filtersGrouped]);
931
- const [searchResults, searchText, setSearchText] = useTextSearch(filtersToShow, item => [item.title]);
932
- const { filtersGrouped: searchResultsGrouped } = useGroupFilters(searchResults, []);
933
- const { filtersGrouped: filtersToShowGrouped } = useGroupFilters(filtersToShow, []);
934
- const appliedCustomFields = useMemo(() => appliedFilters.filter(filter => filter.group === "CUSTOM_FIELDS"), [appliedFilters]);
1015
+ const { appliedFilters, showDirectlyFilters, hasCustomFields, filtersToShowGrouped, searchResultsGrouped, searchText, appliedCustomFields, removeCustomFieldsGroup, setSearchText, } = useFiltersMenu({
1016
+ filterBarDefinition,
1017
+ filterBarConfig,
1018
+ hiddenFilters,
1019
+ allowShowFiltersDirectly,
1020
+ });
935
1021
  return (jsxs("div", { className: twMerge("flex items-center gap-2", className), "data-testid": dataTestId, children: [jsx(Popover, { onOpenStateChange: open => {
936
1022
  if (!open) {
937
1023
  setShowCustomFilters(false);
938
1024
  setSearchText("");
939
1025
  }
940
1026
  }, placement: "bottom-start", children: modalState => {
941
- return (jsxs(Fragment, { children: [jsx(PopoverTrigger, { children: jsx("div", { "data-testid": "starred-filters-menu-trigger", id: "starred-filters-menu-trigger", children: jsx(Tooltip, { disabled: !compact || modalState.isOpen, label: jsx(MultipleFilterTooltipLabel, { filterBarConfig: filterBarConfig }), children: jsx(Button, { prefix: jsx(Icon, { ariaHidden: true, color: filterBarConfig.appliedFilterKeys().length > 0 ? "primary" : undefined, name: "Filter", size: "small" }), size: "small", suffix: compact && filterBarConfig.appliedFilterKeys().length > 0 && isSm ? (jsxs("div", { children: [jsxs("span", { "aria-hidden": true, children: ["(", filterBarConfig.appliedFilterKeys().length, ")"] }), jsxs("span", { className: "sr-only", children: [filterBarConfig.appliedFilterKeys().length, " filters applied"] })] })) : undefined, variant: "secondary", children: jsx("span", { className: "hidden sm:block", children: title ?? t("filtersBar.filtersHeading") }) }) }) }) }), jsx(PopoverContent, { cellPadding: 100, children: jsxs(Card, { className: "max-h-[min(600px,_calc(100dvh-32px))] w-[300px] overflow-y-hidden", dataTestId: "starred-filters-menu-popover", children: [jsxs("div", { className: "flex flex-col gap-1 p-1", children: [jsx(Search, { autoFocus: true, dataTestId: "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 px-3", children: [jsx(Text, { className: "text-secondary-400", size: "small", children: jsx(FiltersAppliedCountLabel, { filterBarConfig: filterBarConfig }) }), filterBarConfig.appliedFilterKeys().length > 0 ? (jsx(ResetFiltersButton, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] })] }), jsx(Separator, {}), jsxs(CardBody, { className: "gap-1 p-1", density: "none", disableGap: true, children: [jsx(GroupedFiltersList, { className: "flex flex-col gap-1", filterBarConfig: filterBarConfig, filtersGrouped: searchText
942
- ? searchResultsGrouped
943
- : showCustomFilters
944
- ? filtersToShowGrouped
945
- : removeCustomFields(filtersToShowGrouped) }), hasCustomFields && !showCustomFilters && !searchText ? (jsx(CustomFieldsHiddenGroup, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, onShow: () => {
946
- setShowCustomFilters(true);
947
- } })) : null] })] }) })] }));
1027
+ return (jsxs(Fragment, { children: [jsx(PopoverTrigger, { children: jsx("div", { "data-testid": "starred-filters-menu-trigger", id: "starred-filters-menu-trigger", children: jsx(Tooltip, { disabled: !compact || modalState.isOpen, label: jsx(MultipleFilterTooltipLabel, { filterBarConfig: filterBarConfig }), children: jsx(Button, { prefix: jsx(Icon, { ariaHidden: true, color: filterBarConfig.appliedFilterKeys().length > 0 ? "primary" : undefined, name: "Filter", size: "small" }), size: "small", suffix: compact && showAppliedFiltersCount && filterBarConfig.appliedFilterKeys().length > 0 && isSm ? (jsxs("div", { children: [jsxs("span", { "aria-hidden": true, children: ["(", filterBarConfig.appliedFilterKeys().length, ")"] }), jsxs("span", { className: "sr-only", children: [filterBarConfig.appliedFilterKeys().length, " filters applied"] })] })) : undefined, variant: "secondary", ...buttonProps, children: title !== "" ? (jsx("span", { className: "hidden sm:block", children: title ?? t("filtersBar.filtersHeading") })) : null }) }) }) }), jsx(PopoverContent, { cellPadding: 100, children: jsx(FiltersMenuContent, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, filtersToShowGrouped: filtersToShowGrouped, hasCustomFields: hasCustomFields, removeCustomFieldsGroup: removeCustomFieldsGroup, searchResultsGrouped: searchResultsGrouped, searchText: searchText, setSearchText: setSearchText, setShowCustomFilters: setShowCustomFilters, showCustomFilters: showCustomFilters }) })] }));
948
1028
  } }), showDirectlyFilters.length > 0 ? (jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, !compact ? (jsxs(Fragment, { children: [appliedFilters.filter(filter => !filter.showDirectly).length > 0 ? (jsx("div", { className: "h-4 w-[1px] bg-slate-300", "data-testid": "applied-filters-buttons" })) : null, jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: appliedFilters }), filterBarConfig.appliedFilterKeys().length > 0 ? (jsx(ResetFiltersButton, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] })) : null] }));
949
1029
  };
950
- const Separator = () => jsx("hr", { className: "border-secondary-200", role: "separator" });
951
- const FiltersAppliedCountLabel = ({ filterBarConfig, }) => {
952
- const [t] = useTranslation();
953
- switch (filterBarConfig.appliedFilterKeys().length) {
954
- case 0:
955
- return t("filtersBar.appliedFiltersTooltip.none");
956
- case 1:
957
- return filterBarConfig.appliedFilterKeys()[0] ? t("filtersMenu.appliedFiltersLabel.singular") : null;
958
- default:
959
- return jsx(Fragment, { children: t("filtersMenu.appliedFiltersLabel.plural", { count: filterBarConfig.appliedFilterKeys().length }) });
960
- }
961
- };
962
- const CustomFieldsHiddenGroup = ({ appliedCustomFields, filterBarConfig, onShow, }) => {
963
- const [t] = useTranslation();
964
- 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: onShow, prefix: jsx(Text, { className: "text-secondary-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] })] }));
965
- };
966
- const removeCustomFields = (filtersGrouped) => filtersGrouped.filter(group => group.key !== "CUSTOM_FIELDS");
967
1030
 
968
1031
  /**
969
1032
  * Filter is a React component that renders a filter element based on the provided filter definition and state.
@@ -1222,8 +1285,8 @@ const HierarchicalCheckboxFilter = ({ filterDefinition, filterBarActions, option
1222
1285
  /**
1223
1286
  * The FilterBar component serves as a wrapper for managing filters.
1224
1287
  */
1225
- const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact = true, title, }) => {
1226
- return (jsx(FiltersMenu, { className: className, compact: compact, dataTestId: `${filterBarConfig.name}-filterbar`, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, title: title }));
1288
+ const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact = true, title, allowShowFiltersDirectly = true, }) => {
1289
+ return (jsx(FiltersMenu, { allowShowFiltersDirectly: allowShowFiltersDirectly, className: className, compact: compact, dataTestId: `${filterBarConfig.name}-filterbar`, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, title: title }));
1227
1290
  };
1228
1291
 
1229
1292
  // Can't import jest.fn so must define a function that does nothing but mimics the jest.fn
@@ -1885,8 +1948,8 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, }) => {
1885
1948
  setValue: (key, callback) => setValue(setFilterBarConfig, key, callback),
1886
1949
  });
1887
1950
  useEffect(() => {
1888
- onValuesChange?.(filterBarConfig.values);
1889
1951
  saveData(filterBarConfig, filterBarDefinition);
1952
+ onValuesChange?.(filterBarConfig.values);
1890
1953
  }, [filterBarConfig, filterBarDefinition, onValuesChange, saveData]);
1891
1954
  return useMemo(() => {
1892
1955
  return {
@@ -2039,4 +2102,4 @@ const mergeFilters = (filterBarDefinition, extraFilters) => {
2039
2102
  */
2040
2103
  setupLibraryTranslations();
2041
2104
 
2042
- export { DefaultCheckboxFilter, DefaultDateRangeFilter, DefaultMinMaxFilter, DefaultRadioFilter, DynamicFilterList, FilterBar, FilterComponent, FilterEvents, FilterHeader, FilterResults, FilterTableComponent, FiltersMenu, FiltersRenderer, GroupedFiltersList, HierarchicalCheckboxFilter, ResetFiltersButton, areaFilterGeoJsonGeometrySchema, isAreaFilterValue, isArrayFilterValue, isBooleanValue, isDateRangeValue, isMinMaxFilterValue, isStringArrayFilterValue, isValueName, isValueNameArray, mergeFilters, mockFilterBar, toggleFilterValue, useFilterBar, useFilterBarAsync, useSearchParamAsFilter, validateFilter };
2105
+ export { DefaultCheckboxFilter, DefaultDateRangeFilter, DefaultMinMaxFilter, DefaultRadioFilter, DynamicFilterList, FilterBar, FilterButtonTooltipLabel, FilterComponent, FilterEvents, FilterHeader, FilterResults, FilterTableComponent, FiltersMenu, FiltersMenuContent, FiltersRenderer, GroupedFiltersList, HierarchicalCheckboxFilter, ResetFiltersButton, areaFilterGeoJsonGeometrySchema, isAreaFilterValue, isArrayFilterValue, isBooleanValue, isDateRangeValue, isMinMaxFilterValue, isStringArrayFilterValue, isValueName, isValueNameArray, mergeFilters, mockFilterBar, toggleFilterValue, useFilterBar, useFilterBarAsync, useFiltersMenu, useSearchParamAsFilter, validateFilter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/filters-filter-bar",
3
- "version": "1.3.198",
3
+ "version": "1.3.201",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -14,17 +14,17 @@
14
14
  "tailwind-merge": "^2.0.0",
15
15
  "string-ts": "^2.0.0",
16
16
  "zod": "3.23.4",
17
- "@trackunit/iris-app-api": "1.3.127",
17
+ "@trackunit/iris-app-api": "1.3.128",
18
18
  "@trackunit/react-core-hooks": "1.3.131",
19
- "@trackunit/react-filter-components": "1.3.165",
20
- "@trackunit/react-date-and-time-components": "1.3.167",
19
+ "@trackunit/react-filter-components": "1.3.166",
20
+ "@trackunit/react-date-and-time-components": "1.3.168",
21
21
  "@trackunit/shared-utils": "1.5.121",
22
- "@trackunit/react-form-components": "1.3.165",
22
+ "@trackunit/react-form-components": "1.3.166",
23
23
  "@trackunit/react-core-contexts-api": "1.4.127",
24
24
  "@trackunit/geo-json-utils": "1.3.121",
25
- "@trackunit/i18n-library-translation": "1.3.136",
25
+ "@trackunit/i18n-library-translation": "1.3.137",
26
26
  "@trackunit/css-class-variance-utilities": "1.3.121",
27
- "@trackunit/react-components": "1.4.145",
27
+ "@trackunit/react-components": "1.4.146",
28
28
  "@trackunit/react-test-setup": "1.0.11"
29
29
  },
30
30
  "module": "./index.esm.js",
@@ -26,9 +26,13 @@ interface FilterBarProps<TFilterBarDefinition extends FilterBarDefinition> {
26
26
  * The title of the filter bar default is "Filters" (translated)
27
27
  */
28
28
  title?: string;
29
+ /**
30
+ * If true, the filters marked as showDirectly will not be shown directly in the filter bar
31
+ */
32
+ allowShowFiltersDirectly?: boolean;
29
33
  }
30
34
  /**
31
35
  * The FilterBar component serves as a wrapper for managing filters.
32
36
  */
33
- export declare const FilterBar: <TFilterBarDefinition extends FilterBarDefinition>({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact, title, }: FilterBarProps<TFilterBarDefinition>) => import("react/jsx-runtime").JSX.Element;
37
+ export declare const FilterBar: <TFilterBarDefinition extends FilterBarDefinition>({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact, title, allowShowFiltersDirectly, }: FilterBarProps<TFilterBarDefinition>) => import("react/jsx-runtime").JSX.Element;
34
38
  export {};
@@ -0,0 +1,7 @@
1
+ import { FilterBarConfig, FilterBarDefinition, FilterMapActions, FilterMapGetter } from "../types/FilterTypes";
2
+ /**
3
+ * Tooltip label for the filter button
4
+ */
5
+ export declare const FilterButtonTooltipLabel: <TFilterBarDefinition extends FilterBarDefinition>({ filterBarConfig, }: {
6
+ filterBarConfig: FilterBarConfig<TFilterBarDefinition> & FilterMapActions & FilterMapGetter;
7
+ }) => string | import("react/jsx-runtime").JSX.Element | null;
@@ -1,7 +1,11 @@
1
- import { CommonProps } from "@trackunit/react-components";
1
+ import { ButtonProps, CommonProps } from "@trackunit/react-components";
2
2
  import { ReactElement } from "react";
3
3
  import { FilterBarConfig, FilterBarDefinition, FilterMapActions, FilterMapGetter } from "../types/FilterTypes";
4
4
  interface FiltersMenuProps<TFilterBarDefinition extends FilterBarDefinition> extends CommonProps {
5
+ /**
6
+ * If true, the filters marked as showDirectly will not be shown directly in the filter bar
7
+ */
8
+ allowShowFiltersDirectly?: boolean;
5
9
  /**
6
10
  * Configuration for the filter bar.
7
11
  */
@@ -9,7 +13,7 @@ interface FiltersMenuProps<TFilterBarDefinition extends FilterBarDefinition> ext
9
13
  /**
10
14
  * The definition of the filter bar, specifying its structure and filters.
11
15
  */
12
- filterBarDefinition: FilterBarDefinition;
16
+ filterBarDefinition: TFilterBarDefinition;
13
17
  /**
14
18
  * If you want some of the filters to be hidden, but still programmatically enabled
15
19
  */
@@ -18,10 +22,18 @@ interface FiltersMenuProps<TFilterBarDefinition extends FilterBarDefinition> ext
18
22
  * If true, the starred filters will be displayed in a compact mode
19
23
  */
20
24
  compact?: boolean;
25
+ /**
26
+ * If true, the applied filters count will be shown in the filter button, only visible when compact is true
27
+ */
28
+ showAppliedFiltersCount?: boolean;
21
29
  /**
22
30
  * The title of the filter bar default is "Filters" (translated)
23
31
  */
24
32
  title?: string;
33
+ /**
34
+ * The icon props for the filter button to override the icon look and feel.
35
+ */
36
+ buttonProps?: ButtonProps;
25
37
  }
26
38
  /**
27
39
  * FilterMenu is a React component that displays a list of filters in a popover menu based on the provided filter bar configuration.
@@ -29,5 +41,5 @@ interface FiltersMenuProps<TFilterBarDefinition extends FilterBarDefinition> ext
29
41
  * @template TFilterBarDefinition - The type representing the filter bar definition.
30
42
  * @returns {ReactElement} - Returns the FilterMenu component.
31
43
  */
32
- export declare const FiltersMenu: <TFilterBarDefinition extends FilterBarDefinition>({ filterBarDefinition, filterBarConfig, hiddenFilters, compact, title, dataTestId, className, }: FiltersMenuProps<TFilterBarDefinition>) => ReactElement;
44
+ export declare const FiltersMenu: <TFilterBarDefinition extends FilterBarDefinition>({ filterBarDefinition, filterBarConfig, hiddenFilters, compact, title, dataTestId, className, showAppliedFiltersCount, buttonProps, allowShowFiltersDirectly, }: FiltersMenuProps<TFilterBarDefinition>) => ReactElement;
33
45
  export {};
@@ -0,0 +1,17 @@
1
+ import { GroupOfFilters } from "../hooks/useGroupFilters";
2
+ import { FilterBarConfig, FilterBarDefinition, FilterDefinition, FilterMapActions, FilterMapGetter } from "../types/FilterTypes";
3
+ /**
4
+ *
5
+ */
6
+ export declare const FiltersMenuContent: ({ filterBarConfig, setShowCustomFilters, setSearchText, searchText, searchResultsGrouped, filtersToShowGrouped, removeCustomFieldsGroup, hasCustomFields, appliedCustomFields, showCustomFilters, }: {
7
+ filterBarConfig: FilterBarConfig<FilterBarDefinition> & FilterMapActions & FilterMapGetter;
8
+ setShowCustomFilters: (show: boolean) => void;
9
+ setSearchText: (text: string) => void;
10
+ searchText: string;
11
+ searchResultsGrouped: GroupOfFilters[];
12
+ filtersToShowGrouped: GroupOfFilters[];
13
+ removeCustomFieldsGroup: (groupOfFilters: GroupOfFilters[]) => GroupOfFilters[];
14
+ hasCustomFields: boolean;
15
+ appliedCustomFields: FilterDefinition[];
16
+ showCustomFilters: boolean;
17
+ }) => import("react/jsx-runtime").JSX.Element;
@@ -4,10 +4,12 @@ export * from "./DefaultFilterTypes";
4
4
  export * from "./DefaultMinMaxFilter";
5
5
  export * from "./DefaultRadioFilter";
6
6
  export * from "./DynamicFilterList";
7
+ export * from "./FilterButtonTooltipLabel";
7
8
  export * from "./FilterComponent";
8
9
  export * from "./FilterHeader";
9
10
  export * from "./FilterResults";
10
11
  export * from "./FiltersMenu";
12
+ export * from "./FiltersMenuContent";
11
13
  export * from "./FiltersRenderer";
12
14
  export * from "./FilterTableComponent";
13
15
  export * from "./GroupedFiltersList";
@@ -0,0 +1,25 @@
1
+ import { FilterBarConfig, FilterBarDefinition, FilterMapActions, FilterMapGetter } from "../types/FilterTypes";
2
+ import { GroupOfFilters } from "./useGroupFilters";
3
+ type UseFiltersMenuProps<TFilterBarDefinition extends FilterBarDefinition> = {
4
+ filterBarDefinition: TFilterBarDefinition;
5
+ filterBarConfig: FilterBarConfig<TFilterBarDefinition> & FilterMapActions & FilterMapGetter;
6
+ hiddenFilters?: string[];
7
+ allowShowFiltersDirectly?: boolean;
8
+ };
9
+ /**
10
+ * This hook is used to manage the filters menu.
11
+ * It returns the filters that should be shown in the menu, the filters that should be shown directly in the filter bar,
12
+ * and the filters that should be shown in the search results.
13
+ */
14
+ export declare const useFiltersMenu: <TFilterBarDefinition extends FilterBarDefinition>({ filterBarDefinition, filterBarConfig, hiddenFilters, allowShowFiltersDirectly, }: UseFiltersMenuProps<TFilterBarDefinition>) => {
15
+ appliedFilters: import("../types/FilterTypes").FilterDefinition[];
16
+ hasCustomFields: boolean;
17
+ showDirectlyFilters: import("../types/FilterTypes").FilterDefinition[];
18
+ appliedCustomFields: import("../types/FilterTypes").FilterDefinition[];
19
+ searchText: string;
20
+ setSearchText: import("react").Dispatch<string>;
21
+ filtersToShowGrouped: GroupOfFilters[];
22
+ searchResultsGrouped: GroupOfFilters[];
23
+ removeCustomFieldsGroup: (groupOfFilters: GroupOfFilters[]) => GroupOfFilters[];
24
+ };
25
+ export {};
@@ -3,6 +3,7 @@ export * from "./FilterBar";
3
3
  export * from "./hooks/mockFilterBar";
4
4
  export * from "./hooks/useFilterBar";
5
5
  export * from "./hooks/useFilterBarAsync";
6
+ export * from "./hooks/useFiltersMenu";
6
7
  export * from "./hooks/useSearchParamAsFilter";
7
8
  export * from "./types/FilterTypes";
8
9
  export * from "./utils/FilterEvents";
@@ -66,7 +66,7 @@ export type FilterMapGetter = {
66
66
  export type FilterName = string;
67
67
  export type FilterBarDefinition = Record<FilterName, FilterDefinition>;
68
68
  export type FilterTranslation = string;
69
- export type FilterGroup = "METADATA" | "STATUS" | "SITES" | "CUSTOMERS" | "OTHER" | "CUSTOM_FIELDS" | "MY_NETWORK" | "ASSET" | "OPERATOR" | "SERVICE" | "RENTAL" | "INTEGRATION";
69
+ export type FilterGroup = "METADATA" | "STATUS" | "SITES" | "CUSTOMERS" | "OTHER" | "CUSTOM_FIELDS" | "MY_NETWORK" | "FLEET_HEALTH" | "ASSET" | "OPERATOR" | "SERVICE" | "RENTAL" | "INTEGRATION";
70
70
  export interface AbstractFilterDefinition {
71
71
  /**
72
72
  * The unique key or identifier for the filter.