Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add selectOnTab to ComboBox #3399

Merged
merged 1 commit into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions packages/core/src/__tests__/__e2e__/combo-box/ComboBox.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {
ObjectValue,
MultiplePills,
MultiplePillsTruncated,
SelectOnTab,
} = composeStories(comboBoxStories);

describe("Given a ComboBox", () => {
Expand Down Expand Up @@ -64,6 +65,46 @@ describe("Given a ComboBox", () => {
);
});

it("single select should be able to filter and select an option with a Tab key press by default", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(<Default onSelectionChange={selectionChangeSpy} />);
cy.findByRole("combobox").realClick();
cy.realType("Ala");
cy.findByRole("option", { name: "Alabama" }).should("be.activeDescendant");
cy.realPress("Tab");
cy.findByRole("combobox").should("have.value", "Alabama");
cy.get("@selectionChange").should(
"have.been.calledWith",
Cypress.sinon.match.any,
Cypress.sinon.match.array.deepEquals(["Alabama"])
);
});

it("single select should be able to filter and select an option with a Tab key press when selectOnTab is passed as true", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(<Default selectOnTab onSelectionChange={selectionChangeSpy} />);
cy.findByRole("combobox").realClick();
cy.realType("Ala");
cy.realPress("Tab");
cy.findByRole("combobox").should("have.value", "Alabama");
cy.get("@selectionChange").should(
"have.been.calledWith",
Cypress.sinon.match.any,
Cypress.sinon.match.array.deepEquals(["Alabama"])
);
});

it("single select should not be able to filter and select an option with a Tab key press when selectOnTab is passed as false", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(
<Default selectOnTab={false} onSelectionChange={selectionChangeSpy} />
);
cy.findByRole("combobox").realClick();
cy.realType("Ala");
cy.realPress("Tab");
cy.get("@selectionChange").should("not.have.been.called");
});

it("should be able to filter and select quick-select an option with Enter", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(<Default onSelectionChange={selectionChangeSpy} />);
Expand Down Expand Up @@ -281,6 +322,42 @@ describe("Given a ComboBox", () => {
cy.get("@selectionChange").should("not.have.been.called");
});

it("should not allow you to select a disabled option if selectOnTab is true", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(
<DisabledOption
selectOnTab
multiselect
onSelectionChange={selectionChangeSpy}
/>
);
cy.findByRole("combobox").realClick();
cy.findByRole("option", { name: "California" }).should(
"have.attr",
"aria-disabled",
"true"
);
cy.realType("California");
cy.realPress("Tab");
cy.get("@selectionChange").should("not.have.been.called");
});

it("should not allow you to select a option on Tab press if list is not open", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(
<DisabledOption
open={false}
selectOnTab
multiselect
onSelectionChange={selectionChangeSpy}
/>
);
cy.findByRole("combobox").realClick();
cy.realType("alabama");
cy.realPress("Tab");
cy.get("@selectionChange").should("not.have.been.called");
});

it("should allow multiple options to be selected with a mouse", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(<Multiselect onSelectionChange={selectionChangeSpy} />);
Expand Down Expand Up @@ -354,6 +431,49 @@ describe("Given a ComboBox", () => {
cy.findByRole("combobox").should("have.value", "");
});

it("should allow multiselect to select on tab key press if selectOnTab is passed as true", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(<SelectOnTab onSelectionChange={selectionChangeSpy} />);
cy.findByRole("combobox").realClick();
cy.realType("Ala");
cy.realPress("Tab");
cy.get("@selectionChange").should(
"have.been.calledWith",
Cypress.sinon.match.any,
Cypress.sinon.match.array.deepEquals(["Alabama"])
);
cy.findByRole("button", { name: /^Alabama/ }).should("be.visible");
});

it("by default multiselect should not allow to select on tab key press", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(<Multiselect onSelectionChange={selectionChangeSpy} />);
cy.findByRole("combobox").realClick();
cy.realType("Ala");
cy.realPress("Tab");
cy.get("@selectionChange").should("not.have.been.called");
});

