Giter Club home page Giter Club logo

react-menu's Introduction

React-Menu

An accessible and keyboard-friendly React menu library.

Live examples and docs

NPM NPM TypeScript Known Vulnerabilities

Features

  • Unstyled and lightweight (8kB) React menu components
  • Unlimited levels of submenu
  • Supports dropdown, hover, and context menu
  • Supports radio and checkbox menu items
  • Flexible menu positioning
  • Comprehensive keyboard interactions
  • Customisable styling
  • Level 3 support of React 18 concurrent rendering
  • Supports server-side rendering
  • Implements WAI-ARIA menu pattern

Install

with npm

npm install @szhsin/react-menu

or with Yarn

yarn add @szhsin/react-menu

Usage

import { Menu, MenuItem, MenuButton, SubMenu } from '@szhsin/react-menu';

export default function App() {
  return (
    <Menu menuButton={<MenuButton>Open menu</MenuButton>}>
      <MenuItem>New File</MenuItem>
      <MenuItem>Save</MenuItem>
      <SubMenu label="Edit">
        <MenuItem>Cut</MenuItem>
        <MenuItem>Copy</MenuItem>
        <MenuItem>Paste</MenuItem>
      </SubMenu>
      <MenuItem>Print...</MenuItem>
    </Menu>
  );
}

Edit on CodeSandbox

Visit more examples and docs

FAQs

Still on an old version? Please checkout our migration guides.

License

MIT Licensed.

react-menu's People

Contributors

bozdoz avatar dependabot[bot] avatar dstawinski avatar elringus avatar fporzecki avatar galexandrade avatar gcangussu avatar mikedijkstra avatar panciumihai avatar ramsaysewell avatar szhsin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-menu's Issues

Closing controlled menu

Hi
I am using controlled menu, and wanted to ask how do I close the menu once I click on the submenu?

ControlledMenu with isOpen to true throws an error

The following code throws an error Cannot read property 'getBoundingClientRect' of null

const [isOpen, setOpen] = useState(true);
const ref = useRef(null);

  <button ref={ref} onClick={() => setOpen(true)}>
      Open menu
  </button>

  <ControlledMenu anchorRef={ref} isOpen={isOpen}
                  onClose={() => setOpen(false)}>
      <MenuItem>New File</MenuItem>
      <MenuItem>Save</MenuItem>
      <MenuItem>Close Window</MenuItem>
  </ControlledMenu>

React router support?

How to use this menu as navigational menu?

Adding href property skips router navigation. Adding nested <Link /> inside <MenuItem /> doesn't work.

I don't think there is a need for href prop at all. MenuItem children should be responsible for what is displayed as menu item and behaviour.

SubMenus can't be extracted to component

I have the following code:

  const sortByAccessed = () => setSortMode(ExplorerSortMode.Accessed);
  const sortByChanged = () => setSortMode(ExplorerSortMode.Changed);
  const sortByCreated = () => setSortMode(ExplorerSortMode.Created);
  const sortByKind = () => setSortMode(ExplorerSortMode.Kind);
  const sortByModified = () => setSortMode(ExplorerSortMode.Modified);
  const sortByName = () => setSortMode(ExplorerSortMode.Name);
<ControlledMenu
  anchorPoint={contextMenuAnchorPoint}
  isOpen={isContextMenuOpen}
  onClose={hideContextMenu}
>
  <MenuItem onClick={onOpenPress}>Open</MenuItem>
  <SubMenu label="Sort By">
    <MenuItem onClick={sortByName}>Name</MenuItem>
    <MenuItem onClick={sortByKind}>Kind</MenuItem>
    <MenuDivider />
    <MenuItem onClick={sortByAccessed}>Date accessed</MenuItem>
    <MenuItem onClick={sortByChanged}>Date changed</MenuItem>
    <MenuItem onClick={sortByCreated}>Date created</MenuItem>
    <MenuItem onClick={sortByModified}>Date modified</MenuItem>
  </SubMenu>
</ControlledMenu>

...and I need to reuse this submenu on another component, so I extracted it to a component:

import React, { FC } from 'react';

import { MenuDivider, MenuItem, SubMenu } from '@szhsin/react-menu';

import { useAppContext } from '../../hooks/use-app-context.hook';
import { ExplorerSortMode } from '../../types/explorer.types';

