React. Headless UI Menu Items can't receive focus on keyboard navigation - reactjs

I have a dropdown menu builth with Headless UI Menu component, but the menu links inside this dropdown are not getting focused on keyboard navigation.
my code goes something like so:
function Dropdown(parent, children) {
return (
<Menu as="div" className="Submenu">
{
({close}) => (
<>
<Menu.Button>
{ parent.label }
</Menu.Button>
<Menu.Items as="ul" className="subitems">
{
children.map(child => (
<Menu.Item as="li" className="subitem" key={ child.id }>
<Link href={ child.link }>
{ child.label }
</Link>
</Menu.Item>
))
}
</Menu.Items>
</>
)
}
</Menu>
);
}
The only thing getting the focus is the <ul></ul that wraps the items, but once I press tab the dropdown closes and the focus goes to the next item on the parent level menu.
The final html rendered is this:
I've tried to add tabIndex={0} to the tags, but the output stays the same.
I also tried to create a Link wrapper component ike suggested here, but it didn't work.

Related

Problem with reseting openKeys in Ant Design Menu within Sider

I have antd Menu inside collapsible Sider. I've set default open keys for one of Submenus and they should open one at a time. Here is my code for the Menu:
const MainMenu = ({ defaultOpenKeys }) => {
const [openKeys, setOpenKeys] = useState(defaultOpenKeys);
const rootKeys = ["sub1", "sub2"];
// Open only one submenu at a time
const onOpenChange = props => {
const latestOpenKey = props.find(key => openKeys.indexOf(key) === -1);
if (rootKeys.indexOf(latestOpenKey) === -1) {
setOpenKeys(props);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : defaultOpenKeys);
}
};
return (
<Menu
theme="dark"
openKeys={openKeys}
defaultSelectedKeys={["1"]}
mode="inline"
onOpenChange={onOpenChange}
>
<Menu.Item key="1">
Option 1
</Menu.Item>
<SubMenu key="sub1" title="User">
<Menu.Item key="2">Tom</Menu.Item>
</SubMenu>
<SubMenu key="sub2" title="Team">
<Menu.Item key="3">Team 1</Menu.Item>
</SubMenu>
</Menu>
);
};
export default MainMenu;
I pass defaultOpenKeys from the Sider.
const SiderDemo = () => {
const [collapsed, setCollapsed] = useState(false);
const toggleSider = () => {
setCollapsed(!collapsed);
};
return (
<Layout style={{ minHeight: "100vh" }}>
<Button type="primary" onClick={toggleSider}>
{React.createElement(
collapsed ? MenuFoldOutlined : MenuUnfoldOutlined
)}
</Button>
<Sider
collapsible
collapsed={collapsed}
collapsedWidth={0}
trigger={null}
>
<Menu defaultOpenKeys={["sub1"]} />
</Sider>
...
</Layout>
);
};
It works on mount, but when I collapse the Sider, defaultOpenKeys are being reset. How can I keep defaultOpenKeys from being reset, when the Sider is collapsed?
I have created a codesandbox and added console log in the Menu. You can see that defaultOpenKeys and openKeys are the same on mount. When I collapse the Sider, the console log is triggered twice. The first time defaultOpenKeys and openKeys are the same. And the second time openKeys become empty. How can I fix that?
Reason: on closing the sidebar it is closing opened sidemenu so it gonna trigger openchange with empty array and hence your logic making it reset to empty.
Here is code sandbox link with updated code
https://codesandbox.io/s/sider-demo-0der5?file=/Menu.jsx
Suggestion: Its anti pattern to assign props to initial state. if prop value changed in parent component then the new prop value will never be displayed because intial state will never update the current state of the component. The initialization of state from props only runs when the component is first created

ReactRouterHashLink not palying well with material UI drawer

