@trackunit/react-filter-components 1.3.130 → 1.3.133

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
@@ -3,6 +3,7 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
5
5
  var reactComponents = require('@trackunit/react-components');
6
+ var react = require('react');
6
7
  var tailwindMerge = require('tailwind-merge');
7
8
  var reactFormComponents = require('@trackunit/react-form-components');
8
9
 
@@ -30,9 +31,27 @@ const cvaFilterCustomButton = cssClassVarianceUtilities.cvaMerge(["justify-norma
30
31
  * @param {FilterProps} props - The props for the Filter component
31
32
  * @returns {ReactElement} Filter component
32
33
  */
33
- const Filter = ({ title, children, popoverProps, isActive, activeLabel, menuListProps, className, dataTestId, withStickyHeader = false, readOnly, ...rest }) => {
34
- const buttonProps = { ...rest };
35
- return (jsxRuntime.jsxs(reactComponents.Popover, { dataTestId: dataTestId ? `${dataTestId}-popover` : undefined, placement: "bottom-start", ...popoverProps, children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsxs(reactComponents.Button, { className: cvaFilterCustomButton({ isActive, className }), dataTestId: dataTestId, disabled: readOnly, size: "small", variant: "secondary", ...buttonProps, children: [title, " ", isActive ? (jsxRuntime.jsx("div", { className: "grid overflow-hidden text-ellipsis whitespace-nowrap", children: jsxRuntime.jsx("span", { className: "text-primary-600 truncate", children: activeLabel }) })) : undefined] }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx(reactComponents.MenuList, { className: cvaMenuListOverrides({ withStickyHeader }), dataTestId: dataTestId ? dataTestId : undefined, withStickyHeader: withStickyHeader, ...menuListProps, children: children }) })] }));
34
+ const Filter = ({ title, asIcon, children, popoverProps, isActive, activeLabel, activeOptionsCount, menuListProps, className, dataTestId, withStickyHeader = false, readOnly, visualStyle = "button", size = "small", }) => {
35
+ const handleClick = react.useCallback((event) => {
36
+ event.stopPropagation();
37
+ }, []);
38
+ const renderButton = () => {
39
+ if (asIcon) {
40
+ return (jsxRuntime.jsx(reactComponents.IconButton, { className: className, dataTestId: dataTestId, disabled: readOnly, icon: jsxRuntime.jsx(reactComponents.Icon, { color: isActive ? "primary" : undefined, name: asIcon, size: "small" }), onClick: handleClick, size: size, variant: "ghost-neutral" }));
41
+ }
42
+ return (jsxRuntime.jsxs(reactComponents.Button, { className: cvaFilterCustomButton({ isActive, className }), dataTestId: dataTestId, disabled: readOnly, onClick: handleClick, size: size, variant: "secondary", children: [title, isActive ? (jsxRuntime.jsx("div", { className: "grid overflow-hidden text-ellipsis whitespace-nowrap", children: jsxRuntime.jsx("span", { className: "text-primary-600 truncate", children: activeLabel }) })) : null] }));
43
+ };
44
+ return (jsxRuntime.jsx(reactComponents.Popover, { dataTestId: dataTestId ? `${dataTestId}-popover` : undefined, placement: "top-start", ...popoverProps, children: modalState => {
45
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: visualStyle === "list-item" ? (jsxRuntime.jsxs("li", { className: reactComponents.cvaInteractableItem({
46
+ disabled: readOnly,
47
+ selected: modalState.isOpen,
48
+ cursor: "pointer",
49
+ className: [
50
+ className,
51
+ "text-secondary-700 hover:text-secondary-800 grid list-none grid-flow-col items-center justify-between gap-1 rounded px-3 py-2 text-sm",
52
+ ],
53
+ }), "data-testid": dataTestId, tabIndex: 0, children: [jsxRuntime.jsx("span", { className: tailwindMerge.twMerge("truncate", isActive && "font-semibold"), children: title }), jsxRuntime.jsxs("span", { className: "grid grid-flow-col items-center justify-between text-slate-900", children: [isActive && activeLabel ? (jsxRuntime.jsx(reactComponents.Badge, { className: !activeOptionsCount ? "mx-2" : "mx-1", compact: !activeOptionsCount, count: activeOptionsCount })) : null, readOnly ? null : jsxRuntime.jsx(reactComponents.Icon, { ariaHidden: true, color: "secondary", name: "ChevronRight", size: "small" })] })] })) : (renderButton()) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx(reactComponents.MenuList, { className: cvaMenuListOverrides({ withStickyHeader }), dataTestId: dataTestId ? dataTestId : undefined, withStickyHeader: withStickyHeader, ...menuListProps, children: children }) })] }));
54
+ } }));
36
55
  };
