@transferwise/components 0.0.0-experimental-c654bc3 → 0.0.0-experimental-a7e0e6f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.esm.js +47 -141
- package/build/index.esm.js.map +1 -1
- package/build/index.js +47 -141
- package/build/index.js.map +1 -1
- package/build/main.css +7 -5
- package/build/styles/dimmer/Dimmer.css +0 -3
- package/build/styles/drawer/Drawer.css +3 -0
- package/build/styles/main.css +7 -5
- package/build/styles/modal/Modal.css +4 -2
- package/build/types/index.d.ts +2 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/typeahead/Typeahead.d.ts +98 -57
- package/build/types/typeahead/Typeahead.d.ts.map +1 -1
- package/build/types/typeahead/index.d.ts +3 -2
- package/build/types/typeahead/index.d.ts.map +1 -1
- package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts +23 -41
- package/build/types/typeahead/typeaheadInput/TypeaheadInput.d.ts.map +1 -1
- package/build/types/typeahead/typeaheadOption/TypeaheadOption.d.ts +9 -17
- package/build/types/typeahead/typeaheadOption/TypeaheadOption.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/dimmer/Dimmer.css +0 -3
- package/src/dimmer/Dimmer.less +0 -4
- package/src/drawer/Drawer.css +3 -0
- package/src/drawer/Drawer.less +4 -0
- package/src/index.ts +2 -1
- package/src/main.css +7 -5
- package/src/modal/Modal.css +4 -2
- package/src/modal/Modal.less +5 -1
- package/src/modal/Modal.story.tsx +4 -2
- package/src/typeahead/Typeahead.spec.js +1 -1
- package/src/typeahead/{Typeahead.story.js → Typeahead.story.tsx} +8 -7
- package/src/typeahead/{Typeahead.js → Typeahead.tsx} +105 -106
- package/src/typeahead/index.ts +4 -0
- package/src/typeahead/typeaheadInput/{TypeaheadInput.js → TypeaheadInput.tsx} +43 -42
- package/src/typeahead/typeaheadOption/{TypeaheadOption.js → TypeaheadOption.tsx} +10 -20
- package/src/typeahead/index.js +0 -3
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { select, boolean } from '@storybook/addon-knobs';
|
|
2
|
+
import { StoryContext } from '@storybook/react';
|
|
2
3
|
import { userEvent, within } from '@storybook/test';
|
|
3
4
|
import { Search as SearchIcon } from '@transferwise/icons';
|
|
4
5
|
import { useState } from 'react';
|
|
5
6
|
|
|
6
7
|
import { Sentiment } from '../common';
|
|
7
8
|
|
|
8
|
-
import Typeahead from './Typeahead';
|
|
9
|
+
import { Typeahead, TypeaheadOption } from './Typeahead';
|
|
9
10
|
|
|
10
11
|
export default {
|
|
11
12
|
component: Typeahead,
|
|
12
13
|
title: 'Forms/Typeahead',
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
const validateChip = (option) => {
|
|
16
|
+
const validateChip = (option: TypeaheadOption) => {
|
|
16
17
|
// eslint-disable-next-line unicorn/no-unsafe-regex
|
|
17
18
|
return /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
|
|
18
19
|
option.label,
|
|
@@ -43,7 +44,7 @@ export const createable = () => {
|
|
|
43
44
|
);
|
|
44
45
|
};
|
|
45
46
|
|
|
46
|
-
createable.play = async ({ canvasElement }) => {
|
|
47
|
+
createable.play = async ({ canvasElement }: StoryContext) => {
|
|
47
48
|
const canvas = within(canvasElement);
|
|
48
49
|
await userEvent.type(canvas.getByRole('combobox'), 'chip{Enter}chip2{Enter}');
|
|
49
50
|
};
|
|
@@ -76,7 +77,7 @@ export const Basic = () => {
|
|
|
76
77
|
]);
|
|
77
78
|
|
|
78
79
|
const validateChipWhenMultiple = () => {
|
|
79
|
-
return multiple && allowNew ? (option) => validateChip(option) : undefined;
|
|
80
|
+
return multiple && allowNew ? (option: TypeaheadOption) => validateChip(option) : undefined;
|
|
80
81
|
};
|
|
81
82
|
|
|
82
83
|
const multiple = boolean('multiple', false);
|
|
@@ -101,8 +102,8 @@ export const Basic = () => {
|
|
|
101
102
|
showNewEntry={showNewEntry}
|
|
102
103
|
placeholder="placeholder"
|
|
103
104
|
chipSeparators={[',', ' ']}
|
|
104
|
-
validateChip={validateChipWhenMultiple}
|
|
105
|
-
alert={showAlert
|
|
105
|
+
validateChip={validateChipWhenMultiple()}
|
|
106
|
+
alert={showAlert ? { message: `Couldn't add item`, type: alertType } : undefined}
|
|
106
107
|
addon={<SearchIcon size={24} />}
|
|
107
108
|
options={options}
|
|
108
109
|
inputAutoComplete="off"
|
|
@@ -115,7 +116,7 @@ export const Basic = () => {
|
|
|
115
116
|
);
|
|
116
117
|
};
|
|
117
118
|
|
|
118
|
-
Basic.play = async ({ canvasElement }) => {
|
|
119
|
+
Basic.play = async ({ canvasElement }: StoryContext) => {
|
|
119
120
|
const canvas = within(canvasElement);
|
|
120
121
|
await userEvent.type(canvas.getByRole('combobox'), 'abc{ArrowDown}');
|
|
121
122
|
};
|
|
@@ -4,18 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
import { Cross as CrossIcon } from '@transferwise/icons';
|
|
6
6
|
import classNames from 'classnames';
|
|
7
|
+
import { DebouncedFunc } from 'lodash';
|
|
7
8
|
import clamp from 'lodash.clamp';
|
|
8
9
|
import debounce from 'lodash.debounce';
|
|
9
|
-
import
|
|
10
|
-
import { Component } from 'react';
|
|
10
|
+
import { Component, ReactNode } from 'react';
|
|
11
11
|
|
|
12
12
|
import Chip from '../chips/Chip';
|
|
13
|
-
import { Size, Sentiment } from '../common';
|
|
13
|
+
import { Size, Sentiment, SizeMedium, SizeLarge } from '../common';
|
|
14
14
|
import {
|
|
15
15
|
addClickClassToDocumentOnIos,
|
|
16
16
|
removeClickClassFromDocumentOnIos,
|
|
17
|
+
stopPropagation,
|
|
17
18
|
} from '../common/domHelpers';
|
|
18
19
|
import InlineAlert from '../inlineAlert';
|
|
20
|
+
import { InlineAlertProps } from '../inlineAlert/InlineAlert';
|
|
19
21
|
|
|
20
22
|
import TypeaheadInput from './typeaheadInput/TypeaheadInput';
|
|
21
23
|
import TypeaheadOption from './typeaheadOption/TypeaheadOption';
|
|
@@ -23,8 +25,81 @@ import TypeaheadOption from './typeaheadOption/TypeaheadOption';
|
|
|
23
25
|
const DEFAULT_MIN_QUERY_LENGTH = 3;
|
|
24
26
|
const SEARCH_DELAY = 200;
|
|
25
27
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
+
export type TypeaheadOption<T = string> = {
|
|
29
|
+
label: string;
|
|
30
|
+
note?: string;
|
|
31
|
+
secondary?: string;
|
|
32
|
+
value?: T;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface TypeaheadProps<T> {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
addon: ReactNode;
|
|
39
|
+
alert?: {
|
|
40
|
+
message: InlineAlertProps['children'];
|
|
41
|
+
type: InlineAlertProps['type'];
|
|
42
|
+
};
|
|
43
|
+
allowNew: boolean;
|
|
44
|
+
autoFillOnBlur?: boolean;
|
|
45
|
+
autoFocus: boolean;
|
|
46
|
+
chipSeparators?: string[];
|
|
47
|
+
clearable: boolean;
|
|
48
|
+
footer: ReactNode;
|
|
49
|
+
initialValue: TypeaheadOption<T>[];
|
|
50
|
+
inputAutoComplete?: string;
|
|
51
|
+
maxHeight: number;
|
|
52
|
+
minQueryLength: number;
|
|
53
|
+
placeholder?: string;
|
|
54
|
+
multiple: boolean;
|
|
55
|
+
options: TypeaheadOption<T>[];
|
|
56
|
+
searchDelay: number;
|
|
57
|
+
showSuggestions: boolean;
|
|
58
|
+
showNewEntry: boolean;
|
|
59
|
+
size?: SizeMedium | SizeLarge;
|
|
60
|
+
|
|
61
|
+
onBlur?: () => void;
|
|
62
|
+
onChange: (options: TypeaheadOption<T>[]) => void;
|
|
63
|
+
onFocus?: () => void;
|
|
64
|
+
onInputChange?: (query: string) => void;
|
|
65
|
+
onSearch?: (query: string) => void;
|
|
66
|
+
validateChip?: (chip: TypeaheadOption<T>) => boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type TypeaheadState<T> = {
|
|
70
|
+
selected: TypeaheadOption<T>[];
|
|
71
|
+
keyboardFocusedOptionIndex: number | null;
|
|
72
|
+
errorState: boolean;
|
|
73
|
+
query: string;
|
|
74
|
+
optionsShown?: boolean;
|
|
75
|
+
isFocused?: boolean;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export class Typeahead<T> extends Component<TypeaheadProps<T>, TypeaheadState<T>> {
|
|
79
|
+
declare props: TypeaheadProps<T> &
|
|
80
|
+
Required<Pick<TypeaheadProps<T>, keyof typeof Typeahead.defaultProps>>;
|
|
81
|
+
|
|
82
|
+
static defaultProps = {
|
|
83
|
+
addon: null,
|
|
84
|
+
allowNew: false,
|
|
85
|
+
autoFillOnBlur: true,
|
|
86
|
+
autoFocus: false,
|
|
87
|
+
chipSeparators: [],
|
|
88
|
+
clearable: true,
|
|
89
|
+
footer: null,
|
|
90
|
+
initialValue: [],
|
|
91
|
+
inputAutoComplete: 'new-password',
|
|
92
|
+
maxHeight: null,
|
|
93
|
+
minQueryLength: DEFAULT_MIN_QUERY_LENGTH,
|
|
94
|
+
multiple: false,
|
|
95
|
+
searchDelay: SEARCH_DELAY,
|
|
96
|
+
showSuggestions: true,
|
|
97
|
+
showNewEntry: true,
|
|
98
|
+
size: Size.MEDIUM,
|
|
99
|
+
validateChip: () => true,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
constructor(props: TypeaheadProps<T>) {
|
|
28
103
|
super(props);
|
|
29
104
|
const { searchDelay, initialValue, multiple } = props;
|
|
30
105
|
this.handleSearchDebounced = debounce(this.handleSearch, searchDelay);
|
|
@@ -37,7 +112,9 @@ export default class Typeahead extends Component {
|
|
|
37
112
|
};
|
|
38
113
|
}
|
|
39
114
|
|
|
40
|
-
|
|
115
|
+
handleSearchDebounced: DebouncedFunc<(query: string) => void>;
|
|
116
|
+
|
|
117
|
+
UNSAFE_componentWillReceiveProps(nextProps: TypeaheadProps<T>) {
|
|
41
118
|
if (nextProps.multiple !== this.props.multiple) {
|
|
42
119
|
this.setState((previousState) => {
|
|
43
120
|
const { selected } = previousState;
|
|
@@ -49,6 +126,7 @@ export default class Typeahead extends Component {
|
|
|
49
126
|
}
|
|
50
127
|
return {
|
|
51
128
|
query: '',
|
|
129
|
+
selected: previousState.selected,
|
|
52
130
|
};
|
|
53
131
|
});
|
|
54
132
|
}
|
|
@@ -59,20 +137,16 @@ export default class Typeahead extends Component {
|
|
|
59
137
|
}
|
|
60
138
|
|
|
61
139
|
handleOnFocus = () => {
|
|
62
|
-
const { onFocus } = this.props;
|
|
63
140
|
this.showMenu();
|
|
64
|
-
|
|
65
|
-
if (onFocus) {
|
|
66
|
-
this.props.onFocus();
|
|
67
|
-
}
|
|
141
|
+
this.props.onFocus?.();
|
|
68
142
|
};
|
|
69
143
|
|
|
70
|
-
onOptionSelected = (event, item) => {
|
|
144
|
+
onOptionSelected = (event: React.MouseEvent, item: TypeaheadOption<T>) => {
|
|
71
145
|
event.preventDefault();
|
|
72
146
|
this.selectItem(item);
|
|
73
147
|
};
|
|
74
148
|
|
|
75
|
-
handleOnChange = (event) => {
|
|
149
|
+
handleOnChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
76
150
|
const { optionsShown, selected } = this.state;
|
|
77
151
|
const { multiple, onInputChange } = this.props;
|
|
78
152
|
|
|
@@ -94,7 +168,7 @@ export default class Typeahead extends Component {
|
|
|
94
168
|
});
|
|
95
169
|
};
|
|
96
170
|
|
|
97
|
-
handleOnPaste = (event) => {
|
|
171
|
+
handleOnPaste: React.ClipboardEventHandler<HTMLInputElement> = (event) => {
|
|
98
172
|
const { allowNew, multiple, chipSeparators } = this.props;
|
|
99
173
|
const { selected } = this.state;
|
|
100
174
|
|
|
@@ -113,7 +187,7 @@ export default class Typeahead extends Component {
|
|
|
113
187
|
}
|
|
114
188
|
};
|
|
115
189
|
|
|
116
|
-
handleOnKeyDown = (event) => {
|
|
190
|
+
handleOnKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
|
|
117
191
|
const { showSuggestions, allowNew, multiple, chipSeparators, options } = this.props;
|
|
118
192
|
const { keyboardFocusedOptionIndex, query, selected } = this.state;
|
|
119
193
|
const chipsMode = !showSuggestions && allowNew && multiple;
|
|
@@ -133,7 +207,7 @@ export default class Typeahead extends Component {
|
|
|
133
207
|
break;
|
|
134
208
|
case 'Enter':
|
|
135
209
|
event.preventDefault();
|
|
136
|
-
if (options[keyboardFocusedOptionIndex]) {
|
|
210
|
+
if (keyboardFocusedOptionIndex && options[keyboardFocusedOptionIndex]) {
|
|
137
211
|
this.selectItem(options[keyboardFocusedOptionIndex]);
|
|
138
212
|
} else if (allowNew && query.trim()) {
|
|
139
213
|
this.selectItem({ label: query });
|
|
@@ -150,7 +224,7 @@ export default class Typeahead extends Component {
|
|
|
150
224
|
}
|
|
151
225
|
};
|
|
152
226
|
|
|
153
|
-
moveFocusedOption(offset) {
|
|
227
|
+
moveFocusedOption(offset: number) {
|
|
154
228
|
this.setState((previousState) => {
|
|
155
229
|
const { keyboardFocusedOptionIndex } = previousState;
|
|
156
230
|
const { options } = this.props;
|
|
@@ -164,7 +238,7 @@ export default class Typeahead extends Component {
|
|
|
164
238
|
});
|
|
165
239
|
}
|
|
166
240
|
|
|
167
|
-
selectItem = (item) => {
|
|
241
|
+
selectItem = (item: TypeaheadOption<T>) => {
|
|
168
242
|
const { multiple } = this.props;
|
|
169
243
|
let selected = [...this.state.selected];
|
|
170
244
|
let query;
|
|
@@ -183,15 +257,7 @@ export default class Typeahead extends Component {
|
|
|
183
257
|
});
|
|
184
258
|
};
|
|
185
259
|
|
|
186
|
-
|
|
187
|
-
event.stopPropagation();
|
|
188
|
-
event.preventDefault();
|
|
189
|
-
if (event.nativeEvent && event.nativeEvent.stopImmediatePropagation) {
|
|
190
|
-
event.nativeEvent.stopImmediatePropagation();
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
handleSearch = (query) => {
|
|
260
|
+
handleSearch = (query: string) => {
|
|
195
261
|
const { onSearch } = this.props;
|
|
196
262
|
if (onSearch) {
|
|
197
263
|
onSearch(query);
|
|
@@ -246,7 +312,7 @@ export default class Typeahead extends Component {
|
|
|
246
312
|
);
|
|
247
313
|
};
|
|
248
314
|
|
|
249
|
-
updateSelectedValue = (selected) => {
|
|
315
|
+
updateSelectedValue = (selected: TypeaheadOption<T>[]) => {
|
|
250
316
|
const { onChange, validateChip } = this.props;
|
|
251
317
|
|
|
252
318
|
const errorState = selected.some((chip) => !validateChip(chip));
|
|
@@ -255,7 +321,7 @@ export default class Typeahead extends Component {
|
|
|
255
321
|
});
|
|
256
322
|
};
|
|
257
323
|
|
|
258
|
-
clear = (event) => {
|
|
324
|
+
clear = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
259
325
|
event.preventDefault();
|
|
260
326
|
if (this.state.selected.length > 0) {
|
|
261
327
|
this.updateSelectedValue([]);
|
|
@@ -266,7 +332,7 @@ export default class Typeahead extends Component {
|
|
|
266
332
|
});
|
|
267
333
|
};
|
|
268
334
|
|
|
269
|
-
removeChip = (option) => {
|
|
335
|
+
removeChip = (option: TypeaheadOption<T>) => {
|
|
270
336
|
const { selected } = this.state;
|
|
271
337
|
|
|
272
338
|
if (selected.length > 0) {
|
|
@@ -274,8 +340,8 @@ export default class Typeahead extends Component {
|
|
|
274
340
|
}
|
|
275
341
|
};
|
|
276
342
|
|
|
277
|
-
renderChip = (option
|
|
278
|
-
const valid = this.props.validateChip(option);
|
|
343
|
+
renderChip = (option: TypeaheadOption<T>, idx: number): ReactNode => {
|
|
344
|
+
const valid = this.props.validateChip?.(option);
|
|
279
345
|
|
|
280
346
|
return (
|
|
281
347
|
<Chip
|
|
@@ -299,7 +365,10 @@ export default class Typeahead extends Component {
|
|
|
299
365
|
allowNew,
|
|
300
366
|
showNewEntry,
|
|
301
367
|
dropdownOpen,
|
|
302
|
-
}
|
|
368
|
+
}: Pick<TypeaheadProps<T>, 'footer' | 'options' | 'id' | 'allowNew' | 'showNewEntry'> &
|
|
369
|
+
Pick<TypeaheadState<T>, 'keyboardFocusedOptionIndex' | 'query'> & {
|
|
370
|
+
dropdownOpen?: boolean;
|
|
371
|
+
}) => {
|
|
303
372
|
const optionsToRender = [...options];
|
|
304
373
|
if (
|
|
305
374
|
allowNew &&
|
|
@@ -364,6 +433,7 @@ export default class Typeahead extends Component {
|
|
|
364
433
|
const menu = this.renderMenu({
|
|
365
434
|
footer,
|
|
366
435
|
options,
|
|
436
|
+
id,
|
|
367
437
|
keyboardFocusedOptionIndex,
|
|
368
438
|
query,
|
|
369
439
|
allowNew,
|
|
@@ -384,7 +454,7 @@ export default class Typeahead extends Component {
|
|
|
384
454
|
'typeahead--multiple': multiple,
|
|
385
455
|
open: dropdownOpen,
|
|
386
456
|
})}
|
|
387
|
-
onClick={
|
|
457
|
+
onClick={stopPropagation}
|
|
388
458
|
>
|
|
389
459
|
<div
|
|
390
460
|
className={classNames('form-group', {
|
|
@@ -409,6 +479,7 @@ export default class Typeahead extends Component {
|
|
|
409
479
|
selected,
|
|
410
480
|
maxHeight,
|
|
411
481
|
}}
|
|
482
|
+
id={id}
|
|
412
483
|
name={name}
|
|
413
484
|
value={query}
|
|
414
485
|
typeaheadId={id}
|
|
@@ -435,75 +506,3 @@ export default class Typeahead extends Component {
|
|
|
435
506
|
);
|
|
436
507
|
}
|
|
437
508
|
}
|
|
438
|
-
|
|
439
|
-
Typeahead.propTypes = {
|
|
440
|
-
id: PropTypes.string.isRequired,
|
|
441
|
-
name: PropTypes.string.isRequired,
|
|
442
|
-
options: PropTypes.arrayOf(
|
|
443
|
-
PropTypes.shape({
|
|
444
|
-
label: PropTypes.string.isRequired,
|
|
445
|
-
note: PropTypes.string,
|
|
446
|
-
secondary: PropTypes.string,
|
|
447
|
-
value: PropTypes.object,
|
|
448
|
-
}),
|
|
449
|
-
).isRequired,
|
|
450
|
-
initialValue: PropTypes.arrayOf(
|
|
451
|
-
PropTypes.shape({
|
|
452
|
-
label: PropTypes.string.isRequired,
|
|
453
|
-
note: PropTypes.string,
|
|
454
|
-
secondary: PropTypes.string,
|
|
455
|
-
}),
|
|
456
|
-
),
|
|
457
|
-
onChange: PropTypes.func.isRequired,
|
|
458
|
-
allowNew: PropTypes.bool,
|
|
459
|
-
autoFocus: PropTypes.bool,
|
|
460
|
-
clearable: PropTypes.bool,
|
|
461
|
-
multiple: PropTypes.bool,
|
|
462
|
-
showSuggestions: PropTypes.bool,
|
|
463
|
-
showNewEntry: PropTypes.bool,
|
|
464
|
-
searchDelay: PropTypes.number,
|
|
465
|
-
maxHeight: PropTypes.number,
|
|
466
|
-
minQueryLength: PropTypes.number,
|
|
467
|
-
addon: PropTypes.node,
|
|
468
|
-
placeholder: PropTypes.string,
|
|
469
|
-
alert: PropTypes.shape({
|
|
470
|
-
message: PropTypes.string.isRequired,
|
|
471
|
-
type: PropTypes.oneOf(['error', 'warning', 'neutral']).isRequired,
|
|
472
|
-
}),
|
|
473
|
-
footer: PropTypes.node,
|
|
474
|
-
validateChip: PropTypes.func,
|
|
475
|
-
onSearch: PropTypes.func,
|
|
476
|
-
onBlur: PropTypes.func,
|
|
477
|
-
onInputChange: PropTypes.func,
|
|
478
|
-
onFocus: PropTypes.func,
|
|
479
|
-
chipSeparators: PropTypes.arrayOf(PropTypes.string),
|
|
480
|
-
size: PropTypes.oneOf(['md', 'lg']),
|
|
481
|
-
inputAutoComplete: PropTypes.string,
|
|
482
|
-
autoFillOnBlur: PropTypes.bool,
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
Typeahead.defaultProps = {
|
|
486
|
-
allowNew: false,
|
|
487
|
-
autoFocus: false,
|
|
488
|
-
clearable: true,
|
|
489
|
-
multiple: false,
|
|
490
|
-
maxHeight: null,
|
|
491
|
-
showSuggestions: true,
|
|
492
|
-
showNewEntry: true,
|
|
493
|
-
searchDelay: SEARCH_DELAY,
|
|
494
|
-
minQueryLength: DEFAULT_MIN_QUERY_LENGTH,
|
|
495
|
-
addon: null,
|
|
496
|
-
placeholder: null,
|
|
497
|
-
alert: null,
|
|
498
|
-
footer: null,
|
|
499
|
-
size: Size.MEDIUM,
|
|
500
|
-
chipSeparators: [],
|
|
501
|
-
initialValue: [],
|
|
502
|
-
onSearch: null,
|
|
503
|
-
onBlur: null,
|
|
504
|
-
onInputChange: null,
|
|
505
|
-
onFocus: null,
|
|
506
|
-
validateChip: () => true,
|
|
507
|
-
inputAutoComplete: 'new-password',
|
|
508
|
-
autoFillOnBlur: true,
|
|
509
|
-
};
|
|
@@ -2,16 +2,42 @@
|
|
|
2
2
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
3
3
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
|
4
4
|
import classnames from 'classnames';
|
|
5
|
-
import
|
|
6
|
-
import { Component } from 'react';
|
|
5
|
+
import { Component, ReactNode } from 'react';
|
|
7
6
|
|
|
8
7
|
import { Input } from '../../inputs/Input';
|
|
8
|
+
import { TypeaheadOption, TypeaheadProps } from '../Typeahead';
|
|
9
9
|
|
|
10
10
|
const DEFAULT_INPUT_MIN_WIDTH = 10;
|
|
11
11
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
export type TypeaheadInputProps<T> = {
|
|
13
|
+
typeaheadId: string;
|
|
14
|
+
value: string;
|
|
15
|
+
selected: TypeaheadOption<T>[];
|
|
16
|
+
optionsShown?: boolean;
|
|
17
|
+
autoComplete: string;
|
|
18
|
+
onChange: React.ChangeEventHandler<HTMLInputElement>;
|
|
19
|
+
onKeyDown: React.KeyboardEventHandler<HTMLInputElement>;
|
|
20
|
+
onFocus: () => void;
|
|
21
|
+
onPaste: React.ClipboardEventHandler<HTMLInputElement>;
|
|
22
|
+
renderChip: (chip: TypeaheadOption<T>, index: number) => ReactNode;
|
|
23
|
+
} & Pick<
|
|
24
|
+
TypeaheadProps<T>,
|
|
25
|
+
'id' | 'name' | 'autoFocus' | 'multiple' | 'placeholder' | 'maxHeight' | 'onFocus'
|
|
26
|
+
>;
|
|
27
|
+
|
|
28
|
+
type TypeaheadInputState = {
|
|
29
|
+
inputWidth: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default class TypeaheadInput<T> extends Component<
|
|
33
|
+
TypeaheadInputProps<T>,
|
|
34
|
+
TypeaheadInputState
|
|
35
|
+
> {
|
|
36
|
+
inputRef: HTMLInputElement | null = null;
|
|
37
|
+
sizerRef: HTMLDivElement | null = null;
|
|
38
|
+
|
|
39
|
+
constructor(props: TypeaheadInputProps<T>) {
|
|
40
|
+
super(props);
|
|
15
41
|
this.state = {
|
|
16
42
|
inputWidth: DEFAULT_INPUT_MIN_WIDTH,
|
|
17
43
|
};
|
|
@@ -20,11 +46,11 @@ export default class TypeaheadInput extends Component {
|
|
|
20
46
|
componentDidMount() {
|
|
21
47
|
const { autoFocus } = this.props;
|
|
22
48
|
if (autoFocus) {
|
|
23
|
-
this.inputRef
|
|
49
|
+
this.inputRef?.focus();
|
|
24
50
|
}
|
|
25
51
|
}
|
|
26
52
|
|
|
27
|
-
componentDidUpdate(previousProps) {
|
|
53
|
+
componentDidUpdate(previousProps: TypeaheadInputProps<T>) {
|
|
28
54
|
if (previousProps.value !== this.props.value && this.props.multiple) {
|
|
29
55
|
this.recalculateWidth();
|
|
30
56
|
}
|
|
@@ -33,7 +59,9 @@ export default class TypeaheadInput extends Component {
|
|
|
33
59
|
recalculateWidth = () => {
|
|
34
60
|
requestAnimationFrame(() => {
|
|
35
61
|
this.setState({
|
|
36
|
-
inputWidth:
|
|
62
|
+
inputWidth: this.sizerRef
|
|
63
|
+
? Math.max(DEFAULT_INPUT_MIN_WIDTH, this.sizerRef.scrollWidth + 10)
|
|
64
|
+
: DEFAULT_INPUT_MIN_WIDTH,
|
|
37
65
|
});
|
|
38
66
|
});
|
|
39
67
|
};
|
|
@@ -41,12 +69,12 @@ export default class TypeaheadInput extends Component {
|
|
|
41
69
|
renderInput = () => {
|
|
42
70
|
const {
|
|
43
71
|
typeaheadId,
|
|
44
|
-
autoFocus,
|
|
72
|
+
autoFocus = false,
|
|
45
73
|
multiple,
|
|
46
74
|
name,
|
|
47
|
-
optionsShown,
|
|
48
|
-
placeholder,
|
|
49
|
-
selected,
|
|
75
|
+
optionsShown = false,
|
|
76
|
+
placeholder = '',
|
|
77
|
+
selected = [],
|
|
50
78
|
value,
|
|
51
79
|
onChange,
|
|
52
80
|
onKeyDown,
|
|
@@ -85,14 +113,14 @@ export default class TypeaheadInput extends Component {
|
|
|
85
113
|
};
|
|
86
114
|
|
|
87
115
|
render() {
|
|
88
|
-
const { multiple, selected, value, maxHeight, renderChip } = this.props;
|
|
116
|
+
const { multiple, selected = [], value, maxHeight = null, renderChip } = this.props;
|
|
89
117
|
|
|
90
118
|
return multiple ? (
|
|
91
119
|
<div
|
|
92
120
|
className="form-control typeahead__input-container"
|
|
93
|
-
style={maxHeight
|
|
121
|
+
style={{ maxHeight: maxHeight ?? undefined }}
|
|
94
122
|
onClick={() => {
|
|
95
|
-
this.inputRef
|
|
123
|
+
this.inputRef?.focus();
|
|
96
124
|
}}
|
|
97
125
|
>
|
|
98
126
|
<div className="typeahead__input-wrapper">
|
|
@@ -115,30 +143,3 @@ export default class TypeaheadInput extends Component {
|
|
|
115
143
|
);
|
|
116
144
|
}
|
|
117
145
|
}
|
|
118
|
-
|
|
119
|
-
TypeaheadInput.propTypes = {
|
|
120
|
-
typeaheadId: PropTypes.string.isRequired,
|
|
121
|
-
name: PropTypes.string.isRequired,
|
|
122
|
-
autoFocus: PropTypes.bool,
|
|
123
|
-
multiple: PropTypes.bool.isRequired,
|
|
124
|
-
value: PropTypes.string.isRequired,
|
|
125
|
-
selected: PropTypes.arrayOf(PropTypes.object),
|
|
126
|
-
placeholder: PropTypes.string,
|
|
127
|
-
optionsShown: PropTypes.bool,
|
|
128
|
-
maxHeight: PropTypes.number,
|
|
129
|
-
autoComplete: PropTypes.string.isRequired,
|
|
130
|
-
|
|
131
|
-
onChange: PropTypes.func.isRequired,
|
|
132
|
-
renderChip: PropTypes.func.isRequired,
|
|
133
|
-
onKeyDown: PropTypes.func.isRequired,
|
|
134
|
-
onFocus: PropTypes.func.isRequired,
|
|
135
|
-
onPaste: PropTypes.func.isRequired,
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
TypeaheadInput.defaultProps = {
|
|
139
|
-
autoFocus: false,
|
|
140
|
-
maxHeight: null,
|
|
141
|
-
placeholder: '',
|
|
142
|
-
optionsShown: false,
|
|
143
|
-
selected: [],
|
|
144
|
-
};
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
/* eslint-disable jsx-a11y/anchor-is-valid */
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import PropTypes from 'prop-types';
|
|
4
3
|
|
|
4
|
+
import { TypeaheadOption } from '../Typeahead';
|
|
5
5
|
import highlight from '../util/highlight';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export type TypeaheadOptionProps<T> = {
|
|
8
|
+
option: TypeaheadOption<T>;
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
onClick?: React.MouseEventHandler;
|
|
11
|
+
query?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const Option = <T,>(props: TypeaheadOptionProps<T>) => {
|
|
15
|
+
const { option, selected = false, onClick = () => {}, query = '' } = props;
|
|
9
16
|
const { label, note, secondary } = option;
|
|
10
17
|
|
|
11
18
|
return (
|
|
@@ -27,21 +34,4 @@ const Option = (props) => {
|
|
|
27
34
|
);
|
|
28
35
|
};
|
|
29
36
|
|
|
30
|
-
Option.propTypes = {
|
|
31
|
-
option: PropTypes.shape({
|
|
32
|
-
label: PropTypes.string.isRequired,
|
|
33
|
-
note: PropTypes.string,
|
|
34
|
-
secondary: PropTypes.string,
|
|
35
|
-
}).isRequired,
|
|
36
|
-
query: PropTypes.string,
|
|
37
|
-
selected: PropTypes.bool,
|
|
38
|
-
onClick: PropTypes.func,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
Option.defaultProps = {
|
|
42
|
-
selected: false,
|
|
43
|
-
query: '',
|
|
44
|
-
onClick: () => {},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
37
|
export default Option;
|
package/src/typeahead/index.js
DELETED