export const AppSortByContextMenu: FC = () => {
  const { setSortMode } = useAppContext();

  const sortByAccessed = () => setSortMode(ExplorerSortMode.Accessed);
  const sortByChanged = () => setSortMode(ExplorerSortMode.Changed);
  const sortByCreated = () => setSortMode(ExplorerSortMode.Created);
  const sortByKind = () => setSortMode(ExplorerSortMode.Kind);
  const sortByModified = () => setSortMode(ExplorerSortMode.Modified);
  const sortByName = () => setSortMode(ExplorerSortMode.Name);

  return (
    <SubMenu label="Sort By">
      <MenuItem onClick={sortByName}>Name</MenuItem>
      <MenuItem onClick={sortByKind}>Kind</MenuItem>
      <MenuDivider />
      <MenuItem onClick={sortByAccessed}>Date accessed</MenuItem>
      <MenuItem onClick={sortByChanged}>Date changed</MenuItem>
      <MenuItem onClick={sortByCreated}>Date created</MenuItem>
      <MenuItem onClick={sortByModified}>Date modified</MenuItem>
    </SubMenu>
  );
};
<ControlledMenu
  anchorPoint={contextMenuAnchorPoint}
  isOpen={isContextMenuOpen}
  onClose={hideContextMenu}
>
  <MenuItem onClick={onOpenPress}>Open</MenuItem>
  <AppSortByContextMenu />
</ControlledMenu>

...but nothing renders in place of AppSortByContextMenu. Is this simply not possible by design or did I mess up somewhere?
A warning in the terminal suggests using an applyHOC function, but I'm not using class components. Is there an FC alternative?

Generate child components without higher order components?

Hi there, from the docs it seems that the only way to use wrapped child components is via higher order components and applyHOC(). I tried it and was able to get it to work... but it took many hours and in general I find the HOC approach very cumbersome and unintuitive. Sadly if HOC's are the only way to generate child components I'm afraid it'll be a dealbreaker for my use case.

I was wondering if there is any way to do a more "traditional" approach to using wrapped child components? Something like the following example. I know there is an internal validation that will fail if I try this. Is there a way to perhaps turn off the validation (of course I would take care to only return the proper elements)?
Sandbox link: https://codesandbox.io/s/cool-sara-uqgqe?file=/src/App.js

const MyMenuItem = (text) => <MenuItem>{text}</MenuItem>
const App = () => (
  <Menu>
       <MyMenuItem text="hello" />
  </Menu>
)

// returns the following validation error...
The permitted children inside a Menu or SubMenu are MenuDivider, MenuHeader, MenuItem, FocusableItem, MenuRadioGroup, SubMenu. If you create HOC of these components, you can use the applyHOC or applyStatics helper, see more at: https://szhsin.github.io/react-menu/docs#utils-apply-hoc 

thanks in advance.

Has hidden dependency on prop-types

Thanks for the great library! :)

New to react, so I don't have many libraries/deps installed

Had to do:

npm install --save prop-types

else I got:

Module not found: Error: Can't resolve 'prop-types' in ...

Suggest moving prop-types from devDependencies to normal dependencies.

Webpack 5 no longer supports process in frontend code

Users could add the polyfill themself but webpack recommends that process is no longer used.
https://webpack.js.org/migrate/5/#run-a-single-build-and-follow-advises -> Level 5: Runtime Errors

React-menu contains this line where it breaks:
const isProd = process && process.env && process.env.NODE_ENV === 'production';

It looks like your using it to help the developer and throw warnings. I'm not sure how you can add a better detection for development mode in webpack. There are some tips in the migration guide.

react-testing-library example?

I'm currently adding some tests using react-testing-library, but I am having a difficulty with verifying that the menu has closed. In particular, it does not appear that the class rc-menu--open is removed after clicking the button a second time. I have tried setting the prop keepMounted={false} and updated my test logic, but that also does not appear to work.

This may not be an issue with react-menu itself, but the way I am testing it with react-testing-library.

const PredefinedMenu = () => {
  return (
    <Menu
      menuButton={<button>Test Menu</button>}
      keepMounted={false}
      animation={false}
    >
      <MenuItem value="item1">Item 1</MenuItem>
      <MenuItem value="item2">Item 2</MenuItem>
      <MenuItem value="item3">Item 3</MenuItem>
    </Menu>
  )
}

