@trackunit/filters-filter-bar 1.3.151 → 1.3.154

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.esm.js CHANGED
@@ -1,16 +1,16 @@
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, Popover, PopoverTrigger, PopoverContent, MenuList, cvaInteractableItem, useViewportBreakpoints, Tooltip, Icon, IconButton, Card, CardBody, CardFooter } from '@trackunit/react-components';
3
+ import { VirtualizedList, Text, Button, useViewportBreakpoints, Popover, PopoverTrigger, Tooltip, Icon, PopoverContent, Card, CardBody, MoreMenu, IconButton, MenuList } from '@trackunit/react-components';
4
4
  import { useAnalytics, useTextSearch, useCurrentUser } from '@trackunit/react-core-hooks';
5
- import { FilterBody, RadioFilterItem, CheckBoxFilterItem, FilterHeader as FilterHeader$1, FilterFooter, Filter as Filter$1 } from '@trackunit/react-filter-components';
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';
7
7
  import { capitalize } from 'string-ts';
8
8
  import { createEvent } from '@trackunit/react-core-contexts-api';
9
- import { Search, NumberField, RadioGroup, ToggleSwitchOption } from '@trackunit/react-form-components';
9
+ import { Search, NumberField, RadioGroup } from '@trackunit/react-form-components';
10
10
  import { DayRangePicker } from '@trackunit/react-date-and-time-components';
11
11
  import { nonNullable, capitalize as capitalize$1, objectValues, truthy, objectKeys } from '@trackunit/shared-utils';
12
- import { cvaMerge } from '@trackunit/css-class-variance-utilities';
13
12
  import { twMerge } from 'tailwind-merge';
13
+ import { cvaMerge } from '@trackunit/css-class-variance-utilities';
14
14
  import { dequal } from 'dequal';
15
15
  import isEqual from 'lodash/isEqual';
16
16
  import { geoJsonPolygonSchema, geoJsonMultiPolygonSchema } from '@trackunit/geo-json-utils';
