Skip to content

Commit 5a4074e

Browse files
authored
fix: multiselect relationship bug and improve accessibility (#6286)
## Description Closes [#117](payloadcms/payload-3.0-demo#177) - hitting the space key while the `ReactSelect` is in focus crashes the page. This PR makes the following changes: - Multivalue select component updated to only use `id`, drag feature does not work when using `uuid()` - Ensures relationship field (multi and single value) can be accessed via the keyboard - [X] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [X] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [X] Existing test suite passes locally with my changes
1 parent 0e9bbec commit 5a4074e

File tree

14 files changed

+106
-11
lines changed

14 files changed

+106
-11
lines changed

packages/ui/src/elements/ReactSelect/ClearIndicator/index.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22

33
.clear-indicator {
44
cursor: pointer;
5+
6+
&:focus-visible {
7+
outline: var(--accessibility-outline);
8+
}
59
}

packages/ui/src/elements/ReactSelect/ClearIndicator/index.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,24 @@ const baseClass = 'clear-indicator'
1111

1212
export const ClearIndicator: React.FC<ClearIndicatorProps<OptionType, true>> = (props) => {
1313
const {
14+
clearValue,
1415
innerProps: { ref, ...restInnerProps },
1516
} = props
1617

1718
return (
18-
<div className={baseClass} ref={ref} {...restInnerProps}>
19+
<div
20+
className={baseClass}
21+
ref={ref}
22+
{...restInnerProps}
23+
onKeyDown={(e) => {
24+
if (e.key === 'Enter') {
25+
clearValue()
26+
e.stopPropagation()
27+
}
28+
}}
29+
role="button"
30+
tabIndex={0}
31+
>
1932
<X className={`${baseClass}__icon`} />
2033
</div>
2134
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@import '../../../scss/styles.scss';
2+
3+
.dropdown-indicator {
4+
cursor: pointer;
5+
@include btn-reset;
6+
7+
&:focus-visible {
8+
outline: var(--accessibility-outline);
9+
}
10+
11+
&__icon {
12+
.stroke {
13+
stroke-width: 1px;
14+
}
15+
}
16+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { DropdownIndicatorProps } from 'react-select'
2+
3+
import { Chevron } from '@payloadcms/ui/icons/Chevron'
4+
import React from 'react'
5+
6+
import type { Option as OptionType } from '../types.js'
7+
8+
import './index.scss'
9+
10+
const baseClass = 'dropdown-indicator'
11+
export const DropdownIndicator: React.FC<
12+
DropdownIndicatorProps<OptionType, true> & {
13+
innerProps: JSX.IntrinsicElements['button']
14+
}
15+
> = (props) => {
16+
const {
17+
innerProps: { ref, ...restInnerProps },
18+
} = props
19+
20+
return (
21+
<button
22+
className={baseClass}
23+
ref={ref}
24+
{...restInnerProps}
25+
onKeyDown={(e) => {
26+
if (e.key === 'Enter') e.key = ' '
27+
}}
28+
type="button"
29+
>
30+
<Chevron className={`${baseClass}__icon`} />
31+
</button>
32+
)
33+
}

packages/ui/src/elements/ReactSelect/MultiValue/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const MultiValue: React.FC<MultiValueProps<Option>> = (props) => {
2121
} = props
2222

2323
const { attributes, isDragging, listeners, setNodeRef, transform } = useDraggableSortable({
24-
id: typeof value === 'object' && 'path' in value ? String(value.path) : uuid(),
24+
id: typeof value === 'string' && value.toString(),
2525
disabled: !isSortable,
2626
})
2727

@@ -51,6 +51,7 @@ export const MultiValue: React.FC<MultiValueProps<Option>> = (props) => {
5151
ref: setNodeRef,
5252
style: {
5353
transform,
54+
...attributes?.style,
5455
},
5556
}}
5657
/>

packages/ui/src/elements/ReactSelect/MultiValueLabel/index.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,8 @@
1313
overflow: hidden;
1414
white-space: nowrap;
1515
}
16+
17+
&:focus-visible {
18+
outline: var(--accessibility-outline);
19+
}
1620
}

packages/ui/src/elements/ReactSelect/MultiValueRemove/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ export const MultiValueRemove: React.FC<
3131
setShowTooltip(false)
3232
onClick(e)
3333
}}
34+
onKeyDown={(e) => {
35+
if (e.key === 'Enter') {
36+
e.stopPropagation()
37+
}
38+
}}
39+
onMouseDown={(e) => e.stopPropagation()}
3440
onMouseEnter={() => setShowTooltip(true)}
3541
onMouseLeave={() => setShowTooltip(false)}
3642
onTouchEnd={onTouchEnd}

packages/ui/src/elements/ReactSelect/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import type { Option } from './types.js'
1111
import type { Props as ReactSelectAdapterProps } from './types.js'
1212
export type { Option } from './types.js'
1313

14-
import { Chevron } from '../../icons/Chevron/index.js'
1514
import { useTranslation } from '../../providers/Translation/index.js'
1615
import { DraggableSortable } from '../DraggableSortable/index.js'
1716
import { ClearIndicator } from './ClearIndicator/index.js'
1817
import { Control } from './Control/index.js'
18+
import { DropdownIndicator } from './DropdownIndicator/index.js'
1919
import { MultiValue } from './MultiValue/index.js'
2020
import { MultiValueLabel } from './MultiValueLabel/index.js'
2121
import { MultiValueRemove } from './MultiValueRemove/index.js'
@@ -73,7 +73,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
7373
components={{
7474
ClearIndicator,
7575
Control,
76-
DropdownIndicator: Chevron,
76+
DropdownIndicator,
7777
MultiValue,
7878
MultiValueLabel,
7979
MultiValueRemove,
@@ -144,7 +144,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
144144
components={{
145145
ClearIndicator,
146146
Control,
147-
DropdownIndicator: Chevron,
147+
DropdownIndicator,
148148
MultiValue,
149149
MultiValueLabel,
150150
MultiValueRemove,

packages/ui/src/fields/Relationship/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
492492
onSave,
493493
setDrawerIsOpen,
494494
}}
495-
disabled={readOnly || formProcessing}
495+
disabled={readOnly || formProcessing || drawerIsOpen}
496496
filterOption={enableWordBoundarySearch ? filterOption : undefined}
497497
isLoading={isLoading}
498498
isMulti={hasMany}

packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,9 @@
3434
&:hover {
3535
background-color: var(--theme-elevation-100);
3636
}
37+
38+
&:focus-visible {
39+
outline: var(--accessibility-outline);
40+
}
3741
}
3842
}

0 commit comments

Comments
 (0)