import { WebComponent, choice, string } from '../src/index.ts'; import { share } from './main-context.ts'; import { SWATCHES, THEMES } from './themes.ts'; /** * Light DOM radio group. The radios are real light-DOM ``s, so arrow * keys, focus and form behaviour are native. A pick is shared with the nearest * `` as a `theme` update. */ const SPEC = { template: `
${THEMES.map( (theme) => ` `, ).join('')}
`, css: ` color-swatch-picker { display: block; font: 0.9rem/1.3 system-ui, sans-serif; } color-swatch-picker fieldset { border: 0; margin: 0; padding: 0; } color-swatch-picker legend { font-weight: 600; padding: 0; margin-bottom: 0.35rem; } color-swatch-picker .swatches { display: flex; flex-wrap: wrap; gap: 0.5rem; } color-swatch-picker .swatch { position: relative; display: grid; place-items: end center; width: 5.5rem; height: 3.5rem; padding-bottom: 0.3rem; border-radius: 12px; background: var(--swatch); color: #fff; font-weight: 600; text-transform: capitalize; text-shadow: 0 1px 2px rgb(0 0 0 / 35%); cursor: pointer; transition: transform 150ms ease, box-shadow 150ms ease; } color-swatch-picker .swatch:hover { transform: translateY(-2px); } color-swatch-picker .swatch input { position: absolute; opacity: 0; pointer-events: none; } color-swatch-picker .swatch:has(:checked) { box-shadow: 0 0 0 3px var(--page, #fff), 0 0 0 5px var(--ink, #1d1d29); } color-swatch-picker .swatch:has(:focus-visible) { outline: 2px solid #7c5cff; outline-offset: 6px; } `, events: { change: true, }, element: { legend: 'legend' }, elements: { radios: 'input[type=radio]' }, props: { label: string({ default: 'Theme' }), value: choice(THEMES, { default: 'sunset' }), }, } as const; // Each picker needs its own radio group name. let count = 0; export class ColorSwatchPicker extends WebComponent(SPEC) { readonly #name = `color-swatch-picker-${(count += 1)}`; override onConnect(): void { for (const radio of this.refs.radios) radio.name = this.#name; this.#render(); } override onPropChange(): void { this.#render(); } override handleEvent(event: Event): void { const radio = this.refs.radios.find((input) => input === event.target); const theme = THEMES.find((name) => name === radio?.value); if (theme && this.value !== theme) { this.value = theme; share(this, 'theme', theme); } } #render(): void { if (this.refs.legend) this.refs.legend.textContent = this.label; for (const radio of this.refs.radios) radio.checked = radio.value === this.value; } } customElements.define('color-swatch-picker', ColorSwatchPicker); declare global { interface HTMLElementTagNameMap { 'color-swatch-picker': ColorSwatchPicker; } }