@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.
@@ -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;AAkDF,eAAO,MAAM,SAAS,sDAOnB,cAAc,4CA4DhB,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
- ${({ theme }) => `
12
- border: 0.5px solid ${theme.border};
13
- border-radius: 4px;
14
- padding: ${spacing.r16};
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;AAmInD,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
+ {"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"}
@@ -6,6 +6,7 @@ const Container = styled.div `
6
6
  flex-direction: row;
7
7
  flex: 1;
8
8
  overflow: hidden;
9
+ color: ${(props) => props.theme.textPrimary};
9
10
  `;
10
11
  const FillAvailableFlexBox = styled.div `
11
12
  flex: 1;
@@ -1,3 +1,4 @@
1
- declare const SelectStyle: import("styled-components").StyledComponent<any, import("styled-components").DefaultTheme, any, any>;
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":"AAMA,QAAA,MAAM,WAAW,sGAmQhB,CAAC;AACF,OAAO,EAAE,WAAW,EAAE,CAAC"}
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
- declare function SelectWithOptionContext(props: SelectProps): import("react/jsx-runtime").JSX.Element;
27
- declare namespace SelectWithOptionContext {
28
- var Option: typeof import("./Selectv2.component").Option;
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,KAMN,MAAM,OAAO,CAAC;AAcf,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;AAiOD,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;AAgBF,iBAAS,uBAAuB,CAAC,KAAK,EAAE,WAAW,2CAyBlD;kBAzBQ,uBAAuB;;;AAyJhC,eAAO,MAAM,MAAM,gCAA0B,CAAC"}
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 SelectWithOptionContext(props) {
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 selectRef = useRef();
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
- selectRef.current.blur();
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
- selectRef.current &&
239
- selectRef.current.select) {
240
- selectRef.current.select.clearValue();
258
+ internalSelectRef.current &&
259
+ internalSelectRef.current.select) {
260
+ internalSelectRef.current.select.clearValue();
241
261
  }
242
- }, [value, selectRef, isEmptyStringInOptions]);
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: selectRef, isMenuBottom: isMenuBottom, setIsMenuBottom: setIsMenuBottom, onBlur: rest.onBlur, onFocus: rest.onFocus, onMenuClose: () => setKeyboardFocusEnabled(false), onKeyDown: (event) => {
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
- selectRef &&
255
- !selectRef.current.state.isOpen) {
256
- selectRef.current.setState({
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.159.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
- ${({ theme }) =>
24
- `
25
- border: 0.5px solid ${theme.border};
26
- border-radius: 4px;
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<{
@@ -8,6 +8,7 @@ const Container = styled.div`
8
8
  flex-direction: row;
9
9
  flex: 1;
10
10
  overflow: hidden;
11
+ color: ${(props) => props.theme.textPrimary};
11
12
  `;
12
13
 
13
14
  const FillAvailableFlexBox = styled.div`
@@ -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 { components } from 'react-select';
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 = ({ children, ...props }) => {
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 SelectWithOptionContext(props: SelectProps) {
335
- const [options, setOptions] = useState<Record<string, SelectOptionProps>>({});
336
-
337
- const register = (option: SelectOptionProps) => {
338
- setOptions((prevOptions) => ({
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 selectRef = useRef<any>();
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
- selectRef.current.blur();
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
- selectRef.current &&
418
- selectRef.current.select
488
+ internalSelectRef.current &&
489
+ internalSelectRef.current.select
419
490
  ) {
420
- selectRef.current.select.clearValue();
491
+ internalSelectRef.current.select.clearValue();
421
492
  }
422
- }, [value, selectRef, isEmptyStringInOptions]);
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={selectRef}
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
- selectRef &&
468
- !selectRef.current.state.isOpen
538
+ internalSelectRef.current &&
539
+ !internalSelectRef.current.state.isOpen
469
540
  ) {
470
- selectRef.current.setState({
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
  });