Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RadioControl: Convert component to TypeScript #41568

Merged
merged 5 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
### Internal

- `FormTokenField`: Convert to TypeScript and refactor to functional component ([#41216](https://github.com/WordPress/gutenberg/pull/41216)).
- `RadioControl`: Convert to TypeScript ([#41568](https://github.com/WordPress/gutenberg/pull/41568)).

## 19.12.0 (2022-06-01)

Expand Down Expand Up @@ -41,6 +42,7 @@
- `Surface`: Convert to TypeScript ([#41212](https://github.com/WordPress/gutenberg/pull/41212)).
- `Autocomplete` updated to satisfy `react/exhuastive-deps` eslint rule ([#41382](https://github.com/WordPress/gutenberg/pull/41382))
- `DateDayPicker` updated to satisfy `react/exhuastive-deps` eslint rule ([#41470](https://github.com/WordPress/gutenberg/pull/41470)).

### Experimental

- `Spacer`: Add RTL support. ([#41172](https://github.com/WordPress/gutenberg/pull/41172))
Expand Down
40 changes: 17 additions & 23 deletions packages/components/src/radio-control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,57 +75,51 @@ const MyRadioControl = () => {
onChange={ ( value ) => setOption( value ) }
/>
);
}
};
```

### Props

The component accepts the following props:

### hideLabelFromVision
#### `help`: `string | WPElement`

If true, the label will only be visible to screen readers.
If this property is added, a help text will be generated using help property as the content.

- Type: `Boolean`
- Required: No

#### label
#### `hideLabelFromVision`: `boolean`

If this property is added, a label will be generated using label property as the content.
If true, the label will only be visible to screen readers.

- Type: `String`
- Required: No

#### help
#### `label`: `string`

If this property is added, a help text will be generated using help property as the content.
If this property is added, a label will be generated using label property as the content.

- Type: `String|WPElement`
- Required: No

#### selected
#### `onChange`: `( value: string ) => void`

The value property of the currently selected option.
A function that receives the value of the new option that is being selected as input.

- Type: `Object`
- Required: No
- Required: Yes

#### options
#### `options`: `{ label: string, value: string }[]`

An array of objects containing the following properties:
An array of objects containing the value and label of the options.

- `label`: (string) The label to be shown to the user.
- `value`: (Object) The internal value compared against select and passed to onChange.
- `label`: `string` The label to be shown to the user.
- `value`: `string` The internal value compared against select and passed to onChange.

* Type: `Array`
* Required: No

#### onChange
#### `selected`: `string`

A function that receives the value of the new option that is being selected as input.
The value property of the currently selected option.

- Type: `function`
- Required: Yes
- Required: No

## Related components

Expand Down
69 changes: 0 additions & 69 deletions packages/components/src/radio-control/index.js

This file was deleted.

107 changes: 107 additions & 0 deletions packages/components/src/radio-control/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* External dependencies
*/
import { isEmpty } from 'lodash';
import classnames from 'classnames';
import type { ChangeEvent } from 'react';

/**
* WordPress dependencies
*/
import { useInstanceId } from '@wordpress/compose';

/**
* Internal dependencies
*/
import BaseControl from '../base-control';
import type { WordPressComponentProps } from '../ui/context';
import type { RadioControlProps } from './types';

/**
* Render a user interface to select the user type using radio inputs.
*
* ```jsx
* import { RadioControl } from '@wordpress/components';
* import { useState } from '@wordpress/element';
*
* const MyRadioControl = () => {
* const [ option, setOption ] = useState( 'a' );
*
* return (
* <RadioControl
* label="User type"
* help="The type of the current user"
* selected={ option }
* options={ [
* { label: 'Author', value: 'a' },
* { label: 'Editor', value: 'e' },
* ] }
* onChange={ ( value ) => setOption( value ) }
* />
* );
* };
* ```
*/
export function RadioControl(
// ref is omitted until we have `WordPressComponentPropsWithoutRef` or add
// ref forwarding to RadioControl.
props: Omit<
WordPressComponentProps< RadioControlProps, 'input', false >,
'ref'
>
Comment on lines +48 to +51
Copy link
Contributor

@ciampo ciampo Jun 9, 2022

Choose a reason for hiding this comment

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

A bit late to the party, but I was curious about why it was necessary to omit ref ? Was it causing any errors ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Opened #41641 to add ref forwarding support

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

Didn't want to add any runtime changes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good, and I agree that it's a good rationale! I was mostly curious to know in case it caused any type errors :)

) {
const {
label,
className,
selected,
help,
onChange,
hideLabelFromVision,
options = [],
...additionalProps
} = props;
const instanceId = useInstanceId( RadioControl );
const id = `inspector-radio-control-${ instanceId }`;
const onChangeValue = ( event: ChangeEvent< HTMLInputElement > ) =>
onChange( event.target.value );

if ( isEmpty( options ) ) {
return null;
}

return (
<BaseControl
label={ label }
id={ id }
hideLabelFromVision={ hideLabelFromVision }
help={ help }
className={ classnames( className, 'components-radio-control' ) }
>
{ options.map( ( option, index ) => (
<div
key={ `${ id }-${ index }` }
className="components-radio-control__option"
>
<input
id={ `${ 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
}
{ ...additionalProps }
/>
<label htmlFor={ `${ id }-${ index }` }>
{ option.label }
</label>
</div>
) ) }
</BaseControl>
);
}

export default RadioControl;
41 changes: 0 additions & 41 deletions packages/components/src/radio-control/stories/index.js

This file was deleted.

72 changes: 72 additions & 0 deletions packages/components/src/radio-control/stories/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* External dependencies
*/
import type { ComponentMeta, ComponentStory } from '@storybook/react';

/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import RadioControl from '..';

const meta: ComponentMeta< typeof RadioControl > = {
component: RadioControl,
title: 'Components/RadioControl',
argTypes: {
onChange: {
action: 'onChange',
},
selected: {
control: { type: null },
},
label: {
control: { type: 'text' },
},
help: {
control: { type: 'text' },
},
},
parameters: {
controls: {
expanded: true,
},
docs: { source: { state: 'open' } },
},
};
export default meta;

const Template: ComponentStory< typeof RadioControl > = ( {
onChange,
options,
...args
} ) => {
const [ value, setValue ] = useState( options?.[ 0 ]?.value );

return (
<RadioControl
{ ...args }
selected={ value }
options={ options }
onChange={ ( v ) => {
setValue( v );
onChange( v );
} }
/>
);
};

export const Default: ComponentStory< typeof RadioControl > = Template.bind(
{}
);
Default.args = {
label: 'Post visibility',
options: [
{ label: 'Public', value: 'public' },
{ label: 'Private', value: 'private' },
{ label: 'Password Protected', value: 'password' },
],
};
Loading