Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Readonly MultiSelect #12435

Merged
merged 11 commits into from
Nov 8, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -4917,6 +4917,9 @@ Map {
"open": Object {
"type": "bool",
},
"readOnly": Object {
"type": "bool",
},
"selectedItems": Object {
"type": "array",
},
Expand Down
13 changes: 10 additions & 3 deletions packages/react/src/components/ListBox/ListBoxSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function ListBoxSelection({
translateWithId: t,
disabled,
onClearSelection,
readOnly,
}) {
const prefix = usePrefix();
const className = cx(`${prefix}--list-box__selection`, {
Expand All @@ -31,7 +32,7 @@ function ListBoxSelection({
});
const handleOnClick = (event) => {
event.stopPropagation();
if (disabled) {
if (disabled || readOnly) {
return;
}
clearSelection(event);
Expand All @@ -41,7 +42,7 @@ function ListBoxSelection({
};
const handleOnKeyDown = (event) => {
event.stopPropagation();
if (disabled) {
if (disabled || readOnly) {
return;
}

Expand Down Expand Up @@ -75,7 +76,8 @@ function ListBoxSelection({
onKeyDown={handleOnKeyDown}
disabled={disabled}
aria-label={t('clear.all')}
title={description}>
title={description}
aria-disabled={readOnly ? true : undefined}>
<Close />
</div>
</div>
Expand Down Expand Up @@ -134,6 +136,11 @@ ListBoxSelection.propTypes = {
*/
onKeyDown: PropTypes.func,

/**
* Whether or not the Dropdown is readonly
*/
readOnly: PropTypes.bool,

/**
* Specify an optional `selectionCount` value that will be used to determine
* whether the selection should display a badge or a single clear icon.
Expand Down
34 changes: 31 additions & 3 deletions packages/react/src/components/MultiSelect/MultiSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const MultiSelect = React.forwardRef(function MultiSelect(
onMenuChange,
direction,
selectedItems: selected,
readOnly,
},
ref
) {
Expand Down Expand Up @@ -169,6 +170,7 @@ const MultiSelect = React.forwardRef(function MultiSelect(
[`${prefix}--multi-select--selected`]:
selectedItems && selectedItems.length > 0,
[`${prefix}--list-box--up`]: direction === 'top',
[`${prefix}--multi-select--readonly`]: readOnly,
}
);

Expand Down Expand Up @@ -234,6 +236,24 @@ const MultiSelect = React.forwardRef(function MultiSelect(
: setIsFocused(evt.type === 'focus' ? true : false);
};

const readOnlyEventHandlers = readOnly
? {
onClick: (evt) => {
// NOTE: does not prevent click
evt.preventDefault();
// focus on the element as per readonly input behavior
evt.target.focus();
},
onKeyDown: (evt) => {
const selectAccessKeys = ['ArrowDown', 'ArrowUp', ' ', 'Enter'];
// This prevents the select from opening for the above keys
if (selectAccessKeys.includes(evt.key)) {
evt.preventDefault();
}
},
}
: {};

return (
<div className={wrapperClasses}>
<label className={titleClasses} {...getLabelProps()}>
Expand All @@ -252,6 +272,7 @@ const MultiSelect = React.forwardRef(function MultiSelect(
size={size}
className={className}
disabled={disabled}
readOnly={readOnly}
light={light}
invalid={invalid}
invalidText={invalidText}
Expand All @@ -271,13 +292,15 @@ const MultiSelect = React.forwardRef(function MultiSelect(
type="button"
className={`${prefix}--list-box__field`}
disabled={disabled}
aria-disabled={disabled}
aria-disabled={disabled || readOnly}
{...toggleButtonProps}
ref={mergeRefs(toggleButtonProps.ref, ref)}
onKeyDown={onKeyDown}>
onKeyDown={onKeyDown}
{...readOnlyEventHandlers}>
{selectedItems.length > 0 && (
<ListBox.Selection
clearSelection={!disabled ? clearSelection : noop}
readOnly={readOnly}
clearSelection={!disabled && !readOnly ? clearSelection : noop}
selectionCount={selectedItems.length}
translateWithId={translateWithId}
disabled={disabled}
Expand Down Expand Up @@ -449,6 +472,11 @@ MultiSelect.propTypes = {
*/
open: PropTypes.bool,

/**
* Whether or not the Dropdown is readonly
*/
readOnly: PropTypes.bool,

/**
* For full control of the selected items
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export default {
open: {
table: { disable: true },
},
readOnly: {
control: { type: 'boolean' },
},
tay1orjones marked this conversation as resolved.
Show resolved Hide resolved
title: {
table: { disable: true },
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,20 @@ describe('MultiSelect', () => {
).toBeFalsy();
});

it('should not be interactive if readonly', () => {
const items = generateItems(4, generateGenericItem);
const label = 'test-label';
const { container } = render(
<MultiSelect id="test" readOnly={true} label={label} items={items} />
);
const labelNode = getByText(container, label);
Simulate.click(labelNode);

expect(
container.querySelector('[aria-expanded="true"][aria-haspopup="listbox"]')
).toBeFalsy();
});

describe('Component API', () => {
it('should set the default selected items with the `initialSelectedItems` prop', () => {
const items = generateItems(4, generateGenericItem);
Expand Down
39 changes: 39 additions & 0 deletions packages/styles/scss/components/multiselect/_multiselect.scss
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,43 @@
.#{$prefix}--list-box__menu {
visibility: hidden;
}

$icon-disabled: $icon-disabled;
tay1orjones marked this conversation as resolved.
Show resolved Hide resolved
.#{$prefix}--multi-select.#{$prefix}--multi-select--readonly,
.#{$prefix}--multi-select.#{$prefix}--multi-select--readonly:hover {
background-color: transparent;
}

.#{$prefix}--multi-select.#{$prefix}--multi-select--readonly
.#{$prefix}--list-box__menu-icon
svg {
fill: $icon-disabled;
}

.#{$prefix}--multi-select.#{$prefix}--multi-select--readonly
.#{$prefix}--tag--filter,
.#{$prefix}--multi-select.#{$prefix}--multi-select--readonly
.#{$prefix}--tag__close-icon:hover {
background-color: transparent;
color: $text-primary;
cursor: default;
}

.#{$prefix}--multi-select.#{$prefix}--multi-select--readonly
.#{$prefix}--tag--filter {
box-shadow: 0 0 0 1px $background-inverse;
}

.#{$prefix}--multi-select.#{$prefix}--multi-select--readonly
.#{$prefix}--tag--filter
svg {
fill: $icon-disabled;
}

.#{$prefix}--multi-select.#{$prefix}--multi-select--readonly
.#{$prefix}--list-box__field,
.#{$prefix}--multi-select.#{$prefix}--multi-select--readonly
.#{$prefix}--list-box__menu-icon {
cursor: default;
}
}