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 form component #3036

Open
wants to merge 49 commits into
base: canary
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2f9214a
chore: add support validationBehavior aria
ryo-manba May 12, 2024
3cdfb4b
Merge branch 'canary' into chore/validation-behavior-external
ryo-manba May 14, 2024
556ed6a
chore: add validationBehavior to Provider
ryo-manba May 14, 2024
a70cb4e
chore: add autocomplete validation test
ryo-manba May 14, 2024
dd6016d
chore: add checkbox validation test
ryo-manba May 15, 2024
21fb084
fix(input): require condition
ryo-manba May 15, 2024
46b9109
docs: add description of validationBehavior props
ryo-manba May 15, 2024
341af23
chore: add support validationBehavior props for date components
ryo-manba May 18, 2024
8292a78
docs(dates): add description of validationBehavior props
ryo-manba May 18, 2024
6dbd032
chore: add changeset
ryo-manba May 18, 2024
3f14cff
chore: format
ryo-manba May 18, 2024
e036824
chore: fix test
ryo-manba May 18, 2024
198a3a4
fix: select validationBehavior is not support yet
ryo-manba May 18, 2024
c337b95
fix: select validationBehavior not supported yet
ryo-manba May 18, 2024
e025943
feat: add form component with input support
ryo-manba May 21, 2024
05291c9
feat: add support form context
ryo-manba May 26, 2024
25c5bfd
Merge branch 'canary' into feat/form-component
ryo-manba May 27, 2024
d3a2aa5
Merge branch 'canary' into feat/form-component
ryo-manba Jun 2, 2024
9dd85d6
chore: wip add support for form server errors
ryo-manba Jun 5, 2024
e106bb1
chore: add support checkbox server validation
ryo-manba Jun 10, 2024
c90d3de
chore: add support radio server validation
ryo-manba Jun 10, 2024
9ad68ed
chore: update pnpm-lock.yaml
ryo-manba Jun 10, 2024
031f576
chore: add support input server validation
ryo-manba Jun 11, 2024
2d747b9
chore: add support autocomplete server validation
ryo-manba Jun 17, 2024
29b2ad4
Merge branch 'canary' into feat/form-component
ryo-manba Sep 21, 2024
d5de46c
chore(form): add server validation stories
ryo-manba Sep 29, 2024
0262ca1
Merge remote-tracking branch 'origin' into feat/form-component
ryo-manba Sep 29, 2024
49a8430
chore: fix test
ryo-manba Sep 29, 2024
2118854
chore: add date-picker validation test
ryo-manba Sep 29, 2024
04275be
chore: update form stories
ryo-manba Sep 29, 2024
b6b5140
chore: add changeset
ryo-manba Sep 29, 2024
6cd9987
chore: update react-aria version
ryo-manba Sep 29, 2024
ed9c325
chore: add pnpm-lock.yaml
ryo-manba Sep 29, 2024
797254f
chore: update react-aria version
ryo-manba Sep 29, 2024
aba36bc
chore: add comment
ryo-manba Sep 29, 2024
10a18a7
chore: update react-aria version
ryo-manba Sep 29, 2024
311c39f
chore: fix change set
ryo-manba Sep 29, 2024
2990522
Merge branch 'canary' into feat/form-component
ryo-manba Oct 15, 2024
da2e93b
Merge branch 'canary' into feat/form-component
ryo-manba Oct 26, 2024
7baff39
chore: export form component in the main package
jrgarciadev Nov 4, 2024
c32610f
fix: conflicts
jrgarciadev Nov 4, 2024
13c5794
fix: conflicts
jrgarciadev Nov 4, 2024
3313077
Merge branch 'canary' into feat/form-component
ryo-manba Nov 6, 2024
3c0321a
chore: upgrade react-aria
ryo-manba Nov 6, 2024
6a95b78
chore: fixed internationalized/date version
ryo-manba Nov 6, 2024
09aa2ea
fix: build error
ryo-manba Nov 6, 2024
beaef58
chore: upgrade docs react-aria version
ryo-manba Nov 6, 2024
4fac70c
fix: remove comment
ryo-manba Nov 6, 2024
9bc5f4f
fix: debug setting
ryo-manba Nov 6, 2024
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
5 changes: 5 additions & 0 deletions .changeset/fuzzy-lies-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nextui-org/table": minor
---
ryo-manba marked this conversation as resolved.
Show resolved Hide resolved