test('react-menu', async () => {
  const {container} = render(<PredefinedMenu />)
  const menuButton = container.querySelector('button')
  expect(container).toBeInTheDocument()

  // First render will not render the list element and only render the button
  expect(container.querySelector('ul')).toBe(null)
  expect(menuButton).toHaveTextContent('Test Menu')

  // Upon clicking the button, the list element will be rendered and displayed
  fireEvent.click(menuButton)
  expect(container.querySelector('ul')).not.toBe(null)
  expect(container.querySelector('ul')).toHaveClass('rc-menu--open')

  // Upon clicking the button again, the menu should close and be removed from DOM
  fireEvent.click(menuButton)
  await waitForElementToBeRemoved(container.querySelector('ul'))
})

Code Sandbox

Hard dependency on React 16.8 conflicts with my Projects React 17.0.1

I started my project recently with CRA and so it uses the latest react 17.0.1.
So when doing npm install I get

npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR!   react@"^17.0.1" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0" from @szhsin/[email protected]
npm ERR! node_modules/@szhsin/react-menu
npm ERR!   @szhsin/react-menu@"*" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

Menu won't update the position when resizing and using direction top

Hello, I'm using your library and I really like it!

I'm creating a menu with a search field that filters items as seen in your example, except the menu are rendered with a "top" position rather than the default position.

The problem is that, when the items get filtered, the menu doesn't resize correctly so it looks shorter but the position doesn't get updated.

Here's an example to reproduce my error:

https://stackblitz.com/edit/react-menu-wont-resize-rcrwmg?file=index.tsx

Currently opened submenu should stay open while mouse moves towards it

