From d7aa034661d9008f27327ca3f42aaaa57835feee Mon Sep 17 00:00:00 2001 From: asepharyana Date: Sun, 20 Sep 2026 14:57:18 +0700 Subject: [PATCH] feat(frontend): staggered list reveals + toast tone accents (level 5 polish) --- .../src/app/(dashboard)/dashboard/view.tsx | 4 +++- .../src/app/(dashboard)/messages/view.tsx | 15 +++++++++++---- services/frontend/src/app/globals.css | 8 ++++++++ .../src/components/LiveModerationFeed.tsx | 6 ++++-- .../frontend/src/components/primitives/toast.tsx | 6 ++++-- services/frontend/src/lib/utils.ts | 7 +++++++ 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/services/frontend/src/app/(dashboard)/dashboard/view.tsx b/services/frontend/src/app/(dashboard)/dashboard/view.tsx index fe3f4a82..1cb662c2 100644 --- a/services/frontend/src/app/(dashboard)/dashboard/view.tsx +++ b/services/frontend/src/app/(dashboard)/dashboard/view.tsx @@ -32,6 +32,7 @@ import { useActivity, useStats, useTopReactions } from "@/hooks"; import { useStaggerReveal } from "@/hooks/use-gsap-animation"; import { formatNumber } from "@/lib/format"; import type { DashboardStats } from "@/lib/types"; +import { staggerDelay } from "@/lib/utils"; function deriveSignal(stats?: DashboardStats) { if (!stats) @@ -334,7 +335,8 @@ export function DashboardView({ return (
{String(i + 1).padStart(2, "0")} diff --git a/services/frontend/src/app/(dashboard)/messages/view.tsx b/services/frontend/src/app/(dashboard)/messages/view.tsx index 197f553d..5c7d40f8 100644 --- a/services/frontend/src/app/(dashboard)/messages/view.tsx +++ b/services/frontend/src/app/(dashboard)/messages/view.tsx @@ -61,6 +61,7 @@ import type { MessageMetadata, MessageRecord, } from "@/lib/types"; +import { staggerDelay } from "@/lib/utils"; import { useWebSocket } from "@/lib/ws/context"; export function MessagesView({ @@ -354,7 +355,8 @@ export function MessagesView({ {semantic.data.map((r, i) => (
@@ -473,7 +475,7 @@ export function MessagesView({ >
{viewMode === "timeline" && timelineNodes - ? timelineNodes.map((node) => + ? timelineNodes.map((node, i) => node.type === "date" ? (
), ) - : display.map((m) => ( + : display.map((m, i) => ( @@ -879,10 +883,12 @@ function MessageRow({ m, selected, onSelect, + index = 0, }: { m: MessageRecord; selected: string | null; onSelect: (id: string) => void; + index?: number; }) { const meta = parseMeta(m.metadata); const attachments = meta?.attachments ?? []; @@ -907,11 +913,12 @@ function MessageRow({ key={m.id} type="button" onClick={() => onSelect(m.id)} - className={`msg-feed-card flex w-full items-start gap-3 rounded-[8px] border p-2.5 text-left transition-all ${ + className={`msg-feed-card animate-stagger flex w-full items-start gap-3 rounded-[8px] border p-2.5 text-left transition-all ${ selected === m.id ? "border-signal/50 bg-signal/10 shadow-xs" : "border-hairline bg-surface-2 hover:border-hairline-focus hover:bg-surface" }`} + style={staggerDelay(index)} >
diff --git a/services/frontend/src/app/globals.css b/services/frontend/src/app/globals.css index a31a7ddf..02f96f69 100644 --- a/services/frontend/src/app/globals.css +++ b/services/frontend/src/app/globals.css @@ -644,6 +644,13 @@ .animate-fade-up { animation: fade-up 0.18s cubic-bezier(0.16, 1, 0.3, 1); } .animate-toast-in { animation: toast-in 0.2s cubic-bezier(0.16, 1, 0.3, 1); } +/* Staggered list-item reveal — pair with inline `animationDelay` from + staggerDelay(i) in lib/utils.ts. Fades + slides up, forwards fill so + items stay put after animating. */ +.animate-stagger { + animation: fade-up 0.24s cubic-bezier(0.16, 1, 0.3, 1) both; +} + /* Sweeping glow line across a hero/placeholder panel. */ .scan-line { position: absolute; @@ -690,6 +697,7 @@ .animate-eq, .animate-fade-up, .animate-toast-in, + .animate-stagger, .scan-line, .ambient-blob, .ambient-mote, diff --git a/services/frontend/src/components/LiveModerationFeed.tsx b/services/frontend/src/components/LiveModerationFeed.tsx index e49f005e..206578a9 100644 --- a/services/frontend/src/components/LiveModerationFeed.tsx +++ b/services/frontend/src/components/LiveModerationFeed.tsx @@ -5,6 +5,7 @@ import { useEffect, useRef } from "react"; import { Badge } from "@/components/primitives"; import { formatRelativeTime } from "@/lib/format"; import type { ModerationAction } from "@/lib/types"; +import { staggerDelay } from "@/lib/utils"; const ACTION_LABEL: Record = { delete_message: "Deleted", @@ -106,12 +107,13 @@ export function LiveModerationFeed({ AWAITING MODERATION DISPATCH STREAM...
) : ( - actions.map((a) => { + actions.map((a, i) => { const tone = severityTone(a.severity); return (
{actionIcon(a.action_type)} diff --git a/services/frontend/src/components/primitives/toast.tsx b/services/frontend/src/components/primitives/toast.tsx index c7ed1403..d1598b0a 100644 --- a/services/frontend/src/components/primitives/toast.tsx +++ b/services/frontend/src/components/primitives/toast.tsx @@ -76,8 +76,10 @@ export function Toaster({ position = "bottom-right" }: { position?: string }) { key={t.id} className={cn( "glass animate-toast-in pointer-events-auto flex items-start gap-3 p-3.5 transition-colors", - t.tone === "signal" && "border-signal/30", - t.tone === "vermilion" && "border-vermilion/30", + t.tone === "signal" && "border-signal/40", + t.tone === "vermilion" && "border-vermilion/40", + t.tone === "signal" && + "shadow-[0_0_0_1px_var(--color-signal)/20]", )} >