DEV Community

Cover image for Menu Component with RiotJS (Material Design)
Steeve
Steeve

Posted on

2 1 1 1 1

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.

Example of a Menu opening when a button is clicked

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.

GIF of a Menu Component made with RiotJS and BeerCSS

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>
Enter fullscreen mode Exit fullscreen mode

Click to learn more about creating Button Component with RiotJS

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

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>
Enter fullscreen mode Exit fullscreen mode

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" 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.

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 🍻

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (2)

Collapse
 
steeve profile image
Steeve

Hey folks! Please share your questions in the comments section, I would be glad to help you with RiotJS! Have a great day! Cheers 🍻

Some comments may only be visible to logged-in visitors. Sign in to view all comments.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more