feat(frontend): staggered list reveals + toast tone accents (level 5 polish)

This commit is contained in:
asepharyana
2026-09-20 14:57:18 +07:00
parent ae3b22df93
commit d7aa034661
6 changed files with 37 additions and 9 deletions
@@ -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 (
<div
key={ch.channel_id}
className="flex items-center gap-3 text-xs"
className="animate-stagger flex items-center gap-3 text-xs"
style={staggerDelay(i)}
>
<span className="font-mono w-4 text-[10px] text-ink-faint">
{String(i + 1).padStart(2, "0")}
@@ -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) => (
<div
key={r.message_id ?? i}
className="hud-card flex items-start gap-3 p-3"
className="hud-card animate-stagger flex items-start gap-3 p-3"
style={staggerDelay(i)}
>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
@@ -473,7 +475,7 @@ export function MessagesView({
>
<div ref={streamRef} className="space-y-1.5">
{viewMode === "timeline" && timelineNodes
? timelineNodes.map((node) =>
? timelineNodes.map((node, i) =>
node.type === "date" ? (
<div
key={`date-${node.iso}`}
@@ -486,15 +488,17 @@ export function MessagesView({
<MessageRow
key={node.m.id}
m={node.m}
index={i}
selected={selected}
onSelect={setSelected}
/>
),
)
: display.map((m) => (
: display.map((m, i) => (
<MessageRow
key={m.id}
m={m}
index={i}
selected={selected}
onSelect={setSelected}
/>
@@ -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)}
>
<Avatar src={m.avatar_url} name={m.server_nick ?? m.username} size={32} />
<div className="min-w-0 flex-1">
+8
View File
@@ -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,