@@ -126,7 +126,7 @@ var defaultTranslations = {
126
126
  "filtersBar.defaultMinMaxFilters.min": "Min",
127
127
  "filtersBar.deselectAll": "Deselect All",
128
128
  "filtersBar.emptyResults": "No results were found for your search term.",
129
- "filtersBar.filtersHeading": "Filters",
129
+ "filtersBar.filtersHeading": "Filter",
130
130
  "filtersBar.groups.ASSET": "Asset",
131
131
  "filtersBar.groups.CUSTOM_FIELDS": "Custom Fields",
132
132
  "filtersBar.groups.CUSTOMERS": "Customers",
@@ -145,10 +145,15 @@ var defaultTranslations = {
145
145
  "filtersBar.loading": "Loading...",
146
146
  "filtersBar.myNetworkGroup": "My Network",
147
147
  "filtersBar.resetFilter": "Reset",
148
- "filtersBar.resetFilters": "Reset Filters",
148
+ "filtersBar.resetFilters": "Reset all",
149
+ "filtersBar.searchFiltersPlaceholder": "Search filters...",
150
+ "filtersBar.searchPlaceholder": "Search...",
149
151
  "filtersBar.selectAll": "Select All",
152
+ "filtersBar.showAll": "Show all",
150
153
  "filtersBar.starredFilters.title": "Filters",
151
154
  "filtersBar.starredFiltersTitle": "Starred Filters",
155
+ "filtersMenu.appliedFiltersLabel.plural": "{{count}} filters applied",
156
+ "filtersMenu.appliedFiltersLabel.singular": "1 filter applied",
152
157
  "fleetlist.column.activity": "Activity",
153
158
  "fleetlist.column.assetIds": "Asset Ids",
154
159
  "fleetlist.column.assetType": "Asset Type",
@@ -327,6 +332,7 @@ const FilterEvents = {
327
332
  "Starring Filter - Toggled": createEvent({
328
333
  description: "A filter was starred or unstarred",
329
334
  }),
335
+ "Showing Filters - All or Favorites": createEvent(),
330
336
  };
331
337
 
332
338
  /**
@@ -350,11 +356,11 @@ const toggleFilterValue = (value) => {
350
356
  *
351
357
  * @returns {ReactElement} - Returns the DynamicFilterList component.
352
358
  */
353
- const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked, count, showRequestMoreUseSearch = false, type, }) => {
359
+ const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked, count, showRequestMoreUseSearch = false, type, className, }) => {
354
360
  const [t] = useTranslation();
355
361
  const parentRef = useRef(null);
356
362
  const updatedRowCount = useMemo(() => (showRequestMoreUseSearch ? rowCount + 1 : rowCount), [rowCount, showRequestMoreUseSearch]);
357
- return (jsx(FilterBody, { limitSize: true, ref: parentRef, children: jsx(VirtualizedList, { count: updatedRowCount, rowHeight: 40, separator: "space", children: index => {
363
+ return (jsx(FilterBody, { limitSize: true, ref: parentRef, children: jsx(VirtualizedList, { className: className, count: updatedRowCount, rowHeight: 40, separator: "space", children: index => {
358
364
  return (jsx("div", { children: showRequestMoreUseSearch && index === rowCount ? (jsxs("div", { className: "p-3 pt-2", children: [jsx("span", { className: "text-sm text-gray-600", children: t("filter.more.options.if.you.search.title", { count: rowCount }) }), jsx("br", {}), jsx("span", { className: "text-sm italic text-gray-400", children: t("filter.more.options.if.you.search.description") })] })) : type === "Radio" ? (jsx(RadioFilterItem, { dataTestId: "dynamic-filter-radio-" + keyMapper(index), itemCount: count(index), label: labelMapper(index), selected: checked(index), value: keyMapper(index) }, keyMapper(index))) : (jsx(CheckBoxFilterItem, { checked: checked(index), dataTestId: "dynamic-filter-check-box-" + keyMapper(index), itemCount: count(index), label: labelMapper(index), name: keyMapper(index), onChange: () => {
359
365
  onChange?.(index);
360
366
  } }, keyMapper(index))) }));
@@ -366,17 +372,26 @@ const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked
366
372
  *
367
373
  * @returns {ReactElement} - Returns the FilterHeader component.
368
374
  */
369
- const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanges, resetIndividualFilterToInitialState, onResetFilter, loading = false, children, className, dataTestId, }) => {
375
+ const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanges, resetIndividualFilterToInitialState, onResetFilter, loading = false, className, dataTestId, hideHeader = false, children, }) => {
370
376
  const [t] = useTranslation();
371
377
  const handleResetFilter = () => {
372
378
  resetIndividualFilterToInitialState(filterKey);
373
379
  onResetFilter?.();
374
380
  };
375
- return (jsxs(FilterHeader$1, { className: className, dataTestId: dataTestId ?? `${filterKey}-filter-header`, loading: loading, onReset: handleResetFilter, resetLabel: t("filtersBar.resetFilter"), showReset: filterHasChanges, title: title, children: [searchEnabled ? (jsx(Search, { autoFocus: true, fieldSize: "small", id: `${filterKey}-search`, onChange: e => searchProps.onChange(e.currentTarget.value), onKeyDown: e => {
376
- if (e.key === "Enter" && searchProps.onEnter) {
377
- searchProps.onEnter(searchProps.value);
378
- }
379
- }, placeholder: searchProps.placeholder ?? t("assetFilters.searchPlaceholder"), suffix: jsx(Text, { size: "small", subtle: true, children: searchProps.count }), value: searchProps.value })) : null, children] }));
381
+ return (jsx(FilterHeader$1, { className: className, dataTestId: dataTestId ?? `${filterKey}-filter-header`, loading: loading, onReset: handleResetFilter, resetLabel: t("filtersBar.resetFilter"), searchComponent: searchEnabled ? jsx(FilterHeaderSearchComponent, { filterKey: filterKey, searchProps: searchProps }) : undefined, showReset: filterHasChanges, title: hideHeader ? undefined : title, children: children }));
382
+ };
383
+ /**
384
+ * FilterHeaderSearchComponent renders a search input for the FilterHeader component.
385
+ */
386
+ const FilterHeaderSearchComponent = ({ searchProps, filterKey, }) => {
387
+ const [t] = useTranslation();
388
+ return (jsx(Search, { autoFocus: true, className: "w-full", fieldSize: "small", id: `${filterKey}-search`, onChange: e => searchProps.onChange(e.currentTarget.value), onClear: () => {
389
+ searchProps.onClear?.();
390
+ }, onKeyDown: e => {
391
+ if (e.key === "Enter" && searchProps.onEnter) {
392
+ searchProps.onEnter(searchProps.value);
393
+ }
394
+ }, placeholder: searchProps.placeholder ?? t("filtersBar.searchPlaceholder"), suffix: jsx(Text, { size: "small", subtle: true, children: searchProps.count }), value: searchProps.value }));
380
395
  };
381
396
 
382
397
  /**
@@ -396,7 +411,7 @@ const FilterResults = ({ results, ignoreUndefined, loading, children, }) => {
396
411
  };
397
412
  const EmptyResults = ({ loading }) => {
398
413
  const [t] = useTranslation();
399
- return (jsx("div", { className: "grid h-20 w-full place-content-center pl-2 pr-2", "data-testid": "empty-filter-results", children: jsx(Text, { align: "center", italicize: true, subtle: true, children: loading ? t("filtersBar.loading") : t("filtersBar.emptyResults") }) }));
414
+ return (jsx("div", { className: "grid h-20 w-[280px] place-content-center pl-2 pr-2", "data-testid": "empty-filter-results", children: jsx(Text, { align: "center", className: "w-full break-words", italicize: true, subtle: true, children: loading ? t("filtersBar.loading") : t("filtersBar.emptyResults") }) }));
400
415
  };
401
416
 
402
417
  /**
@@ -409,7 +424,6 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
409
424
  const { logEvent } = useAnalytics(FilterEvents);
410
425
  const [hasSelectedAll, setHasSelectedAll] = useState(false);
411
426
  const { t } = useTranslation();
412
- const [selectedCount, setSelectedCount] = useState(0);
413
427
  const handleSetValue = (value) => {
414
428
  logEvent("Filters Applied - V2", {
415
429
  type: filterName ?? `${capitalize(filterDefinition.filterKey)}Filter`,
@@ -474,19 +488,18 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
474
488
  useEffect(() => {
475
489
  const values = filterBarActions.getValuesByKey(filterDefinition.filterKey);
476
490
  if (Array.isArray(values)) {
477
- setSelectedCount(values.length);
478
- if (values.length === 0) {
479
- setHasSelectedAll(false);
491
+ const optionsLength = undefinedCount ? options.length - 1 : options.length;
492
+ if (values.length === optionsLength) {
493
+ setHasSelectedAll(true);
480
494
  }
481
495
  else {
482
- setHasSelectedAll(true);
496
+ setHasSelectedAll(false);
483
497
  }
484
498
  }
485
499
  else {
486
- setSelectedCount(0);
487
500
  setHasSelectedAll(true);
488
501
  }
489
- }, [filterBarActions, filterDefinition.filterKey, undefinedCount]);
502
+ }, [filterBarActions, filterDefinition.filterKey, options.length, undefinedCount]);
490
503
  const handleSelectAll = () => {
491
504
  if (hasSelectedAll) {
492
505
  setMultipleValues([]);
@@ -504,19 +517,20 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
504
517
  return (jsxs(Fragment, { children: [jsx(FilterHeader, { ...filterDefinition, ...filterBarActions, filterHasChanges: filterBarActions.appliedFilterKeys().includes(filterDefinition.filterKey), loading: loading, searchEnabled: true, searchProps: {
505
518
  value: customSearch?.value ?? searchText,
506
519
  onChange: customSearch?.onChange ?? setSearchText,
520
+ onClear: customSearch?.onClear ?? (() => setSearchText("")),
507
521
  count: undefinedCount ? filteredOptions.length - 1 : filteredOptions.length,
508
- } }), options.length >= 2 ? (jsxs(Button, { className: "mb-1 ml-1 mt-1", dataTestId: "selectAllButton", onClick: handleSelectAll, size: "small", variant: "ghost", children: [hasSelectedAll ? t("filtersBar.deselectAll") : t("filtersBar.selectAll"), " (", selectedCount || (undefinedCount ? options.length - 1 : options.length), ")"] })) : null, jsx(FilterResults, { ignoreUndefined: undefinedCount !== null, loading: loading, results: results, children: res => (jsx(DynamicFilterList, { checked: index => {
522
+ }, children: options.length >= 2 ? (jsx(Button, { className: "place-self-start", dataTestId: "selectAllButton", disabled: hasSelectedAll, onClick: handleSelectAll, size: "small", variant: "ghost", children: t("filtersBar.selectAll") })) : null }), jsx(FilterResults, { ignoreUndefined: undefinedCount !== null, loading: loading, results: results, children: res => (jsx(DynamicFilterList, { checked: index => {
509
523
  return filterDefinition.type === "valueNameArray"
510
524
  ? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, res[index]?.key || "")
511
525
  : filterBarActions.arrayIncludesValue(filterDefinition.filterKey, res[index]?.key || "");
512
- }, count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", onChange: index => {
526
+ }, className: "mb-1", count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", onChange: index => {
513
527
  const result = res[index];
514
528
  if (result) {
515
529
  handleSetValue(result);
516
530
  }
517
531
  }, rowCount: undefinedCount ? res.length - 1 : res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "CheckBox" })) }), showUndefinedOptionWithCountAtBottom && undefinedCount ? (jsx("div", { className: "m-1", children: jsx(CheckBoxFilterItem, { checked: filterDefinition.type === "valueNameArray"
518
532
  ? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, results[undefinedCount.index]?.key || "")
519
- : filterBarActions.arrayIncludesValue(filterDefinition.filterKey, results[undefinedCount.index]?.key || ""), className: "rounded-none border-t-2", dataTestId: "dynamic-filter-check-box-undefined", itemCount: undefinedCount.count, label: results[undefinedCount.index]?.label, name: "dynamic-filter-check-box-undefined", onChange: () => {
533
+ : filterBarActions.arrayIncludesValue(filterDefinition.filterKey, results[undefinedCount.index]?.key || ""), dataTestId: "dynamic-filter-check-box-undefined", itemCount: undefinedCount.count, label: results[undefinedCount.index]?.label, name: "dynamic-filter-check-box-undefined", onChange: () => {
520
534
  const result = results[undefinedCount.index];
521
535
  if (result) {
522
536
  handleSetValue(result);
@@ -621,10 +635,282 @@ const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loadi
621
635
  return (jsxs(Fragment, { children: [jsx(FilterHeader, { ...filterBarActions, ...filterDefinition, filterHasChanges: filterBarActions.appliedFilterKeys().includes(filterDefinition.filterKey), loading: loading, searchEnabled: true, searchProps: {
622
636
  value: customSearch?.value ?? searchText,
623
637
  onChange: customSearch?.onChange ?? setSearchText,
638
+ onClear: customSearch?.onClear ?? (() => setSearchText("")),
624
639
  count: filteredOptions.length,
625
640
  } }), jsx(FilterResults, { loading: loading, results: customSearch ? options : filteredOptions, children: res => (jsx(RadioGroup, { id: "DefaultRadioFilter", onChange: e => {
626
641
  handleClick(e.currentTarget.value);
627
- }, value: selectedRadioId?.key || "", children: jsx(DynamicFilterList, { checked: index => filterBarActions.objectIncludesValue(filterDefinition.filterKey, res[index]?.key || ""), count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
642
+ }, 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
+
645
+ /**
646
+ * Returns the two first values, appends counter if more.
647
+ *
648
+ * @param {string[]} [input] Array of values to reduce
649
+ * @returns {*} {string}
650
+ */
651
+ const reduceFilterText = (input) => {
652
+ if (!Array.isArray(input)) {
653
+ return input;
654
+ }
655
+ // Only first two elements
656
+ const firstTwo = input.slice(0, 2);
657
+ const remainder = input.slice(2, input.length).length;
658
+ const firstTwoJoined = firstTwo.join(", ");
659
+ const remainderStr = remainder > 0 ? `, +${remainder}` : "";
660
+ return firstTwoJoined + remainderStr;
661
+ };
662
+
663
+ /**
664
+ * Filter is a React component that renders a filter element based on the provided filter definition and state.
665
+ *
666
+ * @returns {ReactElement} - Returns the Filter component.
667
+ */
668
+ const FilterComponent = ({ filter, filterBarActions, filterState, readOnly = false, visualStyle = "button", className, asIcon, size, }) => {
669
+ const values = filterBarActions.getValuesByKey(filter.filterKey);
670
+ const valuesLength = values && Array.isArray(values) ? values.length : undefined;
671
+ const getFilterText = () => {
672
+ if (!values) {
673
+ return undefined;
674
+ }
675
+ if (filter.valueAsText) {
676
+ return reduceFilterText(filter.valueAsText(values));
677
+ }
678
+ else if (filter.type === "valueNameArray") {
679
+ return reduceFilterText(values.map(value => value.name));
680
+ }
681
+ else if (filter.type === "valueName") {
682
+ return values.name;
683
+ }
684
+ return values.toString();
685
+ };
686
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
687
+ const setValue = (callback) => {
688
+ const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
689
+ if (filter.type === "string") {
690
+ filterBarActions.setStringValue(filter.filterKey, newValue);
691
+ }
692
+ else if (filter.type === "stringArray") {
693
+ filterBarActions.setArrayValue(filter.filterKey, newValue);
694
+ }
695
+ else if (filter.type === "dateRange") {
696
+ filterBarActions.setDateRange(filter.filterKey, newValue);
697
+ }
698
+ else if (filter.type === "area") {
699
+ filterBarActions.setArea(newValue);
700
+ }
701
+ else if (filter.type === "valueNameArray") {
702
+ filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
703
+ }
704
+ else if (filter.type === "valueName") {
705
+ filterBarActions.setObjectValue(filter.filterKey, newValue);
706
+ }
707
+ else if (filter.type === "minMax") {
708
+ filterBarActions.setMinMaxValue(filter.filterKey, newValue);
709
+ }
710
+ else if (filter.type === "boolean") {
711
+ filterBarActions.setBooleanValue(filter.filterKey, newValue);
712
+ }
713
+ else {
714
+ filterBarActions.setNumberValue(filter.filterKey, newValue);
715
+ }
716
+ };
717
+ const text = getFilterText();
718
+ const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
719
+ const showDirectly = filter.showDirectly || false;
720
+ return showDirectly ? (jsx(Fragment, { children: filter.component({
721
+ filterDefinition: filter,
722
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
723
+ value: values,
724
+ setValue,
725
+ filterBarActions,
726
+ filterState,
727
+ }) })) : (jsx(Filter, { activeLabel: activeFilterText, activeOptionsCount: valuesLength, asIcon: asIcon, className: className, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterBarActions.appliedFilterKeys().includes(filter.filterKey), popoverProps: { placement: visualStyle === "list-item" ? "right-start" : "bottom-start" }, readOnly: readOnly, size: size, title: filter.title, visualStyle: visualStyle, withStickyHeader: true, children: filter.component({
728
+ filterDefinition: filter,
729
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
730
+ value: values,
731
+ setValue,
732
+ filterBarActions,
733
+ filterState,
734
+ }) }));
735
+ };
736
+
737
+ /**
738
+ * Custom React hook for grouping and filtering a list of filters.
739
+ *
740
+ * @param {FilterDefinition[]} filterDefinitions - The list of filter definitions to group and filter.
741
+ * @param {string[]} hiddenFilters - An array of filter keys representing filters to be hidden.
742
+ * @returns {UseGroupFiltersReturnType} An object containing the grouped filters.
743
+ */
744
+ const useGroupFilters = (filterDefinitions, hiddenFilters) => {
745
+ const [t] = useTranslation();
746
+ const filtersGrouped = useMemo(() => {
747
+ const keys = uniqueKeysFromGroups(filterDefinitions);
748
+ return keys
749
+ .map(key => ({
750
+ key,
751
+ title: t(`filtersBar.groups.${key}`),
752
+ filters: filterDefinitions.filter(rawFilter => rawFilter.group === key && hiddenFilters.includes(rawFilter.filterKey) === false),
753
+ }))
754
+ .filter(filter => filter.filters.length > 0);
755
+ }, [filterDefinitions, hiddenFilters, t]);
756
+ return useMemo(() => ({ filtersGrouped }), [filtersGrouped]);
757
+ };
758
+ const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
759
+
760
+ /**
761
+ * FiltersRenderer renders an array of Filter components from filter definitions
762
+ * It ignores hidden filters.
763
+ *
764
+ * Note: This component does not provide any styling or layout - use a wrapper for proper list styling.
765
+ *
766
+ * @param {object} props - The component props
767
+ * @param {FilterDefinition[]} props.filters - Array of filter definitions to render
768
+ * @param {FilterBarConfig & FilterMapActions & FilterMapGetter} props.filterBarConfig - The filter bar configuration
769
+ * @param {ComponentProps<typeof FilterComponent>["visualStyle"]} [props.visualStyle] - The visual style of the filters
770
+ * @returns {ReactElement[] | null} - Returns an array of Filter components or null
771
+ */
772
+ const FiltersRenderer = ({ filters, filterBarConfig, visualStyle, }) => {
773
+ return filters.length === 0
774
+ ? null
775
+ : filters
776
+ .filter(filter => !filter.isHidden)
777
+ .map(filter => (jsx(FilterComponent, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters }, visualStyle: visualStyle }, `filter-${filter.filterKey}`)));
778
+ };
779
+
780
+ /**
781
+ * FiltersList is a React component that displays a list of filters within a filter bar.
782
+ *
783
+ * @returns {ReactElement} - Returns the FiltersList component.
784
+ */
785
+ const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, dataTestId = "grouped-filters-list", }) => {
786
+ return (jsx("div", { className: className, "data-testid": dataTestId, role: "menu", children: filtersGrouped.map((group, idx) => {
787
+ const isLastGroup = idx === filtersGrouped.length - 1;
788
+ 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));
789
+ }) }));
790
+ };
791
+
792
+ /**
793
+ * ResetFiltersButton is a React component that provides a button for resetting filters.
794
+ *
795
+ * @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
796
+ */
797
+ const ResetFiltersButton = ({ resetFiltersToInitialState, dataTestId, className, }) => {
798
+ const [t] = useTranslation();
799
+ return (jsxs(Button, { className: className, dataTestId: dataTestId ?? "reset-filters-button", onClick: () => {
800
+ resetFiltersToInitialState();
801
+ }, size: "small", variant: "ghost", children: [t("filtersBar.resetFilters"), jsx("span", { className: "sr-only", children: "Resets all applied filters" })] }));
802
+ };
803
+
804
+ /**
805
+ * FilterMenu is a React component that displays a list of filters in a popover menu based on the provided filter bar configuration.
806
+ *
807
+ * @template TFilterBarDefinition - The type representing the filter bar definition.
808
+ * @returns {ReactElement} - Returns the FilterMenu component.
809
+ */
810
+ const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact, title, dataTestId = "filters-menu", className, }) => {
811
+ const [t] = useTranslation();
812
+ const { isSm } = useViewportBreakpoints();
813
+ const [showCustomFilters, setShowCustomFilters] = useState(false);
814
+ // TODO: Add analytics if event requirements are defined
815
+ // const { logEvent } = useAnalytics(FilterEvents);
816
+ const hideInMenu = useMemo(() => {
817
+ return objectValues(filterBarDefinition)
818
+ .map(filter => {
819
+ const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
820
+ const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true; // TODO: Starred menu concept should be completely removed everywhere
821
+ const showMenuAnywayBecauseFilterHasChanged = filterBarConfig.appliedFilterKeys().includes(filter.filterKey);
822
+ return (!showInFilterBar || !showInStarredMenu) && !showMenuAnywayBecauseFilterHasChanged
823
+ ? filter.filterKey
824
+ : null;
825
+ })
826
+ .filter(truthy);
827
+ }, [filterBarConfig, filterBarDefinition]);
828
+ const { filtersGrouped } = useGroupFilters(objectValues(filterBarDefinition), [...hideInMenu, ...hiddenFilters]);
829
+ const { appliedFilters, filtersToShow, showDirectlyFilters, hasCustomFields } = useMemo(() => {
830
+ const allFilters = filtersGrouped.map(group => group.filters).flat();
831
+ return {
832
+ appliedFilters: allFilters.filter(filter => filterBarConfig.appliedFilterKeys().includes(filter.filterKey)),
833
+ filtersToShow: allFilters.filter(filter => !filter.showDirectly),
834
+ showDirectlyFilters: allFilters.filter(filter => filter.showDirectly),
835
+ hasCustomFields: allFilters.some(filter => filter.group === "CUSTOM_FIELDS"),
836
+ };
837
+ }, [filterBarConfig, filtersGrouped]);
838
+ const [searchResults, searchText, setSearchText] = useTextSearch(filtersToShow, item => [item.title]);
839
+ const { filtersGrouped: searchResultsGrouped } = useGroupFilters(searchResults, []);
840
+ const { filtersGrouped: filtersToShowGrouped } = useGroupFilters(filtersToShow, []);
841
+ const appliedCustomFields = useMemo(() => appliedFilters.filter(filter => filter.group === "CUSTOM_FIELDS"), [appliedFilters]);
842
+ return (jsxs("div", { className: twMerge("flex items-center gap-2", className), "data-testid": dataTestId, children: [jsx(Popover, { onOpenStateChange: open => {
843
+ if (!open) {
844
+ setShowCustomFilters(false);
845
+ setSearchText("");
846
+ }
847
+ }, placement: "bottom-start", children: modalState => {
848
+ 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(FilterButtonTooltipLabel, { 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
849
+ ? searchResultsGrouped
850
+ : showCustomFilters
851
+ ? filtersToShowGrouped
852
+ : removeCustomFields(filtersToShowGrouped) }), hasCustomFields && !showCustomFilters && !searchText ? (jsx(CustomFieldsHiddenGroup, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, onShow: () => {
853
+ setShowCustomFilters(true);
854
+ } })) : null] })] }) })] }));
855
+ } }), 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] }));
856
+ };
857
+ const Separator = () => jsx("hr", { className: "border-secondary-200", role: "separator" });
858
+ const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
859
+ const [t] = useTranslation();
860
+ switch (filterBarConfig.appliedFilterKeys().length) {
861
+ case 0:
862
+ return t("filtersBar.appliedFiltersTooltip.none");
863
+ case 1:
864
+ return filterBarConfig.appliedFilterKeys()[0]
865
+ ? t("filtersBar.appliedFiltersTooltip.singular", {
866
+ filterName: filterBarConfig.getFilterTitle(filterBarConfig.appliedFilterKeys()[0]),
867
+ })
868
+ : null; // should never happen though
869
+ default:
870
+ 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))) })] }));
871
+ }
872
+ };
873
+ const FiltersAppliedCountLabel = ({ filterBarConfig, }) => {
874
+ const [t] = useTranslation();
875
+ switch (filterBarConfig.appliedFilterKeys().length) {
876
+ case 0:
877
+ return t("filtersBar.appliedFiltersTooltip.none");
878
+ case 1:
879
+ return filterBarConfig.appliedFilterKeys()[0] ? t("filtersMenu.appliedFiltersLabel.singular") : null;
880
+ default:
881
+ return jsx(Fragment, { children: t("filtersMenu.appliedFiltersLabel.plural", { count: filterBarConfig.appliedFilterKeys().length }) });
882
+ }
883
+ };
884
+ const CustomFieldsHiddenGroup = ({ appliedCustomFields, filterBarConfig, onShow, }) => {
885
+ const [t] = useTranslation();
886
+ 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] })] }));
887
+ };
888
+ const removeCustomFields = (filtersGrouped) => filtersGrouped.filter(group => group.key !== "CUSTOM_FIELDS");
889
+
890
+ /**
891
+ * Filter is a React component that renders a filter element based on the provided filter definition and state.
892
+ *
893
+ * @returns {ReactElement} - Returns the Filter component.
894
+ */
895
+ const FilterTableComponent = ({ filterKey, filterBarDefinition, filterBarConfig, }) => {
896
+ const ensureFilterKey = useCallback((key) => {
897
+ // eslint-disable-next-line local-rules/no-typescript-assertion
898
+ return key;
899
+ }, []);
900
+ const filters = useMemo(() => {
901
+ if (Array.isArray(filterKey)) {
902
+ return filterKey.map(key => filterBarDefinition[ensureFilterKey(key)]).filter(Boolean);
903
+ }
904
+ return [];
905
+ }, [filterKey, filterBarDefinition, ensureFilterKey]);
906
+ if (Array.isArray(filterKey)) {
907
+ return (jsx(MoreMenu, { customButton: jsx(IconButton, { icon: jsx(Icon, { color: filterKey.some(key => filterBarConfig.appliedFilterKeys().includes(key)) ? "primary" : undefined, name: "Filter", size: "small" }), onClick: event => event.stopPropagation(), size: "extraSmall", variant: "ghost-neutral" }), children: () => (jsx(MenuList, { children: filters.map((value, index) => value && (jsx(FilterComponent, { filter: value, filterBarActions: filterBarConfig, filterState: filterBarConfig, visualStyle: "list-item" }, index))) })) }));
908
+ }
909
+ const filter = filterBarDefinition[ensureFilterKey(filterKey)];
910
+ if (!filter) {
911
+ return null;
912
+ }
913
+ return (jsx("div", { onClick: event => event.stopPropagation(), children: jsx(FilterComponent, { asIcon: "Filter", filter: filter, filterBarActions: filterBarConfig, filterState: filterBarConfig, size: "extraSmall" }) }));
628
914
  };
