Menu Component with RiotJS (Material Design)

Menu Component with RiotJS (Material Design)

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.

These articles form a series focusing on RiotJS paired with BeerCSS, 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.

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. Bonus: Hide the menu when a click happens outside the component.

To show a Menu when a button is clicked: The menu is part of the Button Component, as a Slot. 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 and I added RiotJS syntax for the logic:

<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

Let’s break down the code:

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.
The button label is passed as Slot.
The Menu is passed as a Named Slot “Menu”: <slot name=”menu”></slot>.
An optional icon can be passed as an attribute if props.icon exists, it will show a Google Font Icon

Finally, load and instantiate the c-button.riot in a front page named index.riot:

<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:

The component is imported with import cButton from “./components/c-button.riot”; then loaded in the components:{} Riot object.
The button component is instantiated with <c-button> on the HTML.
The menu is passed as a slot into a <menu> HTML tag
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>.
The state of the menu is stored into a state:{} Riot object, through the Boolean variable state.active.

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.
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!
When a click occurs outside the Menu, the event “focusout” is caught to hide the Menu with the expression: onfocusout={ () => update({ active: false })}.
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.

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
Test with Vitest in a JsDom Environment

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 🍻

Leave a Reply

Your email address will not be published. Required fields are marked *