@trackunit/filters-filter-bar 0.0.579 → 0.0.581

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 CHANGED
@@ -4,13 +4,13 @@ var jsxRuntime = require('react/jsx-runtime');
4
4
  var i18nLibraryTranslation = require('@trackunit/i18n-library-translation');
5
5
  var reactCoreHooks = require('@trackunit/react-core-hooks');
6
6
  var reactFilterComponents = require('@trackunit/react-filter-components');
7
- var sharedUtils = require('@trackunit/shared-utils');
8
7
  var react = require('react');
8
+ var stringTs = require('string-ts');
9
9
  var reactCoreContextsApi = require('@trackunit/react-core-contexts-api');
10
10
  var reactComponents = require('@trackunit/react-components');
11
11
  var reactFormComponents = require('@trackunit/react-form-components');
12
12
  var reactDateAndTimeComponents = require('@trackunit/react-date-and-time-components');
13
- var stringTs = require('string-ts');
13
+ var sharedUtils = require('@trackunit/shared-utils');
14
14
  var tailwindMerge = require('tailwind-merge');
15
15
  var dequal = require('dequal');
16
16
  var isEqual = require('lodash/isEqual');
@@ -408,7 +408,7 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
408
408
  const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
409
409
  const handleSetValue = (value) => {
410
410
  logEvent("Filters Applied - V2", {
411
- type: filterName !== null && filterName !== void 0 ? filterName : `${sharedUtils.capitalize(filterDefinition.filterKey)}Filter`,
411
+ type: filterName !== null && filterName !== void 0 ? filterName : `${stringTs.capitalize(filterDefinition.filterKey)}Filter`,
412
412
  value: value.key,
413
413
  });
414
414
  if (filterDefinition.type === "stringArray") {
@@ -421,10 +421,10 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
421
421
  return toggleFilterValue(value.key)(prev);
422
422
  });
423
423
  }
