-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Checkbox and CheckboxGroup components (#463)
* scaffolding Checkbox and CheckboxGroup components * roughing out component styling * component styling * isSelected and isIndeterminate state handling * roughing out CheckboxGroup * refining Checkbox and CheckboxGroup styling * CheckboxGroup error handling * fiddling with styling * slow, grinding progress * fix isSelected/isIndeterminate styling collision * required and error handling * stories and docs * cleaning up checkbox and label positioning * fleshing out checkbox stories and docs * add selected + invalid style * expanding checkboxgroup docs and stories * further docs and stories expansion * Merge pull request #468 from bcgov/feature/bump-react-component-library-deps Bump React component library dependencies * Revert "Merge pull request #468 from bcgov/feature/bump-react-component-library-deps" This reverts commit 60c564b. * update Checkbox to use 3.1.0 tokens * Add controlled checkbox example to Vite kitchensink app * Move cursor CSS rules to Checkbox container (label) component * Fix typos in Checkbox stories * Disabled Checkbox label gets disabled font color * Add Checkbox styling for isDisabled + isIndeterminate state * Add Checkbox styling for isIndeterminate + isInvalid state * Change Checkbox flex container gap to 10px to match current design * Update Checkbox border styles for current design * Remove extended interface from Checkbox component * Remove children prop from CheckboxGroupProps interface in favor of children from ReactAriaCheckboxGroupProps * Add flexWrap prop to CheckboxGroup to allow wrapping * Add isInvalid and isSelected args to Checkbox story meta object * Add failing tests for Checkbox * Make tests for Checkbox pass by adding explicit type information to render props * Add failing tests for CheckboxGroup * Make tests for CheckboxGroup pass by adding explicit type information to render props * Move CheckboxGroup flexWrap styling prop to options list from parent container * add flexWrap to storybook args --------- Co-authored-by: Tyler Krys <[email protected]>
- Loading branch information
1 parent
22ac1f3
commit f859877
Showing
20 changed files
with
824 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
packages/react-components/src/components/Checkbox/Checkbox.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
.bcds-react-aria-Checkbox { | ||
cursor: pointer; | ||
display: flex; | ||
flex-direction: row; | ||
gap: 10px; | ||
align-items: center; | ||
} | ||
|
||
/* Checkbox */ | ||
.bcds-react-aria-Checkbox > .checkbox { | ||
align-self: first baseline; | ||
display: flex; | ||
margin-top: var(--layout-margin-hair); | ||
justify-content: space-around; | ||
width: var(--icons-size-small); | ||
height: var(--icons-size-small); | ||
min-width: var(--icons-size-small); | ||
background-color: var(--surface-color-secondary-default); | ||
border-color: var(--surface-color-border-medium); | ||
border-style: solid; | ||
border-width: var(--layout-border-width-small); | ||
border-radius: var(--layout-border-radius-small); | ||
} | ||
.bcds-react-aria-Checkbox > .checkbox > svg { | ||
align-self: center; | ||
} | ||
|
||
/* Label */ | ||
.bcds-react-aria-Checkbox > .label { | ||
font: var(--typography-regular-small-body); | ||
color: var(--typography-color-secondary); | ||
} | ||
|
||
/* Focus */ | ||
.bcds-react-aria-Checkbox[data-focused] > .checkbox { | ||
outline: solid var(--layout-border-width-medium) | ||
var(--surface-color-border-active); | ||
outline-offset: var(--layout-margin-hair); | ||
} | ||
|
||
/* Hover */ | ||
.bcds-react-aria-Checkbox[data-hovered] > .checkbox { | ||
border-color: var(--surface-color-border-dark); | ||
} | ||
.bcds-react-aria-Checkbox[data-selected][data-hovered] > .checkbox { | ||
background-color: var(--surface-color-primary-hover); | ||
border-color: var(--surface-color-primary-hover); | ||
} | ||
|
||
/* Pressed */ | ||
.bcds-react-aria-Checkbox[data-pressed] > .checkbox { | ||
background-color: var(--surface-color-primary-default); | ||
} | ||
|
||
/* Selected */ | ||
.bcds-react-aria-Checkbox[data-selected] > .checkbox { | ||
background-color: var(--surface-color-primary-default); | ||
border-color: var(--surface-color-primary-default); | ||
} | ||
.bcds-react-aria-Checkbox[data-selected] > .checkbox > svg { | ||
color: var(--icons-color-primary-invert); | ||
} | ||
|
||
/* Disabled */ | ||
.bcds-react-aria-Checkbox[data-disabled] { | ||
cursor: not-allowed; | ||
} | ||
.bcds-react-aria-Checkbox[data-disabled] > .checkbox { | ||
border-color: var(--surface-color-border-medium); | ||
background-color: var(--surface-color-primary-disabled); | ||
} | ||
.bcds-react-aria-Checkbox[data-disabled] > .label { | ||
color: var(--typography-color-disabled); | ||
} | ||
.bcds-react-aria-Checkbox[data-selected][data-disabled] > .checkbox { | ||
border-color: var(--surface-color-primary-disabled); | ||
background-color: var(--surface-color-primary-disabled); | ||
} | ||
.bcds-react-aria-Checkbox[data-selected][data-disabled] > .checkbox > svg { | ||
color: var(--icons-color-disabled); | ||
} | ||
|
||
/* Indeterminate */ | ||
.bcds-react-aria-Checkbox[data-indeterminate] > .checkbox { | ||
background-color: var(--surface-color-primary-default); | ||
border-color: var(--surface-color-primary-default); | ||
} | ||
.bcds-react-aria-Checkbox[data-indeterminate][data-disabled] > .checkbox { | ||
background-color: var(--surface-color-primary-disabled); | ||
border-color: var(--surface-color-primary-disabled); | ||
} | ||
.bcds-react-aria-Checkbox[data-indeterminate][data-invalid] > .checkbox { | ||
background-color: var(--surface-color-primary-danger-button-default); | ||
} | ||
.bcds-react-aria-Checkbox[data-indeterminate] > .checkbox > svg { | ||
color: var(--icons-color-primary-invert); | ||
align-self: center; | ||
vertical-align: middle; | ||
} | ||
.bcds-react-aria-Checkbox[data-indeterminate][data-disabled] > .checkbox > svg { | ||
color: var(--icons-color-disabled); | ||
} | ||
|
||
/* Read-only */ | ||
.bcds-react-aria-Checkbox[data-readonly] { | ||
cursor: not-allowed; | ||
} | ||
|
||
/* Invalid */ | ||
.bcds-react-aria-Checkbox[data-invalid] > .checkbox { | ||
border-color: var(--support-border-color-danger); | ||
} | ||
.bcds-react-aria-Checkbox[data-invalid] > .checkbox > svg { | ||
color: var(--icons-color-primary-invert); | ||
} | ||
.bcds-react-aria-Checkbox[data-selected][data-invalid] > .checkbox { | ||
background-color: var(--surface-color-primary-danger-button-default); | ||
} |
22 changes: 22 additions & 0 deletions
22
packages/react-components/src/components/Checkbox/Checkbox.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { render, screen, fireEvent } from "@testing-library/react"; | ||
import "@testing-library/jest-dom"; // for matchers like toBeChecked | ||
|
||
import Checkbox from "./Checkbox"; | ||
|
||
describe("Checkbox component", () => { | ||
test("Checkbox renders unchecked, user clicks it, checkbox is checked", () => { | ||
render(<Checkbox>I agree</Checkbox>); | ||
const checkbox = screen.getByLabelText(/i agree/i); | ||
expect(checkbox).not.toBeChecked(); | ||
fireEvent.click(checkbox); | ||
expect(checkbox).toBeChecked(); | ||
}); | ||
|
||
test("Checkbox renders checked, user clicks it, checkbox is unchecked", () => { | ||
render(<Checkbox defaultSelected={true}>Email me my results</Checkbox>); | ||
const checkbox = screen.getByLabelText(/email me my results/i); | ||
expect(checkbox).toBeChecked(); | ||
fireEvent.click(checkbox); | ||
expect(checkbox).not.toBeChecked(); | ||
}); | ||
}); |
32 changes: 32 additions & 0 deletions
32
packages/react-components/src/components/Checkbox/Checkbox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { | ||
Checkbox as ReactAriaCheckbox, | ||
CheckboxProps, | ||
CheckboxRenderProps, | ||
} from "react-aria-components"; | ||
|
||
import SvgCheckIcon from "../Icons/SvgCheckIcon"; | ||
import SvgDashIcon from "../Icons/SvgDashIcon"; | ||
|
||
import "./Checkbox.css"; | ||
|
||
export default function Checkbox({ value, children, ...props }: CheckboxProps) { | ||
return ( | ||
<ReactAriaCheckbox | ||
className="bcds-react-aria-Checkbox" | ||
value={value} | ||
{...props} | ||
> | ||
{({ isRequired, isSelected, isIndeterminate }: CheckboxRenderProps) => ( | ||
<> | ||
<div className="checkbox"> | ||
{isSelected && !isIndeterminate && <SvgCheckIcon />} | ||
{isIndeterminate && <SvgDashIcon />} | ||
</div> | ||
<span className="label"> | ||
<>{children}</> {isRequired && "(required)"} | ||
</span> | ||
</> | ||
)} | ||
</ReactAriaCheckbox> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default } from "./Checkbox"; | ||
export type { CheckboxProps } from "./Checkbox"; |
48 changes: 48 additions & 0 deletions
48
packages/react-components/src/components/CheckboxGroup/CheckboxGroup.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
.bcds-react-aria-CheckboxGroup { | ||
display: flex; | ||
flex-direction: column; | ||
gap: var(--layout-margin-small); | ||
} | ||
|
||
/* Orientation */ | ||
.bcds-react-aria-CheckboxGroup--options { | ||
display: flex; | ||
} | ||
|
||
.bcds-react-aria-CheckboxGroup--options.vertical { | ||
flex-direction: column; | ||
gap: var(--layout-margin-small); | ||
} | ||
|
||
.bcds-react-aria-CheckboxGroup--options.horizontal { | ||
flex-direction: row; | ||
gap: var(--layout-margin-medium); | ||
} | ||
.bcds-react-aria-CheckboxGroup--options.horizontal.flex-wrap-nowrap { | ||
flex-wrap: nowrap; | ||
} | ||
.bcds-react-aria-CheckboxGroup--options.horizontal.flex-wrap-wrap { | ||
flex-wrap: wrap; | ||
} | ||
.bcds-react-aria-CheckboxGroup--options.horizontal.flex-wrap-wrap-reverse { | ||
flex-wrap: wrap-reverse; | ||
} | ||
|
||
/* Parts */ | ||
.bcds-react-aria-CheckboxGroup--label { | ||
font: var(--typography-regular-small-body); | ||
color: var(--typography-color-primary); | ||
} | ||
|
||
.bcds-react-aria-CheckboxGroup--description { | ||
font: var(--typography-regular-small-body); | ||
color: var(--typography-color-secondary); | ||
} | ||
|
||
.bcds-react-aria-CheckboxGroup--error { | ||
display: inline-flex; | ||
flex-direction: row; | ||
gap: var(--layout-margin-xsmall); | ||
font: var(--typography-regular-small-body); | ||
color: var(--typography-color-danger); | ||
} |
32 changes: 32 additions & 0 deletions
32
packages/react-components/src/components/CheckboxGroup/CheckboxGroup.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { render, screen, fireEvent } from "@testing-library/react"; | ||
import "@testing-library/jest-dom"; // for matchers like toBeChecked | ||
|
||
import CheckboxGroup from "./CheckboxGroup"; | ||
import Checkbox from "../Checkbox/Checkbox"; | ||
|
||
describe("CheckboxGroup component", () => { | ||
beforeEach(() => { | ||
render( | ||
<CheckboxGroup label="Fruits" defaultValue={["orange"]}> | ||
<Checkbox value="apple">Apple</Checkbox> | ||
<Checkbox value="orange">Orange</Checkbox> | ||
<Checkbox value="banana">Banana</Checkbox> | ||
</CheckboxGroup> | ||
); | ||
}); | ||
|
||
test("renders 3 checkboxes with middle one checked", () => { | ||
const checkboxes = screen.getAllByRole("checkbox"); | ||
expect(checkboxes).toHaveLength(3); | ||
expect(checkboxes[0]).not.toBeChecked(); | ||
expect(checkboxes[1]).toBeChecked(); | ||
expect(checkboxes[2]).not.toBeChecked(); | ||
}); | ||
|
||
test("user clicks 'orange' option, it is no longer checked", () => { | ||
const checkboxOrange = screen.getByLabelText(/orange/i); | ||
expect(checkboxOrange).toBeChecked(); | ||
fireEvent.click(checkboxOrange); | ||
expect(checkboxOrange).not.toBeChecked(); | ||
}); | ||
}); |
74 changes: 74 additions & 0 deletions
74
packages/react-components/src/components/CheckboxGroup/CheckboxGroup.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { | ||
CheckboxGroup as ReactAriaCheckboxGroup, | ||
CheckboxGroupRenderProps, | ||
FieldError, | ||
Label, | ||
Text, | ||
} from "react-aria-components"; | ||
|
||
import type { | ||
CheckboxGroupProps as ReactAriaCheckboxGroupProps, | ||
ValidationResult, | ||
} from "react-aria-components"; | ||
|
||
import "./CheckboxGroup.css"; | ||
import SvgExclamationIcon from "../Icons/SvgExclamationIcon"; | ||
|
||
export interface CheckboxGroupProps extends ReactAriaCheckboxGroupProps { | ||
/* Group orientation, defaults to `vertical` */ | ||
orientation?: "horizontal" | "vertical"; | ||
/* Group label */ | ||
label?: string; | ||
/* Group description */ | ||
description?: string; | ||
/* Error message */ | ||
errorMessage?: string | ((validation: ValidationResult) => string); | ||
/** `flex-wrap` style property, defaults to `nowrap` */ | ||
flexWrap?: "nowrap" | "wrap" | "wrap-reverse"; | ||
} | ||
|
||
export default function CheckboxGroup({ | ||
orientation = "vertical", | ||
label, | ||
description, | ||
errorMessage, | ||
flexWrap = "nowrap", | ||
children, | ||
...props | ||
}: CheckboxGroupProps) { | ||
return ( | ||
<ReactAriaCheckboxGroup | ||
className={`bcds-react-aria-CheckboxGroup`} | ||
{...props} | ||
> | ||
{({ isRequired, isInvalid }: CheckboxGroupRenderProps) => ( | ||
<> | ||
{label && ( | ||
<Label className="bcds-react-aria-CheckboxGroup--label"> | ||
{label} {isRequired && "(required)"} | ||
</Label> | ||
)} | ||
<div | ||
className={`bcds-react-aria-CheckboxGroup--options ${orientation} flex-wrap-${flexWrap}`} | ||
> | ||
<>{children}</> | ||
</div> | ||
{description && ( | ||
<Text | ||
slot="description" | ||
className="bcds-react-aria-CheckboxGroup--description" | ||
> | ||
{description} | ||
</Text> | ||
)} | ||
{isInvalid && ( | ||
<div className="bcds-react-aria-CheckboxGroup--error"> | ||
<SvgExclamationIcon /> | ||
<FieldError>{errorMessage}</FieldError> | ||
</div> | ||
)} | ||
</> | ||
)} | ||
</ReactAriaCheckboxGroup> | ||
); | ||
} |
2 changes: 2 additions & 0 deletions
2
packages/react-components/src/components/CheckboxGroup/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default } from "./CheckboxGroup"; | ||
export type { CheckboxGroupProps } from "./CheckboxGroup"; |
18 changes: 18 additions & 0 deletions
18
packages/react-components/src/components/Icons/SvgCheckIcon/SvgCheckIcon.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export default function SvgCheckIcon({ id = "check-icon" }) { | ||
return ( | ||
<svg | ||
id={id} | ||
width="10" | ||
height="10" | ||
viewBox="0 0 10 10" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<g> | ||
<path | ||
d="M9.09447 1.16176C8.67766 0.834916 8.07489 0.907409 7.74747 1.32376L3.63547 6.55676L2.21847 4.85676C1.87495 4.46651 1.2832 4.42064 0.883639 4.75329C0.484082 5.08594 0.421942 5.6762 0.743468 6.08476L2.92047 8.69776C2.94047 8.72176 2.96947 8.72976 2.99047 8.75176C3.0133 8.78196 3.03802 8.81068 3.06447 8.83776C3.10707 8.86542 3.15189 8.8895 3.19847 8.90976C3.23068 8.92977 3.26407 8.9478 3.29847 8.96376C3.41159 9.01295 3.53316 9.03977 3.65647 9.04276H3.65747C3.78585 9.04006 3.91238 9.01149 4.02947 8.95876C4.06502 8.9413 4.09942 8.92159 4.13247 8.89976C4.18141 8.87729 4.22826 8.85052 4.27247 8.81976C4.29837 8.79134 4.32242 8.76128 4.34447 8.72976C4.36447 8.70976 4.39347 8.69976 4.41247 8.67576L9.25747 2.50976C9.58477 2.09253 9.51178 1.48896 9.09447 1.16176V1.16176Z" | ||
fill="currentColor" | ||
/> | ||
</g> | ||
</svg> | ||
); | ||
} |
1 change: 1 addition & 0 deletions
1
packages/react-components/src/components/Icons/SvgCheckIcon/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from "./SvgCheckIcon"; |
16 changes: 16 additions & 0 deletions
16
packages/react-components/src/components/Icons/SvgDashIcon/SvgDashIcon.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export default function SvgDashIcon({ id = "dash-icon" }) { | ||
return ( | ||
<svg | ||
id={id} | ||
width="8" | ||
height="10" | ||
viewBox="0 0 8 2" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M6.99005 1.96004H1.01005C0.479855 1.96004 0.0500488 1.53023 0.0500488 1.00004C0.0500488 0.469846 0.479855 0.0400391 1.01005 0.0400391H6.99005C7.52024 0.0400391 7.95005 0.469846 7.95005 1.00004C7.95005 1.53023 7.52024 1.96004 6.99005 1.96004H6.99005Z" | ||
fill="currentColor" | ||
/> | ||
</svg> | ||
); | ||
} |
1 change: 1 addition & 0 deletions
1
packages/react-components/src/components/Icons/SvgDashIcon/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from "./SvgDashIcon"; |
Oops, something went wrong.