629
915
 
630
916
  /**
@@ -852,263 +1138,11 @@ const HierarchicalCheckboxFilter = ({ filterDefinition, filterBarActions, option
852
1138
  return (jsxs(Fragment, { children: [jsx(FilterHeader, { ...filterDefinition, ...filterBarActions, filterHasChanges: filterBarActions.appliedFilterKeys().includes(filterDefinition.filterKey), loading: loading, ...searchHeaderProps }), jsx("div", { className: "max-h-60 overflow-y-auto", children: jsx(FilterResults, { loading: loading, results: filteredOptions, children: results => (jsx(Fragment, { children: results.map(option => (jsx(RenderHierarchicalOption, { cascadeSelection: cascadeSelection, filterBarActions: filterBarActions, filterDefinition: filterDefinition, logEvent: logEvent, option: option, optionsMap: optionsMap, selectedValues: selectedValues, setValue: setValue }, option.key))) })) }) })] }));
853
1139
  };
854
1140
 
855
- /**
856
- * Custom React hook for grouping and filtering a list of filters.
857
- *
858
- * @param {FilterDefinition[]} filterDefinitions - The list of filter definitions to group and filter.
859
- * @param {string[]} hiddenFilters - An array of filter keys representing filters to be hidden.
860
- * @returns {UseGroupFiltersReturnType} An object containing the grouped filters.
861
- */
862
- const useStarredGroupFilters = (filterDefinitions, hiddenFilters) => {
863
- const [t] = useTranslation();
864
- const filtersGrouped = useMemo(() => {
865
- const keys = uniqueKeysFromGroups(filterDefinitions);
866
- return keys
867
- .map(key => ({
868
- key,
869
- title: t(`filtersBar.groups.${key}`),
870
- filters: filterDefinitions.filter(rawFilter => rawFilter.group === key && hiddenFilters.includes(rawFilter.filterKey) === false),
871
- }))
872
- .filter(filter => filter.filters.length > 0);
873
- }, [filterDefinitions, hiddenFilters, t]);
874
- return useMemo(() => ({ filtersGrouped }), [filtersGrouped]);
875
- };
876
- const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
877
-
878
- /**
879
- * Returns the two first values, appends counter if more.
880
- *
881
- * @param {string[]} [input] Array of values to reduce
882
- * @returns {*} {string}
883
- */
884
- const reduceFilterText = (input) => {
885
- if (!Array.isArray(input)) {
886
- return input;
887
- }
888
- // Only first two elements
889
- const firstTwo = input.slice(0, 2);
890
- const remainder = input.slice(2, input.length).length;
891
- const firstTwoJoined = firstTwo.join(", ");
892
- const remainderStr = remainder > 0 ? `, +${remainder}` : "";
893
- return firstTwoJoined + remainderStr;
894
- };
895
-
896
- /**
897
- * Filter is a React component that renders a filter element based on the provided filter definition and state.
898
- *
899
- * @returns {ReactElement} - Returns the Filter component.
900
- */
901
- const Filter = ({ filter, filterBarActions, filterState, }) => {
902
- const values = filterBarActions.getValuesByKey(filter.filterKey);
903
- const getFilterText = () => {
904
- if (!values) {
905
- return undefined;
906
- }
907
- if (filter.valueAsText) {
908
- return reduceFilterText(filter.valueAsText(values));
909
- }
910
- else if (filter.type === "valueNameArray") {
911
- return reduceFilterText(values.map(value => value.name));
912
- }
913
- else if (filter.type === "valueName") {
914
- return values.name;
915
- }
916
- return values.toString();
917
- };
918
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
919
- const setValue = (callback) => {
920
- const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
921
- if (filter.type === "string") {
922
- filterBarActions.setStringValue(filter.filterKey, newValue);
923
- }
924
- else if (filter.type === "stringArray") {
925
- filterBarActions.setArrayValue(filter.filterKey, newValue);
926
- }
927
- else if (filter.type === "dateRange") {
928
- filterBarActions.setDateRange(filter.filterKey, newValue);
929
- }
930
- else if (filter.type === "area") {
931
- filterBarActions.setArea(newValue);
932
- }
933
- else if (filter.type === "valueNameArray") {
934
- filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
935
- }
936
- else if (filter.type === "valueName") {
937
- filterBarActions.setObjectValue(filter.filterKey, newValue);
938
- }
939
- else if (filter.type === "minMax") {
940
- filterBarActions.setMinMaxValue(filter.filterKey, newValue);
941
- }
942
- else if (filter.type === "boolean") {
943
- filterBarActions.setBooleanValue(filter.filterKey, newValue);
944
- }
945
- else {
946
- filterBarActions.setNumberValue(filter.filterKey, newValue);
947
- }
948
- };
949
- const text = getFilterText();
950
- const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
951
- const showDirectly = filter.showDirectly || false;
952
- return showDirectly ? (jsx(Fragment, { children: filter.component({
953
- filterDefinition: filter,
954
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
955
- value: values,
956
- setValue,
957
- filterBarActions,
958
- filterState,
959
- }) })) : (jsx(Filter$1, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: Boolean(text?.length), popoverProps: { placement: "right-start" }, title: filter.title, withStickyHeader: true, children: filter.component({
960
- filterDefinition: filter,
961
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
962
- value: values,
963
- setValue,
964
- filterBarActions,
965
- filterState,
966
- }) }));
967
- };
968
-
969
- /**
970
- * ResetFiltersButton is a React component that provides a button for resetting filters.
971
- *
972
- * @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
973
- */
974
- const ResetFiltersButton = ({ filtersHaveBeenApplied, resetFiltersToInitialState, dataTestId, className, }) => {
975
- const [t] = useTranslation();
976
- if (!filtersHaveBeenApplied) {
977
- return null;
978
- }
979
- return (jsx(Button, { className: className, dataTestId: dataTestId ?? "reset-filters-button", onClick: () => {
980
- resetFiltersToInitialState();
981
- }, size: "small", variant: "ghost-neutral", children: t("filtersBar.resetFilters") }));
982
- };
983
-
984
- /**
985
- * StarredFiltersMenu is a React component that displays a menu of starred filters within a filter bar.
986
- *
987
- * @returns {ReactElement} - Returns the StarredFiltersMenu component.
988
- */
989
- const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starredFilterKeys, hiddenFilters = [], className, dataTestId, }) => {
990
- const [t] = useTranslation();
991
- const { logEvent } = useAnalytics(FilterEvents);
992
- const numberOfShowDirectlyFilters = objectValues(filterBarDefinition).filter(filter => filter.showDirectly).length; //TODO: this is a bit of a silly workaround to get the count to match without changing any other logic as I am pressed for time...but it works for now.🙈
993
- const hideInStarredMenu = useMemo(() => {
994
- return objectValues(filterBarDefinition)
995
- .map(filter => {
996
- const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
997
- const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
998
- return filter.showDirectly || !showInStarredMenu || !showInFilterBar ? filter.filterKey : null;
999
- })
1000
- .filter(truthy);
1001
- }, [filterBarDefinition]);
1002
- const { filtersGrouped } = useStarredGroupFilters(objectValues(filterBarDefinition), [
1003
- ...hideInStarredMenu,
1004
- ...hiddenFilters,
1005
- ]);
1006
- const hiddenFiltersCount = useMemo(() => {
1007
- const nonHiddenStarredFilterKeys = starredFilterKeys.filter(key => !hideInStarredMenu.includes(key));
1008
- return (filtersGrouped.map(group => group.filters).flat().length +
1009
- hiddenFilters.length -
1010
- nonHiddenStarredFilterKeys.length +
1011
- numberOfShowDirectlyFilters);
1012
- }, [filtersGrouped, hiddenFilters, starredFilterKeys, hideInStarredMenu, numberOfShowDirectlyFilters]);
1013
- const getHiddenFiltersLabel = () => {
1014
- switch (hiddenFiltersCount) {
1015
- case 0:
1016
- return t("filtersBar.hiddenFilters.toggleFilters");
1017
- case 1:
1018
- return t("filtersBar.hiddenFilters.singular");
1019
- default:
1020
- return t("filtersBar.hiddenFilters.plural", { count: hiddenFiltersCount });
1021
- }
1022
- };
1023
- return (jsxs(Popover, { placement: "bottom-start", children: [jsx(PopoverTrigger, { children: jsx(Button, { className: className, dataTestId: dataTestId ?? "starred-filters-menu", size: "small", variant: "ghost", children: getHiddenFiltersLabel() }) }), jsx(PopoverContent, { children: jsx(MenuList, { className: "overflow-hidden", dataTestId: "starred-filters-menu-popover", children: jsx("div", { className: "max-h-[55dvh] w-72 overflow-y-auto px-3 pb-2", children: filtersGrouped.map((group, idx) => {
1024
- const isLast = idx === filtersGrouped.length - 1;
1025
- return (jsxs("div", { children: [jsx(Text, { className: "pb-1 pt-2", dataTestId: "starred-filters-group-title", size: "small", subtle: true, uppercase: true, children: group.title }), jsx("div", { className: "grid", children: group.filters.map(filter => {
1026
- return !filter.isHidden ? (jsx(ToggleSwitchOption, { className: cvaInteractableItem({
1027
- selected: false,
1028
- cursor: "pointer",
1029
- focused: "auto",
1030
- className: "py-1 pl-2 pr-3",
1031
- }), "data-testid": `${filter.filterKey}-star-filter`, id: `visible-filters-toggle-${filter.filterKey}`, label: filter.title, onChange: toggled => {
1032
- updateStarredFilters(filter.filterKey);
1033
- logEvent("Starring Filter - Toggled", {
1034
- type: filter.filterKey,
1035
- value: toggled.toString(),
1036
- });
1037
- }, showInputFocus: false, size: "medium", toggled: starredFilterKeys.includes(filter.filterKey) }, filter.filterKey)) : null;
1038
- }) }), !isLast && jsx("div", { className: "mt-2 h-[1px] w-full bg-neutral-300" })] }, group.key));
1039
- }) }) }) })] }));
1040
- };
1041
-
1042
- /**
1043
- * StarredFilters is a React component that displays a list of starred filters based on the provided filter bar configuration.
1044
- *
1045
- * @template TFilterBarDefinition - The type representing the filter bar definition.
1046
- * @returns {ReactElement} - Returns the StarredFilters component.
1047
- */
1048
- const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact, dataTestId, className, title, }) => {
1049
- const [t] = useTranslation();
1050
- const { isLg } = useViewportBreakpoints();
1051
- const isCompactMode = useMemo(() => compact ?? !isLg, [compact, isLg]);
1052
- const hideInMenu = useMemo(() => {
1053
- return objectValues(filterBarDefinition)
1054
- .map(filter => {
1055
- const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
1056
- const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
1057
- const showMenuAnywayBecauseFilterHasChanged = filterBarConfig.appliedFilterKeys().includes(filter.filterKey);
1058
- return (!showInFilterBar || !showInStarredMenu) && !showMenuAnywayBecauseFilterHasChanged
1059
- ? filter.filterKey
1060
- : null;
1061
- })
1062
- .filter(truthy);
1063
- }, [filterBarConfig, filterBarDefinition]);
1064
- const { filtersGrouped } = useStarredGroupFilters(objectValues(filterBarDefinition), [
1065
- ...hideInMenu,
1066
- ...hiddenFilters,
1067
- ]);
1068
- const { appliedFilters, filtersToShow, showDirectlyFilters } = useMemo(() => {
1069
- const allFilters = filtersGrouped.map(group => group.filters).flat();
1070
- const starredFilters = allFilters.filter(filter => {
1071
- return (filterBarConfig.starredFilterKeys.includes(filter.filterKey) &&
1072
- !filter.showDirectly);
1073
- });
1074
- return {
1075
- appliedFilters: starredFilters.filter(filter => filterBarConfig.appliedFilterKeys().includes(filter.filterKey)),
1076
- filtersToShow: starredFilters.filter(filter => !filter.showDirectly),
1077
- showDirectlyFilters: allFilters.filter(filter => filter.showDirectly),
1078
- };
1079
- }, [filterBarConfig, filtersGrouped]);
1080
- 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
1081
- ? `(${filterBarConfig.appliedFilterKeys().length})`
1082
- : undefined, variant: "secondary", children: title ? title : 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 sm:w-[350px]", 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] }));
1083
- };
1084
- const FiltersList = ({ filters, filterBarConfig }) => {
1085
- return filters.length === 0
1086
- ? null
1087
- : filters.map(filter => {
1088
- return (jsx(Filter, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters } }, `filter-${filter.filterKey}`));
1089
- });
1090
- };
1091
- const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
1092
- const [t] = useTranslation();
1093
- switch (filterBarConfig.appliedFilterKeys().length) {
1094
- case 0:
1095
- return t("filtersBar.appliedFiltersTooltip.none");
1096
- case 1:
1097
- return filterBarConfig.appliedFilterKeys()[0]
1098
- ? t("filtersBar.appliedFiltersTooltip.singular", {
1099
- filterName: filterBarConfig.getFilterTitle(filterBarConfig.appliedFilterKeys()[0]),
1100
- })
1101
- : null; // should never happen though
1102
- default:
1103
- 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))) })] }));
1104
- }
1105
- };
1106
-
1107
1141
  /**
1108
1142
  * The FilterBar component serves as a wrapper for managing filters.
1109
1143
  */
