Skip to content

Commit 06b2ec7

Browse files
committed
chore: Refactor DropdownMenu component to handle submenu opening and closing
1 parent 7f996d0 commit 06b2ec7

File tree

3 files changed

+86
-15
lines changed

3 files changed

+86
-15
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-lightning-design-system",
3-
"version": "5.6.0",
3+
"version": "5.6.1",
44
"description": "Salesforce Lightning Design System components built with React",
55
"main": "lib/scripts/index.js",
66
"module": "module/scripts/index.js",

src/scripts/DropdownMenu.tsx

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ export const DropdownMenuHandlerContext = createContext<DropdownMenuHandler>(
6868
{}
6969
);
7070

71+
type OpenSubmenuContext = {
72+
openSubmenuKeys: { [key: string]: { isOpen: boolean; level: number } };
73+
handleSubmenuOpen: (key: string, level: number) => void;
74+
};
75+
76+
export const OpenSubmenuContext = createContext<OpenSubmenuContext>({
77+
openSubmenuKeys: {},
78+
handleSubmenuOpen: () => {},
79+
});
80+
7181
/**
7282
*
7383
*/
@@ -82,6 +92,8 @@ export type DropdownMenuItemProps = {
8292
onClick?: (e: React.SyntheticEvent) => void;
8393
submenu?: ReactNode;
8494
submenuItems?: Array<{ key: string | number } & DropdownMenuItemProps>;
95+
openSubmenuKey?: string | number;
96+
level?: number;
8597
} & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'onClick'>;
8698

8799
/**
@@ -104,13 +116,16 @@ export const DropdownMenuItem: FC<DropdownMenuItemProps> = (props) => {
104116
submenu: submenu_,
105117
submenuItems,
106118
children,
119+
level = 0,
107120
...rprops
108121
} = props;
109122

110123
const { onMenuSelect, onMenuBlur, onMenuFocus } = useContext(
111124
DropdownMenuHandlerContext
112125
);
113126

127+
const { openSubmenuKeys, handleSubmenuOpen } = useContext(OpenSubmenuContext);
128+
114129
const onKeyDown = useEventCallback((e: KeyboardEvent<HTMLAnchorElement>) => {
115130
if (e.keyCode === 13 || e.keyCode === 32) {
116131
// return or space
@@ -162,8 +177,8 @@ export const DropdownMenuItem: FC<DropdownMenuItemProps> = (props) => {
162177

163178
const onMenuItemClick = useEventCallback(
164179
(e: SyntheticEvent<HTMLAnchorElement>) => {
165-
if (submenu) {
166-
setSubmenuExpanded((expanded) => !expanded);
180+
if (submenu && eventKey !== undefined) {
181+
handleSubmenuOpen(eventKey.toString(), level + 1);
167182
return;
168183
}
169184
onClick?.(e);
@@ -192,11 +207,13 @@ export const DropdownMenuItem: FC<DropdownMenuItemProps> = (props) => {
192207
(submenuItems ? (
193208
<DropdownSubmenu label={label}>
194209
{submenuItems?.map(({ key, ...itemProps }) => (
195-
<DropdownMenuItem key={key} {...itemProps} />
210+
<DropdownMenuItem key={key} level={level + 1} {...itemProps} />
196211
))}
197212
</DropdownSubmenu>
198213
) : undefined);
199-
const [submenuExpanded, setSubmenuExpanded] = useState(false);
214+
215+
const submenuExpanded =
216+
eventKey !== undefined ? openSubmenuKeys[eventKey]?.isOpen ?? false : false;
200217

201218
const menuItemClass = classnames(
202219
'slds-dropdown__item',
@@ -340,6 +357,27 @@ const DropdownMenuInner: FC<DropdownMenuProps & AutoAlignInjectedProps> = (
340357
}),
341358
[onBlur, onFocus, onMenuSelect]
342359
);
360+
361+
const [openSubmenuKeys, setOpenSubmenuKeys] = useState<{
362+
[key: string]: { isOpen: boolean; level: number };
363+
}>({});
364+
365+
const handleSubmenuOpen = (key: string, level: number) => {
366+
setOpenSubmenuKeys((prevState) => {
367+
const newState = { ...prevState };
368+
Object.keys(newState).forEach((submenuKey) => {
369+
// メニューをクリックしてサブメニューを開く、サブメニューをクリックするとそのサブメニューを開く
370+
// 開いているメニューをクリックした場合、そのメニューとサブメニューを閉じる
371+
// 同じレベルのメニューをクリックした場合、それ以外の同じレベルのメニューとそのサブメニューを閉じる
372+
if (newState[submenuKey].level >= level && key !== submenuKey) {
373+
newState[submenuKey].isOpen = false;
374+
}
375+
});
376+
newState[key] = { isOpen: !newState[key]?.isOpen, level };
377+
return newState;
378+
});
379+
};
380+
343381
return (
344382
<div
345383
className={dropdownClassNames}
@@ -354,7 +392,11 @@ const DropdownMenuInner: FC<DropdownMenuProps & AutoAlignInjectedProps> = (
354392
{header ? <MenuHeader>{header}</MenuHeader> : null}
355393
<ul className='slds-dropdown__list' role='menu'>
356394
<DropdownMenuHandlerContext.Provider value={handlers}>
357-
{children}
395+
<OpenSubmenuContext.Provider
396+
value={{ openSubmenuKeys, handleSubmenuOpen }}
397+
>
398+
{children}
399+
</OpenSubmenuContext.Provider>
358400
</DropdownMenuHandlerContext.Provider>
359401
</ul>
360402
</div>

stories/DropdownMenu.stories.tsx

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,19 +120,48 @@ export const WithSubmenu: StoryObj<StoryProps> = {
120120
children: 'Menu Item Three',
121121
submenuItems: [
122122
{
123-
eventKey: 21,
124-
key: 21,
123+
eventKey: 31,
124+
key: 31,
125125
label: 'Menu Item Three - One',
126+
submenuItems: [
127+
{
128+
eventKey: 311,
129+
key: 311,
130+
label: 'Menu Item Three - One - One',
131+
},
132+
{
133+
eventKey: 312,
134+
key: 312,
135+
label: 'Menu Item Three - One - Two',
136+
},
137+
{
138+
eventKey: 313,
139+
key: 313,
140+
label: 'Menu Item Three - One - Three',
141+
},
142+
],
126143
},
127144
{
128-
eventKey: 22,
129-
key: 22,
145+
eventKey: 32,
146+
key: 32,
130147
label: 'Menu Item Three - Two',
131-
},
132-
{
133-
eventKey: 23,
134-
key: 23,
135-
label: 'Menu Item Three - Three',
148+
submenuItems: [
149+
{
150+
eventKey: 321,
151+
key: 321,
152+
label: 'Menu Item Three - Two - One',
153+
},
154+
{
155+
eventKey: 322,
156+
key: 322,
157+
label: 'Menu Item Three - Two - Two',
158+
},
159+
{
160+
eventKey: 323,
161+
key: 323,
162+
label: 'Menu Item Three - Two - Three',
163+
},
164+
],
136165
},
137166
],
138167
},

0 commit comments

Comments
 (0)