Skip to content

Commit 1e904ce

Browse files
authored
vuuTypeahead api change (#1736)
1 parent 4a2827a commit 1e904ce

File tree

12 files changed

+331
-128
lines changed

12 files changed

+331
-128
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import { asTimeString } from "@vuu-ui/vuu-utils";
1919

2020
export interface DataItemEditControlProps {
2121
InputProps?: Partial<InputProps>;
22-
TypeaheadProps?: Pick<VuuTypeaheadInputProps, "highlightFirstSuggestion">;
22+
TypeaheadProps?: Pick<
23+
VuuTypeaheadInputProps,
24+
"highlightFirstSuggestion" | "minCharacterCountToTriggerSuggestions"
25+
>;
2326
className?: string;
2427
commitOnBlur?: boolean;
2528
commitWhenCleared?: boolean;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ import { getDataItemEditControl } from "@vuu-ui/vuu-data-react";
1616
import { ForwardedRef, forwardRef, ReactElement } from "react";
1717
import { ColumnFilterHookProps, useColumnFilter } from "./useColumnFilter";
1818
import { VuuTable } from "@vuu-ui/vuu-protocol-types";
19+
import { VuuTypeaheadInputHookProps } from "@vuu-ui/vuu-ui-controls/src/vuu-typeahead-input/useVuuTypeaheadInput";
1920

2021
const classBase = "vuuColumnFilter";
2122

2223
export interface ColumnFilterProps
2324
extends SegmentedButtonGroupProps,
25+
Pick<VuuTypeaheadInputHookProps, "minCharacterCountToTriggerSuggestions">,
2426
ColumnFilterHookProps {
2527
/**
2628
* Display operator picker.
@@ -36,6 +38,7 @@ export const ColumnFilter = forwardRef(function ColumnFilter(
3638
{
3739
column,
3840
className,
41+
minCharacterCountToTriggerSuggestions,
3942
onCommit: onCommitProp,
4043
operator = "=",
4144
showOperatorPicker = false,
@@ -104,6 +107,9 @@ export const ColumnFilter = forwardRef(function ColumnFilter(
104107
) : null}
105108
{getDataItemEditControl({
106109
InputProps: { inputProps },
110+
TypeaheadProps: {
111+
minCharacterCountToTriggerSuggestions,
112+
},
107113
commitOnBlur: false,
108114
commitWhenCleared: true,
109115
dataDescriptor: column,

vuu-ui/packages/vuu-filters/src/column-filter/useColumnFilter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export type ColumnFilterHookProps = {
6060
* Filter value. Pair of values expected when operator is
6161
* 'between'
6262
*/
63-
value?: ColumnFilterValue;
63+
value: ColumnFilterValue;
6464
/**
6565
* Filter change events.
6666
*/

vuu-ui/packages/vuu-filters/src/filter-provider/FilterProvider.tsx

Lines changed: 54 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FilterChangeHandler } from "@vuu-ui/vuu-filter-types";
1+
import { Filter, FilterChangeHandler } from "@vuu-ui/vuu-filter-types";
22
import {
33
createContext,
44
ReactElement,
@@ -11,11 +11,21 @@ import { FilterMenuActionHandler } from "../filter-pill/FilterMenu";
1111
import { FilterDescriptor } from "../saved-filters/useSavedFilterPanel";
1212
import { FilterNamePrompt } from "../saved-filters/FilterNamePrompt";
1313
import { DeleteFilterPrompt } from "../saved-filters/DeleteFilterPrompt";
14+
import {
15+
activateFilter,
16+
findFilter,
17+
insertOrReplaceFilter,
18+
renameFilter,
19+
} from "./filter-descriptor-utils";
20+
21+
const UNSAVED_FILTER = "unsaved-filter";
1422

1523
export interface FilterContextProps {
1624
activeFilter: FilterDescriptor | undefined;
25+
deleteFilter: (filterId: string) => void;
1726
saveFilter: (filter: FilterDescriptor) => void;
1827
savedFilters?: FilterDescriptor[];
28+
// TODO do we need this ?
1929
onApplyFilter: FilterChangeHandler;
2030
onFilterMenuAction?: FilterMenuActionHandler;
2131
setActiveFilter: (filterId?: string) => void;
@@ -28,6 +38,11 @@ export const FilterContext = createContext<FilterContextProps>({
2838
console.warn(
2939
"[FilterContext] onApplyFilter, no FilterProvider has been configured",
3040
),
41+
deleteFilter: () =>
42+
console.warn(
43+
"[FilterContext] deleteFilter, no FilterProvider has been configured",
44+
),
45+
3146
saveFilter: () =>
3247
console.warn(
3348
"[FilterContext] saveFilter, no FilterProvider has been configured",
@@ -53,20 +68,6 @@ export const FilterProvider = ({
5368
console.log("filter changed");
5469
}, []);
5570

56-
const findFilter = useCallback(
57-
(filterId: string) => {
58-
const filter = filterDescriptors.find(({ id }) => id === filterId);
59-
if (filter) {
60-
return filter;
61-
} else {
62-
throw Error(
63-
`[FilterProvider] findFilter, filter not found ${filterId}`,
64-
);
65-
}
66-
},
67-
[filterDescriptors],
68-
);
69-
7071
const deleteFilter = useCallback(
7172
(filterId: string) => {
7273
setFilterDescriptors((filterDescriptors) => {
@@ -80,23 +81,14 @@ export const FilterProvider = ({
8081
[onFiltersSaved],
8182
);
8283

83-
const renameFilter = useCallback(
84-
(filterId: string, filterName: string) => {
84+
const applyNewName = useCallback(
85+
(filterId: string, name: string) => {
8586
setFilterDescriptors((currentFilterDescriptors) => {
86-
const newFilterDescriptors =
87-
currentFilterDescriptors.map<FilterDescriptor>((f) => {
88-
if (f.id === filterId) {
89-
return {
90-
...f,
91-
filter: {
92-
...f.filter,
93-
name: filterName,
94-
},
95-
};
96-
} else {
97-
return f;
98-
}
99-
});
87+
const newFilterDescriptors = renameFilter(
88+
currentFilterDescriptors,
89+
filterId,
90+
name,
91+
);
10092
onFiltersSaved?.(newFilterDescriptors);
10193
return newFilterDescriptors;
10294
});
@@ -115,13 +107,13 @@ export const FilterProvider = ({
115107
onConfirm={(name) => {
116108
setDialog(null);
117109
if (originalFilterName !== name) {
118-
renameFilter(id, name);
110+
applyNewName(id, name);
119111
}
120112
}}
121113
/>,
122114
);
123115
},
124-
[renameFilter],
116+
[applyNewName],
125117
);
126118

127119
const promptForConfirmationOfDelete = useCallback(
@@ -142,22 +134,26 @@ export const FilterProvider = ({
142134

143135
const handleFilterMenuAction = useCallback<FilterMenuActionHandler>(
144136
(filterId, actionType) => {
145-
const targetFilter = findFilter(filterId);
137+
const targetFilter = findFilter(filterDescriptors, filterId);
146138
switch (actionType) {
147139
case "close":
148-
console.log(`clode filter ${filterId}`);
140+
console.log(`close filter ${filterId}`);
149141
break;
150142
case "edit":
151143
console.log(`edit filter ${filterId}`);
152144
break;
153145
case "remove":
154-
promptForConfirmationOfDelete(targetFilter);
146+
if (filterId === UNSAVED_FILTER) {
147+
console.log("remove unsaved filter");
148+
} else {
149+
promptForConfirmationOfDelete(targetFilter);
150+
}
155151
break;
156152
case "rename":
157153
return PromptForFilterName(targetFilter);
158154
}
159155
},
160-
[findFilter, promptForConfirmationOfDelete, PromptForFilterName],
156+
[filterDescriptors, promptForConfirmationOfDelete, PromptForFilterName],
161157
);
162158

163159
const handleSaveFilter = useCallback(
@@ -179,38 +175,34 @@ export const FilterProvider = ({
179175
[onFiltersSaved],
180176
);
181177

182-
const setActiveFilter = useCallback(
183-
(filterId?: string) => {
184-
setFilterDescriptors((currentFilterDescriptors) => {
185-
const targetFilter = filterId ? findFilter(filterId) : undefined;
186-
const newFilterDescriptors =
187-
currentFilterDescriptors.map<FilterDescriptor>((f) => {
188-
if (f.id === filterId) {
189-
return {
190-
...f,
191-
active: !f.active,
192-
};
193-
} else if (!targetFilter?.active && f.active) {
194-
return {
195-
...f,
196-
active: false,
197-
};
198-
} else {
199-
return f;
200-
}
201-
});
202-
return newFilterDescriptors;
203-
});
204-
},
205-
[findFilter],
206-
);
178+
/**
179+
* Allows switching between saved filtere. ALternatively, an anonymous
180+
* filter can be assigned. This is to allow for a dynamically created
181+
* filter to be active.
182+
*/
183+
const setActiveFilter = useCallback((filter?: string | Filter) => {
184+
if (typeof filter === "string") {
185+
setFilterDescriptors((currentFilterDescriptors) =>
186+
activateFilter(currentFilterDescriptors, filter),
187+
);
188+
} else if (filter) {
189+
setFilterDescriptors((currentFilterDescriptors) =>
190+
insertOrReplaceFilter(currentFilterDescriptors, {
191+
active: true,
192+
filter,
193+
id: UNSAVED_FILTER,
194+
}),
195+
);
196+
}
197+
}, []);
207198

208199
return (
209200
<FilterContext.Provider
210201
value={{
211202
activeFilter: filterDescriptors.find((f) => f.active),
212203
onApplyFilter: handleApplyFilter,
213204
onFilterMenuAction: handleFilterMenuAction,
205+
deleteFilter,
214206
saveFilter: handleSaveFilter,
215207
savedFilters: filterDescriptors,
216208
setActiveFilter,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { FilterDescriptor } from "../saved-filters/useSavedFilterPanel";
2+
3+
export function findFilter(
4+
filterDescriptors: FilterDescriptor[],
5+
filterId: string,
6+
throwIfNotFound?: true,
7+
): FilterDescriptor;
8+
export function findFilter(
9+
filterDescriptors: FilterDescriptor[],
10+
filterId: string,
11+
throwIfNotFound: false,
12+
): FilterDescriptor | undefined;
13+
export function findFilter(
14+
filterDescriptors: FilterDescriptor[],
15+
filterId: string,
16+
throwIfNotFound = true,
17+
) {
18+
const filter = filterDescriptors.find(({ id }) => id === filterId);
19+
if (filter) {
20+
return filter;
21+
} else if (throwIfNotFound) {
22+
throw Error(`[FilterProvider] findFilter, filter not found ${filterId}`);
23+
}
24+
}
25+
26+
export const deactivateFilter = (filterDescriptors: FilterDescriptor[]) =>
27+
activateFilter(filterDescriptors, undefined);
28+
29+
export const activateFilter = (
30+
filterDescriptors: FilterDescriptor[],
31+
activeFilterId?: string,
32+
) =>
33+
filterDescriptors.map<FilterDescriptor>((f) => {
34+
if (f.id === activeFilterId) {
35+
return {
36+
...f,
37+
active: !f.active,
38+
};
39+
} else if (f.active) {
40+
return {
41+
...f,
42+
active: false,
43+
};
44+
} else {
45+
return f;
46+
}
47+
});
48+
49+
export const insertOrReplaceFilter = (
50+
filterDescriptors: FilterDescriptor[],
51+
filterDescriptor: FilterDescriptor,
52+
) => {
53+
if (!filterDescriptors.some(({ id }) => id === filterDescriptor.id)) {
54+
return deactivateFilter(filterDescriptors).concat(filterDescriptor);
55+
} else {
56+
return filterDescriptors.map<FilterDescriptor>((f) => {
57+
if (f.id === filterDescriptor.id) {
58+
return filterDescriptor;
59+
} else if (f.active) {
60+
return {
61+
...f,
62+
active: false,
63+
};
64+
} else {
65+
return f;
66+
}
67+
});
68+
}
69+
};
70+
71+
export const renameFilter = (
72+
filterDescriptors: FilterDescriptor[],
73+
filterId: string,
74+
name: string,
75+
) =>
76+
filterDescriptors.map<FilterDescriptor>((f) => {
77+
if (f.id === filterId) {
78+
return {
79+
...f,
80+
filter: {
81+
...f.filter,
82+
name,
83+
},
84+
};
85+
} else {
86+
return f;
87+
}
88+
});

vuu-ui/packages/vuu-ui-controls/src/__tests__/__component__/vuu-typeahead-input/VuuTypeaheadInput.cy.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { LocalDataSourceProvider } from "@vuu-ui/vuu-data-test";
22
import {
33
CurrencyWithTypeaheadAllowFreeText,
44
CurrencyWithTypeaheadDisallowFreeText,
5+
ShowsSuggestionsNoTextRequired,
56
} from "../../../../../../showcase/src/examples/UiControls/VuuTypeaheadInput.examples";
67
import { CommitHandler } from "@vuu-ui/vuu-utils";
78

@@ -237,4 +238,24 @@ describe("VuuTypeaheadInput", () => {
237238
);
238239
});
239240
});
241+
describe("Given a TypeaheadInput that shows suggestions with no text input", () => {
242+
it("Then clicking the inut shows suggestions", () => {
243+
cy.mount(
244+
<LocalDataSourceProvider>
245+
<ShowsSuggestionsNoTextRequired />
246+
</LocalDataSourceProvider>,
247+
);
248+
cy.findByRole("combobox").realClick();
249+
cy.findAllByRole("option").should("have.length", 5);
250+
});
251+
it("Then clicking the trigger shows suggestions", () => {
252+
cy.mount(
253+
<LocalDataSourceProvider>
254+
<ShowsSuggestionsNoTextRequired />
255+
</LocalDataSourceProvider>,
256+
);
257+
cy.findByRole("button", { name: "Show options" }).realClick();
258+
cy.findAllByRole("option").should("have.length", 5);
259+
});
260+
});
240261
});

0 commit comments

Comments
 (0)