Skip to content

Commit

Permalink
Merge pull request #450 from omnifed/448-feat-make-checkbox-recipe
Browse files Browse the repository at this point in the history
448 feat make checkbox recipe
  • Loading branch information
caseybaggz committed Sep 6, 2024
2 parents 026131c + fafefc6 commit 29d0773
Show file tree
Hide file tree
Showing 15 changed files with 636 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/app/data/categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"selection": {
"name": "Selection",
"description": "Components that allow users to select choices.",
"items": ["Radio", "Select", "Toggle"]
"items": ["Drag & Drop", "Checkbox", "Radio", "Select", "Toggle"]
},
"inputs": {
"name": "Inputs",
Expand Down
36 changes: 36 additions & 0 deletions docs/app/react/checkbox/a11y.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
---

import {
WhenToUseAdmonition
} from '@/app/components/Admonition'
import OverviewList from '@/app/components/OverviewList'

## Use Cases

<OverviewList intro="Users should be able to:" rules={[
'Navigate to a checkbox with assistive technology',
'Toggle the checkbox on and off',
'Get appropriate feedback based on input type documented under Interaction & style',
]} />

## Interaction & Style

The parent checkbox has three states: selected, unselected, and mixed.

Checkboxes can be selected or unselected regardless of the state of the other checkboxes in a group.

If some, but not all, child checkboxes are checked, the parent checkbox becomes mixed. Selecting an mixed parent checkbox will check all of its child checkboxes.

## Keyboard Navigation

| Keys | Actions |
| -------- | --------------------------------------------------------------- |
| Tab | To move to the next element in a group |
| Space | Toggles a focused checkbox between selected and unselected |

## Labeling Elements

If the UI text is correctly linked to the checkbox, assistive tech (such as a screen reader) will read the UI text followed by the component's role.

The accessibility label for an individual checkbox is typically the same as its adjacent text label.
101 changes: 101 additions & 0 deletions docs/app/react/checkbox/components/checkbox-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
'use client'

import { Checkmark } from '@cerberus-design/icons'
import { Field, Label, Show } from '@cerberus-design/react'
import { hstack, vstack } from '@cerberus/styled-system/patterns'
import {
checkbox,
type CheckboxVariantProps,
} from '@cerberus/styled-system/recipes'
import {
useCallback,
useState,
type ChangeEvent,
type InputHTMLAttributes,
} from 'react'

type CheckboxProps = CheckboxVariantProps &
Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'id'> & {
id: string
}

function Checkbox(props: CheckboxProps) {
const { size, ...nativeProps } = props
const styles = checkbox({ size })
return (
<div className={styles.root}>
<input {...nativeProps} className={styles.input} type="checkbox" />
<Show when={props.checked}>
<span className={styles.icon}>
<Checkmark />
</span>
</Show>
</div>
)
}

interface OverviewState {
legal: boolean
terms: boolean
}

export function OverviewPreview() {
const [checked, setChecked] = useState<OverviewState>({
legal: false,
terms: false,
})

const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const key = event.currentTarget.name as keyof OverviewState
setChecked((prev) => ({
...prev,
[key]: !prev[key],
}))
}, [])

return (
<div
className={vstack({
alignItems: 'start',
gap: '4',
})}
>
<Field required>
<Label
className={hstack({
justify: 'flex-start',
})}
htmlFor="terms"
size="sm"
>
<Checkbox
checked={checked.terms}
id="terms"
name="terms"
onChange={handleChange}
size="md"
/>
I agree to the terms and conditions
</Label>
</Field>
<Field>
<Label
className={hstack({
justify: 'flex-start',
})}
htmlFor="legal"
size="sm"
>
<Checkbox
checked={checked.legal}
id="legal"
name="legal"
onChange={handleChange}
size="md"
/>
I would like to receive marketing emails
</Label>
</Field>
</div>
)
}
116 changes: 116 additions & 0 deletions docs/app/react/checkbox/components/radio-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Field, Label, Radio } from '@cerberus-design/react'
import { css } from '@cerberus/styled-system/css'
import { hstack } from '@cerberus/styled-system/patterns'

export function DefaultRadioPreview() {
return (
<Field>
<Radio id="valid" name="states" value="valid" defaultChecked>
<Label htmlFor="valid">Default (valid)</Label>
</Radio>
</Field>
)
}

export function InvalidRadioPreview() {
return (
<Field invalid>
<Radio id="invalid" name="states" value="invalid" defaultChecked>
<Label htmlFor="invalid">Invalid</Label>
</Radio>
</Field>
)
}

export function DisabledRadioPreview() {
return (
<Field disabled>
<Radio id="disabled" name="states" value="disabled" defaultChecked>
<Label htmlFor="disabled">Disabled</Label>
</Radio>
</Field>
)
}

