Skip to content

Commit 09fa909

Browse files
Merge pull request #2557 from Shopify/funcify-option
[Option] Converted to a functional component
2 parents 263149e + 5087d58 commit 09fa909

File tree

3 files changed

+96
-98
lines changed

3 files changed

+96
-98
lines changed

UNRELEASED.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@
2828
- Converted `BulkActionButton` into a functional component ([#2542](https://github.com/Shopify/polaris-react/pull/2542))
2929
- Converted `Focus` into a functional component ([#2540](https://github.com/Shopify/polaris-react/pull/2540))
3030
- Converted `Tooltip` into a functional component ([#2543](https://github.com/Shopify/polaris-react/pull/2543))
31+
- Converted `Option` into a functional component ([#2541](https://github.com/Shopify/polaris-react/pull/2541))
3132

3233
### Deprecations
Lines changed: 85 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import React from 'react';
1+
import React, {useCallback} from 'react';
22

3+
import {useToggle} from '../../../../utilities/use-toggle';
34
import {classNames} from '../../../../utilities/css';
45
import {IconProps} from '../../../../types';
56
import {ThumbnailProps} from '../../../Thumbnail';
@@ -24,106 +25,92 @@ export interface OptionProps {
2425
onClick(section: number, option: number): void;
2526
}
2627

27-
interface State {
28-
focused: boolean;
29-
}
30-
31-
export class Option extends React.Component<OptionProps, State> {
32-
state: State = {
33-
focused: false,
34-
};
35-
36-
render() {
37-
const {
38-
label,
39-
value,
40-
id,
41-
select,
42-
active,
43-
allowMultiple,
44-
disabled,
45-
role,
46-
media,
47-
} = this.props;
48-
const {focused} = this.state;
49-
50-
const mediaMarkup = media ? (
51-
<div className={styles.Media}>{media}</div>
52-
) : null;
53-
54-
const singleSelectClassName = classNames(
55-
styles.SingleSelectOption,
56-
focused && styles.focused,
57-
disabled && styles.disabled,
58-
select && styles.select,
59-
active && styles.active,
60-
);
61-
62-
const multiSelectClassName = classNames(
63-
styles.Label,
64-
active && styles.active,
65-
);
66-
67-
const checkBoxRole = role === 'option' ? 'presentation' : undefined;
68-
69-
const optionMarkup = allowMultiple ? (
70-
<label htmlFor={id} className={multiSelectClassName}>
71-
<div className={styles.Checkbox}>
72-
<Checkbox
73-
id={id}
74-
value={value}
75-
checked={select}
76-
active={active}
77-
disabled={disabled}
78-
onChange={this.handleClick}
79-
role={checkBoxRole}
80-
/>
81-
</div>
82-
{mediaMarkup}
83-
{label}
84-
</label>
85-
) : (
86-
<button
87-
id={id}
88-
type="button"
89-
className={singleSelectClassName}
90-
onClick={this.handleClick}
91-
disabled={disabled}
92-
onFocus={this.toggleFocus}
93-
onBlur={this.toggleFocus}
94-
>
95-
{mediaMarkup}
96-
{label}
97-
</button>
98-
);
99-
100-
const scrollMarkup = active ? <Scrollable.ScrollTo /> : null;
101-
102-
return (
103-
<li
104-
key={id}
105-
className={styles.Option}
106-
tabIndex={-1}
107-
aria-selected={active}
108-
role={role}
109-
>
110-
{scrollMarkup}
111-
{optionMarkup}
112-
</li>
113-
);
114-
}
115-
116-
private handleClick = () => {
117-
const {onClick, section, index, disabled} = this.props;
118-
28+
export function Option({
29+
label,
30+
value,
31+
id,
32+
select,
33+
active,
34+
allowMultiple,
35+
disabled,
36+
role,
37+
media,
38+
onClick,
39+
section,
40+
index,
41+
}: OptionProps) {
42+
const {value: focused, toggle: toggleFocused} = useToggle(false);
43+
44+
const handleClick = useCallback(() => {
11945
if (disabled) {
12046
return;
12147
}
12248

12349
onClick(section, index);
124-
};
125-
126-
private toggleFocus = () => {
127-
this.setState((prevState) => ({focused: !prevState.focused}));
128-
};
50+
}, [disabled, index, onClick, section]);
51+
52+
const mediaMarkup = media ? (
53+
<div className={styles.Media}>{media}</div>
54+
) : null;
55+
56+
const singleSelectClassName = classNames(
57+
styles.SingleSelectOption,
58+
focused && styles.focused,
59+
disabled && styles.disabled,
60+
select && styles.select,
61+
active && styles.active,
62+
);
63+
64+
const multiSelectClassName = classNames(
65+
styles.Label,
66+
active && styles.active,
67+
);
68+
69+
const checkBoxRole = role === 'option' ? 'presentation' : undefined;
70+
71+
const optionMarkup = allowMultiple ? (
72+
<label htmlFor={id} className={multiSelectClassName}>
73+
<div className={styles.Checkbox}>
74+
<Checkbox
75+
id={id}
76+
value={value}
77+
checked={select}
78+
active={active}
79+
disabled={disabled}
80+
onChange={handleClick}
81+
role={checkBoxRole}
82+
/>
83+
</div>
84+
{mediaMarkup}
85+
{label}
86+
</label>
87+
) : (
88+
<button
89+
id={id}
90+
type="button"
91+
className={singleSelectClassName}
92+
onClick={handleClick}
93+
disabled={disabled}
94+
onFocus={toggleFocused}
95+
onBlur={toggleFocused}
96+
>
97+
{mediaMarkup}
98+
{label}
99+
</button>
100+
);
101+
102+
const scrollMarkup = active ? <Scrollable.ScrollTo /> : null;
103+
104+
return (
105+
<li
106+
key={id}
107+
className={styles.Option}
108+
tabIndex={-1}
109+
aria-selected={active}
110+
role={role}
111+
>
112+
{scrollMarkup}
113+
{optionMarkup}
114+
</li>
115+
);
129116
}

src/components/OptionList/components/Option/tests/Option.test.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
// eslint-disable-next-line no-restricted-imports
33
import {mountWithAppProvider} from 'test-utilities/legacy';
4+
import {mountWithApp} from 'test-utilities';
45
import {Checkbox} from '../../Checkbox';
56
import {Option, OptionProps} from '../Option';
67

@@ -87,6 +88,15 @@ describe('<Option />', () => {
8788
expect(checkbox.prop('checked')).toBe(select);
8889
expect(checkbox.prop('disabled')).toBe(disabled);
8990
});
91+
92+
it('renders media content when provided', () => {
93+
const id = 'media';
94+
const option = mountWithApp(
95+
<Option {...defaultProps} media={<div id={id} />} />,
96+
);
97+
98+
expect(option).toContainReactComponent('div', {id});
99+
});
90100
});
91101

92102
function noop() {}

0 commit comments

Comments
 (0)