Skip to content

Commit

Permalink
fix: AccordionPanelのkeydownイベントリスナーをTriggerに移動
Browse files Browse the repository at this point in the history
  • Loading branch information
neet committed Dec 27, 2024
1 parent 6fead5e commit 1559731
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -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(
<form>
<AccordionPanel>
<AccordionPanelItem name="accordion-panel-1">
<AccordionPanelTrigger>アコーディオンパネル1</AccordionPanelTrigger>
<AccordionPanelContent>
<Fieldset title="ラジオボタン" innerMargin={0.5}>
<RadioButton name="radio1">ラジオボタン1-1</RadioButton>
<RadioButton name="radio1">ラジオボタン1-2</RadioButton>
</Fieldset>
</AccordionPanelContent>
</AccordionPanelItem>

<AccordionPanelItem name="accordion-panel-2">
<AccordionPanelTrigger>アコーディオンパネル2</AccordionPanelTrigger>
<AccordionPanelContent>
<Fieldset title="ラジオボタン" innerMargin={0.5}>
<RadioButton name="radio2">ラジオボタン2-1</RadioButton>
<RadioButton name="radio2">ラジオボタン2-2</RadioButton>
</Fieldset>
</AccordionPanelContent>
</AccordionPanelItem>
</AccordionPanel>
</form>,
)

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(
<form>
<AccordionPanel>
<AccordionPanelItem name="accordion-panel-1">
<AccordionPanelTrigger>アコーディオンパネル1</AccordionPanelTrigger>
<AccordionPanelContent>
<Fieldset title="ラジオボタン" innerMargin={0.5}>
<RadioButton name="radio1">ラジオボタン1-1</RadioButton>
<RadioButton name="radio1">ラジオボタン1-2</RadioButton>
</Fieldset>
</AccordionPanelContent>
</AccordionPanelItem>

<AccordionPanelItem name="accordion-panel-2">
<AccordionPanelTrigger>アコーディオンパネル2</AccordionPanelTrigger>
<AccordionPanelContent>
<Fieldset title="ラジオボタン" innerMargin={0.5}>
<RadioButton name="radio2">ラジオボタン2-1</RadioButton>
<RadioButton name="radio2">ラジオボタン2-2</RadioButton>
</Fieldset>
</AccordionPanelContent>
</AccordionPanelItem>
</AccordionPanel>
</form>,
)

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()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
/** アイコンの左右位置 */
Expand Down Expand Up @@ -71,40 +64,6 @@ export const AccordionPanel: React.FC<Props & ElementProps> = ({
[expandableMultiply, expandedItems],
)

const handleKeyPress = (event: React.KeyboardEvent<HTMLDivElement>): 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])
Expand All @@ -121,13 +80,7 @@ export const AccordionPanel: React.FC<Props & ElementProps> = ({
}}
>
{/* eslint-disable-next-line smarthr/a11y-delegate-element-has-role-presentation */}
<div
{...props}
className={styles}
ref={parentRef}
onKeyDown={handleKeyPress}
role="presentation"
/>
<div {...props} className={styles} ref={parentRef} role="presentation" />
</AccordionPanelContext.Provider>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
/** ヘッダ部分のテキストのスタイル */
Expand Down Expand Up @@ -70,8 +76,14 @@ export const AccordionPanelTrigger: FC<Props & ElementProps> = ({
}
}, [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)

Expand All @@ -89,6 +101,39 @@ export const AccordionPanelTrigger: FC<Props & ElementProps> = ({
}
}, [onClickTrigger, name, isExpanded, onClickProps, expandedItems, expandableMultiply])

const handleKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (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
}
}
}

return (
// eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content
<Heading tag={headingTag} type={headingType}>
Expand All @@ -98,6 +143,7 @@ export const AccordionPanelTrigger: FC<Props & ElementProps> = ({
aria-expanded={isExpanded}
aria-controls={`${name}-content`}
onClick={handleClick}
onKeyDown={handleKeyDown}
className={buttonStyle}
data-component="AccordionHeaderButton"
type="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]'))

Expand Down

0 comments on commit 1559731

Please sign in to comment.