The `layoutNode` prop has been removed due to the update to react-aria.
47 changes: 47 additions & 0 deletions .changeset/polite-mails-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
"@nextui-org/accordion": patch
"@nextui-org/avatar": patch
"@nextui-org/breadcrumbs": patch
"@nextui-org/button": patch
"@nextui-org/calendar": patch
"@nextui-org/card": patch
"@nextui-org/chip": patch
"@nextui-org/date-input": patch
"@nextui-org/divider": patch
"@nextui-org/dropdown": patch
"@nextui-org/kbd": patch
"@nextui-org/link": patch
"@nextui-org/listbox": patch
"@nextui-org/menu": patch
"@nextui-org/modal": patch
"@nextui-org/navbar": patch
"@nextui-org/pagination": patch
"@nextui-org/popover": patch
"@nextui-org/progress": patch
"@nextui-org/select": patch
"@nextui-org/slider": patch
"@nextui-org/snippet": patch
"@nextui-org/switch": patch
"@nextui-org/tabs": patch
"@nextui-org/tooltip": patch
"@nextui-org/user": patch
"@nextui-org/react": patch
"@nextui-org/system": patch
"@nextui-org/system-rsc": patch
"@nextui-org/use-aria-accordion": patch
"@nextui-org/use-aria-accordion-item": patch
"@nextui-org/use-aria-button": patch
"@nextui-org/use-aria-link": patch
"@nextui-org/use-aria-menu": patch
"@nextui-org/use-aria-modal-overlay": patch
"@nextui-org/use-aria-multiselect": patch
"@nextui-org/use-aria-overlay": patch
"@nextui-org/use-aria-toggle-button": patch
"@nextui-org/use-disclosure": patch
"@nextui-org/use-intersection-observer": patch
"@nextui-org/use-is-mobile": patch
"@nextui-org/use-pagination": patch
"@nextui-org/aria-utils": patch
---

update react-aria version
5 changes: 5 additions & 0 deletions .changeset/purple-berries-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nextui-org/form": major
---

add form component
9 changes: 9 additions & 0 deletions .changeset/sharp-pianos-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@nextui-org/autocomplete": minor
"@nextui-org/checkbox": minor
"@nextui-org/date-picker": minor
"@nextui-org/input": minor
"@nextui-org/radio": minor
---

