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