-
-
Notifications
You must be signed in to change notification settings - Fork 31.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MenuUnstyled] Create MenuUnstyled and useMenu #30961
Changes from 34 commits
70bbeb9
5355eed
906c89e
eebfe60
b0fd03d
2c67501
243b4d5
29522aa
70bffdb
53303c0
48f6c06
2426b43
51b26d1
9461f49
c9cd01c
d7bd3a7
02ce2ea
662ceed
87f0911
096e112
878d407
2c2e5d6
d784001
7c4dcf5
31c5c8c
d72c49b
d61b3f2
beb6315
3b950cd
2171704
fb91225
06e1c9c
f0b6e00
0abace3
ad71ebf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import * as React from 'react'; | ||
import MenuUnstyled from '@mui/base/MenuUnstyled'; | ||
import MenuItemUnstyled, { | ||
menuItemUnstyledClasses, | ||
} from '@mui/base/MenuItemUnstyled'; | ||
import { buttonUnstyledClasses } from '@mui/base/ButtonUnstyled'; | ||
import PopperUnstyled from '@mui/base/PopperUnstyled'; | ||
import { styled } from '@mui/system'; | ||
|
||
const blue = { | ||
100: '#DAECFF', | ||
200: '#99CCF3', | ||
400: '#3399FF', | ||
500: '#007FFF', | ||
600: '#0072E5', | ||
900: '#003A75', | ||
}; | ||
|
||
const grey = { | ||
100: '#E7EBF0', | ||
200: '#E0E3E7', | ||
300: '#CDD2D7', | ||
400: '#B2BAC2', | ||
500: '#A0AAB4', | ||
600: '#6F7E8C', | ||
700: '#3E5060', | ||
800: '#2D3843', | ||
900: '#1A2027', | ||
}; | ||
|
||
const StyledListbox = styled('ul')( | ||
({ theme }) => ` | ||
font-family: IBM Plex Sans, sans-serif; | ||
font-size: 0.875rem; | ||
box-sizing: border-box; | ||
padding: 5px; | ||
margin: 10px 0; | ||
min-width: 200px; | ||
background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; | ||
border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[300]}; | ||
border-radius: 0.75em; | ||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | ||
overflow: auto; | ||
outline: 0px; | ||
`, | ||
); | ||
|
||
const StyledMenuItem = styled(MenuItemUnstyled)( | ||
({ theme }) => ` | ||
list-style: none; | ||
padding: 8px; | ||
border-radius: 0.45em; | ||
cursor: default; | ||
|
||
&:last-of-type { | ||
border-bottom: none; | ||
} | ||
|
||
&.${menuItemUnstyledClasses.focusVisible} { | ||
outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; | ||
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; | ||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | ||
} | ||
|
||
&.${menuItemUnstyledClasses.disabled} { | ||
color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]}; | ||
} | ||
|
||
&:hover:not(.${menuItemUnstyledClasses.disabled}) { | ||
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; | ||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | ||
} | ||
`, | ||
); | ||
|
||
const TriggerButton = styled('button')( | ||
({ theme }) => ` | ||
font-family: IBM Plex Sans, sans-serif; | ||
font-size: 0.875rem; | ||
box-sizing: border-box; | ||
min-height: calc(1.5em + 22px); | ||
min-width: 200px; | ||
background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; | ||
border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[300]}; | ||
border-radius: 0.75em; | ||
margin: 0.5em; | ||
padding: 10px; | ||
text-align: left; | ||
line-height: 1.5; | ||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | ||
|
||
&:hover { | ||
background: ${theme.palette.mode === 'dark' ? '' : grey[100]}; | ||
border-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]}; | ||
} | ||
|
||
&.${buttonUnstyledClasses.focusVisible} { | ||
outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[100]}; | ||
} | ||
|
||
&::after { | ||
content: '▾'; | ||
float: right; | ||
} | ||
`, | ||
); | ||
|
||
const Popper = styled(PopperUnstyled)` | ||
z-index: 1; | ||
`; | ||
|
||
export default function UnstyledMenuSimple() { | ||
const [anchorEl, setAnchorEl] = React.useState(null); | ||
const isOpen = Boolean(anchorEl); | ||
const buttonRef = React.useRef(null); | ||
const menuActions = React.useRef(null); | ||
|
||
const handleButtonClick = (event) => { | ||
if (isOpen) { | ||
setAnchorEl(null); | ||
} else { | ||
setAnchorEl(event.currentTarget); | ||
} | ||
}; | ||
|
||
const handleButtonKeyDown = (event) => { | ||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { | ||
event.preventDefault(); | ||
setAnchorEl(event.currentTarget); | ||
if (event.key === 'ArrowUp') { | ||
menuActions.current?.highlightLastItem(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like that we need to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In addition to this:
Has too much boilerplate which is important to not be missed. I would propose to tackle this in the next PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I absolutely agree it's too much code. I will create the MenuButton for sure. |
||
} | ||
} | ||
}; | ||
|
||
const close = () => { | ||
setAnchorEl(null); | ||
buttonRef.current.focus(); | ||
}; | ||
|
||
const createHandleMenuClick = (menuItem) => { | ||
return () => { | ||
// eslint-disable-next-line no-console | ||
console.log(`Clicked on ${menuItem}`); | ||
close(); | ||
}; | ||
}; | ||
|
||
return ( | ||
<div> | ||
<TriggerButton | ||
type="button" | ||
onClick={handleButtonClick} | ||
onKeyDown={handleButtonKeyDown} | ||
ref={buttonRef} | ||
aria-controls={isOpen ? 'simple-menu' : undefined} | ||
aria-expanded={isOpen || undefined} | ||
aria-haspopup="menu" | ||
> | ||
Language | ||
</TriggerButton> | ||
|
||
<MenuUnstyled | ||
actions={menuActions} | ||
open={isOpen} | ||
onClose={close} | ||
anchorEl={anchorEl} | ||
components={{ Root: Popper, Listbox: StyledListbox }} | ||
componentsProps={{ listbox: { id: 'simple-menu' } }} | ||
> | ||
<StyledMenuItem onClick={createHandleMenuClick('English')}> | ||
English | ||
</StyledMenuItem> | ||
<StyledMenuItem onClick={createHandleMenuClick('中文')}>中文</StyledMenuItem> | ||
<StyledMenuItem onClick={createHandleMenuClick('Português')}> | ||
Português | ||
</StyledMenuItem> | ||
</MenuUnstyled> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import * as React from 'react'; | ||
import MenuUnstyled, { MenuUnstyledActions } from '@mui/base/MenuUnstyled'; | ||
import MenuItemUnstyled, { | ||
menuItemUnstyledClasses, | ||
} from '@mui/base/MenuItemUnstyled'; | ||
import { buttonUnstyledClasses } from '@mui/base/ButtonUnstyled'; | ||
import PopperUnstyled from '@mui/base/PopperUnstyled'; | ||
import { styled } from '@mui/system'; | ||
|
||
const blue = { | ||
100: '#DAECFF', | ||
200: '#99CCF3', | ||
400: '#3399FF', | ||
500: '#007FFF', | ||
600: '#0072E5', | ||
900: '#003A75', | ||
}; | ||
|
||
const grey = { | ||
100: '#E7EBF0', | ||
200: '#E0E3E7', | ||
300: '#CDD2D7', | ||
400: '#B2BAC2', | ||
500: '#A0AAB4', | ||
600: '#6F7E8C', | ||
700: '#3E5060', | ||
800: '#2D3843', | ||
900: '#1A2027', | ||
}; | ||
|
||
const StyledListbox = styled('ul')( | ||
({ theme }) => ` | ||
font-family: IBM Plex Sans, sans-serif; | ||
font-size: 0.875rem; | ||
box-sizing: border-box; | ||
padding: 5px; | ||
margin: 10px 0; | ||
min-width: 200px; | ||
background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; | ||
border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[300]}; | ||
border-radius: 0.75em; | ||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | ||
overflow: auto; | ||
outline: 0px; | ||
`, | ||
); | ||
|
||
const StyledMenuItem = styled(MenuItemUnstyled)( | ||
({ theme }) => ` | ||
list-style: none; | ||
padding: 8px; | ||
border-radius: 0.45em; | ||
cursor: default; | ||
|
||
&:last-of-type { | ||
border-bottom: none; | ||
} | ||
|
||
&.${menuItemUnstyledClasses.focusVisible} { | ||
outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; | ||
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; | ||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | ||
} | ||
|
||
&.${menuItemUnstyledClasses.disabled} { | ||
color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]}; | ||
} | ||
|
||
&:hover:not(.${menuItemUnstyledClasses.disabled}) { | ||
background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; | ||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | ||
} | ||
`, | ||
); | ||
|
||
const TriggerButton = styled('button')( | ||
({ theme }) => ` | ||
font-family: IBM Plex Sans, sans-serif; | ||
font-size: 0.875rem; | ||
box-sizing: border-box; | ||
min-height: calc(1.5em + 22px); | ||
min-width: 200px; | ||
background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; | ||
border: 1px solid ${theme.palette.mode === 'dark' ? grey[800] : grey[300]}; | ||
border-radius: 0.75em; | ||
margin: 0.5em; | ||
padding: 10px; | ||
text-align: left; | ||
line-height: 1.5; | ||
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; | ||
|
||
&:hover { | ||
background: ${theme.palette.mode === 'dark' ? '' : grey[100]}; | ||
border-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]}; | ||
} | ||
|
||
&.${buttonUnstyledClasses.focusVisible} { | ||
outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[100]}; | ||
} | ||
|
||
&::after { | ||
content: '▾'; | ||
float: right; | ||
} | ||
`, | ||
); | ||
|
||
const Popper = styled(PopperUnstyled)` | ||
z-index: 1; | ||
`; | ||
|
||
export default function UnstyledMenuSimple() { | ||
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null); | ||
const isOpen = Boolean(anchorEl); | ||
const buttonRef = React.useRef<HTMLButtonElement>(null); | ||
const menuActions = React.useRef<MenuUnstyledActions>(null); | ||
|
||
const handleButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => { | ||
if (isOpen) { | ||
setAnchorEl(null); | ||
} else { | ||
setAnchorEl(event.currentTarget); | ||
} | ||
}; | ||
|
||
const handleButtonKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => { | ||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { | ||
event.preventDefault(); | ||
setAnchorEl(event.currentTarget); | ||
if (event.key === 'ArrowUp') { | ||
menuActions.current?.highlightLastItem(); | ||
} | ||
} | ||
}; | ||
|
||
const close = () => { | ||
setAnchorEl(null); | ||
buttonRef.current!.focus(); | ||
}; | ||
|
||
const createHandleMenuClick = (menuItem: string) => { | ||
return () => { | ||
// eslint-disable-next-line no-console | ||
console.log(`Clicked on ${menuItem}`); | ||
close(); | ||
}; | ||
}; | ||
|
||
return ( | ||
<div> | ||
<TriggerButton | ||
type="button" | ||
onClick={handleButtonClick} | ||
onKeyDown={handleButtonKeyDown} | ||
ref={buttonRef} | ||
aria-controls={isOpen ? 'simple-menu' : undefined} | ||
aria-expanded={isOpen || undefined} | ||
aria-haspopup="menu" | ||
> | ||
Language | ||
</TriggerButton> | ||
|
||
<MenuUnstyled | ||
actions={menuActions} | ||
open={isOpen} | ||
onClose={close} | ||
anchorEl={anchorEl} | ||
components={{ Root: Popper, Listbox: StyledListbox }} | ||
componentsProps={{ listbox: { id: 'simple-menu' } }} | ||
> | ||
<StyledMenuItem onClick={createHandleMenuClick('English')}> | ||
English | ||
</StyledMenuItem> | ||
<StyledMenuItem onClick={createHandleMenuClick('中文')}>中文</StyledMenuItem> | ||
<StyledMenuItem onClick={createHandleMenuClick('Português')}> | ||
Português | ||
</StyledMenuItem> | ||
</MenuUnstyled> | ||
</div> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the
ArrowUp
is pressed, the last element should be foced when the menu opens.