37
56
  const cvaMenuListOverrides = cssClassVarianceUtilities.cvaMerge(["overflow-x-hidden", "relative", "!max-w-full", "p-0"], {
38
57
  variants: {
@@ -46,7 +65,7 @@ const cvaMenuListOverrides = cssClassVarianceUtilities.cvaMerge(["overflow-x-hid
46
65
  /**
47
66
  * The FilterBody component is used to display the title of the filter and a reset button.
48
67
  * IT is intended for use in the Filter component.
49
- * The reset button will only be visible if the showReset prop is set to true.
68
+ * The reset button will be enabled if the showReset prop is set to true.
50
69
  *
51
70
  * @param {FilterBodyProps} props - The props for the FilterBody component
52
71
  */
@@ -82,13 +101,13 @@ const cvaFilterFooter = cssClassVarianceUtilities.cvaMerge(["flex", "justify-end
82
101
  /**
83
102
  * The FilterHeader component is used to display the title of the filter and a reset button.
84
103
  * IT is intended for use in the Filter component.
85
- * The reset button will only be visible if the showReset prop is set to true.
104
+ * The reset button will only be enabled if the showReset prop is set to true.
86
105
  *
87
106
  * @param {FilterHeaderProps} props - The props for the FilterHeader component
88
107
  * @returns {ReactElement} FilterHeader component
89
108
  */
90
- const FilterHeader = ({ title, resetLabel, showReset, dataTestId, onReset, loading, children, className, ...rest }) => {
91
- return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("grid gap-1 border-b-2 p-1", className), ...rest, "data-testid": dataTestId, children: [jsxRuntime.jsxs("div", { className: "flex h-6 items-center justify-between gap-1 pl-1 text-sm font-medium uppercase text-slate-600", children: [jsxRuntime.jsx("h4", { children: title }), loading ? (jsxRuntime.jsx("div", { children: jsxRuntime.jsx(reactComponents.Spinner, { size: "small" }) })) : (jsxRuntime.jsx(reactComponents.Button, { className: cvaFilterHeaderButton({ isVisible: showReset }), "data-testid": `${dataTestId}-reset-button`, onClick: onReset, size: "small", variant: "ghost", children: resetLabel }))] }), children] }));
109
+ const FilterHeader = ({ dataTestId, title, resetLabel, showReset, onReset, loading, children, searchComponent, className, ...rest }) => {
110
+ return (jsxRuntime.jsx("div", { className: tailwindMerge.twMerge(className, "p-1", "min-w-[280px]"), ...rest, "data-testid": dataTestId, children: title ? (jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-between gap-1 text-sm font-medium uppercase text-slate-600", children: [searchComponent, jsxRuntime.jsxs("div", { className: "flex w-full justify-between gap-1", children: [children, jsxRuntime.jsx("div", { className: "ml-auto", children: loading ? (jsxRuntime.jsx(reactComponents.Spinner, { size: "small" })) : (jsxRuntime.jsx(reactComponents.Button, { "data-testid": `${dataTestId}-reset-button`, disabled: !showReset, onClick: onReset, size: "small", variant: "ghost", children: resetLabel })) })] }), jsxRuntime.jsx("hr", { className: "w-full border-slate-200" })] })) : null }));
92
111
  };
93
112
  const cvaFilterHeaderButton = cssClassVarianceUtilities.cvaMerge([], {
94
113
  variants: {
@@ -113,7 +132,7 @@ const CheckBoxFilterItem = ({ itemCount, className, suffix, dataTestId, label, .
113
132
  return (jsxRuntime.jsx(reactFormComponents.Checkbox, { className: reactComponents.cvaInteractableItem({
114
133
  selected: "auto",
115
134
  focused: "auto",
116
- className: tailwindMerge.twMerge(["py-1", "pl-2", "pr-3", "w-full", "h-auto", "items-center", "gap-x-2", "select-none", "rounded"], className),
135
+ className: tailwindMerge.twMerge(["py-1", "mx-1", "pl-2", "pr-3", "w-auto", "h-auto", "items-center", "gap-x-2", "select-none", "rounded"], className),
117
136
  }), dataTestId: dataTestId ?? `${label}-checkbox-filter-item`, label: label, suffix: itemCount !== undefined || suffix ? (jsxRuntime.jsxs("span", { className: reactComponents.cvaMenuItemSuffix({ selected: rest.checked }), children: [suffix, itemCount === undefined ? null : itemCount] })) : null, ...rest }));
118
137
  };
119
138
 
package/index.esm.js CHANGED
@@ -1,6 +1,7 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { cvaMerge } from '@trackunit/css-class-variance-utilities';
3
- import { Popover, PopoverTrigger, Button, PopoverContent, MenuList, Spinner, cvaInteractableItem, cvaMenuItemSuffix } from '@trackunit/react-components';
3
+ import { Popover, PopoverTrigger, cvaInteractableItem, Badge, Icon, PopoverContent, MenuList, IconButton, Button, Spinner, cvaMenuItemSuffix } from '@trackunit/react-components';
4
+ import { useCallback } from 'react';
4
5
  import { twMerge } from 'tailwind-merge';
5
6
  import { Checkbox, RadioItem } from '@trackunit/react-form-components';
6
7
 
@@ -28,9 +29,27 @@ const cvaFilterCustomButton = cvaMerge(["justify-normal"], {
28
29
  * @param {FilterProps} props - The props for the Filter component
29
30
  * @returns {ReactElement} Filter component
30
31
  */
31
- const Filter = ({ title, children, popoverProps, isActive, activeLabel, menuListProps, className, dataTestId, withStickyHeader = false, readOnly, ...rest }) => {
32
- const buttonProps = { ...rest };
33
- return (jsxs(Popover, { dataTestId: dataTestId ? `${dataTestId}-popover` : undefined, placement: "bottom-start", ...popoverProps, children: [jsx(PopoverTrigger, { children: jsxs(Button, { className: cvaFilterCustomButton({ isActive, className }), dataTestId: dataTestId, disabled: readOnly, size: "small", variant: "secondary", ...buttonProps, children: [title, " ", isActive ? (jsx("div", { className: "grid overflow-hidden text-ellipsis whitespace-nowrap", children: jsx("span", { className: "text-primary-600 truncate", children: activeLabel }) })) : undefined] }) }), jsx(PopoverContent, { children: jsx(MenuList, { className: cvaMenuListOverrides({ withStickyHeader }), dataTestId: dataTestId ? dataTestId : undefined, withStickyHeader: withStickyHeader, ...menuListProps, children: children }) })] }));
32
+ const Filter = ({ title, asIcon, children, popoverProps, isActive, activeLabel, activeOptionsCount, menuListProps, className, dataTestId, withStickyHeader = false, readOnly, visualStyle = "button", size = "small", }) => {
33
+ const handleClick = useCallback((event) => {
34
+ event.stopPropagation();
35
+ }, []);
36
+ const renderButton = () => {
37
+ if (asIcon) {
38
+ return (jsx(IconButton, { className: className, dataTestId: dataTestId, disabled: readOnly, icon: jsx(Icon, { color: isActive ? "primary" : undefined, name: asIcon, size: "small" }), onClick: handleClick, size: size, variant: "ghost-neutral" }));
39
+ }
40
+ return (jsxs(Button, { className: cvaFilterCustomButton({ isActive, className }), dataTestId: dataTestId, disabled: readOnly, onClick: handleClick, size: size, variant: "secondary", children: [title, isActive ? (jsx("div", { className: "grid overflow-hidden text-ellipsis whitespace-nowrap", children: jsx("span", { className: "text-primary-600 truncate", children: activeLabel }) })) : null] }));
41
+ };
42
+ return (jsx(Popover, { dataTestId: dataTestId ? `${dataTestId}-popover` : undefined, placement: "top-start", ...popoverProps, children: modalState => {
43
+ return (jsxs(Fragment, { children: [jsx(PopoverTrigger, { children: visualStyle === "list-item" ? (jsxs("li", { className: cvaInteractableItem({
44
+ disabled: readOnly,
45
+ selected: modalState.isOpen,
46
+ cursor: "pointer",
47
+ className: [
48
+ className,
49
+ "text-secondary-700 hover:text-secondary-800 grid list-none grid-flow-col items-center justify-between gap-1 rounded px-3 py-2 text-sm",
50
+ ],
51
+ }), "data-testid": dataTestId, tabIndex: 0, children: [jsx("span", { className: twMerge("truncate", isActive && "font-semibold"), children: title }), jsxs("span", { className: "grid grid-flow-col items-center justify-between text-slate-900", children: [isActive && activeLabel ? (jsx(Badge, { className: !activeOptionsCount ? "mx-2" : "mx-1", compact: !activeOptionsCount, count: activeOptionsCount })) : null, readOnly ? null : jsx(Icon, { ariaHidden: true, color: "secondary", name: "ChevronRight", size: "small" })] })] })) : (renderButton()) }), jsx(PopoverContent, { children: jsx(MenuList, { className: cvaMenuListOverrides({ withStickyHeader }), dataTestId: dataTestId ? dataTestId : undefined, withStickyHeader: withStickyHeader, ...menuListProps, children: children }) })] }));
52
+ } }));
34
53
  };
35
54
  const cvaMenuListOverrides = cvaMerge(["overflow-x-hidden", "relative", "!max-w-full", "p-0"], {
36
55
  variants: {
@@ -44,7 +63,7 @@ const cvaMenuListOverrides = cvaMerge(["overflow-x-hidden", "relative", "!max-w-
44
63
  /**
45
64
  * The FilterBody component is used to display the title of the filter and a reset button.
46
65
  * IT is intended for use in the Filter component.
47
- * The reset button will only be visible if the showReset prop is set to true.
66
+ * The reset button will be enabled if the showReset prop is set to true.
48
67
  *
49
68
  * @param {FilterBodyProps} props - The props for the FilterBody component
50
69
  */
@@ -80,13 +99,13 @@ const cvaFilterFooter = cvaMerge(["flex", "justify-end", "p-1"]);
80
99
  /**
81
100
  * The FilterHeader component is used to display the title of the filter and a reset button.
82
101
  * IT is intended for use in the Filter component.
83
- * The reset button will only be visible if the showReset prop is set to true.
102
+ * The reset button will only be enabled if the showReset prop is set to true.
84
103
  *
85
104
  * @param {FilterHeaderProps} props - The props for the FilterHeader component
86
105
  * @returns {ReactElement} FilterHeader component
87
106
  */
88
- const FilterHeader = ({ title, resetLabel, showReset, dataTestId, onReset, loading, children, className, ...rest }) => {
89
- return (jsxs("div", { className: twMerge("grid gap-1 border-b-2 p-1", className), ...rest, "data-testid": dataTestId, children: [jsxs("div", { className: "flex h-6 items-center justify-between gap-1 pl-1 text-sm font-medium uppercase text-slate-600", children: [jsx("h4", { children: title }), loading ? (jsx("div", { children: jsx(Spinner, { size: "small" }) })) : (jsx(Button, { className: cvaFilterHeaderButton({ isVisible: showReset }), "data-testid": `${dataTestId}-reset-button`, onClick: onReset, size: "small", variant: "ghost", children: resetLabel }))] }), children] }));
107
+ const FilterHeader = ({ dataTestId, title, resetLabel, showReset, onReset, loading, children, searchComponent, className, ...rest }) => {
108
+ return (jsx("div", { className: twMerge(className, "p-1", "min-w-[280px]"), ...rest, "data-testid": dataTestId, children: title ? (jsxs("div", { className: "flex flex-col items-center justify-between gap-1 text-sm font-medium uppercase text-slate-600", children: [searchComponent, jsxs("div", { className: "flex w-full justify-between gap-1", children: [children, jsx("div", { className: "ml-auto", children: loading ? (jsx(Spinner, { size: "small" })) : (jsx(Button, { "data-testid": `${dataTestId}-reset-button`, disabled: !showReset, onClick: onReset, size: "small", variant: "ghost", children: resetLabel })) })] }), jsx("hr", { className: "w-full border-slate-200" })] })) : null }));
90
109
  };
91
110
  const cvaFilterHeaderButton = cvaMerge([], {
92
111
  variants: {
@@ -111,7 +130,7 @@ const CheckBoxFilterItem = ({ itemCount, className, suffix, dataTestId, label, .
111
130
  return (jsx(Checkbox, { className: cvaInteractableItem({
112
131
  selected: "auto",
113
132
  focused: "auto",
114
- className: twMerge(["py-1", "pl-2", "pr-3", "w-full", "h-auto", "items-center", "gap-x-2", "select-none", "rounded"], className),
133
+ className: twMerge(["py-1", "mx-1", "pl-2", "pr-3", "w-auto", "h-auto", "items-center", "gap-x-2", "select-none", "rounded"], className),
115
134
  }), dataTestId: dataTestId ?? `${label}-checkbox-filter-item`, label: label, suffix: itemCount !== undefined || suffix ? (jsxs("span", { className: cvaMenuItemSuffix({ selected: rest.checked }), children: [suffix, itemCount === undefined ? null : itemCount] })) : null, ...rest }));
116
135
  };
117
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-filter-components",
3
- "version": "1.3.130",
3
+ "version": "1.3.133",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -9,9 +9,10 @@
9
9
  "dependencies": {
10
10
  "react": "19.0.0",
11
11
  "tailwind-merge": "^2.0.0",
12
- "@trackunit/css-class-variance-utilities": "1.3.97",
13
- "@trackunit/react-components": "1.4.114",
14
- "@trackunit/react-form-components": "1.3.130"
12
+ "@trackunit/ui-icons": "1.3.99",
13
+ "@trackunit/css-class-variance-utilities": "1.3.98",
14
+ "@trackunit/react-components": "1.4.117",
15
+ "@trackunit/react-form-components": "1.3.133"
15
16
  },
16
17
  "module": "./index.esm.js",
17
18
  "main": "./index.cjs.js",
@@ -1,10 +1,12 @@
1
1
  import { ButtonProps, MenuListProps, PopoverProps } from "@trackunit/react-components";
2
+ import { IconName } from "@trackunit/ui-icons";
2
3
  import { ReactElement } from "react";
4
+ export type FilterVisualStyle = "button" | "list-item";
3
5
  export interface FilterProps extends ButtonProps {
4
6
  /**
5
7
  * The title/name of the filter.
6
8
  */
7
- title: string;
9
+ title?: string;
8
10
  /**
9
11
  * Used to indicate if the user has any active selection in the filter.
10
12
  */
@@ -13,6 +15,14 @@ export interface FilterProps extends ButtonProps {
13
15
  * The label that will be displayed when the filter is active.
14
16
  */
15
17
  activeLabel?: string;
18
+ /**
19
+ * The number of active options for multi select filters.
20
+ */
21
+ activeOptionsCount?: number;
22
+ /**
23
+ * The icon to be displayed in the filter button.
24
+ */
25
+ asIcon?: IconName;
16
26
  /**
17
27
  * When enabled the menu list padding will be removed, and a grid will be applied to keep the first column "sticky".
18
28
  * This intended to be used withe the FilterHeader component.
@@ -38,6 +48,12 @@ export interface FilterProps extends ButtonProps {
38
48
  * A flag to set a filter component into readonly mode used for presenting filtering rules that cannot be change by user
39
49
  */
40
50
  readOnly?: boolean;
51
+ /**
52
+ * The visual style of the filter. List-item or Button.
53
+ *
54
+ * @default "button"
55
+ */
56
+ visualStyle?: FilterVisualStyle;
41
57
  }
42
58
  /**
43
59
  * The Filter component is the base component used in the manager to filter data.
@@ -51,4 +67,4 @@ export interface FilterProps extends ButtonProps {
51
67
  * @param {FilterProps} props - The props for the Filter component
52
68
  * @returns {ReactElement} Filter component
53
69
  */
54
- export declare const Filter: ({ title, children, popoverProps, isActive, activeLabel, menuListProps, className, dataTestId, withStickyHeader, readOnly, ...rest }: FilterProps) => ReactElement;
70
+ export declare const Filter: ({ title, asIcon, children, popoverProps, isActive, activeLabel, activeOptionsCount, menuListProps, className, dataTestId, withStickyHeader, readOnly, visualStyle, size, }: FilterProps) => ReactElement;
@@ -15,7 +15,7 @@ export interface FilterBodyProps extends CommonProps {
15
15
  /**
16
16
  * The FilterBody component is used to display the title of the filter and a reset button.
17
17
  * IT is intended for use in the Filter component.
18
- * The reset button will only be visible if the showReset prop is set to true.
18
+ * The reset button will be enabled if the showReset prop is set to true.
19
19
  *
20
20
  * @param {FilterBodyProps} props - The props for the FilterBody component
21
21
  */
@@ -1,12 +1,12 @@
1
1
  import { CommonProps } from "@trackunit/react-components";
2
- import { ReactNode } from "react";
2
+ import { ReactElement, ReactNode } from "react";
3
3
  export interface FilterHeaderProps extends CommonProps {
4
4
  /**
5
5
  * The title of the filter will be displayed in the header.
6
6
  */
7
- title: string;
7
+ title?: string;
8
8
  /**
9
- * Used to indicate if the user has any active selection in the filter.
9
+ * Used to indicate if the user has any active selection in the filter that can be reset.
10
10
  */
11
11
  showReset?: boolean;
12
12
  /**
@@ -19,20 +19,24 @@ export interface FilterHeaderProps extends CommonProps {
19
19
  */
20
20
  onReset?: () => void;
21
21
  /**
22
- * Optional elements to render blow the title and reset button.
23
- * This is intended for use with the search component.
22
+ * Optional search component to render in the header.
23
+ */
24
+ searchComponent?: ReactNode;
25
+ /**
26
+ * Optional elements to render along the reset button.
27
+ * For example: a "Select All" button.
24
28
  */
25
29
  children?: ReactNode;
26
30
  }
27
31
  /**
28
32
  * The FilterHeader component is used to display the title of the filter and a reset button.
29
33
  * IT is intended for use in the Filter component.
30
- * The reset button will only be visible if the showReset prop is set to true.
34
+ * The reset button will only be enabled if the showReset prop is set to true.
31
35
  *
32
36
  * @param {FilterHeaderProps} props - The props for the FilterHeader component
33
37
  * @returns {ReactElement} FilterHeader component
34
38
  */
35
- export declare const FilterHeader: ({ title, resetLabel, showReset, dataTestId, onReset, loading, children, className, ...rest }: FilterHeaderProps) => import("react/jsx-runtime").JSX.Element;
39
+ export declare const FilterHeader: ({ dataTestId, title, resetLabel, showReset, onReset, loading, children, searchComponent, className, ...rest }: FilterHeaderProps) => ReactElement;
36
40
  export declare const cvaFilterHeaderButton: (props?: ({
37
41
  isVisible?: boolean | null | undefined;
38
42
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;