@trackunit/react-filter-components 2.4.4 → 2.4.7-alpha-770d9994af7.0
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 +48 -27
- package/index.esm.js +49 -28
- package/package.json +5 -5
- package/src/Filter/Filter.d.ts +4 -4
package/index.cjs.js
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
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');
|
|
7
|
-
var tailwindMerge = require('tailwind-merge');
|
|
8
6
|
var reactFormComponents = require('@trackunit/react-form-components');
|
|
7
|
+
var tailwindMerge = require('tailwind-merge');
|
|
9
8
|
|
|
10
9
|
const cvaFilterCustomButton = cssClassVarianceUtilities.cvaMerge(["justify-normal"], {
|
|
11
10
|
variants: {
|
|
@@ -32,36 +31,58 @@ const cvaFilterCustomButton = cssClassVarianceUtilities.cvaMerge(["justify-norma
|
|
|
32
31
|
* @returns {ReactElement} Filter component
|
|
33
32
|
*/
|
|
34
33
|
const Filter = ({ title = "", asIcon = undefined, children, popoverProps, isActive = false, activeLabel, activeOptionsCount, menuListProps, className, "data-testid": dataTestId, withStickyHeader = false, readOnly = false, visualStyle = "button", size = "small", }) => {
|
|
35
|
-
const handleClick = react.useCallback((event) => {
|
|
36
|
-
event.stopPropagation();
|
|
37
|
-
}, []);
|
|
38
34
|
const renderButton = () => {
|
|
39
35
|
if (asIcon) {
|
|
40
|
-
return (jsxRuntime.jsx(reactComponents.IconButton, { className: className, "data-testid": dataTestId, disabled: readOnly, icon: jsxRuntime.jsx(reactComponents.Icon, { name: asIcon, size: "small" }),
|
|
36
|
+
return (jsxRuntime.jsx(reactComponents.IconButton, { className: className, "data-testid": dataTestId, disabled: readOnly, icon: jsxRuntime.jsx(reactComponents.Icon, { name: asIcon, size: "small" }), size: size, title: title, variant: isActive ? "ghost" : "ghost-neutral" }));
|
|
41
37
|
}
|
|
42
|
-
return (jsxRuntime.jsxs(reactComponents.Button, { className: cvaFilterCustomButton({ isActive, className }), "data-testid": dataTestId, disabled: readOnly,
|
|
38
|
+
return (jsxRuntime.jsxs(reactComponents.Button, { className: cvaFilterCustomButton({ isActive, className }), "data-testid": dataTestId, disabled: readOnly, 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
39
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
} }));
|
|
40
|
+
const renderSubmenuContent = () => children !== undefined ? (jsxRuntime.jsx(reactComponents.MenuContent, { className: cvaMenuShellOverrides({ withStickyHeader }), "data-testid": dataTestId ? dataTestId : undefined, listClassName: cvaMenuListOverrides({ withStickyHeader }), ...menuListProps, children: children })) : (jsxRuntime.jsx(jsxRuntime.Fragment, {}));
|
|
41
|
+
if (visualStyle === "list-item") {
|
|
42
|
+
return (jsxRuntime.jsx(reactComponents.MenuItem, { className: className, "data-testid": dataTestId, disabled: readOnly, submenu: children !== undefined ? renderSubmenuContent() : undefined,
|
|
43
|
+
// See the sizing comment on the button variant's `Popover` below -- same reasoning applies to the
|
|
44
|
+
// submenu Popover that `MenuItem` renders internally for this visual style.
|
|
45
|
+
submenuSizing: popoverProps?.sizing ?? { minWidth: 280 }, suffix: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isActive && activeLabel && activeOptionsCount === undefined ? (jsxRuntime.jsx(reactComponents.Badge, { className: "mx-2", compact: true })) : (jsxRuntime.jsx("div", { className: "flex min-w-[30px] shrink-0 items-center justify-center", children: isActive && activeLabel && activeOptionsCount !== undefined ? (jsxRuntime.jsx(reactComponents.Badge, { count: activeOptionsCount, size: "condensed" })) : null })), readOnly ? null : jsxRuntime.jsx(reactComponents.Icon, { ariaHidden: true, color: "neutral", name: "ChevronRight", size: "small" })] }), children: jsxRuntime.jsx("span", { className: "flex-grow truncate", children: title }) }));
|
|
46
|
+
}
|
|
47
|
+
return (jsxRuntime.jsxs(reactComponents.Popover, { ...popoverProps, activation: { click: true }, "data-testid": dataTestId ? `${dataTestId}-popover` : undefined, dismissal: { ancestorScroll: true }, placement: "top-start",
|
|
48
|
+
// Filter content (e.g. FilterHeader) wants a comfortable minimum width, but that constraint needs to
|
|
49
|
+
// be reconciled against the *actual* available viewport space -- which only floating-ui's `size()`
|
|
50
|
+
// middleware knows at layout time. Setting it here (rather than as a hardcoded min-width deep inside
|
|
51
|
+
// the filter content) lets the middleware clamp it so the popover shrinks instead of overflowing when
|
|
52
|
+
// space is tight. Callers can still override via `popoverProps.sizing`.
|
|
53
|
+
sizing: popoverProps?.sizing ?? { minWidth: 280 }, children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: renderButton() }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: renderSubmenuContent() })] }));
|
|
59
54
|
};
|
|
60
|
-
const
|
|
55
|
+
const cvaMenuShellOverrides = cssClassVarianceUtilities.cvaMerge(["overflow-x-hidden", "relative", "!max-w-full", "p-0"], {
|
|
61
56
|
variants: {
|
|
62
57
|
withStickyHeader: {
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
// `grid-rows-[minmax(0,1fr)]` is the load-bearing part here: without it this shell's `max-h` only
|
|
59
|
+
// caps the *container*, not its single grid-item child (the sticky-header list below) -- the child's
|
|
60
|
+
// row track sizes to its own content and can grow taller than the container, silently clipping
|
|
61
|
+
// (not scrolling) whatever doesn't fit since the container is `overflow-y-hidden`. This mirrors the
|
|
62
|
+
// fix already applied to the top-level filters menu shell in `FiltersMenuContent`.
|
|
63
|
+
true: ["overflow-y-hidden", "grid-rows-[minmax(0,1fr)]", "max-h-[max(40dvh,var(--spacing-64))]"],
|
|
64
|
+
false: "",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const cvaMenuListOverrides = cssClassVarianceUtilities.cvaMerge([], {
|
|
69
|
+
variants: {
|
|
70
|
+
withStickyHeader: {
|
|
71
|
+
// `gap-y-0` cancels `cvaMenuList`'s default inter-item gap: with the header (`FilterHeader`,
|
|
72
|
+
// ending in its own `<hr>`) and the scrollable body as the only two flex children here, that
|
|
73
|
+
// gap would otherwise show up as a sliver of whitespace above the body's scrollbar.
|
|
74
|
+
true: [
|
|
75
|
+
"flex",
|
|
76
|
+
"min-h-0",
|
|
77
|
+
"flex-col",
|
|
78
|
+
"!max-h-none",
|
|
79
|
+
"!overflow-y-hidden",
|
|
80
|
+
"overflow-y-hidden",
|
|
81
|
+
"gap-y-0",
|
|
82
|
+
"p-0",
|
|
83
|
+
"[&>*:nth-child(2)]:min-h-0",
|
|
84
|
+
],
|
|
85
|
+
false: "",
|
|
65
86
|
},
|
|
66
87
|
},
|
|
67
88
|
});
|
|
@@ -80,7 +101,7 @@ FilterBody.displayName = "FilterBody";
|
|
|
80
101
|
const cvaFilterBody = cssClassVarianceUtilities.cvaMerge(["grid", "gap-1", "w-full"], {
|
|
81
102
|
variants: {
|
|
82
103
|
limitSize: {
|
|
83
|
-
true: "max-h-[50dvh] max-w-sm overflow-hidden
|
|
104
|
+
true: "max-h-[50dvh] max-w-sm overflow-hidden",
|
|
84
105
|
false: "",
|
|
85
106
|
},
|
|
86
107
|
},
|
|
@@ -110,7 +131,7 @@ const cvaFilterFooter = cssClassVarianceUtilities.cvaMerge(["flex", "justify-end
|
|
|
110
131
|
* @returns {ReactElement} FilterHeader component
|
|
111
132
|
*/
|
|
112
133
|
const FilterHeader = ({ "data-testid": dataTestId, title, resetLabel, showReset, onReset, loading = false, children, searchComponent, className, ref, style, ...rest }) => {
|
|
113
|
-
return (jsxRuntime.jsx("div", { className:
|
|
134
|
+
return (jsxRuntime.jsx("div", { className: className, style: style, ...rest, "data-testid": dataTestId, ref: ref, children: title ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "p-1", children: jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-between gap-1 text-sm font-medium uppercase text-neutral-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,
|
|
114
135
|
// eslint-disable-next-line @trackunit/prefer-event-specific-callback-naming
|
|
115
136
|
onClick: onReset, size: "small", variant: "ghost", children: resetLabel })) })] })] }) }), jsxRuntime.jsx("hr", { className: "w-full border-neutral-200" })] })) : null }));
|
|
116
137
|
};
|
package/index.esm.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
3
|
-
import {
|
|
4
|
-
import { useCallback } from 'react';
|
|
5
|
-
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import { MenuItem, Badge, Icon, Popover, PopoverTrigger, PopoverContent, MenuContent, IconButton, Button, Spinner, cvaMenuItemSuffix, cvaInteractableItem } from '@trackunit/react-components';
|
|
6
4
|
import { Checkbox, useRadioItemChecked, RadioItem } from '@trackunit/react-form-components';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
7
6
|
|
|
8
7
|
const cvaFilterCustomButton = cvaMerge(["justify-normal"], {
|
|
9
8
|
variants: {
|
|
@@ -30,36 +29,58 @@ const cvaFilterCustomButton = cvaMerge(["justify-normal"], {
|
|
|
30
29
|
* @returns {ReactElement} Filter component
|
|
31
30
|
*/
|
|
32
31
|
const Filter = ({ title = "", asIcon = undefined, children, popoverProps, isActive = false, activeLabel, activeOptionsCount, menuListProps, className, "data-testid": dataTestId, withStickyHeader = false, readOnly = false, visualStyle = "button", size = "small", }) => {
|
|
33
|
-
const handleClick = useCallback((event) => {
|
|
34
|
-
event.stopPropagation();
|
|
35
|
-
}, []);
|
|
36
32
|
const renderButton = () => {
|
|
37
33
|
if (asIcon) {
|
|
38
|
-
return (jsx(IconButton, { className: className, "data-testid": dataTestId, disabled: readOnly, icon: jsx(Icon, { name: asIcon, size: "small" }),
|
|
34
|
+
return (jsx(IconButton, { className: className, "data-testid": dataTestId, disabled: readOnly, icon: jsx(Icon, { name: asIcon, size: "small" }), size: size, title: title, variant: isActive ? "ghost" : "ghost-neutral" }));
|
|
39
35
|
}
|
|
40
|
-
return (jsxs(Button, { className: cvaFilterCustomButton({ isActive, className }), "data-testid": dataTestId, disabled: readOnly,
|
|
36
|
+
return (jsxs(Button, { className: cvaFilterCustomButton({ isActive, className }), "data-testid": dataTestId, disabled: readOnly, 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
37
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} }));
|
|
38
|
+
const renderSubmenuContent = () => children !== undefined ? (jsx(MenuContent, { className: cvaMenuShellOverrides({ withStickyHeader }), "data-testid": dataTestId ? dataTestId : undefined, listClassName: cvaMenuListOverrides({ withStickyHeader }), ...menuListProps, children: children })) : (jsx(Fragment, {}));
|
|
39
|
+
if (visualStyle === "list-item") {
|
|
40
|
+
return (jsx(MenuItem, { className: className, "data-testid": dataTestId, disabled: readOnly, submenu: children !== undefined ? renderSubmenuContent() : undefined,
|
|
41
|
+
// See the sizing comment on the button variant's `Popover` below -- same reasoning applies to the
|
|
42
|
+
// submenu Popover that `MenuItem` renders internally for this visual style.
|
|
43
|
+
submenuSizing: popoverProps?.sizing ?? { minWidth: 280 }, suffix: jsxs(Fragment, { children: [isActive && activeLabel && activeOptionsCount === undefined ? (jsx(Badge, { className: "mx-2", compact: true })) : (jsx("div", { className: "flex min-w-[30px] shrink-0 items-center justify-center", children: isActive && activeLabel && activeOptionsCount !== undefined ? (jsx(Badge, { count: activeOptionsCount, size: "condensed" })) : null })), readOnly ? null : jsx(Icon, { ariaHidden: true, color: "neutral", name: "ChevronRight", size: "small" })] }), children: jsx("span", { className: "flex-grow truncate", children: title }) }));
|
|
44
|
+
}
|
|
45
|
+
return (jsxs(Popover, { ...popoverProps, activation: { click: true }, "data-testid": dataTestId ? `${dataTestId}-popover` : undefined, dismissal: { ancestorScroll: true }, placement: "top-start",
|
|
46
|
+
// Filter content (e.g. FilterHeader) wants a comfortable minimum width, but that constraint needs to
|
|
47
|
+
// be reconciled against the *actual* available viewport space -- which only floating-ui's `size()`
|
|
48
|
+
// middleware knows at layout time. Setting it here (rather than as a hardcoded min-width deep inside
|
|
49
|
+
// the filter content) lets the middleware clamp it so the popover shrinks instead of overflowing when
|
|
50
|
+
// space is tight. Callers can still override via `popoverProps.sizing`.
|
|
51
|
+
sizing: popoverProps?.sizing ?? { minWidth: 280 }, children: [jsx(PopoverTrigger, { children: renderButton() }), jsx(PopoverContent, { children: renderSubmenuContent() })] }));
|
|
57
52
|
};
|
|
58
|
-
const
|
|
53
|
+
const cvaMenuShellOverrides = cvaMerge(["overflow-x-hidden", "relative", "!max-w-full", "p-0"], {
|
|
59
54
|
variants: {
|
|
60
55
|
withStickyHeader: {
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
// `grid-rows-[minmax(0,1fr)]` is the load-bearing part here: without it this shell's `max-h` only
|
|
57
|
+
// caps the *container*, not its single grid-item child (the sticky-header list below) -- the child's
|
|
58
|
+
// row track sizes to its own content and can grow taller than the container, silently clipping
|
|
59
|
+
// (not scrolling) whatever doesn't fit since the container is `overflow-y-hidden`. This mirrors the
|
|
60
|
+
// fix already applied to the top-level filters menu shell in `FiltersMenuContent`.
|
|
61
|
+
true: ["overflow-y-hidden", "grid-rows-[minmax(0,1fr)]", "max-h-[max(40dvh,var(--spacing-64))]"],
|
|
62
|
+
false: "",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const cvaMenuListOverrides = cvaMerge([], {
|
|
67
|
+
variants: {
|
|
68
|
+
withStickyHeader: {
|
|
69
|
+
// `gap-y-0` cancels `cvaMenuList`'s default inter-item gap: with the header (`FilterHeader`,
|
|
70
|
+
// ending in its own `<hr>`) and the scrollable body as the only two flex children here, that
|
|
71
|
+
// gap would otherwise show up as a sliver of whitespace above the body's scrollbar.
|
|
72
|
+
true: [
|
|
73
|
+
"flex",
|
|
74
|
+
"min-h-0",
|
|
75
|
+
"flex-col",
|
|
76
|
+
"!max-h-none",
|
|
77
|
+
"!overflow-y-hidden",
|
|
78
|
+
"overflow-y-hidden",
|
|
79
|
+
"gap-y-0",
|
|
80
|
+
"p-0",
|
|
81
|
+
"[&>*:nth-child(2)]:min-h-0",
|
|
82
|
+
],
|
|
83
|
+
false: "",
|
|
63
84
|
},
|
|
64
85
|
},
|
|
65
86
|
});
|
|
@@ -78,7 +99,7 @@ FilterBody.displayName = "FilterBody";
|
|
|
78
99
|
const cvaFilterBody = cvaMerge(["grid", "gap-1", "w-full"], {
|
|
79
100
|
variants: {
|
|
80
101
|
limitSize: {
|
|
81
|
-
true: "max-h-[50dvh] max-w-sm overflow-hidden
|
|
102
|
+
true: "max-h-[50dvh] max-w-sm overflow-hidden",
|
|
82
103
|
false: "",
|
|
83
104
|
},
|
|
84
105
|
},
|
|
@@ -108,7 +129,7 @@ const cvaFilterFooter = cvaMerge(["flex", "justify-end", "border-t", "border-neu
|
|
|
108
129
|
* @returns {ReactElement} FilterHeader component
|
|
109
130
|
*/
|
|
110
131
|
const FilterHeader = ({ "data-testid": dataTestId, title, resetLabel, showReset, onReset, loading = false, children, searchComponent, className, ref, style, ...rest }) => {
|
|
111
|
-
return (jsx("div", { className:
|
|
132
|
+
return (jsx("div", { className: className, style: style, ...rest, "data-testid": dataTestId, ref: ref, children: title ? (jsxs(Fragment, { children: [jsx("div", { className: "p-1", children: jsxs("div", { className: "flex flex-col items-center justify-between gap-1 text-sm font-medium uppercase text-neutral-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,
|
|
112
133
|
// eslint-disable-next-line @trackunit/prefer-event-specific-callback-naming
|
|
113
134
|
onClick: onReset, size: "small", variant: "ghost", children: resetLabel })) })] })] }) }), jsx("hr", { className: "w-full border-neutral-200" })] })) : null }));
|
|
114
135
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-filter-components",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.7-alpha-770d9994af7.0",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"tailwind-merge": "^2.0.0",
|
|
11
|
-
"@trackunit/ui-icons": "1.14.
|
|
12
|
-
"@trackunit/css-class-variance-utilities": "1.14.
|
|
13
|
-
"@trackunit/react-components": "2.
|
|
14
|
-
"@trackunit/react-form-components": "2.4.
|
|
11
|
+
"@trackunit/ui-icons": "1.14.3-alpha-770d9994af7.0",
|
|
12
|
+
"@trackunit/css-class-variance-utilities": "1.14.3-alpha-770d9994af7.0",
|
|
13
|
+
"@trackunit/react-components": "2.6.2-alpha-770d9994af7.0",
|
|
14
|
+
"@trackunit/react-form-components": "2.4.7-alpha-770d9994af7.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"@tanstack/react-router": "^1.114.29",
|
package/src/Filter/Filter.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ButtonProps,
|
|
1
|
+
import { ButtonProps, MenuContentProps, PopoverProps } from "@trackunit/react-components";
|
|
2
2
|
import { IconName } from "@trackunit/ui-icons";
|
|
3
3
|
import { ReactElement } from "react";
|
|
4
4
|
export type FilterVisualStyle = "button" | "list-item";
|
|
@@ -33,17 +33,17 @@ export interface FilterProps extends ButtonProps {
|
|
|
33
33
|
/**
|
|
34
34
|
* Child elements will be rendered inside the menu list and are only visible when the popover is open.
|
|
35
35
|
*/
|
|
36
|
-
children?:
|
|
36
|
+
children?: MenuContentProps["children"];
|
|
37
37
|
/**
|
|
38
38
|
* Can be used to override the pops of the popover.
|
|
39
39
|
* The render prop cannot be overridden.
|
|
40
40
|
*/
|
|
41
41
|
popoverProps?: Omit<PopoverProps, "render">;
|
|
42
42
|
/**
|
|
43
|
-
* Can be used to override the
|
|
43
|
+
* Can be used to override the props of the MenuContent.
|
|
44
44
|
* The render prop cannot be overridden.
|
|
45
45
|
*/
|
|
46
|
-
menuListProps?: Omit<
|
|
46
|
+
menuListProps?: Omit<MenuContentProps, "children">;
|
|
47
47
|
/**
|
|
48
48
|
* A flag to set a filter component into readonly mode used for presenting filtering rules that cannot be change by user
|
|
49
49
|
*/
|