Skip to content
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

Add support for submenus #287

Open
corymharper opened this issue Sep 27, 2021 · 1 comment
Open

Add support for submenus #287

corymharper opened this issue Sep 27, 2021 · 1 comment

Comments

@corymharper
Copy link
Contributor

Right now, the only fully supported implementation by our hook is a one level deep menu, submenus require extra keyboard controls and considerations that aren't currently implemented.

A merge request that closes this issue should ensure that the WAI-ARIA Practices are still followed to conformity for a vertical menu. This includes:

Right Arrow:

  • When focus is in a menu and on a menuitem that has a submenu, opens the submenu and places focus on its first item.

Left Arrow:

  • When focus is in a submenu of an item in a menu, closes the submenu and returns focus to the parent menuitem.

Escape:

  • Close the menu that contains focus and return focus to the element or context, e.g., menu button or parent menuitem, from which the menu was opened.

The hook also supports the behavior of moving to the first menu item that starts with a specific printable character when it is pressed. If submenus are supported, that behavior should be contained to the current menu context (i.e. the menu within which elements currently have focus).

@corymharper
Copy link
Contributor Author

corymharper commented Apr 3, 2023

Right now I'm leaning towards the best design being to add an option submenu: boolean = false. When enabled two new controls would be added, which are detailed in the issue description for the right arrow and the left arrow, no new work would really need to be done for escape. When submenu was true, instead of returning buttonProps from the hook for the menu button we would return parentProps, which would be shaped like the following:

type ParentProps = {
        onKeyDown: (e: React.KeyboardEvent | React.MouseEvent) => void;
        onClick: (e: React.KeyboardEvent | React.MouseEvent) => void;
        tabIndex: -1;
        ref: React.RefObject<T>;
        role: 'menuitem';
        'aria-haspopup': true;
        'aria-expanded': boolean;
};

Very similar to buttonProps with some small differences. On the users side, it would be expected they'd be using the hook in some fashion similar to this:

div[role='menu'] {
    visibility: hidden;
}

div[role='menu'].visible {
    visibility: visible;
}
// Inner menu
const Submenu = React.forwardRef((props: { 
    parentMenuItem: {
        text: string;
        children: { text: string }[];
    },
    ...props,
}, ref) => {
    const { parentProps, itemProps, isOpen } = useDropdownMenu(props.parentMenuItem.children.length, { submenu: true });
    
    return (
        <React.Fragment>
            <a {...parentProps} ref={ref}>Parent menu item</a>

            <div className={isOpen ? 'visible' : ''} role='menu'>
                {props.parentMenuItem.children.map((child, i) =>
                    <a {...itemProps[i]}>{child.text}</a>
                )}
            </div>
        </React.Fragment>
    );
})


// Outer menu
const DropdownMenu = () => {
    const items = [
        {
            text: 'I am a submenu',
            children: [
                {
                    text: 'I am a child of a submenu'
                }
        }
    ];

    const { buttonProps, itemProps, isOpen } = useDropdownMenu(items.length);

    return (
        <React.Fragment>
            <button {...buttonProps}>Example</button>

            <div className={isOpen ? 'visible' : ''} role='menu'>
                    <Submenu {...itemProps[0]} parentMenuItem={items[0]}  />
            </div>
        </React.Fragment>
    );
}

There's probably some issues with what I've drafted above but that would be the general idea, keep the hook mostly the same, implement the new submenu option and related behavior, then expect developer's to leverage that behavior by encapsulating their submenus into their own components. This leverages all the behavior built into the hook already, and still does a lot of work for developers wanting to build menus with submenus while using our hook.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

1 participant