fast icon indicating copy to clipboard operation
fast copied to clipboard

rfc: add `MenuButton`

Open KingOfTac opened this issue 3 years ago • 21 comments
trafficstars

MenuButton

Overview

As defined by the W3C:

A menu button is a button that opens a menu. It is often styled as a typical push button with a downward pointing arrow or triangle to hint that activating the button will display a menu.

Background

A common request in the FAST community is how to make dropdowns, either for action menus or navigation. It is also a very common pattern amongst UI libraries, and while it is something that can produced using a combination of components that already exist in FAST it still a difficult pattern to implement correctly when accessibility becomes involved.

Use Cases

  • Jon wants to find a specific service offered by a company, so he clicks on the services menu button in the navbar to see a menu of all the services the company has to offer.
  • Jane wants to learn more about her favorite brand, so she clicks on the company menu button to see a menu of links to the brand's contact, about us, and careers pages.
  • Joe is writing a blog post using a wysiwyg editor and clicks on a menu button in the editor's toolbar to see a list of formatting actions available.

Features

  • Expanded/Collapsed Indicator: An indicator (usually down/up chevrons) that visually indicates the expanded/collapsed state of the menu, as well as indicating that the button opens a menu.

  • MenuPosition: Provides a way to set the position of the menu relative to the reference button.

Risks and Challenges

  • Should Menu be used, or should a custom menu that uses the styling from Menu be used? (similiar to how Select's and Combobox's listboxes are built.)

  • If Menu is used, will it need to be updated to support Anchors with a role of menuitem. Menu already supports slotting elements with a menuitem role, but this poses a styling challenge.

    • Alternatively, could MenuItem be updated to support the capabilities of Anchor or should a purpose built element exist for menu items that are used for navigation?
  • Could MenuButton also be used for a split button?

Prior Art/Examples


Design

API

Component Name:

  • fast-menu-button

Attributes & Properties:

  • menu-position - enum that contains all of the valid positions for the menu.
  • viewport-lock - boolean that enables the menu to stay within the view.
  • menu-width - enum with options for auto, reference width, and content width
  • menu-height - enum with options for auto, reference height, and content height
  • menu-offset - number. This could be an attribute, but leaning more towards a design token since this tends be something shared amongst popups that have a reference element in other design systems, i.e. Fluent UI's Select & Combobox.

Events

  • expand - fires when the menu is expanded
  • collapse - fires when the menu is collapsed

Anatomy

Notes:

  • When the host element gets connected to the DOM, it will need to setup a click handler on the document in order to trigger the light dismiss of the menu.

  • Some design systems add an arrow element to the menu that points at the button element. This can either be handled through component anatomy, or if floating-ui is used for the positioning, its arrow middleware can be used.

<!-- shadow dom -->
<button
  id="{buttonId}"
  class="control"
  part="control"
  aria-haspopup="true"
  aria-controls="{menuId}"
  aria-expanded="{expanded || null}"
>
  <slot name="start"></slot>
  <slot></slot>
  <slot name="end"></slot>
  <span class="indicator" part="indicator">
    <slot name="expanded-icon">
      {MenuButtonOptions.expandedIcon}
    </slot>
    <slot name="collapsed-icon">
      {MenuButtonOptions.collapsedIcon}
    </slot>
  </span>
</button>
<div
  id="{menuId}"
  class="menu"
  part="menu"
  role="menu"
  aria-labelledby="{buttonId}"
>
  <slot name="item">
    Menu items / dividers and other presentation content go here.
    Anything that is not `role=menuitem` is ignored by keyboard navigation
  </slot>
</div>

Slot Names

  • default - use the default slot for the button content
  • start - the start slot before the button's content slot
  • end - the end slot after the button's content and before the indicator slots
  • expanded-icon - the icon to show the expanded state of the menu
  • collapsed-icon - the icon to show the collapsed state of the menu
  • item - the menu items

CSS Parts

  • control - the button element
  • indicator - the wrapper element around the expanded-icon & collapsed-icon slots
  • menu - the menu element

Implementation

States

  • expanded - if the menu is visible

Accessibility

The MenuButton should align to the patterns provided by the W3C: https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/

Globalization

The MenuButton will need to reverse the start and end slots, as well as the indicator for users in an RTL setting. The menu positions for start and end will also need to reverse as well.

Dependencies

  • Potentially floating-ui, if FAST ends up moving in that direction

  • If floating-ui is not used, then AnchoredRegion

  • Could use Button, Menu, and MenuItem if the implementation uses composition in favor of custom anatomy that is styled like these components.

    • There is an accessibility concern with reusing Button in MenuButton. While prototyping with Button NVDA announces that the menu button is clickable twice, while using a native button does not have this behavior.

