Tabs Component with RiotJS (Material Design)
Tags: riotjs, javascript, beginners, frontend
Published: 2024-04-01 17:45:07

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](https://dev.to/steeve/riotjs-material-design-how-to-setup-beercss-269c).

> These articles form a series focusing on [RiotJS](https://riot.js.org/) paired with [BeerCSS](https://www.beercss.com/), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jpikn4tcu8e4230mfwf6.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nsb19rf1mra9d85wh0sr.gif)

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](https://www.beercss.com/#tabs)) in `./components/c-tabs.riot`:
```html
<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>
```
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.
3. 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](https://fonts.google.com/icons).
4. The list of tabs is looped through thanks to the [Each](https://riot.js.org/documentation/#loops) Riot expression applied on the `<a></a>` HTML tags: `<a each={ (tab, index) in props.tabs }> { tab.label } </a>`
5. 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.
6. 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 }>`.
7. 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**:
```html
<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>
```
Code details:
1. Components are imported with `import cTabs from "./components/c-tabs.riot";` then loaded in the **components:{}** Riot object.
2. The `tabs`component 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:
* [Test with Vitest and Riot-SSR in a Node Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-node-env-hk0)
* [Test with Vitest in a JsDom Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-jsdom-env-182l)

## 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 🍻