Skip to content

Commit

Permalink
fix: multiselect relationship bug and improve accessibility (#6286)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
JessChowdhury committed May 15, 2024
1 parent 0e9bbec commit 5a4074e
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@

.clear-indicator {
cursor: pointer;

&:focus-visible {
outline: var(--accessibility-outline);
}
}
15 changes: 14 additions & 1 deletion packages/ui/src/elements/ReactSelect/ClearIndicator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,24 @@ const baseClass = 'clear-indicator'

export const ClearIndicator: React.FC<ClearIndicatorProps<OptionType, true>> = (props) => {
const {
clearValue,
innerProps: { ref, ...restInnerProps },
} = props

return (
<div className={baseClass} ref={ref} {...restInnerProps}>
<div
className={baseClass}
ref={ref}
{...restInnerProps}
onKeyDown={(e) => {
if (e.key === 'Enter') {
clearValue()
e.stopPropagation()
}
}}
role="button"
tabIndex={0}
>
<X className={`${baseClass}__icon`} />
</div>
)
Expand Down
16 changes: 16 additions & 0 deletions packages/ui/src/elements/ReactSelect/DropdownIndicator/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@import '../../../scss/styles.scss';

.dropdown-indicator {
cursor: pointer;
@include btn-reset;

&:focus-visible {
outline: var(--accessibility-outline);
}

&__icon {
.stroke {
stroke-width: 1px;
}
}
}
33 changes: 33 additions & 0 deletions packages/ui/src/elements/ReactSelect/DropdownIndicator/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { DropdownIndicatorProps } from 'react-select'

import { Chevron } from '@payloadcms/ui/icons/Chevron'
import React from 'react'

import type { Option as OptionType } from '../types.js'

import './index.scss'

const baseClass = 'dropdown-indicator'
export const DropdownIndicator: React.FC<
DropdownIndicatorProps<OptionType, true> & {
innerProps: JSX.IntrinsicElements['button']
}
> = (props) => {
const {
innerProps: { ref, ...restInnerProps },
} = props

return (
<button
className={baseClass}
ref={ref}
{...restInnerProps}
onKeyDown={(e) => {
if (e.key === 'Enter') e.key = ' '
}}
type="button"
>
<Chevron className={`${baseClass}__icon`} />
</button>
)
}
3 changes: 2 additions & 1 deletion packages/ui/src/elements/ReactSelect/MultiValue/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const MultiValue: React.FC<MultiValueProps<Option>> = (props) => {
} = props

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

Expand Down Expand Up @@ -51,6 +51,7 @@ export const MultiValue: React.FC<MultiValueProps<Option>> = (props) => {
ref: setNodeRef,
style: {
transform,
...attributes?.style,
},
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@
overflow: hidden;
white-space: nowrap;
}

&:focus-visible {
outline: var(--accessibility-outline);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export const MultiValueRemove: React.FC<
setShowTooltip(false)
onClick(e)
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.stopPropagation()
}
}}
onMouseDown={(e) => e.stopPropagation()}
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
onTouchEnd={onTouchEnd}
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/elements/ReactSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import type { Option } from './types.js'
import type { Props as ReactSelectAdapterProps } from './types.js'
export type { Option } from './types.js'

import { Chevron } from '../../icons/Chevron/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { DraggableSortable } from '../DraggableSortable/index.js'
import { ClearIndicator } from './ClearIndicator/index.js'
import { Control } from './Control/index.js'
import { DropdownIndicator } from './DropdownIndicator/index.js'
import { MultiValue } from './MultiValue/index.js'
import { MultiValueLabel } from './MultiValueLabel/index.js'
import { MultiValueRemove } from './MultiValueRemove/index.js'
Expand Down Expand Up @@ -73,7 +73,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
components={{
ClearIndicator,
Control,
DropdownIndicator: Chevron,
DropdownIndicator,
MultiValue,
MultiValueLabel,
MultiValueRemove,
Expand Down Expand Up @@ -144,7 +144,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
components={{
ClearIndicator,
Control,
DropdownIndicator: Chevron,
DropdownIndicator,
MultiValue,
MultiValueLabel,
MultiValueRemove,
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/fields/Relationship/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
onSave,
setDrawerIsOpen,
}}
disabled={readOnly || formProcessing}
disabled={readOnly || formProcessing || drawerIsOpen}
filterOption={enableWordBoundarySearch ? filterOption : undefined}
isLoading={isLoading}
isMulti={hasMany}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,9 @@
&:hover {
background-color: var(--theme-elevation-100);
}

&:focus-visible {
outline: var(--accessibility-outline);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export const MultiValueLabel: React.FC<MultiValueProps<Option>> = (props) => {
aria-label={`Edit ${label}`}
className={`${baseClass}__drawer-toggler`}
onClick={() => setShowTooltip(false)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.stopPropagation()
}
}}
onMouseDown={(e) => e.stopPropagation()} // prevents react-select dropdown from opening
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
Expand All @@ -69,7 +74,7 @@ export const MultiValueLabel: React.FC<MultiValueProps<Option>> = (props) => {
<Tooltip className={`${baseClass}__tooltip`} show={showTooltip}>
{t('general:editLabel', { label: '' })}
</Tooltip>
<Edit />
<Edit className={`${baseClass}__icon`} />
</DocumentDrawerToggler>
<DocumentDrawer onSave={/* onSave */ null} />
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
height: base(0.75);
}

&:focus {
outline: none;
&:focus-visible {
outline: var(--accessibility-outline);
}

&:hover {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export const SingleValue: React.FC<SingleValueProps<Option>> = (props) => {
aria-label={t('general:editLabel', { label })}
className={`${baseClass}__drawer-toggler`}
onClick={() => setShowTooltip(false)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.stopPropagation()
}
}}
onMouseDown={(e) => e.stopPropagation()} // prevents react-select dropdown from opening
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
Expand Down
8 changes: 6 additions & 2 deletions packages/ui/src/icons/Edit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import React from 'react'

import './index.scss'

export const Edit: React.FC = () => (
<svg className="icon icon--edit" viewBox="0 0 25 25" xmlns="http://www.w3.org/2000/svg">
export const Edit: React.FC<{ className?: string }> = ({ className }) => (
<svg
className={[className, 'icon icon--edit'].filter(Boolean).join(' ')}
viewBox="0 0 25 25"
xmlns="http://www.w3.org/2000/svg"
>
<polygon
className="fill"
points="16.92 16.86 8.25 16.86 8.25 8.21 12.54 8.21 12.54 6.63 6.68 6.63 6.68 18.43 18.5 18.43 18.5 12.53 16.92 12.53 16.92 16.86"
Expand Down

0 comments on commit 5a4074e

Please sign in to comment.