@transferwise/components 0.0.0-experimental-ddeb968 → 0.0.0-experimental-80dda9c
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/README.md +8 -0
- package/build/common/panel/Panel.js +2 -1
- package/build/common/panel/Panel.js.map +1 -1
- package/build/common/panel/Panel.mjs +2 -1
- package/build/common/panel/Panel.mjs.map +1 -1
- package/build/select/Select.js +125 -41
- package/build/select/Select.js.map +1 -1
- package/build/select/Select.mjs +121 -41
- package/build/select/Select.mjs.map +1 -1
- package/build/select/index.js +8 -0
- package/build/select/index.js.map +1 -0
- package/build/select/index.mjs +6 -0
- package/build/select/index.mjs.map +1 -0
- package/build/snackbar/Snackbar.js +2 -1
- package/build/snackbar/Snackbar.js.map +1 -1
- package/build/snackbar/Snackbar.mjs +2 -1
- package/build/snackbar/Snackbar.mjs.map +1 -1
- package/build/types/index.d.ts +0 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/select/Select.d.ts +94 -64
- package/build/types/select/Select.d.ts.map +1 -1
- package/build/types/select/index.d.ts +2 -2
- package/build/types/select/index.d.ts.map +1 -1
- package/build/types/select/option/index.d.ts +1 -1
- package/build/types/select/option/index.d.ts.map +1 -1
- package/build/types/select/searchBox/index.d.ts +1 -1
- package/build/types/select/searchBox/index.d.ts.map +1 -1
- package/build/types/snackbar/Snackbar.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/index.ts +0 -6
- package/src/select/{Select.tsx → Select.js} +155 -137
- package/src/select/{Select.spec.tsx → Select.spec.js} +6 -8
- package/src/select/index.js +3 -0
- package/src/snackbar/Snackbar.tsx +7 -1
- package/src/select/index.ts +0 -7
- /package/src/select/option/{index.ts → index.js} +0 -0
- /package/src/select/searchBox/{SearchBox.spec.tsx → SearchBox.spec.js} +0 -0
- /package/src/select/searchBox/{index.ts → index.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transferwise/components",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-80dda9c",
|
|
4
4
|
"description": "Neptune React components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
"rollup": "^4.18.1",
|
|
93
93
|
"rollup-preserve-directives": "^1.1.1",
|
|
94
94
|
"storybook": "^8.2.2",
|
|
95
|
-
"@transferwise/less-config": "3.1.0",
|
|
96
95
|
"@transferwise/neptune-css": "14.13.1",
|
|
97
|
-
"@wise/components-theming": "1.5.0"
|
|
96
|
+
"@wise/components-theming": "1.5.0",
|
|
97
|
+
"@transferwise/less-config": "3.1.0"
|
|
98
98
|
},
|
|
99
99
|
"peerDependencies": {
|
|
100
100
|
"@transferwise/icons": "^3.7.0",
|
|
@@ -123,6 +123,7 @@
|
|
|
123
123
|
"lodash.debounce": "^4.0.8",
|
|
124
124
|
"lodash.topairs": "^4.3.0",
|
|
125
125
|
"merge-props": "^6.0.0",
|
|
126
|
+
"prop-types": "^15.8.1",
|
|
126
127
|
"react-popper": "^2.3.0",
|
|
127
128
|
"react-transition-group": "^4.4.5",
|
|
128
129
|
"virtua": "^0.33.3"
|
|
@@ -141,6 +142,7 @@
|
|
|
141
142
|
"build:clean": "rm -rf lib build",
|
|
142
143
|
"build:crowdin-source-file": "formatjs extract 'src/**/*.messages.+(js|ts|tsx)' --out-file src/i18n/en.json --format simple && prettier --write src/i18n/*.json",
|
|
143
144
|
"build:js": "rollup --config --sourcemap",
|
|
145
|
+
"build:generate-types": "node ./scripts/generate-type-declarations.js",
|
|
144
146
|
"build:copy-files": "cpx 'src/**/!(db)/*.{json,svg}' build",
|
|
145
147
|
"build:copy-css": "cpx 'src/main.css' build/ & cpx 'src/**/*.css' build/styles/",
|
|
146
148
|
"build:copy-lang": "cpx 'src/i18n/*.json' build/i18n && cpx 'src/i18n/index.js' build/i18n",
|
package/src/index.ts
CHANGED
|
@@ -67,12 +67,6 @@ export type { RadioProps } from './radio';
|
|
|
67
67
|
export type { RadioGroupProps, RadioGroupRadio } from './radioGroup';
|
|
68
68
|
export type { RadioOptionProps } from './radioOption';
|
|
69
69
|
export type { SegmentedControlProps } from './segmentedControl';
|
|
70
|
-
export type {
|
|
71
|
-
SelectItem,
|
|
72
|
-
SelectItemWithPlaceholder,
|
|
73
|
-
SelectOptionItem,
|
|
74
|
-
SelectProps,
|
|
75
|
-
} from './select';
|
|
76
70
|
export type { SlidingPanelProps } from './slidingPanel';
|
|
77
71
|
export type { SnackbarProps } from './snackbar/Snackbar';
|
|
78
72
|
export type { SnackbarContextType } from './snackbar/SnackbarContext';
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useTheme } from '@wise/components-theming';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
3
4
|
import { useState, useEffect, useRef, useMemo, useId } from 'react';
|
|
4
5
|
import { useIntl } from 'react-intl';
|
|
5
6
|
|
|
6
|
-
import Button
|
|
7
|
+
import Button from '../button';
|
|
7
8
|
import Chevron from '../chevron';
|
|
8
|
-
import { Position
|
|
9
|
+
import { Position } from '../common';
|
|
9
10
|
import BottomSheet from '../common/bottomSheet';
|
|
10
11
|
import { stopPropagation } from '../common/domHelpers';
|
|
11
12
|
import { useLayout } from '../common/hooks';
|
|
@@ -20,56 +21,35 @@ import SearchBox from './searchBox';
|
|
|
20
21
|
const DEFAULT_SEARCH_VALUE = '';
|
|
21
22
|
const DEFAULT_OPTIONS_PAGE_SIZE = 1000;
|
|
22
23
|
|
|
23
|
-
const includesString = (string1
|
|
24
|
-
string1.toLowerCase().includes(string2.toLowerCase());
|
|
24
|
+
const includesString = (string1, string2) => string1.toLowerCase().includes(string2.toLowerCase());
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
value: any;
|
|
28
|
-
label?: React.ReactNode;
|
|
29
|
-
icon?: React.ReactNode;
|
|
30
|
-
currency?: string;
|
|
31
|
-
note?: React.ReactNode;
|
|
32
|
-
secondary?: React.ReactNode;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface SelectItem extends Partial<SelectOptionItem> {
|
|
36
|
-
header?: React.ReactNode;
|
|
37
|
-
separator?: boolean;
|
|
38
|
-
disabled?: boolean;
|
|
39
|
-
searchStrings?: string[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface SelectItemWithPlaceholder extends SelectItem {
|
|
43
|
-
placeholder?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function defaultFilterFunction(option: SelectItemWithPlaceholder, searchValue: string) {
|
|
26
|
+
function defaultFilterFunction(option, searchValue) {
|
|
47
27
|
if (isPlaceholderOption(option)) {
|
|
48
28
|
return true;
|
|
49
29
|
}
|
|
50
30
|
const { label, note, secondary, currency, searchStrings } = option;
|
|
51
31
|
return (
|
|
52
|
-
(
|
|
53
|
-
(
|
|
54
|
-
(
|
|
32
|
+
(!!label && includesString(label, searchValue)) ||
|
|
33
|
+
(!!note && includesString(note, searchValue)) ||
|
|
34
|
+
(!!secondary && includesString(secondary, searchValue)) ||
|
|
55
35
|
(!!currency && includesString(currency, searchValue)) ||
|
|
56
36
|
(!!searchStrings && searchStrings.some((string) => includesString(string, searchValue)))
|
|
57
37
|
);
|
|
58
38
|
}
|
|
59
39
|
|
|
60
|
-
function isActionableOption(option
|
|
40
|
+
function isActionableOption(option) {
|
|
61
41
|
return !option.header && !option.separator && !option.disabled;
|
|
62
42
|
}
|
|
63
43
|
|
|
64
|
-
function isHeaderOption(option
|
|
44
|
+
function isHeaderOption(option) {
|
|
65
45
|
return option != null && 'header' in option;
|
|
66
46
|
}
|
|
67
47
|
|
|
68
|
-
function isSeparatorOption(option
|
|
48
|
+
function isSeparatorOption(option) {
|
|
69
49
|
return option != null && 'separator' in option;
|
|
70
50
|
}
|
|
71
51
|
|
|
72
|
-
function clamp(from
|
|
52
|
+
function clamp(from, to, value) {
|
|
73
53
|
return Math.max(Math.min(to, value), from);
|
|
74
54
|
}
|
|
75
55
|
|
|
@@ -78,62 +58,24 @@ function clamp(from: number, to: number, value: number) {
|
|
|
78
58
|
*/
|
|
79
59
|
const DEFAULT_SELECTED_OPTION = null;
|
|
80
60
|
|
|
81
|
-
function isPlaceholderOption(option
|
|
61
|
+
function isPlaceholderOption(option) {
|
|
82
62
|
return option === DEFAULT_SELECTED_OPTION || 'placeholder' in option;
|
|
83
63
|
}
|
|
84
64
|
|
|
85
|
-
function isSearchableOption(option
|
|
65
|
+
function isSearchableOption(option) {
|
|
86
66
|
return !isHeaderOption(option) && !isSeparatorOption(option) && !isPlaceholderOption(option);
|
|
87
67
|
}
|
|
88
68
|
|
|
89
|
-
const getUniqueIdForOption = (parentId
|
|
69
|
+
const getUniqueIdForOption = (parentId = '', option) => {
|
|
90
70
|
if (option == null) {
|
|
91
71
|
return undefined;
|
|
92
72
|
}
|
|
93
73
|
|
|
94
|
-
|
|
95
|
-
const uniqueOptionId =
|
|
96
|
-
option.value || (typeof option.label === 'string' ? option.label.replace(/\s/g, '') : '');
|
|
74
|
+
const uniqueOptionId = option.value || (option.label?.replace(/\s/g, '') ?? '');
|
|
97
75
|
|
|
98
|
-
return `option-${parentId
|
|
76
|
+
return `option-${parentId}-${uniqueOptionId}`;
|
|
99
77
|
};
|
|
100
78
|
|
|
101
|
-
export interface SelectProps {
|
|
102
|
-
placeholder?: string;
|
|
103
|
-
id?: string;
|
|
104
|
-
required?: boolean;
|
|
105
|
-
disabled?: boolean;
|
|
106
|
-
inverse?: boolean;
|
|
107
|
-
dropdownRight?: `${Size.EXTRA_SMALL | Size.SMALL | Size.MEDIUM | Size.LARGE | Size.EXTRA_LARGE}`;
|
|
108
|
-
dropdownWidth?: `${Size.SMALL | Size.MEDIUM | Size.LARGE}`;
|
|
109
|
-
size?: `${Size.SMALL | Size.MEDIUM | Size.LARGE}`;
|
|
110
|
-
block?: boolean;
|
|
111
|
-
selected?: SelectOptionItem;
|
|
112
|
-
/**
|
|
113
|
-
* Search toggle
|
|
114
|
-
* if `true` default search functionality being enabled (not case sensitive search in option labels & currency props)
|
|
115
|
-
* if `function` you can define your own search function to implement custom search experience. This search function used while filtering the options array.
|
|
116
|
-
*/
|
|
117
|
-
search?: boolean | ((option: SelectItemWithPlaceholder, searchValue: string) => boolean);
|
|
118
|
-
options: SelectItem[];
|
|
119
|
-
searchValue?: string;
|
|
120
|
-
searchPlaceholder?: string;
|
|
121
|
-
classNames?: Record<string, string>;
|
|
122
|
-
dropdownUp?: boolean;
|
|
123
|
-
buttonProps?: Extract<ButtonProps, { as?: 'button' }>;
|
|
124
|
-
dropdownProps?: React.ComponentPropsWithoutRef<'ul'>;
|
|
125
|
-
onChange: (value: SelectItem | typeof DEFAULT_SELECTED_OPTION) => void;
|
|
126
|
-
onFocus?: React.FocusEventHandler<HTMLDivElement>;
|
|
127
|
-
onBlur?: React.FocusEventHandler<HTMLDivElement>;
|
|
128
|
-
/**
|
|
129
|
-
* To have full control of your search value and response use `onSearchChange` function combined with `searchValue` and custom filtering on the options array.
|
|
130
|
-
* DO NOT USE TOGETHER WITH `search` PROPERTY
|
|
131
|
-
*/
|
|
132
|
-
onSearchChange?: (value: string) => void;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const defaultClassNames = {};
|
|
136
|
-
|
|
137
79
|
/**
|
|
138
80
|
* @deprecated Use `SelectInput` instead (https://neptune.wise.design/blog/2023-11-28-adopting-our-new-selectinput)
|
|
139
81
|
*/
|
|
@@ -144,8 +86,8 @@ export default function Select({
|
|
|
144
86
|
disabled,
|
|
145
87
|
inverse,
|
|
146
88
|
dropdownWidth,
|
|
147
|
-
size
|
|
148
|
-
block
|
|
89
|
+
size,
|
|
90
|
+
block,
|
|
149
91
|
selected,
|
|
150
92
|
search,
|
|
151
93
|
onChange,
|
|
@@ -153,28 +95,29 @@ export default function Select({
|
|
|
153
95
|
onBlur,
|
|
154
96
|
options: defaultOptions,
|
|
155
97
|
onSearchChange,
|
|
156
|
-
searchValue: initSearchValue
|
|
98
|
+
searchValue: initSearchValue,
|
|
157
99
|
searchPlaceholder,
|
|
158
|
-
|
|
100
|
+
// eslint-disable-next-line unicorn/prevent-abbreviations
|
|
101
|
+
classNames: classNamesProp,
|
|
159
102
|
dropdownUp,
|
|
160
103
|
dropdownProps,
|
|
161
104
|
buttonProps,
|
|
162
|
-
}
|
|
105
|
+
}) {
|
|
163
106
|
const inputAttributes = useInputAttributes();
|
|
164
107
|
|
|
165
108
|
const { formatMessage } = useIntl();
|
|
166
109
|
const { isModern } = useTheme();
|
|
167
|
-
const s = (className
|
|
110
|
+
const s = (className) => classNamesProp[className] || className;
|
|
168
111
|
const [open, setOpen] = useState(false);
|
|
169
112
|
const [searchValue, setSearchValue] = useState(DEFAULT_SEARCH_VALUE);
|
|
170
|
-
const [keyboardFocusedOptionIndex, setKeyboardFocusedOptionIndex] = useState(
|
|
171
|
-
const keyboardFocusedReference = useRef
|
|
172
|
-
const previousKeyboardFocusedOptionIndex = useRef(
|
|
113
|
+
const [keyboardFocusedOptionIndex, setKeyboardFocusedOptionIndex] = useState(null);
|
|
114
|
+
const keyboardFocusedReference = useRef();
|
|
115
|
+
const previousKeyboardFocusedOptionIndex = useRef();
|
|
173
116
|
const [numberOfOptionsShown, setNumberOfOptionsShown] = useState(DEFAULT_OPTIONS_PAGE_SIZE);
|
|
174
|
-
const searchBoxReference = useRef
|
|
175
|
-
const selectReference = useRef
|
|
176
|
-
const dropdownButtonReference = useRef
|
|
177
|
-
const optionsListReference = useRef
|
|
117
|
+
const searchBoxReference = useRef(null);
|
|
118
|
+
const selectReference = useRef(null);
|
|
119
|
+
const dropdownButtonReference = useRef(null);
|
|
120
|
+
const optionsListReference = useRef(null);
|
|
178
121
|
const isSearchEnabled = !!onSearchChange || !!search;
|
|
179
122
|
const isDropdownAutoWidth = dropdownWidth == null;
|
|
180
123
|
|
|
@@ -202,7 +145,7 @@ export default function Select({
|
|
|
202
145
|
const { isMobile } = useLayout();
|
|
203
146
|
|
|
204
147
|
useEffect(() => {
|
|
205
|
-
let cancelled
|
|
148
|
+
let cancelled;
|
|
206
149
|
|
|
207
150
|
if (keyboardFocusedOptionIndex >= 0) {
|
|
208
151
|
requestAnimationFrame(() => {
|
|
@@ -225,40 +168,42 @@ export default function Select({
|
|
|
225
168
|
setOpen(true);
|
|
226
169
|
};
|
|
227
170
|
|
|
228
|
-
const handleTouchStart
|
|
171
|
+
const handleTouchStart = (event) => {
|
|
229
172
|
if (event.currentTarget === event.target && open) {
|
|
230
173
|
handleCloseOptions();
|
|
231
174
|
}
|
|
232
175
|
};
|
|
233
176
|
|
|
234
|
-
const handleOnFocus
|
|
235
|
-
onFocus
|
|
177
|
+
const handleOnFocus = (event) => {
|
|
178
|
+
if (onFocus) {
|
|
179
|
+
onFocus(event);
|
|
180
|
+
}
|
|
236
181
|
};
|
|
237
182
|
|
|
238
|
-
const handleOnBlur
|
|
183
|
+
const handleOnBlur = (event) => {
|
|
239
184
|
const { nativeEvent } = event;
|
|
240
185
|
if (nativeEvent) {
|
|
241
186
|
const elementReceivingFocus = nativeEvent.relatedTarget;
|
|
242
187
|
const select = event.currentTarget;
|
|
243
|
-
if (
|
|
244
|
-
select &&
|
|
245
|
-
elementReceivingFocus instanceof Node &&
|
|
246
|
-
select.contains(elementReceivingFocus)
|
|
247
|
-
) {
|
|
188
|
+
if (select && elementReceivingFocus && select.contains(elementReceivingFocus)) {
|
|
248
189
|
return;
|
|
249
190
|
}
|
|
250
191
|
}
|
|
251
192
|
|
|
252
|
-
onBlur
|
|
193
|
+
if (onBlur) {
|
|
194
|
+
onBlur(event);
|
|
195
|
+
}
|
|
253
196
|
};
|
|
254
197
|
|
|
255
|
-
const handleSearchChange
|
|
198
|
+
const handleSearchChange = (event) => {
|
|
256
199
|
setNumberOfOptionsShown(DEFAULT_OPTIONS_PAGE_SIZE);
|
|
257
200
|
setSearchValue(event.target.value);
|
|
258
|
-
onSearchChange
|
|
201
|
+
if (onSearchChange) {
|
|
202
|
+
onSearchChange(event.target.value);
|
|
203
|
+
}
|
|
259
204
|
};
|
|
260
205
|
|
|
261
|
-
const handleKeyDown
|
|
206
|
+
const handleKeyDown = (event) => {
|
|
262
207
|
switch (event.key) {
|
|
263
208
|
case 'ArrowUp':
|
|
264
209
|
case 'ArrowDown':
|
|
@@ -302,25 +247,25 @@ export default function Select({
|
|
|
302
247
|
};
|
|
303
248
|
|
|
304
249
|
function selectKeyboardFocusedOption() {
|
|
305
|
-
if (keyboardFocusedOptionIndex
|
|
250
|
+
if (keyboardFocusedOptionIndex != null && selectableOptions.length > 0) {
|
|
306
251
|
selectOption(selectableOptions[keyboardFocusedOptionIndex]);
|
|
307
252
|
}
|
|
308
253
|
}
|
|
309
254
|
|
|
310
|
-
function moveFocusWithDifference(difference
|
|
255
|
+
function moveFocusWithDifference(difference) {
|
|
311
256
|
const selectedOptionIndex = selectableOptions.reduce((optionIndex, current, index) => {
|
|
312
|
-
if (optionIndex
|
|
257
|
+
if (optionIndex != null) {
|
|
313
258
|
return optionIndex;
|
|
314
259
|
}
|
|
315
260
|
if (isOptionSelected(selected, current)) {
|
|
316
261
|
return index;
|
|
317
262
|
}
|
|
318
|
-
return
|
|
319
|
-
},
|
|
320
|
-
const previousFocusedIndex = previousKeyboardFocusedOptionIndex.current;
|
|
263
|
+
return null;
|
|
264
|
+
}, null);
|
|
265
|
+
const previousFocusedIndex = previousKeyboardFocusedOptionIndex.current ?? -1;
|
|
321
266
|
let indexToStartMovingFrom = previousFocusedIndex;
|
|
322
|
-
if (previousFocusedIndex
|
|
323
|
-
if (selectedOptionIndex
|
|
267
|
+
if (previousFocusedIndex === -1) {
|
|
268
|
+
if (selectedOptionIndex == null) {
|
|
324
269
|
setKeyboardFocusedOptionIndex(0);
|
|
325
270
|
} else {
|
|
326
271
|
indexToStartMovingFrom = selectedOptionIndex;
|
|
@@ -342,7 +287,8 @@ export default function Select({
|
|
|
342
287
|
if (
|
|
343
288
|
!isSearchEnabled &&
|
|
344
289
|
optionsListReference.current &&
|
|
345
|
-
previousKeyboardFocusedOptionIndex.current
|
|
290
|
+
(previousKeyboardFocusedOptionIndex.current == null ||
|
|
291
|
+
Number.isNaN(previousKeyboardFocusedOptionIndex.current))
|
|
346
292
|
) {
|
|
347
293
|
optionsListReference.current.focus();
|
|
348
294
|
}
|
|
@@ -350,31 +296,31 @@ export default function Select({
|
|
|
350
296
|
|
|
351
297
|
previousKeyboardFocusedOptionIndex.current = keyboardFocusedOptionIndex;
|
|
352
298
|
} else {
|
|
353
|
-
previousKeyboardFocusedOptionIndex.current =
|
|
299
|
+
previousKeyboardFocusedOptionIndex.current = null;
|
|
354
300
|
}
|
|
355
301
|
}, [open, searchValue, isSearchEnabled, isMobile, keyboardFocusedOptionIndex]);
|
|
356
302
|
|
|
357
303
|
const handleCloseOptions = () => {
|
|
358
304
|
setOpen(false);
|
|
359
|
-
setKeyboardFocusedOptionIndex(
|
|
305
|
+
setKeyboardFocusedOptionIndex(null);
|
|
360
306
|
if (dropdownButtonReference.current) {
|
|
361
307
|
dropdownButtonReference.current.focus();
|
|
362
308
|
}
|
|
363
309
|
};
|
|
364
310
|
|
|
365
|
-
function createSelectHandlerForOption(option
|
|
366
|
-
return (event
|
|
311
|
+
function createSelectHandlerForOption(option) {
|
|
312
|
+
return (event) => {
|
|
367
313
|
stopPropagation(event);
|
|
368
314
|
selectOption(option);
|
|
369
315
|
};
|
|
370
316
|
}
|
|
371
317
|
|
|
372
|
-
function selectOption(option
|
|
318
|
+
function selectOption(option) {
|
|
373
319
|
onChange(isPlaceholderOption(option) ? DEFAULT_SELECTED_OPTION : option);
|
|
374
320
|
handleCloseOptions();
|
|
375
321
|
}
|
|
376
322
|
|
|
377
|
-
function renderOptionsList({ className
|
|
323
|
+
function renderOptionsList({ className } = {}) {
|
|
378
324
|
const dropdownClass = clsx(
|
|
379
325
|
s('np-dropdown-menu'),
|
|
380
326
|
{
|
|
@@ -391,8 +337,8 @@ export default function Select({
|
|
|
391
337
|
id={listboxId}
|
|
392
338
|
role="listbox"
|
|
393
339
|
aria-orientation="vertical"
|
|
394
|
-
aria-activedescendant={getUniqueIdForOption(id, selected
|
|
395
|
-
tabIndex=
|
|
340
|
+
aria-activedescendant={getUniqueIdForOption(id, selected)}
|
|
341
|
+
tabIndex="-1"
|
|
396
342
|
className={dropdownClass}
|
|
397
343
|
{...dropdownProps}
|
|
398
344
|
>
|
|
@@ -416,7 +362,7 @@ export default function Select({
|
|
|
416
362
|
}
|
|
417
363
|
|
|
418
364
|
function ShowMoreOption() {
|
|
419
|
-
function handleOnClick(event
|
|
365
|
+
function handleOnClick(event) {
|
|
420
366
|
stopPropagation(event);
|
|
421
367
|
setNumberOfOptionsShown(numberOfOptionsShown + DEFAULT_OPTIONS_PAGE_SIZE);
|
|
422
368
|
}
|
|
@@ -454,7 +400,7 @@ export default function Select({
|
|
|
454
400
|
}
|
|
455
401
|
|
|
456
402
|
// eslint-disable-next-line react/prop-types
|
|
457
|
-
function HeaderOption({ children }
|
|
403
|
+
function HeaderOption({ children }) {
|
|
458
404
|
return (
|
|
459
405
|
<li // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
|
|
460
406
|
className={clsx(s('np-dropdown-header'), s('np-text-title-group'))}
|
|
@@ -466,11 +412,11 @@ export default function Select({
|
|
|
466
412
|
);
|
|
467
413
|
}
|
|
468
414
|
|
|
469
|
-
function isOptionSelected(selected
|
|
415
|
+
function isOptionSelected(selected, option) {
|
|
470
416
|
return selected?.value === option?.value;
|
|
471
417
|
}
|
|
472
418
|
|
|
473
|
-
const renderOption = (option
|
|
419
|
+
const renderOption = (option, index) => {
|
|
474
420
|
const separatorOption = option;
|
|
475
421
|
if (isSeparatorOption(separatorOption) && separatorOption?.separator) {
|
|
476
422
|
return <SeparatorOption key={index} />;
|
|
@@ -508,25 +454,20 @@ export default function Select({
|
|
|
508
454
|
aria-selected={isActive}
|
|
509
455
|
aria-disabled={option.disabled}
|
|
510
456
|
role="option"
|
|
511
|
-
tabIndex=
|
|
457
|
+
tabIndex="-1"
|
|
512
458
|
className={className}
|
|
513
459
|
onClick={handleOnClick}
|
|
514
460
|
onKeyPress={handleOnClick}
|
|
515
461
|
>
|
|
516
462
|
{/* @ts-expect-error options needs DOM refactoring */}
|
|
517
463
|
<a disabled={selectOption.disabled}>
|
|
518
|
-
<Option
|
|
519
|
-
label={undefined}
|
|
520
|
-
value={undefined}
|
|
521
|
-
{...selectOption}
|
|
522
|
-
classNames={classNamesProp}
|
|
523
|
-
/>
|
|
464
|
+
<Option {...selectOption} classNames={classNamesProp} />
|
|
524
465
|
</a>
|
|
525
466
|
</li>
|
|
526
467
|
);
|
|
527
468
|
};
|
|
528
469
|
|
|
529
|
-
function getIndexWithoutHeadersForIndexWithHeaders(index
|
|
470
|
+
function getIndexWithoutHeadersForIndexWithHeaders(index) {
|
|
530
471
|
return options.reduce((sum, option, currentIndex) => {
|
|
531
472
|
if (currentIndex < index && isActionableOption(option)) {
|
|
532
473
|
return sum + 1;
|
|
@@ -537,11 +478,11 @@ export default function Select({
|
|
|
537
478
|
|
|
538
479
|
const hasActiveOptions = !!defaultOptions.length;
|
|
539
480
|
if (open && (initSearchValue || searchValue)) {
|
|
540
|
-
if (hasActiveOptions && keyboardFocusedOptionIndex
|
|
481
|
+
if (hasActiveOptions && keyboardFocusedOptionIndex == null) {
|
|
541
482
|
setKeyboardFocusedOptionIndex(0);
|
|
542
483
|
}
|
|
543
|
-
if (!hasActiveOptions && keyboardFocusedOptionIndex
|
|
544
|
-
setKeyboardFocusedOptionIndex(
|
|
484
|
+
if (!hasActiveOptions && keyboardFocusedOptionIndex != null) {
|
|
485
|
+
setKeyboardFocusedOptionIndex(null);
|
|
545
486
|
}
|
|
546
487
|
}
|
|
547
488
|
|
|
@@ -567,8 +508,8 @@ export default function Select({
|
|
|
567
508
|
inverse ? s('np-dropdown-toggle-navy') : null,
|
|
568
509
|
)}
|
|
569
510
|
// reset Button's styles
|
|
570
|
-
type={
|
|
571
|
-
priority={
|
|
511
|
+
type={null}
|
|
512
|
+
priority={null}
|
|
572
513
|
disabled={disabled}
|
|
573
514
|
aria-controls={listboxId}
|
|
574
515
|
aria-expanded={open}
|
|
@@ -576,7 +517,7 @@ export default function Select({
|
|
|
576
517
|
{...buttonProps}
|
|
577
518
|
>
|
|
578
519
|
{selected ? (
|
|
579
|
-
<Option
|
|
520
|
+
<Option {...selected} classNames={classNamesProp} selected />
|
|
580
521
|
) : (
|
|
581
522
|
<span className={s('form-control-placeholder')}>{placeholder}</span>
|
|
582
523
|
)}
|
|
@@ -621,3 +562,80 @@ export default function Select({
|
|
|
621
562
|
</div>
|
|
622
563
|
);
|
|
623
564
|
}
|
|
565
|
+
|
|
566
|
+
Select.propTypes = {
|
|
567
|
+
placeholder: PropTypes.string,
|
|
568
|
+
id: PropTypes.string,
|
|
569
|
+
required: PropTypes.bool,
|
|
570
|
+
disabled: PropTypes.bool,
|
|
571
|
+
inverse: PropTypes.bool,
|
|
572
|
+
dropdownRight: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
|
|
573
|
+
dropdownWidth: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
574
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
575
|
+
block: PropTypes.bool,
|
|
576
|
+
selected: PropTypes.shape({
|
|
577
|
+
value: PropTypes.any.isRequired,
|
|
578
|
+
label: PropTypes.node,
|
|
579
|
+
icon: PropTypes.node,
|
|
580
|
+
currency: PropTypes.string,
|
|
581
|
+
note: PropTypes.node,
|
|
582
|
+
secondary: PropTypes.node,
|
|
583
|
+
}),
|
|
584
|
+
/**
|
|
585
|
+
* Search toggle
|
|
586
|
+
* if `true` default search functionality being enabled (not case sensitive search in option labels & currency props)
|
|
587
|
+
* if `function` you can define your own search function to implement custom search experience. This search function used while filtering the options array. The custom search function takes two parameters. First is the option the second is the keyword.
|
|
588
|
+
*/
|
|
589
|
+
search: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
|
|
590
|
+
options: PropTypes.arrayOf(
|
|
591
|
+
PropTypes.shape({
|
|
592
|
+
value: PropTypes.any,
|
|
593
|
+
label: PropTypes.node,
|
|
594
|
+
header: PropTypes.node,
|
|
595
|
+
icon: PropTypes.node,
|
|
596
|
+
currency: PropTypes.string,
|
|
597
|
+
note: PropTypes.node,
|
|
598
|
+
secondary: PropTypes.node,
|
|
599
|
+
separator: PropTypes.bool,
|
|
600
|
+
disabled: PropTypes.bool,
|
|
601
|
+
searchStrings: PropTypes.arrayOf(PropTypes.string),
|
|
602
|
+
}),
|
|
603
|
+
).isRequired,
|
|
604
|
+
searchValue: PropTypes.string,
|
|
605
|
+
searchPlaceholder: PropTypes.string,
|
|
606
|
+
classNames: PropTypes.objectOf(PropTypes.string),
|
|
607
|
+
dropdownUp: PropTypes.bool,
|
|
608
|
+
buttonProps: PropTypes.object,
|
|
609
|
+
dropdownProps: PropTypes.object,
|
|
610
|
+
onChange: PropTypes.func.isRequired,
|
|
611
|
+
onFocus: PropTypes.func,
|
|
612
|
+
onBlur: PropTypes.func,
|
|
613
|
+
/**
|
|
614
|
+
* To have full control of your search value and response use `onSearchChange` function combined with `searchValue` and custom filtering on the options array.
|
|
615
|
+
* DO NOT USE TOGETHER WITH `search` PROPERTY
|
|
616
|
+
*/
|
|
617
|
+
onSearchChange: PropTypes.func,
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
Select.defaultProps = {
|
|
621
|
+
id: undefined,
|
|
622
|
+
placeholder: undefined,
|
|
623
|
+
size: 'md',
|
|
624
|
+
dropdownRight: null,
|
|
625
|
+
dropdownWidth: null,
|
|
626
|
+
inverse: false,
|
|
627
|
+
required: false,
|
|
628
|
+
disabled: false,
|
|
629
|
+
block: true,
|
|
630
|
+
selected: null,
|
|
631
|
+
onFocus: null,
|
|
632
|
+
onBlur: null,
|
|
633
|
+
onSearchChange: undefined,
|
|
634
|
+
search: false,
|
|
635
|
+
searchValue: '',
|
|
636
|
+
searchPlaceholder: undefined,
|
|
637
|
+
classNames: {},
|
|
638
|
+
dropdownUp: false,
|
|
639
|
+
buttonProps: {},
|
|
640
|
+
dropdownProps: {},
|
|
641
|
+
};
|
|
@@ -2,7 +2,6 @@ import { Breakpoint } from '../common';
|
|
|
2
2
|
import { render, screen, userEvent, waitFor, mockMatchMedia } from '../test-utils';
|
|
3
3
|
|
|
4
4
|
import Select from '.';
|
|
5
|
-
import { SelectItemWithPlaceholder } from './Select';
|
|
6
5
|
|
|
7
6
|
mockMatchMedia();
|
|
8
7
|
|
|
@@ -43,7 +42,7 @@ describe('Select', () => {
|
|
|
43
42
|
global.requestAnimationFrame = originalRAF;
|
|
44
43
|
});
|
|
45
44
|
|
|
46
|
-
const openSelect = async (container
|
|
45
|
+
const openSelect = async (container) => {
|
|
47
46
|
const button = screen.getByRole('button');
|
|
48
47
|
await userEvent.click(button);
|
|
49
48
|
};
|
|
@@ -82,7 +81,7 @@ describe('Select', () => {
|
|
|
82
81
|
});
|
|
83
82
|
|
|
84
83
|
describe('when options contain searchables', () => {
|
|
85
|
-
let input
|
|
84
|
+
let input;
|
|
86
85
|
|
|
87
86
|
const searchableOptions = [
|
|
88
87
|
{
|
|
@@ -208,8 +207,8 @@ describe('Select', () => {
|
|
|
208
207
|
container.querySelector('.np-dropdown-item'),
|
|
209
208
|
);
|
|
210
209
|
expect(
|
|
211
|
-
screen.getByText('Hungarian forint').parentElement
|
|
212
|
-
|
|
210
|
+
screen.getByText('Hungarian forint').parentElement.parentElement.parentElement
|
|
211
|
+
.parentElement,
|
|
213
212
|
).toHaveClass('np-dropdown-item clickable np-dropdown-item--focused');
|
|
214
213
|
});
|
|
215
214
|
|
|
@@ -232,7 +231,7 @@ describe('Select', () => {
|
|
|
232
231
|
expect(container.querySelector('.np-dropdown-item.np-dropdown-item--focused')).toStrictEqual(
|
|
233
232
|
container.querySelector('.np-dropdown-item'),
|
|
234
233
|
);
|
|
235
|
-
expect(screen.getByText('Estonia').parentElement
|
|
234
|
+
expect(screen.getByText('Estonia').parentElement.parentElement).toHaveClass(
|
|
236
235
|
'np-dropdown-item clickable np-dropdown-item--focused',
|
|
237
236
|
);
|
|
238
237
|
});
|
|
@@ -248,8 +247,7 @@ describe('Select', () => {
|
|
|
248
247
|
});
|
|
249
248
|
|
|
250
249
|
it('should filter the options with a custom search function with 3 results', async () => {
|
|
251
|
-
const searchFunction = (option
|
|
252
|
-
typeof option.label === 'string' && option.label.includes(searchValue);
|
|
250
|
+
const searchFunction = (option, searchValue) => option.label.includes(searchValue);
|
|
253
251
|
const { container } = render(<Select {...props} search={searchFunction} />);
|
|
254
252
|
await openSelect(container);
|
|
255
253
|
|
|
@@ -107,7 +107,13 @@ export class Snackbar extends Component<SnackbarProps, SnackbarState> {
|
|
|
107
107
|
}}
|
|
108
108
|
unmountOnExit
|
|
109
109
|
>
|
|
110
|
-
<Body
|
|
110
|
+
<Body
|
|
111
|
+
ref={this.bodyRef}
|
|
112
|
+
as="span"
|
|
113
|
+
className="snackbar__text"
|
|
114
|
+
role="status"
|
|
115
|
+
aria-live="assertive"
|
|
116
|
+
>
|
|
111
117
|
{text}
|
|
112
118
|
{action ? (
|
|
113
119
|
<ActionButton className="snackbar__text__action" onClick={action.onClick}>
|
package/src/select/index.ts
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|