Skip to content

Commit

Permalink
Add ToggleButtonGroup
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed Nov 1, 2024
1 parent deeed5e commit 7c449d8
Show file tree
Hide file tree
Showing 22 changed files with 1,504 additions and 55 deletions.
29 changes: 29 additions & 0 deletions docs/data/api/toggle-button-group-item.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"props": {
"value": { "type": { "name": "any" }, "required": true },
"aria-label": { "type": { "name": "string" } },
"aria-labelledby": { "type": { "name": "string" } },
"className": { "type": { "name": "union", "description": "func<br>&#124;&nbsp;string" } },
"onPressedChange": {
"type": { "name": "func" },
"signature": {
"type": "function(pressed: boolean, event: Event) => void",
"describedArgs": ["pressed", "event"]
}
},
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } }
},
"name": "ToggleButtonGroupItem",
"imports": [
"import { ToggleButtonGroup } from '@base_ui/react/ToggleButtonGroup';\nconst ToggleButtonGroupItem = ToggleButtonGroup.Item;"
],
"classes": [],
"spread": true,
"themeDefaultProps": true,
"muiName": "ToggleButtonGroupItem",
"forwardsRefTo": "HTMLButtonElement",
"filename": "/packages/mui-base/src/ToggleButtonGroup/Item/ToggleButtonGroupItem.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/react-toggle-button-group/\">ToggleButtonGroup</a></li></ul>",
"cssComponent": false
}
20 changes: 20 additions & 0 deletions docs/data/api/toggle-button-group-root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"props": {
"className": { "type": { "name": "union", "description": "func<br>&#124;&nbsp;string" } },
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } },
"toggleMultiple": { "type": { "name": "bool" }, "default": "false" }
},
"name": "ToggleButtonGroupRoot",
"imports": [
"import { ToggleButtonGroup } from '@base_ui/react/ToggleButtonGroup';\nconst ToggleButtonGroupRoot = ToggleButtonGroup.Root;"
],
"classes": [],
"spread": true,
"themeDefaultProps": true,
"muiName": "ToggleButtonGroupRoot",
"forwardsRefTo": "HTMLDivElement",
"filename": "/packages/mui-base/src/ToggleButtonGroup/Root/ToggleButtonGroupRoot.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/react-toggle-button-group/\">ToggleButtonGroup</a></li></ul>",
"cssComponent": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use client';
import * as React from 'react';
import { ToggleButtonGroup } from '@base_ui/react/ToggleButtonGroup';
import classes from './styles.module.css';

