From 44c9fab5b0e34484c9afb858a553d4a2aa30209d Mon Sep 17 00:00:00 2001 From: Santi Albo Date: Thu, 17 Feb 2022 11:23:52 +0100 Subject: [PATCH] fix(use-checkbox): add form-control support to use-checkbox (#5564) --- .changeset/fair-clocks-thank.md | 5 + packages/checkbox/package.json | 1 + packages/checkbox/src/use-checkbox.ts | 36 +++- .../checkbox/stories/checkbox.stories.tsx | 71 +++++- packages/checkbox/tests/checkbox.test.tsx | 202 ++++++++++++++++++ packages/switch/stories/switch.stories.tsx | 58 ++++- packages/switch/tests/switch.test.tsx | 146 ++++++++++++- 7 files changed, 503 insertions(+), 16 deletions(-) create mode 100644 .changeset/fair-clocks-thank.md diff --git a/.changeset/fair-clocks-thank.md b/.changeset/fair-clocks-thank.md new file mode 100644 index 00000000000..2cd62665038 --- /dev/null +++ b/.changeset/fair-clocks-thank.md @@ -0,0 +1,5 @@ +--- +"@chakra-ui/checkbox": patch +--- + +Add Form-Control support for useCheckbox diff --git a/packages/checkbox/package.json b/packages/checkbox/package.json index fb0cfb2111f..50d2eb8cd5e 100644 --- a/packages/checkbox/package.json +++ b/packages/checkbox/package.json @@ -38,6 +38,7 @@ "url": "https://github.com/chakra-ui/chakra-ui/issues" }, "dependencies": { + "@chakra-ui/form-control": "1.5.6", "@chakra-ui/hooks": "1.8.2", "@chakra-ui/react-utils": "1.2.2", "@chakra-ui/utils": "1.10.2", diff --git a/packages/checkbox/src/use-checkbox.ts b/packages/checkbox/src/use-checkbox.ts index bb863ad9b8c..01603d4d664 100644 --- a/packages/checkbox/src/use-checkbox.ts +++ b/packages/checkbox/src/use-checkbox.ts @@ -1,3 +1,4 @@ +import { useFormControlProps } from "@chakra-ui/form-control" import { useBoolean, useCallbackRef, @@ -6,7 +7,7 @@ import { useUpdateEffect, } from "@chakra-ui/hooks" import { mergeRefs, PropGetter } from "@chakra-ui/react-utils" -import { callAllHandlers, dataAttr, focus, warn } from "@chakra-ui/utils" +import { callAllHandlers, dataAttr, focus, omit, warn } from "@chakra-ui/utils" import { visuallyHiddenStyle } from "@chakra-ui/visually-hidden" import React, { ChangeEvent, @@ -120,30 +121,45 @@ export interface CheckboxState { * @see Docs https://chakra-ui.com/checkbox#hooks */ export function useCheckbox(props: UseCheckboxProps = {}) { + const formControlProps = useFormControlProps(props) + const { + isDisabled, + isReadOnly, + isRequired, + isInvalid, + id, + onBlur, + onFocus, + "aria-describedby": ariaDescribedBy, + } = formControlProps + const { defaultIsChecked, defaultChecked = defaultIsChecked, isChecked: checkedProp, isFocusable, - isDisabled, - isReadOnly, - isRequired, onChange, isIndeterminate, - isInvalid, name, value, - id, - onBlur, - onFocus, tabIndex = undefined, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-invalid": ariaInvalid, - "aria-describedby": ariaDescribedBy, - ...htmlProps + ...rest } = props + const htmlProps = omit(rest, [ + "isDisabled", + "isReadOnly", + "isRequired", + "isInvalid", + "id", + "onBlur", + "onFocus", + "aria-describedby", + ]) + const onChangeProp = useCallbackRef(onChange) const onBlurProp = useCallbackRef(onBlur) const onFocusProp = useCallbackRef(onFocus) diff --git a/packages/checkbox/stories/checkbox.stories.tsx b/packages/checkbox/stories/checkbox.stories.tsx index fb05be7d5b9..756b0795da8 100644 --- a/packages/checkbox/stories/checkbox.stories.tsx +++ b/packages/checkbox/stories/checkbox.stories.tsx @@ -1,10 +1,12 @@ /* eslint-disable jsx-a11y/label-has-associated-control */ +import { FormControl, FormLabel } from "@chakra-ui/form-control" import { Icon } from "@chakra-ui/icon" import { Container, Divider, Heading, Stack, + HStack, Flex, Box, Text, @@ -234,8 +236,13 @@ export const ControlledCheckboxGroup = () => { export const CustomCheckboxGroup = () => { function CustomCheckbox(props: any) { - const { state, getCheckboxProps, getInputProps, getLabelProps, htmlProps } = - useCheckbox(props) + const { + state, + getCheckboxProps, + getInputProps, + getLabelProps, + htmlProps, + } = useCheckbox(props) return ( { ) } +export const WithFormControl = () => { + return ( + <> + + Opt-in Example + + + Opt-in 1 + Opt-in 2 + Opt-in 3 + + + + + + Invalid Opt-in Example + + + Invalid Opt-in 1 + Invalid Opt-in 2 + Invalid Opt-in 3 + + + + + + Disabled Opt-in Example + + + Disabled Opt-in 1 + Disabled Opt-in 2 + Disabled Opt-in 3 + + + + + + Readonly Opt-in Example + + + Readonly Opt-in 1 + Readonly Opt-in 2 + Readonly Opt-in 3 + + + + + + Required Opt-in Example + + + Required Opt-in 1 + Required Opt-in 2 + Required Opt-in 3 + + + + + ) +} diff --git a/packages/checkbox/tests/checkbox.test.tsx b/packages/checkbox/tests/checkbox.test.tsx index 9d1335f3cd2..90cac081542 100644 --- a/packages/checkbox/tests/checkbox.test.tsx +++ b/packages/checkbox/tests/checkbox.test.tsx @@ -9,6 +9,7 @@ import { userEvent, } from "@chakra-ui/test-utils" import * as React from "react" +import { FormControl, FormLabel } from "@chakra-ui/form-control" import { Checkbox, CheckboxGroup, @@ -327,3 +328,204 @@ test("useCheckboxGroup can handle both strings and numbers", () => { expect(checkboxTwo).not.toBeChecked() expect(checkboxThree).not.toBeChecked() }) + +test("Uncontrolled FormControl - should not check if form-control disabled", () => { + const { container } = render( + + Disabled Opt-in Example + + Disabled Opt-in 1 + + Disabled Opt-in 2 + + + Disabled Opt-in 3 + + + + Disabled Opt-in 1 + + Disabled Opt-in 2 + + + Disabled Opt-in 3 + + + , + ) + + const [ + checkboxOne, + checkboxTwo, + checkboxThree, + checkboxFour, + checkboxFive, + checkboxSix, + ] = Array.from(container.querySelectorAll("input")) + + expect(checkboxOne).toBeDisabled() + expect(checkboxTwo).toBeDisabled() + expect(checkboxThree).not.toBeDisabled() + + expect(checkboxFour).not.toBeDisabled() + expect(checkboxFive).toBeDisabled() + expect(checkboxSix).not.toBeDisabled() + + fireEvent.click(checkboxOne) + fireEvent.click(checkboxTwo) + fireEvent.click(checkboxThree) + + fireEvent.click(checkboxFour) + fireEvent.click(checkboxFive) + fireEvent.click(checkboxSix) + + expect(checkboxOne).not.toBeChecked() + expect(checkboxTwo).not.toBeChecked() + expect(checkboxThree).toBeChecked() + + expect(checkboxFour).toBeChecked() + expect(checkboxFive).not.toBeChecked() + expect(checkboxSix).toBeChecked() +}) + +test("Uncontrolled FormControl - mark label as invalid", () => { + const { container } = render( + + Invalid Opt-in Example + + Invalid Opt-in 1 + + Invalid Opt-in 2 + + + Invalid Opt-in 3 + + + , + ) + + const [checkboxOne, checkboxTwo, checkboxThree] = Array.from( + container.querySelectorAll("input"), + ) + + expect(checkboxOne).toHaveAttribute("aria-invalid", "true") + expect(checkboxTwo).toHaveAttribute("aria-invalid", "true") + expect(checkboxThree).toHaveAttribute("aria-invalid", "false") + + const [labelOne, labelTwo, labelThree] = Array.from( + container.querySelectorAll("span.chakra-checkbox__label"), + ) + + expect(labelOne).toHaveAttribute("data-invalid", "") + expect(labelTwo).toHaveAttribute("data-invalid", "") + expect(labelThree).not.toHaveAttribute("data-invalid") + + const [controlOne, controlTwo, controlThree] = Array.from( + container.querySelectorAll("span.chakra-checkbox__control"), + ) + + expect(controlOne).toHaveAttribute("data-invalid", "") + expect(controlTwo).toHaveAttribute("data-invalid", "") + expect(controlThree).not.toHaveAttribute("data-invalid") +}) + +test("Uncontrolled FormControl - mark label required", () => { + const { container } = render( + + Required Opt-in Example + + Required Opt-in 1 + + Required Opt-in 2 + + + Required Opt-in 3 + + + , + ) + + const [checkboxOne, checkboxTwo, checkboxThree] = Array.from( + container.querySelectorAll("input"), + ) + + expect(checkboxOne).toBeRequired() + expect(checkboxTwo).toBeRequired() + expect(checkboxThree).not.toBeRequired() +}) + +test("Uncontrolled FormControl - mark readonly", () => { + const { container } = render( + + ReadOnly Opt-in Example + + ReadOnly Opt-in 1 + + ReadOnly Opt-in 2 + + + ReadOnly Opt-in 3 + + + , + ) + + const [checkboxOne, checkboxTwo, checkboxThree] = Array.from( + container.querySelectorAll("input"), + ) + + expect(checkboxOne).toHaveAttribute("readOnly") + expect(checkboxTwo).toHaveAttribute("readOnly") + expect(checkboxThree).not.toHaveAttribute("readOnly") + + const [controlOne, controlTwo, controlThree] = Array.from( + container.querySelectorAll("span.chakra-checkbox__control"), + ) + + expect(controlOne).toHaveAttribute("data-readonly", "") + expect(controlTwo).toHaveAttribute("data-readonly", "") + expect(controlThree).not.toHaveAttribute("data-readonly") +}) + +test("Uncontrolled FormControl - calls all onFocus EventHandler", () => { + const formControlOnFocusMock = jest.fn() + const checkboxOnFocusMock = jest.fn() + + const { container } = render( + + onFocus xample + + + onFocus Opt-in 1 + + + , + ) + + const [checkboxOne] = Array.from(container.querySelectorAll("input")) + fireEvent.focus(checkboxOne) + expect(formControlOnFocusMock).toHaveBeenCalled() + expect(checkboxOnFocusMock).toHaveBeenCalled() +}) + +test("Uncontrolled FormControl - calls all onBlur EventHandler", () => { + const formControlOnBlurMock = jest.fn() + const checkboxOnBlurMock = jest.fn() + + const { container } = render( + + onBlur Example + + + onBlur EOpt-in 1 + + + , + ) + + const [checkboxOne] = Array.from(container.querySelectorAll("input")) + fireEvent.focus(checkboxOne) + fireEvent.blur(checkboxOne) + expect(formControlOnBlurMock).toHaveBeenCalled() + expect(checkboxOnBlurMock).toHaveBeenCalled() +}) diff --git a/packages/switch/stories/switch.stories.tsx b/packages/switch/stories/switch.stories.tsx index 8a2892b3e90..ace5e78d35f 100644 --- a/packages/switch/stories/switch.stories.tsx +++ b/packages/switch/stories/switch.stories.tsx @@ -1,7 +1,8 @@ -import { HStack } from "@chakra-ui/layout" +import { FormControl, FormLabel } from "@chakra-ui/form-control" +import { HStack, Stack } from "@chakra-ui/layout" import { chakra } from "@chakra-ui/system" import * as React from "react" -import { useForm, SubmitHandler } from "react-hook-form" +import { SubmitHandler, useForm } from "react-hook-form" import { Switch } from "../src" export default { @@ -81,9 +82,60 @@ export const WithReactHookForm = () => { return (
- {/* */} + {/* */} ) } + +export const WithFormControl = () => { + return ( + <> + + Opt-in Example + + Opt-in 1 + Opt-in 2 + Opt-in 3 + + + + + Invalid Opt-in Example + + Invalid Opt-in 1 + Invalid Opt-in 2 + Invalid Opt-in 3 + + + + + Disabled Opt-in Example + + Disabled Opt-in 1 + Disabled Opt-in 2 + Disabled Opt-in 3 + + + + + Readonly Opt-in Example + + Readonly Opt-in 1 + Readonly Opt-in 2 + Readonly Opt-in 3 + + + + + Required Opt-in Example + + Required Opt-in 1 + Required Opt-in 2 + Required Opt-in 3 + + + + ) +} diff --git a/packages/switch/tests/switch.test.tsx b/packages/switch/tests/switch.test.tsx index 4ebefde3a4d..bf87f8cdee5 100644 --- a/packages/switch/tests/switch.test.tsx +++ b/packages/switch/tests/switch.test.tsx @@ -1,4 +1,5 @@ -import { render, userEvent } from "@chakra-ui/test-utils" +import { FormControl, FormLabel } from "@chakra-ui/form-control" +import { userEvent, render, fireEvent } from "@chakra-ui/test-utils" import * as React from "react" import { Switch } from "../src" @@ -55,3 +56,146 @@ test("Controlled - should check and uncheck", () => { expect(input).not.toBeChecked() expect(onChange).toHaveBeenCalled() }) + +test("Uncontrolled FormControl - should not check if form-control disabled", () => { + const { container } = render( + + Disabled Opt-in Example + + + + , + ) + + const [switchOne, switchTwo, switchThree] = Array.from( + container.querySelectorAll("input"), + ) + + expect(switchOne).toBeDisabled() + expect(switchTwo).toBeDisabled() + expect(switchThree).not.toBeDisabled() + + userEvent.click(switchOne) + userEvent.click(switchTwo) + userEvent.click(switchThree) + + expect(switchOne).not.toBeChecked() + expect(switchTwo).not.toBeChecked() + expect(switchThree).toBeChecked() +}) + +test("Uncontrolled FormControl - mark label as invalid", () => { + const { container } = render( + + Invalid Opt-in Example + Invalid Opt-in 1 + Invalid Opt-in 2 + Invalid Opt-in 3 + , + ) + + const [switchOne, switchTwo, switchThree] = Array.from( + container.querySelectorAll("input"), + ) + + expect(switchOne).toHaveAttribute("aria-invalid", "true") + expect(switchTwo).toHaveAttribute("aria-invalid", "true") + expect(switchThree).toHaveAttribute("aria-invalid", "false") + + const [labelOne, labelTwo, labelThree] = Array.from( + container.querySelectorAll("span.chakra-switch__label"), + ) + + expect(labelOne).toHaveAttribute("data-invalid", "") + expect(labelTwo).toHaveAttribute("data-invalid", "") + expect(labelThree).not.toHaveAttribute("data-invalid") + + const [controlOne, controlTwo, controlThree] = Array.from( + container.querySelectorAll("span.chakra-switch__track"), + ) + + expect(controlOne).toHaveAttribute("data-invalid", "") + expect(controlTwo).toHaveAttribute("data-invalid", "") + expect(controlThree).not.toHaveAttribute("data-invalid") +}) + +test("Uncontrolled FormControl - mark required", () => { + const { container } = render( + + Required Opt-in Example + + + + , + ) + + const [switchOne, switchTwo, switchThree] = Array.from( + container.querySelectorAll("input"), + ) + + expect(switchOne).toBeRequired() + expect(switchTwo).toBeRequired() + expect(switchThree).not.toBeRequired() +}) + +test("Uncontrolled FormControl - mark readonly", () => { + const { container } = render( + + ReadOnly Opt-in Example + + + + , + ) + + const [switchOne, switchTwo, switchThree] = Array.from( + container.querySelectorAll("input"), + ) + + expect(switchOne).toHaveAttribute("readOnly") + expect(switchTwo).toHaveAttribute("readOnly") + expect(switchThree).not.toHaveAttribute("readOnly") + + const [controlOne, controlTwo, controlThree] = Array.from( + container.querySelectorAll("span.chakra-switch__track"), + ) + + expect(controlOne).toHaveAttribute("data-readonly", "") + expect(controlTwo).toHaveAttribute("data-readonly", "") + expect(controlThree).not.toHaveAttribute("data-readonly") +}) + +test("Uncontrolled FormControl - calls all onFocus EventHandler", () => { + const formControlOnFocusMock = jest.fn() + const switchOnFocusMock = jest.fn() + + const { container } = render( + + onFocus Example + + , + ) + + const [switchOne] = Array.from(container.querySelectorAll("input")) + fireEvent.focus(switchOne) + expect(formControlOnFocusMock).toHaveBeenCalled() + expect(switchOnFocusMock).toHaveBeenCalled() +}) + +test("Uncontrolled FormControl - calls all onBlur EventHandler", () => { + const formControlOnBlurMock = jest.fn() + const switchOnBlurMock = jest.fn() + + const { container } = render( + + onBlur Example + + , + ) + + const [switchOne] = Array.from(container.querySelectorAll("input")) + fireEvent.focus(switchOne) + fireEvent.blur(switchOne) + expect(formControlOnBlurMock).toHaveBeenCalled() + expect(switchOnBlurMock).toHaveBeenCalled() +})