Skip to content

Commit

Permalink
fix(use-checkbox): add form-control support to use-checkbox (#5564)
Browse files Browse the repository at this point in the history
  • Loading branch information
santialbo committed Feb 17, 2022
1 parent b0ff068 commit 44c9fab
Show file tree
Hide file tree
Showing 7 changed files with 503 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-clocks-thank.md
@@ -0,0 +1,5 @@
---
"@chakra-ui/checkbox": patch
---

Add Form-Control support for useCheckbox
1 change: 1 addition & 0 deletions packages/checkbox/package.json
Expand Up @@ -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",
Expand Down
36 changes: 26 additions & 10 deletions packages/checkbox/src/use-checkbox.ts
@@ -1,3 +1,4 @@
import { useFormControlProps } from "@chakra-ui/form-control"
import {
useBoolean,
useCallbackRef,
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
71 changes: 69 additions & 2 deletions 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,
Expand Down Expand Up @@ -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 (
<chakra.label
Expand Down Expand Up @@ -283,3 +290,63 @@ export const CustomCheckboxGroup = () => {
</Stack>
)
}
export const WithFormControl = () => {
return (
<>
<FormControl id="optIn">
<FormLabel>Opt-in Example</FormLabel>
<CheckboxGroup defaultValue={["1", "3"]}>
<HStack>
<Checkbox value="1">Opt-in 1</Checkbox>
<Checkbox value="2">Opt-in 2</Checkbox>
<Checkbox value="3">Opt-in 3</Checkbox>
</HStack>
</CheckboxGroup>
</FormControl>

<FormControl id="optInInvalid" isInvalid mt={4}>
<FormLabel>Invalid Opt-in Example</FormLabel>
<CheckboxGroup defaultValue={["2", "3"]}>
<Stack spacing={2}>
<Checkbox value="1">Invalid Opt-in 1</Checkbox>
<Checkbox value="2">Invalid Opt-in 2</Checkbox>
<Checkbox value="3">Invalid Opt-in 3</Checkbox>
</Stack>
</CheckboxGroup>
</FormControl>

<FormControl id="optInDisabled" isDisabled mt={4}>
<FormLabel>Disabled Opt-in Example</FormLabel>
<CheckboxGroup defaultValue={["2", "3"]}>
<Stack spacing={2}>
<Checkbox value="1">Disabled Opt-in 1</Checkbox>
<Checkbox value="2">Disabled Opt-in 2</Checkbox>
<Checkbox value="3">Disabled Opt-in 3</Checkbox>
</Stack>
</CheckboxGroup>
</FormControl>

<FormControl id="optInReadonly" isReadOnly mt={4}>
<FormLabel>Readonly Opt-in Example</FormLabel>
<CheckboxGroup defaultValue={["2", "3"]}>
<Stack spacing={2}>
<Checkbox value="1">Readonly Opt-in 1</Checkbox>
<Checkbox value="2">Readonly Opt-in 2</Checkbox>
<Checkbox value="3">Readonly Opt-in 3</Checkbox>
</Stack>
</CheckboxGroup>
</FormControl>

<FormControl id="optInRequired" isRequired mt={4}>
<FormLabel>Required Opt-in Example</FormLabel>
<CheckboxGroup defaultValue={["2", "3"]}>
<Stack spacing={2}>
<Checkbox value="1">Required Opt-in 1</Checkbox>
<Checkbox value="2">Required Opt-in 2</Checkbox>
<Checkbox value="3">Required Opt-in 3</Checkbox>
</Stack>
</CheckboxGroup>
</FormControl>
</>
)
}
202 changes: 202 additions & 0 deletions packages/checkbox/tests/checkbox.test.tsx
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
<FormControl isDisabled mt={4}>
<FormLabel>Disabled Opt-in Example</FormLabel>
<CheckboxGroup>
<Checkbox value="1">Disabled Opt-in 1</Checkbox>
<Checkbox value="2" isDisabled>
Disabled Opt-in 2
</Checkbox>
<Checkbox value="3" isDisabled={false}>
Disabled Opt-in 3
</Checkbox>
</CheckboxGroup>
<CheckboxGroup isDisabled={false}>
<Checkbox value="1">Disabled Opt-in 1</Checkbox>
<Checkbox value="2" isDisabled>
Disabled Opt-in 2
</Checkbox>
<Checkbox value="3" isDisabled={false}>
Disabled Opt-in 3
</Checkbox>
</CheckboxGroup>
</FormControl>,
)

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(
<FormControl isInvalid mt={4}>
<FormLabel>Invalid Opt-in Example</FormLabel>
<CheckboxGroup>
<Checkbox value="1">Invalid Opt-in 1</Checkbox>
<Checkbox value="2" isInvalid>
Invalid Opt-in 2
</Checkbox>
<Checkbox value="3" isInvalid={false}>
Invalid Opt-in 3
</Checkbox>
</CheckboxGroup>
</FormControl>,
)

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(
<FormControl isRequired mt={4}>
<FormLabel>Required Opt-in Example</FormLabel>
<CheckboxGroup>
<Checkbox value="1">Required Opt-in 1</Checkbox>
<Checkbox value="2" isRequired>
Required Opt-in 2
</Checkbox>
<Checkbox value="3" isRequired={false}>
Required Opt-in 3
</Checkbox>
</CheckboxGroup>
</FormControl>,
)

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(
<FormControl isReadOnly mt={4}>
<FormLabel>ReadOnly Opt-in Example</FormLabel>
<CheckboxGroup>
<Checkbox value="1">ReadOnly Opt-in 1</Checkbox>
<Checkbox value="2" isReadOnly>
ReadOnly Opt-in 2
</Checkbox>
<Checkbox value="3" isReadOnly={false}>
ReadOnly Opt-in 3
</Checkbox>
</CheckboxGroup>
</FormControl>,
)

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(
<FormControl mt={4} onFocus={formControlOnFocusMock}>
<FormLabel>onFocus xample</FormLabel>
<CheckboxGroup>
<Checkbox value="1" onFocus={checkboxOnFocusMock}>
onFocus Opt-in 1
</Checkbox>
</CheckboxGroup>
</FormControl>,
)

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(
<FormControl mt={4} onBlur={formControlOnBlurMock}>
<FormLabel>onBlur Example</FormLabel>
<CheckboxGroup>
<Checkbox value="1" onBlur={checkboxOnBlurMock}>
onBlur EOpt-in 1
</Checkbox>
</CheckboxGroup>
</FormControl>,
)

const [checkboxOne] = Array.from(container.querySelectorAll("input"))
fireEvent.focus(checkboxOne)
fireEvent.blur(checkboxOne)
expect(formControlOnBlurMock).toHaveBeenCalled()
expect(checkboxOnBlurMock).toHaveBeenCalled()
})

1 comment on commit 44c9fab

@vercel
Copy link

@vercel vercel bot commented on 44c9fab Feb 17, 2022

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.