Skip to content

Commit 993e5f8

Browse files
extend-column-filters (#1700)
ColumnFilter will be the default filter implementation for a standalone vuu column filter It will offer operator selection (optionally) and value selection, with appropriate filter value editor for the column type
1 parent 75332d6 commit 993e5f8

File tree

6 files changed

+491
-77
lines changed

6 files changed

+491
-77
lines changed

vuu-ui/packages/vuu-data-react/src/data-editing/get-data-item-edit-control.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface DataItemEditControlProps {
2727
*/
2828
dataDescriptor: DataValueDescriptor;
2929
defaultValue?: string | number | readonly string[];
30+
value?: string | number | readonly string[]; //TODO - will be used in VuuTimePicker
3031
errorMessage?: string;
3132
onCommit: CommitHandler<HTMLElement>;
3233
table?: TableSchemaTable;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { LocalDataSourceProvider } from "@vuu-ui/vuu-data-test";
2+
import { TextColumnFilterValueSetViaBtn } from "../../../../../showcase/src/examples/Filters/ColumnFilter.examples";
3+
4+
describe("ColumnFilter", () => {
5+
describe("WHEN text columnfilter is rendered", () => {
6+
beforeEach(() => {
7+
cy.mount(
8+
<LocalDataSourceProvider>
9+
<TextColumnFilterValueSetViaBtn />
10+
</LocalDataSourceProvider>,
11+
);
12+
});
13+
14+
it("THEN the component is rendered with an initial value", () => {
15+
const container = cy.findByTestId("columnfilter");
16+
container.should("have.class", "vuuColumnFilter");
17+
container.find("input").should("have.value", "AAOP.N");
18+
});
19+
20+
it("THEN the component shows suggestions on input", () => {
21+
const container = cy.findByTestId("columnfilter");
22+
container.find("input").clear().type("A");
23+
cy.findAllByRole("option", { name: "AAOO.L" }).should("be.visible");
24+
});
25+
26+
it("THEN component renders a new value provided via state set from outside the container", () => {
27+
const container = cy.findByTestId("columnfilter");
28+
const input = container.find("input");
29+
input.should("have.value", "AAOP.N");
30+
cy.contains("button", "AAOQ.OQ").realClick();
31+
input.should("have.value", "AAOQ.OQ");
32+
cy.contains("button", "AAOU.MI").realClick();
33+
input.should("have.value", "AAOU.MI");
34+
});
35+
});
36+
});

vuu-ui/packages/vuu-filters/src/column-filter/ColumnFilter.tsx

Lines changed: 81 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,62 +13,92 @@ import cx from "clsx";
1313

1414
import columnFilterCss from "./ColumnFilter.css";
1515
import { getDataItemEditControl } from "@vuu-ui/vuu-data-react";
16-
import { useCallback, useMemo } from "react";
17-
import { CommitHandler } from "@vuu-ui/vuu-utils";
16+
import { ForwardedRef, forwardRef, ReactElement, useMemo } from "react";
1817
import { VuuTable } from "@vuu-ui/vuu-protocol-types";
19-
import {
20-
assertValidValue,
21-
ColumnFilterHookProps,
22-
FilterValue,
23-
} from "./useColumnFilter";
18+
import { assertValidOperator, assertValidValue, Operator, useColumnFilter } from "./useColumnFilter";
19+
import { ColumnDescriptor } from "@vuu-ui/vuu-table-types";
2420

2521
const classBase = "vuuColumnFilter";
22+
export type FilterValue = string | readonly string[] | number;
23+
export type ColumnFilterValue = FilterValue | [FilterValue, FilterValue];
2624

27-
export interface ColumnFilterProps
28-
extends ColumnFilterHookProps,
29-
SegmentedButtonGroupProps {
25+
export interface ColumnFilterProps extends SegmentedButtonGroupProps {
26+
column: ColumnDescriptor;
27+
operator?: Operator;
28+
/**
29+
* Display operator picker.
30+
*/
3031
showOperatorPicker?: boolean;
3132
/**
3233
* VuuTable is required if typeahead support is expected.
3334
*/
3435
table?: VuuTable;
35-
3636
/**
37-
* Initial filter value. Pair of values expewcted when operator is
37+
* Filter value. Pair of values expected when operator is
3838
* 'between'
3939
*/
40-
value?: FilterValue | [FilterValue, FilterValue];
40+
value?: ColumnFilterValue;
41+
/**
42+
* Filter change events.
43+
*/
44+
onFilterChange?: (
45+
value: ColumnFilterValue | undefined,
46+
columnName: string,
47+
op: Operator,
48+
) => void;
4149
}
4250

43-
export const ColumnFilter = ({
44-
column,
45-
className,
46-
operator = "=",
47-
showOperatorPicker = false,
48-
table,
49-
value,
50-
...buttonGroupProps
51-
}: ColumnFilterProps) => {
51+
export const ColumnFilter = forwardRef(function ColumnFilter(
52+
{
53+
column,
54+
className,
55+
operator = "=",
56+
showOperatorPicker = false,
57+
table,
58+
value,
59+
onFilterChange,
60+
...buttonGroupProps
61+
}: ColumnFilterProps,
62+
forwardRef: ForwardedRef<HTMLDivElement>,
63+
) {
5264
const targetWindow = useWindow();
5365
useComponentCssInjection({
54-
testId: "vuu-filter-bar",
66+
testId: "vuu-column-filter",
5567
css: columnFilterCss,
5668
window: targetWindow,
5769
});
5870

71+
const {
72+
op,
73+
allowedOperators,
74+
filterValue,
75+
inputProps,
76+
rangeInputProps,
77+
onOperatorChange,
78+
handleCommit,
79+
handleRangeCommit,
80+
} = useColumnFilter({
81+
operator,
82+
column,
83+
value,
84+
onFilterChange,
85+
});
86+
5987
useMemo(
60-
() => assertValidValue(column, operator, value),
61-
[column, operator, value],
88+
() => assertValidOperator(allowedOperators, column, operator),
89+
[column, operator, allowedOperators],
6290
);
6391

64-
const onCommit = useCallback<CommitHandler<HTMLElement>>((e, value) => {
65-
console.log(`onCommit ${value}`);
66-
}, []);
92+
useMemo(
93+
() => assertValidValue(column, op, filterValue),
94+
[column, op, filterValue],
95+
);
6796

6897
return (
6998
<SegmentedButtonGroup
7099
{...buttonGroupProps}
71100
className={cx(classBase, className)}
101+
ref={forwardRef}
72102
>
73103
{showOperatorPicker ? (
74104
<Menu placement="bottom-start">
@@ -80,37 +110,44 @@ export const ColumnFilter = ({
80110
data-embedded
81111
sentiment="neutral"
82112
>
83-
{operator}
113+
{op}
84114
</Button>
85115
</MenuTrigger>
86116
<MenuPanel>
87-
<MenuItem>=</MenuItem>
88-
<MenuItem>!=</MenuItem>
89-
<MenuItem>starts</MenuItem>
90-
<MenuItem>ends</MenuItem>
91-
<MenuItem>contains</MenuItem>
117+
{allowedOperators.map((operator) => (
118+
<MenuItem
119+
key={operator}
120+
onClick={() => onOperatorChange(operator)}
121+
>
122+
{operator}
123+
</MenuItem>
124+
))}
92125
</MenuPanel>
93126
</Menu>
94127
) : null}
95128
{getDataItemEditControl({
129+
InputProps: { inputProps },
96130
dataDescriptor: column,
97-
defaultValue: Array.isArray(value)
98-
? (value[0] as string)
99-
: (value as string),
100-
onCommit,
131+
onCommit: handleCommit,
132+
defaultValue: Array.isArray(filterValue) ? filterValue[0] : filterValue,
101133
table,
102134
})}
103-
{operator === "between"
135+
{op === "between"
104136
? getDataItemEditControl({
105137
className: `${classBase}-rangeHigh`,
138+
InputProps: { inputProps: rangeInputProps },
106139
dataDescriptor: column,
107-
defaultValue: Array.isArray(value)
108-
? (value[1] as string)
109-
: undefined,
110-
onCommit,
140+
onCommit: handleRangeCommit,
141+
defaultValue: Array.isArray(filterValue)
142+
? filterValue[1]
143+
: filterValue,
111144
table,
112145
})
113146
: null}
114147
</SegmentedButtonGroup>
115148
);
116-
};
149+
}) as (
150+
props: ColumnFilterProps & {
151+
ref?: ForwardedRef<HTMLDivElement>;
152+
},
153+
) => ReactElement<ColumnFilterProps>;

0 commit comments

Comments
 (0)