-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add radio option to ButtonGroup (#20805)
* Add radio mode to ButtonGroup with aria attributes * Destructure child props * Add tab index for radio buttons * Add keyboard handlers * Remove TODO comment Current behavior is consistent with RadioControl * Add storybook example * Add documentation example * Add StoryShot snapshot * Mention ButtonGroup in RadioControl * Convert to using context instead of cloneElement * Merge refs so forwardRef is still usable with radio group * Refactor to move button props to button component * Refactor for readability * Add comment about default value * Consolidate ref and className * Fix useMemo return value * Update snapshots Reordered props and removed value prop * Partially revert snapshots * Update prop order to match snapshots * Update comments for clarity * Move changes to radio-group and radio * Remove radio button group storybook snapshot * Update ButtonGroup extra props to override role * Implement forwardRef for ButtonGroup * Update snapshot with forwardRef(ButtonGroup) * Update RadioControl to mention RadioGroup * Replace Radio/RadioGroup with Reakit version * Add storybook stories for Radio/RadioGroup * Update Radio/RadioGroup READMEs * Update docs manifest * Add __experimental prefix for Radio/RadioGroup * Pass through disabled state to children * Remove Radio ids from stories * Update snapshots
- Loading branch information
Showing
12 changed files
with
567 additions
and
7 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
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
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
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
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,11 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { createContext } from '@wordpress/element'; | ||
|
||
const RadioContext = createContext( { | ||
state: null, | ||
setState: () => {}, | ||
} ); | ||
|
||
export default RadioContext; |
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
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,87 @@ | ||
# RadioGroup | ||
|
||
Use a RadioGroup component when you want users to select one option from a small set of options. | ||
|
||
![RadioGroup component](https://wordpress.org/gutenberg/files/2018/12/s_96EC471FE9C9D91A996770229947AAB54A03351BDE98F444FD3C1BF0CED365EA_1541792995815_ButtonGroup.png) | ||
|
||
## Table of contents | ||
|
||
1. [Design guidelines](#design-guidelines) | ||
2. [Development guidelines](#development-guidelines) | ||
3. [Related components](#related-components) | ||
|
||
## Design guidelines | ||
|
||
### Usage | ||
|
||
#### Selected action | ||
|
||
Only one option in a radio group can be selected and active at a time. Selecting one option deselects any other. | ||
|
||
### Best practices | ||
|
||
Radio groups should: | ||
|
||
- **Be clearly and accurately labeled.** | ||
- **Clearly communicate that clicking or tapping will trigger an action.** | ||
- **Use established colors appropriately.** For example, only use red buttons for actions that are difficult or impossible to undo. | ||
- **Have consistent locations in the interface.** | ||
- **Have a default option already selected.** | ||
|
||
### States | ||
|
||
#### Active and available radio groups | ||
|
||
A radio group’s state makes it clear which option is active. Hover and focus states express the available selection options for buttons in a button group. | ||
|
||
#### Disabled radio groups | ||
|
||
Radio groups that cannot be selected can either be given a disabled state, or be hidden. | ||
|
||
## Development guidelines | ||
|
||
### Usage | ||
|
||
#### Controlled | ||
|
||
```jsx | ||
import { Radio, RadioGroup } from '@wordpress/components'; | ||
import { useState } from '@wordpress/element'; | ||
|
||
const MyControlledRadioRadioGroup = () => { | ||
const [ checked, setChecked ] = useState( '25' ); | ||
return ( | ||
<RadioGroup accessibilityLabel="Width" onChange={ setChecked } checked={ checked }> | ||
<Radio value="25">25%</Radio> | ||
<Radio value="50">50%</Radio> | ||
<Radio value="75">75%</Radio> | ||
<Radio value="100">100%</Radio> | ||
</RadioGroup> | ||
); | ||
}; | ||
``` | ||
|
||
#### Uncontrolled | ||
|
||
When using the RadioGroup component as an uncontrolled component, the default value can be set with the `defaultChecked` prop. | ||
|
||
```jsx | ||
import { Radio, RadioGroup } from '@wordpress/components'; | ||
import { useState } from '@wordpress/element'; | ||
|
||
const MyUncontrolledRadioRadioGroup = () => { | ||
return ( | ||
<RadioGroup accessibilityLabel="Width" defaultChecked="25"> | ||
<Radio value="25">25%</Radio> | ||
<Radio value="50">50%</Radio> | ||
<Radio value="75">75%</Radio> | ||
<Radio value="100">100%</Radio> | ||
</RadioGroup> | ||
); | ||
}; | ||
``` | ||
|
||
## Related components | ||
|
||
- For simple buttons that are related, use a `ButtonGroup` component. | ||
- For traditional radio options, use a `RadioControl` component. |
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,53 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { useRadioState, RadioGroup as ReakitRadioGroup } from 'reakit/Radio'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { forwardRef } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import ButtonGroup from '../button-group'; | ||
import RadioContext from '../radio-context'; | ||
|
||
function RadioGroup( | ||
{ | ||
accessibilityLabel, | ||
checked, | ||
defaultChecked, | ||
disabled, | ||
onChange, | ||
...props | ||
}, | ||
ref | ||
) { | ||
const radioState = useRadioState( { | ||
state: defaultChecked, | ||
baseId: props.id, | ||
} ); | ||
const radioContext = { | ||
...radioState, | ||
disabled, | ||
// controlled or uncontrolled | ||
state: checked || radioState.state, | ||
setState: onChange || radioState.setState, | ||
}; | ||
|
||
return ( | ||
<RadioContext.Provider value={ radioContext }> | ||
<ReakitRadioGroup | ||
ref={ ref } | ||
as={ ButtonGroup } | ||
aria-label={ accessibilityLabel } | ||
{ ...radioState } | ||
{ ...props } | ||
/> | ||
</RadioContext.Provider> | ||
); | ||
} | ||
|
||
export default forwardRef( RadioGroup ); |
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,71 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useState } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import Radio from '../../radio'; | ||
import RadioGroup from '../'; | ||
|
||
export default { title: 'Components/RadioGroup', component: RadioGroup }; | ||
|
||
export const _default = () => { | ||
/* eslint-disable no-restricted-syntax */ | ||
return ( | ||
<RadioGroup | ||
// id is required for server side rendering | ||
id="default-radiogroup" | ||
accessibilityLabel="options" | ||
defaultChecked="option2" | ||
> | ||
<Radio value="option1">Option 1</Radio> | ||
<Radio value="option2">Option 2</Radio> | ||
<Radio value="option3">Option 3</Radio> | ||
</RadioGroup> | ||
); | ||
/* eslint-enable no-restricted-syntax */ | ||
}; | ||
|
||
export const disabled = () => { | ||
/* eslint-disable no-restricted-syntax */ | ||
return ( | ||
<RadioGroup | ||
// id is required for server side rendering | ||
id="disabled-radiogroup" | ||
disabled | ||
accessibilityLabel="options" | ||
defaultChecked="option2" | ||
> | ||
<Radio value="option1">Option 1</Radio> | ||
<Radio value="option2">Option 2</Radio> | ||
<Radio value="option3">Option 3</Radio> | ||
</RadioGroup> | ||
); | ||
/* eslint-enable no-restricted-syntax */ | ||
}; | ||
|
||
const ControlledRadioGroupWithState = () => { | ||
const [ checked, setChecked ] = useState( 'option2' ); | ||
|
||
/* eslint-disable no-restricted-syntax */ | ||
return ( | ||
<RadioGroup | ||
// id is required for server side rendering | ||
id="controlled-radiogroup" | ||
accessibilityLabel="options" | ||
checked={ checked } | ||
onChange={ setChecked } | ||
> | ||
<Radio value="option1">Option 1</Radio> | ||
<Radio value="option2">Option 2</Radio> | ||
<Radio value="option3">Option 3</Radio> | ||
</RadioGroup> | ||
); | ||
/* eslint-enable no-restricted-syntax */ | ||
}; | ||
|
||
export const controlled = () => { | ||
return <ControlledRadioGroupWithState />; | ||
}; |
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,36 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { Radio as ReakitRadio } from 'reakit/Radio'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useContext, forwardRef } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import Button from '../button'; | ||
import RadioContext from '../radio-context'; | ||
|
||
function Radio( { children, value, ...props }, ref ) { | ||
const radioContext = useContext( RadioContext ); | ||
const checked = radioContext.state === value; | ||
|
||
return ( | ||
<ReakitRadio | ||
ref={ ref } | ||
as={ Button } | ||
isPrimary={ checked } | ||
isSecondary={ ! checked } | ||
value={ value } | ||
{ ...radioContext } | ||
{ ...props } | ||
> | ||
{ children || value } | ||
</ReakitRadio> | ||
); | ||
} | ||
|
||
export default forwardRef( Radio ); |
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,20 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import RadioGroup from '../../radio-group'; | ||
import Radio from '../'; | ||
|
||
export default { title: 'Components/Radio', component: Radio }; | ||
|
||
export const _default = () => { | ||
// Radio components must be a descendent of a RadioGroup component. | ||
/* eslint-disable no-restricted-syntax */ | ||
return ( | ||
// id is required for server side rendering | ||
<RadioGroup id="default-radiogroup" accessibilityLabel="options"> | ||
<Radio value="option1">Option 1</Radio> | ||
<Radio value="option2">Option 2</Radio> | ||
</RadioGroup> | ||
); | ||
/* eslint-enable no-restricted-syntax */ | ||
}; |
Oops, something went wrong.