WIP PWA for Grain
at main 86 lines 1.9 kB view raw
1import { LitElement, html, css } from 'lit'; 2 3export class GrainCarouselDots extends LitElement { 4 static properties = { 5 total: { type: Number }, 6 current: { type: Number } 7 }; 8 9 static styles = css` 10 :host { 11 display: flex; 12 justify-content: center; 13 align-items: center; 14 gap: 4px; 15 padding: var(--space-sm) 0; 16 } 17 .dot { 18 width: 6px; 19 height: 6px; 20 border-radius: 50%; 21 background: var(--color-text-secondary); 22 opacity: 0.4; 23 transition: opacity 0.2s, transform 0.2s; 24 } 25 .dot.active { 26 opacity: 1; 27 background: white; 28 } 29 .dot.small { 30 width: 4px; 31 height: 4px; 32 opacity: 0.3; 33 } 34 .dot.tiny { 35 width: 3px; 36 height: 3px; 37 opacity: 0.2; 38 } 39 `; 40 41 constructor() { 42 super(); 43 this.total = 0; 44 this.current = 0; 45 } 46 47 #getDotClass(index) { 48 const distance = Math.abs(index - this.current); 49 if (index === this.current) return 'dot active'; 50 if (distance === 1) return 'dot'; 51 if (distance === 2) return 'dot small'; 52 return 'dot tiny'; 53 } 54 55 render() { 56 if (this.total <= 1) return null; 57 58 // For 5 or fewer, show all dots 59 if (this.total <= 5) { 60 return html` 61 ${Array.from({ length: this.total }, (_, i) => html` 62 <div class="${this.#getDotClass(i)}"></div> 63 `)} 64 `; 65 } 66 67 // For more than 5, use sliding window of 5 dots centered on current 68 const maxVisible = 5; 69 let start = Math.max(0, this.current - 2); 70 let end = start + maxVisible; 71 72 if (end > this.total) { 73 end = this.total; 74 start = end - maxVisible; 75 } 76 77 return html` 78 ${Array.from({ length: end - start }, (_, i) => { 79 const index = start + i; 80 return html`<div class="${this.#getDotClass(index)}"></div>`; 81 })} 82 `; 83 } 84} 85 86customElements.define('grain-carousel-dots', GrainCarouselDots);