it("should not allow multiselect to deselect the already selected value on tab key press if selectOnTab is passed as true", () => {
const selectionChangeSpy = cy.stub().as("selectionChange");
cy.mount(<SelectOnTab onSelectionChange={selectionChangeSpy} />);
cy.findByRole("combobox").realClick();
cy.realType("Ala");
cy.realPress("Tab");
cy.get("@selectionChange").should(
"have.been.calledWith",
Cypress.sinon.match.any,
Cypress.sinon.match.array.deepEquals(["Alabama"])
);
cy.findByRole("button", { name: /^Alabama/ }).should("be.visible");
cy.findByRole("combobox").realClick();
cy.realType("Alabama");
cy.realPress("Tab");
cy.findByRole("button", { name: /^Alabama/ }).should("be.visible");
cy.findByRole("combobox").realClick();
cy.findByRole("option", { name: "Alabama" }).should("be.ariaSelected");
});

it("should have form field support", () => {
cy.mount(<WithFormField />);
cy.findByRole("combobox").should("have.accessibleName", "State");
Expand Down
13 changes: 12 additions & 1 deletion packages/core/src/combo-box/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export type ComboBoxProps<Item = string> = {
* The options to display in the combo box.
*/
children?: ReactNode;
/**
* If true, options will be selected on tab key press.
*/
selectOnTab?: boolean;
} & UseComboBoxProps<Item> &
PillInputProps;

Expand All @@ -60,6 +64,7 @@ export const ComboBox = forwardRef(function ComboBox<Item>(
endAdornment,
readOnly: readOnlyProp,
multiselect,
selectOnTab = multiselect ? false : true,
onSelectionChange,
selected,
defaultSelected,
Expand Down Expand Up @@ -248,7 +253,13 @@ export const ComboBox = forwardRef(function ComboBox<Item>(

break;
case "Tab":
if (!multiselect && activeState) {
if (
openState &&
selectOnTab &&
activeState &&
!activeState?.disabled &&
!selectedState.includes(activeState?.value)
origami-z marked this conversation as resolved.
Show resolved Hide resolved
) {
select(event, activeState);
}
break;
Expand Down
6 changes: 6 additions & 0 deletions packages/core/stories/combo-box/combo-box.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,12 @@ export const FreeText: StoryFn<ComboBoxProps> = (args) => {
);
};

export const SelectOnTab = Template.bind({});
SelectOnTab.args = {
multiselect: true,
selectOnTab: true,
};

export const ClearSelection: StoryFn<ComboBoxProps> = (args) => {
const [value, setValue] = useState(getTemplateDefaultValue(args));
const [selected, setSelected] = useState<string[]>([]);
Expand Down
8 changes: 8 additions & 0 deletions site/docs/components/combo-box/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ Use the `truncate` prop when you are limited on space and want to prevent the mu

</LivePreview>

<LivePreview componentName="combo-box" exampleName="SelectOnTab" >

## Select on Tab

Use the `selectOnTab` prop to select the active option from the list on Tab key press.

</LivePreview>

<LivePreview componentName="combo-box" exampleName="ClearSelection" >

## Clear Selection
Expand Down
34 changes: 34 additions & 0 deletions site/src/examples/combo-box/SelectOnTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ChangeEvent, ReactElement, useState } from "react";
import { ComboBox, ComboBoxProps, Option } from "@salt-ds/core";
import { shortColorData } from "./exampleData";

export const SelectOnTab = (): ReactElement => {
const [value, setValue] = useState("");
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setValue(value);
};

const handleSelectionChange: ComboBoxProps["onSelectionChange"] = () => {
setValue("");
};

return (
<ComboBox
onChange={handleChange}
onSelectionChange={handleSelectionChange}
value={value}
multiselect
style={{ width: "266px" }}
selectOnTab
>
{shortColorData
.filter((color) =>
color.toLowerCase().includes(value.trim().toLowerCase())
)
.map((color) => (
<Option value={color} key={color} />
))}
</ComboBox>
);
};
1 change: 1 addition & 0 deletions site/src/examples/combo-box/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export * from "./CustomFiltering";
export * from "./ServerSideData";
export * from "./ObjectValues";
export * from "./Truncation";
export * from "./SelectOnTab";
export * from "./ClearSelection";
Loading