Fanciest Picklist ever

Creating a LWC to graphically display a picklist to amp up your user experience. Not because you need to – but because you can:

Lightning Design System: https://summer-20.lightningdesignsystem.com/icons/

Clicking on a star will lock in the value and you will be able to access it inside a screen flow:

Here is the LWC (for filling out stars):

HTML
<template>
    <div class="rating-container">
        <h2>{headerText}</h2>
    <div class="star-rating">
     
        <template for:each={stars} for:item="star">
            <lightning-icon
                key={star.value}
                icon-name={star.icon}
                alternative-text={star.value}
                size="medium"
                onmouseover={handleStarHover}
                onmouseout={handleStarMouseOut}
                data-value={star.value}
                class="my-icon"
                onclick={handleStarClick}>
                           
            </lightning-icon>
        </template>
    </div>
</div>
</template>
Code language: HTML, XML (xml)
java script

import { LightningElement, api, track } from 'lwc';

export default class FeedbackStarRating extends LightningElement {
    @api headerText;

    _ratingValue = 0; // Private variable for ratingValue
    selectedStarValue = 0; // Tracks currently selected stars
    mouseOutTimeoutId;

    // Setter ensures the value updates when assigned by the Flow
    @api 
    get ratingValue() {
        return this._ratingValue;
    }
    set ratingValue(value) {
        this._ratingValue = value;
        this.selectedStarValue = value; // Ensure stars update immediately
    }

    // Getter to dynamically update star icons
    get stars() {
        return [1, 2, 3, 4, 5].map(value => ({
            value,
            icon: value <= this.selectedStarValue ? 'utility:favorite' : 'utility:favorite_alt'
        }));
    }

    handleStarHover(event) {
        if (this.selectedStarValue === 0) {
            clearTimeout(this.mouseOutTimeoutId);
            this.selectedStarValue = parseInt(event.target.dataset.value, 10);
        }
    }

    handleStarMouseOut() {
        this.mouseOutTimeoutId = setTimeout(() => {
            if (this.selectedStarValue === 0) {
                this.selectedStarValue = 0;
            }
        }, 250);
    }

    handleStarClick(event) {
        this.selectedStarValue = parseInt(event.target.dataset.value, 10);
        this._ratingValue = this.selectedStarValue;

        // Dispatch event for Flow or parent component
        this.dispatchEvent(new CustomEvent('ratingchange', {
            detail: { ratingValue: this.selectedStarValue }
        }));
    }
}

Code language: JavaScript (javascript)
CSS

.star-rating {
    display: flex;
    justify-content: flex-start; /* or center or flex-end */
}
.my-icon {
    --lwc-iconSize: 2rem; /* Adjust the size as needed */
    --slds-c-icon-color-foreground-default: yellow; /* Makes the stars yellow */
    cursor: pointer;
}


:host {
    background-color: #cee8fe; /* Sets the background color to light blue */
    padding: 10px; /* Optional: Adds some padding around the component */
    border-radius: 5px; /* Optional: Adds rounded corners for a softer look */
    display: block;
}
Code language: CSS (css)
meta xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>51.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__FlowScreen</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__FlowScreen">
            <property name="headerText" type="String" label="Header Text" description="Header text for the star rating component"/>
            <property name="ratingValue" type="Integer" label="Rating Value" />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Code language: HTML, XML (xml)

For hearts

HTML

<template>
    <div class="rating-container">
        <h2>{headerText}</h2>
        <div class="icon-rating">
            <template for:each={icons} for:item="icon">
                <lightning-icon
                    key={icon.value}
                    icon-name={icon.icon}
                    alternative-text={icon.value}
                    size="medium"
                    onmouseover={handleIconHover}
                    onmouseout={handleIconMouseOut}
                    data-value={icon.value}
                    data-selected={icon.filled}
                    class="rating-icon"
                    onclick={handleIconClick}>
                </lightning-icon>
            </template>
        </div>
    </div>
</template>
Code language: HTML, XML (xml)
java script

import { LightningElement, api } from 'lwc';

export default class FeedbackRating extends LightningElement {
    @api ratingValue;
    @api headerText;
    selectedIconValue = 0; 

    icons = [
        { value: 1, icon: 'utility:heart' },
        { value: 2, icon: 'utility:heart' },
        { value: 3, icon: 'utility:heart' },
        { value: 4, icon: 'utility:heart' },
        { value: 5, icon: 'utility:heart' },
    ];

