From b5b0c9c0ec5729deb115c55ba64bc427ba2a433b Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 20 Dec 2025 18:08:45 +0000 Subject: [PATCH] animations test --- frontend/package-lock.json | 102 +++--- frontend/package.json | 1 + .../components/fileEditor/FileCardMorph.tsx | 30 ++ .../fileEditor/FileEditor.module.css | 17 + .../core/components/fileEditor/FileEditor.tsx | 58 ++- .../src/core/components/layout/Workbench.tsx | 75 +++- .../core/components/pageEditor/PageEditor.tsx | 1 + .../components/pageEditor/PageThumbnail.tsx | 32 +- .../core/components/shared/MorphOverlay.tsx | 196 ++++++++++ .../core/components/viewer/EmbedPdfViewer.tsx | 18 + .../components/viewer/ViewerMorphAnchor.tsx | 49 +++ .../components/viewer/ViewerPageMorphs.tsx | 155 ++++++++ .../contexts/WorkbenchTransitionContext.tsx | 344 ++++++++++++++++++ frontend/src/core/hooks/useMorphElement.ts | 99 +++++ .../core/hooks/useWorkbenchMorphTransition.ts | 30 ++ frontend/src/core/pages/HomePage.tsx | 9 +- 16 files changed, 1128 insertions(+), 88 deletions(-) create mode 100644 frontend/src/core/components/fileEditor/FileCardMorph.tsx create mode 100644 frontend/src/core/components/shared/MorphOverlay.tsx create mode 100644 frontend/src/core/components/viewer/ViewerMorphAnchor.tsx create mode 100644 frontend/src/core/components/viewer/ViewerPageMorphs.tsx create mode 100644 frontend/src/core/contexts/WorkbenchTransitionContext.tsx create mode 100644 frontend/src/core/hooks/useMorphElement.ts create mode 100644 frontend/src/core/hooks/useWorkbenchMorphTransition.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f788474bbb..1d148339f5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -51,6 +51,7 @@ "@tauri-apps/plugin-http": "^2.5.4", "autoprefixer": "^10.4.21", "axios": "^1.12.2", + "framer-motion": "^12.23.26", "globals": "^16.4.0", "i18next": "^25.5.2", "i18next-browser-languagedetector": "^8.2.0", @@ -457,7 +458,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -501,7 +501,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -582,7 +581,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz", "integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/engines": "1.5.0", "@embedpdf/models": "1.5.0" @@ -682,7 +680,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz", "integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -699,7 +696,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.5.0.tgz", "integrity": "sha512-ckHgTfvkW6c5Ta7Mc+Dl9C2foVnvEpqEJ84wyBnqrU0OWbe/jsiPhyKBVeartMGqNI/kVfaQTXupyrKhekAVmg==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -717,7 +713,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz", "integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -771,7 +766,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz", "integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -806,7 +800,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz", "integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -843,7 +836,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz", "integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -919,7 +911,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.5.0.tgz", "integrity": "sha512-G8GDyYRhfehw72+r4qKkydnA5+AU8qH67g01Y12b0DzI0VIzymh/05Z4dK8DsY3jyWPXJfw2hlg5+KDHaMBHgQ==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -1075,7 +1066,6 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -1119,7 +1109,6 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2150,7 +2139,6 @@ "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.6.tgz", "integrity": "sha512-paTl+0x+O/QtgMtqVJaG8maD8sfiOdgPmLOyG485FmeGZ1L3KMdEkhxZtmdGlDFsLXhmMGQ57ducT90bvhXX5A==", "license": "MIT", - "peer": true, "dependencies": { "@floating-ui/react": "^0.27.16", "clsx": "^2.1.1", @@ -2201,7 +2189,6 @@ "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.6.tgz", "integrity": "sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "^18.x || ^19.x" } @@ -2269,7 +2256,6 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz", "integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4", "@mui/core-downloads-tracker": "^7.3.5", @@ -3202,7 +3188,6 @@ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz", "integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.16" } @@ -3321,6 +3306,7 @@ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", "license": "MIT", + "peer": true, "peerDependencies": { "acorn": "^8.9.0" } @@ -4097,7 +4083,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4426,7 +4411,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4437,7 +4421,6 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4507,7 +4490,6 @@ "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -5221,6 +5203,7 @@ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz", "integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==", "license": "MIT", + "peer": true, "dependencies": { "@vue/shared": "3.5.24" } @@ -5230,6 +5213,7 @@ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz", "integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==", "license": "MIT", + "peer": true, "dependencies": { "@vue/reactivity": "3.5.24", "@vue/shared": "3.5.24" @@ -5240,6 +5224,7 @@ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz", "integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==", "license": "MIT", + "peer": true, "dependencies": { "@vue/reactivity": "3.5.24", "@vue/runtime-core": "3.5.24", @@ -5252,6 +5237,7 @@ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz", "integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-ssr": "3.5.24", "@vue/shared": "3.5.24" @@ -5278,7 +5264,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5686,6 +5671,7 @@ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">= 0.4" } @@ -5962,7 +5948,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -7010,8 +6995,7 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz", "integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/dezalgo": { "version": "1.0.4", @@ -7406,7 +7390,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7577,7 +7560,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7744,7 +7726,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/espree": { "version": "10.4.0", @@ -7809,6 +7792,7 @@ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz", "integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } @@ -8272,6 +8256,37 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.23.26", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz", + "integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "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 + } + } + }, + "node_modules/framer-motion/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/fs-extra": { "version": "11.3.2", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", @@ -8899,7 +8914,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.27.6" }, @@ -9376,6 +9390,7 @@ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.6" } @@ -9696,7 +9711,6 @@ "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.19", "@asamuzakjp/dom-selector": "^6.7.3", @@ -10283,7 +10297,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/locate-path": { "version": "6.0.0", @@ -10678,6 +10693,19 @@ "node": ">=18" } }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11442,7 +11470,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11722,7 +11749,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -12105,7 +12131,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12115,7 +12140,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13627,6 +13651,7 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">= 0.4" } @@ -13835,7 +13860,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -14137,7 +14161,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14219,7 +14242,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -14424,7 +14446,6 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -14595,7 +14616,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -14609,7 +14629,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -15221,7 +15240,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/zod": { "version": "3.25.76", diff --git a/frontend/package.json b/frontend/package.json index 914b7bb1f9..864015e8eb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,6 +47,7 @@ "@tauri-apps/plugin-http": "^2.5.4", "autoprefixer": "^10.4.21", "axios": "^1.12.2", + "framer-motion": "^12.23.26", "globals": "^16.4.0", "i18next": "^25.5.2", "i18next-browser-languagedetector": "^8.2.0", diff --git a/frontend/src/core/components/fileEditor/FileCardMorph.tsx b/frontend/src/core/components/fileEditor/FileCardMorph.tsx new file mode 100644 index 0000000000..de19f8bfeb --- /dev/null +++ b/frontend/src/core/components/fileEditor/FileCardMorph.tsx @@ -0,0 +1,30 @@ +import { useMorphElement } from '@app/hooks/useMorphElement'; + +interface FileCardMorphProps { + fileId: string; + children: React.ReactNode; + className?: string; +} + +/** + * Wrapper for file cards that registers them for morphing animations + */ +export function FileCardMorph({ fileId, children, className }: FileCardMorphProps) { + const morphRef = useMorphElement( + `file-${fileId}`, + { fileId, type: 'file' }, + { onlyIn: ['fileEditor'] } + ); + + return ( +
+ {children} +
+ ); +} diff --git a/frontend/src/core/components/fileEditor/FileEditor.module.css b/frontend/src/core/components/fileEditor/FileEditor.module.css index 4f26c8bce5..3418521a71 100644 --- a/frontend/src/core/components/fileEditor/FileEditor.module.css +++ b/frontend/src/core/components/fileEditor/FileEditor.module.css @@ -399,3 +399,20 @@ font-size: 0.875rem; opacity: 0.8; } + +/* ========================= + Grid Animation Styles + ========================= */ + +.gridContainer { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + row-gap: 1.5rem; + padding: 1rem; + pointer-events: auto; +} + +.fileCardItem { + /* Target for stagger animations */ + will-change: transform, opacity; +} diff --git a/frontend/src/core/components/fileEditor/FileEditor.tsx b/frontend/src/core/components/fileEditor/FileEditor.tsx index 76e6bb1a26..6d9664c1e7 100644 --- a/frontend/src/core/components/fileEditor/FileEditor.tsx +++ b/frontend/src/core/components/fileEditor/FileEditor.tsx @@ -5,16 +5,19 @@ import { import { Dropzone } from '@mantine/dropzone'; import { useFileSelection, useFileState, useFileManagement, useFileActions, useFileContext } from '@app/contexts/FileContext'; import { useNavigationActions } from '@app/contexts/NavigationContext'; +import { useMorphElement } from '@app/hooks/useMorphElement'; import { zipFileService } from '@app/services/zipFileService'; import { detectFileExtension } from '@app/utils/fileUtils'; import FileEditorThumbnail from '@app/components/fileEditor/FileEditorThumbnail'; import AddFileCard from '@app/components/fileEditor/AddFileCard'; import FilePickerModal from '@app/components/shared/FilePickerModal'; +import { FileCardMorph } from '@app/components/fileEditor/FileCardMorph'; import { FileId, StirlingFile } from '@app/types/fileContext'; import { alert } from '@app/components/toast'; import { downloadBlob } from '@app/utils/downloadUtils'; import { useFileEditorRightRailButtons } from '@app/components/fileEditor/fileEditorRightRailButtons'; import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext'; +import styles from '@app/components/fileEditor/FileEditor.module.css'; interface FileEditorProps { @@ -379,42 +382,37 @@ const FileEditor = ({ ) : ( -
+
{/* Add File Card - only show when files exist */} {activeStirlingFileStubs.length > 0 && ( - +
+ +
)} {activeStirlingFileStubs.map((record, index) => { return ( - + + + ); })}
diff --git a/frontend/src/core/components/layout/Workbench.tsx b/frontend/src/core/components/layout/Workbench.tsx index 0fa31dd244..229ecd3a6d 100644 --- a/frontend/src/core/components/layout/Workbench.tsx +++ b/frontend/src/core/components/layout/Workbench.tsx @@ -1,12 +1,16 @@ import { Box } from '@mantine/core'; +import { useState, useEffect } from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; import { useRainbowThemeContext } from '@app/components/shared/RainbowThemeProvider'; import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext'; import { useFileHandler } from '@app/hooks/useFileHandler'; import { useFileState } from '@app/contexts/FileContext'; import { useNavigationState, useNavigationActions } from '@app/contexts/NavigationContext'; -import { isBaseWorkbench } from '@app/types/workbench'; +import { isBaseWorkbench, type BaseWorkbenchType } from '@app/types/workbench'; import { useViewer } from '@app/contexts/ViewerContext'; import { useAppConfig } from '@app/contexts/AppConfigContext'; +import { useWorkbenchMorphTransition } from '@app/hooks/useWorkbenchMorphTransition'; +import { useWorkbenchTransition } from '@app/contexts/WorkbenchTransitionContext'; import styles from '@app/components/layout/Workbench.module.css'; import TopControls from '@app/components/shared/TopControls'; @@ -28,6 +32,14 @@ export default function Workbench() { const { workbench: currentView } = useNavigationState(); const { actions: navActions } = useNavigationActions(); const setCurrentView = navActions.setWorkbench; + + // Track the displayed workbench (for transitions) + const [displayedView, setDisplayedView] = useState(currentView); + const [isTransitioning, setIsTransitioning] = useState(false); + + // Workbench morphing transition hook + const { transition } = useWorkbenchMorphTransition(); + const { transitionState } = useWorkbenchTransition(); const activeFiles = selectors.getFiles(); const { previewFile, @@ -52,6 +64,33 @@ export default function Workbench() { // Get active file index from ViewerContext const { activeFileIndex, setActiveFileIndex } = useViewer(); + // Trigger morphing transition when workbench changes + useEffect(() => { + if (currentView === displayedView || isTransitioning) return; + + const performTransition = async () => { + if (isBaseWorkbench(currentView) && isBaseWorkbench(displayedView)) { + // Both are base workbenches - perform morphing transition + setIsTransitioning(true); + + await transition(displayedView as BaseWorkbenchType, currentView as BaseWorkbenchType, () => { + // Swap content after source snapshots are captured + setDisplayedView(currentView); + }); + + // Transition completes automatically after animation + setTimeout(() => { + setIsTransitioning(false); + }, 550); + } else { + // One is custom view - just swap without morph + setDisplayedView(currentView); + } + }; + + performTransition(); + }, [currentView, displayedView, isTransitioning, transition]); + const handlePreviewClose = () => { setPreviewFile(null); const previousMode = sessionStorage.getItem('previousMode'); @@ -71,12 +110,15 @@ export default function Workbench() { }; const renderMainContent = () => { + // Use displayedView for rendering (sync with transition) + const viewToRender = displayedView; + // Check for custom workbench views first - if (!isBaseWorkbench(currentView)) { - const customView = customWorkbenchViews.find((view) => view.workbenchId === currentView && view.data != null); + if (!isBaseWorkbench(viewToRender)) { + const customView = customWorkbenchViews.find((view) => view.workbenchId === viewToRender && view.data != null); if (customView) { // PDF text editor handles its own empty state (shows dropzone when no document) - const handlesOwnEmptyState = currentView === 'custom:pdfTextEditor'; + const handlesOwnEmptyState = viewToRender === 'custom:pdfTextEditor'; if (handlesOwnEmptyState || activeFiles.length > 0) { const CustomComponent = customView.component; return ; @@ -92,7 +134,7 @@ export default function Workbench() { ); } - switch (currentView) { + switch (viewToRender) { case "fileEditor": return ( @@ -189,15 +231,20 @@ export default function Workbench() { {/* Dismiss All Errors Button */} - {/* Main content area */} - - {renderMainContent()} - + {/* Main content area with workbench transitions */} + + + {renderMainContent()} + +