Menu Component with RiotJS (Material Design)
Tags: riotjs, javascript, beginners, frontend
Published: 2024-04-07 19:17:37

This article covers creating a Riot Menu component, using the Material Design CSS BeerCSS, and executing an action on click events.

Before starting, make sure you have a base application running, or read my previous article Setup [Riot + BeerCSS + Vite](https://dev.to/steeve/riotjs-material-design-how-to-setup-beercss-269c).

> These articles form a series focusing on [RiotJS](https://riot.js.org/) paired with [BeerCSS](https://www.beercss.com/), designed to guide you through creating components and mastering best practices for building production-ready applications. I assume you have a foundational understanding of Riot; however, feel free to refer to the documentation if needed: https://riot.js.org/documentation/

A menu opens upon interaction with an element (such as an icon, button, or input field) or when users perform a specific action. The menu displays a list of choices on a temporary surface, it allows users to make a selection to execute actions. 

![Example of a Menu opening when a button is clicked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uq5ec1bsubfr51lsjcd8.png)

## Menu Component Base

The goal is to create a Riot app with a Menu appearing when a button is clicked, hide it when an item is clicked, and execute an action. <u>Bonus:</u> Hide the menu when a click happens outside the component.

![GIF of a Menu Component made with RiotJS and BeerCSS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f7ly06oolsymdnscosir.gif)

**To show a Menu when a button is clicked:** The menu is part of the Button Component, as a [Slot](https://riot.js.org/documentation/#slots). The `<slot>` Riot tag injects custom HTML templates in a child component from its parent.

The following Button Component code is used to make the Menu visible, located in the  **./components/c-button.riot**. The HTML comes from the [BeerCSS documentation](https://www.beercss.com/#buttons) and I added [RiotJS syntax](https://riot.js.org/documentation/#syntax) for the logic:
```html
<c-button>
  <button>
    <span><slot></slot></span>
    <i icon={ props?.icon }>props?.icon</i>
    <slot name="menu"></slot>
  </button>
</c-button>
```
> Click to learn more about creating [Button Component with RiotJS](https://dev.to/steeve/button-component-with-riotjs-material-design-174p)

Let's break down the code:
1. The `<c-button>` and `</c-button>` define a custom root tag, with the same name as the file. You must write it; otherwise, it may create unexpected results. Using the `<button></button>` as a root tag or redefining native HTML tags is a bad practice, starting with `c-` is a good convention.
2. The button label is passed as [Slot](https://riot.js.org/documentation/#slots).
3. The Menu is passed as a Named Slot "Menu": `<slot name="menu"></slot>`. 
4. An optional icon can be passed as an attribute if **props.icon** exists, it will show a [Google Font Icon](https://fonts.google.com/icons)

Finally, load and instantiate the **c-button.riot** in a front page named **index.riot**:
```html
<index-riot>
    <div style="width:600px;padding:20px;">
        <c-button icon="arrow_drop_down" onclick={ (ev) => toggle(ev, 'button') } onfocusout={ () => update({ active: false })}>
            Menu
            <template slot="menu">
                <menu  class="no-wrap{ state.active === true ? ' active' : ''}">
                    <a class="row" onclick={ (ev) => toggle(ev, 'item1') }>
                        <i>visibility</i>
                        <span class="max">Item 1</span>
                    </a>
                    <a class="row" onclick={ (ev) => toggle(ev, 'item2') }>
                        <i>content_copy</i>
                        <span class="max">Item 2</span>
                        <span>⌘C</span>
                    </a>
                    <a class="row" onclick={ (ev) => toggle(ev, 'item3') }>
                        <i>edit</i>
                        <span class="max">Item 3</span>
                    </a>
                    <div class="small-divider"></div>
                    <a class="row" onclick={ (ev) => toggle(ev, 'item4') }>
                        <img class="circle tiny" src="../favicon.png">
                        <div class="max">
                            <div>Item 4</div>
                            <label>Some text here</label>
                        </div>
                    </a>
                </menu>
            </template>
        </c-button>
    </div>
    <script>
        import cButton from "../components/c-button.riot"

        export default {
            components: {
                cButton
            },
            state: {
                active: false
            },
            toggle (ev, origin) {
                ev.stopPropagation();
                ev.preventDefault();
                // Hide the menu
                this.update({ active: this.state.active === true ? false : true })
                if (origin === 'item1') {
                    // do something
                } else if (origin === 'item2') {
                    // do something else
                }
            }
        }
    </script>
</index-riot>
```
> Source Code: https://github.com/steevepay/riot-beercss/blob/main/examples/index.menu.riot

Code details:
1. The component is imported with `import cButton from "./components/c-button.riot";` then loaded in the **components:{}** Riot object.
2. The `button` component is instantiated with `<c-button>` on the HTML.
3. The menu is passed as a slot into a `<menu>` HTML tag
4. Each item of the list has the following architecture, wrapped in a `<a>` tag with an icon and label: `<a class="row"><i>icon</i><span class="max">Label</span></a>`.
5. The state of the menu is stored into a **state:{}** Riot object, through the Boolean variable **state.active**.
6. **To make the menu visible, the menu must contain the class "active":** When the **state.active** is true, it applies the class "active"; otherwise, it applies nothing.
7. When a click occurs on the button, the function `toggle` is executed to assign the opposite Boolean to **state.active**. At the same time, a String is passed to the `toggle` function to define the **origin** of the click, either: `button`, or an item of the menu. Thanks to the origin, a specific function can be executed: API calls, open a page, and any action!
8. When a click occurs outside the Menu, the event "[focusout](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event)" is caught to hide the Menu with the expression: `onfocusout={ () => update({ active: false })}`.
9. An item on the Menu can have a different style, for example, the last item on the list prints an image instead of an icon, followed by a title and a subtitle. Find all Menu examples on the [BeerCSS Menu documentation](https://www.beercss.com/#menus).

## Menu Component Testing

It exists two methods for testing the Menu component, and it is covered in two different articles:
* [Test with Vitest and Riot-SSR in a Node Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-node-env-hk0)
* [Test with Vitest in a JsDom Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-jsdom-env-182l)

## Conclusion

Voilà 🎉 We made a Menu Riot Component using Material Design elements with BeerCSS.

The source code of the Menu bar is available on Github:
https://github.com/steevepay/riot-beercss/blob/main/components/c-menu.riot

Have a great day! Cheers 🍻