Appendix

Resources

W3C Spec for Menu, Menubar, and MenuButton W3C Accessible MenuButton Example

KingOfTac avatar Jul 22 '22 01:07 KingOfTac

I don't how I missed #5469 when looking for existing issues tracking this.

KingOfTac avatar Jul 22 '22 02:07 KingOfTac

WIP Examples

KingOfTac avatar Jul 22 '22 09:07 KingOfTac

Thanks for this! One immediate thought that I have here is that I think the current RFC details two implementations. What I mean by that is that I think this illustrates both the foundation implementation, as well as what I would expect from the CLI. Specifically, I wonder about the "universal" applicability of menu-width, menu-height, and menu-offset. I really like these for the "FAST CLI" starter implementation, where those can be driven by tokens, but I don't think there are necessary for every menu button (foundational). Thoughts on that distinction @KingofTac? Again, I really like those, but they aren't necessary for an implementation where I may have a menu button with a menu that is holistically controlled by CSS as one example.

chrisdholt avatar Jul 28 '22 04:07 chrisdholt

From a foundation point of view, I agree they aren't needed.

Getting into the implementation specifics, I would say if floating-ui is used by the foundation class, it would be pretty easy for implementations to provide their own version of computePosition and add the specific middewares and positioning details that they want. So with that in mind the CLI starter could very well add the menu-width and menu-height as tokens and override the foundation MenuButton's computePosition and pass it the size middleware using those tokens.

KingOfTac avatar Jul 28 '22 16:07 KingOfTac

From a foundation point of view, I agree they aren't needed.

Getting into the implementation specifics, I would say if floating-ui is used by the foundation class, it would be pretty easy for implementations to provide their own version of computePosition and add the specific middewares and positioning details that they want. So with that in mind the CLI starter could very well add the menu-width and menu-height as tokens and override the foundation MenuButton's computePosition and pass it the size middleware using those tokens.

This was exactly the consideration I had when working to incorporate Floating-UI into a couple of our components as part of #6186. My thought was that while it might make for a robust API, exposing everything would make for a very "cluttered" API by default. Instead, I figured a better way might be to have a public method that could be overridden to provide a more custom and bespoke implementation of computePosition (something like setMenuPosition()).

A couple of follow-up thoughts/questions here:

  1. Ideally I'd like to be able to use menu, because that's a not-insignificant portion of code that does represent a menu. As noted, it doesn't represent all types of menu, but we can look to pick that work up either as part of menu or by creating a more common navigation "menu" approach (list w/ links) for example...

  2. If we have labelledby in the shadow dom for the menu, but the role of menu is on a slotted element, does that do what we expect accessibility wise? I'm not sure it's going to pick it up as expected.

  3. Do you have an example of the implementation/prototype that was causing issues w/ NVDA (is that in the WIP examples)?

chrisdholt avatar Jul 28 '22 17:07 chrisdholt

  1. I think using Menu is perfectly doable. The challenges with it are, like you said it doesn't represent all menus.

  2. This comment made me realize I missed adding the menu role in the anatomy section, so I went and updated that above. I think you're right that labelledby won't cross the shadow boundary so that's why I intended the menu element to be a part of MenuButton's anatomy. I can see cases where you would want to slot in a custom menu element, so thoughts/ feedback here would be great.

  3. I'll have to find where I have that prototype since that implementation was from several months ago. I'll link here when I find it.

KingOfTac avatar Jul 28 '22 19:07 KingOfTac

@chrisdholt Not a fully functioning menu button, but this shows the Behavior I was seeing with NVDA and using Button https://stackblitz.com/edit/typescript-lh92fh?file=index.ts

When the menu button receives focus, it will announce that it is clickable more than once

KingOfTac avatar Aug 01 '22 16:08 KingOfTac

@chrisdholt I'm just circling back to this. If you want, I can update the spec to be focused on the foundation implementation with notes on extending and implementing custom versions.

KingOfTac avatar Aug 18 '22 18:08 KingOfTac

@chrisdholt I'm just circling back to this. If you want, I can update the spec to be focused on the foundation implementation with notes on extending and implementing custom versions.

I think that would be great. Having the spec focus on Foundation and then illustrate extensibility as examples will transition well for the CLI and documentation IMO. I'd love if our documentation broadened to open up examples of extending classes to address common scenarios

chrisdholt avatar Aug 18 '22 18:08 chrisdholt

Sounds good. I'll get a new version up this weekend.

KingOfTac avatar Aug 18 '22 18:08 KingOfTac

