Skip to content

Commit

Permalink
menu tsx demos
Browse files Browse the repository at this point in the history
  • Loading branch information
varunmulay22 committed Mar 1, 2023
1 parent 97944be commit 31b86cc
Show file tree
Hide file tree
Showing 12 changed files with 658 additions and 8 deletions.
2 changes: 2 additions & 0 deletions docs/data/joy/components/menu/BasicMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import MenuItem from '@mui/joy/MenuItem';
export default function BasicMenu() {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);

const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};
Expand Down
44 changes: 44 additions & 0 deletions docs/data/joy/components/menu/BasicMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from 'react';
import Button from '@mui/joy/Button';
import Menu from '@mui/joy/Menu';
import MenuItem from '@mui/joy/MenuItem';

export default function BasicMenu() {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

return (
<div>
<Button
id="basic-demo-button"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
variant="outlined"
color="neutral"
onClick={handleClick}
>
Dashboard
</Button>
<Menu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
aria-labelledby="basic-demo-button"
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu>
</div>
);
}
90 changes: 90 additions & 0 deletions docs/data/joy/components/menu/GroupMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as React from 'react';
import Button from '@mui/joy/Button';
import List from '@mui/joy/List';
import ListItem from '@mui/joy/ListItem';
import ListItemDecorator from '@mui/joy/ListItemDecorator';
import ListDivider from '@mui/joy/ListDivider';
import Menu from '@mui/joy/Menu';
import MenuItem from '@mui/joy/MenuItem';
import ArrowRight from '@mui/icons-material/ArrowRight';
import ArrowDropDown from '@mui/icons-material/ArrowDropDown';

export default function BasicMenu() {
const SIZES = ['X-Small', 'Small', 'Medium', 'Large', 'X-Large'];
const [size, setSize] = React.useState('Medium');
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};

return (
<div>
<Button
id="group-demo-button"
aria-controls={open ? 'group-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
variant="outlined"
color="neutral"
onClick={handleClick}
endDecorator={<ArrowDropDown />}
>
Size
</Button>
<Menu
id="group-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
aria-labelledby="group-demo-button"
sx={{ minWidth: 160, '--List-decorator-size': '24px' }}
>
<MenuItem
onClick={() => {
const nextIndex = SIZES.indexOf(size) - 1;
const value = nextIndex < 0 ? SIZES[SIZES.length - 1] : SIZES[nextIndex];
setSize(value);
handleClose();
}}
>
Smaller
</MenuItem>
<MenuItem
onClick={() => {
const nextIndex = SIZES.indexOf(size) + 1;
const value = nextIndex > SIZES.length - 1 ? SIZES[0] : SIZES[nextIndex];
setSize(value);
handleClose();
}}
>
Larger
</MenuItem>
<ListDivider />
<ListItem nested>
<List aria-label="Font sizes">
{SIZES.map((item) => (
<MenuItem
key={item}
role="menuitemradio"
aria-checked={item === size ? 'true' : 'false'}
onClick={() => {
setSize(item);
handleClose();
}}
>
<ListItemDecorator>
{item === size && <ArrowRight />}
</ListItemDecorator>{' '}
{item}
</MenuItem>
))}
</List>
</ListItem>
</Menu>
</div>
);
}
17 changes: 11 additions & 6 deletions docs/data/joy/components/menu/MenuIconSideNavExample.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import Menu, { menuClasses } from '@mui/joy/Menu';
import PropTypes from 'prop-types';
import Menu from '@mui/joy/Menu';
import MenuItem from '@mui/joy/MenuItem';
import IconButton from '@mui/joy/IconButton';
import List from '@mui/joy/List';
Expand All @@ -9,8 +10,6 @@ import Apps from '@mui/icons-material/Apps';
import Settings from '@mui/icons-material/Settings';
import Person from '@mui/icons-material/Person';

// 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/
const modifiers = [
{
name: 'offset',
Expand Down Expand Up @@ -98,15 +97,21 @@ function MenuButton({ children, menu, open, onOpen, onLeaveMenu, label, ...props
placement: 'right-start',
sx: {
width: 288,
[`& .${menuClasses.listbox}`]: {
'--List-padding': 'var(--List-divider-gap)',
},
},
})}
</React.Fragment>
);
}

MenuButton.propTypes = {
children: PropTypes.node,
label: PropTypes.string.isRequired,
menu: PropTypes.element.isRequired,
onLeaveMenu: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
};

export default function MenuIconSideNavExample() {
const [menuIndex, setMenuIndex] = React.useState(null);
const itemProps = {
Expand Down
202 changes: 202 additions & 0 deletions docs/data/joy/components/menu/MenuIconSideNavExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import * as React from 'react';
import Menu 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';

interface MenuButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
menu: React.ReactElement;
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 MenuButton({
children,
menu,
open,
onOpen,
onLeaveMenu,
label,
...props
}: MenuButtonProps) {
const buttonRef = React.useRef<HTMLButtonElement>(null);
const isOnButton = React.useRef(false);
const menuActions = React.useRef<any>(null);
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);
if (event.key === 'ArrowUp') {
menuActions.current?.highlightLastItem();
}
}
};

return (
<React.Fragment>
<IconButton
{...props}
ref={buttonRef}
variant="plain"
color="neutral"
aria-haspopup="menu"
aria-expanded={open ? 'true' : undefined}
aria-controls={open ? `nav-example-menu-${label}` : undefined}
onMouseDown={() => {
internalOpen.current = open;
}}
onClick={() => {
if (!internalOpen.current) {
onOpen();
}
}}
onMouseEnter={() => {
onOpen();
isOnButton.current = true;
}}
onMouseLeave={() => {
isOnButton.current = false;
}}
onKeyDown={handleButtonKeyDown}
sx={{
bgcolor: open ? 'neutral.plainHoverBg' : undefined,
'&.Joy-focusVisible': {
bgcolor: 'neutral.plainHoverBg',
},
}}
>
{children}
</IconButton>
{React.cloneElement(menu, {
open,
onClose: () => {
menu.props.onClose?.();
buttonRef.current?.focus();
},
onMouseLeave: () => {
onLeaveMenu(() => isOnButton.current);
},
actions: menuActions,
anchorEl: buttonRef.current,
modifiers,
slotProps: {
listbox: {
id: `nav-example-menu-${label}`,
'aria-label': label,
},
},
placement: 'right-start',
sx: {
width: 288,
},
})}
</React.Fragment>
);
}

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) => {
if (index === latestIndex) {
return null;
}
return latestIndex;
});
}
}, 200);
};
return (
<Sheet sx={{ borderRadius: 'sm', py: 1, mr: 20, bgcolor: 'background.body' }}>
<List>
<ListItem>
<MenuButton
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 />
</MenuButton>
</ListItem>
<ListItem>
<MenuButton
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 />
</MenuButton>
</ListItem>
<ListItem>
<MenuButton
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 />
</MenuButton>
</ListItem>
</List>
</Sheet>
);
}
Loading

0 comments on commit 31b86cc

Please sign in to comment.