Skip to content

Commit

Permalink
[RadioGroup] Create new RadioGroup component (#487)
Browse files Browse the repository at this point in the history
  • Loading branch information
atomiks authored Sep 4, 2024
1 parent 9c04f2c commit 0c87b6e
Show file tree
Hide file tree
Showing 50 changed files with 2,760 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from 'react';
import * as RadioGroup from '@base_ui/react/RadioGroup';
import * as Radio from '@base_ui/react/Radio';
import { styled } from '@mui/system';

export default function UnstyledRadioGroupIntroduction() {
return (
<RadioGroup.Root name="root" style={{ display: 'flex', gap: 8 }}>
<RadioItem value="light">
<Indicator />
Light
</RadioItem>
<RadioItem value="medium">
<Indicator />
Medium
</RadioItem>
<RadioItem value="heavy">
<Indicator />
Heavy
</RadioItem>
</RadioGroup.Root>
);
}

const grey = {
100: '#E5EAF2',
200: '#D8E0E9',
300: '#CBD4E2',
};

const blue = {
400: '#3399FF',
600: '#0072E6',
800: '#004C99',
};

const RadioItem = styled(Radio.Root)`
display: flex;
align-items: center;
padding: 8px 16px;
border-radius: 4px;
border: none;
background-color: ${grey[100]};
color: black;
outline: none;
font-size: 16px;
cursor: default;
&:hover {
background-color: ${grey[100]};
}
&:focus-visible {
outline: 2px solid ${blue[400]};
outline-offset: 2px;
}
&[data-radio='checked'] {
background-color: ${blue[600]};
color: white;
}
`;

const Indicator = styled(Radio.Indicator)`
border-radius: 50%;
width: 8px;
height: 8px;
margin-right: 8px;
outline: 1px solid black;
&[data-radio='checked'] {
background-color: white;
border: none;
outline: none;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from 'react';
import * as RadioGroup from '@base_ui/react/RadioGroup';
import * as Radio from '@base_ui/react/Radio';
import { styled } from '@mui/system';

export default function UnstyledRadioGroupIntroduction() {
return (
<RadioGroup.Root name="root" style={{ display: 'flex', gap: 8 }}>
<RadioItem value="light">
<Indicator />
Light
</RadioItem>
<RadioItem value="medium">
<Indicator />
Medium
</RadioItem>
<RadioItem value="heavy">
<Indicator />
Heavy
</RadioItem>
</RadioGroup.Root>
);
}

const grey = {
100: '#E5EAF2',
200: '#D8E0E9',
300: '#CBD4E2',
};

const blue = {
400: '#3399FF',
600: '#0072E6',
800: '#004C99',
};

const RadioItem = styled(Radio.Root)`
display: flex;
align-items: center;
padding: 8px 16px;
border-radius: 4px;
border: none;
background-color: ${grey[100]};
color: black;
outline: none;
font-size: 16px;
cursor: default;
&:hover {
background-color: ${grey[100]};
}
&:focus-visible {
outline: 2px solid ${blue[400]};
outline-offset: 2px;
}
&[data-radio='checked'] {
background-color: ${blue[600]};
color: white;
}
`;

const Indicator = styled(Radio.Indicator)`
border-radius: 50%;
width: 8px;
height: 8px;
margin-right: 8px;
outline: 1px solid black;
&[data-radio='checked'] {
background-color: white;
border: none;
outline: none;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<RadioGroup.Root name="root" style={{ display: 'flex', gap: 8 }}>
<RadioItem value="light">
<Indicator />
Light
</RadioItem>
<RadioItem value="medium">
<Indicator />
Medium
</RadioItem>
<RadioItem value="heavy">
<Indicator />
Heavy
</RadioItem>
</RadioGroup.Root>
135 changes: 135 additions & 0 deletions docs/data/base/components/radio-group/radio-group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
productId: base-ui
title: React Radio Group component
components: RadioGroupRoot, RadioRoot, RadioIndicator
githubLabel: 'component: radio'
waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/radio/
---

# Radio Group

<p class="description">Radio Groups contain a set of checkable buttons where only one of the buttons can be checked at a time.</p>

{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}

{{"component": "modules/components/ComponentPageTabs.js"}}

## Introduction

{{"demo": "UnstyledRadioGroupIntroduction", "defaultCodeOpen": false, "bg": "gradient"}}

## Installation

Base UI components are all available as a single package.

<codeblock storageKey="package-manager">

```bash npm
npm install @base_ui/react
```

```bash yarn
yarn add @base_ui/react
```

```bash pnpm
pnpm add @base_ui/react
```

</codeblock>

Once you have the package installed, import the components.

```ts
import * as RadioGroup from '@base_ui/react/RadioGroup';
import * as Radio from '@base_ui/react/Radio';
```

## Anatomy

Radio Group is composed of a `Root` and `Radio` components:

- `<RadioGroup.Root />` is a top-level element that wraps the other components.
- `<Radio.Root />` renders an individual `<button>` radio item.
- `<Radio.Indicator />` renders a `<span>` for providing a visual indicator. You can style this itself and/or place an icon inside.

```jsx
<RadioGroup.Root>
<Radio.Root>
<Radio.Indicator />
</Radio.Root>
</RadioGroup.Root>
```

## Identifying items

The `value` prop is required on `Radio.Root` to identify it in the Radio Group:

```jsx
<RadioGroup.Root>
<Radio.Root value="a">
<Radio.Indicator />
</Radio.Root>
<Radio.Root value="b">
<Radio.Indicator />
</Radio.Root>
</RadioGroup.Root>
```

## Default value

The `defaultValue` prop determines the initial value of the component when uncontrolled, linked to the `value` prop on an individual Radio item:

```jsx
<RadioGroup.Root defaultValue="a">
<Radio.Root value="a" />
<Radio.Root value="b" />
</RadioGroup.Root>
```

## Controlled

The `value` and `onValueChange` props contain the `value` string of the currently selected Radio item in the Radio Group:

```jsx
const [value, setValue] = React.useState('a');

return (
<RadioGroup.Root value={value} onValueChange={setValue}>
<Radio.Root value="a" />
<Radio.Root value="b" />
</RadioGroup.Root>
);
```

## Styling

The `Radio` components have a `[data-radio]` attribute with values `"checked"` or `"unchecked"` to style based on the checked state:

```jsx
<Radio.Root className="Radio">
<Radio.Indicator className="RadioIndicator" />
</Radio.Root>
```

```css
.Radio {
border: 1px solid black;
}

.RadioIndicator {
width: 20px;
height: 20px;
border-radius: 50%;
border: 1px solid black;
}

.Radio[data-radio='checked'] {
background: black;
color: white;
}

.RadioIndicator[data-radio='checked'] {
background: white;
}
```
2 changes: 1 addition & 1 deletion docs/data/base/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const pages: readonly MuiPage[] = [
{ pathname: '/base-ui/react-checkbox', title: 'Checkbox' },
{ pathname: '/base-ui/react-checkbox-group', title: 'Checkbox Group' },
{ pathname: '/base-ui/react-number-field', title: 'Number Field' },
// { pathname: '/base-ui/react-radio-group', title: 'Radio Group', planned: true },
{ pathname: '/base-ui/react-radio-group', title: 'Radio Group' },
// { pathname: '/base-ui/react-select', title: 'Select' },
{ pathname: '/base-ui/react-slider', title: 'Slider' },
{ pathname: '/base-ui/react-switch', title: 'Switch' },
Expand Down
12 changes: 12 additions & 0 deletions docs/data/base/pagesApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,18 @@ module.exports = [
pathname: '/base-ui/react-progress/components-api/#progress-track',
title: 'ProgressTrack',
},
{
pathname: '/base-ui/react-radio-group/components-api/#radio-group-root',
title: 'RadioGroupRoot',
},
{
pathname: '/base-ui/react-radio-group/components-api/#radio-indicator',
title: 'RadioIndicator',
},
{
pathname: '/base-ui/react-radio-group/components-api/#radio-root',
title: 'RadioRoot',
},
{ pathname: '/base-ui/react-select/components-api/#select', title: 'Select' },
{
pathname: '/base-ui/react-slider/components-api/#slider-control',
Expand Down
26 changes: 26 additions & 0 deletions docs/pages/base-ui/api/radio-group-root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"props": {
"className": { "type": { "name": "union", "description": "func<br>&#124;&nbsp;string" } },
"defaultValue": { "type": { "name": "any" } },
"disabled": { "type": { "name": "bool" }, "default": "false" },
"name": { "type": { "name": "string" } },
"onValueChange": { "type": { "name": "func" } },
"readOnly": { "type": { "name": "bool" }, "default": "false" },
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } },
"required": { "type": { "name": "bool" }, "default": "false" },
"value": { "type": { "name": "any" } }
},
"name": "RadioGroupRoot",
"imports": [
"import * as RadioGroup from '@base_ui/react/RadioGroup';\nconst RadioGroupRoot = RadioGroup.Root;"
],
"classes": [],
"spread": true,
"themeDefaultProps": true,
"muiName": "RadioGroupRoot",
"forwardsRefTo": "HTMLDivElement",
"filename": "/packages/mui-base/src/RadioGroup/Root/RadioGroupRoot.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/base-ui/react-radio-group/\">Radio Group</a></li></ul>",
"cssComponent": false
}
20 changes: 20 additions & 0 deletions docs/pages/base-ui/api/radio-indicator.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" } },
"keepMounted": { "type": { "name": "bool" }, "default": "true" },
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } }
},
"name": "RadioIndicator",
"imports": [
"import * as Radio from '@base_ui/react/Radio';\nconst RadioIndicator = Radio.Indicator;"
],
"classes": [],
"spread": true,
"themeDefaultProps": true,
"muiName": "RadioIndicator",
"forwardsRefTo": "HTMLSpanElement",
"filename": "/packages/mui-base/src/Radio/Indicator/RadioIndicator.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/base-ui/react-radio-group/\">Radio Group</a></li></ul>",
"cssComponent": false
}
Loading

0 comments on commit 0c87b6e

Please sign in to comment.