@scality/core-ui 0.159.0 → 0.160.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/dist/components/accordion/Accordion.component.d.ts.map +1 -1
- package/dist/components/accordion/Accordion.component.js +4 -5
- package/dist/components/layout/v2/AppContainer.d.ts.map +1 -1
- package/dist/components/layout/v2/AppContainer.js +1 -0
- package/dist/components/selectv2/SelectStyle.d.ts +2 -1
- package/dist/components/selectv2/SelectStyle.d.ts.map +1 -1
- package/dist/components/selectv2/Selectv2.component.d.ts +16 -6
- package/dist/components/selectv2/Selectv2.component.d.ts.map +1 -1
- package/dist/components/selectv2/Selectv2.component.js +67 -30
- package/package.json +2 -1
- package/src/lib/components/accordion/Accordion.component.tsx +4 -6
- package/src/lib/components/layout/v2/AppContainer.tsx +1 -0
- package/src/lib/components/selectv2/Selectv2.component.tsx +148 -42
- package/src/lib/components/selectv2/selectv2.test.tsx +156 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Accordion.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/accordion/Accordion.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAUjD,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;
|
|
1
|
+
{"version":3,"file":"Accordion.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/accordion/Accordion.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAUjD,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAgDF,eAAO,MAAM,SAAS,sDAOnB,cAAc,4CA4DhB,CAAC"}
|
|
@@ -8,11 +8,10 @@ import { Text } from '../text/Text.component';
|
|
|
8
8
|
const AccordionContainer = styled(Box) `
|
|
9
9
|
width: 100%;
|
|
10
10
|
height: auto;
|
|
11
|
-
${
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`}
|
|
11
|
+
padding: ${spacing.r16};
|
|
12
|
+
border-radius: 4px;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
${({ theme }) => `border: 0.5px solid ${theme.border};`}
|
|
16
15
|
`;
|
|
17
16
|
const AccordionHeader = styled.button `
|
|
18
17
|
-webkit-appearance: none;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppContainer.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/layout/v2/AppContainer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGhD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"AppContainer.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/layout/v2/AppContainer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGhD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAoInD,iBAAS,YAAY,CAAC,EACpB,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACV,GAAG,IAAI,EACR,EAAE;IACD,QAAQ,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IACxC,iBAAiB,CAAC,EAAE,YAAY,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,2CASA;kBAlBQ,YAAY;8DA/FlB;QACD,UAAU,CAAC,EAAE,WAAW,CAAC;QACzB,QAAQ,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;KACzC;qGA8BE;QACD,QAAQ,EAAE,GAAG,CAAC;QACd,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,UAAU,CAAC,EAAE,WAAW,CAAC;KAC1B;mFA8BE;QACD,QAAQ,EAAE,SAAS,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,UAAU,CAAC,EAAE,WAAW,CAAC;KAC1B;;;AA8CD,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import Select from 'react-select';
|
|
2
|
+
declare const SelectStyle: import("styled-components").StyledComponent<typeof Select, import("styled-components").DefaultTheme, {}, never>;
|
|
2
3
|
export { SelectStyle };
|
|
3
4
|
//# sourceMappingURL=SelectStyle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectStyle.d.ts","sourceRoot":"","sources":["../../../src/lib/components/selectv2/SelectStyle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SelectStyle.d.ts","sourceRoot":"","sources":["../../../src/lib/components/selectv2/SelectStyle.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,cAAc,CAAC;AAMlC,QAAA,MAAM,WAAW,iHAmQhB,CAAC;AACF,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { ForwardRefExoticComponent, RefAttributes } from 'react';
|
|
2
|
+
import { GroupTypeBase, OptionTypeBase } from 'react-select';
|
|
3
|
+
import ReactSelect from 'react-select/src/Select';
|
|
2
4
|
export type OptionProps = {
|
|
3
5
|
title?: string;
|
|
4
6
|
disabled?: boolean;
|
|
@@ -8,6 +10,15 @@ export type OptionProps = {
|
|
|
8
10
|
disabledReason?: React.ReactNode;
|
|
9
11
|
};
|
|
10
12
|
export declare function Option({ value, children, disabled, icon, disabledReason, ...rest }: OptionProps): JSX.Element;
|
|
13
|
+
export interface SelectRef<OptionType extends OptionTypeBase, IsMulti extends boolean, GroupType extends GroupTypeBase<OptionType>> {
|
|
14
|
+
select: ReactSelect<OptionType, IsMulti, GroupType> | null;
|
|
15
|
+
focus: () => void;
|
|
16
|
+
blur: () => void;
|
|
17
|
+
openMenu: () => void;
|
|
18
|
+
closeMenu: () => void;
|
|
19
|
+
setValue: (value: string) => void;
|
|
20
|
+
clearValue: () => void;
|
|
21
|
+
}
|
|
11
22
|
export type SelectProps = {
|
|
12
23
|
id: string;
|
|
13
24
|
placeholder?: string;
|
|
@@ -23,10 +34,9 @@ export type SelectProps = {
|
|
|
23
34
|
/** use menuPositon='fixed' inside modal to avoid display issue */
|
|
24
35
|
menuPosition?: 'fixed' | 'absolute';
|
|
25
36
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export declare const Select: typeof SelectWithOptionContext;
|
|
37
|
+
type SelectComponentType<OptionType extends OptionTypeBase, IsMulti extends boolean, GroupType extends GroupTypeBase<OptionType>> = ForwardRefExoticComponent<SelectProps & RefAttributes<SelectRef<OptionType, IsMulti, GroupType>>> & {
|
|
38
|
+
Option: typeof Option;
|
|
39
|
+
};
|
|
40
|
+
export declare const Select: SelectComponentType<OptionTypeBase, boolean, GroupTypeBase<OptionTypeBase>>;
|
|
31
41
|
export {};
|
|
32
42
|
//# sourceMappingURL=Selectv2.component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Selectv2.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/selectv2/Selectv2.component.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Selectv2.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/selectv2/Selectv2.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAOZ,yBAAyB,EACzB,aAAa,EAEd,MAAM,OAAO,CAAC;AAEf,OAAO,EAEL,aAAa,EACb,cAAc,EAEf,MAAM,cAAc,CAAC;AAQtB,OAAO,WAAW,MAAM,yBAAyB,CAAC;AAKlD,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAClC,CAAC;AAkBF,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,cAAc,EACd,GAAG,IAAI,EACR,EAAE,WAAW,GAAG,GAAG,CAAC,OAAO,CA0B3B;AAuOD,MAAM,WAAW,SAAS,CACxB,UAAU,SAAS,cAAc,EACjC,OAAO,SAAS,OAAO,EACvB,SAAS,SAAS,aAAa,CAAC,UAAU,CAAC;IAE3C,MAAM,EAAE,WAAW,CAAC,UAAU,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC;IAC3D,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAChC,IAAI,CAAC,EAAE,GAAG,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;CACrC,CAAC;AAWF,KAAK,mBAAmB,CACtB,UAAU,SAAS,cAAc,EACjC,OAAO,SAAS,OAAO,EACvB,SAAS,SAAS,aAAa,CAAC,UAAU,CAAC,IACzC,yBAAyB,CAC3B,WAAW,GAAG,aAAa,CAAC,SAAS,CAAC,UAAU,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CACvE,GAAG;IACF,MAAM,EAAE,OAAO,MAAM,CAAC;CACvB,CAAC;AAkOF,eAAO,MAAM,MAAM,6EAA0B,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useState, useEffect, useRef, } from 'react';
|
|
2
|
+
import { createContext, useContext, useState, useEffect, useRef, forwardRef, useImperativeHandle, } from 'react';
|
|
3
3
|
import { ScrollbarWrapper, Tooltip } from '../../index';
|
|
4
|
-
import { components } from 'react-select';
|
|
4
|
+
import { components, } from 'react-select';
|
|
5
5
|
import { Icon } from '../icon/Icon.component';
|
|
6
6
|
import { SelectStyle } from './SelectStyle';
|
|
7
7
|
import { FixedSizeList as List } from 'react-window';
|
|
@@ -177,38 +177,58 @@ const ValueContainer = ({ children, ...props }) => {
|
|
|
177
177
|
return (_jsxs(components.ValueContainer, { ...props, ...ariaProps, children: [icon ? _jsx("div", { className: "value-container-icon", children: icon }) : null, _jsx("div", { children: children })] }));
|
|
178
178
|
};
|
|
179
179
|
const OptionContext = createContext(null);
|
|
180
|
-
function
|
|
181
|
-
const [options, setOptions] = useState({});
|
|
182
|
-
const register = (option) => {
|
|
183
|
-
setOptions((prevOptions) => ({
|
|
184
|
-
...prevOptions,
|
|
185
|
-
[option.value]: option,
|
|
186
|
-
}));
|
|
187
|
-
};
|
|
188
|
-
const unregister = (value) => {
|
|
189
|
-
setOptions((prevOptions) => {
|
|
190
|
-
const { [value]: _, ...rest } = prevOptions;
|
|
191
|
-
return rest;
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
return (_jsx(OptionContext.Provider, { value: { options, register, unregister }, children: _jsxs(_Fragment, { children: [_jsx(SelectBox, { ...props }), props.children] }) }));
|
|
195
|
-
}
|
|
196
|
-
function SelectBox({ placeholder = 'Select...', disabled = false, value, onChange, variant = 'default', className, size = '1', id, ...rest }) {
|
|
180
|
+
function SelectBox({ placeholder = 'Select...', disabled = false, value, onChange, variant = 'default', className, size = '1', id, selectRef, ...rest }) {
|
|
197
181
|
const [keyboardFocusEnabled, setKeyboardFocusEnabled] = useState(false);
|
|
198
182
|
const [searchSelection, setSearchSelection] = useState('');
|
|
199
183
|
const [searchValue, setSearchValue] = useState('');
|
|
200
184
|
const [customPlaceholder, setPlaceholder] = useState(placeholder);
|
|
201
185
|
const isDefaultVariant = variant === 'default';
|
|
202
186
|
const [isMenuBottom, setIsMenuBottom] = useState(true);
|
|
203
|
-
const
|
|
187
|
+
const internalSelectRef = useRef(null);
|
|
188
|
+
useImperativeHandle(selectRef, () => ({
|
|
189
|
+
focus: () => {
|
|
190
|
+
if (internalSelectRef.current) {
|
|
191
|
+
internalSelectRef.current.focus();
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
blur: () => {
|
|
195
|
+
if (internalSelectRef.current) {
|
|
196
|
+
internalSelectRef.current.blur();
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
select: internalSelectRef.current,
|
|
200
|
+
openMenu: () => {
|
|
201
|
+
if (internalSelectRef.current) {
|
|
202
|
+
internalSelectRef.current.setState({ menuIsOpen: true });
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
closeMenu: () => {
|
|
206
|
+
if (internalSelectRef.current) {
|
|
207
|
+
internalSelectRef.current.setState({ menuIsOpen: false });
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
setValue: (newValue) => {
|
|
211
|
+
if (internalSelectRef.current) {
|
|
212
|
+
const option = options.find((opt) => opt.value === newValue);
|
|
213
|
+
if (option) {
|
|
214
|
+
internalSelectRef.current.select.setValue(option);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
clearValue: () => {
|
|
219
|
+
if (internalSelectRef.current && internalSelectRef.current.select) {
|
|
220
|
+
internalSelectRef.current.select.clearValue();
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
}), [internalSelectRef]);
|
|
204
224
|
const options = useOptions();
|
|
205
225
|
const handleChange = (option) => {
|
|
206
226
|
const newValue = option ? option.value : '';
|
|
207
227
|
if (onChange && typeof onChange === 'function' && newValue !== value) {
|
|
208
228
|
onChange(newValue);
|
|
209
229
|
}
|
|
210
|
-
if (options && options.length > NOPT_SEARCH) {
|
|
211
|
-
|
|
230
|
+
if (options && options.length > NOPT_SEARCH && internalSelectRef.current) {
|
|
231
|
+
internalSelectRef.current.blur();
|
|
212
232
|
}
|
|
213
233
|
};
|
|
214
234
|
const handleSearchInput = (inputValue, { action }) => {
|
|
@@ -235,11 +255,11 @@ function SelectBox({ placeholder = 'Select...', disabled = false, value, onChang
|
|
|
235
255
|
useEffect(() => {
|
|
236
256
|
if (!isEmptyStringInOptions &&
|
|
237
257
|
value === '' &&
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
internalSelectRef.current &&
|
|
259
|
+
internalSelectRef.current.select) {
|
|
260
|
+
internalSelectRef.current.select.clearValue();
|
|
241
261
|
}
|
|
242
|
-
}, [value,
|
|
262
|
+
}, [value, isEmptyStringInOptions]);
|
|
243
263
|
return (_jsx(ScrollbarWrapper, { children: _jsx(_Fragment, { children: options && (_jsx(SelectStyle, { inputId: id, className: ['sc-select', className].join(' '), classNamePrefix: "sc-select", name: "sc-select", value: searchSelection || options.find((opt) => opt.value === value), inputValue: options.length > NOPT_SEARCH ? searchValue : undefined, selectedOption: options.find((opt) => opt.value === value), keyboardFocusEnabled: keyboardFocusEnabled, options: options, isDisabled: disabled, placeholder: customPlaceholder, menuPlacement: "auto", isSearchable: options.length > NOPT_SEARCH, components: {
|
|
244
264
|
Input: Input,
|
|
245
265
|
Option: InternalOption(convertSizeToRem(size), isDefaultVariant),
|
|
@@ -248,12 +268,12 @@ function SelectBox({ placeholder = 'Select...', disabled = false, value, onChang
|
|
|
248
268
|
ValueContainer: ValueContainer,
|
|
249
269
|
DropdownIndicator: DropdownIndicator,
|
|
250
270
|
IndicatorSeparator: null,
|
|
251
|
-
}, isDefault: isDefaultVariant, ITEMS_PER_SCROLL_WINDOW: ITEMS_PER_SCROLL_WINDOW, onChange: handleChange, onInputChange: handleSearchInput, ref:
|
|
271
|
+
}, isDefault: isDefaultVariant, ITEMS_PER_SCROLL_WINDOW: ITEMS_PER_SCROLL_WINDOW, onChange: handleChange, onInputChange: handleSearchInput, ref: internalSelectRef, isMenuBottom: isMenuBottom, setIsMenuBottom: setIsMenuBottom, onBlur: rest.onBlur, onFocus: rest.onFocus, onMenuClose: () => setKeyboardFocusEnabled(false), onKeyDown: (event) => {
|
|
252
272
|
if (event &&
|
|
253
273
|
event.key === 'Enter' &&
|
|
254
|
-
|
|
255
|
-
!
|
|
256
|
-
|
|
274
|
+
internalSelectRef.current &&
|
|
275
|
+
!internalSelectRef.current.state.isOpen) {
|
|
276
|
+
internalSelectRef.current.setState({
|
|
257
277
|
menuIsOpen: true,
|
|
258
278
|
});
|
|
259
279
|
}
|
|
@@ -262,5 +282,22 @@ function SelectBox({ placeholder = 'Select...', disabled = false, value, onChang
|
|
|
262
282
|
}
|
|
263
283
|
}, width: convertSizeToRem(size), ...rest })) }) }));
|
|
264
284
|
}
|
|
285
|
+
const SelectWithOptionContext = forwardRef((props, ref) => {
|
|
286
|
+
const [options, setOptions] = useState({});
|
|
287
|
+
const register = (option) => {
|
|
288
|
+
setOptions((prevOptions) => ({
|
|
289
|
+
...prevOptions,
|
|
290
|
+
[option.value]: option,
|
|
291
|
+
}));
|
|
292
|
+
};
|
|
293
|
+
const unregister = (value) => {
|
|
294
|
+
setOptions((prevOptions) => {
|
|
295
|
+
const { [value]: _, ...rest } = prevOptions;
|
|
296
|
+
return rest;
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
return (_jsx(OptionContext.Provider, { value: { options, register, unregister }, children: _jsxs(_Fragment, { children: [_jsx(SelectBox, { ...props, selectRef: ref }), props.children] }) }));
|
|
300
|
+
});
|
|
301
|
+
SelectWithOptionContext.displayName = 'Select';
|
|
265
302
|
SelectWithOptionContext.Option = Option;
|
|
266
303
|
export const Select = SelectWithOptionContext;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scality/core-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.160.0",
|
|
4
4
|
"description": "Scality common React component library",
|
|
5
5
|
"author": "Scality Engineering",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@types/react-dom": "^18.3.1",
|
|
58
58
|
"@types/react-router": "^5.1.20",
|
|
59
59
|
"@types/react-router-dom": "^5.3.3",
|
|
60
|
+
"@types/react-select": "^4.0.18",
|
|
60
61
|
"@types/react-table": "^7.7.11",
|
|
61
62
|
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
|
62
63
|
"@types/react-window": "^1.8.5",
|
|
@@ -20,12 +20,10 @@ export type AccordionProps = {
|
|
|
20
20
|
const AccordionContainer = styled(Box)`
|
|
21
21
|
width: 100%;
|
|
22
22
|
height: auto;
|
|
23
|
-
${
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
padding: ${spacing.r16};
|
|
28
|
-
`}
|
|
23
|
+
padding: ${spacing.r16};
|
|
24
|
+
border-radius: 4px;
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
${({ theme }) => `border: 0.5px solid ${theme.border};`}
|
|
29
27
|
`;
|
|
30
28
|
|
|
31
29
|
const AccordionHeader = styled.button<{
|
|
@@ -4,9 +4,18 @@ import React, {
|
|
|
4
4
|
useState,
|
|
5
5
|
useEffect,
|
|
6
6
|
useRef,
|
|
7
|
+
forwardRef,
|
|
8
|
+
ForwardRefExoticComponent,
|
|
9
|
+
RefAttributes,
|
|
10
|
+
useImperativeHandle,
|
|
7
11
|
} from 'react';
|
|
8
12
|
import { ScrollbarWrapper, Tooltip } from '../../index';
|
|
9
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
components,
|
|
15
|
+
GroupTypeBase,
|
|
16
|
+
OptionTypeBase,
|
|
17
|
+
ValueContainerProps,
|
|
18
|
+
} from 'react-select';
|
|
10
19
|
import { Icon } from '../icon/Icon.component';
|
|
11
20
|
import { SelectStyle } from './SelectStyle';
|
|
12
21
|
import { FixedSizeList, FixedSizeList as List } from 'react-window';
|
|
@@ -14,6 +23,7 @@ import { convertRemToPixels } from '../../utils';
|
|
|
14
23
|
import { spacing } from '../../spacing';
|
|
15
24
|
import { convertSizeToRem } from '../inputv2/inputv2';
|
|
16
25
|
import { ConstrainedText } from '../constrainedtext/Constrainedtext.component';
|
|
26
|
+
import ReactSelect from 'react-select/src/Select';
|
|
17
27
|
|
|
18
28
|
const ITEMS_PER_SCROLL_WINDOW = 4;
|
|
19
29
|
// more/equal than NOPT_SEARCH options enable search
|
|
@@ -280,7 +290,14 @@ const MenuList = (props) => {
|
|
|
280
290
|
return <components.MenuList {...props}>{children}</components.MenuList>;
|
|
281
291
|
};
|
|
282
292
|
|
|
283
|
-
const ValueContainer =
|
|
293
|
+
const ValueContainer = <
|
|
294
|
+
OptionType extends OptionTypeBase,
|
|
295
|
+
IsMulti extends boolean,
|
|
296
|
+
GroupType extends GroupTypeBase<OptionType>,
|
|
297
|
+
>({
|
|
298
|
+
children,
|
|
299
|
+
...props
|
|
300
|
+
}: ValueContainerProps<OptionType, IsMulti, GroupType>) => {
|
|
284
301
|
const selectedOption = props.selectProps.selectedOption;
|
|
285
302
|
const icon = selectedOption ? selectedOption.icon : null;
|
|
286
303
|
const ariaProps = {
|
|
@@ -300,6 +317,19 @@ const ValueContainer = ({ children, ...props }) => {
|
|
|
300
317
|
</components.ValueContainer>
|
|
301
318
|
);
|
|
302
319
|
};
|
|
320
|
+
export interface SelectRef<
|
|
321
|
+
OptionType extends OptionTypeBase,
|
|
322
|
+
IsMulti extends boolean,
|
|
323
|
+
GroupType extends GroupTypeBase<OptionType>,
|
|
324
|
+
> {
|
|
325
|
+
select: ReactSelect<OptionType, IsMulti, GroupType> | null;
|
|
326
|
+
focus: () => void;
|
|
327
|
+
blur: () => void;
|
|
328
|
+
openMenu: () => void;
|
|
329
|
+
closeMenu: () => void;
|
|
330
|
+
setValue: (value: string) => void;
|
|
331
|
+
clearValue: () => void;
|
|
332
|
+
}
|
|
303
333
|
|
|
304
334
|
export type SelectProps = {
|
|
305
335
|
id: string;
|
|
@@ -316,6 +346,7 @@ export type SelectProps = {
|
|
|
316
346
|
/** use menuPositon='fixed' inside modal to avoid display issue */
|
|
317
347
|
menuPosition?: 'fixed' | 'absolute';
|
|
318
348
|
};
|
|
349
|
+
|
|
319
350
|
type SelectOptionProps = {
|
|
320
351
|
value: string;
|
|
321
352
|
label: React.ReactNode;
|
|
@@ -325,40 +356,27 @@ type SelectOptionProps = {
|
|
|
325
356
|
disabledReason?: React.ReactNode;
|
|
326
357
|
};
|
|
327
358
|
|
|
359
|
+
type SelectComponentType<
|
|
360
|
+
OptionType extends OptionTypeBase,
|
|
361
|
+
IsMulti extends boolean,
|
|
362
|
+
GroupType extends GroupTypeBase<OptionType>,
|
|
363
|
+
> = ForwardRefExoticComponent<
|
|
364
|
+
SelectProps & RefAttributes<SelectRef<OptionType, IsMulti, GroupType>>
|
|
365
|
+
> & {
|
|
366
|
+
Option: typeof Option;
|
|
367
|
+
};
|
|
368
|
+
|
|
328
369
|
const OptionContext = createContext<{
|
|
329
370
|
options: Record<string, SelectOptionProps>;
|
|
330
371
|
register: (option: SelectOptionProps) => void;
|
|
331
372
|
unregister: (value: string) => void;
|
|
332
373
|
} | null>(null);
|
|
333
374
|
|
|
334
|
-
function
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
...prevOptions,
|
|
340
|
-
[option.value]: option,
|
|
341
|
-
}));
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
const unregister = (value: string) => {
|
|
345
|
-
setOptions((prevOptions) => {
|
|
346
|
-
const { [value]: _, ...rest } = prevOptions;
|
|
347
|
-
return rest;
|
|
348
|
-
});
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
return (
|
|
352
|
-
<OptionContext.Provider value={{ options, register, unregister }}>
|
|
353
|
-
<>
|
|
354
|
-
<SelectBox {...props} />
|
|
355
|
-
{props.children}
|
|
356
|
-
</>
|
|
357
|
-
</OptionContext.Provider>
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function SelectBox({
|
|
375
|
+
function SelectBox<
|
|
376
|
+
OptionType extends OptionTypeBase,
|
|
377
|
+
IsMulti extends boolean,
|
|
378
|
+
GroupType extends GroupTypeBase<OptionType>,
|
|
379
|
+
>({
|
|
362
380
|
placeholder = 'Select...',
|
|
363
381
|
disabled = false,
|
|
364
382
|
value,
|
|
@@ -367,15 +385,68 @@ function SelectBox({
|
|
|
367
385
|
className,
|
|
368
386
|
size = '1',
|
|
369
387
|
id,
|
|
388
|
+
selectRef,
|
|
370
389
|
...rest
|
|
371
|
-
}: SelectProps
|
|
390
|
+
}: SelectProps & {
|
|
391
|
+
selectRef?: React.Ref<SelectRef<OptionType, IsMulti, GroupType>>;
|
|
392
|
+
}) {
|
|
372
393
|
const [keyboardFocusEnabled, setKeyboardFocusEnabled] = useState(false);
|
|
373
394
|
const [searchSelection, setSearchSelection] = useState('');
|
|
374
395
|
const [searchValue, setSearchValue] = useState('');
|
|
375
396
|
const [customPlaceholder, setPlaceholder] = useState(placeholder);
|
|
376
397
|
const isDefaultVariant = variant === 'default';
|
|
377
398
|
const [isMenuBottom, setIsMenuBottom] = useState(true);
|
|
378
|
-
const
|
|
399
|
+
const internalSelectRef = useRef<
|
|
400
|
+
ReactSelect<OptionType, IsMulti, GroupType> & {
|
|
401
|
+
setState: (state: { menuIsOpen: boolean }) => void;
|
|
402
|
+
state: { isOpen: boolean };
|
|
403
|
+
select: {
|
|
404
|
+
setValue: (option: SelectOptionProps) => void;
|
|
405
|
+
clearValue: () => void;
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
>(null);
|
|
409
|
+
|
|
410
|
+
useImperativeHandle(
|
|
411
|
+
selectRef,
|
|
412
|
+
() => ({
|
|
413
|
+
focus: () => {
|
|
414
|
+
if (internalSelectRef.current) {
|
|
415
|
+
internalSelectRef.current.focus();
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
blur: () => {
|
|
419
|
+
if (internalSelectRef.current) {
|
|
420
|
+
internalSelectRef.current.blur();
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
select: internalSelectRef.current,
|
|
424
|
+
openMenu: () => {
|
|
425
|
+
if (internalSelectRef.current) {
|
|
426
|
+
internalSelectRef.current.setState({ menuIsOpen: true });
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
closeMenu: () => {
|
|
430
|
+
if (internalSelectRef.current) {
|
|
431
|
+
internalSelectRef.current.setState({ menuIsOpen: false });
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
setValue: (newValue: string) => {
|
|
435
|
+
if (internalSelectRef.current) {
|
|
436
|
+
const option = options.find((opt) => opt.value === newValue);
|
|
437
|
+
if (option) {
|
|
438
|
+
internalSelectRef.current.select.setValue(option);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
clearValue: () => {
|
|
443
|
+
if (internalSelectRef.current && internalSelectRef.current.select) {
|
|
444
|
+
internalSelectRef.current.select.clearValue();
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
}),
|
|
448
|
+
[internalSelectRef],
|
|
449
|
+
);
|
|
379
450
|
|
|
380
451
|
const options = useOptions();
|
|
381
452
|
|
|
@@ -385,8 +456,8 @@ function SelectBox({
|
|
|
385
456
|
onChange(newValue);
|
|
386
457
|
}
|
|
387
458
|
|
|
388
|
-
if (options && options.length > NOPT_SEARCH) {
|
|
389
|
-
|
|
459
|
+
if (options && options.length > NOPT_SEARCH && internalSelectRef.current) {
|
|
460
|
+
internalSelectRef.current.blur();
|
|
390
461
|
}
|
|
391
462
|
};
|
|
392
463
|
|
|
@@ -414,12 +485,12 @@ function SelectBox({
|
|
|
414
485
|
if (
|
|
415
486
|
!isEmptyStringInOptions &&
|
|
416
487
|
value === '' &&
|
|
417
|
-
|
|
418
|
-
|
|
488
|
+
internalSelectRef.current &&
|
|
489
|
+
internalSelectRef.current.select
|
|
419
490
|
) {
|
|
420
|
-
|
|
491
|
+
internalSelectRef.current.select.clearValue();
|
|
421
492
|
}
|
|
422
|
-
}, [value,
|
|
493
|
+
}, [value, isEmptyStringInOptions]);
|
|
423
494
|
|
|
424
495
|
return (
|
|
425
496
|
<ScrollbarWrapper>
|
|
@@ -454,7 +525,7 @@ function SelectBox({
|
|
|
454
525
|
ITEMS_PER_SCROLL_WINDOW={ITEMS_PER_SCROLL_WINDOW}
|
|
455
526
|
onChange={handleChange}
|
|
456
527
|
onInputChange={handleSearchInput}
|
|
457
|
-
ref={
|
|
528
|
+
ref={internalSelectRef}
|
|
458
529
|
isMenuBottom={isMenuBottom}
|
|
459
530
|
setIsMenuBottom={setIsMenuBottom}
|
|
460
531
|
onBlur={rest.onBlur}
|
|
@@ -464,10 +535,10 @@ function SelectBox({
|
|
|
464
535
|
if (
|
|
465
536
|
event &&
|
|
466
537
|
event.key === 'Enter' &&
|
|
467
|
-
|
|
468
|
-
!
|
|
538
|
+
internalSelectRef.current &&
|
|
539
|
+
!internalSelectRef.current.state.isOpen
|
|
469
540
|
) {
|
|
470
|
-
|
|
541
|
+
internalSelectRef.current.setState({
|
|
471
542
|
menuIsOpen: true,
|
|
472
543
|
});
|
|
473
544
|
} else {
|
|
@@ -483,5 +554,40 @@ function SelectBox({
|
|
|
483
554
|
);
|
|
484
555
|
}
|
|
485
556
|
|
|
557
|
+
const SelectWithOptionContext = forwardRef<
|
|
558
|
+
SelectRef<OptionTypeBase, boolean, GroupTypeBase<OptionTypeBase>>,
|
|
559
|
+
SelectProps
|
|
560
|
+
>((props, ref) => {
|
|
561
|
+
const [options, setOptions] = useState<Record<string, SelectOptionProps>>({});
|
|
562
|
+
|
|
563
|
+
const register = (option: SelectOptionProps) => {
|
|
564
|
+
setOptions((prevOptions) => ({
|
|
565
|
+
...prevOptions,
|
|
566
|
+
[option.value]: option,
|
|
567
|
+
}));
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const unregister = (value: string) => {
|
|
571
|
+
setOptions((prevOptions) => {
|
|
572
|
+
const { [value]: _, ...rest } = prevOptions;
|
|
573
|
+
return rest;
|
|
574
|
+
});
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
return (
|
|
578
|
+
<OptionContext.Provider value={{ options, register, unregister }}>
|
|
579
|
+
<>
|
|
580
|
+
<SelectBox {...props} selectRef={ref} />
|
|
581
|
+
{props.children}
|
|
582
|
+
</>
|
|
583
|
+
</OptionContext.Provider>
|
|
584
|
+
);
|
|
585
|
+
}) as SelectComponentType<
|
|
586
|
+
OptionTypeBase,
|
|
587
|
+
boolean,
|
|
588
|
+
GroupTypeBase<OptionTypeBase>
|
|
589
|
+
>;
|
|
590
|
+
|
|
591
|
+
SelectWithOptionContext.displayName = 'Select';
|
|
486
592
|
SelectWithOptionContext.Option = Option;
|
|
487
593
|
export const Select = SelectWithOptionContext;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { screen, render as testingRender } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import React, { useState } from 'react';
|
|
3
|
+
import React, { useState, useRef } from 'react';
|
|
4
4
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
|
5
|
-
import { Option, Select } from '../selectv2/Selectv2.component';
|
|
5
|
+
import { Option, Select, SelectRef } from '../selectv2/Selectv2.component';
|
|
6
6
|
|
|
7
7
|
const render = (args) => {
|
|
8
8
|
return testingRender(
|
|
@@ -452,4 +452,158 @@ describe('SelectV2', () => {
|
|
|
452
452
|
*/
|
|
453
453
|
await userEvent.click(screen.getByRole('option', { name: /account 1/i }));
|
|
454
454
|
});
|
|
455
|
+
|
|
456
|
+
describe('Ref API', () => {
|
|
457
|
+
it('should expose focus method via ref', async () => {
|
|
458
|
+
const RefTestComponent = () => {
|
|
459
|
+
const selectRef = useRef<SelectRef>(null);
|
|
460
|
+
const [value, setValue] = useState<string>('');
|
|
461
|
+
|
|
462
|
+
return (
|
|
463
|
+
<div>
|
|
464
|
+
<button onClick={() => selectRef.current?.focus()}>Focus</button>
|
|
465
|
+
<Select
|
|
466
|
+
id="ref-test"
|
|
467
|
+
value={value}
|
|
468
|
+
onChange={(value) => setValue(value)}
|
|
469
|
+
ref={selectRef}
|
|
470
|
+
placeholder="Select with ref"
|
|
471
|
+
>
|
|
472
|
+
{simpleOptions}
|
|
473
|
+
</Select>
|
|
474
|
+
</div>
|
|
475
|
+
);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
render(<RefTestComponent />);
|
|
479
|
+
expect(selectors.input()).not.toHaveFocus();
|
|
480
|
+
|
|
481
|
+
userEvent.click(screen.getByRole('button', { name: /Focus/i }));
|
|
482
|
+
expect(selectors.input()).toHaveFocus();
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('should expose openMenu and closeMenu methods via ref', async () => {
|
|
486
|
+
const RefTestComponent = () => {
|
|
487
|
+
const selectRef = useRef<SelectRef>(null);
|
|
488
|
+
const [value, setValue] = useState<string>('');
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<div>
|
|
492
|
+
<button onClick={() => selectRef.current?.openMenu()}>
|
|
493
|
+
Open Menu
|
|
494
|
+
</button>
|
|
495
|
+
<button onClick={() => selectRef.current?.closeMenu()}>
|
|
496
|
+
Close Menu
|
|
497
|
+
</button>
|
|
498
|
+
<Select
|
|
499
|
+
id="ref-test"
|
|
500
|
+
value={value}
|
|
501
|
+
onChange={(value) => setValue(value)}
|
|
502
|
+
ref={selectRef}
|
|
503
|
+
placeholder="Select with ref"
|
|
504
|
+
>
|
|
505
|
+
{simpleOptions}
|
|
506
|
+
</Select>
|
|
507
|
+
</div>
|
|
508
|
+
);
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
render(<RefTestComponent />);
|
|
512
|
+
expect(selectors.options()).toHaveLength(0);
|
|
513
|
+
|
|
514
|
+
userEvent.click(screen.getByRole('button', { name: /Open Menu/i }));
|
|
515
|
+
expect(selectors.options().length).toBeGreaterThan(0);
|
|
516
|
+
simpleOptions.forEach((opt) => {
|
|
517
|
+
const option = selectors.option(opt.props.label);
|
|
518
|
+
expect(option).toBeInTheDocument();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
userEvent.click(screen.getByRole('button', { name: /Close Menu/i }));
|
|
522
|
+
expect(selectors.options()).toHaveLength(0);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should expose setValue and clearValue methods via ref', async () => {
|
|
526
|
+
const handleChange = jest.fn();
|
|
527
|
+
|
|
528
|
+
const RefTestComponent = () => {
|
|
529
|
+
const [value, setValue] = useState('');
|
|
530
|
+
const selectRef = useRef<SelectRef>(null);
|
|
531
|
+
|
|
532
|
+
return (
|
|
533
|
+
<div>
|
|
534
|
+
<button
|
|
535
|
+
onClick={() => {
|
|
536
|
+
selectRef.current?.setValue('0');
|
|
537
|
+
setValue('0');
|
|
538
|
+
}}
|
|
539
|
+
>
|
|
540
|
+
Set Value
|
|
541
|
+
</button>
|
|
542
|
+
<button
|
|
543
|
+
onClick={() => {
|
|
544
|
+
selectRef.current?.clearValue();
|
|
545
|
+
setValue('');
|
|
546
|
+
}}
|
|
547
|
+
>
|
|
548
|
+
Clear
|
|
549
|
+
</button>
|
|
550
|
+
<Select
|
|
551
|
+
id="ref-test"
|
|
552
|
+
value={value}
|
|
553
|
+
onChange={(newValue) => {
|
|
554
|
+
setValue(newValue);
|
|
555
|
+
handleChange(newValue);
|
|
556
|
+
}}
|
|
557
|
+
ref={selectRef}
|
|
558
|
+
placeholder="Select with ref"
|
|
559
|
+
>
|
|
560
|
+
{simpleOptions}
|
|
561
|
+
</Select>
|
|
562
|
+
</div>
|
|
563
|
+
);
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
render(<RefTestComponent />);
|
|
567
|
+
|
|
568
|
+
const select = selectors.select();
|
|
569
|
+
expect(select).toHaveTextContent('Select with ref');
|
|
570
|
+
|
|
571
|
+
userEvent.click(screen.getByRole('button', { name: /Set Value/i }));
|
|
572
|
+
expect(select).toHaveTextContent('Item 0');
|
|
573
|
+
|
|
574
|
+
userEvent.click(screen.getByRole('button', { name: /Clear/i }));
|
|
575
|
+
expect(select).toHaveTextContent('Select with ref');
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('should expose blur method via ref', async () => {
|
|
579
|
+
const RefTestComponent = () => {
|
|
580
|
+
const selectRef = useRef<SelectRef>(null);
|
|
581
|
+
const [value, setValue] = useState<string>('');
|
|
582
|
+
|
|
583
|
+
return (
|
|
584
|
+
<div>
|
|
585
|
+
<button onClick={() => selectRef.current?.focus()}>Focus</button>
|
|
586
|
+
<button onClick={() => selectRef.current?.blur()}>Blur</button>
|
|
587
|
+
<Select
|
|
588
|
+
id="ref-test"
|
|
589
|
+
value={value}
|
|
590
|
+
onChange={(value) => setValue(value)}
|
|
591
|
+
ref={selectRef}
|
|
592
|
+
placeholder="Select with ref"
|
|
593
|
+
>
|
|
594
|
+
{simpleOptions}
|
|
595
|
+
</Select>
|
|
596
|
+
</div>
|
|
597
|
+
);
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
render(<RefTestComponent />);
|
|
601
|
+
|
|
602
|
+
userEvent.click(screen.getByRole('button', { name: /Focus/i }));
|
|
603
|
+
expect(selectors.input()).toHaveFocus();
|
|
604
|
+
|
|
605
|
+
userEvent.click(screen.getByRole('button', { name: /Blur/i }));
|
|
606
|
+
expect(selectors.input()).not.toHaveFocus();
|
|
607
|
+
});
|
|
608
|
+
});
|
|
455
609
|
});
|