-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Select.tsx
143 lines (126 loc) · 3.45 KB
/
Select.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import * as React from 'react';
import {classNames} from '@shopify/react-utilities/styles';
import {createUniqueIDFactory} from '@shopify/javascript-utilities/other';
import Labelled, {Action, Error, helpTextID, errorID} from '../Labelled';
import Icon from '../Icon';
import * as styles from './Select.scss';
import arrowIcon from './icons/arrow.svg';
export type Option = string | {
value: string,
label: string,
disabled?: boolean,
};
export interface Group {
title: string,
options: Option[],
}
export interface Props {
options?: Option[],
groups?: (Group | Option)[],
label: string,
labelAction?: Action,
labelHidden?: boolean,
helpText?: React.ReactNode,
id?: string,
name?: string,
error?: Error,
disabled?: boolean,
value?: string,
placeholder?: string,
onChange?(selected: string, id: string): void,
onFocus?(): void,
onBlur?(): void,
}
const PLACEHOLDER_VALUE = '__placeholder__';
const getUniqueID = createUniqueIDFactory('Select');
export default function Select({
id = getUniqueID(),
name,
groups,
options,
labelHidden,
labelAction,
helpText,
label,
error,
value,
placeholder,
disabled,
onChange,
onFocus,
onBlur,
}: Props) {
let optionsMarkup: React.ReactNode;
if (options != null) {
optionsMarkup = options.map(renderOption);
} else if (groups != null) {
optionsMarkup = groups.map(renderGroup);
}
const isPlaceholder = value == null && placeholder != null;
const className = classNames(
styles.Select,
error && styles.error,
disabled && styles.disabled,
isPlaceholder && styles.placeholder,
);
const handleChange = onChange
? ((event: React.ChangeEvent<HTMLSelectElement>) => onChange(event.currentTarget.value, id))
: undefined;
const describedBy: string[] = [];
if (helpText) { describedBy.push(helpTextID(id)); }
if (error && typeof error === 'string') { describedBy.push(errorID(id)); }
const placeholderOption = isPlaceholder
? <option label={placeholder} value={PLACEHOLDER_VALUE} disabled hidden />
: null;
return (
<Labelled
id={id}
label={label}
error={error}
action={labelAction}
labelHidden={labelHidden}
helpText={helpText}
>
<div className={className}>
<select
id={id}
name={name}
value={value}
defaultValue={PLACEHOLDER_VALUE}
className={styles.Input}
disabled={disabled}
onFocus={onFocus}
onBlur={onBlur}
onChange={handleChange}
aria-invalid={Boolean(error)}
aria-describedby={describedBy.length ? describedBy.join(' ') : undefined}
>
{placeholderOption}
{optionsMarkup}
</select>
<div className={styles.Icon}>
<Icon source={arrowIcon} />
</div>
<div className={styles.Backdrop} />
</div>
</Labelled>
);
}
function renderOption(option: Option) {
if (typeof option === 'string') {
return <option key={option} value={option}>{option}</option>;
} else {
return <option key={option.value} value={option.value} disabled={option.disabled}>{option.label}</option>;
}
}
function renderGroup(groupOrOption: Group | Option) {
if (groupOrOption.hasOwnProperty('title')) {
const {title, options} = groupOrOption as Group;
return (
<optgroup label={title} key={title}>
{options.map(renderOption)}
</optgroup>
);
}
return renderOption(groupOrOption as Option);
}