diff --git a/integration/specs/MultiSelect/multiSelect-9.spec.js b/integration/specs/MultiSelect/multiSelect-9.spec.js
new file mode 100644
index 000000000..e722b7378
--- /dev/null
+++ b/integration/specs/MultiSelect/multiSelect-9.spec.js
@@ -0,0 +1,26 @@
+const PageMultiSelect = require('../../../src/components/MultiSelect/pageObject');
+
+const MULTI_SELECT = '#multiselect-component-9';
+
+describe('MultiSelect base', () => {
+ beforeAll(() => {
+ browser.url('/#!/MultiSelect/9');
+ });
+ beforeEach(() => {
+ browser.refresh();
+ const component = $(MULTI_SELECT);
+ component.waitForExist();
+ });
+
+ it('should not put the input element focused when clicked', () => {
+ const input = new PageMultiSelect(MULTI_SELECT);
+ input.click();
+ expect(input.hasFocus()).toBe(false);
+ });
+
+ it('should not put the input element focused when the label element is clicked', () => {
+ const input = new PageMultiSelect(MULTI_SELECT);
+ input.clickLabel();
+ expect(input.hasFocus()).toBe(false);
+ });
+});
diff --git a/src/components/MultiSelect/__test__/multiSelect.spec.js b/src/components/MultiSelect/__test__/multiSelect.spec.js
index 664cac575..61de11eff 100644
--- a/src/components/MultiSelect/__test__/multiSelect.spec.js
+++ b/src/components/MultiSelect/__test__/multiSelect.spec.js
@@ -5,7 +5,7 @@ import Option from '../../Option';
import HelpText from '../../Input/styled/helpText';
import ErrorText from '../../Input/styled/errorText';
import Label from '../../Input/label/labelText';
-import { StyledChip, StyledPlaceholder, StyledInput } from '../styled';
+import { StyledChip, StyledPlaceholder, StyledInput, StyledText } from '../styled';
describe('', () => {
it('should render Label when label prop is passed', () => {
@@ -28,6 +28,21 @@ describe('', () => {
expect(component.find(StyledPlaceholder).exists()).toBe(true);
});
+ it('should render the default variant', () => {
+ const value = [
+ {
+ label: 'First',
+ name: 'first',
+ },
+ {
+ label: 'Second',
+ name: 'second',
+ },
+ ];
+ const component = mount();
+ expect(component.find(StyledText).exists()).toBe(true);
+ });
+
it('should render the correct amount of chips', () => {
const value = [
{
@@ -40,7 +55,7 @@ describe('', () => {
},
];
const component = mount(
-
+
,
@@ -57,7 +72,7 @@ describe('', () => {
];
const mockOnChange = jest.fn();
const component = mount(
-
+
,
@@ -80,4 +95,44 @@ describe('', () => {
component.find(StyledInput).simulate('blur');
expect(mockOnBlur).toHaveBeenCalledTimes(1);
});
+
+ it('should not render the buttons when readOnly', () => {
+ const value = [
+ {
+ label: 'First',
+ name: 'first',
+ },
+ {
+ label: 'Second',
+ name: 'second',
+ },
+ ];
+ const component = mount(
+
+
+
+ ,
+ );
+ expect(component.find('button').exists()).toBe(false);
+ });
+
+ it('should not render the buttons when disabled', () => {
+ const value = [
+ {
+ label: 'First',
+ name: 'first',
+ },
+ {
+ label: 'Second',
+ name: 'second',
+ },
+ ];
+ const component = mount(
+
+
+
+ ,
+ );
+ expect(component.find('button').exists()).toBe(false);
+ });
});
diff --git a/src/components/MultiSelect/chips.js b/src/components/MultiSelect/chips.js
index 1e51e8c32..b45acdcd7 100644
--- a/src/components/MultiSelect/chips.js
+++ b/src/components/MultiSelect/chips.js
@@ -3,19 +3,24 @@ import PropTypes from 'prop-types';
import { StyledChip } from './styled';
function Chips(props) {
- const { value, variant, onDelete } = props;
+ const { value, variant, onDelete, disabled, readOnly } = props;
if (!value) {
return null;
}
+
if (Array.isArray(value)) {
- return value.map(val => (
- onDelete(val)}
- />
- ));
+ return value.map(val => {
+ const onDeleteCallback = disabled || readOnly ? null : () => onDelete(val);
+
+ return (
+
+ );
+ });
}
return onDelete(value)} />;
}
@@ -34,12 +39,16 @@ Chips.propTypes = {
),
]),
variant: PropTypes.oneOf(['base', 'neutral', 'outline-brand', 'brand']),
+ disabled: PropTypes.bool,
+ readOnly: PropTypes.bool,
onDelete: PropTypes.func,
};
Chips.defaultProps = {
value: undefined,
variant: 'base',
+ disabled: undefined,
+ readOnly: undefined,
onDelete: () => {},
};
diff --git a/src/components/MultiSelect/helpers/__test__/getContent.spec.js b/src/components/MultiSelect/helpers/__test__/getContent.spec.js
new file mode 100644
index 000000000..a7df41185
--- /dev/null
+++ b/src/components/MultiSelect/helpers/__test__/getContent.spec.js
@@ -0,0 +1,18 @@
+import getContent from '../getContent';
+
+describe('getContent', () => {
+ it('should return null', () => {
+ const values = [false, true, undefined, null];
+ values.forEach(value => {
+ expect(getContent(value)).toBe(null);
+ });
+ });
+
+ it('should return the right string', () => {
+ const values = [{ label: 'Label' }, [{ label: 'Label 1' }, { label: 'Label 2' }]];
+ const expected = ['Label', 'Label 1, Label 2'];
+ values.forEach((value, index) => {
+ expect(getContent(value)).toBe(expected[index]);
+ });
+ });
+});
diff --git a/src/components/MultiSelect/helpers/getContent.js b/src/components/MultiSelect/helpers/getContent.js
new file mode 100644
index 000000000..2880509b0
--- /dev/null
+++ b/src/components/MultiSelect/helpers/getContent.js
@@ -0,0 +1,9 @@
+export default function getContent(value) {
+ if (!value || typeof value !== 'object') {
+ return null;
+ }
+ if (Array.isArray(value)) {
+ return value.map(item => item.label).join(', ');
+ }
+ return value.label;
+}
diff --git a/src/components/MultiSelect/index.d.ts b/src/components/MultiSelect/index.d.ts
index 423f7675d..770408447 100644
--- a/src/components/MultiSelect/index.d.ts
+++ b/src/components/MultiSelect/index.d.ts
@@ -16,8 +16,9 @@ export interface MultiSelectProps extends BaseProps {
required?: boolean;
disabled?: boolean;
readOnly?: boolean;
- variant?: 'default' | 'bare';
+ variant?: 'default' | 'chip';
chipVariant?: 'base' | 'neutral' | 'outline-brand' | 'brand';
+ isBare?: boolean;
hideLabel?: boolean;
value?: MultiSelectOption[];
onChange?: (value: MultiSelectOption[]) => void;
diff --git a/src/components/MultiSelect/index.js b/src/components/MultiSelect/index.js
index 122134c46..9ce8ef0cf 100644
--- a/src/components/MultiSelect/index.js
+++ b/src/components/MultiSelect/index.js
@@ -7,6 +7,7 @@ import {
useErrorMessageId,
useReduxForm,
useLabelId,
+ useWindowResize,
} from '../../libs/hooks';
import {
StyledInput,
@@ -15,6 +16,7 @@ import {
StyledButtonIcon,
StyledPlaceholder,
StyledCombobox,
+ StyledText,
} from './styled';
import InternalDropdown from '../InternalDropdown';
import InternalOverlay from '../InternalOverlay';
@@ -26,6 +28,7 @@ import { ENTER_KEY, SPACE_KEY, ESCAPE_KEY, TAB_KEY } from '../../libs/constants'
import { hasChips, positionResolver } from './helpers';
import Chips from './chips';
import normalizeValue from './helpers/normalizeValue';
+import getContent from './helpers/getContent';
const MultiSelect = React.forwardRef((props, ref) => {
const {
@@ -40,9 +43,10 @@ const MultiSelect = React.forwardRef((props, ref) => {
required,
disabled,
readOnly,
- tabIndex,
+ tabIndex: tabIndexInProps,
variant,
chipVariant,
+ isBare,
value,
onChange,
onFocus,
@@ -55,13 +59,13 @@ const MultiSelect = React.forwardRef((props, ref) => {
const comboboxRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
- comboboxRef.current.focus();
+ triggerRef.current.focus();
},
click: () => {
- comboboxRef.current.click();
+ triggerRef.current.click();
},
blur: () => {
- comboboxRef.current.blur();
+ triggerRef.current.blur();
},
}));
@@ -77,7 +81,7 @@ const MultiSelect = React.forwardRef((props, ref) => {
setIsOpen(false);
// eslint-disable-next-line no-use-before-define
stopListeningOutsideClick();
- comboboxRef.current.focus();
+ setTimeout(() => triggerRef.current.focus(), 0);
};
const handleChange = val => {
@@ -141,8 +145,22 @@ const MultiSelect = React.forwardRef((props, ref) => {
dropdownRef,
handleOutsideClick,
);
+ useWindowResize(() => setIsOpen(false), isOpen);
const shouldRenderChips = hasChips(value);
+ const tabIndex = disabled || readOnly ? '-1' : tabIndexInProps;
+ const content =
+ variant === 'chip' ? (
+
+ ) : (
+ {getContent(value)}
+ );
return (
@@ -155,7 +173,7 @@ const MultiSelect = React.forwardRef((props, ref) => {
/>
{
onFocus={onFocus}
onBlur={onBlur}
onKeyDown={handleKeyDown}
- tabIndex={tabIndex}
+ tabIndex="-1"
ref={comboboxRef}
aria-labelledby={labelId}
>
@@ -181,20 +199,20 @@ const MultiSelect = React.forwardRef((props, ref) => {
{placeholder}
-
-
-
+ {content}
- }
- onClick={handleTriggerClick}
- disabled={disabled}
- ref={triggerRef}
- tabIndex="-1"
- />
+
+ }
+ onClick={handleTriggerClick}
+ disabled={disabled}
+ ref={triggerRef}
+ tabIndex={tabIndex}
+ />
+
{},
onFocus: () => {},
diff --git a/src/components/MultiSelect/readme.md b/src/components/MultiSelect/readme.md
index ac8bdd37c..70674e99c 100644
--- a/src/components/MultiSelect/readme.md
+++ b/src/components/MultiSelect/readme.md
@@ -38,6 +38,47 @@ const MultiSelectExample = props => {
```
+##### MultiSelect with chip variant
+
+```js
+import React, { useState, useRef } from 'react';
+import { MultiSelect, Option } from 'react-rainbow-components';
+
+const containerStyles = {
+ maxWidth: 400,
+};
+
+const MultiSelectExample = props => {
+ const [value, setValue] = useState([]);
+
+ return (
+
+ } />
+ } />
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ )
+}
+
+
+```
+
##### MultiSelect bare
```js
@@ -60,7 +101,7 @@ const MultiSelectExample = props => {
value={value}
onChange={setValue}
bottomHelpText="You can select several options"
- variant="bare"
+ isBare
>
} />
} />
@@ -140,6 +181,7 @@ const MultiSelectExample = props => {
return (
- props.variant === 'bare' &&
+ props.isBare &&
`
background: transparent;
border-color: transparent;
`}
:focus,
- :active {
+ :active,
+ :focus-within {
outline: 0;
padding: 0.0325rem 0;
border: 2px solid ${props => props.palette.brand.main};
@@ -122,3 +123,12 @@ export const StyledPlaceholder = attachThemeAttrs(styled.span)`
text-overflow: ellipsis;
white-space: nowrap;
`;
+
+export const StyledText = attachThemeAttrs(styled.span)`
+ color: ${props => props.palette.text.main};
+ font-weight: 500;
+ font-size: ${FONT_SIZE_TEXT_LARGE};
+ align-self: center;
+ max-width: 100%;
+ padding: 0.1rem 0.8rem;
+`;
diff --git a/src/libs/hooks/index.js b/src/libs/hooks/index.js
index 6868c00a2..08f00dfc3 100644
--- a/src/libs/hooks/index.js
+++ b/src/libs/hooks/index.js
@@ -6,3 +6,4 @@ export { default as useOutsideClick } from './useOutsideClick';
export { default as useReduxForm } from './useReduxForm';
export { default as useUniqueIdentifier } from './useUniqueIdentifier';
export { default as useDisclosure } from './useDisclosure';
+export { default as useWindowResize } from './useWindowResize';