Skip to content

Commit 90e0d2c

Browse files
AtsushiMclaude
andcommitted
refactor!: AccordionPanel を Details / Summary にrename し、ul>li>Section構造に変更
## 変更内容 ### コンポーネントのrename - `AccordionPanel` → `Details` - `AccordionPanelItem` → `DetailsItem` - `AccordionPanelContent` → `DetailsContent` - `AccordionPanelTrigger` → `Summary` ### マークアップ構造の変更 - Details: `<div role="presentation">` → `<ul>` - DetailsItem: `<Section>` → `<li><Section className="shr-contents" /></li>` ### 詳細な変更 - ディレクトリ名: `AccordionPanel/` → `Accordion/` - CSS class名: `smarthr-ui-AccordionPanel-*` → `smarthr-ui-Details-*` - Context名: `AccordionPanelContext` → `DetailsContext`, `AccordionPanelItemContext` → `DetailsItemContext` - Helper関数の型: `HTMLDivElement` → `HTMLUListElement` - data-component: `AccordionHeaderButton` → `SummaryButton` Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 2637cfd commit 90e0d2c

20 files changed

Lines changed: 485 additions & 486 deletions

packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.test.tsx renamed to packages/smarthr-ui/src/components/Accordion/Details.test.tsx

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { userEvent } from 'storybook/test'
55
import { Fieldset } from '../Fieldset'
66
import { RadioButton } from '../RadioButton'
77

8-
import { AccordionPanel } from './AccordionPanel'
9-
import { AccordionPanelContent } from './AccordionPanelContent'
10-
import { AccordionPanelItem } from './AccordionPanelItem'
11-
import { AccordionPanelTrigger } from './AccordionPanelTrigger'
8+
import { Details } from './Details'
9+
import { DetailsContent } from './DetailsContent'
10+
import { DetailsItem } from './DetailsItem'
11+
import { Summary } from './Summary'
1212

