Add a menu component.

Review Request #13641 — Created March 17, 2024 and submitted

Information

Ink
master

Reviewers

The menu component provides a drop-down/pop-up or embedded menu,
complete with standard menu items, checkbox menu items, radio menu
items, and separators.

Menu items may contain icons and keyboard shortcuts bound to those
items.

All parts of the menu are built with accessibility in mind, making it
usable by people interacting with assistive technologies like screen
readers.

Much of the implementation was built based on the experiences building
the original menu implementation in Review Board and in the
reviewboard.org website, with enhancements to enable crafting, keyboard
shortcuts, checkbox/radio items, embedded mode, and to fix interaction
issues.

Tested the menu in embedded and drop-down modes.

Tested that keyboard shortcuts are registered and work.

Tested light and dark modes.

Unit tests pass.

Summary ID
Add a menu component.
The menu component provides a drop-down/pop-up or embedded menu, complete with standard menu items, checkbox menu items, radio menu items, and separators. Menu items may contain icons and keyboard shortcuts bound to those items. All parts of the menu are built with accessibility in mind, making it usable by people interacting with assistive technologies like screen readers. Much of the implementation was built based on the experiences building the original menu implementation in Review Board and in the reviewboard.org website, with enhancements to enable crafting, keyboard shortcuts, checkbox/radio items, embedded mode, and to fix interaction issues.
8858c44155c9a570f4805a6c4ef53a60ff69bac2

Description From Last Updated

As I read through these, I'm wondering if we really want to be re-exporting everything. We're getting a whole bunch …

daviddavid

This can just return here instead of assigning to a variable.

daviddavid

This blank line is odd.

daviddavid

Can we put fall-through cases adjacent to each other? I think I prefer: /* Move down an item */ case …

daviddavid

If we compress the cases above, we can have only one blank line between sections, which seems more appropriate given …

daviddavid

This API seems error-prone. It seems like it would be easy for a user to do sendKeys(el, 'Esc') and get …

daviddavid
david
  1. 
      
  2. src/ink/js/components/views/index.ts (Diff revision 1)
     
     
    Show all issues

    As I read through these, I'm wondering if we really want to be re-exporting everything. We're getting a whole bunch of interfaces like MenuViewOptions along with the components themselves.

    In other code I've been writing, my approach has been to export anything that could be used elsewhere from the file, but only re-export the top-level classes. Code that actually needs the symbols for things like the options interfaces is comparatively rare, and can import what they need directly.

    1. I'll need to look up best practices here. I don't really want consumers of Ink to have to know a single thing about the file structure (I'd prefer that be an internal detail).

      Ink's also built to be specialized, since we'll need to ultimately do that in Review Board and Djblets, so being able to extend these types is pretty important. I think because of that, it makes sense to make these first-class citizens alongside the classes themselves.

      I'll think about it some more.

    2. For now, I'm going to export everything and keep the consuming application from having to know anything about the internal module structure. Everything is suitably namespaced, and I think this makes the most sense in the case of Ink.

  3. src/ink/js/components/views/menuView.ts (Diff revision 1)
     
     
    Show all issues

    This can just return here instead of assigning to a variable.

  4. src/ink/js/components/views/menuView.ts (Diff revision 1)
     
     
    Show all issues

    This blank line is odd.

    1. Since ESLint is set to check for sorted keys, I'm using the blank line to group the parameter options separately from the callback options.

  5. src/ink/js/components/views/menuView.ts (Diff revision 1)
     
     
     
     
    Show all issues

    Can we put fall-through cases adjacent to each other? I think I prefer:

    /* Move down an item */
    case 'ArrowDown': /* Fall through */
    case 'Down':
        ...
        break
    
    /** Move up an item */
    case 'ArrowUp': /* Fall through */
    case 'Up':
        ...
        break
    
    1. Wouldn't that be nice. Glares at ESLint.

      I've put hours into trying to get that to work.

  6. src/ink/js/components/views/menuView.ts (Diff revision 1)
     
     
     
    Show all issues

    If we compress the cases above, we can have only one blank line between sections, which seems more appropriate given the rest of our coding style.

  7. src/ink/js/testing/events.ts (Diff revision 1)
     
     
     
     
    Show all issues

    This API seems error-prone. It seems like it would be easy for a user to do sendKeys(el, 'Esc') and get confusing behavior.

    I think I'd prefer that we either remove the string mode entirely in favor of always passing lists, or we have a separate function name for sending a sequence of single-character keys.

    1. This is purely for unit testing (and not even shipped with the built ink.js), so I'm not too worried about having to call it the correct way. But I take the point.

      Gotta think about this.

    2. I'm punting on this, given that this isn't even shipped outside of Ink and the side-effects are pretty noticeable currently. If I tackle this, other files will be affected, so I'd rather do it separately. And given the time constraints, it's not something I want to spend extra time on right now.

  8. 
      
chipx86
david
  1. Ship It!
  2. 
      
chipx86
Review request changed
Status:
Completed
Change Summary:
Pushed to master (fda78fd)