export default function ToggleButtonGroupIntroduction() {
const [value, setValue] = React.useState(['align-center']);
return (
<ToggleButtonGroup.Root
className={classes.root}
value={value}
onValueChange={(newValue) => {
if (newValue.length > 0) {
setValue(newValue);
}
}}
aria-label="Text alignment"
>
<ToggleButtonGroup.Item
className={classes.button}
value="align-left"
aria-label="Align left"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className={classes.icon}
>
<line x1="17" y1="10" x2="3" y2="10" />
<line x1="21" y1="6" x2="3" y2="6" />
<line x1="21" y1="14" x2="3" y2="14" />
<line x1="17" y1="18" x2="3" y2="18" />
</svg>
</ToggleButtonGroup.Item>

<ToggleButtonGroup.Item
className={classes.button}
value="align-center"
aria-label="Align center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className={classes.icon}
>
<line x1="18" y1="10" x2="6" y2="10" />
<line x1="21" y1="6" x2="3" y2="6" />
<line x1="21" y1="14" x2="3" y2="14" />
<line x1="18" y1="18" x2="6" y2="18" />
</svg>
</ToggleButtonGroup.Item>

<ToggleButtonGroup.Item
className={classes.button}
value="align-right"
aria-label="Align right"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className={classes.icon}
>
<line x1="21" y1="10" x2="7" y2="10" />
<line x1="21" y1="6" x2="3" y2="6" />
<line x1="21" y1="14" x2="3" y2="14" />
<line x1="21" y1="18" x2="7" y2="18" />
</svg>
</ToggleButtonGroup.Item>
</ToggleButtonGroup.Root>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use client';
import * as React from 'react';
import { ToggleButtonGroup } from '@base_ui/react/ToggleButtonGroup';
import classes from './styles.module.css';

export default function ToggleButtonGroupIntroduction() {
const [value, setValue] = React.useState(['align-center']);
return (
<ToggleButtonGroup.Root
className={classes.root}
value={value}
onValueChange={(newValue) => {
if (newValue.length > 0) {
setValue(newValue as string[]);
}
}}
aria-label="Text alignment"
>
<ToggleButtonGroup.Item
className={classes.button}
value="align-left"
aria-label="Align left"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className={classes.icon}
>
<line x1="17" y1="10" x2="3" y2="10" />
<line x1="21" y1="6" x2="3" y2="6" />
<line x1="21" y1="14" x2="3" y2="14" />
<line x1="17" y1="18" x2="3" y2="18" />
</svg>
</ToggleButtonGroup.Item>

<ToggleButtonGroup.Item
className={classes.button}
value="align-center"
aria-label="Align center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className={classes.icon}
>
<line x1="18" y1="10" x2="6" y2="10" />
<line x1="21" y1="6" x2="3" y2="6" />
<line x1="21" y1="14" x2="3" y2="14" />
<line x1="18" y1="18" x2="6" y2="18" />
</svg>
</ToggleButtonGroup.Item>

<ToggleButtonGroup.Item
className={classes.button}
value="align-right"
aria-label="Align right"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className={classes.icon}
>
<line x1="21" y1="10" x2="7" y2="10" />
<line x1="21" y1="6" x2="3" y2="6" />
<line x1="21" y1="14" x2="3" y2="14" />
<line x1="21" y1="18" x2="7" y2="18" />
</svg>
</ToggleButtonGroup.Item>
</ToggleButtonGroup.Root>
);
}
56 changes: 56 additions & 0 deletions docs/data/components/toggle-button-group/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
.root {
display: flex;
}

.button {
--size: 2.5rem;
--corner: 0.4rem;
--border-color: var(--gray-outline-2);

display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
height: var(--size);
width: var(--size);
border: 1px solid var(--border-color);
background-color: var(--gray-container-2);
color: var(--gray-400);
}

.button:first-child {
border-radius: var(--corner) 0 0 var(--corner);
border-right-color: transparent;
}

.button:last-child {
border-radius: 0 var(--corner) var(--corner) 0;
border-left-color: transparent;
}

.button:hover {
background-color: var(--gray-surface-1);
outline: 1px solid var(--gray-500);
outline-offset: -1px;
color: var(--gray-text-2);
cursor: pointer;
z-index: 1;
}

.button:focus-visible {
outline: 2px solid var(--gray-900);
z-index: 1;
}

.icon {
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}

.button[data-pressed] {
background-color: #fefefe;
color: var(--gray-text-2);
}
110 changes: 110 additions & 0 deletions docs/data/components/toggle-button-group/toggle-button-group.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
productId: base-ui
title: React ToggleButtonGroup component
description: ToggleButtonGroup provides a set of two-state buttons that can either be off (not pressed) or on (pressed).
components: ToggleButtonGroupRoot, ToggleButtonGroupItem
githubLabel: 'component: toggle button'
waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/radio-group/
packageName: '@base_ui/react'
---

# ToggleButtonGroup

<Description />

<ComponentLinkHeader design={false} />

<Demo demo="ToggleButtonGroupIntroduction" defaultCodeOpen="false" bg="gradient" />

## Installation

<InstallationInstructions componentName="ToggleButtonGroup" />

## Anatomy

ToggleButtonGroups are composed with two components:

- `<ToggleButtonGroup.Root />` is the outer component that wraps a set of [`toggle buttons`](/components/react-toggle-button)
- `<ToggleButtonGroup.Item />`s renders the `<button>`

```tsx
<ToggleButtonGroup.Root>
<ToggleButtonGroup.Item value="bold">Bold</ToggleButtonGroup.Item>
<ToggleButtonGroup.Item value="italics">Italics</ToggleButtonGroup.Item>
<ToggleButtonGroup.Item value="underline">Underline</ToggleButtonGroup.Item>
</ToggleButtonGroup.Root>
```

## Value

Each `Item` in the group must be given a unique value, e.g. a string like `'bold'`, `'italics'` in the examples above.

### Uncontrolled

When uncontrolled, use the `defaultValue` prop to set the initial state of the group:

```tsx
// only the "Italics" button is initially pressed
<ToggleButtonGroup.Root defaultValue={['italics']}>
<ToggleButtonGroup.Item value="bold">Bold</ToggleButtonGroup.Item>
<ToggleButtonGroup.Item value="italics">Italics</ToggleButtonGroup.Item>
<ToggleButtonGroup.Item value="underline">Underline</ToggleButtonGroup.Item>
</ToggleButtonGroup.Root>
```

### Controlled

When controlled, pass the `value` and `onValueChange` props to `ToggleButtonGroup.Root`:

```tsx
const [value, setValue] = React.useState(['italics']);

return (
<ToggleButtonGroup.Root value={value} onValueChange={setValue}>
<ToggleButtonGroup.Item value="bold" />
<ToggleButtonGroup.Item value="italics" />
<ToggleButtonGroup.Item value="underline" />
</ToggleButtonGroup.Root>
);
```

## Customization

### Allow multiple buttons to be pressed

Use the `toggleMultiple` prop to allow multiple buttons to be pressed at the same time:

```tsx
<ToggleButtonGroup.Root toggleMultiple>
<ToggleButtonGroup.Item value="align-left" />
<ToggleButtonGroup.Item value="align-center" />
<ToggleButtonGroup.Item value="align-right" />
</ToggleButtonGroup.Root>
```

### One button must be pressed

Use controlled mode to always keep one button in the pressed state:

```tsx
const [value, setValue] = React.useState(['align-left']);

const handleValueChange = (newValue) => {
if (newValue.length > 0) {
setValue(newValue);
}
};

return (
<ToggleButtonGroup.Root value={value} onValueChange={handleValueChange}>
<ToggleButtonGroup.Item value="align-left" />
<ToggleButtonGroup.Item value="align-center" />
<ToggleButtonGroup.Item value="align-right" />
</ToggleButtonGroup.Root>
);
```

## Accessibility

- The `Root` component, and every `Item` must be given must be given an accessible name with `aria-label` or `aria-labelledby`.
- The `Item`'s label or accessible name _must not_ change based on the pressed state to ensure a smooth screen-reader experience, otherwise it may duplicate the announcement of the pressed state based on `aria-pressed`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"componentDescription": "",
"propDescriptions": {
"aria-label": { "description": "The label for the toggle button." },
"aria-labelledby": {
"description": "An id or space-separated list of ids of elements that label the toggle button."
},
"className": {
"description": "Class names applied to the element or a function that returns them based on the component&#39;s state."
},
"onPressedChange": {
"description": "Callback fired when the pressed state is changed.",
"typeDescriptions": {
"pressed": "The new pressed state.",
"event": "The event source of the callback."
}
},
"render": { "description": "A function to customize rendering of the component." },
"value": {
"description": "A unique value that identifies the component when used inside a ToggleButtonGroup"
}
},
"classDescriptions": {}
}
Loading

0 comments on commit 7c449d8

Please sign in to comment.