Skip to content

Commit

Permalink
Tokenized input next (#2798)
Browse files Browse the repository at this point in the history
Co-authored-by: lilyvc <[email protected]>
  • Loading branch information
Fercas123 and lilyvc authored Jan 15, 2024
1 parent 8dd0170 commit bbc4ccb
Show file tree
Hide file tree
Showing 24 changed files with 2,435 additions and 1 deletion.
11 changes: 11 additions & 0 deletions .changeset/lucky-worms-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@salt-ds/lab": patch
---

Added TokenizedInputNext to lab.

Tokenized input provides an input field for text that’s converted into a pill within the field, or tokenized, when the user enters a delimiting character.

```tsx
<TokenizedInputNext />
```
2 changes: 1 addition & 1 deletion packages/core/src/form-field-context/FormFieldContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface A11yValueProps {
labelId?: string;
}

type NecessityType = "required" | "optional" | "asterisk";
export type NecessityType = "required" | "optional" | "asterisk";

export interface a11yValueAriaProps {
"aria-labelledby": A11yValueProps["labelId"];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { composeStories } from "@storybook/react";
import * as tokenizedInputNextStories from "@stories/tokenized-input-next/tokenized-input-next.stories";
import { checkAccessibility } from "../../../../../../cypress/tests/checkAccessibility";

const composedStories = composeStories(tokenizedInputNextStories);

const { Default, WithCollapsedButton } = composedStories;

describe("GIVEN a Tokenized Input", () => {
checkAccessibility(composedStories);
describe("WHEN disabled", () => {
it("SHOULD mount as disabled", () => {
cy.mount(<Default disabled />);
cy.findByRole("textbox").should("be.disabled");
});
});
describe("WHEN readonly", () => {
it("SHOULD mount as readonly", () => {
cy.mount(<Default defaultSelected={["Tokyo"]} readOnly />);
cy.findByRole("textbox").should("have.attr", "readonly");
});
it("should not allow to remove or add items", () => {
cy.mount(<Default defaultSelected={["Tokyo"]} readOnly />);
cy.findByRole("option").should("exist");
// Remove the item
cy.realPress("ArrowLeft");
cy.realPress("Backspace");

// pill should not have been removed
cy.findByRole("option").should("exist");
});
});
describe("WHEN mounted as an uncontrolled component", () => {
it("should render the Tokenized Input with pre selected items", () => {
cy.mount(<WithCollapsedButton />);
cy.findByRole("textbox").should("exist");
cy.findByTestId("expand-button").should("exist");
});
it("should allow adding items by typing and pressing the delimiter", () => {
cy.mount(<Default />);
cy.findByRole("textbox").focus();
cy.findByRole("textbox").type("Tokio,");
cy.findByRole("option").should("exist");
});
it.skip("should highlihht pills if navigating with arrows", () => {
// FIXME: option and testid are in the button, not pill wrapper
cy.mount(<Default defaultSelected={["Tokyo", "Delhi", "Shanghai"]} />);
cy.findByRole("textbox").focus();

// navigating trough pills with arrows
cy.realPress("ArrowLeft");
cy.findAllByRole("option")
.eq(2)
.should("have.class", "saltInputPill-pillHighlighted");
cy.realPress("ArrowLeft");
cy.realPress("ArrowRight");
cy.findAllByRole("option")
.eq(1)
.should("have.class", "saltInputPill-pillHighlighted");
});
it("should be able to change delimiter", () => {
cy.mount(<Default delimiters={[";"]} />);
cy.findByRole("textbox").focus();
cy.findByRole("textbox").type("Tokio, Delhi, Shanghai");
cy.findAllByRole("option").should("have.length", 0);
cy.findByRole("textbox").type("{selectall}{backspace}");
cy.findByRole("textbox").type("Tokio; Delhi; Shanghai;");
cy.findAllByRole("option").should("have.length", 3);
});
it("should be able to take an array of delimiters", () => {
cy.mount(<Default delimiters={[";", "/", "."]} />);
cy.findByRole("textbox").focus();
cy.findByRole("textbox").type("Tokio, Delhi, Shanghai");
cy.findAllByRole("option").should("have.length", 0);
cy.findByRole("textbox").type("{selectall}{backspace}");
cy.findByRole("textbox").type("Tokio; Delhi/ Shanghai.");
cy.findAllByRole("option").should("have.length", 3);
});
it("should allow removing items by clicking on the close button", () => {
cy.mount(<Default defaultSelected={["Tokyo"]} />);
cy.findByRole("textbox").should("exist");
cy.findByRole("textbox").focus();
// pill should exist
cy.findByRole("option").should("exist");

// Remove the item
cy.realPress("ArrowLeft");
cy.realPress("Backspace");

// pill should not exist after removal
cy.findByRole("option").should("not.exist");
});

it("should clear input on clicking the clear button", () => {
cy.mount(<Default defaultSelected={["Tokyo"]} />);
cy.findByRole("textbox").focus();
cy.findByTestId("clear-button").realClick();
cy.findByRole("textbox").should("have.value", "");
});
it("should expand on clicking the expand button and collapse when blur", () => {
cy.mount(<WithCollapsedButton />);
cy.findByRole("textbox").focus();
cy.findAllByRole("option").should("have.length", 50);
cy.get('[data-testid="pill"]').eq(49).should("be.visible");
// Move focus out of Tokenized input
cy.realPress("Tab");
cy.realPress("Tab");

cy.findByRole("textbox").should("not.be.focused");
cy.get('[data-testid="pill"]').should("have.length", 50);
cy.findAllByTestId("pill").eq(49).should("not.be.visible");
});
it("should not display the clear button if there is no selection", () => {
cy.mount(<Default />);
cy.findByTestId("clear-button").should("not.exist");
});
it("should return focus to input if an item is closed", () => {
cy.mount(<Default defaultSelected={["Tokyo"]} />);
cy.findByRole("textbox").focus();
// clear
cy.findByTestId("clear-button").realClick();
cy.findByRole("textbox").should("have.focus");
});
it("should trigger event callbacks when actions are prompted", () => {
const onChangeSpy = cy.spy().as("onChange");
const onClearSpy = cy.spy().as("onClear");
const onExpandSpy = cy.spy().as("onExpand");
const onCollapseSpy = cy.spy().as("onCollapse");
cy.mount(
<WithCollapsedButton
onChange={onChangeSpy}
onClear={onClearSpy}
onExpand={onExpandSpy}
onCollapse={onCollapseSpy}
/>
);
// expand
cy.findByTestId("expand-button").should("exist");
cy.findByTestId("expand-button").realClick();
// Move focus out of Tokenized input
cy.realPress("Tab");
cy.realPress("Tab");
//clear input
cy.findByRole("textbox").focus();
cy.findByTestId("clear-button").realClick();
cy.findByRole("textbox").should("have.focus");
// type and add an item
cy.findByRole("textbox").type("Tokio,");
cy.findByRole("option").should("exist");

cy.get("@onClear").should("have.been.called");
cy.get("@onExpand").should("have.been.called");
cy.get("@onChange").should("have.been.called");
});
});

describe("WHEN mounted as a controlled component", () => {
it("THEN have the specified value", () => {
cy.mount(<Default defaultSelected={["Delhi"]} value="Tokio" />);
cy.findByRole("textbox").should("have.value", "Tokio");
});

describe("THEN the user input is updated", () => {
it("SHOULD call onChange with the new value", () => {
const changeSpy = cy.stub().as("changeSpy");
cy.mount(
<Default
defaultSelected={["Delhi"]}
value="Tokio"
onChange={changeSpy}
/>
);
cy.findByRole("textbox").focus();
cy.findByRole("textbox").click().clear().type("Mexico City,");
cy.get("@changeSpy").should("have.been.called");
});
});
});
});
1 change: 1 addition & 0 deletions packages/lab/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export * from "./tabs";
export * from "./tabs-next";
export * from "./toast-group";
export * from "./tokenized-input";
export * from "./tokenized-input-next";
export * from "./toolbar";
export * from "./tree";
export * from "./utils";
Expand Down
Loading

1 comment on commit bbc4ccb

@vercel
Copy link

@vercel vercel bot commented on bbc4ccb Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.