141 lines
4.0 KiB
TypeScript
141 lines
4.0 KiB
TypeScript
import { css, CSSResultArray, html, LitElement, TemplateResult } from 'lit';
|
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
|
|
/**
|
|
* @cssprop --filled-star-color - The color of the filled stars.
|
|
* @cssprop --empty-star-color - The color of the empty stars.
|
|
*/
|
|
@customElement('z-reviews.stars')
|
|
export class ReviewsStarsComponent extends LitElement {
|
|
/// Private variables ///
|
|
|
|
/// Protected variables ///
|
|
@state() protected _filledStarCount = 0;
|
|
|
|
/// Public variables ///
|
|
@property({ type: Number }) public rating: number | undefined;
|
|
@property({ type: Number, attribute: 'out-of' }) public outOf: number | undefined;
|
|
@property({ type: Number }) public stars: number = 5;
|
|
@property({ type: Boolean, attribute: 'allow-fractional' }) public allowFractional = false;
|
|
|
|
/// constructor & lifecycle ///
|
|
override connectedCallback(): void {
|
|
super.connectedCallback();
|
|
|
|
this.validateProperties();
|
|
}
|
|
|
|
override updated(changedProperties: Map<string | number | symbol, unknown>): void {
|
|
super.updated(changedProperties);
|
|
|
|
this._filledStarCount = this.calculateFilledStarCount();
|
|
// this.requestUpdate();
|
|
}
|
|
|
|
/// Public methods ///
|
|
|
|
/// Protected methods ///
|
|
protected override render(): TemplateResult {
|
|
return html`
|
|
<div class="stars">${this.renderStars()}</div>
|
|
<div
|
|
class="stars stars--filled"
|
|
style="${styleMap({
|
|
'--filled-star-count': this._filledStarCount,
|
|
'--_filled-star-width': `${(this._filledStarCount / this.stars) * 100}%`,
|
|
})}"
|
|
>
|
|
${this.renderStars()}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
protected renderStars(): TemplateResult {
|
|
const stars: TemplateResult[] = [];
|
|
|
|
for (let i = 0; i < this.stars; i++) {
|
|
stars.push(this.renderStar());
|
|
}
|
|
|
|
return html`${stars}`;
|
|
}
|
|
|
|
protected renderStar(): TemplateResult {
|
|
return html`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" class="icon">
|
|
<!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
|
|
<path
|
|
d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"
|
|
/>
|
|
</svg>
|
|
</svg>`;
|
|
}
|
|
|
|
/// Private methods ///
|
|
/**
|
|
* Validate the properties for the rules of availability.
|
|
*
|
|
* @throws {Error} If the properties are not valid.
|
|
*/
|
|
private validateProperties(): void {
|
|
console.log(this.rating, this.stars, this.outOf, this.allowFractional);
|
|
if (!this.rating) {
|
|
throw new Error('Rating is required.');
|
|
}
|
|
|
|
if (!this.outOf) {
|
|
throw new Error('Out of is required when rating is used.');
|
|
}
|
|
|
|
if (this.stars < 1) {
|
|
throw new Error('Stars cannot be less than 1.');
|
|
}
|
|
|
|
if (this.rating! > this.outOf) {
|
|
throw new Error('Rating cannot be greater than out of.');
|
|
}
|
|
}
|
|
|
|
private calculateFilledStarCount(): number {
|
|
if (this.allowFractional) {
|
|
return (this.rating! / this.outOf!) * this.stars;
|
|
}
|
|
|
|
return Math.round((this.rating! / this.outOf!) * this.stars);
|
|
}
|
|
|
|
/// Statics ///
|
|
|
|
static override styles: CSSResultArray = [
|
|
css`
|
|
:host {
|
|
display: flex;
|
|
position: relative;
|
|
}
|
|
|
|
.icon {
|
|
vertical-align: -0.125em;
|
|
flex: 0 0 1em;
|
|
height: 1em;
|
|
width: 1em;
|
|
fill: currentColor;
|
|
}
|
|
|
|
.stars {
|
|
display: flex;
|
|
color: var(--empty-star-color, #e4e5e9);
|
|
}
|
|
|
|
.stars--filled {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
overflow: hidden;
|
|
opacity: 1;
|
|
right: calc(100% - var(--_filled-star-width));
|
|
color: var(--filled-star-color, #f7b731);
|
|
}
|
|
`,
|
|
];
|
|
}
|