export function CustomRadioPreview() {
return (
<Field>
<Radio
className={css({
borderColor: 'yellow',
_groupHover: {
bgColor: 'black',
},
_checked: {
bg: 'yellow',
},
})}
id="custom"
name="states"
value="custom"
defaultChecked
>
<Label htmlFor="custom">Wu-Tang</Label>
</Radio>
</Field>
)
}

export function OverviewRadioGroup() {
return (
<fieldset
className={hstack({
gap: '4',
p: '4',
rounded: 'xl',
})}
name="pet"
role="radiogroup"
>
<Field>
<Radio id="dog" name="pet" value="dog" defaultChecked>
<Label htmlFor="dog">🐶 Dog</Label>
</Radio>
</Field>

<Field>
<Radio id="cat" name="pet" value="cat">
<Label htmlFor="cat">😸 Cat</Label>
</Radio>
</Field>

<Field>
<Radio id="both" name="pet" value="both">
<Label htmlFor="both">🐶😸 Both</Label>
</Radio>
</Field>
</fieldset>
)
}

export function OverviewRadioSizes() {
return (
<fieldset
className={hstack({
gap: '4',
p: '4',
rounded: 'xl',
})}
name="sizes"
role="radiogroup"
>
<Field>
<Radio id="sm" name="sizes" value="sm" size="sm">
<Label htmlFor="sm" size="sm">
Small
</Label>
</Radio>
</Field>
<Field>
<Radio id="md" name="sizes" value="md">
<Label htmlFor="md">Medium (default)</Label>
</Radio>
</Field>
</fieldset>
)
}
84 changes: 84 additions & 0 deletions docs/app/react/checkbox/dev.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
npm: '@cerberus-design/react'
source: 'components/Checkbox.tsx'
recipe: 'slots/checkbox.ts'
---

import { NoteAdmonition } from '@/app/components/Admonition'
import CodePreview from '@/app/components/CodePreview'
import {
OverviewPreview
} from '@/app/react/checkbox/components/checkbox-preview'

```ts
import { Field, Label, Checkbox } from '@cerberus-design/react'
```

## Usage

<CodePreview preview={<OverviewPreview />} />

## Customizing

<CodePreview preview={<OverviewPreview />}>
```tsx title="custom-radio.tsx"
import { Field, Label, Radio } from '@cerberus-design/react'

function CustomRadioPreview() {
return (
<Field>
<Radio
className={css({
borderColor: 'yellow',
_groupHover: {
bgColor: 'black',
},
_checked: {
bg: 'yellow',
},
})}
id="custom"
name="states"
value="custom"
defaultChecked
>
<Label htmlFor="custom">Wu-Tang</Label>
</Radio>
</Field>
)
}
```
</CodePreview>

## API

<NoteAdmonition description="The Radio component must be used within a Field provider." />

```ts showLineNumbers=false
export interface FieldProps {
disabled?: boolean
invalid?: boolean
required?: boolean
readOnly?: boolean
}

define function Field(props: PropsWithChildren<FieldProps>): ReactNode
```

```ts showLineNumbers=false
export interface RadioProps extends InputHTMLAttributes<HTMLInputElement> {
id: string
size?: 'sm' | 'md' | 'lg'
}
define function Radio(props: RadioProps): ReactNode
```

### Props

The Radio component accepts the following props:

| Name | Default | Description |
| -------- | ------- | ------------------------------------------------------------- |
| id | | An identifier that is shared with the Label `htmlFor` attribute. |
| size | `md` | The size of the input field. |
35 changes: 35 additions & 0 deletions docs/app/react/checkbox/guidelines.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
---

import CodePreview from '@/app/components/CodePreview'
import OverviewList from '@/app/components/OverviewList'
import {
WhenToUseAdmonition,
WhenNotToUseAdmonition,
} from '@/app/components/Admonition'
import {
OverviewPreview
} from '@/app/react/checkbox/components/checkbox-preview'

## Usage

Checkboxes visually group similar items effectively and take up less space than toggles.

<WhenToUseAdmonition description="Use a checkbox when a user needs to opt-into multiple options in a list." />

<WhenNotToUseAdmonition description="When you need the user to select a single option use a Toggle." />

## Standard Checkbox

<CodePreview preview={<OverviewPreview />} />


## Alternate selection controls

Checkboxes, radio buttons, and toggles are the three main types of selection controls. They all help people make choices, like selecting options or switching settings on or off.

Use checkboxes to select multiple related options in a list.

Use radio buttons to select a single option in a list.

Use toggles to select standalone or more verbose options in a list, like settings.
Loading

0 comments on commit 29d0773

Please sign in to comment.