Most menus nowadays implement some logic that keeps the currently opened submenu open while the mouse moves towards it, even if that mouse hovers over another menu item (in the parent's list) on its way to the currently opened submenu.

This can be achieved by setting a timeout for the submenu's hide callback, within that timeout if the mouse hovers over a parent's list menu item, the submenu will be kept open.

Frustrating: https://i.imgur.com/iTOORe3.gifv

Not frustrating: https://i.imgur.com/e5eB4bx.gifv

Menu isn't being anchored to the anchor element

What happened

I cannot get the Menu top level component to properly anchor and position to the trigger.

Problem description

I grabbed a piece of sample code from the docs to try and experiment with the library. If you take a look at the code below, you will see that I'm trying to render the Menu inside a portal and have it positioned to the right of the MenuButton.

After running this code in my application, you can see that the Menu is being rendered to the bottom of the page instead of being anchored to the right of the trigger element. I'm not sure if I am incorrectly using the library or if this is a bug?

I've tried removing the portal prop and tinkering with the position and offset properties but have had no success.

Relevant code

const TestMenu = () => {
  return (
    <Menu
      menuButton={<MenuButton>Open menu</MenuButton>}
      align="center"
      direction="right"
      portal
    >
      <MenuItem>New File</MenuItem>
      <MenuItem>Save</MenuItem>
      <MenuItem>Close Window</MenuItem>
    </Menu>
  );
};

image

Feature request: Allow `undefined` children

Hi,

thank you for this fantastic library. During my use I ran into a scenario where I need to dynamically change the contents of the menu. I wanted to achieve this by:

<Menu>
    <MenuItem>...</MenuItem>
    { someCondition ? <MenuItem>Conditionally shown item</MenuItem> : undefined }
   <MenuItem>...</MenuItem>
   ...
</Menu>

Sadly, this does not work and results in Uncaught TypeError: Cannot read property 'type' of null

I want to take advantage of JSX. Building the child array as an array would work but would not be as convenient.

rendering menus side by side? Having issue figuring out how

I'm trying to render multiple menu buttons side by side using this module, but coming into issue, as they render one on top of the other. Is this something related to the stylesheet, or am I missing a attribute/parameter somewhere?
Here's a code snippet below for reference

import React from 'react';
import {
    Menu,
    MenuItem,
    MenuButton,
    SubMenu
} from '@szhsin/react-menu';
import '@szhsin/react-menu/dist/index.css'

class TopMenuDropdown extends React.Component {
    constructor(props) {
        super(props);
    }
    render () {
        return (
            <div>
                {this.props.TMPMenuTestCategory.map (({name,items},i) =>
                    {
                    return <Menu
                        align={'end'}
                        key={i}
                        menuButton={<MenuButton>{name}</MenuButton>}
                        reposition={'initial'}
                        >
                        {items.map((item,j) =>
                            {
                                console.log(item,j);
                            return <MenuItem key={j}>{item}</MenuItem>
                            }
                        )}
                    </Menu>
                } )}
            </div>
        )
    }
}

Feature Request: Support Menu inside overflow parent containers

Hello,

I really like this lib: code, docs and examples.
When I've found it some time ago I was delighted (even after used reactstrap and antd multi level dropdown)
So good you share it.

But what I observed now the menu is rendered direct in DOM, direct below its trigger.
Without any js/react popper or react portals it cannot force through overflow parents container
(the ones with overflow: hidden; position: relative;)

I hope you will find the way.
I will keep my fingers crossed.

Typescript Support?

Is there any type declarations for this library? I can't find any. If not no problem I can work on creating them and send a PR.

IE11 Support?

I hate to be the guy asking this... but does react-menu support IE11? I have tried it locally in a project but the menu disappears as soon as the menu button is clicked. In a CRA with just react-menu:

import { Menu, MenuItem, MenuButton } from "@szhsin/react-menu/dist/index.js";
import "@szhsin/react-menu/dist/index.css";

function App() {
  return (
    <div>
       <Menu menuButton={<MenuButton>Open menu</MenuButton>}>
         <MenuItem>Test Item 1</MenuItem>
         <MenuItem>Test Item 2</MenuItem>
      </Menu>
    </div>
  );
}

This will render the button correctly, but once pressed, IE11 will output an error saying Object doesn't support property or method 'includes'. Since this can probably be resolved with a polyfill, I added:

import "core-js/features/array/includes";

With that, the menu opens, but instantly closes right after and behaves radically.

ControlledMenu isOpen initially set to true

I have a situation where I am expecting a ControlledMenu to be open by default. Simply hardcoding the isOpen prop to be true will result in a getBoundingClientRect error.

<button ref={buttonRef}>Menu</button>

<ControlledMenu
    anchorRef={buttonRef}
    isOpen={true}
>
...

Perhaps the button has yet to render and react-menu is attempting to determine the placement of the menu on the screen? I'm currently working around this by setting the isOpen to false and using a short timer to set open to true. But it is definitely not a good long-term solution.

Code Sandbox

React Components as Menu's Children?

Is it possible to have React components that render/return MenuItem or SubMenu as a child of Menu or ControlledMenu? For example, if you wanted to apply some logic or special functionality, or just break a very large menu into smaller pieces, how would you approach this?

const SomeItems = () => (
  <>
    <MenuItem>New File</MenuItem>
    <MenuItem>Save</MenuItem>
    <MenuItem>Close Window</MenuItem>
  </>
);

...

<ControlledMenu anchorRef={buttonRef} isOpen={open}>
  <SomeItems />
</Controlled Menu>

I noticed that you can can bypass the children check by specifying:

SomeItems.__name__ = 'MenuItem'

Here are more examples:
code sandbox

Clicking Checkboxes and Radio Menu items is closing the Menu.

I am trying to use checkbox menu items and MenuRadioGroup with normal Menu (not the ControlledMenu). Keyboard navigation and selection seems to be great. And when selected any menuItem with spaceBar, the item gets selected/unselected accordingly and the Menu will be still in OPEN state. But whereas, when I try to use EnterKey OR use a mouse click the menuItem gets selected/unselected but in this case, the Menu is closing. Is there a way to let the Menu (not ControlledMenu) stay open when the user hits Enter/mouse click on a checkboxMenuItem or RadioGroupMenuItem.

Hover over to produce a popup box for hovered text?

I noticed there is a hover option and things you can do, however, is it possible for the hover option, to produce another item that 'pops' out for the entree?

Main use is to have menu items in one language, which, if you hover over, would produce a different box with English text equivalent to that entry?

Is this something that your Menu module could do, or would I have to find a separate way of getting this to work for the MenuItem elements?

Something equivalent to this
image

Feature request: Allow the use of MenuDivider inside MenuRadioGroup

Assuming I have a component rendering the following:

<MenuRadioGroup
  value={appSettings.sortMode}
  onChange={onSortModeChange}
>
  <MenuItem value={ExplorerSortMode.Name}>Name</MenuItem>
  <MenuItem value={ExplorerSortMode.Kind}>Kind</MenuItem>
  <MenuDivider />
  <MenuItem value={ExplorerSortMode.Accessed}>Date accessed</MenuItem>
  <MenuItem value={ExplorerSortMode.Changed}>Date changed</MenuItem>
  <MenuItem value={ExplorerSortMode.Created}>Date created</MenuItem>
  <MenuItem value={ExplorerSortMode.Modified}>Date modified</MenuItem>
</MenuRadioGroup>

The MenuDivider will be ignored, with the following warning being logged in the terminal: The permitted children inside a MenuRadioGroup are MenuItem.

I see no reason why MenuDivider wouldn't be allowed here.

Getting warning when used with ssr

I'm using this with next js and the following is being printed to the console

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.

Any idea how to remove this?

Menu Items that are part of MenuRadioGroup are not calling the onClick handler on actual Menu

Problem Description
I have a functionality that is common for all menu item clicks, so I decided to have that in the onClick handler of the Menu, expecting the menuOnclickHandler would be called for each MenuItem click. But this is not happening if the MenuItem is from a MenuRadioGroup. Also the onClick handler on the RadioMenuItem is not getting called either. Is this intentional? If so what is the reason behind it. Or is this a bug?

CodeSandbox example

https://codesandbox.io/s/serene-minsky-g6xeh?file=/src/App.js

In that example you can notice, that when clicked on the RadioMenuItems, results are not getting updated.

Thanks in advance! :)