Hi, I'm interested in building a split button and dropdown button components for FAST. What's the status of this project? Obviously you've got the spec here, but has any work been done to implement this?

I see two other issues asking for similar stuff:

Also, there was a (discarded?) PR to add a split button in the past.

So stepping back a moment, two questions:

  1. For @KingOfTac what's the status of this menu button? Has code been written yet?
  2. For @chrisdholt, I'm hesitant to contribute here for fear my work would be discarded. Would FAST welcome a MenuButton, SplitButton, and NavBar? Or is this something you guys don't want, or would rather build yourselves?

JudahGabriel avatar Sep 09 '22 20:09 JudahGabriel

Hi, I'm interested in building a split button and dropdown button components for FAST. What's the status of this project? Obviously you've got the spec here, but has any work been done to implement this?

I see two other issues asking for similar stuff:

Also, there was a (discarded?) PR to add a split button in the past.

So stepping back a moment, two questions:

  1. For @KingOfTac what's the status of this menu button? Has code been written yet?

  2. For @chrisdholt, I'm hesitant to contribute here for fear my work would be discarded. Would FAST welcome a MenuButton, SplitButton, and NavBar? Or is this something you guys don't want, or would rather build yourselves?

This spec is still rather new. It needs more work and there are still a few unknowns around whether or not to compose existing components into this one or not. I have been working on a new version of the spec that I hope we'll be able to start implementing.

Another reason why there hasn't been much new component work is that most of the team has been focused on the next major release of FAST which is turning out to be one of the largest with many new features and breaking changes. For this reason I have been hesitant to start implementation until vNext is closer to release in order to reduce refactoring due to breaking changes in fast-element and fast-foundation.

KingOfTac avatar Sep 09 '22 20:09 KingOfTac

Ah, interesting indeed. Well, I'm beginning to question whether I should wait to contribute then. Thanks for the heads up.

@chrisdholt want to weigh in? Would it be wise to wait to contribute until FAST vNext is released?

JudahGabriel avatar Sep 09 '22 21:09 JudahGabriel

Hi, I'm interested in building a split button and dropdown button components for FAST. What's the status of this project? Obviously you've got the spec here, but has any work been done to implement this?

I see two other issues asking for similar stuff:

It appears there was a (discarded?) PR to add a split button in the past.

This all makes me a bit hesitant to contribute here

Well, first - we'd welcome a contribution here. Specific to the split button work, the hope was to bring that into a more well rounded menu button approach, perhaps via a slot (noted here: https://github.com/microsoft/fast/pull/4083#issuecomment-1030250715) because it lends itself better to our goals. The biggest concern with serving both menu button (as represented in this issue) and split button are that our primary method of approach is composition. A year or two ago there was a good amount of discussion around split buttons and their patterns - one big thing that landed was that they are two tab stops. They are both buttons, one being a normal button and one following the menu button pattern (still a button). So when exploring split button, I tend to ask how those things "compose", considering this is two buttons and we value composition, the most intuitive template for a composable split button is either one or two slots and the class itself likely doesn't need any logic because the element just organizes slotted content. Given that context, it was proposed in the spec review that perhaps split button as a construct is better served by being slotted in with a menu button. This would allow slotting the second via the light DOM for full control, etc. We decided we wanted to explore that route rather than deliver something that was a generic wrapper for buttons - Brook was going to pick that back up, but he ended up moving on to a different project and the need for that component fell off (until now). There is more to that backstory, but hopefully that gives at least some insight into why that was closed and nothing materialized from it.

I think the key here is that we'd love a contribution to FAST for any number of things component-wise and we certainly don't need to be the ones to build it ourselves. With that said though, I think any contribution directly to fast-foundation for instance needs to go through some kind of spec review to ensure that we're aligned in our approach, goals are being met, etc. We'd happily take a navbar, or navigation menu (we don't have that...basically a list w/ anchors), or other things - but I think the expectation for it bubbling into foundation is that it's aligned with our current approach and values - hopefully that makes sense. An example would be - if your concept for a menu is to forgo slots and composition in favor of taking in item data from an array; that's certainly doable but it doesn't align with our current approach of "composition first". We'll gladly take contributions (and we do quite often), but in order for us to provide consistent experience we need to ensure that the approach is consistent with our goals. With that said, if you're up for it, we'd welcome a contribution and we're happy to help work through certain nuances. Additionally, perhaps you have an idea for how we can approach composition while providing a great DX out of the box - we'd love to hear that and see how our approach might evolve.

chrisdholt avatar Sep 09 '22 21:09 chrisdholt

Ah, interesting indeed. Well, I'm beginning to question whether I should wait to contribute then. Thanks for the heads up.

@chrisdholt want to weigh in? Would it be wise to wait to contribute until FAST vNext is released?

Eh....I say no :). It depends though, I know that could be a bit chaotic.

