Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101<script lang="ts"> import type { Snippet } from "svelte"; import { prefersReducedMotion } from "svelte/motion"; import { buttonIcon, type ButtonIconVariants } from "$lib/components/ui/Button.svelte"; import { CROSSFADE_DURATION, CROSSFADE_EASING } from "$lib/motion/spring";
interface Props { on: boolean; failed?: boolean; size?: ButtonIconVariants["size"]; offLabel: string; onLabel: string; off: Snippet<[string]>; onIcon: Snippet<[string]>; err: Snippet<[string]>; }
let { on, failed = false, size = "small", offLabel, onLabel, off, onIcon, err }: Props = $props();
const icon = $derived(buttonIcon({ size })); const fade = $derived(prefersReducedMotion.current ? 0 : CROSSFADE_DURATION); const face = $derived(failed ? "err" : on ? "on" : "off");</script>
<span class="contents" style="--swap: {fade}ms; --swap-ease: {CROSSFADE_EASING}"> <span class={["faces grid", icon]} data-face={face} aria-hidden="true"> {@render off(`face off ${icon}`)} {@render onIcon(`face on ${icon}`)} {@render err(`face err ${icon}`)} </span> <span class="labels" data-on={on} aria-hidden="true"> <span class="label off">{offLabel}</span> <span class="label on">{onLabel}</span> </span> <span class="sr-only">{on ? onLabel : offLabel}</span></span>
<style> .faces :global(.face) { grid-column: 1; grid-row: 1; opacity: 0; scale: 0.92; filter: blur(1px); transition: opacity var(--swap) var(--swap-ease), scale var(--swap) var(--swap-ease), filter var(--swap) var(--swap-ease); }
.faces[data-face="off"] :global(.off), .faces[data-face="on"] :global(.on), .faces[data-face="err"] :global(.err) { opacity: 1; scale: 1; filter: blur(0); }
/* only the visible word is in flow, so the box hugs it and the gap next to the icon stays honest; the button's min-width holds the widest arrangement so nothing beside it moves. keep that min-width in step if the labels ever change. */ .labels { position: relative; }
.label { display: block; white-space: nowrap; opacity: 0; translate: 0 3px; filter: blur(1px); transition: opacity var(--swap) var(--swap-ease), translate var(--swap) var(--swap-ease), filter var(--swap) var(--swap-ease); }
.labels[data-on="false"] .on, .labels[data-on="true"] .off { position: absolute; top: 0; left: 0; }
.labels[data-on="false"] .off, .labels[data-on="true"] .on { opacity: 1; translate: 0 0; filter: blur(0); }</style>