Icon Component with RiotJS (Material Design)
Tags: riotjs, frontend, beginners, javascript
Published: 2024-03-25 08:44:34

This article covers creating a Riot Icon component, using the Material Design CSS BeerCSS. 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/

There are three types of icons on BeerCSS: default using Google Material Glyphs, SVG, or as an image (see following screenshot). The goal is to create an icon Riot component that supports all three formats and listens to click events.

![Icons from the BeerCSS documentation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vbhwsv2exangsx2sepks.png)

## Icon Component Base

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

Into **./components/c-icon.riot**, write the following HTML (found on the BeerCSS documentation):
```html
<c-icon>
    <i class="
        { props?.tiny ? ' tiny' : null}
        { props?.small ? ' small' : null}
        { props?.large ? ' large' : null}
        { props?.extra ? ' extra' : null}
        
        { props?.fill ? ' fill' : null}

        { props?.primary ? ' primary-text' : null}
        { props?.secondary ? ' secondary-text' : null}
        { props?.tertiary ? ' tertiary-text' : null}
    ">
        <slot></slot>
    </i>
</c-icon>
```
Let's break down the code:

1. The `<c-icon>` and `</c-icon>` defined a custom root tag, with the same name as the file. You must write it; otherwise, it may create unexpected results. Using the <label> as a root tag or redefining native HTML tags is a bad practice, so starting `c-` is a good naming.
2. Using the `<slot>` tag you can inject custom HTML templates in a child component from its parent: Now we can inject either a string (for Google Material Icon), an SVG tag, or an image tag. 
3. A BeerCSS icon supports different sizes and theme classes: Each class is applied conditionally if the corresponding **props** exists. Properties are defined on the HTML attribute name.

Finally, the **c-icon.riot** can be instantiated into a front page **index.riot**:
```html
<index-riot>
    <div style="width:600px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>
        
        <c-icon>check_circle</c-icon>
        <c-icon large="true">
            <svg viewBox="0 0 24 24">
                <path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7.07,18.28C7.5,17.38 10.12,16.5 12,16.5C13.88,16.5 16.5,17.38 16.93,18.28C15.57,19.36 13.86,20 12,20C10.14,20 8.43,19.36 7.07,18.28M18.36,16.83C16.93,15.09 13.46,14.5 12,14.5C10.54,14.5 7.07,15.09 5.64,16.83C4.62,15.5 4,13.82 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,13.82 19.38,15.5 18.36,16.83M12,6C10.06,6 8.5,7.56 8.5,9.5C8.5,11.44 10.06,13 12,13C13.94,13 15.5,11.44 15.5,9.5C15.5,7.56 13.94,6 12,6M12,11A1.5,1.5 0 0,1 10.5,9.5A1.5,1.5 0 0,1 12,8A1.5,1.5 0 0,1 13.5,9.5A1.5,1.5 0 0,1 12,11Z"></path>
            </svg>
        </c-icon>
        <c-icon extra="true">
              <img src="/favicon.png">
        </c-icon>
    </div>
    <script>
        import cIcon from "./components/c-icon.riot";

        export default {
            components: {
                cIcon
            }
        }
    </script>
</index-riot>
```
Code break-down:

1. The component is imported with `import cIcon from "./components/c-icon.riot";` then loaded into the **components:{}** Riot object.
2. On the HTML, the Icon component is instantiated with `<c-icon>icon_name<c-icon>`
3. Since the component uses a slot, different types of icons can be passed: as an SVG or as an image.
4. If the icon is coming from Google Material Fonts, and if the icon name has a space character in it, replace each space with an underscore. For instance, if the name on Google Font is "Chevron Right", you must write on the slot: `chevron_right`.

Screenshot of the generated HTML:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9rpf1vxlbz4em86ueu9q.png)

To apply different icon sizes and themes, pass the following attributes to the component tag definition: `tiny`, `small`, `large`, `extra`, `primary`, `secondary`, `tertiary`, or `fill`, such as:
```html
<c-icon tiny="true">check_circle</c-icon>
<c-icon small="true">check_circle</c-icon>
<c-icon>check_circle</c-icon>
<c-icon large="true">check_circle</c-icon>
<c-icon extra="true">check_circle</c-icon>
<c-icon extra="true" fill="true">check_circle</c-icon>
<c-icon extra="true" fill="true" primary="true">check_circle</c-icon>
<c-icon extra="true" fill="true" secondary="true">check_circle</c-icon>
<c-icon extra="true" fill="true" tertiary="true">check_circle</c-icon>
```
The generated HTML of icon variations:

![Icons variation of the Icon Component made with Riot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q30upqi59rrrv5o6xnbk.png)

## Icon Component Testing

It exists two methods for testing the Icon 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 created an Icon Riot Component using Material Design elements with BeerCSS. 

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

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

Have a great day! Cheers 🍻