Tabs Component with RiotJS (Material Design)
Steeve
Posted on April 1, 2024
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.
Tabs Component Base
The goal is to create a Riot app with tabs, change the active one on click, and change the page content:
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>
Let's break down the code:
- 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 startingc-
is a good naming. - 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. - 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>
- 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. - 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 }>
. - 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>
Code details:
- Components are imported with
import cTabs from "./components/c-tabs.riot";
then loaded in the components:{} Riot object. - The
tabs
component is instantiated with<c-tabs/>
on the HTML. - 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.
- Pass to the Tabs components states as Props:
<c-tabs active={ state.active } tabs={ state.tabs } onchange={ changed } />
- 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. - 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 🍻
Posted on April 1, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.