feat(frontend): micro-interactions — page-enter transition + MetricTile lift
- Add animate-fade-up utility (reduced-motion aware) and a PageTransition wrapper; every dashboard view now rises + settles on mount/route change. - MetricTile gains a subtle hover lift (-translate-y) + ring-focus glow. - Theme toggle and command palette (⌘K) already existed and persist; no-op.
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { PageTransition } from "@/components/shared";
|
||||
import { AnalysisView } from "./view";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default function AnalysisPage() {
|
||||
return <AnalysisView />;
|
||||
return (
|
||||
<PageTransition>
|
||||
<AnalysisView />
|
||||
</PageTransition>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PageTransition } from "@/components/shared";
|
||||
import { getActivity, getDashboardStats } from "@/lib/api/server";
|
||||
import { DashboardView } from "./view";
|
||||
|
||||
@@ -14,5 +15,9 @@ export default async function DashboardPage() {
|
||||
} catch {
|
||||
// Backend unavailable — client hooks will surface the error state.
|
||||
}
|
||||
return <DashboardView initialStats={stats} initialActivity={activity} />;
|
||||
return (
|
||||
<PageTransition>
|
||||
<DashboardView initialStats={stats} initialActivity={activity} />
|
||||
</PageTransition>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PageTransition } from "@/components/shared";
|
||||
import { getMediaStatus } from "@/lib/api/server";
|
||||
import { MediaView } from "./view";
|
||||
|
||||
@@ -10,5 +11,9 @@ export default async function MediaPage() {
|
||||
} catch {
|
||||
/* client hooks surface errors */
|
||||
}
|
||||
return <MediaView initialStatus={status} />;
|
||||
return (
|
||||
<PageTransition>
|
||||
<MediaView initialStatus={status} />
|
||||
</PageTransition>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PageTransition } from "@/components/shared";
|
||||
import { getConfig, getGuilds } from "@/lib/api/server";
|
||||
import { MessagesView } from "./view";
|
||||
|
||||
@@ -12,9 +13,11 @@ export default async function MessagesPage() {
|
||||
/* client hooks surface errors */
|
||||
}
|
||||
return (
|
||||
<MessagesView
|
||||
initialGuilds={guilds}
|
||||
initialGuildId={config?.monitorGuildId ?? null}
|
||||
/>
|
||||
<PageTransition>
|
||||
<MessagesView
|
||||
initialGuilds={guilds}
|
||||
initialGuildId={config?.monitorGuildId ?? null}
|
||||
/>
|
||||
</PageTransition>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PageTransition } from "@/components/shared";
|
||||
import { getModerationActions, getModerationStats } from "@/lib/api/server";
|
||||
import { ModerationView } from "./view";
|
||||
|
||||
@@ -14,5 +15,9 @@ export default async function ModerationPage() {
|
||||
} catch {
|
||||
/* client hooks surface errors */
|
||||
}
|
||||
return <ModerationView initialStats={stats} initialActions={actions} />;
|
||||
return (
|
||||
<PageTransition>
|
||||
<ModerationView initialStats={stats} initialActions={actions} />
|
||||
</PageTransition>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PageTransition } from "@/components/shared";
|
||||
import { getRecordings } from "@/lib/api/server";
|
||||
import { RecordingsView } from "./view";
|
||||
|
||||
@@ -12,5 +13,9 @@ export default async function RecordingsPage() {
|
||||
} catch {
|
||||
/* client hooks surface errors */
|
||||
}
|
||||
return <RecordingsView initialItems={recordings?.items} />;
|
||||
return (
|
||||
<PageTransition>
|
||||
<RecordingsView initialItems={recordings?.items} />
|
||||
</PageTransition>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PageTransition } from "@/components/shared";
|
||||
import { getGuilds, getVoiceStatus } from "@/lib/api/server";
|
||||
import { VoiceView } from "./view";
|
||||
|
||||
@@ -11,5 +12,9 @@ export default async function VoicePage() {
|
||||
} catch {
|
||||
/* client hooks surface errors */
|
||||
}
|
||||
return <VoiceView initialStatus={status} initialGuilds={guilds} />;
|
||||
return (
|
||||
<PageTransition>
|
||||
<VoiceView initialStatus={status} initialGuilds={guilds} />
|
||||
</PageTransition>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -276,6 +276,11 @@
|
||||
}
|
||||
.animate-breathe { animation: breathe 3.5s ease-in-out infinite; }
|
||||
|
||||
/* Page-enter: subtle rise+settle. Respect reduced-motion (handled globally). */
|
||||
.animate-fade-up {
|
||||
animation: fade-up 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.scan-line::after,
|
||||
.scan-line,
|
||||
@@ -283,7 +288,8 @@
|
||||
.animate-spin-disc,
|
||||
.animate-pulse-ring,
|
||||
.animate-shimmer,
|
||||
.animate-breathe {
|
||||
.animate-breathe,
|
||||
.animate-fade-up {
|
||||
animation: none !important;
|
||||
}
|
||||
.scan-line {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { GuildChannelPicker } from "./guild-picker";
|
||||
export { MarkdownLite } from "./markdown";
|
||||
export { PageTransition } from "./page-transition";
|
||||
export { MetricTile, SectionHeader } from "./section";
|
||||
export {
|
||||
EmptyState,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* Wraps a view's root so the whole panel stack rises + settles on mount.
|
||||
* The animation is disabled under prefers-reduced-motion via globals.css.
|
||||
*/
|
||||
export function PageTransition({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return <div className={cn("animate-fade-up", className)}>{children}</div>;
|
||||
}
|
||||
@@ -55,7 +55,12 @@ export function MetricTile({
|
||||
? "var(--color-signal)"
|
||||
: "var(--color-ink)";
|
||||
return (
|
||||
<div className={cn("glass p-4", className)}>
|
||||
<div
|
||||
className={cn(
|
||||
"glass p-4 ring-focus transition-transform duration-200 hover:-translate-y-0.5",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="eyebrow">{label}</div>
|
||||
{icon && <span className="text-ink-faint">{icon}</span>}
|
||||
|
||||
Reference in New Issue
Block a user