Files
react-test/docs/data/joy/components/menu/MenuIconSideNavExample.tsx
how2ice 005cf56baf
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
init project
2025-12-12 14:26:25 +09:00

203 lines
5.5 KiB
TypeScript

import * as React from 'react';
import Menu, { menuClasses } from '@mui/joy/Menu';
import MenuItem from '@mui/joy/MenuItem';
import IconButton from '@mui/joy/IconButton';
import List from '@mui/joy/List';
import ListItem from '@mui/joy/ListItem';
import Sheet from '@mui/joy/Sheet';
import Apps from '@mui/icons-material/Apps';
import Settings from '@mui/icons-material/Settings';
import Person from '@mui/icons-material/Person';
import Dropdown from '@mui/joy/Dropdown';
import MenuButton from '@mui/joy/MenuButton';
// The Menu is built on top of Popper v2, so it accepts `modifiers` prop that will be passed to the Popper.
// https://popper.js.org/docs/v2/modifiers/offset/
interface MenuButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
menu: React.ReactElement<any>;
open: boolean;
onOpen: (
event?:
| React.MouseEvent<HTMLButtonElement>
| React.KeyboardEvent<HTMLButtonElement>,
) => void;
onLeaveMenu: (callback: () => boolean) => void;
label: string;
}
const modifiers = [
{
name: 'offset',
options: {
offset: ({ placement }: any) => {
if (placement.includes('end')) {
return [8, 20];
}
return [-8, 20];
},
},
},
];
function NavMenuButton({
children,
menu,
open,
onOpen,
onLeaveMenu,
label,
...props
}: Omit<MenuButtonProps, 'color'>) {
const isOnButton = React.useRef(false);
const internalOpen = React.useRef(open);
const handleButtonKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
internalOpen.current = open;
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
event.preventDefault();
onOpen(event);
}
};
return (
<Dropdown
open={open}
onOpenChange={(_, isOpen) => {
if (isOpen) {
onOpen?.();
}
}}
>
<MenuButton
{...props}
slots={{ root: IconButton }}
slotProps={{ root: { variant: 'plain', color: 'neutral' } }}
onMouseDown={() => {
internalOpen.current = open;
}}
onClick={() => {
if (!internalOpen.current) {
onOpen();
}
}}
onMouseEnter={() => {
onOpen();
isOnButton.current = true;
}}
onMouseLeave={() => {
isOnButton.current = false;
}}
onKeyDown={handleButtonKeyDown}
sx={[
{
'&:focus-visible': {
bgcolor: 'neutral.plainHoverBg',
},
},
open ? { bgcolor: 'neutral.plainHoverBg' } : { bgcolor: null },
]}
>
{children}
</MenuButton>
{React.cloneElement(menu, {
onMouseLeave: () => {
onLeaveMenu(() => isOnButton.current);
},
modifiers,
slotProps: {
listbox: {
id: `nav-example-menu-${label}`,
'aria-label': label,
},
},
placement: 'right-start',
sx: {
width: 288,
[`& .${menuClasses.listbox}`]: {
'--List-padding': 'var(--ListDivider-gap)',
},
},
})}
</Dropdown>
);
}
export default function MenuIconSideNavExample() {
const [menuIndex, setMenuIndex] = React.useState<null | number>(null);
const itemProps = {
onClick: () => setMenuIndex(null),
};
const createHandleLeaveMenu =
(index: number) => (getIsOnButton: () => boolean) => {
setTimeout(() => {
const isOnButton = getIsOnButton();
if (!isOnButton) {
setMenuIndex((latestIndex: null | number) => {
if (index === latestIndex) {
return null;
}
return latestIndex;
});
}
}, 200);
};
return (
<Sheet sx={{ borderRadius: 'sm', py: 1, mr: 20 }}>
<List>
<ListItem>
<NavMenuButton
label="Apps"
open={menuIndex === 0}
onOpen={() => setMenuIndex(0)}
onLeaveMenu={createHandleLeaveMenu(0)}
menu={
<Menu onClose={() => setMenuIndex(null)}>
<MenuItem {...itemProps}>Application 1</MenuItem>
<MenuItem {...itemProps}>Application 2</MenuItem>
<MenuItem {...itemProps}>Application 3</MenuItem>
</Menu>
}
>
<Apps />
</NavMenuButton>
</ListItem>
<ListItem>
<NavMenuButton
label="Settings"
open={menuIndex === 1}
onOpen={() => setMenuIndex(1)}
onLeaveMenu={createHandleLeaveMenu(1)}
menu={
<Menu onClose={() => setMenuIndex(null)}>
<MenuItem {...itemProps}>Setting 1</MenuItem>
<MenuItem {...itemProps}>Setting 2</MenuItem>
<MenuItem {...itemProps}>Setting 3</MenuItem>
</Menu>
}
>
<Settings />
</NavMenuButton>
</ListItem>
<ListItem>
<NavMenuButton
label="Personal"
open={menuIndex === 2}
onOpen={() => setMenuIndex(2)}
onLeaveMenu={createHandleLeaveMenu(2)}
menu={
<Menu onClose={() => setMenuIndex(null)}>
<MenuItem {...itemProps}>Personal 1</MenuItem>
<MenuItem {...itemProps}>Personal 2</MenuItem>
<MenuItem {...itemProps}>Personal 3</MenuItem>
</Menu>
}
>
<Person />
</NavMenuButton>
</ListItem>
</List>
</Sheet>
);
}