1110
- const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact, title, }) => {
1111
- return (jsx(StarredFilters, { className: className, compact: compact, dataTestId: `${filterBarConfig.name}-filterbar`, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, title: title }));
1144
+ const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact = true, title, }) => {
1145
+ return (jsx(FiltersMenu, { className: className, compact: compact, dataTestId: `${filterBarConfig.name}-filterbar`, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, title: title }));
1112
1146
  };
1113
1147
 
1114
1148
  // Can't import jest.fn so must define a function that does nothing but mimics the jest.fn
@@ -1924,4 +1958,4 @@ const mergeFilters = (filterBarDefinition, extraFilters) => {
1924
1958
  */
1925
1959
  setupLibraryTranslations();
1926
1960
 
1927
- export { DefaultCheckboxFilter, DefaultDateRangeFilter, DefaultMinMaxFilter, DefaultRadioFilter, DynamicFilterList, FilterBar, FilterEvents, FilterHeader, FilterResults, HierarchicalCheckboxFilter, StarredFilters, areaFilterGeoJsonGeometrySchema, isAreaFilterValue, isArrayFilterValue, isBooleanValue, isDateRangeValue, isMinMaxFilterValue, isStringArrayFilterValue, isValueName, isValueNameArray, mergeFilters, mockFilterBar, toggleFilterValue, useFilterBar, useFilterBarAsync, useSearchParamAsFilter, validateFilter };
1961
+ 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 };