@transferwise/components 46.11.0 → 46.12.0
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/i18n/zh-HK.json +2 -2
- package/build/index.esm.js +49 -147
- package/build/index.esm.js.map +1 -1
- package/build/index.js +49 -147
- package/build/index.js.map +1 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/typeahead/Typeahead.d.ts +95 -57
- package/build/types/typeahead/Typeahead.d.ts.map +1 -1
- package/build/types/typeahead/index.d.ts +2 -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/build/types/typeahead/util/highlight.d.ts +2 -1
- package/build/types/typeahead/util/highlight.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/i18n/zh-HK.json +2 -2
- package/src/index.ts +1 -0
- package/src/typeahead/{Typeahead.story.js → Typeahead.story.tsx} +8 -7
- package/src/typeahead/{Typeahead.js → Typeahead.tsx} +110 -111
- package/src/typeahead/index.ts +2 -0
- package/src/typeahead/typeaheadInput/TypeaheadInput.spec.js +1 -0
- package/src/typeahead/typeaheadInput/{TypeaheadInput.js → TypeaheadInput.tsx} +35 -46
- package/src/typeahead/typeaheadOption/{TypeaheadOption.js → TypeaheadOption.tsx} +10 -20
- package/src/typeahead/util/{highlight.js → highlight.tsx} +1 -1
- package/src/typeahead/index.js +0 -3
|
@@ -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,10 +25,80 @@ 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 default 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
|
+
allowNew: false,
|
|
84
|
+
autoFillOnBlur: true,
|
|
85
|
+
autoFocus: false,
|
|
86
|
+
chipSeparators: [],
|
|
87
|
+
clearable: true,
|
|
88
|
+
initialValue: [],
|
|
89
|
+
inputAutoComplete: 'new-password',
|
|
90
|
+
minQueryLength: DEFAULT_MIN_QUERY_LENGTH,
|
|
91
|
+
multiple: false,
|
|
92
|
+
searchDelay: SEARCH_DELAY,
|
|
93
|
+
showSuggestions: true,
|
|
94
|
+
showNewEntry: true,
|
|
95
|
+
size: Size.MEDIUM,
|
|
96
|
+
validateChip: () => true,
|
|
97
|
+
} satisfies Partial<TypeaheadProps<unknown>>;
|
|
98
|
+
|
|
99
|
+
constructor(props: TypeaheadProps<T>) {
|
|
28
100
|
super(props);
|
|
29
|
-
const { searchDelay, initialValue, multiple } = props;
|
|
101
|
+
const { searchDelay, initialValue, multiple } = this.props;
|
|
30
102
|
this.handleSearchDebounced = debounce(this.handleSearch, searchDelay);
|
|
31
103
|
const initialQuery = !multiple && initialValue.length > 0 ? initialValue[0].label : '';
|
|
32
104
|
this.state = {
|
|
@@ -34,10 +106,14 @@ export default class Typeahead extends Component {
|
|
|
34
106
|
errorState: false,
|
|
35
107
|
query: initialQuery,
|
|
36
108
|
keyboardFocusedOptionIndex: null,
|
|
109
|
+
optionsShown: false,
|
|
110
|
+
isFocused: false,
|
|
37
111
|
};
|
|
38
112
|
}
|
|
39
113
|
|
|
40
|
-
|
|
114
|
+
handleSearchDebounced: DebouncedFunc<Typeahead<T>['handleSearch']>;
|
|
115
|
+
|
|
116
|
+
UNSAFE_componentWillReceiveProps(nextProps: TypeaheadProps<T>) {
|
|
41
117
|
if (nextProps.multiple !== this.props.multiple) {
|
|
42
118
|
this.setState((previousState) => {
|
|
43
119
|
const { selected } = previousState;
|
|
@@ -48,6 +124,7 @@ export default class Typeahead extends Component {
|
|
|
48
124
|
};
|
|
49
125
|
}
|
|
50
126
|
return {
|
|
127
|
+
selected: previousState.selected,
|
|
51
128
|
query: '',
|
|
52
129
|
};
|
|
53
130
|
});
|
|
@@ -59,20 +136,16 @@ export default class Typeahead extends Component {
|
|
|
59
136
|
}
|
|
60
137
|
|
|
61
138
|
handleOnFocus = () => {
|
|
62
|
-
const { onFocus } = this.props;
|
|
63
139
|
this.showMenu();
|
|
64
|
-
|
|
65
|
-
if (onFocus) {
|
|
66
|
-
this.props.onFocus();
|
|
67
|
-
}
|
|
140
|
+
this.props.onFocus?.();
|
|
68
141
|
};
|
|
69
142
|
|
|
70
|
-
onOptionSelected = (event, item) => {
|
|
143
|
+
onOptionSelected = (event: React.MouseEvent, item: TypeaheadOption<T>) => {
|
|
71
144
|
event.preventDefault();
|
|
72
145
|
this.selectItem(item);
|
|
73
146
|
};
|
|
74
147
|
|
|
75
|
-
handleOnChange = (event) => {
|
|
148
|
+
handleOnChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
76
149
|
const { optionsShown, selected } = this.state;
|
|
77
150
|
const { multiple, onInputChange } = this.props;
|
|
78
151
|
|
|
@@ -94,7 +167,7 @@ export default class Typeahead extends Component {
|
|
|
94
167
|
});
|
|
95
168
|
};
|
|
96
169
|
|
|
97
|
-
handleOnPaste = (event) => {
|
|
170
|
+
handleOnPaste: React.ClipboardEventHandler<HTMLInputElement> = (event) => {
|
|
98
171
|
const { allowNew, multiple, chipSeparators } = this.props;
|
|
99
172
|
const { selected } = this.state;
|
|
100
173
|
|
|
@@ -113,7 +186,7 @@ export default class Typeahead extends Component {
|
|
|
113
186
|
}
|
|
114
187
|
};
|
|
115
188
|
|
|
116
|
-
handleOnKeyDown = (event) => {
|
|
189
|
+
handleOnKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
|
|
117
190
|
const { showSuggestions, allowNew, multiple, chipSeparators, options } = this.props;
|
|
118
191
|
const { keyboardFocusedOptionIndex, query, selected } = this.state;
|
|
119
192
|
const chipsMode = !showSuggestions && allowNew && multiple;
|
|
@@ -133,7 +206,7 @@ export default class Typeahead extends Component {
|
|
|
133
206
|
break;
|
|
134
207
|
case 'Enter':
|
|
135
208
|
event.preventDefault();
|
|
136
|
-
if (options[keyboardFocusedOptionIndex]) {
|
|
209
|
+
if (keyboardFocusedOptionIndex != null && options[keyboardFocusedOptionIndex]) {
|
|
137
210
|
this.selectItem(options[keyboardFocusedOptionIndex]);
|
|
138
211
|
} else if (allowNew && query.trim()) {
|
|
139
212
|
this.selectItem({ label: query });
|
|
@@ -150,7 +223,7 @@ export default class Typeahead extends Component {
|
|
|
150
223
|
}
|
|
151
224
|
};
|
|
152
225
|
|
|
153
|
-
moveFocusedOption(offset) {
|
|
226
|
+
moveFocusedOption(offset: number) {
|
|
154
227
|
this.setState((previousState) => {
|
|
155
228
|
const { keyboardFocusedOptionIndex } = previousState;
|
|
156
229
|
const { options } = this.props;
|
|
@@ -164,7 +237,7 @@ export default class Typeahead extends Component {
|
|
|
164
237
|
});
|
|
165
238
|
}
|
|
166
239
|
|
|
167
|
-
selectItem = (item) => {
|
|
240
|
+
selectItem = (item: TypeaheadOption<T>) => {
|
|
168
241
|
const { multiple } = this.props;
|
|
169
242
|
let selected = [...this.state.selected];
|
|
170
243
|
let query;
|
|
@@ -183,15 +256,7 @@ export default class Typeahead extends Component {
|
|
|
183
256
|
});
|
|
184
257
|
};
|
|
185
258
|
|
|
186
|
-
|
|
187
|
-
event.stopPropagation();
|
|
188
|
-
event.preventDefault();
|
|
189
|
-
if (event.nativeEvent && event.nativeEvent.stopImmediatePropagation) {
|
|
190
|
-
event.nativeEvent.stopImmediatePropagation();
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
handleSearch = (query) => {
|
|
259
|
+
handleSearch = (query: string) => {
|
|
195
260
|
const { onSearch } = this.props;
|
|
196
261
|
if (onSearch) {
|
|
197
262
|
onSearch(query);
|
|
@@ -246,7 +311,7 @@ export default class Typeahead extends Component {
|
|
|
246
311
|
);
|
|
247
312
|
};
|
|
248
313
|
|
|
249
|
-
updateSelectedValue = (selected) => {
|
|
314
|
+
updateSelectedValue = (selected: TypeaheadOption<T>[]) => {
|
|
250
315
|
const { onChange, validateChip } = this.props;
|
|
251
316
|
|
|
252
317
|
const errorState = selected.some((chip) => !validateChip(chip));
|
|
@@ -255,7 +320,7 @@ export default class Typeahead extends Component {
|
|
|
255
320
|
});
|
|
256
321
|
};
|
|
257
322
|
|
|
258
|
-
clear = (event) => {
|
|
323
|
+
clear = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
259
324
|
event.preventDefault();
|
|
260
325
|
if (this.state.selected.length > 0) {
|
|
261
326
|
this.updateSelectedValue([]);
|
|
@@ -266,7 +331,7 @@ export default class Typeahead extends Component {
|
|
|
266
331
|
});
|
|
267
332
|
};
|
|
268
333
|
|
|
269
|
-
removeChip = (option) => {
|
|
334
|
+
removeChip = (option: TypeaheadOption<T>) => {
|
|
270
335
|
const { selected } = this.state;
|
|
271
336
|
|
|
272
337
|
if (selected.length > 0) {
|
|
@@ -274,8 +339,8 @@ export default class Typeahead extends Component {
|
|
|
274
339
|
}
|
|
275
340
|
};
|
|
276
341
|
|
|
277
|
-
renderChip = (option
|
|
278
|
-
const valid = this.props.validateChip(option);
|
|
342
|
+
renderChip = (option: TypeaheadOption<T>, idx: number): ReactNode => {
|
|
343
|
+
const valid = this.props.validateChip?.(option);
|
|
279
344
|
|
|
280
345
|
return (
|
|
281
346
|
<Chip
|
|
@@ -299,7 +364,10 @@ export default class Typeahead extends Component {
|
|
|
299
364
|
allowNew,
|
|
300
365
|
showNewEntry,
|
|
301
366
|
dropdownOpen,
|
|
302
|
-
}
|
|
367
|
+
}: Pick<TypeaheadProps<T>, 'footer' | 'options' | 'id' | 'allowNew' | 'showNewEntry'> &
|
|
368
|
+
Pick<TypeaheadState<T>, 'keyboardFocusedOptionIndex' | 'query'> & {
|
|
369
|
+
dropdownOpen: boolean;
|
|
370
|
+
}) => {
|
|
303
371
|
const optionsToRender = [...options];
|
|
304
372
|
if (
|
|
305
373
|
allowNew &&
|
|
@@ -364,6 +432,7 @@ export default class Typeahead extends Component {
|
|
|
364
432
|
const menu = this.renderMenu({
|
|
365
433
|
footer,
|
|
366
434
|
options,
|
|
435
|
+
id,
|
|
367
436
|
keyboardFocusedOptionIndex,
|
|
368
437
|
query,
|
|
369
438
|
allowNew,
|
|
@@ -371,10 +440,11 @@ export default class Typeahead extends Component {
|
|
|
371
440
|
dropdownOpen,
|
|
372
441
|
});
|
|
373
442
|
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
const
|
|
443
|
+
const alertType = alert?.type ?? Sentiment.NEUTRAL;
|
|
444
|
+
const hasError = errorState || (alert && alertType === Sentiment.ERROR);
|
|
445
|
+
const displayAlert = (!errorState && alert) || (alert && alertType === Sentiment.ERROR);
|
|
446
|
+
const hasWarning = displayAlert && alertType === Sentiment.WARNING;
|
|
447
|
+
const hasInfo = displayAlert && alertType === Sentiment.NEUTRAL;
|
|
378
448
|
return (
|
|
379
449
|
<div
|
|
380
450
|
id={id}
|
|
@@ -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,39 @@
|
|
|
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, createRef, 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<TypeaheadProps<T>, 'id' | 'name' | 'autoFocus' | 'multiple' | 'placeholder' | 'maxHeight'>;
|
|
24
|
+
|
|
25
|
+
type TypeaheadInputState = {
|
|
26
|
+
inputWidth: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default class TypeaheadInput<T> extends Component<
|
|
30
|
+
TypeaheadInputProps<T>,
|
|
31
|
+
TypeaheadInputState
|
|
32
|
+
> {
|
|
33
|
+
inputRef = createRef<HTMLInputElement>();
|
|
34
|
+
sizerRef = createRef<HTMLDivElement>();
|
|
35
|
+
|
|
36
|
+
constructor(props: TypeaheadInputProps<T>) {
|
|
37
|
+
super(props);
|
|
15
38
|
this.state = {
|
|
16
39
|
inputWidth: DEFAULT_INPUT_MIN_WIDTH,
|
|
17
40
|
};
|
|
@@ -20,11 +43,11 @@ export default class TypeaheadInput extends Component {
|
|
|
20
43
|
componentDidMount() {
|
|
21
44
|
const { autoFocus } = this.props;
|
|
22
45
|
if (autoFocus) {
|
|
23
|
-
this.inputRef.focus();
|
|
46
|
+
this.inputRef.current?.focus();
|
|
24
47
|
}
|
|
25
48
|
}
|
|
26
49
|
|
|
27
|
-
componentDidUpdate(previousProps) {
|
|
50
|
+
componentDidUpdate(previousProps: TypeaheadInputProps<T>) {
|
|
28
51
|
if (previousProps.value !== this.props.value && this.props.multiple) {
|
|
29
52
|
this.recalculateWidth();
|
|
30
53
|
}
|
|
@@ -33,7 +56,7 @@ export default class TypeaheadInput extends Component {
|
|
|
33
56
|
recalculateWidth = () => {
|
|
34
57
|
requestAnimationFrame(() => {
|
|
35
58
|
this.setState({
|
|
36
|
-
inputWidth: Math.max(DEFAULT_INPUT_MIN_WIDTH, this.sizerRef.scrollWidth + 10),
|
|
59
|
+
inputWidth: Math.max(DEFAULT_INPUT_MIN_WIDTH, this.sizerRef.current?.scrollWidth ?? 0 + 10),
|
|
37
60
|
});
|
|
38
61
|
});
|
|
39
62
|
};
|
|
@@ -59,9 +82,7 @@ export default class TypeaheadInput extends Component {
|
|
|
59
82
|
const hasPlaceholder = !multiple || selected.length === 0;
|
|
60
83
|
return (
|
|
61
84
|
<Input
|
|
62
|
-
ref={
|
|
63
|
-
this.inputRef = reference;
|
|
64
|
-
}}
|
|
85
|
+
ref={this.inputRef}
|
|
65
86
|
className={classnames(multiple && 'typeahead__input')}
|
|
66
87
|
name={name}
|
|
67
88
|
id={`input-${typeaheadId}`}
|
|
@@ -90,9 +111,9 @@ export default class TypeaheadInput extends Component {
|
|
|
90
111
|
return multiple ? (
|
|
91
112
|
<div
|
|
92
113
|
className="form-control typeahead__input-container"
|
|
93
|
-
style={
|
|
114
|
+
style={{ maxHeight }}
|
|
94
115
|
onClick={() => {
|
|
95
|
-
this.inputRef.focus();
|
|
116
|
+
this.inputRef.current?.focus();
|
|
96
117
|
}}
|
|
97
118
|
>
|
|
98
119
|
<div className="typeahead__input-wrapper">
|
|
@@ -101,12 +122,7 @@ export default class TypeaheadInput extends Component {
|
|
|
101
122
|
{this.renderInput()}
|
|
102
123
|
<div className="typeahead__input-aligner" />
|
|
103
124
|
</div>
|
|
104
|
-
<div
|
|
105
|
-
ref={(reference) => {
|
|
106
|
-
this.sizerRef = reference;
|
|
107
|
-
}}
|
|
108
|
-
className="sizer form-control typeahead__input"
|
|
109
|
-
>
|
|
125
|
+
<div ref={this.sizerRef} className="sizer form-control typeahead__input">
|
|
110
126
|
{value}
|
|
111
127
|
</div>
|
|
112
128
|
</div>
|
|
@@ -115,30 +131,3 @@ export default class TypeaheadInput extends Component {
|
|
|
115
131
|
);
|
|
116
132
|
}
|
|
117
133
|
}
|
|
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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export default function highlight(value, query) {
|
|
1
|
+
export default function highlight(value: string, query: string) {
|
|
2
2
|
if (value && query) {
|
|
3
3
|
const highlightStart = value.toUpperCase().indexOf(query.trim().toUpperCase());
|
|
4
4
|
const highlightEnd = highlightStart + query.trim().length;
|
package/src/typeahead/index.js
DELETED