Icon Button
Anatomy
States
Default
Hover
Pressed
Focus
Pending
Disabled
Icons
Themes
Light
Dark
Primary override
Example
View code
"use client";
import { IconButton } from "@baseline/ui";
import { useEffect, useRef, useState } from "react";
import { flushSync } from "react-dom";
type CopyOperation = {
id: number;
phase: "idle" | "pending" | "success" | "error";
};
const pendingVisualDelay = 160;
function useDelayedOperationVisual() {
const timer = useRef<number | null>(null);
const [visualPhase, setVisualPhase] = useState<CopyOperation["phase"]>("idle");
useEffect(
() => () => {
if (timer.current !== null) window.clearTimeout(timer.current);
},
[],
);
function begin() {
if (timer.current !== null) window.clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
timer.current = null;
setVisualPhase("pending");
}, pendingVisualDelay);
}
function settle(phase: "idle" | "success" | "error") {
if (timer.current !== null) window.clearTimeout(timer.current);
timer.current = null;
setVisualPhase(phase);
}
return { begin, settle, visualPhase };
}
export function CopyIconAction() {
const nextOperationId = useRef(0);
const [operation, setOperation] = useState<CopyOperation>({ id: 0, phase: "idle" });
const operationVisual = useDelayedOperationVisual();
async function copyExample() {
const id = ++nextOperationId.current;
flushSync(() => setOperation({ id, phase: "pending" }));
operationVisual.begin();
try {
await navigator.clipboard.writeText("Complete example");
setOperation({ id, phase: "success" });
operationVisual.settle("success");
} catch {
setOperation({ id, phase: "error" });
operationVisual.settle("error");
}
}
const status =
operation.phase === "pending"
? "Copying…"
: operation.phase === "success"
? "Copied to clipboard."
: operation.phase === "error"
? "Clipboard unavailable. Try again."
: "";
return (
<>
<IconButton
aria-label="Copy source"
icon={
operationVisual.visualPhase === "pending"
? "pending"
: operationVisual.visualPhase === "success"
? "success"
: operationVisual.visualPhase === "error"
? "retry"
: "copy"
}
onClick={copyExample}
pending={operation.phase === "pending"}
/>
<span
aria-live="polite"
className="bl-visually-hidden"
data-operation-id={operation.id}
data-phase={operation.phase}
role="status"
>
{operation.id > 0 ? <span key={operation.id + "-" + operation.phase}>{status}</span> : null}
</span>
</>
);
}