Prevent Default

Error: Is there a way to prevent the page from reloading when browsing

MenuList container is always rendered

Just noticing that the menu container is always rendered:

const menuList = (
<div id={id}
className={bem(menuContainerClass, null, { theme: theming })()}
role="presentation"
ref={containerRef}
onKeyDown={handleKeyDown}
onBlur={handleBlur}>
<SettingsContext.Provider value={settings}>
<EventHandlersContext.Provider value={eventHandlers}>
<MenuList {...menuListProps}
containerRef={containerRef}
onClose={onClose} />
</EventHandlersContext.Provider>
</SettingsContext.Provider>
</div>
);

I was wondering if the presentation container could be rendered only when open: similar to the menu list itself. Not sure if it is on purpose or not.

Menu loses focus when active menu item is filtered out. Now clicking outside does not close the menu until the menu is focused again

Hi
Not sure if the use case is supported. What I am trying to do is to have a menu with a search input, but I also want the user to be able to type and filter even when the search input is not active.
See https://codesandbox.io/s/wizardly-shamir-rrcxy?file=/src/App.js

In this case, when a menu item is active and I type and filter out the the active menu item, If the number of items becomes less than the last active menu index, then the menu loses focus, but is not closed. Clicking outside does not close the menu as well.
Since the menu is not focussed, it does not handle keyboard events

I was expecting the menu to not lose focus
bug2

Cannot extend MenuItem

Hi @szhsin,
Thanks for publish this library. I want to create MyMenuItem class to extend MenuItem class to provide some custom css. However, when I put MyMenuItem elements in the class inside Menu class, they don't show up. As a quick check in MenuItem source, it seems like I need to wrap defineName around MyMenuItem. I think many people would get trouble because of this. Any way to make this work normally without using defineName?

export const MenuItem = defineName(React.memo(function MenuItem({

Thanks.

MenuList doesn't accept React.forwardRef'ed MenuItem

Our application uses HOC with React.forwardRef call to add certain classNames and loggings.
But, it looks like MenuList doesn't accept a React.forwardRef'ed MenuItem
https://github.com/szhsin/react-menu/blob/v1.2.2/src/components/MenuList.js#L109

Here is a super simplified codesandbox example to demonstrate the issue.
https://codesandbox.io/s/react-menu-forward-ref-example-lzfgu

I can do
WrappedMenuItem.__name__ = 'MenuItem';
But this looks too hacky.

Any suggestions?

Clicking on parent menu closes and opens the submenu

Clicking on the parent menu closes and opens the submenu. This causes some flickering and is not the behaviour I have seen in other menus (I checked in Intellij idea and vscode). Is there any way of keeping the submenu open instead of closing and opening the submenu.

Feature Request: Support more aria attributes on MeunItem

Hi,

First of all, great job and thanks for sharing the component.

We're looking for something with good accessibility and this is wonderful out of the box.

What we would like to see is that all aria-* attributes to be supported in the interactive elements in this repo.

For example, if a menu item opens up a dialog, we'd like to add aria-haspopup="dialog" to the MenuItem's <li>.

A quick and dirty way to support this would be spreading restProps on the <li> on this line.

e.g.

<li {...restProps} {...menuItemProps}>

same as you did here but on the <a>.

So that consumers can pass arbitrary props via MenuItem's prop interface.

Thanks for considering!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.