From 02035fef00d927cdf6ff97ce005592657f9a6d13 Mon Sep 17 00:00:00 2001 From: Jeffrey Kozik Date: Wed, 24 Apr 2024 10:00:34 -0400 Subject: [PATCH] Adds FormControl's "Auto-Wiring" to SelectPanel v2 Component (#4389) * autowires SelectPanelv2 to FormControl * added changeset * Update afraid-beds-lick.md * Button is aria-labelledby by 2 different components now * using querySelector instead of VisuallyHidden approach * Added visually hidden punctuation * removed selectPanelButtonId * hiding visuallyhidden text from screen readers as well * use ariaLabel instead of ariaLabelledby * added a 2nd test for complex button case * updated tests * using aria-labelledby to reference itself, updated tests * flipped order within aria-label * updated conditional, added negative test * removed aria-labelledby * test * fix --------- Co-authored-by: Siddharth Kshetrapal --- .changeset/afraid-beds-lick.md | 5 ++ .../SelectPanel.examples.stories.tsx | 32 +++++++++++ .../drafts/SelectPanel2/SelectPanel.test.tsx | 53 ++++++++++++++++++- .../src/drafts/SelectPanel2/SelectPanel.tsx | 38 +++++++++++-- 4 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 .changeset/afraid-beds-lick.md diff --git a/.changeset/afraid-beds-lick.md b/.changeset/afraid-beds-lick.md new file mode 100644 index 00000000000..72fcfd7cd6c --- /dev/null +++ b/.changeset/afraid-beds-lick.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +experimental/SelectPanel + FormControl: Automatically wires SelectPanel v2 to the accessibility and validation provided by the FormControl component it's nested within diff --git a/packages/react/src/drafts/SelectPanel2/SelectPanel.examples.stories.tsx b/packages/react/src/drafts/SelectPanel2/SelectPanel.examples.stories.tsx index 8af24311ad5..a058a220440 100644 --- a/packages/react/src/drafts/SelectPanel2/SelectPanel.examples.stories.tsx +++ b/packages/react/src/drafts/SelectPanel2/SelectPanel.examples.stories.tsx @@ -847,6 +847,38 @@ export const NestedSelection = () => { ) } +export const WithinForm = () => { + const [selectedTag, setSelectedTag] = React.useState() + + const onSubmit = () => { + if (!selectedTag) return + data.ref = selectedTag // pretending to persist changes + } + + const itemsToShow = data.tags + + return ( + <> +

Within Form

+ + + SelectPanel within FormControl + + {selectedTag || 'Choose a tag'} + + + {itemsToShow.map(tag => ( + setSelectedTag(tag.id)} selected={selectedTag === tag.id}> + {tag.name} + + ))} + + + + + ) +} + export const CreateNewRow = () => { const initialSelectedLabels = data.issue.labelIds // mock initial state: has selected labels const [selectedLabelIds, setSelectedLabelIds] = React.useState(initialSelectedLabels) diff --git a/packages/react/src/drafts/SelectPanel2/SelectPanel.test.tsx b/packages/react/src/drafts/SelectPanel2/SelectPanel.test.tsx index 4b6504f2f60..bd2127ae8c2 100644 --- a/packages/react/src/drafts/SelectPanel2/SelectPanel.test.tsx +++ b/packages/react/src/drafts/SelectPanel2/SelectPanel.test.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {ThemeProvider, ActionList} from '../../' +import {ThemeProvider, ActionList, FormControl} from '../../' import type {RenderResult} from '@testing-library/react' import {render} from '@testing-library/react' import type {UserEvent} from '@testing-library/user-event' @@ -59,6 +59,36 @@ const Fixture = ({onSubmit, onCancel}: Pick + Select Panel Label + + + ) +} + +function SelectPanelWithComplexButtonWithinForm(): JSX.Element { + return ( + + Select Panel Label + {}} onCancel={() => {}}> + +
Assign label
+
+ + + {}} selected={true}> + Item + Item description + + + +
+
+ ) +} + describe('SelectPanel', () => { it('renders Button by default', async () => { const container = render() @@ -148,4 +178,25 @@ describe('SelectPanel', () => { expect(mockOnCancel).toHaveBeenCalledTimes(1) expect(mockOnSubmit).toHaveBeenCalledTimes(0) }) + + it('SelectPanel within FormControl should be labelled by FormControl.Label', async () => { + const component = render() + const buttonByRole = component.getByRole('button') + expect(buttonByRole).toBeVisible() + expect(buttonByRole).toHaveAttribute('aria-label', 'Assign label, Select Panel Label') + }) + + it('SelectPanel with complex button within FormControl should be labelled by FormControl.Label', async () => { + const component = render() + const buttonByRole = component.getByRole('button') + expect(buttonByRole).toBeVisible() + expect(buttonByRole).toHaveAttribute('aria-label', 'Assign label, Select Panel Label') + }) + + it('SelectPanel outside of FormControl should not be automatically assigned aria-label and aria-labelledby', async () => { + const component = render() + const buttonByRole = component.getByRole('button') + expect(buttonByRole).toBeVisible() + expect(buttonByRole).not.toHaveAttribute('aria-label', 'Assign label, Select Panel Label') + }) }) diff --git a/packages/react/src/drafts/SelectPanel2/SelectPanel.tsx b/packages/react/src/drafts/SelectPanel2/SelectPanel.tsx index 62d32b39df8..c882202602c 100644 --- a/packages/react/src/drafts/SelectPanel2/SelectPanel.tsx +++ b/packages/react/src/drafts/SelectPanel2/SelectPanel.tsx @@ -1,8 +1,21 @@ -import React from 'react' +import React, {useEffect, useState, type MutableRefObject} from 'react' import {SearchIcon, XCircleFillIcon, XIcon, FilterRemoveIcon, AlertIcon, ArrowLeftIcon} from '@primer/octicons-react' import type {ButtonProps, TextInputProps, ActionListProps, LinkProps, CheckboxProps} from '../../index' -import {Button, IconButton, Heading, Box, Tooltip, TextInput, Spinner, Text, Octicon, Link, Checkbox} from '../../index' +import { + Button, + IconButton, + Heading, + Box, + Tooltip, + TextInput, + Spinner, + Text, + Octicon, + Link, + Checkbox, + useFormControlForwardedProps, +} from '../../index' import {ActionListContainerContext} from '../../ActionList/ActionListContainerContext' import {useSlots} from '../../hooks/useSlots' import {useProvidedRefOrCreate, useId, useAnchoredPosition} from '../../hooks' @@ -337,7 +350,26 @@ const Panel: React.FC = ({ } const SelectPanelButton = React.forwardRef((props, anchorRef) => { - return