From 5a4074e90a80757fce5daa9d0454d195577e7749 Mon Sep 17 00:00:00 2001 From: Jessica Chowdhury <67977755+JessChowdhury@users.noreply.github.com> Date: Wed, 15 May 2024 14:33:47 +0100 Subject: [PATCH] fix: multiselect relationship bug and improve accessibility (#6286) ## Description Closes [#117](https://github.com/payloadcms/payload-3.0-demo/issues/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 --- .../ReactSelect/ClearIndicator/index.scss | 4 +++ .../ReactSelect/ClearIndicator/index.tsx | 15 ++++++++- .../ReactSelect/DropdownIndicator/index.scss | 16 +++++++++ .../ReactSelect/DropdownIndicator/index.tsx | 33 +++++++++++++++++++ .../elements/ReactSelect/MultiValue/index.tsx | 3 +- .../ReactSelect/MultiValueLabel/index.scss | 4 +++ .../ReactSelect/MultiValueRemove/index.tsx | 6 ++++ .../ui/src/elements/ReactSelect/index.tsx | 6 ++-- packages/ui/src/fields/Relationship/index.tsx | 2 +- .../MultiValueLabel/index.scss | 4 +++ .../MultiValueLabel/index.tsx | 7 +++- .../select-components/SingleValue/index.scss | 4 +-- .../select-components/SingleValue/index.tsx | 5 +++ packages/ui/src/icons/Edit/index.tsx | 8 +++-- 14 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 packages/ui/src/elements/ReactSelect/DropdownIndicator/index.scss create mode 100644 packages/ui/src/elements/ReactSelect/DropdownIndicator/index.tsx diff --git a/packages/ui/src/elements/ReactSelect/ClearIndicator/index.scss b/packages/ui/src/elements/ReactSelect/ClearIndicator/index.scss index cb83ca122b..694307444b 100644 --- a/packages/ui/src/elements/ReactSelect/ClearIndicator/index.scss +++ b/packages/ui/src/elements/ReactSelect/ClearIndicator/index.scss @@ -2,4 +2,8 @@ .clear-indicator { cursor: pointer; + + &:focus-visible { + outline: var(--accessibility-outline); + } } diff --git a/packages/ui/src/elements/ReactSelect/ClearIndicator/index.tsx b/packages/ui/src/elements/ReactSelect/ClearIndicator/index.tsx index f5f8ac72f7..d51ebd2bb6 100644 --- a/packages/ui/src/elements/ReactSelect/ClearIndicator/index.tsx +++ b/packages/ui/src/elements/ReactSelect/ClearIndicator/index.tsx @@ -11,11 +11,24 @@ const baseClass = 'clear-indicator' export const ClearIndicator: React.FC> = (props) => { const { + clearValue, innerProps: { ref, ...restInnerProps }, } = props return ( -
+
{ + if (e.key === 'Enter') { + clearValue() + e.stopPropagation() + } + }} + role="button" + tabIndex={0} + >
) diff --git a/packages/ui/src/elements/ReactSelect/DropdownIndicator/index.scss b/packages/ui/src/elements/ReactSelect/DropdownIndicator/index.scss new file mode 100644 index 0000000000..3c1af3c3bd --- /dev/null +++ b/packages/ui/src/elements/ReactSelect/DropdownIndicator/index.scss @@ -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; + } + } +} diff --git a/packages/ui/src/elements/ReactSelect/DropdownIndicator/index.tsx b/packages/ui/src/elements/ReactSelect/DropdownIndicator/index.tsx new file mode 100644 index 0000000000..e90da3bd1c --- /dev/null +++ b/packages/ui/src/elements/ReactSelect/DropdownIndicator/index.tsx @@ -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 & { + innerProps: JSX.IntrinsicElements['button'] + } +> = (props) => { + const { + innerProps: { ref, ...restInnerProps }, + } = props + + return ( + + ) +} diff --git a/packages/ui/src/elements/ReactSelect/MultiValue/index.tsx b/packages/ui/src/elements/ReactSelect/MultiValue/index.tsx index 76c138f5eb..9db4c4b111 100644 --- a/packages/ui/src/elements/ReactSelect/MultiValue/index.tsx +++ b/packages/ui/src/elements/ReactSelect/MultiValue/index.tsx @@ -21,7 +21,7 @@ export const MultiValue: React.FC> = (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, }) @@ -51,6 +51,7 @@ export const MultiValue: React.FC> = (props) => { ref: setNodeRef, style: { transform, + ...attributes?.style, }, }} /> diff --git a/packages/ui/src/elements/ReactSelect/MultiValueLabel/index.scss b/packages/ui/src/elements/ReactSelect/MultiValueLabel/index.scss index fef4f5c76d..6b2b3c8c6c 100644 --- a/packages/ui/src/elements/ReactSelect/MultiValueLabel/index.scss +++ b/packages/ui/src/elements/ReactSelect/MultiValueLabel/index.scss @@ -13,4 +13,8 @@ overflow: hidden; white-space: nowrap; } + + &:focus-visible { + outline: var(--accessibility-outline); + } } diff --git a/packages/ui/src/elements/ReactSelect/MultiValueRemove/index.tsx b/packages/ui/src/elements/ReactSelect/MultiValueRemove/index.tsx index 721028fb9f..8e1534a852 100644 --- a/packages/ui/src/elements/ReactSelect/MultiValueRemove/index.tsx +++ b/packages/ui/src/elements/ReactSelect/MultiValueRemove/index.tsx @@ -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} diff --git a/packages/ui/src/elements/ReactSelect/index.tsx b/packages/ui/src/elements/ReactSelect/index.tsx index 9259b1b22e..a04676c03d 100644 --- a/packages/ui/src/elements/ReactSelect/index.tsx +++ b/packages/ui/src/elements/ReactSelect/index.tsx @@ -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' @@ -73,7 +73,7 @@ const SelectAdapter: React.FC = (props) => { components={{ ClearIndicator, Control, - DropdownIndicator: Chevron, + DropdownIndicator, MultiValue, MultiValueLabel, MultiValueRemove, @@ -144,7 +144,7 @@ const SelectAdapter: React.FC = (props) => { components={{ ClearIndicator, Control, - DropdownIndicator: Chevron, + DropdownIndicator, MultiValue, MultiValueLabel, MultiValueRemove, diff --git a/packages/ui/src/fields/Relationship/index.tsx b/packages/ui/src/fields/Relationship/index.tsx index 500ec0c107..0db05a2bf8 100644 --- a/packages/ui/src/fields/Relationship/index.tsx +++ b/packages/ui/src/fields/Relationship/index.tsx @@ -492,7 +492,7 @@ const RelationshipField: React.FC = (props) => { onSave, setDrawerIsOpen, }} - disabled={readOnly || formProcessing} + disabled={readOnly || formProcessing || drawerIsOpen} filterOption={enableWordBoundarySearch ? filterOption : undefined} isLoading={isLoading} isMulti={hasMany} diff --git a/packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.scss b/packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.scss index eeea3a13f4..b9d2380405 100644 --- a/packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.scss +++ b/packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.scss @@ -34,5 +34,9 @@ &:hover { background-color: var(--theme-elevation-100); } + + &:focus-visible { + outline: var(--accessibility-outline); + } } } diff --git a/packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.tsx b/packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.tsx index af320b8a76..9c1f652588 100644 --- a/packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.tsx +++ b/packages/ui/src/fields/Relationship/select-components/MultiValueLabel/index.tsx @@ -61,6 +61,11 @@ export const MultiValueLabel: React.FC> = (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)} @@ -69,7 +74,7 @@ export const MultiValueLabel: React.FC> = (props) => { {t('general:editLabel', { label: '' })} - + diff --git a/packages/ui/src/fields/Relationship/select-components/SingleValue/index.scss b/packages/ui/src/fields/Relationship/select-components/SingleValue/index.scss index ef8b4314db..c2b9d0fdc4 100644 --- a/packages/ui/src/fields/Relationship/select-components/SingleValue/index.scss +++ b/packages/ui/src/fields/Relationship/select-components/SingleValue/index.scss @@ -33,8 +33,8 @@ height: base(0.75); } - &:focus { - outline: none; + &:focus-visible { + outline: var(--accessibility-outline); } &:hover { diff --git a/packages/ui/src/fields/Relationship/select-components/SingleValue/index.tsx b/packages/ui/src/fields/Relationship/select-components/SingleValue/index.tsx index 393c4bc6e8..c06bb601e0 100644 --- a/packages/ui/src/fields/Relationship/select-components/SingleValue/index.tsx +++ b/packages/ui/src/fields/Relationship/select-components/SingleValue/index.tsx @@ -50,6 +50,11 @@ export const SingleValue: React.FC> = (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)} diff --git a/packages/ui/src/icons/Edit/index.tsx b/packages/ui/src/icons/Edit/index.tsx index 0e56878342..db29c68cb1 100644 --- a/packages/ui/src/icons/Edit/index.tsx +++ b/packages/ui/src/icons/Edit/index.tsx @@ -2,8 +2,12 @@ import React from 'react' import './index.scss' -export const Edit: React.FC = () => ( - +export const Edit: React.FC<{ className?: string }> = ({ className }) => ( +