I am using the packages material-ui, react-router, and react-router-hash-link. I want to have a material UI drawer (along an appbar) have hashlinks that scroll to specific bodies in the page, however whenever I do this, the scroll will happen, but when I close the drawer it reverts the scrollbar to the top of the page.
I hope you found a solution, for anybody who came across here, here is a solution:
The Drawer is expected to be closed after navigation. So, if you updated the drawer state on the onClick handler it would work as expected.
<Drawer
onClose={this.handleToggleDrawer}
anchor={"right"}
open={this.state.openDrawer}
>
<List>
{links.map(({ text, to, icon }) => (
<ListItem
onClick={this.handleToggleDrawer}
activeClassName={"Mui-selected"}
to={to}
button
key={to}
>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Drawer>
handleToggleDrawer = () => {
const { openDrawer } = this.state;
this.setState({ openDrawer: !openDrawer });
}

How do you close the material UI context menu, without displaying the default context menu?

I am trying to use the material UI menu to display a context menu on a div, as in https://material-ui.com/components/menus/#context-menu
This works I can right-click and it displays.
However, the material-ui context menu when displayed injects a transparent div covering the whole screen behind the menu. This means any further clicks are intercepted by this element. It seems this element will close the menu when a left click is detected but will move and continue displaying the same context menu if you right-click anywhere else on the page, including where the context menu is irrelevant.
Is there any way to display the menu without this transparent div which is removing control from my page?
You can see this action in the example: https://material-ui.com/components/menus/#context-menu
Right-click on the text, then right-click anywhere else (while the menu is still displayed) and you can trigger the menu to appear all over the page, even in the app bar, where the menu options make no sense.
I tried to do it in like this:
export default function MenuPopupState() {
return (
<PopupState variant="popover" popupId="demo-popup-menu">
{popupState => {
const menuProps = bindMenu(popupState);
return (
<React.Fragment>
<Button
variant="contained"
color="primary"
{...bindTrigger(popupState)}
>
Open Menu
</Button>
<Menu
{...menuProps}
onContextMenu={event => {
event.preventDefault();
}}
onMouseDown={e => {
menuProps.onClose();
}}
>
<MenuItem onClick={popupState.close}>Cake</MenuItem>
<MenuItem onClick={popupState.close}>Death</MenuItem>
</Menu>
</React.Fragment>
);
}}
</PopupState>
);
}
https://codesandbox.io/s/material-demo-szpbr?file=/demo.js

Getting id from an event handler that was passed to a button component in React?

I have an App component that renders a MenuBar component. In that MenuBar, I render ListItems (from material-ui) and pass the following as a prop:
onClick = (e) => {
const id = e.target.id;
console.log(id);
console.log('called');
}
the MenuBar component:
render() {
const onClick = this.props.onClick;
const titles = ["Home", "About", "Docket", "Polls", "News"];
const listItems = titles.map(title =>
<ListItem button id={title} onClick={onClick} key={title}>
<ListItemText primary={title} />
</ListItem>);
return (
<List
component="ul"
className="menu-bar">
{listItems}
</List>
);
}
I want to use this title, which I try to retrieve from the event, so that I can render a Home component, or About component, etc, depending on which ListItem is selected. But when I run this and click randomly on some of the ListItems in my browser, the title is only logged to the console sometimes (seemingly randomly). What is the best way for me to access which ListItem was selected, or why is the console logging the title only sometimes?
Here is what I get in the browser console:
Docket
called
called
Polls
called
News
called
The reason why you don't consistently get id is that the html element triggering the click event is not always what you think. Please see this sandbox for an example. When you click on any of the ListItem, sometimes you get (if you click to the left of where the letter is, based on my trials):
<div class="MuiButtonBase-root-1883 MuiListItem-root-1871 MuiListItem-default-1874 MuiListItem-gutters-1879 MuiListItem-button-1880" tabindex="0" role="button" id="a">...</div>
where the id is present. But other times you get:
<span class="MuiTypography-root-1892 MuiTypography-subheading-1899 MuiListItemText-primary-1889">a</span>
where the id is missing.
A solution to this is to pass title directly to the onClick function:
<ListItem button id={title} onClick={() => onClick(title)}>
...
</ListItem>
Accordingly, the onClick function should be updated as follows:
onClick = (title) => {
console.log(title);
console.log('called');
}
currentTarget is what worked it for me. (instead of just target)
I've read elsewhere that maybe MUI is resulting in unexpected behavior. Similar to what Claire Lin stated, in the Material UI List (and for me, it was nested in a MUI Drawer as well), when clicking on a button, the actual target was different from what I expected. The solution I found is to use the following:
Try e.currentTarget.getAttribute('value') , where "value" is data placed on the ListItem.
Here's how I added it. See "value" being assigned, and then logged below in the onClick. (...I tried to remove most of the extra code, but there is still a bit of superfluous code in there just bc it provides context.)
return (
//removed extra code for brevity
<Drawer
anchor={'left'}
open={drawerOpen}
onClose={toggleDrawerOpen(false)}
>
<List>
{itemsList.map((item, index) => {
const { text, icon, id } = item
return (
<ListItem
button
key={id}
value={index} // <<<< HERE!!!
onClick={(e) => {
console.log(e.currentTarget.getAttribute('value')) // <<<< HERE!!!
setDrawerOpen(false)
}}
>
{icon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText primary={text} />
</ListItem>
)
})}
</List>
</Drawer>
...Again, I used "value" and assigned it the "index".
currentTarget is the trick.

How not to open a new tab on clicking middle mouse button?

I want to open a new page in the same tab on clicking middle mouse button.
Here is what i am trying to do...
I have a card component that contains image and footer. When i click middle mouse button on image it should open a new page in the same tab. It opens the new page in the same tab if i click left mouse button. However on clicking middle mouse button it opens the page in new tab...I want to prevent that. Below is the code.
render = () => {
return (
<div className="card">
<Link style={{textDecoration: 'none'}} to="/card/new">
<div>
<Image
/>
</div>
</Link>
<div className="footer">
<div className="info">
<h4>{model.modelname}</h4>
</div>
</div>
</div>
);
};
Can somebody guide me how to prevent the page from opening in new tab on middle mouse click? Thanks.
You can add a mouseDown event and then detect the middle button click like
handleMouseDown = (e) => {
if(e.which === 2) {
// do something on middle mouse button click
}
}
You code might look like
class MyComponent extends Component {
handleMouseDown() {
if(e.which === 2) {
// do something on middle mouse button click
// like this.props.history.push('/card/new');
}
}
render() {
<div style={{textDecoration: 'none'}} onMouseDown={this.handleMouseDown} />
}
}
export default withRouter(MyComponent);

Resources