From 754096aec3cd781b7d8c693bd8d69d98b4ebb340 Mon Sep 17 00:00:00 2001 From: Ryo Igarashi Date: Thu, 26 Dec 2024 12:08:16 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20AccordionPanel=E3=81=AEkeydown=E3=82=A4?= =?UTF-8?q?=E3=83=99=E3=83=B3=E3=83=88=E3=83=AA=E3=82=B9=E3=83=8A=E3=83=BC?= =?UTF-8?q?=E3=82=92Trigger=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AccordionPanel/AccordionPanel.test.tsx | 107 ++++++++++++++++++ .../AccordionPanel/AccordionPanel.tsx | 51 +-------- .../AccordionPanel/AccordionPanelTrigger.tsx | 55 ++++++++- .../AccordionPanel/accordionPanelHelper.ts | 11 -- 4 files changed, 161 insertions(+), 63 deletions(-) create mode 100644 packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.test.tsx diff --git a/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.test.tsx b/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.test.tsx new file mode 100644 index 0000000000..650542e3ff --- /dev/null +++ b/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.test.tsx @@ -0,0 +1,107 @@ +import { userEvent } from '@storybook/test' +import { render, screen } from '@testing-library/react' +import React from 'react' +import { config } from 'react-transition-group' + +import { Fieldset } from '../Fieldset' +import { RadioButton } from '../RadioButton' + +import { AccordionPanel } from './AccordionPanel' +import { AccordionPanelContent } from './AccordionPanelContent' +import { AccordionPanelItem } from './AccordionPanelItem' +import { AccordionPanelTrigger } from './AccordionPanelTrigger' + +describe('AccordionPanel', () => { + beforeAll(() => { + config.disabled = true + }) + + afterAll(() => { + config.disabled = false + }) + + test('アコーディオン内に配置したラジオボタンをキーボード操作できる', async () => { + render( +
+ + + アコーディオンパネル1 + +
+ ラジオボタン1-1 + ラジオボタン1-2 +
+
+
+ + + アコーディオンパネル2 + +
+ ラジオボタン2-1 + ラジオボタン2-2 +
+
+
+
+
, + ) + + await userEvent.keyboard('[Tab]') + await userEvent.keyboard('[Space]') + expect(screen.getByRole('button', { name: 'アコーディオンパネル1' })).toHaveFocus() + + await userEvent.keyboard('[Tab]') + await userEvent.keyboard('[Space]') + expect(screen.getByRole('radio', { name: 'ラジオボタン1-1' })).toHaveFocus() + expect(screen.getByRole('radio', { name: 'ラジオボタン1-1' })).toBeChecked() + + await userEvent.keyboard('[ArrowRight]') + await userEvent.keyboard('[Space]') + expect(screen.getByRole('radio', { name: 'ラジオボタン1-2' })).toHaveFocus() + expect(screen.getByRole('radio', { name: 'ラジオボタン1-2' })).toBeChecked() + + await userEvent.keyboard('[ArrowLeft]') + await userEvent.keyboard('[Space]') + expect(screen.getByRole('radio', { name: 'ラジオボタン1-1' })).toHaveFocus() + expect(screen.getByRole('radio', { name: 'ラジオボタン1-1' })).toBeChecked() + }) + + test('矢印キーでAccordionPanelItem間を移動できる', async () => { + render( +
+ + + アコーディオンパネル1 + +
+ ラジオボタン1-1 + ラジオボタン1-2 +
+
+
+ + + アコーディオンパネル2 + +
+ ラジオボタン2-1 + ラジオボタン2-2 +
+
+
+
+
, + ) + + await userEvent.keyboard('[Tab]') + await userEvent.keyboard('[Space]') + expect(screen.getByRole('button', { name: 'アコーディオンパネル1' })).toHaveFocus() + + await userEvent.keyboard('[ArrowDown]') + expect(screen.getByRole('button', { name: 'アコーディオンパネル2' })).toHaveFocus() + + await userEvent.keyboard('[ArrowUp]') + expect(screen.getByRole('button', { name: 'アコーディオンパネル1' })).toHaveFocus() + }) +}) diff --git a/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.tsx b/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.tsx index ba68f3716b..ad90efeacc 100644 --- a/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.tsx +++ b/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.tsx @@ -13,14 +13,7 @@ import { tv } from 'tailwind-variants' import { flatArrayToMap } from '../../libs/map' -import { - focusFirstSibling, - focusLastSibling, - focusNextSibling, - focusPreviousSibling, - getNewExpandedItems, - keycodes, -} from './accordionPanelHelper' +import { getNewExpandedItems } from './accordionPanelHelper' type Props = PropsWithChildren<{ /** アイコンの左右位置 */ @@ -71,40 +64,6 @@ export const AccordionPanel: React.FC = ({ [expandableMultiply, expandedItems], ) - const handleKeyPress = (event: React.KeyboardEvent): void => { - if (!parentRef?.current) { - return - } - - const keyCode = event.keyCode - const item = event.target as HTMLElement - - switch (keyCode) { - case keycodes.HOME: { - event.preventDefault() - focusFirstSibling(parentRef.current) - break - } - case keycodes.END: { - event.preventDefault() - focusLastSibling(parentRef.current) - break - } - case keycodes.LEFT: - case keycodes.UP: { - event.preventDefault() - focusPreviousSibling(item, parentRef.current) - break - } - case keycodes.RIGHT: - case keycodes.DOWN: { - event.preventDefault() - focusNextSibling(item, parentRef.current) - break - } - } - } - useEffect(() => { if (defaultExpanded.length > 0) setExpanded(flatArrayToMap(defaultExpanded)) }, [defaultExpanded]) @@ -121,13 +80,7 @@ export const AccordionPanel: React.FC = ({ }} > {/* eslint-disable-next-line smarthr/a11y-delegate-element-has-role-presentation */} -
+
) } diff --git a/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanelTrigger.tsx b/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanelTrigger.tsx index b576a47598..59a1ad7724 100644 --- a/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanelTrigger.tsx +++ b/packages/smarthr-ui/src/components/AccordionPanel/AccordionPanelTrigger.tsx @@ -18,7 +18,13 @@ import { TextProps } from '../Text' import { AccordionPanelContext } from './AccordionPanel' import { AccordionPanelItemContext } from './AccordionPanelItem' -import { getNewExpandedItems } from './accordionPanelHelper' +import { + focusFirstSibling, + focusLastSibling, + focusNextSibling, + focusPreviousSibling, + getNewExpandedItems, +} from './accordionPanelHelper' type Props = PropsWithChildren<{ /** ヘッダ部分のテキストのスタイル */ @@ -70,8 +76,14 @@ export const AccordionPanelTrigger: FC = ({ } }, [className]) const { name } = useContext(AccordionPanelItemContext) - const { iconPosition, expandedItems, onClickTrigger, onClickProps, expandableMultiply } = - useContext(AccordionPanelContext) + const { + iconPosition, + expandedItems, + onClickTrigger, + onClickProps, + expandableMultiply, + parentRef, + } = useContext(AccordionPanelContext) const isExpanded = getIsInclude(expandedItems, name) @@ -89,6 +101,42 @@ export const AccordionPanelTrigger: FC = ({ } }, [onClickTrigger, name, isExpanded, onClickProps, expandedItems, expandableMultiply]) + const handleKeyDown: React.KeyboardEventHandler = useCallback( + (event): void => { + if (!parentRef?.current) { + return + } + + const item = event.target as HTMLElement + + switch (event.key) { + case 'Home': { + event.preventDefault() + focusFirstSibling(parentRef.current) + break + } + case 'End': { + event.preventDefault() + focusLastSibling(parentRef.current) + break + } + case 'ArrowLeft': + case 'ArrowUp': { + event.preventDefault() + focusPreviousSibling(item, parentRef.current) + break + } + case 'ArrowRight': + case 'ArrowDown': { + event.preventDefault() + focusNextSibling(item, parentRef.current) + break + } + } + }, + [parentRef], + ) + return ( // eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content @@ -98,6 +146,7 @@ export const AccordionPanelTrigger: FC = ({ aria-expanded={isExpanded} aria-controls={`${name}-content`} onClick={handleClick} + onKeyDown={handleKeyDown} className={buttonStyle} data-component="AccordionHeaderButton" type="button" diff --git a/packages/smarthr-ui/src/components/AccordionPanel/accordionPanelHelper.ts b/packages/smarthr-ui/src/components/AccordionPanel/accordionPanelHelper.ts index 6feb70c6b5..b90492a4a9 100644 --- a/packages/smarthr-ui/src/components/AccordionPanel/accordionPanelHelper.ts +++ b/packages/smarthr-ui/src/components/AccordionPanel/accordionPanelHelper.ts @@ -21,17 +21,6 @@ export const getNewExpandedItems = ( return newState } -export const keycodes = { - SPACE: 32, - ENTER: 13, - HOME: 36, - END: 35, - UP: 38, - RIGHT: 39, - DOWN: 40, - LEFT: 37, -} - export const getSiblingButtons = (parent: HTMLDivElement): HTMLElement[] => Array.from(parent.querySelectorAll('[data-component="AccordionHeaderButton"]'))