    mouseOutTimeoutId;

    handleIconHover(event) {
        if (this.selectedIconValue === 0) {
            clearTimeout(this.mouseOutTimeoutId);
            const hoverValue = parseInt(event.target.dataset.value, 10);
            this.updateIcons(hoverValue, true);
        }
    }

    handleIconMouseOut() {
        this.mouseOutTimeoutId = setTimeout(() => {
            if (this.selectedIconValue === 0) {
                this.updateIcons(0, false);
            }
        }, 250);
    }

    handleIconClick(event) {
        clearTimeout(this.mouseOutTimeoutId);
        const clickedValue = parseInt(event.target.dataset.value, 10);
        this.selectedIconValue = clickedValue;
        this.ratingValue = clickedValue;
        this.updateIcons(clickedValue, true);

        this.dispatchEvent(new CustomEvent('ratingchange', { 
            detail: { ratingValue: clickedValue } 
        }));
    }

    updateIcons(value, isFilled) {
        this.icons = this.icons.map(icon => ({
            ...icon,
            filled: icon.value <= value
        }));
    }
}
Code language: JavaScript (javascript)
CSS

.icon-rating {
    display: flex;
    justify-content: flex-start; /* Aligns the icons to the left */
    gap: 8px; /* Space between icons */
    flex-wrap: wrap; /* Allows wrapping if there's not enough space */
}

.rating-icon {
    --slds-c-icon-color-foreground-default: gray; /* Default color */
    cursor: pointer;
    transition: transform 0.2s ease-in-out;
}

.rating-icon:hover,
.rating-icon[data-selected="true"] {
    --slds-c-icon-color-foreground-default: red; /* Fill color */
    transform: scale(1.2);
}

:host {
    background-color: #ffe5e5;
    padding: 10px;
    border-radius: 5px;
    display: block;
}
Code language: CSS (css)

For Thumbs up

Update java script to

icons = [
        { value: 1, icon: 'utility:like' },
        { value: 2, icon: 'utility:like' },
        { value: 3, icon: 'utility:like' },
        { value: 4, icon: 'utility:like' },
        { value: 5, icon: 'utility:like' },
    ];
Code language: JavaScript (javascript)

Update CSS to

.rating-icon:hover,
.rating-icon[data-selected="true"] {
    --slds-c-icon-color-foreground-default: rgb(255, 217, 0); /* Fill color */
    transform: scale(1.2);
}

:host {
    background-color: #8fe7e9;
    padding: 10px;
    border-radius: 5px;
    display: block;
}

Code language: CSS (css)

If you want to add more values than 1 to 5 simply change the amount of icons:

// Update the icons array for 1 to 10 scale
    icons = [
        { value: 1, icon: 'utility:heart' },
        { value: 2, icon: 'utility:heart' },
        { value: 3, icon: 'utility:heart' },
        { value: 4, icon: 'utility:heart' },
        { value: 5, icon: 'utility:heart' },
        { value: 6, icon: 'utility:heart' },
        { value: 7, icon: 'utility:heart' },
        { value: 8, icon: 'utility:heart' },
        { value: 9, icon: 'utility:heart' },
        { value: 10, icon: 'utility:heart' }
    ];
Code language: JavaScript (javascript)

In order to static ressources as an icon you have to modify your javascript like this:

import { LightningElement, api } from 'lwc';
import Icon from '@salesforce/resourceUrl/ncage'; // Static resource importCode language: JavaScript (javascript)
// Update the icons array to use the static resource
    icons = [
        { value: 1, icon: Icon },  // Using the static resource
        { value: 2, icon: Icon },
        { value: 3, icon: Icon },
        { value: 4, icon: Icon },
        { value: 5, icon: Icon }
    ];Code language: JavaScript (javascript)

and your HTML like this

<template>
    <div class="rating-container">
        <h2>{headerText}</h2>
        <div class="icon-rating">
            <template for:each={icons} for:item="icon">
                <img 
                    key={icon.value}
                    src={icon.icon} 
                    alt="Rating Icon" 
                    data-value={icon.value}
                    class="rating-icon" 
                    onclick={handleIconClick}
                    onmouseover={handleIconHover}
                    onmouseout={handleIconMouseOut} />
            </template>
        </div>
    </div>
</template>

Code language: HTML, XML (xml)

Leave a Reply

Your email address will not be published. Required fields are marked *