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
203 lines
5.5 KiB
TypeScript
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>
|
|
);
|
|
}
|