WIP PWA for Grain
at main 89 lines 2.1 kB view raw
1import { LitElement, html, css } from 'lit'; 2import '../atoms/grain-icon.js'; 3 4export class GrainStatCount extends LitElement { 5 static properties = { 6 icon: { type: String }, 7 count: { type: Number }, 8 filled: { type: Boolean }, 9 interactive: { type: Boolean } 10 }; 11 12 static styles = css` 13 :host { 14 display: inline-flex; 15 align-items: center; 16 gap: var(--space-xs); 17 color: var(--color-text-primary); 18 } 19 button { 20 display: flex; 21 align-items: center; 22 justify-content: center; 23 background: none; 24 border: none; 25 padding: var(--space-sm); 26 margin: calc(-1 * var(--space-sm)); 27 cursor: pointer; 28 color: inherit; 29 border-radius: var(--border-radius); 30 transition: opacity 0.2s; 31 } 32 button:hover { 33 opacity: 0.7; 34 } 35 button:active { 36 transform: scale(0.95); 37 } 38 .count { 39 font-size: var(--font-size-sm); 40 font-weight: var(--font-weight-semibold); 41 } 42 `; 43 44 constructor() { 45 super(); 46 this.count = 0; 47 this.filled = false; 48 this.interactive = false; 49 } 50 51 #formatCount(n) { 52 if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`; 53 if (n >= 1000) return `${(n / 1000).toFixed(1)}K`; 54 return n.toString(); 55 } 56 57 #handleClick() { 58 if (this.interactive) { 59 this.dispatchEvent(new CustomEvent('stat-click', { bubbles: true, composed: true })); 60 } 61 } 62 63 get #iconName() { 64 if (this.icon === 'heart' && this.filled) { 65 return 'heartFilled'; 66 } 67 return this.icon; 68 } 69 70 render() { 71 const color = this.icon === 'heart' && this.filled ? 'var(--color-heart)' : 'inherit'; 72 73 return html` 74 <button 75 type="button" 76 aria-label=${this.icon} 77 style="color: ${color}" 78 @click=${this.#handleClick} 79 > 80 <grain-icon name=${this.#iconName} size="16"></grain-icon> 81 </button> 82 ${this.count > 0 ? html` 83 <span class="count">${this.#formatCount(this.count)}</span> 84 ` : ''} 85 `; 86 } 87} 88 89customElements.define('grain-stat-count', GrainStatCount);