From ab23559e186e424fdb3243fe7c2b6cbec95221be Mon Sep 17 00:00:00 2001 From: heswell Date: Wed, 23 Oct 2024 16:35:20 +0100 Subject: [PATCH] enhance VuuTypeaheadInput allow input that does not match suggestions allow full function in absence of suggestions remove prop drilling of suggestionPtovider --- .../FilterClauseValueEditorText.tsx | 4 +- .../src/useControlledTableNavigation.ts | 3 +- vuu-ui/packages/vuu-ui-controls/package.json | 1 + .../VuuTypeaheadInput.cy.tsx | 177 ++++++++++++++++++ .../vuu-ui-controls/src/common-hooks/index.ts | 1 - .../vuu-typeahead-input/VuuTypeaheadInput.css | 4 + .../vuu-typeahead-input/VuuTypeaheadInput.tsx | 36 +++- .../useVuuTypeaheadInput.ts | 77 ++++++-- vuu-ui/packages/vuu-utils/src/form-utils.ts | 8 +- vuu-ui/packages/vuu-utils/src/index.ts | 2 + .../packages/vuu-utils/src/typeahead-utils.ts | 1 + .../src}/useStateRef.ts | 11 +- .../FilterClause/FilterClause.examples.tsx | 103 ++++------ .../FilterEditor/FilterEditor.examples.tsx | 39 ++-- .../UiControls/VuuTypeaheadInput.examples.tsx | 119 ++++++++++++ .../showcase/src/examples/UiControls/index.ts | 1 + 16 files changed, 469 insertions(+), 118 deletions(-) create mode 100644 vuu-ui/packages/vuu-ui-controls/src/__tests__/__component__/vuu-typeahead-input/VuuTypeaheadInput.cy.tsx create mode 100644 vuu-ui/packages/vuu-utils/src/typeahead-utils.ts rename vuu-ui/packages/{vuu-ui-controls/src/common-hooks => vuu-utils/src}/useStateRef.ts (73%) create mode 100644 vuu-ui/showcase/src/examples/UiControls/VuuTypeaheadInput.examples.tsx diff --git a/vuu-ui/packages/vuu-filters/src/filter-clause/value-editors/FilterClauseValueEditorText.tsx b/vuu-ui/packages/vuu-filters/src/filter-clause/value-editors/FilterClauseValueEditorText.tsx index 7b8a88411..85fd033f6 100644 --- a/vuu-ui/packages/vuu-filters/src/filter-clause/value-editors/FilterClauseValueEditorText.tsx +++ b/vuu-ui/packages/vuu-filters/src/filter-clause/value-editors/FilterClauseValueEditorText.tsx @@ -1,7 +1,7 @@ import { useTypeaheadSuggestions } from "@finos/vuu-data-react"; import type { TypeaheadParams } from "@finos/vuu-protocol-types"; import { ExpandoInput, MultiSelectionHandler } from "@finos/vuu-ui-controls"; -import { CommitHandler, getVuuTable } from "@finos/vuu-utils"; +import { CommitHandler, getVuuTable, NO_DATA_MATCH } from "@finos/vuu-utils"; import { Option } from "@salt-ds/core"; import { FormEvent, @@ -27,8 +27,6 @@ export interface FilterClauseTextValueEditorProps value: string | string[]; } -const NO_DATA_MATCH = ["No matching data"]; - export const FilterClauseValueEditorText = forwardRef( function FilterClauseTextValueEditor( { diff --git a/vuu-ui/packages/vuu-table/src/useControlledTableNavigation.ts b/vuu-ui/packages/vuu-table/src/useControlledTableNavigation.ts index 929fc8124..a4358e2bf 100644 --- a/vuu-ui/packages/vuu-table/src/useControlledTableNavigation.ts +++ b/vuu-ui/packages/vuu-table/src/useControlledTableNavigation.ts @@ -1,5 +1,4 @@ -import { useStateRef } from "@finos/vuu-ui-controls"; -import { dispatchMouseEvent } from "@finos/vuu-utils"; +import { dispatchMouseEvent, useStateRef } from "@finos/vuu-utils"; import { KeyboardEventHandler, useCallback, useRef } from "react"; export const isRowSelectionKey = (key: string) => diff --git a/vuu-ui/packages/vuu-ui-controls/package.json b/vuu-ui/packages/vuu-ui-controls/package.json index ac29a6142..2c2bc79bf 100644 --- a/vuu-ui/packages/vuu-ui-controls/package.json +++ b/vuu-ui/packages/vuu-ui-controls/package.json @@ -15,6 +15,7 @@ "@finos/vuu-table-types": "0.0.26" }, "dependencies": { + "@finos/vuu-data-react": "0.0.26", "@finos/vuu-layout": "0.0.26", "@finos/vuu-popups": "0.0.26", "@finos/vuu-table": "0.0.26", diff --git a/vuu-ui/packages/vuu-ui-controls/src/__tests__/__component__/vuu-typeahead-input/VuuTypeaheadInput.cy.tsx b/vuu-ui/packages/vuu-ui-controls/src/__tests__/__component__/vuu-typeahead-input/VuuTypeaheadInput.cy.tsx new file mode 100644 index 000000000..49a4fd5c0 --- /dev/null +++ b/vuu-ui/packages/vuu-ui-controls/src/__tests__/__component__/vuu-typeahead-input/VuuTypeaheadInput.cy.tsx @@ -0,0 +1,177 @@ +import { + CurrencyWithTypeaheadAllowFreeText, + CurrencyWithTypeaheadDisallowFreeText, +} from "../../../../../../showcase/src/examples/UiControls/VuuTypeaheadInput.examples"; + +describe("VuuTypeaheadInput", () => { + describe("Given a TypeaheadInput that shows currency suggestions and allows free text", () => { + describe("Then a matched input pattern will show currency suggestions", () => { + it("first of which which can be selected to commit by pressing Enter", () => { + const onCommit = cy.stub().as("onCommit"); + cy.mount(); + cy.findByRole("combobox").type("G"); + cy.findByRole("listbox").should("be.visible"); + cy.findAllByRole("option").should("have.length", 2); + cy.findAllByRole("option") + .eq(0) + .should("have.class", "saltOption-active"); + cy.findAllByRole("option") + .eq(0) + .should("have.class", "saltOption-focusVisible"); + cy.realPress("Enter"); + cy.get("@onCommit").should( + "be.calledWithMatch", + { type: "keydown" }, + "GBP", + ); + cy.findByRole("listbox").should("not.exist"); + }); + it("any of which which can be selected (and committed) by clicking", () => { + const onCommit = cy.stub().as("onCommit"); + cy.mount(); + cy.findByRole("combobox").type("G"); + cy.findByRole("listbox").should("be.visible"); + cy.findAllByRole("option").should("have.length", 2); + cy.findAllByRole("option").eq(1).click(); + cy.get("@onCommit").should( + "be.calledWithMatch", + { type: "click" }, + "GBX", + ); + cy.findByRole("listbox").should("not.exist"); + }); + it("which can be navigated with Arrow key", () => { + const onCommit = cy.stub().as("onCommit"); + cy.mount(); + cy.findByRole("combobox").type("G"); + cy.findByRole("listbox").should("be.visible"); + cy.findAllByRole("option").should("have.length", 2); + cy.findAllByRole("option") + .eq(0) + .should("have.class", "saltOption-active"); + cy.findAllByRole("option") + .eq(0) + .should("have.class", "saltOption-focusVisible"); + cy.realPress("ArrowDown"); + cy.realPress("Enter"); + cy.get("@onCommit").should( + "be.calledWithMatch", + { type: "keydown" }, + "GBX", + ); + cy.findByRole("listbox").should("not.exist"); + }); + it("a complete match will always show one suggestion, Enter commits", () => { + const onCommit = cy.stub().as("onCommit"); + cy.mount(); + cy.findByRole("combobox").type("GBP"); + cy.findByRole("listbox").should("be.visible"); + cy.findAllByRole("option").should("have.length", 1); + cy.findAllByRole("option") + .eq(0) + .should("have.class", "saltOption-active"); + cy.findAllByRole("option") + .eq(0) + .should("have.class", "saltOption-focusVisible"); + cy.realPress("Enter"); + cy.get("@onCommit").should( + "be.calledWithMatch", + { type: "keydown" }, + "GBP", + ); + cy.findByRole("listbox").should("not.exist"); + }); + }); + + describe("Then a non-matched input pattern will show no suggestions", () => { + it("and any text can be committed", () => { + const onCommit = cy.stub().as("onCommit"); + cy.mount(); + cy.findByRole("combobox").type("abc"); + + cy.findAllByRole("option").should("have.length", 1); + cy.findAllByRole("option") + .eq(0) + .should("have.attr", "aria-disabled", "true"); + cy.findAllByRole("option") + .eq(0) + .should("have.text", "No matching data"); + + cy.realPress("Enter"); + cy.get("@onCommit").should( + "be.calledWithMatch", + { type: "keydown" }, + "abc", + ); + }); + it("then clearing previously committed text will automatically commit", () => { + const onCommit = cy.stub().as("onCommit"); + cy.mount(); + cy.findByRole("combobox").type("abc"); + cy.realPress("Enter"); + cy.get("@onCommit").should( + "be.calledWithMatch", + { type: "keydown" }, + "abc", + ); + cy.realPress("Backspace"); + cy.realPress("Backspace"); + cy.realPress("Backspace"); + + cy.get("@onCommit").should( + "be.calledWithMatch", + { type: "keydown" }, + "", + ); + }); + }); + }); + + describe("Given a TypeaheadInput that shows currency suggestions and DISALLOWS free text", () => { + it("Then a non-matched input pattern will show no suggestions", () => { + const onCommit = cy.stub().as("onCommit"); + cy.mount(); + cy.findByRole("combobox").type("abc"); + + cy.findAllByRole("option").should("have.length", 1); + cy.findAllByRole("option") + .eq(0) + .should("have.attr", "aria-disabled", "true"); + cy.findAllByRole("option").eq(0).should("have.text", "No matching data"); + + cy.realPress("Enter"); + cy.get("@onCommit").should( + "be.calledWithMatch", + { type: "keydown" }, + "abc", + ); + }); + it("Then commit will not be allowed when input text matches no suggestions", () => { + const onCommit = cy.stub().as("onCommit"); + cy.mount(); + cy.findByRole("combobox").type("abc"); + cy.realPress("Enter"); + cy.get("@onCommit").should("not.be.called"); + }); + + it("Then warning will be shown if commit attempted on non matching text", () => { + const onCommit = cy.stub().as("onCommit"); + cy.mount(); + cy.findByRole("combobox").type("abc"); + cy.realPress("Enter"); + + cy.findAllByRole("option").should("have.length", 1); + cy.findAllByRole("option") + .eq(0) + .should("have.attr", "aria-disabled", "true"); + cy.wait(200); + cy.findAllByRole("option") + .eq(0) + .invoke("text") + .should( + "contain", + "Please select a value from the list of suggestions", + ); + }); + }); +}); diff --git a/vuu-ui/packages/vuu-ui-controls/src/common-hooks/index.ts b/vuu-ui/packages/vuu-ui-controls/src/common-hooks/index.ts index 4c54dfdc4..ae3748717 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/common-hooks/index.ts +++ b/vuu-ui/packages/vuu-ui-controls/src/common-hooks/index.ts @@ -6,4 +6,3 @@ export * from "./useControlled"; export * from "./use-resize-observer"; export * from "./navigationTypes"; export * from "./selectionTypes"; -export * from "./useStateRef"; diff --git a/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/VuuTypeaheadInput.css b/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/VuuTypeaheadInput.css index 1b4fb833d..50a3ce380 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/VuuTypeaheadInput.css +++ b/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/VuuTypeaheadInput.css @@ -33,3 +33,7 @@ width: var(--vuu-icon-size); } } + +.vuuTypeaheadOption[aria-disabled="true"] { + color: var(--salt-content-secondary-foreground); +} diff --git a/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/VuuTypeaheadInput.tsx b/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/VuuTypeaheadInput.tsx index 4df98e4b0..f1f989054 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/VuuTypeaheadInput.tsx +++ b/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/VuuTypeaheadInput.tsx @@ -1,8 +1,5 @@ -import type { - SuggestionProvider, - TableSchemaTable, -} from "@finos/vuu-data-types"; -import type { CommitHandler } from "@finos/vuu-utils"; +import type { TableSchemaTable } from "@finos/vuu-data-types"; +import { NO_DATA_MATCH, type CommitHandler } from "@finos/vuu-utils"; import { ComboBox, Option } from "@salt-ds/core"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; @@ -11,16 +8,29 @@ import { useVuuTypeaheadInput } from "./useVuuTypeaheadInput"; import vuuTypeaheadInputCss from "./VuuTypeaheadInput.css"; const classBase = "vuuTypeaheadInput"; +const [noMatchingData] = NO_DATA_MATCH; export interface VuuTypeaheadInputProps { + /** + * Allows a text string to be submitted that does not match any suggestion + * Defaults to true + */ + allowFreeInput?: boolean; column: string; + /** + * A warning to display to the user if allowFreeText is false and they attempt + * to commit text which does not match any suggestions. A default message will + * be shown if not provided + */ + freeTextWarning?: string; onCommit: CommitHandler; - suggestionProvider?: SuggestionProvider; table: TableSchemaTable; } export const VuuTypeaheadInput = ({ + allowFreeInput, column, + freeTextWarning, onCommit, table, }: VuuTypeaheadInputProps) => { @@ -32,7 +42,9 @@ export const VuuTypeaheadInput = ({ }); const { inputProps, + noFreeText, onChange, + onKeyDown, onOpenChange, onSelectionChange, open, @@ -40,15 +52,20 @@ export const VuuTypeaheadInput = ({ typeaheadValues, value, } = useVuuTypeaheadInput({ + allowFreeInput, column, + freeTextWarning, onCommit, table, }); + + console.log(`render with values ${typeaheadValues.join(",")}`); return ( {typeaheadValues.map((state) => ( - ); diff --git a/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/useVuuTypeaheadInput.ts b/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/useVuuTypeaheadInput.ts index 2f494bd9e..940550f7a 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/useVuuTypeaheadInput.ts +++ b/vuu-ui/packages/vuu-ui-controls/src/vuu-typeahead-input/useVuuTypeaheadInput.ts @@ -1,10 +1,17 @@ import { useTypeaheadSuggestions } from "@finos/vuu-data-react"; import type { TypeaheadParams } from "@finos/vuu-protocol-types"; -import { dispatchKeyboardEvent, getVuuTable } from "@finos/vuu-utils"; +import { + dispatchKeyboardEvent, + getVuuTable, + useStateRef, + NO_DATA_MATCH, +} from "@finos/vuu-utils"; import { ComponentPropsWithoutRef, + KeyboardEventHandler, useCallback, useEffect, + useMemo, useRef, useState, type ChangeEventHandler, @@ -13,26 +20,54 @@ import { } from "react"; import type { VuuTypeaheadInputProps } from "./VuuTypeaheadInput"; -const NO_DATA_MATCH = ["No matching data"]; - export type VuuTypeaheadInputHookProps = Pick< VuuTypeaheadInputProps, - "column" | "onCommit" | "table" + "allowFreeInput" | "column" | "freeTextWarning" | "onCommit" | "table" >; +const defaultFreeTextWarning = + "Please select a value from the list of suggestions. If no suggestions match your text, then the value is not valid. If you believe this should be a valid value, please reach out to the support team"; + export const useVuuTypeaheadInput = ({ + allowFreeInput = true, column, + freeTextWarning, onCommit, table, }: VuuTypeaheadInputHookProps) => { - const [value, setValue] = useState(""); + const NO_FREE_TEXT = useMemo( + () => [freeTextWarning ?? defaultFreeTextWarning], + [freeTextWarning], + ); + const [valueRef, setValue] = useStateRef(""); const [open, setOpen] = useState(false); + const inputRef = useRef(null); const rootRef = useRef(null); const [typeaheadValues, setTypeaheadValues] = useState([]); const getSuggestions = useTypeaheadSuggestions(); + const pendingListFocusRef = useRef(false); + + const { current: value } = valueRef; + + const handleKeyDown = useCallback>( + (evt) => { + const { current: value } = valueRef; + if (evt.key === "Enter" && value !== "") { + if (allowFreeInput) { + onCommit?.(evt, value, "text-input"); + setOpen(false); + } else { + setTypeaheadValues(NO_FREE_TEXT); + } + } + }, + [NO_FREE_TEXT, allowFreeInput, onCommit, valueRef], + ); const callbackRef = useCallback>((el) => { rootRef.current = el; + const input = el?.querySelector("input") ?? null; + inputRef.current = input; }, []); useEffect(() => { @@ -48,10 +83,20 @@ export const useVuuTypeaheadInput = ({ // TODO is this right setTypeaheadValues([]); } else if (suggestions.length === 0 && value) { - setTypeaheadValues(NO_DATA_MATCH); + setTypeaheadValues((values) => + // Do not update if we have already set suggestions to the no free text warning + values === NO_FREE_TEXT ? NO_FREE_TEXT : NO_DATA_MATCH, + ); } else { setTypeaheadValues(suggestions); + if (pendingListFocusRef.current && inputRef.current) { + // This is a workaround for the fact that ComboBox does not automatically + // highlight first list item when items have been populated dynamically. + // This has been raised as a bug. + dispatchKeyboardEvent(inputRef.current, "keydown", "ArrowUp"); + } } + pendingListFocusRef.current = false; }) .catch((err) => { console.error("Error getting suggestions", err); @@ -60,27 +105,21 @@ export const useVuuTypeaheadInput = ({ setTypeaheadValues([]); } } - }, [table, column, getSuggestions, value]); + }, [table, column, getSuggestions, value, NO_FREE_TEXT]); const handleChange: ChangeEventHandler = (evt) => { const { value: newValue } = evt.target; - + const { current: value } = valueRef; if (value === "" && newValue) { setOpen(true); const input = rootRef.current?.querySelector("input"); if (input) { - // This is a workaround for the fact that ComboBox does not automatically - // highlight first list item when items have been populated dynamically. - // This has been raised as a bug. - setTimeout(() => { - dispatchKeyboardEvent(input, "keydown", "ArrowUp"); - }, 150); + pendingListFocusRef.current = true; } } else if (newValue === "" && value) { // treat clear value as a commit event onCommit(evt, ""); } - setValue(newValue); }; @@ -88,7 +127,6 @@ export const useVuuTypeaheadInput = ({ evt: SyntheticEvent, [newSelected]: string[], ) => { - console.log(`useVuuTypeahead handleSelectionChange ${newSelected}`); setValue(newSelected); onCommit( evt as SyntheticEvent, @@ -98,7 +136,7 @@ export const useVuuTypeaheadInput = ({ }; const handleOpenChange = (newOpen: boolean) => { - if (newOpen && value === "") { + if (newOpen && valueRef.current === "") { // ignore this, don't open dropdown unless user has typed at least one character } else { setOpen(newOpen); @@ -109,14 +147,17 @@ export const useVuuTypeaheadInput = ({ autoComplete: "off", }; + const [noFreeText] = NO_FREE_TEXT; return { inputProps, + noFreeText, onChange: handleChange, + onKeyDown: handleKeyDown, onOpenChange: handleOpenChange, onSelectionChange: handleSelectionChange, open, ref: callbackRef, typeaheadValues, - value, + value: valueRef.current, }; }; diff --git a/vuu-ui/packages/vuu-utils/src/form-utils.ts b/vuu-ui/packages/vuu-utils/src/form-utils.ts index 01cccf862..466789712 100644 --- a/vuu-ui/packages/vuu-utils/src/form-utils.ts +++ b/vuu-ui/packages/vuu-utils/src/form-utils.ts @@ -1,5 +1,5 @@ import { VuuRowDataItemType } from "@finos/vuu-protocol-types"; -import { SyntheticEvent } from "react"; +import { KeyboardEvent, SyntheticEvent } from "react"; import { queryClosest } from "./html-utils"; /** @@ -22,4 +22,8 @@ export type InputSource = "typeahead-suggestion" | "text-input"; export type CommitHandler< E extends HTMLElement = HTMLInputElement, T extends VuuRowDataItemType | undefined = string, -> = (evt: SyntheticEvent, value: T, source?: InputSource) => void; +> = ( + evt: SyntheticEvent | KeyboardEvent, + value: T, + source?: InputSource, +) => void; diff --git a/vuu-ui/packages/vuu-utils/src/index.ts b/vuu-ui/packages/vuu-utils/src/index.ts index 649eff250..6eb827803 100644 --- a/vuu-ui/packages/vuu-utils/src/index.ts +++ b/vuu-ui/packages/vuu-utils/src/index.ts @@ -48,10 +48,12 @@ export * from "./shell-layout-types"; export * from "./sort-utils"; export * from "./table-schema-utils"; export * from "./text-utils"; +export * from "./typeahead-utils"; export * from "./ThemeProvider"; export * from "./ts-utils"; export * from "./url-utils"; export * from "./useId"; +export * from "./useStateRef"; export * from "./user-types"; export * from "./useLayoutEffectSkipFirst"; diff --git a/vuu-ui/packages/vuu-utils/src/typeahead-utils.ts b/vuu-ui/packages/vuu-utils/src/typeahead-utils.ts new file mode 100644 index 000000000..9776b7c92 --- /dev/null +++ b/vuu-ui/packages/vuu-utils/src/typeahead-utils.ts @@ -0,0 +1 @@ +export const NO_DATA_MATCH = ["No matching data"]; diff --git a/vuu-ui/packages/vuu-ui-controls/src/common-hooks/useStateRef.ts b/vuu-ui/packages/vuu-utils/src/useStateRef.ts similarity index 73% rename from vuu-ui/packages/vuu-ui-controls/src/common-hooks/useStateRef.ts rename to vuu-ui/packages/vuu-utils/src/useStateRef.ts index e28c8659e..c87ff3206 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/common-hooks/useStateRef.ts +++ b/vuu-ui/packages/vuu-utils/src/useStateRef.ts @@ -7,12 +7,21 @@ import { useState, } from "react"; +/** + * Extension to useState that maintains a ref for the + * current state value. Useful where use of the ref can + * avoid referencing the state vale in a dependency array + * eg on a native event handler. + * + * @param value the initial value to store + */ + const isSimpleStateValue = (arg: SetStateAction): arg is T => typeof arg !== "function"; // Keeps a ref value in sync with a state value export const useStateRef = ( - initialValue: T + initialValue: T, ): [MutableRefObject, Dispatch>] => { const [value, _setValue] = useState(initialValue); const ref = useRef(value); diff --git a/vuu-ui/showcase/src/examples/Filters/FilterClause/FilterClause.examples.tsx b/vuu-ui/showcase/src/examples/Filters/FilterClause/FilterClause.examples.tsx index 16b4bc36d..553e1b723 100644 --- a/vuu-ui/showcase/src/examples/Filters/FilterClause/FilterClause.examples.tsx +++ b/vuu-ui/showcase/src/examples/Filters/FilterClause/FilterClause.examples.tsx @@ -1,63 +1,49 @@ -import { getSchema, vuuModule } from "@finos/vuu-data-test"; +import { LocalDataSourceProvider, getSchema } from "@finos/vuu-data-test"; import { SchemaColumn, TableSchema } from "@finos/vuu-data-types"; import { FilterClauseModel, FilterClause } from "@finos/vuu-filters"; import { useMemo } from "react"; -import { useAutoLoginToVuuServer } from "../../utils"; import "./FilterClause.examples.css"; import { ColumnDescriptorsByName } from "@finos/vuu-filter-types"; let displaySequence = 1; -export const NewFilterClause = () => { - const tableSchema = getSchema("instruments"); - const { typeaheadHook } = vuuModule("SIMUL"); - - const filterClauseModel = useMemo(() => new FilterClauseModel({}), []); - +const FilterClauseTemplate = ({ + filterClauseModel = new FilterClauseModel({}), + tableSchema = getSchema("instruments"), + columnsByName = columnDescriptorsByName(tableSchema.columns), +}: { + columnsByName?: ColumnDescriptorsByName; + filterClauseModel?: FilterClauseModel; + tableSchema?: TableSchema; +}) => { return (
); }; -NewFilterClause.displaySequence = displaySequence++; - -export const NewFilterClauseNoCompletions = () => { - const tableSchema = getSchema("instruments"); - - const filterClauseModel = useMemo(() => new FilterClauseModel({}), []); - - const alwaysEmptyTypeaheadHook = useMemo(() => { - const suggestionFetcher = async () => { - return []; - }; - - return () => suggestionFetcher; - }, []); +export const NewFilterClause = () => { return ( -
- -
+ + + ); }; +NewFilterClause.displaySequence = displaySequence++; + +export const NewFilterClauseNoCompletions = () => { + return ; +}; NewFilterClauseNoCompletions.displaySequence = displaySequence++; export const PartialFilterClauseColumnOnly = () => { - useAutoLoginToVuuServer(); - const tableSchema = getSchema("instruments"); const filterClauseModel = useMemo( () => new FilterClauseModel({ @@ -65,22 +51,15 @@ export const PartialFilterClauseColumnOnly = () => { }), [], ); - return ( -
- -
+ + + ); }; PartialFilterClauseColumnOnly.displaySequence = displaySequence++; export const PartialFilterClauseColumnAndOperator = () => { - const { typeaheadHook } = vuuModule("SIMUL"); - const tableSchema = getSchema("instruments"); const filterClauseModel = useMemo( () => new FilterClauseModel({ @@ -89,25 +68,15 @@ export const PartialFilterClauseColumnAndOperator = () => { }), [], ); - return ( -
- -
+ + + ); }; PartialFilterClauseColumnAndOperator.displaySequence = displaySequence++; export const CompleteFilterClauseTextEquals = () => { - const { typeaheadHook } = vuuModule("SIMUL"); - const tableSchema = getSchema("instruments"); - const filterClauseModel = useMemo( () => new FilterClauseModel({ @@ -119,21 +88,14 @@ export const CompleteFilterClauseTextEquals = () => { ); return ( -
- -
+ + + ); }; CompleteFilterClauseTextEquals.displaySequence = displaySequence++; export const PartialFilterClauseDateColumnOnly = () => { - const { typeaheadHook } = vuuModule("SIMUL"); - const tableColumns: SchemaColumn[] = [ { name: "tradeDate", @@ -173,14 +135,13 @@ export const PartialFilterClauseDateColumnOnly = () => { }, []); return ( -
- + -
+ ); }; PartialFilterClauseDateColumnOnly.displaySequence = displaySequence++; diff --git a/vuu-ui/showcase/src/examples/Filters/FilterEditor/FilterEditor.examples.tsx b/vuu-ui/showcase/src/examples/Filters/FilterEditor/FilterEditor.examples.tsx index 217ef9edc..6984d73e6 100644 --- a/vuu-ui/showcase/src/examples/Filters/FilterEditor/FilterEditor.examples.tsx +++ b/vuu-ui/showcase/src/examples/Filters/FilterEditor/FilterEditor.examples.tsx @@ -1,4 +1,4 @@ -import { getSchema, vuuModule } from "@finos/vuu-data-test"; +import { LocalDataSourceProvider, getSchema } from "@finos/vuu-data-test"; import type { SchemaColumn, TableSchema } from "@finos/vuu-data-types"; import type { Filter } from "@finos/vuu-filter-types"; import { @@ -18,8 +18,6 @@ const FilterEditorTemplate = ({ columnDescriptors = tableSchema.columns, ...props }: Partial) => { - const { typeaheadHook } = vuuModule("SIMUL"); - const onCancel = useCallback(() => { console.log(`cancel filter edit`); }, []); @@ -47,14 +45,15 @@ const FilterEditorTemplate = ({ onCancel={onCancel} onSave={onSave} style={style} - suggestionProvider={typeaheadHook} tableSchema={tableSchema} /> ); }; export const NewFilter = (props: Partial) => ( - + + + ); NewFilter.displaySequence = displaySequence++; @@ -86,11 +85,13 @@ export const NewFilterDateColumns = (props: Partial) => { }, []); return ( - + + + ); }; @@ -105,7 +106,11 @@ export const EditSimplerFilter = (props: Partial) => { }; }, []); - return ; + return ( + + + + ); }; EditSimplerFilter.displaySequence = displaySequence++; @@ -128,7 +133,11 @@ export const EditMultiClauseAndFilter = (props: Partial) => { }; }, []); - return ; + return ( + + + + ); }; EditMultiClauseAndFilter.displaySequence = displaySequence++; @@ -151,6 +160,10 @@ export const EditMultiClauseOrFilter = (props: Partial) => { }; }, []); - return ; + return ( + + + + ); }; EditMultiClauseOrFilter.displaySequence = displaySequence++; diff --git a/vuu-ui/showcase/src/examples/UiControls/VuuTypeaheadInput.examples.tsx b/vuu-ui/showcase/src/examples/UiControls/VuuTypeaheadInput.examples.tsx new file mode 100644 index 000000000..468375b85 --- /dev/null +++ b/vuu-ui/showcase/src/examples/UiControls/VuuTypeaheadInput.examples.tsx @@ -0,0 +1,119 @@ +import { LocalDataSourceProvider } from "@finos/vuu-data-test"; +import { TableSchemaTable } from "@finos/vuu-data-types"; +import { ColumnDescriptor } from "@finos/vuu-table-types"; +import { VuuTypeaheadInput } from "@finos/vuu-ui-controls"; +import { CommitHandler } from "@finos/vuu-utils"; +import { CSSProperties } from "react"; + +let displaySequence = 1; + +const TypeaheadInputTemplate = ({ + allowFreeInput, + column = { name: "currency", serverDataType: "string" }, + onCommit, + table = { module: "SIMUL", table: "instrumentsExtended" }, +}: { + allowFreeInput?: boolean; + column?: ColumnDescriptor; + onCommit?: CommitHandler; + style?: CSSProperties; + table?: TableSchemaTable; +}) => { + const handleCommit: CommitHandler = (evt, value) => { + console.log(`commit ${value}`); + onCommit?.(evt, value); + }; + + return ( + + ); +}; + +export const CurrencyWithTypeaheadAllowFreeText = ({ + onCommit, +}: { + onCommit?: CommitHandler; +}) => { + return ( + +
+ +
+
+ ); +}; +CurrencyWithTypeaheadAllowFreeText.displaySequence = displaySequence++; + +export const CurrencyWithTypeaheadDisallowFreeText = ({ + onCommit, +}: { + onCommit?: CommitHandler; +}) => { + return ( + +
+ +
+
+ ); +}; +CurrencyWithTypeaheadDisallowFreeText.displaySequence = displaySequence++; + +export const CurrencyNoTypeaheadAllowFreeText = () => { + return ( +
+ +
+ ); +}; +CurrencyNoTypeaheadAllowFreeText.displaySequence = displaySequence++; + +export const CurrencyNoTypeaheadDisallowFreeText = () => { + return ( +
+ +
+ ); +}; +CurrencyNoTypeaheadDisallowFreeText.displaySequence = displaySequence++; diff --git a/vuu-ui/showcase/src/examples/UiControls/index.ts b/vuu-ui/showcase/src/examples/UiControls/index.ts index a78681e54..ec610d6cf 100644 --- a/vuu-ui/showcase/src/examples/UiControls/index.ts +++ b/vuu-ui/showcase/src/examples/UiControls/index.ts @@ -10,3 +10,4 @@ export * as Tree from "./Tree.examples"; export * as SplitButton from "./SplitButton.examples"; export * as Tabstrip from "./Tabstrip.examples"; export * as VuuInput from "./VuuInput.examples"; +export * as VuuTypeaheadInput from "./VuuTypeaheadInput.examples";