Skip to content

Commit

Permalink
Improve checkbox examples (#2456)
Browse files Browse the repository at this point in the history
This PR improves the current checkbox docs and fixes two related issues:

- The `Checkbox` component now accepts `string[]` as the `value` prop.
This is to conform with the native input prop type. If a string array is
passed, it will be stringified, just like in the native input element.

- Fixed the `clickOnEnter` prop on `Checkbox` not working when rendering
the component as a native input element.
  • Loading branch information
diegohaz committed May 23, 2023
1 parent 16103fc commit 0f58e63
Show file tree
Hide file tree
Showing 25 changed files with 394 additions and 120 deletions.
6 changes: 6 additions & 0 deletions .changeset/checkbox-examples-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ariakit/react-core": patch
"@ariakit/react": patch
---

The `Checkbox` component now accepts `string[]` as the `value` prop. This is to conform with the native input prop type. If a string array is passed, it will be stringified, just like in the native input element. ([#2456](https://github.com/ariakit/ariakit/pull/2456))
6 changes: 6 additions & 0 deletions .changeset/checkbox-examples-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ariakit/react-core": patch
"@ariakit/react": patch
---

Fixed the `clickOnEnter` prop on `Checkbox` not working when rendering the component as a native input element. ([#2456](https://github.com/ariakit/ariakit/pull/2456))
25 changes: 20 additions & 5 deletions components/checkbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

<a href="../examples/checkbox/index.tsx" data-playground>Example</a>

## Installation
## Examples

```sh
npm i @ariakit/react
```
<div data-cards="examples">

Learn more on the [Getting started](/guide/getting-started) guide.
- [](/examples/checkbox-as-button)
- [](/examples/checkbox-custom)
- [](/examples/checkbox-group)

</div>

## API

Expand All @@ -23,3 +25,16 @@ Learn more on the [Getting started](/guide/getting-started) guide.
&lt;<a href="/apis/checkbox-check">CheckboxCheck</a> /&gt;
&lt;/Checkbox&gt;
</pre>

## Related components

<div data-cards="components">

- [](/components/button)
- [](/components/form)
- [](/components/menu)
- [](/components/radio)
- [](/components/select)
- [](/components/command)

</div>
22 changes: 22 additions & 0 deletions components/group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Group

<p data-description>
Group related elements in a generic container that may have a label. This abstract component is based on the <a href="https://w3c.github.io/aria/#group">WAI-ARIA Group Role</a>.
</p>

## API

<pre data-api>
&lt;<a href="/apis/group">Group</a>&gt;
&lt;<a href="/apis/group-label">GroupLabel</a> /&gt;
&lt;/Group&gt;
</pre>

## Related components

<div data-cards="components">

- [](/components/form)
- [](/components/composite)

</div>
43 changes: 42 additions & 1 deletion examples/checkbox-as-button/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,58 @@
Rendering a custom <a href="/components/checkbox">Checkbox</a> as a <code>button</code> element in React, while keeping it accessible to screen reader and keyboard users.
</p>

<aside data-type="note" title="Need to render a native checkbox element?">

This example demonstrates the rendering of a button element. However, if you intend to render the `Checkbox` as a form control or require the preservation of native input element properties for any specific purpose, please refer to the [Custom Checkbox](/examples/checkbox-custom) example.

</aside>

<a href="./index.tsx" data-playground>Example</a>

## Components

<div data-cards="components">

- [](/components/checkbox)
- [](/components/button)

</div>

## Activating on <kbd>Enter</kbd>

By default, native checkbox elements are activated on <kbd>Space</kbd>, but not on <kbd>Enter</kbd>. The Ariakit `Checkbox` component allows you to control this behavior using the [`clickOnEnter`](/apis/checkbox#clickonenter) and [`clickOnSpace`](/apis/checkbox#clickonspace) props. However, when rendering the `Checkbox` as any non-native input element, the `clickOnEnter` prop will be automatically set to `true`.

## Reading the state

In this example, we're reading the [`value`](/apis/checkbox-store#value) state from the checkbox store to render the button's text. This is done by using the selector form of the [`useState`](/apis/checkbox-store#usestate) hook:

```jsx
const label = checkbox.useState((state) =>
state.value ? "Checked" : "Unchecked"
);
```

Learn more about reading the state on the [Component stores](/guide/component-stores#reading-the-state) guide.

## Styling

When rendering the `Checkbox` component as a non-native `input` element, the `:checked` pseudo-class is not supported. To style the checked state, use the `aria-checked` attribute selector:

```css
.button[aria-checked="true"] {
background-color: hsl(204 100% 40%);
color: hsl(204, 20%, 100%);
color: hsl(204 20% 100%);
}
```

Learn more on the [Styling](/guide/styling) guide.

## Related examples

<div data-cards="examples">

- [](/examples/checkbox-custom)
- [](/examples/checkbox-group)
- [](/examples/menu-item-checkbox)

</div>
20 changes: 20 additions & 0 deletions examples/checkbox-as-button/site-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default function Icon() {
return (
<svg viewBox="0 0 128 128" width={128} height={128}>
<foreignObject width={128} height={128}>
<div className="flex h-full w-full items-center justify-center p-4">
<div className="w-20 gap-2 rounded bg-blue-600 p-2">
<svg
fill="none"
viewBox="0 0 24 24"
strokeWidth={4}
className="h-6 w-6 stroke-white"
>
<path d="M4.5 12.75l6 6 9-13.5" />
</svg>
</div>
</div>
</foreignObject>
</svg>
);
}
3 changes: 0 additions & 3 deletions examples/checkbox-as-button/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@
text-blue-900
bg-blue-200/40
hover:bg-blue-200/60

dark:text-blue-100
dark:bg-blue-600/25
dark:hover:bg-blue-600/40

aria-checked:text-white
aria-checked:bg-blue-600
aria-checked:hover:bg-blue-800

dark:aria-checked:text-white
dark:aria-checked:bg-blue-600
dark:aria-checked:hover:bg-blue-800
Expand Down
7 changes: 0 additions & 7 deletions examples/checkbox-controlled/readme.md

This file was deleted.

48 changes: 48 additions & 0 deletions examples/checkbox-custom/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { forwardRef, useState } from "react";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
import * as Ariakit from "@ariakit/react";

interface CheckboxProps extends ComponentPropsWithoutRef<"input"> {
children?: ReactNode;
}

export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
function Checkbox({ children, ...props }, ref) {
const [checked, setChecked] = useState(props.defaultChecked ?? false);
const [focusVisible, setFocusVisible] = useState(false);
return (
<label
className="checkbox"
data-checked={checked}
data-focus-visible={focusVisible || undefined}
>
<Ariakit.VisuallyHidden>
<Ariakit.Checkbox
{...props}
ref={ref}
clickOnEnter
onFocusVisible={() => setFocusVisible(true)}
onBlur={() => setFocusVisible(false)}
onChange={(event) => {
setChecked(event.target.checked);
props.onChange?.(event);
}}
/>
</Ariakit.VisuallyHidden>
<div className="check" data-checked={checked}>
<svg
fill="none"
stroke="currentColor"
strokeWidth="2"
viewBox="0 0 16 16"
height="1em"
width="1em"
>
<polyline points="4,8 7,12 12,4" />
</svg>
</div>
{children}
</label>
);
}
);
22 changes: 2 additions & 20 deletions examples/checkbox-custom/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,6 @@
import { useState } from "react";
import * as Ariakit from "@ariakit/react";
import { Checkbox } from "./checkbox.jsx";
import "./style.css";

export default function Example() {
const checkbox = Ariakit.useCheckboxStore({ defaultValue: false });
const [focusVisible, setFocusVisible] = useState(false);
const checked = checkbox.useState("value");
return (
<label className="label">
<Ariakit.VisuallyHidden>
<Ariakit.Checkbox
store={checkbox}
onFocusVisible={() => setFocusVisible(true)}
onBlur={() => setFocusVisible(false)}
/>
</Ariakit.VisuallyHidden>
<div className="checkbox" data-focus-visible={focusVisible ? "" : null}>
<Ariakit.CheckboxCheck checked={checked} />
</div>
I have read and agree to the terms and conditions
</label>
);
return <Checkbox defaultChecked>Ariakit</Checkbox>;
}
19 changes: 19 additions & 0 deletions examples/checkbox-custom/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,22 @@
</p>

<a href="./index.tsx" data-playground>Example</a>

## Components

<div data-cards="components">

- [](/components/checkbox)
- [](/components/visually-hidden)

</div>

## Related examples

<div data-cards="examples">

- [](/examples/checkbox-as-button)
- [](/examples/checkbox-group)
- [](/examples/menu-item-checkbox)

</div>
23 changes: 23 additions & 0 deletions examples/checkbox-custom/site-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export default function Icon() {
return (
<svg viewBox="0 0 128 128" width={128} height={128}>
<foreignObject width={128} height={128}>
<div className="flex h-full items-center justify-center p-4">
<div className="flex flex-col gap-2 rounded-lg border-[3px] border-blue-600 bg-white p-2 dark:bg-gray-700">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-600 p-2">
<svg
fill="none"
viewBox="0 0 24 24"
strokeWidth={4}
className="h-full w-full stroke-white"
>
<path d="M4.5 12.75l6 6 9-13.5" />
</svg>
</div>
<div className="h-2 w-14 bg-black/50 dark:bg-white/50" />
</div>
</div>
</foreignObject>
</svg>
);
}
49 changes: 36 additions & 13 deletions examples/checkbox-custom/style.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
@import url("../checkbox/style.css");

.checkbox {
@apply
w-5
h-5
rounded
flex
justify-center
items-center
border
text-blue-900
bg-blue-200/40
border-blue-600
dark:text-blue-100
dark:bg-blue-600/25
dark:border-blue-200/40
gap-2
p-4
pr-6
rounded-lg
select-none
border-2
border-black/30
dark:border-white/30
data-[checked=true]:border-blue-600
data-[checked=true]:dark:border-blue-600
bg-white
dark:bg-gray-700
shadow
dark:shadow-dark
data-[focus-visible]:outline
data-[focus-visible]:outline-4
data-[focus-visible]:outline-blue-600/25
;
}

.check {
@apply
block
rounded-full
p-0.5
text-lg
bg-gray-150
dark:bg-gray-850
[border:inherit]
[stroke-dasharray:15]
[stroke-dashoffset:15]
data-[checked=true]:bg-blue-600
data-[checked=true]:dark:bg-blue-600
data-[checked=true]:text-white
data-[checked=true]:[stroke-dashoffset:0]
transition-[stroke-dashoffset]
;
}
17 changes: 13 additions & 4 deletions examples/checkbox-custom/test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import { click, getByRole, press } from "@ariakit/test";

test("check/uncheck on click", async () => {
expect(getByRole("checkbox")).not.toBeChecked();
await click(getByRole("checkbox"));
expect(getByRole("checkbox")).toBeChecked();
await click(getByRole("checkbox"));
expect(getByRole("checkbox")).not.toBeChecked();
await click(getByRole("checkbox"));
expect(getByRole("checkbox")).toBeChecked();
});

test("check/uncheck on space", async () => {
expect(getByRole("checkbox")).not.toBeChecked();
expect(getByRole("checkbox")).toBeChecked();
await press.Tab();
await press.Space();
expect(getByRole("checkbox")).toBeChecked();
expect(getByRole("checkbox")).not.toBeChecked();
await press.Space();
expect(getByRole("checkbox")).toBeChecked();
});

test("check/uncheck on enter", async () => {
expect(getByRole("checkbox")).toBeChecked();
await press.Tab();
await press.Enter();
expect(getByRole("checkbox")).not.toBeChecked();
await press.Enter();
expect(getByRole("checkbox")).toBeChecked();
});

1 comment on commit 0f58e63

@vercel
Copy link

@vercel vercel bot commented on 0f58e63 May 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ariakit – ./

ariakit.org
ariakit-ariakit.vercel.app
ariakit-git-main-ariakit.vercel.app
www.ariakit.org

Please sign in to comment.