We're exploring a few things that I think are really exciting right now, but due to gaps I haven't put together an RFC on the direction. Even then, I don't think it would impact the work you'd be contributing in super significant ways...perhaps some light refactoring but I don't see it significantly changing approaches for instance.

@EisenbergEffect may have thoughts here too but in terms of creating new components, I think most of the breaks that would be most painful have already been made on the FAST Element side of things.

chrisdholt avatar Sep 09 '22 21:09 chrisdholt

In terms of component building, I think you can use the v2 stuff in master with the v1 documentation. There aren't really breaking changes in how components are built and we've made the primary organizational changes in foundation. So, I think you're good to go.

EisenbergEffect avatar Sep 09 '22 21:09 EisenbergEffect

We'd happily take a navbar, or navigation menu (we don't have that...basically a list w/ anchors), or other things - but I think the expectation for it bubbling into foundation is that it's aligned with our current approach and values - hopefully that makes sense. An example would be - if your concept for a menu is to forgo slots and composition in favor of taking in item data from an array; that's certainly doable but it doesn't align with our current approach of "composition first".

Alright, that does make sense.

I'd be doing this contribution as part of Microsoft's Fix / Hack / Learn (FHL) week, meaning I have limited time to do this. Probably not enough time to build a well-rounded spec and component that conforms to W3C specs, but maybe enough to get started.

If anything, I could start a spec and create a proof-of-concept component(s) during FHL week, then get feedback from you all and iterate on that in my spare time.

JudahGabriel avatar Sep 09 '22 21:09 JudahGabriel

We'd happily take a navbar, or navigation menu (we don't have that...basically a list w/ anchors), or other things - but I think the expectation for it bubbling into foundation is that it's aligned with our current approach and values - hopefully that makes sense. An example would be - if your concept for a menu is to forgo slots and composition in favor of taking in item data from an array; that's certainly doable but it doesn't align with our current approach of "composition first".

Alright, that does make sense.

I'd be doing this contribution as part of Microsoft's Fix / Hack / Learn (FHL) week, meaning I have limited time to do this. Probably not enough time to build a well-rounded spec and component that conforms to W3C specs, but maybe enough to get started.

If anything, I could start a spec and create a proof-of-concept component(s) during FHL week, then get feedback from you all and iterate on that in my spare time.

Sounds good - and it doesn't have to be exhaustive. I'd provide as much content as needed and no more - we are pretty open to be iterative, etc. The big thing we're looking for is to identify any gaps in assumption or understanding and provide feedback up front to avoid someone going in an opposite direction or one that misses a few gotchas, requirements, etc.

chrisdholt avatar Sep 09 '22 21:09 chrisdholt

Also, for some context here, the reason I'm interested in contributing these components is because I work on apps.microsoft.com, and we're looking to rewrite our front-end using web components. It's currently in React + old FAST (before Web Components!), but we're considering migrating to newer tech, and one option is modern FAST.

Looking at modern FAST and doing some quick tech spikes, I noticed it was missing some components we need, such as nav bar with collapsible menus on mobile, split buttons, and menu buttons. Hence why I'm interested in contributing. 🙂

JudahGabriel avatar Sep 09 '22 21:09 JudahGabriel

Contributions welcome - rising tide raises all ships and whatnot :)

chrisdholt avatar Sep 09 '22 21:09 chrisdholt

@chrisdholt @EisenbergEffect @JudahGabriel

New MenuButton spec that explores supporting split buttons. Per comments above, this merely positions the buttons and does not incorporate logic specific to split buttons.

Some basic exploratory examples here. https://stackblitz.com/edit/typescript-zg9qwx?file=index.html

MenuButton V2

Overview

As defined by the W3C:

A menu button is a button that opens a menu. It is often styled as a typical push button with a downward pointing arrow or triangle to hint that activating the button will display a menu.

Background

A common request in the FAST community is how to make dropdowns, either for action menus or navigation. It is also a very common pattern amongst UI libraries, and while it is something that can produced using a combination of components that already exist in FAST it still a difficult pattern to implement correctly when accessibility becomes involved.

Use Cases

  • Jon wants to find a specific service offered by a company, so he clicks on the services menu button in the navbar to see a menu of all the services the company has to offer.
  • Jane wants to learn more about her favorite brand, so she clicks on the company menu button to see a menu of links to the brand's contact, about us, and careers pages.
  • Joe is writing a blog post using a wysiwyg editor and clicks on a menu button in the editor's toolbar to see a list of formatting actions available.

