From 217ecc1aa1eb2fbbc0b4e2837eef77ab3c9c1772 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Tue, 18 Aug 2026 11:01:00 +0700 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20content=20micro-animations=20?= =?UTF-8?q?=E2=80=94=20stagger,=20button=20press,=20toast=20polish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - animate-stagger utility + staggerDelay() helper; lists now rise in sequence (recordings grid, moderation rows, message list, media queue, dashboard tiles). - Button gets a subtle active:scale-[0.97] press feedback. - Toaster: toast-in slide-up, tone-accent border, rounded hover-close target. - All motion is reduced-motion aware (killed under prefers-reduced-motion). --- .../src/app/(dashboard)/dashboard/view.tsx | 9 ++++++++ .../src/app/(dashboard)/media/view.tsx | 4 +++- .../src/app/(dashboard)/messages/view.tsx | 6 +++-- .../src/app/(dashboard)/moderation/view.tsx | 12 ++++++---- .../src/app/(dashboard)/recordings/view.tsx | 6 +++-- services/frontend/src/app/globals.css | 23 ++++++++++++++++++- .../src/components/primitives/button.tsx | 2 +- .../src/components/primitives/toast.tsx | 10 +++++--- .../src/components/shared/section.tsx | 3 +++ services/frontend/src/lib/utils.ts | 13 +++++++++++ 10 files changed, 74 insertions(+), 14 deletions(-) diff --git a/services/frontend/src/app/(dashboard)/dashboard/view.tsx b/services/frontend/src/app/(dashboard)/dashboard/view.tsx index ef060b9..5e870ee 100644 --- a/services/frontend/src/app/(dashboard)/dashboard/view.tsx +++ b/services/frontend/src/app/(dashboard)/dashboard/view.tsx @@ -29,6 +29,7 @@ import { } from "@/hooks"; import { formatNumber } from "@/lib/format"; import type { DashboardStats } from "@/lib/types"; +import { staggerDelay } from "@/lib/utils"; function deriveSignal(stats?: DashboardStats) { if (!stats) return { tone: "signal" as const, label: "nominal" }; @@ -113,23 +114,31 @@ export function DashboardView({ value={formatNumber(s.total_messages)} tone="signal" icon={} + className="animate-stagger" + style={staggerDelay(0)} /> 0 ? "vermilion" : "neutral"} hint={`${s.today_flagged} today`} + className="animate-stagger" + style={staggerDelay(1)} /> } + className="animate-stagger" + style={staggerDelay(2)} /> } + className="animate-stagger" + style={staggerDelay(3)} /> diff --git a/services/frontend/src/app/(dashboard)/media/view.tsx b/services/frontend/src/app/(dashboard)/media/view.tsx index d413ee6..3a19cb2 100644 --- a/services/frontend/src/app/(dashboard)/media/view.tsx +++ b/services/frontend/src/app/(dashboard)/media/view.tsx @@ -28,6 +28,7 @@ import { } from "@/hooks"; import { formatDuration } from "@/lib/format"; import type { MediaState } from "@/lib/types"; +import { staggerDelay } from "@/lib/utils"; import { useWebSocket } from "@/lib/ws/context"; export function MediaView({ initialStatus }: { initialStatus?: MediaState }) { @@ -205,7 +206,8 @@ export function MediaView({ initialStatus }: { initialStatus?: MediaState }) { {queueList.map((item, i) => (
{i + 1}
diff --git a/services/frontend/src/app/(dashboard)/messages/view.tsx b/services/frontend/src/app/(dashboard)/messages/view.tsx index 1105ab4..fa8b850 100644 --- a/services/frontend/src/app/(dashboard)/messages/view.tsx +++ b/services/frontend/src/app/(dashboard)/messages/view.tsx @@ -45,6 +45,7 @@ import { safeParseJsonArray, } from "@/lib/format"; import type { AiStatus, Guild, MessageRecord } from "@/lib/types"; +import { staggerDelay } from "@/lib/utils"; import { useWebSocket } from "@/lib/ws/context"; export function MessagesView({ @@ -254,16 +255,17 @@ export function MessagesView({ } }} > - {display.map((m) => ( + {display.map((m, i) => ( diff --git a/services/frontend/src/components/shared/section.tsx b/services/frontend/src/components/shared/section.tsx index f5e7e74..80331f9 100644 --- a/services/frontend/src/components/shared/section.tsx +++ b/services/frontend/src/components/shared/section.tsx @@ -37,6 +37,7 @@ export function MetricTile({ spark, icon, className, + style, }: { label: string; value: React.ReactNode; @@ -45,6 +46,7 @@ export function MetricTile({ spark?: number[]; icon?: React.ReactNode; className?: string; + style?: React.CSSProperties; }) { const toneColor = tone === "vermilion" @@ -60,6 +62,7 @@ export function MetricTile({ "glass p-4 ring-focus transition-transform duration-200 hover:-translate-y-0.5", className, )} + style={style} >
{label}
diff --git a/services/frontend/src/lib/utils.ts b/services/frontend/src/lib/utils.ts index 365058c..183cf42 100644 --- a/services/frontend/src/lib/utils.ts +++ b/services/frontend/src/lib/utils.ts @@ -4,3 +4,16 @@ import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +/** + * Returns an inline style that staggers a list item's entrance animation. + * Pair with the `animate-stagger` class. Caps the delay so long lists still + * appear promptly. + */ +export function staggerDelay( + index: number, + step = 45, + max = 600, +): React.CSSProperties { + return { animationDelay: `${Math.min(index * step, max)}ms` }; +}