Menu Component with RiotJS (Material Design)

steeve

Steeve

Posted on April 7, 2024

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 🍻

💖 💪 🙅 🚩
steeve
Steeve

Posted on April 7, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related