Features

  • Expanded/Collapsed Indicator: An indicator (usually down/up chevrons) that visually indicates the expanded/collapsed state of the menu, as well as indicating that the button opens a menu.

  • MenuPosition: Provides a way to set the position of the menu relative to the reference button.

  • SplitButton compisition via start/end slots When a button is slotted into either the start or end slot or both, the menu button becomes a split button.

Risks and Challenges

  • This version composes Menu which reduces the amount of logic for focus and keyboard navigation, however in order to fully align with the ARIA patterns for MenuButtons Menu will need to be updated to support a roving tabindex.
  • Typically, in design systems that use rounded corners for buttons, only the outer corners of a split button are rounded. Styling for these scenarios can be challenging when the buttons each live in different slots and/or some in light-dom and others in shadow-dom. One solution to this challenge is to watch for changes on the start/end slots for slotted button elements and add a class and/or part attribute to the internal container around the slots and internal button so that the buttons can be styled properly.

Prior Art/Examples


design

API

Component Name:

  • fast-menu-button

Attributes & Properties:

  • menu-position - enum that contains all of the valid positions for the menu. Defaults to bottom-start;
  • expanded - boolean that indicates whether or not the menu button is in its expanded state. This attribute is not used for AT as the internal Button element has the aria-expanded attribute. This attribute is meant to enable easier ergonomics for styling the MenuButton via light-dom based on its current state.
  • auto-update - boolean to enable auto updating the menu's position as the MenuButton moves around on the page.

Events

  • expand - fires when the menu is expanded.
  • collapse - fires when the menu is collapsed.

    an alternative worth exploring is a single expanded-change event that fires in both scenarios. This aligns better with the custom events spec where custom event names need a hyphen in them, as well as aligning with other Foundation component's events.

Anatomy

Notes: When the host element gets connected to the DOM, it will need to setup a click handler on the document in order to trigger the light dismiss of the menu.

  • Some design systems add an arrow element to the menu that points at the button element. This can either be handled through component anatomy, or if a library like floating-ui is used for the positioning, its arrow middleware can be used.

MenuButtonOptions

  • collapsedIcon - Auther definable icon for the collapsed state.
  • expandedIcon - Auther definable icon for the expanded state.
<div part="control">
  <slot name="start"></slot>
  <fast-button
    aria-haspopup="true"
    aria-expanded="{expanded || null}"
  >
    <slot></slot>
    {
      options
        ? expanded
          ? options.expandedIcon
          : options.collapsedIcon
        : ''
    }
  </fast-button>
  <slot name="end"></slot>
</div>
<fast-menu
  role="menu"
  part="menu"
>
  <slot name="item">
    Menu items / dividers and other presentation content go here.
    Anything that is not `role=menuitem` is ignored by keyboard navigation
  </slot>
</fast-menu>

Slot Names

Notes: I'm on the fence on whether the default slot should be the internal button's content or the menu items. On the one hand you don't need to wrap the button's text with an element to slot it, but then you need to specify slot="item" on every item which becomes tedious and adds clutter to the markup.

  • default - use the default slot for the button content.
  • start - can be used for icons and text. If another button is slotted here then the MenuButton turns into a SplitButton.
  • end - can be used for icons and text. If another button is slotted here then the MenuButton turns into a SplitButton.
  • item - The menu items.

CSS Parts

  • control - The container around the button and start/end slots.
  • menu - The menu element.

Implementation

States

  • expanded - If the menu is visible

Accessibility

  • The MenuButton should align to the patterns provided by the W3C: https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/
  • When additional buttons are slotted, the MenuButton transitions into a split button. In this scenario additional tab stops need to be handled for the extra button(s).

Globalization

The MenuButton will need to reverse the start and end slots, as well as the indicator for users in an RTL setting. The menu positions for start and end will also need to reverse.

Dependencies

  • Potentially floating-ui, if FAST ends up moving in that direction

  • If floating-ui is not used, then AnchoredRegion or another utility if AnchoredRegion is deprecated.

  • Menu

  • Button

    • There is an accessibility concern with reusing Button in MenuButton. While prototyping with Button NVDA announces that the menu button is clickable twice, while using a native button does not have this behavior. I suspect this has something to do with Button delegating focus, but haven't thoroughly tested this yet. Setting role="none" on the Button could be a temporary workaround until a better solution is found.

Appendix

Resources

W3C Spec for Menu, Menubar, and MenuButton W3C Accessible MenuButton Example

KingOfTac avatar Sep 10 '22 05:09 KingOfTac