2 Commits
4 changed files with 84 additions and 21 deletions

No files matched your search

@@ -58,7 +58,7 @@ export function DashboardView({
error,
mutate: mutateStats,
} = useStats(initialStats);
const { data: activity } = useActivity(14, initialActivity as never);
const { data: activity } = useActivity(14, initialActivity);
const { data: reactions } = useTopReactions();
const ambient = useAmbient();
@@ -35,15 +35,18 @@ export function AmbientCanvas({
let raf = 0;
let last = performance.now();
// Lerp state
// Lerp state (start values match the inline style defaults on the element).
let r = 45,
g = 212,
b = 191;
let targetR = 45,
targetG = 212,
targetB = 191;
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;
@@ -53,21 +56,34 @@ export function AmbientCanvas({
const tgt = targetRef.current;
const [tr, tg, tb] = TONE_css[tgt.tone].split(",").map(Number);
targetR = tr;
targetG = tg;
targetB = tb;
targetIntensity = 0.15 + tgt.intensity * 0.85;
const targetIntensity = 0.15 + tgt.intensity * 0.85;
r = lerp(r, targetR, 0.03);
g = lerp(g, targetG, 0.03);
b = lerp(b, targetB, 0.03);
r = lerp(r, tr, 0.03);
g = lerp(g, tg, 0.03);
b = lerp(b, tb, 0.03);
intensity = lerp(intensity, targetIntensity, 0.03);
const root = mount;
root.style.setProperty("--ab-r", String(Math.round(r)));
root.style.setProperty("--ab-g", String(Math.round(g)));
root.style.setProperty("--ab-b", String(Math.round(b)));
root.style.setProperty("--ab-alpha", intensity.toFixed(3));
const R = Math.round(r);
const G = Math.round(g);
const B = Math.round(b);
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);
};
@@ -101,6 +101,9 @@ export function Select({
ref={triggerRef}
type="button"
onClick={() => setOpen((o) => !o)}
aria-haspopup="listbox"
aria-expanded={open}
aria-label={selected?.label ?? placeholder}
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",
"focus:outline-none focus:border-signal/60",
@@ -123,6 +126,7 @@ export function Select({
createPortal(
<div
ref={dropdownRef}
role="listbox"
className="glass fixed z-[9999] max-h-72 overflow-auto p-1.5"
style={{
top: pos.top,
@@ -138,6 +142,8 @@ export function Select({
<button
key={o.value}
type="button"
role="option"
aria-selected={o.value === value}
onClick={() => {
onChange(o.value);
setOpen(false);
@@ -1,7 +1,8 @@
"use client";
import { usePathname } from "next/navigation";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { isActivePath, navItems } from "@/lib/navigation";
import { cn } from "@/lib/utils";
@@ -16,11 +17,35 @@ function NavItem({
active: boolean;
Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
}) {
const itemRef = useRef<HTMLAnchorElement>(null);
const [tipPos, setTipPos] = useState<{ top: number; left: number } | null>(null);
const [hovering, setHovering] = useState(false);
// Measure the item and position the tooltip via a Portal on document.body
// so it always paints above the main content (which has its own stacking
// contexts from backdrop-blur panels).
const showTooltip = () => {
const el = itemRef.current;
if (!el) return;
const rect = el.getBoundingClientRect();
setTipPos({ top: rect.top + rect.height / 2, left: rect.right + 10 });
setHovering(true);
};
const hideTooltip = () => {
setHovering(false);
setTipPos(null);
};
return (
<a
ref={itemRef}
href={href}
aria-label={label}
aria-current={active ? "page" : undefined}
onMouseEnter={showTooltip}
onMouseLeave={hideTooltip}
onFocus={showTooltip}
onBlur={hideTooltip}
className={cn(
"nav-dock-item group relative flex size-9 items-center justify-center rounded-[8px] transition-all duration-150",
active
@@ -32,9 +57,25 @@ function NavItem({
{active && (
<span className="absolute -left-[5px] h-3.5 w-[2px] rounded-full bg-signal shadow-[0_0_8px_var(--color-signal-glow)]" />
)}
<span className="pointer-events-none absolute left-full z-50 ml-2.5 hidden whitespace-nowrap rounded-md border border-hairline bg-surface px-2 py-0.5 font-sans text-[11px] font-medium tracking-tight text-ink opacity-0 shadow-lg backdrop-blur-md transition-all group-hover:translate-x-0.5 group-hover:opacity-100 md:block">
{label}
</span>
{/* Tooltip — portalled to body so it never hides behind content */}
{hovering &&
tipPos &&
typeof document !== "undefined" &&
createPortal(
<span
role="tooltip"
className="pointer-events-none fixed z-[9999] translate-y-[-50%] whitespace-nowrap rounded-md border border-hairline bg-surface px-2.5 py-1 font-sans text-[11px] font-medium tracking-tight text-ink shadow-xl backdrop-blur-md"
style={{
top: tipPos.top,
left: tipPos.left,
animation: "fade-up 0.12s ease",
}}
>
{label}
</span>,
document.body,
)}
</a>
);
}