support server validation with form
16 changes: 8 additions & 8 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@codesandbox/sandpack-react": "^2.6.4",
"@iconify/icons-solar": "^1.2.3",
"@iconify/react": "^4.1.1",
"@internationalized/date": "3.5.5",
"@internationalized/date": "3.5.6",
"@mapbox/rehype-prism": "^0.6.0",
"@nextui-org/aria-utils": "workspace:*",
"@nextui-org/badge": "workspace:*",
Expand All @@ -37,14 +37,14 @@
"@nextui-org/use-infinite-scroll": "workspace:*",
"@nextui-org/use-is-mobile": "workspace:*",
"@radix-ui/react-scroll-area": "^1.0.5",
"@react-aria/focus": "3.17.1",
"@react-aria/i18n": "3.11.1",
"@react-aria/interactions": "3.21.3",
"@react-aria/selection": "3.18.1",
"@react-aria/ssr": "3.9.4",
"@react-aria/utils": "3.24.1",
"@react-aria/focus": "3.18.2",
"@react-aria/i18n": "3.12.2",
"@react-aria/interactions": "3.22.2",
"@react-aria/selection": "3.19.3",
"@react-aria/ssr": "3.9.5",
"@react-aria/utils": "3.25.2",
"@react-aria/virtualizer": "3.10.1",
"@react-aria/visually-hidden": "3.8.12",
"@react-aria/visually-hidden": "3.8.15",
"@react-stately/data": "3.11.4",
"@react-stately/layout": "3.13.9",
"@react-stately/tree": "3.8.1",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"@commitlint/cli": "^17.2.0",
"@commitlint/config-conventional": "^17.2.0",
"@react-bootstrap/babel-preset": "^2.1.0",
"@react-types/link": "^3.4.4",
"@react-types/link": "3.5.7",
"@react-types/shared": "3.24.1",
"@storybook/react": "^7.4.6",
"@swc/core": "^1.3.35",
Expand Down
14 changes: 7 additions & 7 deletions packages/components/accordion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@
"@nextui-org/divider": "workspace:*",
"@nextui-org/use-aria-accordion": "workspace:*",
"@nextui-org/dom-animation": "workspace:*",
"@react-aria/interactions": "3.22.2",
"@react-aria/focus": "3.18.2",
"@react-aria/utils": "3.25.2",
"@react-stately/tree": "3.8.4",
"@react-aria/button": "3.9.8",
"@react-types/accordion": "3.0.0-alpha.23",
"@react-types/shared": "3.24.1"
"@react-aria/interactions": "3.22.4",
"@react-aria/focus": "3.18.4",
"@react-aria/utils": "3.25.3",
"@react-stately/tree": "3.8.5",
"@react-aria/button": "3.10.1",
"@react-types/accordion": "3.0.0-alpha.24",
"@react-types/shared": "3.25.0"
ryo-manba marked this conversation as resolved.
Show resolved Hide resolved
},
"devDependencies": {
"@nextui-org/theme": "workspace:*",
Expand Down
4 changes: 2 additions & 2 deletions packages/components/alert/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
"@nextui-org/react-utils": "workspace:*",
"@nextui-org/shared-icons": "workspace:*",
"@nextui-org/shared-utils": "workspace:*",
"@react-stately/utils": "3.10.1",
"@react-aria/utils": "3.24.1",
"@react-stately/utils": "3.10.4",
"@react-aria/utils": "3.25.3",
ryo-manba marked this conversation as resolved.
Show resolved Hide resolved
"@nextui-org/button": "workspace:*"
},
"devDependencies": {
Expand Down
194 changes: 143 additions & 51 deletions packages/components/autocomplete/__tests__/autocomplete.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from "react";
import {within, render, renderHook, act} from "@testing-library/react";
import userEvent, {UserEvent} from "@testing-library/user-event";
import {useForm} from "react-hook-form";
import {Form} from "@nextui-org/form";

import {Autocomplete, AutocompleteItem, AutocompleteProps, AutocompleteSection} from "../src";
import {Modal, ModalContent, ModalBody, ModalHeader, ModalFooter} from "../../modal/src";
Expand Down Expand Up @@ -588,6 +589,60 @@ describe("Autocomplete", () => {
expect(autocomplete2).toHaveFocus();
});

it("should work when key equals textValue", async () => {
const wrapper = render(
<Autocomplete
aria-label="Favorite Animal"
data-testid="when-key-equals-textValue"
defaultSelectedKey="cat"
items={itemsData}
label="Favorite Animal"
>
{(item) => <AutocompleteItem key={item.value}>{item.value}</AutocompleteItem>}
</Autocomplete>,
);

const autocomplete = wrapper.getByTestId("when-key-equals-textValue");

const user = userEvent.setup();

await user.click(autocomplete);

expect(autocomplete).toHaveValue("cat");
expect(autocomplete).toHaveAttribute("aria-expanded", "true");

let listboxItems = wrapper.getAllByRole("option");

await user.click(listboxItems[1]);

expect(autocomplete).toHaveValue("dog");
expect(autocomplete).toHaveAttribute("aria-expanded", "false");
});

it("should work when key equals textValue (controlled)", async () => {
const wrapper = render(
<ControlledAutocomplete data-testid="when-key-equals-textValue" items={itemsData}>
{(item) => <AutocompleteItem key={item.value}>{item.value}</AutocompleteItem>}
</ControlledAutocomplete>,
);

const autocomplete = wrapper.getByTestId("when-key-equals-textValue");

const user = userEvent.setup();

await user.click(autocomplete);

expect(autocomplete).toHaveValue("cat");
expect(autocomplete).toHaveAttribute("aria-expanded", "true");

let listboxItems = wrapper.getAllByRole("option");

await user.click(listboxItems[1]);

expect(autocomplete).toHaveValue("dog");
expect(autocomplete).toHaveAttribute("aria-expanded", "false");
});

describe("validation", () => {
let user;

Expand All @@ -598,9 +653,9 @@ describe("Autocomplete", () => {
describe("validationBehavior=native", () => {
it("supports isRequired", async () => {
const {getByTestId, getByRole, findByRole} = render(
<form data-testid="form">
<Form data-testid="form">
<AutocompleteExample isRequired validationBehavior="native" />
</form>,
</Form>,
);

const input = getByRole("combobox") as HTMLInputElement;
Expand Down Expand Up @@ -628,15 +683,71 @@ describe("Autocomplete", () => {
await user.click(items[0]);
expect(input).toHaveAttribute("aria-describedby");
});

it("supports server validation", async () => {
function Test() {
const [serverErrors, setServerErrors] = React.useState({});
const onSubmit = (e) => {
e.preventDefault();
setServerErrors({
value: "Invalid value.",
});
};

return (
<Form validationErrors={serverErrors} onSubmit={onSubmit}>
<AutocompleteExample data-testid="input" name="value" validationBehavior="native" />
<button data-testid="submit" type="submit">
Submit
</button>
</Form>
);
}

const {getByTestId, getByRole} = render(<Test />);

const input = getByTestId("input") as HTMLInputElement;

expect(input).not.toHaveAttribute("aria-describedby");

await user.click(getByTestId("submit"));

expect(input).toHaveAttribute("aria-describedby");
expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent(
"Invalid value.",
);
expect(input.validity.valid).toBe(false);

await user.tab({shift: true});
await user.keyboard("[ArrowRight]Ze");

act(() => {
jest.runAllTimers();
});

const listbox = getByRole("listbox");
const items = within(listbox).getAllByRole("option");

await user.click(items[0]);
act(() => {
jest.runAllTimers();
});

expect(input).toHaveAttribute("aria-describedby");
await user.tab();

expect(input).not.toHaveAttribute("aria-describedby");
expect(input.validity.valid).toBe(true);
});
ryo-manba marked this conversation as resolved.
Show resolved Hide resolved
});

describe("validationBehavior=aria", () => {
it("supports validate function", async () => {
let {getByRole, findByRole} = render(
<form data-testid="form">
<AutocompleteExample
defaultInputValue="Penguin"
validate={(v) => (v.inputValue === "Penguin" ? "Invalid value" : null)}
defaultSelectedKey="penguin"
validate={(v) => (v.selectedKey === "penguin" ? "Invalid value" : null)}
validationBehavior="aria"
/>
</form>,
Expand Down Expand Up @@ -664,61 +775,42 @@ describe("Autocomplete", () => {
expect(input).not.toHaveAttribute("aria-describedby");
expect(input).not.toHaveAttribute("aria-invalid");
});
});
});

it("should work when key equals textValue", async () => {
const wrapper = render(
<Autocomplete
aria-label="Favorite Animal"
data-testid="when-key-equals-textValue"
defaultSelectedKey="cat"
items={itemsData}
label="Favorite Animal"
>
{(item) => <AutocompleteItem key={item.value}>{item.value}</AutocompleteItem>}
</Autocomplete>,
);

const autocomplete = wrapper.getByTestId("when-key-equals-textValue");

const user = userEvent.setup();

await user.click(autocomplete);

expect(autocomplete).toHaveValue("cat");
expect(autocomplete).toHaveAttribute("aria-expanded", "true");

let listboxItems = wrapper.getAllByRole("option");

await user.click(listboxItems[1]);

expect(autocomplete).toHaveValue("dog");
expect(autocomplete).toHaveAttribute("aria-expanded", "false");
});

it("should work when key equals textValue (controlled)", async () => {
const wrapper = render(
<ControlledAutocomplete data-testid="when-key-equals-textValue" items={itemsData}>
{(item) => <AutocompleteItem key={item.value}>{item.value}</AutocompleteItem>}
</ControlledAutocomplete>,
);
it("supports server validation", async () => {
const {getByTestId, getByRole} = render(
<Form validationErrors={{value: "Invalid value"}}>
<AutocompleteExample data-testid="input" name="value" />
</Form>,
);

const autocomplete = wrapper.getByTestId("when-key-equals-textValue");
const input = getByTestId("input");

const user = userEvent.setup();
expect(input).toHaveAttribute("aria-describedby");
expect(input).toHaveAttribute("aria-invalid", "true");
expect(document.getElementById(input.getAttribute("aria-describedby")!)).toHaveTextContent(
"Invalid value",
);

await user.click(autocomplete);
await user.tab();
await user.keyboard("[ArrowRight]Ze");

expect(autocomplete).toHaveValue("cat");
expect(autocomplete).toHaveAttribute("aria-expanded", "true");
act(() => {
jest.runAllTimers();
});

let listboxItems = wrapper.getAllByRole("option");
const listbox = getByRole("listbox");
const items = within(listbox).getAllByRole("option");

await user.click(listboxItems[1]);
await user.click(items[0]);
act(() => {
jest.runAllTimers();
});

expect(autocomplete).toHaveValue("dog");
expect(autocomplete).toHaveAttribute("aria-expanded", "false");
await user.tab();
expect(input).not.toHaveAttribute("aria-describedby");
expect(input).not.toHaveAttribute("aria-invalid");
});
});
});
});

Expand Down
Loading
Loading