diff --git a/client/components/app-sidebar.tsx b/client/components/app-sidebar.tsx index b3eea17..10c9c3a 100644 --- a/client/components/app-sidebar.tsx +++ b/client/components/app-sidebar.tsx @@ -155,7 +155,7 @@ export function AppSidebar() {

John Doe

client@apax.institutional

- diff --git a/client/components/dashboard-layout.tsx b/client/components/dashboard-layout.tsx index 14d90b8..d550719 100644 --- a/client/components/dashboard-layout.tsx +++ b/client/components/dashboard-layout.tsx @@ -8,14 +8,19 @@ import { Bell, Search } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { useAPAXStore } from '@/lib/store' +import { AnimatePresence } from "framer-motion" +import { MotionWrapper } from "@/components/shared/MotionWrapper" +import { usePathname } from "next/navigation" interface DashboardLayoutProps { children: React.ReactNode } -export function DashboardLayout({ children }: DashboardLayoutProps) { - const { metalPrices } = useAPAXStore() - +export function DashboardLayout({ children}: DashboardLayoutProps) { + const { metalPrices, activeView } = useAPAXStore() + const pathname = usePathname() + //animation key now reacts to store changes automatically + const animationKey = activeView ? `view-${activeView}` : pathname return ( @@ -75,9 +80,13 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { {/* Main Content Area */} -
- {children} -
+
+ + + {children} + + +
) diff --git a/client/components/shared/MotionWrapper.tsx b/client/components/shared/MotionWrapper.tsx new file mode 100644 index 0000000..eba2545 --- /dev/null +++ b/client/components/shared/MotionWrapper.tsx @@ -0,0 +1,67 @@ +'use client'; + +import React from 'react'; +import { motion, HTMLMotionProps, Variants } from 'framer-motion'; + +interface MotionWrapperProps extends HTMLMotionProps<'div'> { + children: React.ReactNode; + delay?: number; + stagger?: boolean; // prop for orchestration +} + +const variants: Variants = { + initial: { opacity: 0, y: 20 }, + animate: (delay: number) => ({ + opacity: 1, + y: 0, + transition: { + delay, + duration: 0.4, + ease: [0.25, 0.1, 0.25, 1], + // if this is a container, stagger the children + staggerChildren: 0.05, + delayChildren: delay, + } + }), + exit: { + opacity: 0, + y: -20, + transition: { duration: 0.2 } + } +}; + +// item variant for children so they can inherit the stagger +export const childVariants: Variants = { + initial: { opacity: 0, y: 20 }, + animate: { + opacity: 1, + y: 0, + transition: { duration: 0.4, ease: [0.25, 0.1, 0.25, 1] } + } +}; + +export const MotionWrapper = ({ + children, + delay = 0, + stagger = false, + className, + ...props +}: MotionWrapperProps) => { + return ( + + {stagger + ? React.Children.map(children, (child) => ( + {child} + )) + : children} + + ); +}; \ No newline at end of file diff --git a/client/components/views/dashboard-view.tsx b/client/components/views/dashboard-view.tsx index 6804e19..c0ed4a3 100644 --- a/client/components/views/dashboard-view.tsx +++ b/client/components/views/dashboard-view.tsx @@ -7,7 +7,7 @@ import { ShariaCertificationHub } from '@/components/sharia-certification-hub' export function DashboardView() { return ( -
+
{/* Live Ledger Ticker */}
diff --git a/client/components/views/por-view.tsx b/client/components/views/por-view.tsx index 3eb4da0..27140c3 100644 --- a/client/components/views/por-view.tsx +++ b/client/components/views/por-view.tsx @@ -35,11 +35,11 @@ export function PorView() { } return ( -
+
{/* Page Header */}
-

+

Proof of Reserve Monitor

diff --git a/client/package.json b/client/package.json index 45b80e4..6f41a76 100644 --- a/client/package.json +++ b/client/package.json @@ -45,6 +45,7 @@ "cmdk": "1.0.4", "date-fns": "4.1.0", "embla-carousel-react": "8.5.1", + "framer-motion": "^12.31.0", "immer": "latest", "lucide-react": "^0.454.0", "next": "16.1.6", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index e97ac2f..5dedbb3 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -116,6 +116,9 @@ importers: embla-carousel-react: specifier: 8.5.1 version: 8.5.1(react@19.0.0) + framer-motion: + specifier: ^12.31.0 + version: 12.31.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) immer: specifier: latest version: 11.1.3 @@ -2036,6 +2039,20 @@ packages: fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + framer-motion@12.31.0: + resolution: {integrity: sha512-Tnd0FU05zGRFI3JJmBegXonF1rfuzYeuXd1QSdQ99Ysnppk0yWBWSW2wUsqzRpS5nv0zPNx+y0wtDj4kf0q5RQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -2415,6 +2432,12 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + motion-dom@12.30.1: + resolution: {integrity: sha512-QXB+iFJRzZTqL+Am4a1CRoHdH+0Nq12wLdqQQZZsfHlp9AMt6PA098L/61oVZsDA+Ep3QSGudzpViyRrhYhGcQ==} + + motion-utils@12.29.2: + resolution: {integrity: sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4918,6 +4941,15 @@ snapshots: fraction.js@5.3.4: {} + framer-motion@12.31.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + motion-dom: 12.30.1 + motion-utils: 12.29.2 + tslib: 2.8.1 + optionalDependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -5278,6 +5310,12 @@ snapshots: minimist@1.2.8: {} + motion-dom@12.30.1: + dependencies: + motion-utils: 12.29.2 + + motion-utils@12.29.2: {} + ms@2.1.3: {} nanoid@3.3.11: {}