Skip to content

Commit

Permalink
RadioControl: add support for option help text (#63751)
Browse files Browse the repository at this point in the history
* Add support for help text

* Alternative, help text is not part of the label and describes the option

* More meaningful help text in example

* CHANGELOG

* Refactor existing usages from custom labels to using the `helpText` property

* Use the Text component for the help text

* helpText => description

* Remove nested ternary

* Reuse basecontrol help text styles

* Tweak spacing

* Rename Storybook example

* Remove unused import

* Move vertical spacing before help text from row-gap to padding

* Increase horizontal gap between radio and label

* Tweak spacing

* Add unit tests

* Rework calculating and assigning element IDs for labels and descriptions

* Use correct classname modifier

* Add test comment
  • Loading branch information
ciampo authored Jul 30, 2024
1 parent 68ccfe3 commit 083bb91
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 89 deletions.
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- `ColorPalette`: Remove extra bottom margin when `CircularOptionPicker` is unneeded ([#63961](https://github.com/WordPress/gutenberg/pull/63961)).
- `CustomSelectControl`: Restore `describedBy` functionality ([#63957](https://github.com/WordPress/gutenberg/pull/63957)).

### Enhancements

- `RadioControl`: add support for option help text ([#63751](https://github.com/WordPress/gutenberg/pull/63751)).

### Internal

- `DropdownMenuV2`: break menu item help text on multiple lines for better truncation. ([#63916](https://github.com/WordPress/gutenberg/pull/63916)).
Expand Down
55 changes: 48 additions & 7 deletions packages/components/src/radio-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ import BaseControl from '../base-control';
import type { WordPressComponentProps } from '../context';
import type { RadioControlProps } from './types';
import { VStack } from '../v-stack';
import { useBaseControlProps } from '../base-control/hooks';
import { StyledHelp } from '../base-control/styles/base-control-styles';

function generateOptionDescriptionId( radioGroupId: string, index: number ) {
return `${ radioGroupId }-${ index }-option-description`;
}

function generateOptionId( radioGroupId: string, index: number ) {
return `${ radioGroupId }-${ index }`;
}

/**
* Render a user interface to select the user type using radio inputs.
Expand Down Expand Up @@ -53,13 +63,23 @@ export function RadioControl(
onChange,
hideLabelFromVision,
options = [],
id: preferredId,
...additionalProps
} = props;
const instanceId = useInstanceId( RadioControl );
const id = `inspector-radio-control-${ instanceId }`;
const id = useInstanceId(
RadioControl,
'inspector-radio-control',
preferredId
);

const onChangeValue = ( event: ChangeEvent< HTMLInputElement > ) =>
onChange( event.target.value );

// Use `useBaseControlProps` to get the id of the help text.
const {
controlProps: { 'aria-describedby': helpTextId },
} = useBaseControlProps( { id, help } );

if ( ! options?.length ) {
return null;
}
Expand All @@ -73,31 +93,52 @@ export function RadioControl(
help={ help }
className={ clsx( className, 'components-radio-control' ) }
>
<VStack spacing={ 2 }>
<VStack
spacing={ 3 }
className={ clsx( 'components-radio-control__group-wrapper', {
'has-help': !! help,
} ) }
>
{ options.map( ( option, index ) => (
<div
key={ `${ id }-${ index }` }
key={ generateOptionId( id, index ) }
className="components-radio-control__option"
>
<input
id={ `${ id }-${ index }` }
id={ generateOptionId( id, index ) }
className="components-radio-control__input"
type="radio"
name={ id }
value={ option.value }
onChange={ onChangeValue }
checked={ option.value === selected }
aria-describedby={
!! help ? `${ id }__help` : undefined
clsx( [
!! option.description &&
generateOptionDescriptionId(
id,
index
),
helpTextId,
] ) || undefined
}
{ ...additionalProps }
/>
<label
className="components-radio-control__label"
htmlFor={ `${ id }-${ index }` }
htmlFor={ generateOptionId( id, index ) }
>
{ option.label }
</label>
{ !! option.description ? (
<StyledHelp
__nextHasNoMarginBottom
id={ generateOptionDescriptionId( id, index ) }
className="components-radio-control__option-description"
>
{ option.description }
</StyledHelp>
) : null }
</div>
) ) }
</VStack>
Expand Down
23 changes: 23 additions & 0 deletions packages/components/src/radio-control/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,26 @@ Default.args = {
{ label: 'Password Protected', value: 'password' },
],
};

export const WithOptionDescriptions: StoryFn< typeof RadioControl > =
Template.bind( {} );
WithOptionDescriptions.args = {
...Default.args,
options: [
{
label: 'Public',
value: 'public',
description: 'Visible to everyone',
},
{
label: 'Private',
value: 'private',
description: 'Only visible to you',
},
{
label: 'Password Protected',
value: 'password',
description: 'Protected by a password',
},
],
};
28 changes: 26 additions & 2 deletions packages/components/src/radio-control/style.scss
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
.components-radio-control__group-wrapper.has-help {
margin-block-end: $grid-unit-15;
}

.components-radio-control__option {
display: flex;
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto minmax(0, max-content);
column-gap: $grid-unit-10;
align-items: center;
}

.components-radio-control__input[type="radio"] {
grid-column: 1;
grid-row: 1;

@include radio-control;

display: inline-flex;
margin: 0 $grid-unit-10 0 0;
margin: 0;
padding: 0;
appearance: none;
cursor: pointer;
Expand All @@ -28,10 +38,24 @@
}

.components-radio-control__label {
grid-column: 2;
grid-row: 1;

cursor: pointer;
line-height: $radio-input-size-sm;

@include break-small() {
line-height: $radio-input-size;
}
}

.components-radio-control__option-description {
grid-column: 2;
grid-row: 2;

padding-block-start: $grid-unit-05;

// Override the top margin of the StyledHelp component from BaseControl.
// TODO: we should tweak the StyledHelp component to not have a top margin.
margin-top: 0;
}
Loading

0 comments on commit 083bb91

Please sign in to comment.