424
- else if (filterDefinition.type === "valueName") {
424
+ else if (filterDefinition.type === "valueNameArray") {
425
425
  // eslint-disable-next-line local-rules/no-typescript-assertion
426
- const setValueAsValueName = setValue;
427
- setValueAsValueName(prev => {
426
+ const setValueAsValueNameArray = setValue;
427
+ setValueAsValueNameArray(prev => {
428
428
  if (!prev) {
429
429
  return [{ name: value.label, value: value.key }];
430
430
  }
@@ -462,7 +462,7 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
462
462
  count: undefinedCount ? filteredOptions.length - 1 : filteredOptions.length,
463
463
  } }), jsxRuntime.jsx(FilterResults, { ignoreUndefined: undefinedCount !== null, loading: loading, results: results, children: res => (jsxRuntime.jsx(DynamicFilterList, { checked: index => {
464
464
  var _a, _b;
465
- return filterDefinition.type === "valueName"
465
+ return filterDefinition.type === "valueNameArray"
466
466
  ? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || "")
467
467
  : filterBarActions.arrayIncludesValue(filterDefinition.filterKey, ((_b = res[index]) === null || _b === void 0 ? void 0 : _b.key) || "");
468
468
  }, count: index => { var _a; return (_a = res[index]) === null || _a === void 0 ? void 0 : _a.count; }, keyMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""; }, labelMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.label) || ""; }, onChange: index => {
@@ -470,7 +470,7 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
470
470
  if (result) {
471
471
  handleSetValue(result);
472
472
  }
473
- }, rowCount: undefinedCount ? res.length - 1 : res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "CheckBox" })) }), showUndefinedOptionWithCountAtBottom && undefinedCount ? (jsxRuntime.jsx(reactFilterComponents.CheckBoxFilterItem, { checked: filterDefinition.type === "valueName"
473
+ }, rowCount: undefinedCount ? res.length - 1 : res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "CheckBox" })) }), showUndefinedOptionWithCountAtBottom && undefinedCount ? (jsxRuntime.jsx(reactFilterComponents.CheckBoxFilterItem, { checked: filterDefinition.type === "valueNameArray"
474
474
  ? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, ((_c = results[undefinedCount.index]) === null || _c === void 0 ? void 0 : _c.key) || "")
475
475
  : filterBarActions.arrayIncludesValue(filterDefinition.filterKey, ((_d = results[undefinedCount.index]) === null || _d === void 0 ? void 0 : _d.key) || ""), className: "rounded-none border-t-2", dataTestId: "dynamic-filter-check-box-undefined", itemCount: undefinedCount.count, label: (_e = results[undefinedCount.index]) === null || _e === void 0 ? void 0 : _e.label, name: "dynamic-filter-check-box-undefined", onChange: () => {
476
476
  const result = results[undefinedCount.index];
@@ -564,29 +564,29 @@ const DefaultMinMaxFilter = ({ filterDefinition, filterName, value, setValue, fi
564
564
  *
565
565
  * @returns {JSX.Element} - Returns the DefaultRadioFilter component.
566
566
  */
567
- const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loading, filterName, customSearch, showRequestMoreUseSearch = false, }) => {
568
- var _a, _b, _c;
567
+ const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loading, filterName, customSearch, showRequestMoreUseSearch = false, setValue, }) => {
568
+ var _a, _b;
569
569
  const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
570
570
  const [filteredOptions, searchText, setSearchText] = reactCoreHooks.useTextSearch(options, item => [item.label]);
571
571
  const handleClick = (selectedId) => {
572
572
  var _a;
573
573
  const { key, label } = (_a = filteredOptions.find(({ key: id }) => id === selectedId)) !== null && _a !== void 0 ? _a : {};
574
574
  if (key && label) {
575
- filterBarActions.setArrayObjectValue(filterDefinition.filterKey, [{ value: key, name: label }]);
575
+ setValue(() => ({ value: key, name: label }));
576
576
  logEvent("Filters Applied - V2", {
577
- type: filterName !== null && filterName !== void 0 ? filterName : `${sharedUtils.capitalize(filterDefinition.filterKey)}Filter`,
577
+ type: filterName !== null && filterName !== void 0 ? filterName : `${stringTs.capitalize(filterDefinition.filterKey)}Filter`,
578
578
  value: String(label),
579
579
  });
580
580
  }
581
581
  };
582
- const selectedRadioId = ((_a = filteredOptions.find(partner => filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, partner.key))) === null || _a === void 0 ? void 0 : _a.key) || "";
582
+ const selectedRadioId = filteredOptions.find(option => filterBarActions.objectIncludesValue(filterDefinition.filterKey, option.key));
583
583
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(FilterHeader, { ...filterBarActions, ...filterDefinition, loading: loading, searchEnabled: true, searchProps: {
584
- value: (_b = customSearch === null || customSearch === void 0 ? void 0 : customSearch.value) !== null && _b !== void 0 ? _b : searchText,
585
- onChange: (_c = customSearch === null || customSearch === void 0 ? void 0 : customSearch.onChange) !== null && _c !== void 0 ? _c : setSearchText,
584
+ value: (_a = customSearch === null || customSearch === void 0 ? void 0 : customSearch.value) !== null && _a !== void 0 ? _a : searchText,
585
+ onChange: (_b = customSearch === null || customSearch === void 0 ? void 0 : customSearch.onChange) !== null && _b !== void 0 ? _b : setSearchText,
586
586
  count: filteredOptions.length,
587
587
  } }), jsxRuntime.jsx(FilterResults, { loading: loading, results: customSearch ? options : filteredOptions, children: res => (jsxRuntime.jsx(reactFormComponents.RadioGroup, { id: "DefaultRadioFilter", onChange: e => {
588
588
  handleClick(e.currentTarget.value);
589
- }, value: selectedRadioId, children: jsxRuntime.jsx(DynamicFilterList, { checked: index => { var _a; return filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""); }, count: index => { var _a; return (_a = res[index]) === null || _a === void 0 ? void 0 : _a.count; }, keyMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""; }, labelMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.label) || ""; }, rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
589
+ }, value: (selectedRadioId === null || selectedRadioId === void 0 ? void 0 : selectedRadioId.key) || "", children: jsxRuntime.jsx(DynamicFilterList, { checked: index => { var _a; return filterBarActions.objectIncludesValue(filterDefinition.filterKey, ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""); }, count: index => { var _a; return (_a = res[index]) === null || _a === void 0 ? void 0 : _a.count; }, keyMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""; }, labelMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.label) || ""; }, rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
590
590
  };
591
591
 
592
592
  /**
@@ -637,17 +637,20 @@ const reduceFilterText = (input) => {
637
637
  */
638
638
  const Filter = ({ filter, filterBarActions, filterState, }) => {
639
639
  const values = filterBarActions.getValuesByKey(filter.filterKey);
640
- const filterText = () => {
641
- if (values) {
642
- if (filter.valueAsText) {
643
- return reduceFilterText(filter.valueAsText(values));
644
- }
645
- else if (filter.type === "valueName") {
646
- return reduceFilterText(values.map(value => value.name));
647
- }
648
- return values.toString();
640
+ const getFilterText = () => {
641
+ if (!values) {
642
+ return undefined;
649
643
  }
650
- return "";
644
+ if (filter.valueAsText) {
645
+ return reduceFilterText(filter.valueAsText(values));
646
+ }
647
+ else if (filter.type === "valueNameArray") {
648
+ return reduceFilterText(values.map(value => value.name));
649
+ }
650
+ else if (filter.type === "valueName") {
651
+ return values.name;
652
+ }
653
+ return values.toString();
651
654
  };
652
655
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
653
656
  const setValue = (callback) => {
@@ -664,9 +667,12 @@ const Filter = ({ filter, filterBarActions, filterState, }) => {
664
667
  else if (filter.type === "area") {
665
668
  filterBarActions.setArea(newValue);
666
669
  }
667
- else if (filter.type === "valueName") {
670
+ else if (filter.type === "valueNameArray") {
668
671
  filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
669
672
  }
673
+ else if (filter.type === "valueName") {
674
+ filterBarActions.setObjectValue(filter.filterKey, newValue);
675
+ }
670
676
  else if (filter.type === "minMax") {
671
677
  filterBarActions.setMinMaxValue(filter.filterKey, newValue);
672
678
  }
@@ -677,7 +683,7 @@ const Filter = ({ filter, filterBarActions, filterState, }) => {
677
683
  filterBarActions.setNumberValue(filter.filterKey, newValue);
678
684
  }
679
685
  };
680
- const text = filterText();
686
+ const text = getFilterText();
681
687
  const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
682
688
  const showDirectly = filter.showDirectly || false;
683
689
  return showDirectly ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: filter.component({
@@ -687,7 +693,7 @@ const Filter = ({ filter, filterBarActions, filterState, }) => {
687
693
  setValue,
688
694
  filterBarActions,
689
695
  filterState,
690
- }) })) : (jsxRuntime.jsx(reactFilterComponents.Filter, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterText().length > 0, popoverProps: { placement: "right-start" }, title: filter.title, withStickyHeader: true, children: filter.component({
696
+ }) })) : (jsxRuntime.jsx(reactFilterComponents.Filter, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: Boolean(text === null || text === void 0 ? void 0 : text.length), popoverProps: { placement: "right-start" }, title: filter.title, withStickyHeader: true, children: filter.component({
691
697
  filterDefinition: filter,
692
698
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
693
699
  value: values,
@@ -772,9 +778,10 @@ const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starred
772
778
  * @template TFilterBarDefinition - The type representing the filter bar definition.
773
779
  * @returns {JSX.Element} - Returns the StarredFilters component.
774
780
  */
775
- const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], }) => {
781
+ const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact, dataTestId, className, }) => {
776
782
  const [t] = useTranslation();
777
- const { isLg } = reactComponents.useViewportSize();
783
+ const { isLg } = reactComponents.useViewportBreakpoints();
784
+ const isCompactMode = compact !== null && compact !== void 0 ? compact : !isLg;
778
785
  const hideInMenu = react.useMemo(() => {
779
786
  return sharedUtils.objectValues(filterBarDefinition)
780
787
  .map(filter => {
@@ -799,9 +806,9 @@ const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters =
799
806
  const appliedFilters = starredFilters.filter(filter => filterBarConfig.appliedFilterKeys.includes(filter.filterKey));
800
807
  const filtersToShow = starredFilters.filter(filter => !filter.showDirectly);
801
808
  const showDirectlyFilters = allFilters.filter(filter => filter.showDirectly);
802
- return (jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [jsxRuntime.jsx(reactComponents.Popover, { placement: "bottom-start", children: modalState => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx("div", { "data-testid": "starred-filters-menu-trigger", children: jsxRuntime.jsxs(reactComponents.Tooltip, { disabled: isLg || modalState.isOpen, label: jsxRuntime.jsx(FilterButtonTooltipLabel, { filterBarConfig: filterBarConfig }), children: [jsxRuntime.jsx(reactComponents.Button, { className: "@xs:flex hidden", prefix: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilterKeys.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", suffix: !isLg && filterBarConfig.appliedFilterKeys.length > 0
809
+ return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex flex-wrap items-center gap-2", className), "data-testid": dataTestId, children: [jsxRuntime.jsx(reactComponents.Popover, { placement: "bottom-start", children: modalState => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx("div", { "data-testid": "starred-filters-menu-trigger", children: jsxRuntime.jsxs(reactComponents.Tooltip, { disabled: !isCompactMode || modalState.isOpen, label: jsxRuntime.jsx(FilterButtonTooltipLabel, { filterBarConfig: filterBarConfig }), children: [jsxRuntime.jsx(reactComponents.Button, { className: "@xs:flex hidden", prefix: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilterKeys.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", suffix: isCompactMode && filterBarConfig.appliedFilterKeys.length > 0
803
810
  ? `(${filterBarConfig.appliedFilterKeys.length})`
804
- : undefined, variant: "secondary", children: t("filtersBar.filtersHeading") }), jsxRuntime.jsx(reactComponents.IconButton, { className: "@xs:hidden", icon: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilterKeys.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", variant: "secondary" })] }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { cellPadding: 100, children: jsxRuntime.jsxs(reactComponents.Card, { className: "max-h-[min(600px,_calc(100dvh-32px))] overflow-hidden", children: [filtersToShow.length > 0 ? (jsxRuntime.jsx(reactComponents.CardBody, { density: "dense", children: jsxRuntime.jsx("div", { className: "flex h-full min-w-min flex-col gap-2", children: jsxRuntime.jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: filtersToShow }) }) })) : null, jsxRuntime.jsxs(reactComponents.CardFooter, { className: filtersToShow.length === 0 ? "border-none" : undefined, density: "dense", children: [jsxRuntime.jsx(StarredFiltersMenu, { className: "mr-auto", filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, starredFilterKeys: filterBarConfig.starredFilterKeys, updateStarredFilters: filterBarConfig.updateStarredFilters }), isLg ? null : (jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState }))] })] }) })] })) }), showDirectlyFilters.length > 0 ? (jsxRuntime.jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, isLg ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [appliedFilters.filter(filter => !filter.showDirectly).length > 0 ? (jsxRuntime.jsx("div", { className: "h-4 w-[1px] bg-slate-300" })) : null, jsxRuntime.jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: appliedFilters }), jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })] })) : null] }));
811
+ : undefined, variant: "secondary", children: t("filtersBar.filtersHeading") }), jsxRuntime.jsx(reactComponents.IconButton, { className: "@xs:hidden", icon: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilterKeys.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", variant: "secondary" })] }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { cellPadding: 100, children: jsxRuntime.jsxs(reactComponents.Card, { className: "max-h-[min(600px,_calc(100dvh-32px))] overflow-hidden", children: [filtersToShow.length > 0 ? (jsxRuntime.jsx(reactComponents.CardBody, { density: "dense", children: jsxRuntime.jsx("div", { className: "flex h-full min-w-min flex-col gap-2", children: jsxRuntime.jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: filtersToShow }) }) })) : null, jsxRuntime.jsxs(reactComponents.CardFooter, { className: filtersToShow.length === 0 ? "border-none" : undefined, density: "dense", children: [jsxRuntime.jsx(StarredFiltersMenu, { className: "mr-auto", filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, starredFilterKeys: filterBarConfig.starredFilterKeys, updateStarredFilters: filterBarConfig.updateStarredFilters }), !isCompactMode ? null : (jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState }))] })] }) })] })) }), showDirectlyFilters.length > 0 ? (jsxRuntime.jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, !isCompactMode ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [appliedFilters.filter(filter => !filter.showDirectly).length > 0 ? (jsxRuntime.jsx("div", { className: "h-4 w-[1px] bg-slate-300" })) : null, jsxRuntime.jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: appliedFilters }), jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })] })) : null] }));
805
812
  };
806
813
  const FiltersList = ({ filters, filterBarConfig }) => {
807
814
  return filters.length === 0
@@ -829,8 +836,8 @@ const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
829
836
  /**
830
837
  * The FilterBar component serves as a wrapper for managing filters.
831
838
  */
832
- const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, }) => {
833
- return (jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("flex", className), "data-testid": `${filterBarConfig.name}-filterbar`, children: jsxRuntime.jsx(StarredFilters, { filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }));
839
+ const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact, }) => {
840
+ return (jsxRuntime.jsx(StarredFilters, { className: className, compact: compact, dataTestId: `${filterBarConfig.name}-filterbar`, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }));
834
841
  };
835
842
 
836
843
  // Can't import jest.fn so must define a function that does nothing but mimics the jest.fn
@@ -867,6 +874,9 @@ const mockFilterBar = {
867
874
  setCriticality: doNothing,
868
875
  setServicePlan: doNothing,
869
876
  },
877
+ objectIncludesValue: doNothing,
878
+ setObjectValue: doNothing,
879
+ toggleObjectValue: doNothing,
870
880
  },
871
881
  filterBarDefinition: {},
872
882
  };
@@ -883,9 +893,12 @@ const getInitialValueFromType = (type) => {
883
893
  if (type === "stringArray") {
884
894
  return [];
885
895
  }
886
- if (type === "valueName") {
896
+ if (type === "valueNameArray") {
887
897
  return [];
888
898
  }
899
+ if (type === "valueName") {
900
+ return { value: undefined, name: undefined };
901
+ }
889
902
  if (type === "minMax") {
890
903
  return { min: undefined, max: undefined };
891
904
  }
@@ -952,7 +965,8 @@ const hasValue = (value) => {
952
965
  return !(Array.isArray(value) && value.length === 0);
953
966
  };
954
967
  const isNotRightType = (filterDefinition, foundFilter) => {
955
- return ((filterDefinition.type === "valueName" && !isValueNameFilterValue(foundFilter)) ||
968
+ return ((filterDefinition.type === "valueNameArray" && !isValueNameArray(foundFilter)) ||
969
+ (filterDefinition.type === "valueName" && !isValueName(foundFilter)) ||
956
970
  (filterDefinition.type === "stringArray" && !isStringArrayFilterValue(foundFilter)) ||
957
971
  (filterDefinition.type === "dateRange" && !isDateRangeValue(foundFilter)) ||
958
972
  (filterDefinition.type === "area" && !isAreaFilterValue(foundFilter)) ||
@@ -1011,13 +1025,21 @@ const isBooleanValue = (value) => {
1011
1025
  return value ? typeof value === "object" && Object.keys(value).includes("booleanValue") : false;
1012
1026
  };
1013
1027
  /**
1014
- *
1028
+ * Type guard to check if a value is a single ValueName object
1015
1029
  */
1016
- const isValueNameFilterValue = (value) => {
1017
- return (isArrayFilterValue(value) &&
1018
- value.every(
1030
+ const isValueName = (value) => {
1031
+ return (typeof value === "object" &&
1032
+ value !== null &&
1033
+ // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1034
+ Object.keys(value).includes("name") &&
1019
1035
  // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1020
- item => typeof item === "object" && Object.keys(item).includes("name") && Object.keys(item).includes("value")));
1036
+ Object.keys(value).includes("value"));
1037
+ };
1038
+ /**
1039
+ * Type guard to check if a value is an array of ValueName objects
1040
+ */
1041
+ const isValueNameArray = (value) => {
1042
+ return isArrayFilterValue(value) && value.every(isValueName);
1021
1043
  };
1022
1044
  /**
1023
1045
  * Validates a filter configuration against filter definitions.
@@ -1112,9 +1134,7 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1112
1134
  return initialFilterBarConfig;
1113
1135
  });
1114
1136
  react.useEffect(() => {
1115
- if (onValuesChange) {
1116
- onValuesChange(filterBarConfig.values);
1117
- }
1137
+ onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(filterBarConfig.values);
1118
1138
  }, [filterBarConfig.values, filterBarConfig, onValuesChange]);
1119
1139
  react.useEffect(() => {
1120
1140
  localStorage.setItem(`filter-${name}`, JSON.stringify(filterBarConfig));
@@ -1166,6 +1186,10 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1166
1186
  const filter = filterBarConfig.values[key];
1167
1187
  return (filter === null || filter === void 0 ? void 0 : filter.find(f => f.value === value)) !== undefined || false;
1168
1188
  },
1189
+ objectIncludesValue(key, value) {
1190
+ const filter = filterBarConfig.values[key];
1191
+ return (filter === null || filter === void 0 ? void 0 : filter.value) === value || false;
1192
+ },
1169
1193
  };
1170
1194
  }, [filterBarConfig.initialState, filterBarConfig.name, filterBarConfig.values, filterBarDefinition]);
1171
1195
  const filterMapActions = react.useMemo(() => {
@@ -1341,6 +1365,17 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1341
1365
  };
1342
1366
  });
1343
1367
  },
1368
+ setObjectValue(key, filterValue) {
1369
+ setFilterBarConfig(prevState => {
1370
+ return {
1371
+ ...prevState,
1372
+ values: {
1373
+ ...prevState.values,
1374
+ [key]: filterValue,
1375
+ },
1376
+ };
1377
+ });
1378
+ },
1344
1379
  //Setting multiple value name objects
1345
1380
  toggleArrayObjectValue(key, filterValue) {
1346
1381
  setFilterBarConfig(prevState => {
@@ -1361,6 +1396,17 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1361
1396
  };
1362
1397
  });
1363
1398
  },
1399
+ toggleObjectValue(key, filterValue) {
1400
+ setFilterBarConfig(prevState => {
1401
+ return {
1402
+ ...prevState,
1403
+ values: {
1404
+ ...prevState.values,
1405
+ [key]: filterValue,
1406
+ },
1407
+ };
1408
+ });
1409
+ },
1364
1410
  // Reset filters to initial state
1365
1411
  resetFiltersToInitialState() {
1366
1412
  setFilterBarConfig(prevState => {
@@ -1426,7 +1472,8 @@ exports.isBooleanValue = isBooleanValue;
1426
1472
  exports.isDateRangeValue = isDateRangeValue;
1427
1473
  exports.isMinMaxFilterValue = isMinMaxFilterValue;
1428
1474
  exports.isStringArrayFilterValue = isStringArrayFilterValue;
1429
- exports.isValueNameFilterValue = isValueNameFilterValue;
1475
+ exports.isValueName = isValueName;
1476
+ exports.isValueNameArray = isValueNameArray;
1430
1477
  exports.mergeFilters = mergeFilters;
1431
1478
  exports.mockFilterBar = mockFilterBar;
1432
1479
  exports.toggleFilterValue = toggleFilterValue;
package/index.esm.js CHANGED
@@ -2,13 +2,13 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
3
3
  import { useAnalytics, useTextSearch } from '@trackunit/react-core-hooks';
4
4
  import { FilterBody, RadioFilterItem, CheckBoxFilterItem, FilterHeader as FilterHeader$1, FilterFooter, Filter as Filter$1 } from '@trackunit/react-filter-components';
5
- import { capitalize, nonNullable, objectValues, truthy } from '@trackunit/shared-utils';
6
5
  import { useRef, useMemo, useState, useEffect, useCallback } from 'react';
6
+ import { capitalize } from 'string-ts';
7
7
  import { createEvent } from '@trackunit/react-core-contexts-api';
8
- import { VirtualizedList, Text, Button, Popover, PopoverTrigger, PopoverContent, MenuList, useViewportSize, Tooltip, Icon, IconButton, Card, CardBody, CardFooter } from '@trackunit/react-components';
8
+ import { VirtualizedList, Text, Button, Popover, PopoverTrigger, PopoverContent, MenuList, useViewportBreakpoints, Tooltip, Icon, IconButton, Card, CardBody, CardFooter } from '@trackunit/react-components';
9
9
  import { Search, NumberField, RadioGroup, Toggle } from '@trackunit/react-form-components';
10
10
  import { DayRangePicker } from '@trackunit/react-date-and-time-components';
11
- import { capitalize as capitalize$1 } from 'string-ts';
11
+ import { nonNullable, capitalize as capitalize$1, objectValues, truthy } from '@trackunit/shared-utils';
12
12
  import { twMerge } from 'tailwind-merge';
13
13
  import { dequal } from 'dequal';
14
14
  import isEqual from 'lodash/isEqual';
@@ -419,10 +419,10 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
419
419
  return toggleFilterValue(value.key)(prev);
420
420
  });
421
421
  }
422
- else if (filterDefinition.type === "valueName") {
422
+ else if (filterDefinition.type === "valueNameArray") {
423
423
  // eslint-disable-next-line local-rules/no-typescript-assertion
424
- const setValueAsValueName = setValue;
425
- setValueAsValueName(prev => {
424
+ const setValueAsValueNameArray = setValue;
425
+ setValueAsValueNameArray(prev => {
426
426
  if (!prev) {
427
427
  return [{ name: value.label, value: value.key }];
428
428
  }
@@ -460,7 +460,7 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
460
460
  count: undefinedCount ? filteredOptions.length - 1 : filteredOptions.length,
461
461
  } }), jsx(FilterResults, { ignoreUndefined: undefinedCount !== null, loading: loading, results: results, children: res => (jsx(DynamicFilterList, { checked: index => {
462
462
  var _a, _b;
463
- return filterDefinition.type === "valueName"
463
+ return filterDefinition.type === "valueNameArray"
464
464
  ? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || "")
465
465
  : filterBarActions.arrayIncludesValue(filterDefinition.filterKey, ((_b = res[index]) === null || _b === void 0 ? void 0 : _b.key) || "");
466
466
  }, count: index => { var _a; return (_a = res[index]) === null || _a === void 0 ? void 0 : _a.count; }, keyMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""; }, labelMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.label) || ""; }, onChange: index => {
@@ -468,7 +468,7 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
468
468
  if (result) {
469
469
  handleSetValue(result);
470
470
  }
471
- }, rowCount: undefinedCount ? res.length - 1 : res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "CheckBox" })) }), showUndefinedOptionWithCountAtBottom && undefinedCount ? (jsx(CheckBoxFilterItem, { checked: filterDefinition.type === "valueName"
471
+ }, rowCount: undefinedCount ? res.length - 1 : res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "CheckBox" })) }), showUndefinedOptionWithCountAtBottom && undefinedCount ? (jsx(CheckBoxFilterItem, { checked: filterDefinition.type === "valueNameArray"
472
472
  ? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, ((_c = results[undefinedCount.index]) === null || _c === void 0 ? void 0 : _c.key) || "")
473
473
  : filterBarActions.arrayIncludesValue(filterDefinition.filterKey, ((_d = results[undefinedCount.index]) === null || _d === void 0 ? void 0 : _d.key) || ""), className: "rounded-none border-t-2", dataTestId: "dynamic-filter-check-box-undefined", itemCount: undefinedCount.count, label: (_e = results[undefinedCount.index]) === null || _e === void 0 ? void 0 : _e.label, name: "dynamic-filter-check-box-undefined", onChange: () => {
474
474
  const result = results[undefinedCount.index];
@@ -514,7 +514,7 @@ const DefaultDateRangeFilter = ({ filterDefinition, filterName, value, setValue,
514
514
  const { logEvent } = useAnalytics(FilterEvents);
515
515
  const handleApply = (fromDateValue, toDateValue) => {
516
516
  logEvent("Filters Applied - V2", {
517
- type: filterName !== null && filterName !== void 0 ? filterName : `${capitalize(filterDefinition.filterKey)}Filter`,
517
+ type: filterName !== null && filterName !== void 0 ? filterName : `${capitalize$1(filterDefinition.filterKey)}Filter`,
518
518
  value: JSON.stringify({ from: fromDateValue, to: toDateValue }),
519
519
  });
520
520
  setValue(() => ({ from: fromDateValue, to: toDateValue }));
@@ -547,7 +547,7 @@ const DefaultMinMaxFilter = ({ filterDefinition, filterName, value, setValue, fi
547
547
  const realMinValue = minValue === 0 ? undefined : minValue !== null && minValue !== void 0 ? minValue : undefined;
548
548
  const realMaxValue = maxValue === 0 ? undefined : maxValue !== null && maxValue !== void 0 ? maxValue : undefined;
549
549
  logEvent("Filters Applied - V2", {
550
- type: filterName !== null && filterName !== void 0 ? filterName : `${capitalize$1(filterDefinition.filterKey)}Filter`,
550
+ type: filterName !== null && filterName !== void 0 ? filterName : `${capitalize(filterDefinition.filterKey)}Filter`,
551
551
  value: JSON.stringify({ min: realMinValue, max: realMaxValue }),
552
552
  });
553
553
  setValue(() => {
@@ -562,29 +562,29 @@ const DefaultMinMaxFilter = ({ filterDefinition, filterName, value, setValue, fi
562
562
  *
563
563
  * @returns {JSX.Element} - Returns the DefaultRadioFilter component.
564
564
  */
565
- const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loading, filterName, customSearch, showRequestMoreUseSearch = false, }) => {
566
- var _a, _b, _c;
565
+ const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loading, filterName, customSearch, showRequestMoreUseSearch = false, setValue, }) => {
566
+ var _a, _b;
567
567
  const { logEvent } = useAnalytics(FilterEvents);
568
568
  const [filteredOptions, searchText, setSearchText] = useTextSearch(options, item => [item.label]);
569
569
  const handleClick = (selectedId) => {
570
570
  var _a;
571
571
  const { key, label } = (_a = filteredOptions.find(({ key: id }) => id === selectedId)) !== null && _a !== void 0 ? _a : {};
572
572
  if (key && label) {
573
- filterBarActions.setArrayObjectValue(filterDefinition.filterKey, [{ value: key, name: label }]);
573
+ setValue(() => ({ value: key, name: label }));
574
574
  logEvent("Filters Applied - V2", {
575
575
  type: filterName !== null && filterName !== void 0 ? filterName : `${capitalize(filterDefinition.filterKey)}Filter`,
576
576
  value: String(label),
577
577
  });
578
578
  }
579
579
  };
580
- const selectedRadioId = ((_a = filteredOptions.find(partner => filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, partner.key))) === null || _a === void 0 ? void 0 : _a.key) || "";
580
+ const selectedRadioId = filteredOptions.find(option => filterBarActions.objectIncludesValue(filterDefinition.filterKey, option.key));
581
581
  return (jsxs(Fragment, { children: [jsx(FilterHeader, { ...filterBarActions, ...filterDefinition, loading: loading, searchEnabled: true, searchProps: {
582
- value: (_b = customSearch === null || customSearch === void 0 ? void 0 : customSearch.value) !== null && _b !== void 0 ? _b : searchText,
583
- onChange: (_c = customSearch === null || customSearch === void 0 ? void 0 : customSearch.onChange) !== null && _c !== void 0 ? _c : setSearchText,
582
+ value: (_a = customSearch === null || customSearch === void 0 ? void 0 : customSearch.value) !== null && _a !== void 0 ? _a : searchText,
583
+ onChange: (_b = customSearch === null || customSearch === void 0 ? void 0 : customSearch.onChange) !== null && _b !== void 0 ? _b : setSearchText,
584
584
  count: filteredOptions.length,
585
585
  } }), jsx(FilterResults, { loading: loading, results: customSearch ? options : filteredOptions, children: res => (jsx(RadioGroup, { id: "DefaultRadioFilter", onChange: e => {
586
586
  handleClick(e.currentTarget.value);
587
- }, value: selectedRadioId, children: jsx(DynamicFilterList, { checked: index => { var _a; return filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""); }, count: index => { var _a; return (_a = res[index]) === null || _a === void 0 ? void 0 : _a.count; }, keyMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""; }, labelMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.label) || ""; }, rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
587
+ }, value: (selectedRadioId === null || selectedRadioId === void 0 ? void 0 : selectedRadioId.key) || "", children: jsx(DynamicFilterList, { checked: index => { var _a; return filterBarActions.objectIncludesValue(filterDefinition.filterKey, ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""); }, count: index => { var _a; return (_a = res[index]) === null || _a === void 0 ? void 0 : _a.count; }, keyMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""; }, labelMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.label) || ""; }, rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
588
588
  };
589
589
 
590
590
  /**
@@ -635,17 +635,20 @@ const reduceFilterText = (input) => {
635
635
  */
636
636
  const Filter = ({ filter, filterBarActions, filterState, }) => {
637
637
  const values = filterBarActions.getValuesByKey(filter.filterKey);
638
- const filterText = () => {
639
- if (values) {
640
- if (filter.valueAsText) {
641
- return reduceFilterText(filter.valueAsText(values));
642
- }
643
- else if (filter.type === "valueName") {
644
- return reduceFilterText(values.map(value => value.name));
645
- }
646
- return values.toString();
638
+ const getFilterText = () => {
639
+ if (!values) {
640
+ return undefined;
647
641
  }
648
- return "";
642
+ if (filter.valueAsText) {
643
+ return reduceFilterText(filter.valueAsText(values));
644
+ }
645
+ else if (filter.type === "valueNameArray") {
646
+ return reduceFilterText(values.map(value => value.name));
647
+ }
648
+ else if (filter.type === "valueName") {
649
+ return values.name;
650
+ }
651
+ return values.toString();
649
652
  };
650
653
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
651
654
  const setValue = (callback) => {
@@ -662,9 +665,12 @@ const Filter = ({ filter, filterBarActions, filterState, }) => {
662
665
  else if (filter.type === "area") {
663
666
  filterBarActions.setArea(newValue);
664
667
  }
665
- else if (filter.type === "valueName") {
668
+ else if (filter.type === "valueNameArray") {
666
669
  filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
667
670
  }
671
+ else if (filter.type === "valueName") {
672
+ filterBarActions.setObjectValue(filter.filterKey, newValue);
673
+ }
668
674
  else if (filter.type === "minMax") {
669
675
  filterBarActions.setMinMaxValue(filter.filterKey, newValue);
670
676
  }
@@ -675,7 +681,7 @@ const Filter = ({ filter, filterBarActions, filterState, }) => {
675
681
  filterBarActions.setNumberValue(filter.filterKey, newValue);
676
682
  }
677
683
  };
678
- const text = filterText();
684
+ const text = getFilterText();
679
685
  const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
680
686
  const showDirectly = filter.showDirectly || false;
681
687
  return showDirectly ? (jsx(Fragment, { children: filter.component({
@@ -685,7 +691,7 @@ const Filter = ({ filter, filterBarActions, filterState, }) => {
685
691
  setValue,
686
692
  filterBarActions,
687
693
  filterState,
688
- }) })) : (jsx(Filter$1, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterText().length > 0, popoverProps: { placement: "right-start" }, title: filter.title, withStickyHeader: true, children: filter.component({
694
+ }) })) : (jsx(Filter$1, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: Boolean(text === null || text === void 0 ? void 0 : text.length), popoverProps: { placement: "right-start" }, title: filter.title, withStickyHeader: true, children: filter.component({
689
695
  filterDefinition: filter,
690
696
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
691
697
  value: values,
@@ -770,9 +776,10 @@ const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starred
770
776
  * @template TFilterBarDefinition - The type representing the filter bar definition.
771
777
  * @returns {JSX.Element} - Returns the StarredFilters component.
772
778
  */
773
- const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], }) => {
779
+ const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact, dataTestId, className, }) => {
774
780
  const [t] = useTranslation();
775
- const { isLg } = useViewportSize();
781
+ const { isLg } = useViewportBreakpoints();
782
+ const isCompactMode = compact !== null && compact !== void 0 ? compact : !isLg;
776
783
  const hideInMenu = useMemo(() => {
777
784
  return objectValues(filterBarDefinition)
778
785
  .map(filter => {
@@ -797,9 +804,9 @@ const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters =
797
804
  const appliedFilters = starredFilters.filter(filter => filterBarConfig.appliedFilterKeys.includes(filter.filterKey));
798
805
  const filtersToShow = starredFilters.filter(filter => !filter.showDirectly);
799
806
  const showDirectlyFilters = allFilters.filter(filter => filter.showDirectly);
800
- return (jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [jsx(Popover, { placement: "bottom-start", children: modalState => (jsxs(Fragment, { children: [jsx(PopoverTrigger, { children: jsx("div", { "data-testid": "starred-filters-menu-trigger", children: jsxs(Tooltip, { disabled: isLg || modalState.isOpen, label: jsx(FilterButtonTooltipLabel, { filterBarConfig: filterBarConfig }), children: [jsx(Button, { className: "@xs:flex hidden", prefix: jsx(Icon, { color: filterBarConfig.appliedFilterKeys.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", suffix: !isLg && filterBarConfig.appliedFilterKeys.length > 0
807
+ return (jsxs("div", { className: twMerge("flex flex-wrap items-center gap-2", className), "data-testid": dataTestId, children: [jsx(Popover, { placement: "bottom-start", children: modalState => (jsxs(Fragment, { children: [jsx(PopoverTrigger, { children: jsx("div", { "data-testid": "starred-filters-menu-trigger", children: jsxs(Tooltip, { disabled: !isCompactMode || modalState.isOpen, label: jsx(FilterButtonTooltipLabel, { filterBarConfig: filterBarConfig }), children: [jsx(Button, { className: "@xs:flex hidden", prefix: jsx(Icon, { color: filterBarConfig.appliedFilterKeys.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", suffix: isCompactMode && filterBarConfig.appliedFilterKeys.length > 0
801
808
  ? `(${filterBarConfig.appliedFilterKeys.length})`
802
- : undefined, variant: "secondary", children: t("filtersBar.filtersHeading") }), jsx(IconButton, { className: "@xs:hidden", icon: jsx(Icon, { color: filterBarConfig.appliedFilterKeys.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", variant: "secondary" })] }) }) }), jsx(PopoverContent, { cellPadding: 100, children: jsxs(Card, { className: "max-h-[min(600px,_calc(100dvh-32px))] overflow-hidden", children: [filtersToShow.length > 0 ? (jsx(CardBody, { density: "dense", children: jsx("div", { className: "flex h-full min-w-min flex-col gap-2", children: jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: filtersToShow }) }) })) : null, jsxs(CardFooter, { className: filtersToShow.length === 0 ? "border-none" : undefined, density: "dense", children: [jsx(StarredFiltersMenu, { className: "mr-auto", filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, starredFilterKeys: filterBarConfig.starredFilterKeys, updateStarredFilters: filterBarConfig.updateStarredFilters }), isLg ? null : (jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState }))] })] }) })] })) }), showDirectlyFilters.length > 0 ? (jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, isLg ? (jsxs(Fragment, { children: [appliedFilters.filter(filter => !filter.showDirectly).length > 0 ? (jsx("div", { className: "h-4 w-[1px] bg-slate-300" })) : null, jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: appliedFilters }), jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })] })) : null] }));
809
+ : undefined, variant: "secondary", children: t("filtersBar.filtersHeading") }), jsx(IconButton, { className: "@xs:hidden", icon: jsx(Icon, { color: filterBarConfig.appliedFilterKeys.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", variant: "secondary" })] }) }) }), jsx(PopoverContent, { cellPadding: 100, children: jsxs(Card, { className: "max-h-[min(600px,_calc(100dvh-32px))] overflow-hidden", children: [filtersToShow.length > 0 ? (jsx(CardBody, { density: "dense", children: jsx("div", { className: "flex h-full min-w-min flex-col gap-2", children: jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: filtersToShow }) }) })) : null, jsxs(CardFooter, { className: filtersToShow.length === 0 ? "border-none" : undefined, density: "dense", children: [jsx(StarredFiltersMenu, { className: "mr-auto", filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, starredFilterKeys: filterBarConfig.starredFilterKeys, updateStarredFilters: filterBarConfig.updateStarredFilters }), !isCompactMode ? null : (jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState }))] })] }) })] })) }), showDirectlyFilters.length > 0 ? (jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, !isCompactMode ? (jsxs(Fragment, { children: [appliedFilters.filter(filter => !filter.showDirectly).length > 0 ? (jsx("div", { className: "h-4 w-[1px] bg-slate-300" })) : null, jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: appliedFilters }), jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })] })) : null] }));
803
810
  };
804
811
  const FiltersList = ({ filters, filterBarConfig }) => {
805
812
  return filters.length === 0
@@ -827,8 +834,8 @@ const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
827
834
  /**
828
835
  * The FilterBar component serves as a wrapper for managing filters.
829
836
  */
830
- const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, }) => {
831
- return (jsx("div", { className: twMerge("flex", className), "data-testid": `${filterBarConfig.name}-filterbar`, children: jsx(StarredFilters, { filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }));
837
+ const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact, }) => {
838
+ return (jsx(StarredFilters, { className: className, compact: compact, dataTestId: `${filterBarConfig.name}-filterbar`, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }));
832
839
  };
833
840
 
834
841
  // Can't import jest.fn so must define a function that does nothing but mimics the jest.fn
@@ -865,6 +872,9 @@ const mockFilterBar = {
865
872
  setCriticality: doNothing,
866
873
  setServicePlan: doNothing,
867
874
  },
875
+ objectIncludesValue: doNothing,
876
+ setObjectValue: doNothing,
877
+ toggleObjectValue: doNothing,
868
878
  },
869
879
  filterBarDefinition: {},
870
880
  };
@@ -881,9 +891,12 @@ const getInitialValueFromType = (type) => {
881
891
  if (type === "stringArray") {
882
892
  return [];
883
893
  }
884
- if (type === "valueName") {
894
+ if (type === "valueNameArray") {
885
895
  return [];
886
896
  }
897
+ if (type === "valueName") {
898
+ return { value: undefined, name: undefined };
899
+ }
887
900
  if (type === "minMax") {
888
901
  return { min: undefined, max: undefined };
889
902
  }
@@ -916,7 +929,7 @@ const createInitialState = (name, mainFilters, initialState, setValue) => {
916
929
  const key = curr.filterKey;
917
930
  return {
918
931
  ...prev,
919
- [`set${capitalize$1(key)}`]: (callback) => setValue(key, callback),
932
+ [`set${capitalize(key)}`]: (callback) => setValue(key, callback),
920
933
  };
921
934
  }, {});
922
935
  const updatedInitialState = mainFilters.reduce((prev, curr) => {
@@ -950,7 +963,8 @@ const hasValue = (value) => {
950
963
  return !(Array.isArray(value) && value.length === 0);
951
964
  };
952
965
  const isNotRightType = (filterDefinition, foundFilter) => {
953
- return ((filterDefinition.type === "valueName" && !isValueNameFilterValue(foundFilter)) ||
966
+ return ((filterDefinition.type === "valueNameArray" && !isValueNameArray(foundFilter)) ||
967
+ (filterDefinition.type === "valueName" && !isValueName(foundFilter)) ||
954
968
  (filterDefinition.type === "stringArray" && !isStringArrayFilterValue(foundFilter)) ||
955
969
  (filterDefinition.type === "dateRange" && !isDateRangeValue(foundFilter)) ||
956
970
  (filterDefinition.type === "area" && !isAreaFilterValue(foundFilter)) ||
@@ -1009,13 +1023,21 @@ const isBooleanValue = (value) => {
1009
1023
  return value ? typeof value === "object" && Object.keys(value).includes("booleanValue") : false;
1010
1024
  };
1011
1025
  /**
1012
- *
1026
+ * Type guard to check if a value is a single ValueName object
1013
1027
  */
1014
- const isValueNameFilterValue = (value) => {
1015
- return (isArrayFilterValue(value) &&
1016
- value.every(
1028
+ const isValueName = (value) => {
1029
+ return (typeof value === "object" &&
1030
+ value !== null &&
1017
1031
  // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1018
- item => typeof item === "object" && Object.keys(item).includes("name") && Object.keys(item).includes("value")));
1032
+ Object.keys(value).includes("name") &&
1033
+ // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1034
+ Object.keys(value).includes("value"));
1035
+ };
1036
+ /**
1037
+ * Type guard to check if a value is an array of ValueName objects
1038
+ */
1039
+ const isValueNameArray = (value) => {
1040
+ return isArrayFilterValue(value) && value.every(isValueName);
1019
1041
  };
1020
1042
  /**
1021
1043
  * Validates a filter configuration against filter definitions.
@@ -1105,14 +1127,12 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1105
1127
  // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1106
1128
  Object.keys(initialFilterBarConfig.values).forEach(key => {
1107
1129
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1108
- initialFilterBarConfig.setters[`set${capitalize$1(key)}`] = (callback) => setValue(key, callback);
1130
+ initialFilterBarConfig.setters[`set${capitalize(key)}`] = (callback) => setValue(key, callback);
1109
1131
  });
1110
1132
  return initialFilterBarConfig;
1111
1133
  });
1112
1134
  useEffect(() => {
1113
- if (onValuesChange) {
1114
- onValuesChange(filterBarConfig.values);
1115
- }
1135
+ onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(filterBarConfig.values);
1116
1136
  }, [filterBarConfig.values, filterBarConfig, onValuesChange]);
1117
1137
  useEffect(() => {
1118
1138
  localStorage.setItem(`filter-${name}`, JSON.stringify(filterBarConfig));
@@ -1164,6 +1184,10 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1164
1184
  const filter = filterBarConfig.values[key];
1165
1185
  return (filter === null || filter === void 0 ? void 0 : filter.find(f => f.value === value)) !== undefined || false;
1166
1186
  },
1187
+ objectIncludesValue(key, value) {
1188
+ const filter = filterBarConfig.values[key];
1189
+ return (filter === null || filter === void 0 ? void 0 : filter.value) === value || false;
1190
+ },
1167
1191
  };
1168
1192
  }, [filterBarConfig.initialState, filterBarConfig.name, filterBarConfig.values, filterBarDefinition]);
1169
1193
  const filterMapActions = useMemo(() => {
@@ -1339,6 +1363,17 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1339
1363
  };
1340
1364
  });
1341
1365
  },
1366
+ setObjectValue(key, filterValue) {
1367
+ setFilterBarConfig(prevState => {
1368
+ return {
1369
+ ...prevState,
1370
+ values: {
1371
+ ...prevState.values,
1372
+ [key]: filterValue,
1373
+ },
1374
+ };
1375
+ });
1376
+ },
1342
1377
  //Setting multiple value name objects
1343
1378
  toggleArrayObjectValue(key, filterValue) {
1344
1379
  setFilterBarConfig(prevState => {
@@ -1359,6 +1394,17 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1359
1394
  };
1360
1395
  });
1361
1396
  },
1397
+ toggleObjectValue(key, filterValue) {
1398
+ setFilterBarConfig(prevState => {
1399
+ return {
1400
+ ...prevState,
1401
+ values: {
1402
+ ...prevState.values,
1403
+ [key]: filterValue,
1404
+ },
1405
+ };
1406
+ });
1407
+ },
1362
1408
  // Reset filters to initial state
1363
1409
  resetFiltersToInitialState() {
1364
1410
  setFilterBarConfig(prevState => {
@@ -1408,4 +1454,4 @@ const mergeFilters = (filterBarDefinition, extraFilters) => {
1408
1454
  */
1409
1455
  setupLibraryTranslations();
1410
1456
 
1411
- export { DefaultCheckboxFilter, DefaultDateRangeFilter, DefaultMinMaxFilter, DefaultRadioFilter, DynamicFilterList, FilterBar, FilterEvents, FilterHeader, FilterResults, StarredFilters, isAreaFilterValue, isArrayFilterValue, isBooleanValue, isDateRangeValue, isMinMaxFilterValue, isStringArrayFilterValue, isValueNameFilterValue, mergeFilters, mockFilterBar, toggleFilterValue, useFilterBar, validateFilter };
1457
+ export { DefaultCheckboxFilter, DefaultDateRangeFilter, DefaultMinMaxFilter, DefaultRadioFilter, DynamicFilterList, FilterBar, FilterEvents, FilterHeader, FilterResults, StarredFilters, isAreaFilterValue, isArrayFilterValue, isBooleanValue, isDateRangeValue, isMinMaxFilterValue, isStringArrayFilterValue, isValueName, isValueNameArray, mergeFilters, mockFilterBar, toggleFilterValue, useFilterBar, validateFilter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/filters-filter-bar",
3
- "version": "0.0.579",
3
+ "version": "0.0.581",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -18,9 +18,13 @@ interface FilterBarProps<TFilterBarDefinition extends FilterBarDefinition> {
18
18
  * @see FilterBarConfig
19
19
  */
20
20
  filterBarConfig: FilterBarConfig<TFilterBarDefinition> & FilterMapActions & FilterMapGetter;
21
+ /**
22
+ * If true, the starred filters will be displayed in a compact mode
23
+ */
24
+ compact?: boolean;
21
25
  }
22
26
  /**
23
27
  * The FilterBar component serves as a wrapper for managing filters.
24
28
  */
25
- export declare const FilterBar: <TFilterBarDefinition extends FilterBarDefinition>({ hiddenFilters, className, filterBarDefinition, filterBarConfig, }: FilterBarProps<TFilterBarDefinition>) => import("react/jsx-runtime").JSX.Element;
29
+ export declare const FilterBar: <TFilterBarDefinition extends FilterBarDefinition>({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact, }: FilterBarProps<TFilterBarDefinition>) => import("react/jsx-runtime").JSX.Element;
26
30
  export {};
@@ -5,4 +5,4 @@ import { DefaultFilterProps } from "./DefaultFilterTypes";
5
5
  *
6
6
  * @returns {JSX.Element} - Returns the DefaultRadioFilter component.
7
7
  */
8
- export declare const DefaultRadioFilter: ({ filterDefinition, filterBarActions, options, loading, filterName, customSearch, showRequestMoreUseSearch, }: DefaultFilterProps<ValueName[]>) => JSX.Element;
8
+ export declare const DefaultRadioFilter: ({ filterDefinition, filterBarActions, options, loading, filterName, customSearch, showRequestMoreUseSearch, setValue, }: DefaultFilterProps<ValueName>) => JSX.Element;
@@ -1,5 +1,6 @@
1
+ import { CommonProps } from "@trackunit/react-components";
1
2
  import { FilterBarConfig, FilterBarDefinition, FilterMapActions, FilterMapGetter } from "../types/FilterTypes";
2
- interface StarredFiltersProps<TFilterBarDefinition extends FilterBarDefinition> {
3
+ interface StarredFiltersProps<TFilterBarDefinition extends FilterBarDefinition> extends CommonProps {
3
4
  /**
4
5
  * Configuration for the filter bar.
5
6
  */
@@ -12,6 +13,10 @@ interface StarredFiltersProps<TFilterBarDefinition extends FilterBarDefinition>
12
13
  * If you want some of the filters to be hidden, but still programmatically enabled
13
14
  */
14
15
  hiddenFilters?: string[];
16
+ /**
17
+ * If true, the starred filters will be displayed in a compact mode
18
+ */
19
+ compact?: boolean;
15
20
  }
16
21
  /**
17
22
  * StarredFilters is a React component that displays a list of starred filters based on the provided filter bar configuration.
@@ -19,5 +24,5 @@ interface StarredFiltersProps<TFilterBarDefinition extends FilterBarDefinition>
19
24
  * @template TFilterBarDefinition - The type representing the filter bar definition.
20
25
  * @returns {JSX.Element} - Returns the StarredFilters component.
21
26
  */
22
- export declare const StarredFilters: <TFilterBarDefinition extends FilterBarDefinition>({ filterBarDefinition, filterBarConfig, hiddenFilters, }: StarredFiltersProps<TFilterBarDefinition>) => import("react/jsx-runtime").JSX.Element;
27
+ export declare const StarredFilters: <TFilterBarDefinition extends FilterBarDefinition>({ filterBarDefinition, filterBarConfig, hiddenFilters, compact, dataTestId, className, }: StarredFiltersProps<TFilterBarDefinition>) => import("react/jsx-runtime").JSX.Element;
23
28
  export {};
@@ -29,6 +29,7 @@ export declare const useFilterBar: <TFilterBarDefinition extends FilterBarDefini
29
29
  getFilterTitle: (key: string) => string;
30
30
  getFilterBarName: () => string;
31
31
  objectArrayIncludesValue: (key: string, value: string) => boolean;
32
+ objectIncludesValue: (key: string, value: string) => boolean;
32
33
  getValuesByKey: (key: string) => FilterValueType;
33
34
  arrayIncludesValue: (key: string, value: string | boolean) => boolean;
34
35
  appliedFilterKeys: (keyof FilterBarDefinition)[];
@@ -44,7 +45,9 @@ export declare const useFilterBar: <TFilterBarDefinition extends FilterBarDefini
44
45
  setDateRange: (key: string, value: DateRangeValue) => void;
45
46
  setMinMaxValue: (key: string, minMaxValue: MinMaxFilterValue) => void;
46
47
  setArrayObjectValue: (key: string, value: ValueName[]) => void;
48
+ setObjectValue: (key: string, value: ValueName) => void;
47
49
  toggleArrayObjectValue: (key: string, value: ValueName) => void;
50
+ toggleObjectValue: (key: string, value: ValueName) => void;
48
51
  resetFiltersToInitialState: () => void;
49
52
  resetIndividualFilterToInitialState: (key: string) => void;
50
53
  name: string;
@@ -14,8 +14,8 @@ export type MinMaxFilterValue = {
14
14
  min?: number;
15
15
  max?: number;
16
16
  };
17
- export type FilterValueType = string[] | ValueName[] | MinMaxFilterValue | GeoJsonPolygon | string | number | BooleanValue | undefined;
18
- export declare type FilterTypes = "boolean" | "string" | "number" | "dateRange" | "area" | "valueName" | "stringArray" | "minMax";
17
+ export type FilterValueType = string[] | ValueName[] | ValueName | MinMaxFilterValue | GeoJsonPolygon | string | number | BooleanValue | undefined;
18
+ export declare type FilterTypes = "boolean" | "string" | "number" | "dateRange" | "area" | "valueNameArray" | "valueName" | "stringArray" | "minMax";
19
19
  export type FilterMapActions = {
20
20
  updateStarredFilters: (filterkey: string) => void;
21
21
  toggleArrayValue: (key: string, value: string) => void;
@@ -27,7 +27,9 @@ export type FilterMapActions = {
27
27
  setDateRange: (key: string, value: DateRangeValue) => void;
28
28
  setMinMaxValue: (key: string, minMaxValue: MinMaxFilterValue) => void;
29
29
  setArrayObjectValue: (key: string, value: ValueName[]) => void;
30
+ setObjectValue: (key: string, value: ValueName) => void;
30
31
  toggleArrayObjectValue: (key: string, value: ValueName) => void;
32
+ toggleObjectValue: (key: string, value: ValueName) => void;
31
33
  resetFiltersToInitialState: () => void;
32
34
  resetIndividualFilterToInitialState: (key: string) => void;
33
35
  };
@@ -35,6 +37,7 @@ export type FilterMapGetter = {
35
37
  getFilterTitle: (key: string) => string;
36
38
  getFilterBarName: () => string;
37
39
  objectArrayIncludesValue: (key: string, value: string) => boolean;
40
+ objectIncludesValue: (key: string, value: string) => boolean;
38
41
  getValuesByKey: (key: string) => FilterValueType;
39
42
  arrayIncludesValue: (key: string, value: string | boolean) => boolean;
40
43
  readonly appliedFilterKeys: (keyof FilterBarDefinition)[];
@@ -109,12 +112,18 @@ export interface AreaFilterDefinition extends AbstractFilterDefinition {
109
112
  valueAsText?: (value: GeoJsonPolygon) => string;
110
113
  component: (filterComponentProps: FilterViewProps<GeoJsonPolygon>) => React.ReactNode;
111
114
  }
112
- export interface ValueNameFilterDefinition extends AbstractFilterDefinition {
113
- type: "valueName";
115
+ export interface ValueNameArrayFilterDefinition extends AbstractFilterDefinition {
116
+ type: "valueNameArray";
114
117
  defaultValue?: ValueName[];
115
118
  valueAsText?: (value: ValueName[]) => string[];
116
119
  component: (filterComponentProps: FilterViewProps<ValueName[]>) => React.ReactNode;
117
120
  }
121
+ export interface ValueNameFilterDefinition extends AbstractFilterDefinition {
122
+ type: "valueName";
123
+ defaultValue?: ValueName;
124
+ valueAsText?: (value: ValueName) => string;
125
+ component: (filterComponentProps: FilterViewProps<ValueName>) => React.ReactNode;
126
+ }
118
127
  export interface StringArrayFilterDefinition extends AbstractFilterDefinition {
119
128
  type: "stringArray";
120
129
  defaultValue?: string[];
@@ -153,7 +162,7 @@ export interface DateRangeFilterDefinition extends AbstractFilterDefinition {
153
162
  valueAsText?: (value: DateRangeValue) => string;
154
163
  component: (filterComponentProps: FilterViewProps<DateRangeValue>) => React.ReactNode;
155
164
  }
156
- export type FilterDefinition = BooleanFilterDefinition | MinMaxFilterDefinition | StringFilterDefinition | NumberFilterDefinition | DateRangeFilterDefinition | AreaFilterDefinition | ValueNameFilterDefinition | StringArrayFilterDefinition;
165
+ export type FilterDefinition = BooleanFilterDefinition | MinMaxFilterDefinition | StringFilterDefinition | NumberFilterDefinition | DateRangeFilterDefinition | AreaFilterDefinition | ValueNameFilterDefinition | ValueNameArrayFilterDefinition | StringArrayFilterDefinition;
157
166
  export type FilterDefinitionValue = NonNullable<FilterDefinition["defaultValue"]>;
158
167
  export interface FilterViewProps<TReturnType> {
159
168
  filterDefinition: FilterDefinition;
@@ -169,7 +178,7 @@ export type FiltersStoreInstance<T extends FilterBarDefinition> = Partial<Filter
169
178
  export interface FilterStateMap<T extends FilterBarDefinition> {
170
179
  filtersMap: Record<string, FilterBarConfig<T>>;
171
180
  }
172
- export type FilterBarInferredValue<T extends FilterBarDefinition, K extends keyof T> = T[K]["type"] extends infer Type ? Type extends "boolean" ? BooleanValue | undefined : Type extends "string" ? string | undefined : Type extends "number" ? number | undefined : Type extends "area" ? GeoJsonPolygon | undefined : Type extends "valueName" ? ValueName[] | undefined : Type extends "stringArray" ? string[] | undefined : Type extends "minMax" ? MinMaxFilterValue | undefined : Type extends "dateRange" ? DateRangeValue | undefined : undefined : undefined;
181
+ export type FilterBarInferredValue<T extends FilterBarDefinition, K extends keyof T> = T[K]["type"] extends infer Type ? Type extends "boolean" ? BooleanValue | undefined : Type extends "string" ? string | undefined : Type extends "number" ? number | undefined : Type extends "area" ? GeoJsonPolygon | undefined : Type extends "valueNameArray" ? ValueName[] | undefined : Type extends "valueName" ? ValueName | undefined : Type extends "stringArray" ? string[] | undefined : Type extends "minMax" ? MinMaxFilterValue | undefined : Type extends "dateRange" ? DateRangeValue | undefined : undefined : undefined;
173
182
  export type SetterKey<T extends string> = `set${Capitalize<T>}`;
174
183
  export type ReverseSetterKey<T extends string> = Uncapitalize<T extends `set${infer U}` ? U : never>;
175
184
  export type ExtractFilterKeys<T extends FilterBarDefinition> = Extract<keyof T, string>;
@@ -6,8 +6,14 @@ type InitialTypes = {
6
6
  Arg: "stringArray";
7
7
  Init: string[];
8
8
  } | {
9
- Arg: "valueName";
9
+ Arg: "valueNameArray";
10
10
  Init: ValueName[];
11
+ } | {
12
+ Arg: "valueName";
13
+ Init: {
14
+ value: undefined;
15
+ name: undefined;
16
+ };
11
17
  } | {
12
18
  Arg: "minMax";
13
19
  Init: {
@@ -28,9 +28,13 @@ export declare const isStringArrayFilterValue: (value: FilterDefinitionValue) =>
28
28
  */
29
29
  export declare const isBooleanValue: (value: FilterDefinitionValue) => value is BooleanValue;
30
30
  /**
31
- *
31
+ * Type guard to check if a value is a single ValueName object
32
+ */
33
+ export declare const isValueName: (value: unknown) => value is ValueName;
34
+ /**
35
+ * Type guard to check if a value is an array of ValueName objects
32
36
  */
33
- export declare const isValueNameFilterValue: (value: FilterDefinitionValue) => value is ValueName[];
37
+ export declare const isValueNameArray: (value: FilterDefinitionValue) => value is ValueName[];
34
38
  /**
35
39
  * Validates a filter configuration against filter definitions.
36
40
  *