Ratings
Overview
Ratings, a meaningful way to show how much an item (mostly product from online store) is appreciated...or not.
It is showed very often with stars and average value of reviewers' ratings. Examples below show you how you can use JSXPine's Rating component.
Installation
Notes
To use this component, you need to initialize your project first. If not done yet, run one of the following command:
npx jsxpine init or yarn jsxpine init or pnpm jsxpine init or bunx jsxpine init.
Go to the installation and usage page to learn more.
jsxpine add ratings
Copied !
-------------------- Components Dependencies -------------------------
import { icons } from "@iconify-json/ri/icons.json";
import clsx from "clsx";
import { SVG } from "./svg.component";
/**
* @typedef {`ri.${keyof typeof import("@iconify-json/ri/icons.json")["icons"]}`} IconName
*/
/**
* @typedef IconProps
* @type {{size?: number, name: IconName, color?: string, applyDefsId?: string} & import("./svg.component").SVGProps}
*/
/**
* Icon component props
* @param {Omit<IconProps, "viewBox">} props
*/
export function Icon({
children,
size = 4,
name,
applyDefsId,
class: className,
...restProps
}) {
const iconType = name.split(".")[0];
const iconName = /** @type {keyof typeof icons} */ (name.split(".")[1]);
if (!icons[iconName]) {
console.error(`"${name}" is not an icon from iconify/${iconType}`);
return "";
}
const { body } = icons[iconName];
// The purpose is to retrieve value from d attribute
let retrieveDValue = "";
const bodyMatch = body.match(/d=".+"/g);
const retrieveDAttribute = bodyMatch ? bodyMatch[0] : "";
if (retrieveDAttribute) {
retrieveDValue = retrieveDAttribute.slice(3, -2);
}
return (
<SVG
viewBox={"0 0 24 24"}
{...restProps}
class={clsx(`size-${size}`, className)}
>
{children}
<path
stroke-linecap="round"
stroke-linejoin="round"
fill={applyDefsId ? `url(#${applyDefsId})` : "currentColor"}
d={retrieveDValue}
/>
</SVG>
);
}
/**
* @typedef SVGProps
* @type {{fill?: string, stroke?: string, strokeWidth?: number, viewBox: string } & Omit<JSX.HtmlTag, "className"> & import("../common/props").CLSXClassProps}
*/
/**
* SVG component props
* @type {import("../common/props").JSXComponent<SVGProps>}
*/
export function SVG(props) {
const {
children,
class: className,
fill = "none",
stroke = "currentColor",
strokeWidth = 0.5,
...restProps
} = props;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill={fill}
stroke={stroke}
class={className}
style={{ strokeWidth: String(strokeWidth) }}
{...restProps}
>
{children}
</svg>
)
}
-------------------- Ratings Component -------------------------
import { Icon } from "./icon.component";
import clsx from "clsx";
/**
* @typedef {Object} LinearGradientDefsProps
* @type {{ applyDefsId: string, color: { empty: string, fill: string }, percentageValue: number} & import("../common/props").HTMLTag}
*/
/**
* @typedef {Object} RatingsProps
* @type {{ applyDefsId: string, nb?: number, value: number, max?: number, size?: number, icon?: import("./icon.component").IconName, color?: { empty: string, fill: string } } & import("../common/props").HTMLTagWithChildren}
*/
/**
* Linear Gradient Defs component props
* @type {import("../common/props").JSXComponent<LinearGradientDefsProps>}
*/
export function LinearGradientDefs(props) {
const { applyDefsId, color, percentageValue } = props;
return (
<defs>
<linearGradient id={applyDefsId}>
{percentageValue === 100 && (
<stop offset="0%" stop-color={color.fill} />
)}
{percentageValue === 0 && <stop offset="0%" stop-color={color.empty} />}
{![0, 100].includes(percentageValue) && (
<>
<stop offset="0%" stop-color={color.fill} />
<stop offset={`${percentageValue}%`} stop-color={color.fill} />
<stop offset={`${percentageValue}%`} stop-color={color.empty} />
<stop offset="100%" stop-color={color.empty} stop-opacity="1" />
</>
)}
</linearGradient>
</defs>
);
}
/**
* Ratings component props
* @type {import("../common/props").JSXComponent<RatingsProps>}
*/
export function Ratings(props) {
const {
children,
class: className,
value,
nb = 1,
max = 5,
color,
size = 6,
icon = "ri.star-fill",
...restProps
} = props;
const chunk = max / nb;
const ratings = Array.from({ length: nb }, (_, index) => {
const slice = index + 1;
return value < (slice - 1) * chunk
? 0
: value > slice * chunk
? chunk
: value - (slice - 1) * chunk;
});
return (
<ul
class={clsx("flex items-center justify-center gap-x-2", className)}
{...restProps}
>
{
children ??
ratings.map((rating) => {
const percentageRating = Number(((rating * 100) / chunk).toFixed(2));
const applyDefsId = crypto.randomUUID();
return (
<li>
<Icon
name={icon}
size={size}
stroke-width="1"
applyDefsId={applyDefsId}
>
{applyDefsId && (
<LinearGradientDefs
percentageValue={percentageRating}
applyDefsId={applyDefsId}
color={{
empty: color?.empty ?? "grey",
fill: color?.fill ?? "orange"
}}
/>
)}
</Icon>
</li>
);
})}
</ul>
);
}
Default Ratings
By default, Ratings displays one star as icon and _value_ props is a integer / float number between 0 and 5. Under the hood, Ratings turns value in percentage.
Copied !
import { Ratings } from "$components/ratings.component";
export function DefaultRatingsExample() {
const value = 3.2;
return (
<div class="p-4 border rounded bg-white text-slate-900">
<div class="flex gap-x-2 items-end">
<span>{value}</span>
<Ratings value={value} applyDefsId="default-ratings-example" />
</div>
</div>
);
}
Number Stars Ratings
By using nb props, number of total ratings icon will be different than 1.
Copied !
import { Ratings } from "$components/ratings.component";
export function FiveStarsRatingsExample() {
return (
<div class="p-4 border rounded bg-white">
<Ratings
nb={5}
size={7}
value={3.6}
applyDefsId="five-stars-ratings-example"
/>
</div>
);
}
Custom Icon Ratings
The icon props takes an astro component and replace the current one which is StarIcon. Pay attention to color.
By using color props, you can show any color you want. It's an object with two properties: fill and empty.
Copied !
import { Ratings } from "$components/ratings.component";
import { COLORS } from "$config/design";
export function CustomIconRatingsExample() {
return (
<div class="p-4 border rounded bg-white">
<Ratings
value={2.4}
nb={3}
max={3}
size={12}
color={{ empty: COLORS.slate[300], fill: COLORS.red[500] }}
icon="ri.heart-3-fill"
applyDefsId="custom-icon-ratings-example"
/>
</div>
);
}