<script lang="ts">
import type { Snippet } from "svelte";
import { buttonIcon, type ButtonIconVariants } from "#lib/components/ui/Button.svelte";
import { CROSSFADE_DURATION, CROSSFADE_EASING } from "#lib/motion/spring.ts";
interface Props {
on: boolean;
failed?: boolean;
size?: ButtonIconVariants["size"];
offLabel: string;
onLabel: string;
labelClass?: string;
off: Snippet<[string]>;
onIcon: Snippet<[string]>;
err: Snippet<[string]>;
}
let {
on,
failed = false,
size = "small",
offLabel,
onLabel,
labelClass,
off,
onIcon,
err
}: Props = $props();
const icon = $derived(buttonIcon({ size }));
const face = $derived(failed ? "err" : on ? "on" : "off");
const layer = (key: typeof face) => `swap-layer ${icon}${key === face ? "" : " swap-icon-out"}`;
let armed = $state(false);
const armOnClick = (node: HTMLElement) => {
const control = node.closest("button, a") ?? node;
const arm = () => (armed = true);
control.addEventListener("click", arm, { capture: true });
return () => control.removeEventListener("click", arm, { capture: true });
};
</script>
<span
class="contents"
{@attach armOnClick}
style:--swap-duration={armed ? `${CROSSFADE_DURATION}ms` : "0ms"}
style:--swap-easing={CROSSFADE_EASING}
>
<span class={["grid", icon]} aria-hidden="true">
{@render off(layer("off"))}
{@render onIcon(layer("on"))}
{@render err(layer("err"))}
</span>
<span class={["grid", labelClass]} aria-hidden="true">
<span class={["whitespace-nowrap swap-layer", on && "swap-label-out"]}>{offLabel}</span>
<span class={["whitespace-nowrap swap-layer", !on && "swap-label-out"]}>{onLabel}</span>
</span>
<span class="sr-only">{on ? onLabel : offLabel}</span>
</span>