fix: enhance accessibility for Select component with ARIA attributes

This commit is contained in:
asepharyana
2026-08-27 18:09:29 +07:00
parent 7375e2ed52
commit a54c34c6b3
3 changed files with 39 additions and 17 deletions
@@ -58,7 +58,7 @@ export function DashboardView({
error, error,
mutate: mutateStats, mutate: mutateStats,
} = useStats(initialStats); } = useStats(initialStats);
const { data: activity } = useActivity(14, initialActivity as never); const { data: activity } = useActivity(14, initialActivity);
const { data: reactions } = useTopReactions(); const { data: reactions } = useTopReactions();
const ambient = useAmbient(); const ambient = useAmbient();
@@ -35,15 +35,18 @@ export function AmbientCanvas({
let raf = 0; let raf = 0;
let last = performance.now(); let last = performance.now();
// Lerp state // Lerp state (start values match the inline style defaults on the element).
let r = 45, let r = 45,
g = 212, g = 212,
b = 191; b = 191;
let targetR = 45,
targetG = 212,
targetB = 191;
let intensity = 0.35; let intensity = 0.35;
let targetIntensity = 0.35; // Last values actually written to the DOM — lets us skip `setProperty`
// entirely once a channel settles, so the continuous loop stops invalidating
// style/layout every frame when the ambient is static.
let lastR = -1,
lastG = -1,
lastB = -1,
lastAlpha = "-1";
const lerp = (a: number, b: number, t: number) => a + (b - a) * t; const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
@@ -53,21 +56,34 @@ export function AmbientCanvas({
const tgt = targetRef.current; const tgt = targetRef.current;
const [tr, tg, tb] = TONE_css[tgt.tone].split(",").map(Number); const [tr, tg, tb] = TONE_css[tgt.tone].split(",").map(Number);
targetR = tr; const targetIntensity = 0.15 + tgt.intensity * 0.85;
targetG = tg;
targetB = tb;
targetIntensity = 0.15 + tgt.intensity * 0.85;
r = lerp(r, targetR, 0.03); r = lerp(r, tr, 0.03);
g = lerp(g, targetG, 0.03); g = lerp(g, tg, 0.03);
b = lerp(b, targetB, 0.03); b = lerp(b, tb, 0.03);
intensity = lerp(intensity, targetIntensity, 0.03); intensity = lerp(intensity, targetIntensity, 0.03);
const root = mount; const root = mount;
root.style.setProperty("--ab-r", String(Math.round(r))); const R = Math.round(r);
root.style.setProperty("--ab-g", String(Math.round(g))); const G = Math.round(g);
root.style.setProperty("--ab-b", String(Math.round(b))); const B = Math.round(b);
root.style.setProperty("--ab-alpha", intensity.toFixed(3)); const A = intensity.toFixed(3);
if (R !== lastR) {
root.style.setProperty("--ab-r", String(R));
lastR = R;
}
if (G !== lastG) {
root.style.setProperty("--ab-g", String(G));
lastG = G;
}
if (B !== lastB) {
root.style.setProperty("--ab-b", String(B));
lastB = B;
}
if (A !== lastAlpha) {
root.style.setProperty("--ab-alpha", A);
lastAlpha = A;
}
raf = requestAnimationFrame(frame); raf = requestAnimationFrame(frame);
}; };
@@ -101,6 +101,9 @@ export function Select({
ref={triggerRef} ref={triggerRef}
type="button" type="button"
onClick={() => setOpen((o) => !o)} onClick={() => setOpen((o) => !o)}
aria-haspopup="listbox"
aria-expanded={open}
aria-label={selected?.label ?? placeholder}
className={cn( className={cn(
"flex w-full items-center justify-between gap-2 rounded-[8px] border border-hairline bg-surface-2 text-left text-ink transition-colors hover:border-signal/40", "flex w-full items-center justify-between gap-2 rounded-[8px] border border-hairline bg-surface-2 text-left text-ink transition-colors hover:border-signal/40",
"focus:outline-none focus:border-signal/60", "focus:outline-none focus:border-signal/60",
@@ -123,6 +126,7 @@ export function Select({
createPortal( createPortal(
<div <div
ref={dropdownRef} ref={dropdownRef}
role="listbox"
className="glass fixed z-[9999] max-h-72 overflow-auto p-1.5" className="glass fixed z-[9999] max-h-72 overflow-auto p-1.5"
style={{ style={{
top: pos.top, top: pos.top,
@@ -138,6 +142,8 @@ export function Select({
<button <button
key={o.value} key={o.value}
type="button" type="button"
role="option"
aria-selected={o.value === value}
onClick={() => { onClick={() => {
onChange(o.value); onChange(o.value);
setOpen(false); setOpen(false);