Skip to content

Commit 756f809

Browse files
committed
Fix disabled SelectNext.Options which allow top be interacted
1 parent 2f582fe commit 756f809

File tree

3 files changed

+107
-32
lines changed

3 files changed

+107
-32
lines changed

src/components/input/SelectNext.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,25 @@ export type SelectOptionStatus = {
2828
export type SelectOptionProps<T> = {
2929
value: T;
3030
disabled?: boolean;
31-
children: (status: SelectOptionStatus) => ComponentChildren;
31+
children:
32+
| ComponentChildren
33+
| ((status: SelectOptionStatus) => ComponentChildren);
3234
classes?: string | string[];
3335
};
3436

37+
function optionChildren(
38+
children:
39+
| ComponentChildren
40+
| ((status: SelectOptionStatus) => ComponentChildren),
41+
status: SelectOptionStatus,
42+
): ComponentChildren {
43+
if (typeof children === 'function') {
44+
return children(status);
45+
}
46+
47+
return children;
48+
}
49+
3550
function SelectOption<T>({
3651
value,
3752
children,
@@ -49,14 +64,18 @@ function SelectOption<T>({
4964
return (
5065
<li
5166
className={classnames(
52-
'w-full ring-inset focus:ring outline-none rounded-none cursor-pointer',
67+
'w-full ring-inset focus:ring outline-none rounded-none',
5368
'border-t first:border-t-0 transition-colors whitespace-nowrap',
54-
{ 'hover:bg-grey-1': !disabled },
69+
{ 'cursor-pointer hover:bg-grey-1': !disabled },
5570
classes,
5671
)}
57-
onClick={() => selectValue(value)}
72+
onClick={() => {
73+
if (!disabled) {
74+
selectValue(value);
75+
}
76+
}}
5877
onKeyPress={e => {
59-
if (e.code === 'Enter' || e.code === 'Space') {
78+
if (!disabled && ['Enter', 'Space'].includes(e.code)) {
6079
e.preventDefault();
6180
selectValue(value);
6281
}
@@ -73,7 +92,7 @@ function SelectOption<T>({
7392
'border-l-brand font-medium': selected,
7493
})}
7594
>
76-
{children({ selected, disabled })}
95+
{optionChildren(children, { selected, disabled })}
7796
</div>
7897
</li>
7998
);

src/components/input/test/SelectNext-test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ describe('SelectNext', () => {
8181
assert.calledWith(onChange.lastCall, items[0]);
8282
});
8383

84+
it('does not change selected value when a disabled option is clicked', () => {
85+
const onChange = sinon.stub();
86+
const wrapper = createComponent({ onChange });
87+
const clickDisabledOption = () =>
88+
wrapper.find(`[data-testid="option-4"]`).simulate('click');
89+
90+
clickDisabledOption();
91+
assert.notCalled(onChange);
92+
});
93+
8494
['Enter', 'Space'].forEach(code => {
8595
it(`changes selected value when ${code} is pressed in option`, () => {
8696
const onChange = sinon.stub();
@@ -101,6 +111,20 @@ describe('SelectNext', () => {
101111
pressKeyInOption(1);
102112
assert.calledWith(onChange.lastCall, items[0]);
103113
});
114+
115+
it(`does not change selected value when ${code} is pressed in a disabled option`, () => {
116+
const onChange = sinon.stub();
117+
const wrapper = createComponent({ onChange });
118+
const pressKeyInDisabledOption = () =>
119+
wrapper
120+
.find(`[data-testid="option-4"]`)
121+
.getDOMNode()
122+
.closest('[role="option"]')
123+
.dispatchEvent(new KeyboardEvent('keypress', { code }));
124+
125+
pressKeyInDisabledOption();
126+
assert.notCalled(onChange);
127+
});
104128
});
105129

106130
it('marks the right item as selected', () => {

src/pattern-library/components/patterns/prototype/SelectNextPage.tsx

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,17 @@ function SelectExample({
5555
>
5656
{items.map(item => (
5757
<SelectNext.Option value={item} key={item.id}>
58-
{() =>
59-
textOnly ? (
60-
<>{item.name}</>
61-
) : (
62-
<>
63-
{item.name}
64-
<div className="grow" />
65-
<div className="rounded px-2 ml-2 bg-grey-7 text-white">
66-
{item.id}
67-
</div>
68-
</>
69-
)
70-
}
58+
{textOnly ? (
59+
item.name
60+
) : (
61+
<>
62+
{item.name}
63+
<div className="grow" />
64+
<div className="rounded px-2 ml-2 bg-grey-7 text-white">
65+
{item.id}
66+
</div>
67+
</>
68+
)}
7169
</SelectNext.Option>
7270
))}
7371
</SelectNext>
@@ -117,19 +115,13 @@ function InputGroupSelectExample({ classes }: { classes?: string }) {
117115
>
118116
{defaultItems.map(item => (
119117
<SelectNext.Option value={item} key={item.id}>
120-
{() => (
121-
<>
122-
{item.name}
123-
<div className="grow" />
124-
<div
125-
className={classnames(
126-
'rounded px-2 ml-2 text-white bg-grey-7',
127-
)}
128-
>
129-
{item.id}
130-
</div>
131-
</>
132-
)}
118+
{item.name}
119+
<div className="grow" />
120+
<div
121+
className={classnames('rounded px-2 ml-2 text-white bg-grey-7')}
122+
>
123+
{item.id}
124+
</div>
133125
</SelectNext.Option>
134126
))}
135127
</SelectNext>
@@ -328,6 +320,46 @@ export default function SelectNextPage() {
328320
</Library.InfoItem>
329321
</Library.Info>
330322
</Library.Example>
323+
<p>
324+
Every <code>SelectNext.Option</code> has its own set of props.
325+
</p>
326+
<Library.Example title="children">
327+
<Library.Info>
328+
<Library.InfoItem label="description">
329+
Content of the option. You can pass a callback to receive the
330+
option status (<code>disabled</code> and <code>selected</code>).
331+
</Library.InfoItem>
332+
<Library.InfoItem label="type">
333+
<code>
334+
ComponentChildren | (({'{'} disabled, selected {'}'}) {'=>'}{' '}
335+
ComponentChildren)
336+
</code>
337+
</Library.InfoItem>
338+
</Library.Info>
339+
</Library.Example>
340+
<Library.Example title="value">
341+
<Library.Info>
342+
<Library.InfoItem label="description">
343+
The value to set when this option is selected.
344+
</Library.InfoItem>
345+
<Library.InfoItem label="type">
346+
<code>T</code>
347+
</Library.InfoItem>
348+
</Library.Info>
349+
</Library.Example>
350+
<Library.Example title="disabled">
351+
<Library.Info>
352+
<Library.InfoItem label="description">
353+
Whether the option is disabled or not.
354+
</Library.InfoItem>
355+
<Library.InfoItem label="type">
356+
<code>boolean</code>
357+
</Library.InfoItem>
358+
<Library.InfoItem label="default">
359+
<code>undefined</code>
360+
</Library.InfoItem>
361+
</Library.Info>
362+
</Library.Example>
331363
</Library.Pattern>
332364

333365
<Library.Pattern title="How to use it">

0 commit comments

Comments
 (0)