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 withc-
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 thetoggle
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:
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 🍻