Merge pull request #4147 from kamre/multi-select-fix

Improvements for multi-select property
This commit is contained in:
Scott Bishel 2022-11-28 13:57:50 -07:00 committed by GitHub
commit ca15e12ceb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 5 deletions

View file

@ -57,6 +57,12 @@ describe('properties/multiSelect', () => {
const board = createBoard() const board = createBoard()
const card = createCard() const card = createCard()
const expectOptionsMenuToBeVisible = (template: IPropertyTemplate) => {
for (const option of template.options) {
expect(screen.getByRole('menuitem', {name: option.value})).toBeInTheDocument()
}
}
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks() jest.resetAllMocks()
}) })
@ -128,6 +134,7 @@ describe('properties/multiSelect', () => {
userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), 'b{enter}') userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), 'b{enter}')
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, ['multi-option-1', 'multi-option-2']) expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, ['multi-option-1', 'multi-option-2'])
expectOptionsMenuToBeVisible(propertyTemplate)
}) })
it('can unselect a option', async () => { it('can unselect a option', async () => {
@ -152,6 +159,58 @@ describe('properties/multiSelect', () => {
userEvent.click(screen.getAllByRole('button', {name: /clear/i})[0]) userEvent.click(screen.getAllByRole('button', {name: /clear/i})[0])
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, ['multi-option-2']) expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, ['multi-option-2'])
expectOptionsMenuToBeVisible(propertyTemplate)
})
it('can unselect a option via backspace', async () => {
const propertyTemplate = buildMultiSelectPropertyTemplate()
const propertyValue = ['multi-option-1', 'multi-option-2']
render(
<MultiSelect
property={new MultiSelectProperty()}
readOnly={false}
showEmptyPlaceholder={false}
propertyTemplate={propertyTemplate}
propertyValue={propertyValue}
board={{...board}}
card={{...card}}
/>,
{wrapper: Wrapper},
)
userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))
userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), '{backspace}')
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, ['multi-option-1'])
expectOptionsMenuToBeVisible(propertyTemplate)
})
it('can close menu on escape', async () => {
const propertyTemplate = buildMultiSelectPropertyTemplate()
const propertyValue = ['multi-option-1', 'multi-option-2']
render(
<MultiSelect
property={new MultiSelectProperty()}
readOnly={false}
showEmptyPlaceholder={false}
propertyTemplate={propertyTemplate}
propertyValue={propertyValue}
board={{...board}}
card={{...card}}
/>,
{wrapper: Wrapper},
)
userEvent.click(screen.getByTestId(nonEditableMultiSelectTestId))
userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), '{escape}')
for (const option of propertyTemplate.options) {
expect(screen.queryByRole('menuitem', {name: option.value})).toBeNull()
}
}) })
it('can create a new option', async () => { it('can create a new option', async () => {
@ -177,6 +236,7 @@ describe('properties/multiSelect', () => {
userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), 'new-value{enter}') userEvent.type(screen.getByRole('combobox', {name: /value selector/i}), 'new-value{enter}')
expect(mockedMutator.insertPropertyOption).toHaveBeenCalledWith(board.id, board.cardProperties, propertyTemplate, expect.objectContaining({value: 'new-value'}), 'add property option') expect(mockedMutator.insertPropertyOption).toHaveBeenCalledWith(board.id, board.cardProperties, propertyTemplate, expect.objectContaining({value: 'new-value'}), 'add property option')
expectOptionsMenuToBeVisible(propertyTemplate)
}) })
it('can delete a option', () => { it('can delete a option', () => {

View file

@ -30,8 +30,8 @@
.IconButton.delete-value { .IconButton.delete-value {
@include z-index(value-selector-delete); @include z-index(value-selector-delete);
width: 16px; width: 16px;
min-width: 16px;
height: 16px; height: 16px;
flex: 0 0 auto;
i { i {
font-size: 16px; font-size: 16px;

View file

@ -71,7 +71,10 @@ const ValueSelectorLabel = (props: LabelProps): JSX.Element => {
) )
} }
return ( return (
<div className='value-menu-option'> <div
className='value-menu-option'
role='menuitem'
>
<div className='label-container'> <div className='label-container'>
<Label color={option.color}>{option.value}</Label> <Label color={option.color}>{option.value}</Label>
</div> </div>
@ -162,7 +165,6 @@ function ValueSelector(props: Props): JSX.Element {
captureMenuScroll={true} captureMenuScroll={true}
maxMenuHeight={1200} maxMenuHeight={1200}
isMulti={props.isMulti} isMulti={props.isMulti}
menuIsOpen={true}
isClearable={true} isClearable={true}
styles={valueSelectorStyle} styles={valueSelectorStyle}
formatOptionLabel={(option: IPropertyOption, meta: FormatOptionLabelMeta<IPropertyOption>) => ( formatOptionLabel={(option: IPropertyOption, meta: FormatOptionLabelMeta<IPropertyOption>) => (
@ -181,24 +183,32 @@ function ValueSelector(props: Props): JSX.Element {
getOptionLabel={(o: IPropertyOption) => o.value} getOptionLabel={(o: IPropertyOption) => o.value}
getOptionValue={(o: IPropertyOption) => o.id} getOptionValue={(o: IPropertyOption) => o.id}
onChange={(value: OnChangeValue<IPropertyOption, true | false>, action: ActionMeta<IPropertyOption>): void => { onChange={(value: OnChangeValue<IPropertyOption, true | false>, action: ActionMeta<IPropertyOption>): void => {
if (action.action === 'select-option') { if (action.action === 'select-option' || action.action === 'pop-value') {
if (Array.isArray(value)) { if (Array.isArray(value)) {
props.onChange((value as IPropertyOption[]).map((option) => option.id)) props.onChange((value as IPropertyOption[]).map((option) => option.id))
} else { } else {
props.onChange((value as IPropertyOption).id) props.onChange((value as IPropertyOption).id)
props.onBlur?.()
} }
} else if (action.action === 'clear') { } else if (action.action === 'clear') {
props.onChange('') props.onChange('')
} }
}} }}
onKeyDown={(event) => {
if (event.key === 'Escape') {
props.onBlur?.()
}
}}
onBlur={props.onBlur} onBlur={props.onBlur}
onCreateOption={props.onCreate} onCreateOption={props.onCreate}
autoFocus={true} autoFocus={true}
value={props.value || null} value={props.value || null}
closeMenuOnSelect={true} closeMenuOnSelect={!props.isMulti}
placeholder={props.emptyValue} placeholder={props.emptyValue}
hideSelectedOptions={false} hideSelectedOptions={false}
defaultMenuIsOpen={true} defaultMenuIsOpen={true}
menuIsOpen={props.isMulti}
blurInputOnSelect={!props.isMulti}
/> />
) )
} }