13-
describe('AccordionPanel', () => {
13+
describe('Details', () => {
1414
beforeAll(() => {
1515
config.disabled = true
1616
})
@@ -22,27 +22,27 @@ describe('AccordionPanel', () => {
2222
test('アコーディオン内に配置したラジオボタンをキーボード操作できる', async () => {
2323
render(
2424
<form>
25-
<AccordionPanel>
26-
<AccordionPanelItem name="accordion-panel-1">
27-
<AccordionPanelTrigger>アコーディオンパネル1</AccordionPanelTrigger>
28-
<AccordionPanelContent>
25+
<Details>
26+
<DetailsItem name="accordion-panel-1">
27+
<Summary>アコーディオンパネル1</Summary>
28+
<DetailsContent>
2929
<Fieldset legend="ラジオボタン" innerMargin={0.5}>
3030
<RadioButton name="radio1">ラジオボタン1-1</RadioButton>
3131
<RadioButton name="radio1">ラジオボタン1-2</RadioButton>
3232
</Fieldset>
33-
</AccordionPanelContent>
34-
</AccordionPanelItem>
33+
</DetailsContent>
34+
</DetailsItem>
3535

36-
<AccordionPanelItem name="accordion-panel-2">
37-
<AccordionPanelTrigger>アコーディオンパネル2</AccordionPanelTrigger>
38-
<AccordionPanelContent>
36+
<DetailsItem name="accordion-panel-2">
37+
<Summary>アコーディオンパネル2</Summary>
38+
<DetailsContent>
3939
<Fieldset legend="ラジオボタン" innerMargin={0.5}>
4040
<RadioButton name="radio2">ラジオボタン2-1</RadioButton>
4141
<RadioButton name="radio2">ラジオボタン2-2</RadioButton>
4242
</Fieldset>
43-
</AccordionPanelContent>
44-
</AccordionPanelItem>
45-
</AccordionPanel>
43+
</DetailsContent>
44+
</DetailsItem>
45+
</Details>
4646
</form>,
4747
)
4848

@@ -66,30 +66,30 @@ describe('AccordionPanel', () => {
6666
expect(screen.getByRole('radio', { name: 'ラジオボタン1-1' })).toBeChecked()
6767
})
6868

69-
test('矢印キーでAccordionPanelItem間を移動できる', async () => {
69+
test('矢印キーでDetailsItem間を移動できる', async () => {
7070
render(
7171
<form>
72-
<AccordionPanel>
73-
<AccordionPanelItem name="accordion-panel-1">
74-
<AccordionPanelTrigger>アコーディオンパネル1</AccordionPanelTrigger>
75-
<AccordionPanelContent>
72+
<Details>
73+
<DetailsItem name="accordion-panel-1">
74+
<Summary>アコーディオンパネル1</Summary>
75+
<DetailsContent>
7676
<Fieldset legend="ラジオボタン" innerMargin={0.5}>
7777
<RadioButton name="radio1">ラジオボタン1-1</RadioButton>
7878
<RadioButton name="radio1">ラジオボタン1-2</RadioButton>
7979
</Fieldset>
80-
</AccordionPanelContent>
81-
</AccordionPanelItem>
80+
</DetailsContent>
81+
</DetailsItem>
8282

83-
<AccordionPanelItem name="accordion-panel-2">
84-
<AccordionPanelTrigger>アコーディオンパネル2</AccordionPanelTrigger>
85-
<AccordionPanelContent>
83+
<DetailsItem name="accordion-panel-2">
84+
<Summary>アコーディオンパネル2</Summary>
85+
<DetailsContent>
8686
<Fieldset legend="ラジオボタン" innerMargin={0.5}>
8787
<RadioButton name="radio2">ラジオボタン2-1</RadioButton>
8888
<RadioButton name="radio2">ラジオボタン2-2</RadioButton>
8989
</Fieldset>
90-
</AccordionPanelContent>
91-
</AccordionPanelItem>
92-
</AccordionPanel>
90+
</DetailsContent>
91+
</DetailsItem>
92+
</Details>
9393
</form>,
9494
)
9595

packages/smarthr-ui/src/components/AccordionPanel/AccordionPanel.tsx renamed to packages/smarthr-ui/src/components/Accordion/Details.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { type VariantProps, tv } from 'tailwind-variants'
1616

1717
import { flatArrayToMap } from '../../libs/map'
1818

19-
import { getNewExpandedItems } from './accordionPanelHelper'
19+
import { getNewExpandedItems } from './detailsHelper'
2020

2121
type AbstractProps = PropsWithChildren<{
2222
/** アイコンの左右位置 */
@@ -29,13 +29,13 @@ type AbstractProps = PropsWithChildren<{
2929
onClick?: (expandedItems: string[]) => void
3030
}> &
3131
VariantProps<typeof classNameGenerator>
32-
type Props = AbstractProps & Omit<ComponentProps<'div'>, keyof AbstractProps>
32+
type Props = AbstractProps & Omit<ComponentProps<'ul'>, keyof AbstractProps>
3333

34-
export const AccordionPanelContext = createContext<{
34+
export const DetailsContext = createContext<{
3535
iconPosition: 'left' | 'right'
3636
expandedItems: Map<string, string>
3737
expandableMultiply: boolean
38-
parentRef: RefObject<HTMLDivElement> | null
38+
parentRef: RefObject<HTMLUListElement> | null
3939
onClickTrigger?: (itemName: string, isExpanded: boolean) => void
4040
onClickProps?: (expandedItems: string[]) => void
4141
}>({
@@ -46,16 +46,16 @@ export const AccordionPanelContext = createContext<{
4646
})
4747

4848
const ROUNDED = {
49-
t_l: '[&>.smarthr-ui-AccordionPanel-item:first-child_.smarthr-ui-AccordionPanel-trigger]:shr-rounded-tl-l',
50-
t_r: '[&>.smarthr-ui-AccordionPanel-item:first-child_.smarthr-ui-AccordionPanel-trigger]:shr-rounded-tr-l',
51-
b_l: '[&>.smarthr-ui-AccordionPanel-item:last-child_.smarthr-ui-AccordionPanel-trigger:not([aria-expanded="true"])]:shr-rounded-bl-l',
52-
b_r: '[&>.smarthr-ui-AccordionPanel-item:last-child_.smarthr-ui-AccordionPanel-trigger:not([aria-expanded="true"])]:shr-rounded-br-l',
49+
t_l: '[&>.smarthr-ui-Details-item:first-child_.smarthr-ui-Details-trigger]:shr-rounded-tl-l',
50+
t_r: '[&>.smarthr-ui-Details-item:first-child_.smarthr-ui-Details-trigger]:shr-rounded-tr-l',
51+
b_l: '[&>.smarthr-ui-Details-item:last-child_.smarthr-ui-Details-trigger:not([aria-expanded="true"])]:shr-rounded-bl-l',
52+
b_r: '[&>.smarthr-ui-Details-item:last-child_.smarthr-ui-Details-trigger:not([aria-expanded="true"])]:shr-rounded-br-l',
5353
}
5454

5555
const ROUNDED_ALL = [ROUNDED.t_l, ROUNDED.t_r, ROUNDED.b_l, ROUNDED.b_r]
5656

5757
const classNameGenerator = tv({
58-
base: 'smarthr-ui-AccordionPanel',
58+
base: 'smarthr-ui-Details',
5959
variants: {
6060
rounded: {
6161
true: ROUNDED_ALL,
@@ -72,7 +72,7 @@ const classNameGenerator = tv({
7272
},
7373
})
7474

75-
export const AccordionPanel: FC<Props> = ({
75+
export const Details: FC<Props> = ({
7676
iconPosition = 'left',
7777
expandableMultiply = true,
7878
defaultExpanded = [],
@@ -82,7 +82,7 @@ export const AccordionPanel: FC<Props> = ({
8282
...rest
8383
}) => {
8484
const [expandedItems, setExpanded] = useState(flatArrayToMap(defaultExpanded))
85-
const parentRef = useRef<HTMLDivElement>(null)
85+
const parentRef = useRef<HTMLUListElement>(null)
8686
const actualClassName = useMemo(
8787
() => classNameGenerator({ className, rounded }),
8888
[rounded, className],
@@ -100,7 +100,7 @@ export const AccordionPanel: FC<Props> = ({
100100
}, [defaultExpanded])
101101

102102
return (
103-
<AccordionPanelContext.Provider
103+
<DetailsContext.Provider
104104
value={{
105105
onClickTrigger,
106106
onClickProps,
@@ -110,7 +110,7 @@ export const AccordionPanel: FC<Props> = ({
110110
parentRef,
111111
}}
112112
>
113-
<div {...rest} ref={parentRef} role="presentation" className={actualClassName} />
114-
</AccordionPanelContext.Provider>
113+
<ul {...rest} ref={parentRef} className={actualClassName} />
114+
</DetailsContext.Provider>
115115
)
116116
}

packages/smarthr-ui/src/components/AccordionPanel/AccordionPanelContent.tsx renamed to packages/smarthr-ui/src/components/Accordion/DetailsContent.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ import { tv } from 'tailwind-variants'
1313

1414
import { getIsInclude } from '../../libs/map'
1515

16-
import { AccordionPanelContext } from './AccordionPanel'
17-
import { AccordionPanelItemContext } from './AccordionPanelItem'
16+
import { DetailsContext } from './Details'
17+
import { DetailsItemContext } from './DetailsItem'
1818

1919
type AbstractProps = PropsWithChildren
2020
type Props = AbstractProps & Omit<ComponentPropsWithoutRef<'div'>, keyof AbstractProps>
2121

2222
const classNameGenerator = tv({
2323
base: [
24-
'smarthr-ui-AccordionPanel-content',
24+
'smarthr-ui-Details-content',
2525
'shr-invisible shr-max-h-0 shr-opacity-0 shr-transition-[max-height,_visible,_opacity] shr-duration-150 shr-ease-in-out',
2626
'[&.entered]:shr-visible [&.entered]:shr-max-h-[revert] [&.entered]:shr-opacity-100',
2727
// HINT: flexなどで囲まれると、非表示だが内容分高さが出てしまい、スクロール領域が不自然に伸びてしまう現象が起きる場合がある
@@ -30,9 +30,9 @@ const classNameGenerator = tv({
3030
],
3131
})
3232

33-
export const AccordionPanelContent: FC<Props> = ({ className, ...rest }) => {
34-
const { name } = useContext(AccordionPanelItemContext)
35-
const { expandedItems } = useContext(AccordionPanelContext)
33+
export const DetailsContent: FC<Props> = ({ className, ...rest }) => {
34+
const { name } = useContext(DetailsItemContext)
35+
const { expandedItems } = useContext(DetailsContext)
3636
const visible = useMemo(() => getIsInclude(expandedItems, name), [expandedItems, name])
3737
const wrapperRef = useRef<HTMLDivElement>(null)
3838
const actualClassName = useMemo(() => classNameGenerator({ className }), [className])
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use client'
2+
3+
import {
4+
type ComponentPropsWithoutRef,
5+
type FC,
6+
type PropsWithChildren,
7+
createContext,
8+
useMemo,
9+
} from 'react'
10+
import { tv } from 'tailwind-variants'
11+
12+
import { Section } from '../SectioningContent'
13+
14+
type AbstractProps = PropsWithChildren<{
15+
/** アイテムを識別するための名前 */
16+
name: string
17+
}>
18+
type Props = AbstractProps & Omit<ComponentPropsWithoutRef<'li'>, keyof AbstractProps>
19+
20+
export const DetailsItemContext = createContext<{ name: string }>({
21+
name: '',
22+
})
23+
24+
const liClassNameGenerator = tv({
25+
base: ['smarthr-ui-Details-item', '[&_+_&]:shr-border-t-shorthand'],
26+
})
27+
28+
const sectionClassNameGenerator = tv({
29+
base: 'shr-contents',
30+
})
31+
32+
export const DetailsItem: FC<Props> = ({ name, className, children, ...rest }) => {
33+
const liClassName = useMemo(() => liClassNameGenerator({ className }), [className])
34+
const sectionClassName = useMemo(() => sectionClassNameGenerator(), [])
35+
36+
return (
37+
<DetailsItemContext.Provider
38+
value={{
39+
name,
40+
}}
41+
>
42+
<li {...rest} className={liClassName}>
43+
{/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content -- Summary 内に Heading が含まれている */}
44+
<Section className={sectionClassName}>{children}</Section>
45+
</li>
46+
</DetailsItemContext.Provider>
47+
)
48+
}

packages/smarthr-ui/src/components/AccordionPanel/AccordionPanelTrigger.tsx renamed to packages/smarthr-ui/src/components/Accordion/Summary.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ import { Heading, type HeadingTagTypes } from '../Heading'
1818
import { FaCaretDownIcon, FaCaretRightIcon } from '../Icon'
1919
import { Cluster } from '../Layout'
2020

21-
import { AccordionPanelContext } from './AccordionPanel'
22-
import { AccordionPanelItemContext } from './AccordionPanelItem'
21+
import { DetailsContext } from './Details'
22+
import { DetailsItemContext } from './DetailsItem'
2323
import {
2424
focusFirstSibling,
2525
focusLastSibling,
2626
focusNextSibling,
2727
focusPreviousSibling,
2828
getNewExpandedItems,
29-
} from './accordionPanelHelper'
29+
} from './detailsHelper'
3030

3131
import type { TextProps } from '../Text'
3232

@@ -45,13 +45,13 @@ const classNameGenerator = tv({
4545
title: 'shr-grow shr-leading-tight',
4646
titleWrapper: 'shr-flex-nowrap',
4747
button: [
48-
'smarthr-ui-AccordionPanel-trigger',
48+
'smarthr-ui-Details-trigger',
4949
'shr-group shr-w-full shr-cursor-pointer shr-appearance-none shr-border-none shr-bg-transparent shr-px-1 shr-py-0.75 shr-text-left shr-text-inherit shr-text-color-inherit',
5050
'disabled:shr-cursor-not-allowed disabled:shr-bg-white-darken disabled:shr-text-disabled',
5151
'hover:shr-bg-white-darken',
5252
'focus-visible:shr-focus-indicator',
53-
// Base 直下に AccordionPanel がある場合、背景が付き抜けないように角丸を指定(Base に overflow: hidden を与えるとフォーカスリングが表示されなくなる)
54-
'[.smarthr-ui-Base_>_.smarthr-ui-AccordionPanel_.smarthr-ui-AccordionPanel-item:first-child_&]:shr-rounded-t-l [.smarthr-ui-Base_>_.smarthr-ui-AccordionPanel_.smarthr-ui-AccordionPanel-item:last-child_&]:shr-rounded-b-l',
53+
// Panel 直下に Details がある場合、背景が付き抜けないように角丸を指定(Panel に overflow: hidden を与えるとフォーカスリングが表示されなくなる)
54+
'[.smarthr-ui-Panel_>_.smarthr-ui-Details_.smarthr-ui-Details-item:first-child_&]:shr-rounded-t-l [.smarthr-ui-Panel_>_.smarthr-ui-Details_.smarthr-ui-Details-item:last-child_&]:shr-rounded-b-l',
5555
],
5656
leftIcon: 'shr-transition-transform shr-duration-100 group-aria-expanded:shr-rotate-90',
5757
rightIcon: 'group-aria-expanded:-shr-rotate-180',
@@ -64,7 +64,7 @@ const classNameGenerator = tv({
6464
],
6565
})
6666

67-
export const AccordionPanelTrigger: FC<Props> = ({
67+
export const Summary: FC<Props> = ({
6868
children,
6969
className,
7070
headingType = 'blockTitle',
@@ -83,15 +83,15 @@ export const AccordionPanelTrigger: FC<Props> = ({
8383
}
8484
}, [className])
8585

86-
const { name } = useContext(AccordionPanelItemContext)
86+
const { name } = useContext(DetailsItemContext)
8787
const {
8888
iconPosition,
8989
expandedItems,
9090
onClickTrigger,
9191
onClickProps,
9292
expandableMultiply,
9393
parentRef,
94-
} = useContext(AccordionPanelContext)
94+
} = useContext(DetailsContext)
9595

9696
const isExpanded = useMemo(() => getIsInclude(expandedItems, name), [expandedItems, name])
9797

@@ -183,7 +183,7 @@ export const AccordionPanelTrigger: FC<Props> = ({
183183
onClick={handleClick}
184184
onKeyDown={handleKeyDown}
185185
className={classNames.button}
186-
data-component="AccordionHeaderButton"
186+
data-component="SummaryButton"
187187
>
188188
<MemoizedTitle iconPosition={iconPosition} classNames={classNames}>
189189
{children}

packages/smarthr-ui/src/components/AccordionPanel/accordionPanelHelper.ts renamed to packages/smarthr-ui/src/components/Accordion/detailsHelper.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,24 @@ export const getNewExpandedItems = (
2121
return newState
2222
}
2323

24-
const getSiblingButtons = (parent: HTMLDivElement): HTMLElement[] =>
25-
Array.from(parent.querySelectorAll('[data-component="AccordionHeaderButton"]'))
24+
const getSiblingButtons = (parent: HTMLUListElement): HTMLElement[] =>
25+
Array.from(parent.querySelectorAll('[data-component="SummaryButton"]'))
2626

27-
export const focusFirstSibling = (parent: HTMLDivElement): void => {
27+
export const focusFirstSibling = (parent: HTMLUListElement): void => {
2828
const siblings = getSiblingButtons(parent)
2929
const first = siblings[0]
3030

3131
first.focus()
3232
}
3333

34-
export const focusLastSibling = (parent: HTMLDivElement): void => {
34+
export const focusLastSibling = (parent: HTMLUListElement): void => {
3535
const siblings = getSiblingButtons(parent)
3636
const last = siblings[siblings.length - 1]
3737

3838
last.focus()
3939
}
4040

41-
export const focusNextSibling = (item: HTMLElement, parent: HTMLDivElement): void => {
41+
export const focusNextSibling = (item: HTMLElement, parent: HTMLUListElement): void => {
4242
const siblings = getSiblingButtons(parent)
4343
const current = siblings.indexOf(item)
4444

@@ -53,7 +53,7 @@ export const focusNextSibling = (item: HTMLElement, parent: HTMLDivElement): voi
5353
}
5454
}
5555

56-
export const focusPreviousSibling = (item: HTMLElement, parent: HTMLDivElement): void => {
56+
export const focusPreviousSibling = (item: HTMLElement, parent: HTMLUListElement): void => {
5757
const siblings = getSiblingButtons(parent)
5858
const current = siblings.indexOf(item)
5959

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { Details } from './Details'
2+
export { DetailsItem } from './DetailsItem'
3+
export { DetailsContent } from './DetailsContent'
4+
export { Summary } from './Summary'

0 commit comments

Comments
 (0)