mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-09-29 16:24:22 +10:00
Audit every animation in the app and shared UI primitives against a frequency-first motion bar: keyboard and high-frequency actions no longer animate, remaining motion uses interruptible CSS transitions with shared easing tokens, and redundant animation code is removed. UI primitives (@reactive-resume/ui) - Dialog, alert dialog, popover and tooltip move from tw-animate keyframes to Base UI data-starting/ending-style transitions; menus, popovers and tooltips skip motion when opened from the keyboard (data-instant). - Dialog gains an `instant` prop; the command palette uses it. - Accordion animates its real panel height; caret rotates instead of swapping icons. - Menu backdrop blur moves onto the popup so it no longer snaps in after the fade; context menus and comboboxes fade only. - Sidebar collapse uses the strong ease-out curve and snaps on Cmd+B. - Toast, sheet, checkbox, tabs, toggle, inputs and message scroller get tokenised easing, correct transition properties and press feedback. - Tabs no longer squeeze a trigger narrower than its label. - Spinners keep spinning under prefers-reduced-motion. Web app - Remove the default route view transition and page-entrance slides. - Add EASE_OUT_STRONG for Motion; replace built-in "easeOut" everywhere. - Switch LazyMotion to domMax so layout and Reorder animations run. - Builder: consolidate 12 section list files into one ItemsSection, opacity-only list rows with popLayout, instant Cmd+0, faster dock zoom, crossfade that no longer dips, transform-based progress bars. - Dashboard: keep previous results while sorting/filtering, no empty-state flash, uncontrolled sidebar (no network round trip on collapse), calmer resume card tilt, no stacked hover wrappers. - Settings: drop entrance/stagger wrappers and ActionButton. - Agent: CSS marquee paused on hover; thread switches keep the layout. - Homepage: fix invalid transition declarations, CSS spotlight drift, scroll-hiding header without a JS spring, tokenised curves. - Theme switches change every color at once. - Remove SSR-only useIsClient guards from the SPA. Docs: rewrite the DESIGN.md animation section around the new tokens.
111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
import type { Icon } from "@phosphor-icons/react";
|
|
import type { ReactNode } from "react";
|
|
import { t } from "@lingui/core/macro";
|
|
import { Trans } from "@lingui/react/macro";
|
|
import { EyeIcon, NotePencilIcon, PaletteIcon } from "@phosphor-icons/react";
|
|
import { Outlet } from "@tanstack/react-router";
|
|
import { useEffect, useState } from "react";
|
|
import { cn } from "@reactive-resume/utils/style";
|
|
import { usePreviewPausedStore } from "@/features/resume/builder/draft";
|
|
import { BuilderSidebarLeft } from "../-sidebar/left";
|
|
import { BuilderSidebarRight } from "../-sidebar/right";
|
|
import { BuilderHeader } from "./header";
|
|
|
|
type MobileBuilderTab = "edit" | "preview" | "design";
|
|
|
|
const MOBILE_BUILDER_TABS = [
|
|
{ value: "edit", icon: NotePencilIcon },
|
|
{ value: "preview", icon: EyeIcon },
|
|
{ value: "design", icon: PaletteIcon },
|
|
] as const satisfies readonly { value: MobileBuilderTab; icon: Icon }[];
|
|
|
|
type MobileSidebarPanelProps = {
|
|
children: ReactNode;
|
|
};
|
|
|
|
function MobileSidebarPanel({ children }: MobileSidebarPanelProps) {
|
|
// Sits below the header (top-14) and above the tab bar (bottom-16). The sidebar's ScrollArea hardcodes
|
|
// `h-[calc(100svh-3.5rem)]`; override it to fill this panel so its last item isn't hidden under the tab bar.
|
|
return (
|
|
<div className="fixed inset-x-0 top-14 bottom-16 z-40 bg-background [&_[data-slot=scroll-area]]:h-full">
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
type MobileBuilderTabBarProps = {
|
|
activeTab: MobileBuilderTab;
|
|
onTabChange: (tab: MobileBuilderTab) => void;
|
|
};
|
|
|
|
function MobileBuilderTabBar({ activeTab, onTabChange }: MobileBuilderTabBarProps) {
|
|
const labels: Record<MobileBuilderTab, string> = { edit: t`Edit`, preview: t`Preview`, design: t`Design` };
|
|
|
|
return (
|
|
<nav className="fixed inset-x-0 bottom-0 z-50 flex border-t bg-popover pb-[env(safe-area-inset-bottom)]">
|
|
{MOBILE_BUILDER_TABS.map(({ value, icon: Icon }) => {
|
|
const isActive = value === activeTab;
|
|
|
|
return (
|
|
<button
|
|
key={value}
|
|
type="button"
|
|
aria-current={isActive ? "page" : undefined}
|
|
onClick={() => onTabChange(value)}
|
|
className={cn(
|
|
"flex min-h-16 flex-1 flex-col items-center justify-center gap-1 text-xs transition-[color,scale] duration-150 ease-out-strong active:scale-[0.97]",
|
|
isActive ? "text-primary" : "text-muted-foreground hover:text-foreground",
|
|
)}
|
|
>
|
|
<Icon className="size-5" weight={isActive ? "fill" : "regular"} />
|
|
<span>{labels[value]}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
export function MobileBuilderShell() {
|
|
// Local state is enough — mobile view mode is shell-scoped and doesn't need to persist.
|
|
const [tab, setTab] = useState<MobileBuilderTab>("edit");
|
|
const setPreviewPaused = usePreviewPausedStore((state) => state.setPaused);
|
|
|
|
// The preview stays mounted under the Edit/Design overlay; pause its render while it's covered.
|
|
useEffect(() => {
|
|
setPreviewPaused(tab !== "preview");
|
|
return () => setPreviewPaused(false);
|
|
}, [tab, setPreviewPaused]);
|
|
|
|
return (
|
|
<div className="flex min-h-[100dvh] flex-col">
|
|
<a
|
|
href="#main-content"
|
|
className="sr-only rounded-md bg-popover px-4 py-2 text-sm ring-2 ring-ring focus:not-sr-only focus:absolute focus:inset-s-2 focus:top-2 focus:z-[100]"
|
|
>
|
|
<Trans>Skip to main content</Trans>
|
|
</a>
|
|
|
|
<BuilderHeader />
|
|
|
|
{/* The preview (fixed inset-0) stays mounted so zoom/pan state survives tab switches. */}
|
|
<main id="main-content" className="flex-1">
|
|
<Outlet />
|
|
</main>
|
|
|
|
{tab === "edit" && (
|
|
<MobileSidebarPanel>
|
|
<BuilderSidebarLeft />
|
|
</MobileSidebarPanel>
|
|
)}
|
|
{tab === "design" && (
|
|
<MobileSidebarPanel>
|
|
<BuilderSidebarRight />
|
|
</MobileSidebarPanel>
|
|
)}
|
|
|
|
<MobileBuilderTabBar activeTab={tab} onTabChange={setTab} />
|
|
</div>
|
|
);
|
|
}
|