UNPKG

@ionic-snippets/star-rating

Version:

A flexible star rating component for Angular with Ionic support

110 lines (105 loc) 12.2 kB
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