Tabs Component with RiotJS (Material Design)

Steeve - Apr 1 - - Dev Community

This article covers creating a Riot Tabs component, using the Material Design CSS BeerCSS, and reacting to 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/

Tabs are used to organize content across different screens and views, for instance, a media application with 3 tabs: Photos, Videos and Audios.

Application of a media center on mobile with three tabs

Tabs Component Base

The goal is to create a Riot app with tabs, change the active one on click, and change the page content:

Active Tabs changes on click event

First, create a new file named c-tabs.riot under the components folder. The c- stands for "component", a useful naming convention and a good practice.

Write the following HTML code (found on the BeerCSS documentation) in ./components/c-tabs.riot:

<c-tabs>
     <div class="tabs
        { props?.min ? ' min' : null }
        { props?.rightAlign ? ' right-align' : null }
        { props?.centerAlign ? ' center-align' : null }
        { props?.leftAlign ? ' left-align' : null }
    ">
        <a each={ (tab, index) in props.tabs } class="{ props.active === index ? 'active ' : null }{ props?.vertical ? 'vertical' : null }" onclick={ (ev) => clicked(ev, index) }>
            <i if={ tab?.icon }>{ tab.icon }</i>
            <img if={ tab?.img } class="circle" src={ tab.img }>
            <span>{ tab.label }</span>
        </a>
    </div>
    <script>
        export default {
            clicked (e, index) {
                e.preventDefault();
                e.stopPropagation();
                this.root.value = index;
                this.root.dispatchEvent(new Event('click'));
                this.root.dispatchEvent(new Event('change'));
            }
        }
    </script>
</c-tabs>
Enter fullscreen mode Exit fullscreen mode

Let's break down the code:

  1. The <c-tabs> and </c-tabs> define a custom root tag, with the same name as the file. You must write it; otherwise, it may create unexpected results. Using the <div> as a root tag or redefining native HTML tags is a bad practice, so starting c- is a good naming.
  2. A list of tabs must be passed as attribute, and it is accessible with props.tabs. Each object of the list will define properties of the tab: The label, an image, or an icon from Google Font Icons.
  3. The list of tabs is looped through thanks to the Each Riot expression applied on the <a></a> HTML tags: <a each={ (tab, index) in props.tabs }> { tab.label } </a>
  4. The active tab is defined with the property active, the index of the selected tab. If the index of the current displayed tab is equal to the active, the active class is applied to the tab.
  5. If the tab has an icon or image, it prints the icon expression <i if={ tab?.icon }>{ tab.icon }</i>, or an image <img if={ tab?.img } class="circle" src={ tab.img }>.
  6. When a click occurs on one of the tabs, the index of the selected tab is passed to the function clicked: the function will Emit two events (change, and click) and provide the selected index.

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

<index-riot>
    <div style="width:800px;padding:20px;">
        <c-tabs active={ state.active } tabs={ state.tabs } onchange={ changed } />
        <div class="page padding { state.active === 0 ? 'active' : null}" if={ state.active === 0 }>
            <h5>Tab 1</h5>
        </div>
        <div class="page padding { state.active === 1 ? 'active' : null}" if={ state.active === 1 }>
            <h5>Tab 2</h5>
        </div>
        <div class="page padding { state.active === 2 ? 'active' : null}" if={ state.active === 2 }>
            <h5>Tab 3</h5>
        </div>
        <div class="page padding { state.active === 3 ? 'active' : null}" if={ state.active === 3 }>
            <h5>Tab 4</h5>
        </div>
    </div>
    <script>
        import cTabs from "./components/c-tabs.riot"

        export default {
            components: {
                cTabs
            },
            state: {
                active: 2,
                tabs: [
                    { label: "Flights", icon: "flight" },
                    { label: "Travel", icon: "card_travel" },
                    { label: "Explore", icon: "explore" },
                    { label: "BeerCSS", img: "./favicon.png" }
                ]
            },
            changed(ev) {
                this.update({
                    active: ev.target.value
                })
            }
        }
    </script>
</index-riot>
Enter fullscreen mode Exit fullscreen mode

Code details:

  1. Components are imported with import cTabs from "./components/c-tabs.riot"; then loaded in the components:{} Riot object.
  2. The tabscomponent is instantiated with <c-tabs/> on the HTML.
  3. To store data into a component, it must be defined into the state:{} Riot object, in our case two data is required:
    • state.tabs: The list of tabs with labels, icons or images
    • state.active: The index of the selected tab.
  4. Pass to the Tabs components states as Props: <c-tabs active={ state.active } tabs={ state.tabs } onchange={ changed } />
  5. When someone clicks on a tab, the change event is fired, and the changed function is executed: The state.active gets the new selected index thanks to the this.update({active: ev.target.value}) Riot function.
  6. When the state.active index changes, the displayed page will also change with an IF condition, such as: <div if={ state.active === 0 }><h5>Tab 1</h5></div>

Tabs Component Testing

It exists two methods for testing the Tabs component, and it is covered in two different articles:

Conclusion

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

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

Feel free to comment if you have questions or need help about RiotJS.

Have a great day! Cheers 🍻

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .