@ionic-snippets/star-rating
Version:
A flexible star rating component for Angular with Ionic support
110 lines (105 loc) • 12.2 kB
JavaScript
import * as i0 from '@angular/core';
import { input, output, computed, Component } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import { IonButton, IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { radioButtonOffOutline, radioButtonOff, squareOutline, square, diamondOutline, diamond, thumbsUpOutline, thumbsUp, heartOutline, heart, starOutline, star } from 'ionicons/icons';
class StarRatingComponent {
constructor() {
// Input Signals
this.rating = input(0, ...(ngDevMode ? [{ debugName: "rating" }] : []));
this.maxRating = input(5, ...(ngDevMode ? [{ debugName: "maxRating" }] : []));
this.iconType = input('star', ...(ngDevMode ? [{ debugName: "iconType" }] : []));
this.editable = input(true, ...(ngDevMode ? [{ debugName: "editable" }] : []));
this.size = input('medium', ...(ngDevMode ? [{ debugName: "size" }] : []));
this.color = input('#ffc107', ...(ngDevMode ? [{ debugName: "color" }] : []));
this.emptyColor = input('#e0e0e0', ...(ngDevMode ? [{ debugName: "emptyColor" }] : []));
this.showRating = input(false, ...(ngDevMode ? [{ debugName: "showRating" }] : []));
// Output Signals
this.ratingChange = output();
this.ratingClick = output();
// Computed signals for derived values
this.stars = computed(() => {
const maxRating = this.maxRating();
return Array.from({ length: maxRating }, (_, i) => i + 1);
}, ...(ngDevMode ? [{ debugName: "stars" }] : []));
// Register the icons we need
addIcons({
star,
'star-outline': starOutline,
heart,
'heart-outline': heartOutline,
'thumbs-up': thumbsUp,
'thumbs-up-outline': thumbsUpOutline,
diamond,
'diamond-outline': diamondOutline,
square,
'square-outline': squareOutline,
'radio-button-off': radioButtonOff,
'radio-button-off-outline': radioButtonOffOutline,
});
}
ngOnInit() { }
onStarClick(starIndex) {
if (!this.editable())
return;
this.ratingChange.emit(starIndex);
this.ratingClick.emit(starIndex);
}
getIconName(starIndex) {
const isFilled = starIndex <= this.rating();
const suffix = isFilled ? '' : '-outline';
// Icon mapping with robust fallbacks
const iconMap = {
heart: `heart${suffix}`,
'thumbs-up': `thumbs-up${suffix}`,
diamond: `diamond${suffix}`,
square: `square${suffix}`,
circle: `radio-button-off${suffix}`,
star: `star${suffix}`,
};
// If the icon doesn't exist in the map, use star as fallback
return iconMap[this.iconType()] || `star${suffix}`;
}
getIconStyle(starIndex) {
return {
color: starIndex <= this.rating() ? this.color() : this.emptyColor(),
fontSize: this.getSizeValue(),
};
}
getSizeValue() {
switch (this.size()) {
case 'small':
return '16px';
case 'large':
return '24px';
default:
return '20px';
}
}
getButtonSize() {
switch (this.size()) {
case 'small':
return 'small';
case 'large':
return 'large';
default:
return 'default';
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: StarRatingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.1.3", type: StarRatingComponent, isStandalone: true, selector: "app-star-rating", inputs: { rating: { classPropertyName: "rating", publicName: "rating", isSignal: true, isRequired: false, transformFunction: null }, maxRating: { classPropertyName: "maxRating", publicName: "maxRating", isSignal: true, isRequired: false, transformFunction: null }, iconType: { classPropertyName: "iconType", publicName: "iconType", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, emptyColor: { classPropertyName: "emptyColor", publicName: "emptyColor", isSignal: true, isRequired: false, transformFunction: null }, showRating: { classPropertyName: "showRating", publicName: "showRating", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { ratingChange: "ratingChange", ratingClick: "ratingClick" }, ngImport: i0, template: "<div class=\"star-rating-container\">\n <div class=\"stars-wrapper\">\n <ion-button\n *ngFor=\"let star of stars(); let i = index\"\n [fill]=\"i + 1 <= rating() ? 'solid' : 'clear'\"\n [size]=\"getButtonSize()\"\n [disabled]=\"!editable()\"\n (click)=\"onStarClick(i + 1)\"\n class=\"rating-button\"\n [attr.data-rating]=\"i + 1\"\n >\n <ion-icon [name]=\"getIconName(i + 1)\" [style]=\"getIconStyle(i + 1)\">\n </ion-icon>\n </ion-button>\n </div>\n\n <div *ngIf=\"showRating()\" class=\"rating-text\">\n {{ rating() }}/{{ maxRating() }}\n </div>\n</div>\n", styles: [".star-rating-container{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.stars-wrapper{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.rating-button{--background: transparent;--background-hover: transparent;--background-activated: transparent;--background-focused: transparent;--border-radius: 50%;--min-width: auto;--min-height: auto;--padding-start: 6px;--padding-end: 6px;--padding-top: 6px;--padding-bottom: 6px;--border-width: 0;--border-style: none;--border-color: transparent;--box-shadow: none;margin:0;box-shadow:none;border:none;outline:none}.rating-button.button-small{--padding-start: 4px;--padding-end: 4px;--padding-top: 4px;--padding-bottom: 4px}.rating-button.button-large{--padding-start: 8px;--padding-end: 8px;--padding-top: 8px;--padding-bottom: 8px}.rating-button:hover{--background: rgba(var(--ion-color-primary-rgb), .1);transform:scale(1.1);--border-width: 0;--box-shadow: none}.rating-button:active{--background: rgba(var(--ion-color-primary-rgb), .2);transform:scale(.95);--border-width: 0;--box-shadow: none}.rating-button:focus{--border-width: 0;--box-shadow: none;outline:none;border:none}.rating-button:focus-visible{--border-width: 0;--box-shadow: none;outline:none;border:none}.rating-button[disabled]{--opacity: .6;cursor:default;--border-width: 0;--box-shadow: none}.rating-button[disabled]:hover{--background: transparent;transform:none}.rating-button:before,.rating-button:after{border:none;outline:none}.rating-button.ion-focused{--border-width: 0;--box-shadow: none;outline:none;border:none}.rating-button:focus-visible{outline:none;border:none;--border-width: 0;--box-shadow: none}.rating-button ion-icon{transition:all .2s ease;filter:drop-shadow(0 1px 2px rgba(0,0,0,.1))}.rating-text{font-size:.9em;color:var(--ion-color-medium);font-weight:500;margin-left:8px}@media (max-width: 768px){.star-rating-container{gap:4px}.stars-wrapper{gap:2px}.rating-button{--padding-start: 4px;--padding-end: 4px;--padding-top: 4px;--padding-bottom: 4px}.rating-button.button-large{--padding-start: 6px;--padding-end: 6px;--padding-top: 6px;--padding-bottom: 6px}.rating-text{font-size:.8em;margin-left:4px}}@media (max-width: 480px){.star-rating-container{flex-direction:column;align-items:flex-start;gap:2px}.rating-text{margin-left:0;margin-top:2px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: StarRatingComponent, decorators: [{
type: Component,
args: [{ selector: 'app-star-rating', standalone: true, imports: [CommonModule, IonButton, IonIcon], template: "<div class=\"star-rating-container\">\n <div class=\"stars-wrapper\">\n <ion-button\n *ngFor=\"let star of stars(); let i = index\"\n [fill]=\"i + 1 <= rating() ? 'solid' : 'clear'\"\n [size]=\"getButtonSize()\"\n [disabled]=\"!editable()\"\n (click)=\"onStarClick(i + 1)\"\n class=\"rating-button\"\n [attr.data-rating]=\"i + 1\"\n >\n <ion-icon [name]=\"getIconName(i + 1)\" [style]=\"getIconStyle(i + 1)\">\n </ion-icon>\n </ion-button>\n </div>\n\n <div *ngIf=\"showRating()\" class=\"rating-text\">\n {{ rating() }}/{{ maxRating() }}\n </div>\n</div>\n", styles: [".star-rating-container{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.stars-wrapper{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.rating-button{--background: transparent;--background-hover: transparent;--background-activated: transparent;--background-focused: transparent;--border-radius: 50%;--min-width: auto;--min-height: auto;--padding-start: 6px;--padding-end: 6px;--padding-top: 6px;--padding-bottom: 6px;--border-width: 0;--border-style: none;--border-color: transparent;--box-shadow: none;margin:0;box-shadow:none;border:none;outline:none}.rating-button.button-small{--padding-start: 4px;--padding-end: 4px;--padding-top: 4px;--padding-bottom: 4px}.rating-button.button-large{--padding-start: 8px;--padding-end: 8px;--padding-top: 8px;--padding-bottom: 8px}.rating-button:hover{--background: rgba(var(--ion-color-primary-rgb), .1);transform:scale(1.1);--border-width: 0;--box-shadow: none}.rating-button:active{--background: rgba(var(--ion-color-primary-rgb), .2);transform:scale(.95);--border-width: 0;--box-shadow: none}.rating-button:focus{--border-width: 0;--box-shadow: none;outline:none;border:none}.rating-button:focus-visible{--border-width: 0;--box-shadow: none;outline:none;border:none}.rating-button[disabled]{--opacity: .6;cursor:default;--border-width: 0;--box-shadow: none}.rating-button[disabled]:hover{--background: transparent;transform:none}.rating-button:before,.rating-button:after{border:none;outline:none}.rating-button.ion-focused{--border-width: 0;--box-shadow: none;outline:none;border:none}.rating-button:focus-visible{outline:none;border:none;--border-width: 0;--box-shadow: none}.rating-button ion-icon{transition:all .2s ease;filter:drop-shadow(0 1px 2px rgba(0,0,0,.1))}.rating-text{font-size:.9em;color:var(--ion-color-medium);font-weight:500;margin-left:8px}@media (max-width: 768px){.star-rating-container{gap:4px}.stars-wrapper{gap:2px}.rating-button{--padding-start: 4px;--padding-end: 4px;--padding-top: 4px;--padding-bottom: 4px}.rating-button.button-large{--padding-start: 6px;--padding-end: 6px;--padding-top: 6px;--padding-bottom: 6px}.rating-text{font-size:.8em;margin-left:4px}}@media (max-width: 480px){.star-rating-container{flex-direction:column;align-items:flex-start;gap:2px}.rating-text{margin-left:0;margin-top:2px}}\n"] }]
}], ctorParameters: () => [] });
/*
* Public API Surface of star-rating-lib
*/
/**
* Generated bundle index. Do not edit.
*/
export { StarRatingComponent };
//# sourceMappingURL=ionic-snippets-star-rating.mjs.map