diff --git a/apps/namadillo/package.json b/apps/namadillo/package.json index a95434df4..56d667981 100644 --- a/apps/namadillo/package.json +++ b/apps/namadillo/package.json @@ -15,6 +15,7 @@ "@tanstack/query-core": "^5.40.0", "@tanstack/react-query": "^5.40.0", "@tanstack/react-query-persist-client": "^5.40.0", + "animejs": "^3.2.2", "bignumber.js": "^9.1.1", "clsx": "^2.1.1", "comlink": "^4.4.1", @@ -91,6 +92,7 @@ "@testing-library/jest-dom": "^6.5.0", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", + "@types/animejs": "^3.1.12", "@types/invariant": "^2.2.37", "@types/jest": "^29.5.12", "@types/lodash.debounce": "^4.0.9", diff --git a/apps/namadillo/src/App/Common/Timeline.tsx b/apps/namadillo/src/App/Common/Timeline.tsx index e76e0b7d8..256956160 100644 --- a/apps/namadillo/src/App/Common/Timeline.tsx +++ b/apps/namadillo/src/App/Common/Timeline.tsx @@ -1,4 +1,8 @@ +import anime from "animejs"; import clsx from "clsx"; +import { useScope } from "hooks/useScope"; +import { useRef } from "react"; +import { FaCheckCircle } from "react-icons/fa"; import { twMerge } from "tailwind-merge"; export type TransactionStep = { bullet?: boolean; @@ -35,17 +39,17 @@ const StepBullet = ({ disabled }: DisabledProps): JSX.Element => ( const StepContent = ({ children, - isNextStep, + isCurrentStep, hasError, disabled, }: React.PropsWithChildren & { - isNextStep: boolean; + isCurrentStep: boolean; hasError: boolean; disabled: boolean; }): JSX.Element => (
@@ -59,8 +63,109 @@ export const Timeline = ({ hasError, complete, }: TransactionTimelineProps): JSX.Element => { + const containerRef = useRef(null); + + useScope( + (query, container) => { + if (!complete) return; + + const timeline = anime.timeline({ + easing: "easeOutExpo", + duration: 1000, + }); + + const containerRect = container.getBoundingClientRect(); + const items = Array.from(query("i,div")); + const hiding = items.slice(0, -1); + const lastItem = items.slice(-1)[0]; + const lastItemText = lastItem.querySelector("p"); + const rings = query('[data-animation="ring"]'); + const confirmation = query('[data-animation="confirmation"]'); + + const lastItemTextHeight = + lastItemText ? lastItemText.getBoundingClientRect().height : 0; + + const marginTop = 4; // ? + + const centerLastItemY = + -containerRect.height / 2 + + lastItem.getBoundingClientRect().height / 2 + + lastItemTextHeight / 2 + + marginTop; + + // Hide items, except last one + timeline.add({ + targets: hiding, + opacity: 0, + delay: anime.stagger(30), + }); + + // Move last item to the center of the screen + timeline.add( + { + targets: lastItem, + translateY: centerLastItemY, + }, + "-=700" + ); + + // Try to hide any existing text contained on the last item, as soon + // as the item goes up + timeline.add( + { + targets: lastItem.querySelector("p"), + opacity: 0, + duration: 400, + }, + "-=1000" + ); + + // Display concentric rings, one by one + timeline.add( + { + targets: rings, + opacity: [0, 1], + duration: 800, + scale: [0, 1], + delay: anime.stagger(100), + }, + "-=600" + ); + + // Sucks everything into the screen + timeline.add({ + targets: [...rings, lastItem], + scale: 0, + duration: 300, + }); + + // Displays success box confirmation + timeline.add({ + targets: confirmation, + duration: 1000, + scale: [0, 1], + opacity: [0, 1], + }); + }, + containerRef, + [complete] + ); + + const renderRing = (className: string): JSX.Element => ( + + ); + return ( -
+
    {steps.map((step, index: number) => { return ( @@ -80,11 +185,9 @@ export const Timeline = ({ currentStepIndex} /> )} currentStepIndex} - hasError={Boolean(hasError)} + hasError={!!hasError} > {step.children} @@ -92,6 +195,26 @@ export const Timeline = ({ ); })}
+ + {renderRing("h-30")} + {renderRing("h-60")} + {renderRing("h-96")} + + + +
); }; diff --git a/apps/namadillo/src/App/Sidebars/YourStakingDistribution.tsx b/apps/namadillo/src/App/Sidebars/YourStakingDistribution.tsx index 6faedf790..a075b1d95 100644 --- a/apps/namadillo/src/App/Sidebars/YourStakingDistribution.tsx +++ b/apps/namadillo/src/App/Sidebars/YourStakingDistribution.tsx @@ -1,8 +1,10 @@ import { PieChart, PieChartData } from "@namada/components"; import { shortenAddress } from "@namada/utils"; import BigNumber from "bignumber.js"; -import { AnimatePresence, motion } from "framer-motion"; +import clsx from "clsx"; +import { AnimatePresence } from "framer-motion"; import { useState } from "react"; +import { twMerge } from "tailwind-merge"; import { MyValidator } from "types"; type YourStakingDistributionProps = { @@ -62,29 +64,34 @@ export const YourStakingDistribution = ({ setDisplayedValidator(myValidators[index]); }} > -
+
- {displayedValidator === undefined && ( - - Your Stake Distribution - - )} - {displayedValidator && ( - - {displayedValidator.validator.alias} - - {getFormattedPercentage(displayedValidator)} - - - )} + + Your Stake Distribution + + + {displayedValidator?.validator.alias} + + {displayedValidator && + getFormattedPercentage(displayedValidator)} + +
diff --git a/apps/namadillo/src/App/Transactions/TransferTimelineErrorEntry.tsx b/apps/namadillo/src/App/Transactions/TransferTimelineErrorEntry.tsx index cdb564392..f20f871bc 100644 --- a/apps/namadillo/src/App/Transactions/TransferTimelineErrorEntry.tsx +++ b/apps/namadillo/src/App/Transactions/TransferTimelineErrorEntry.tsx @@ -11,10 +11,10 @@ export const TransferTimelineErrorEntry = ({ }: TransferTimelineErrorEntryProps): JSX.Element => { return ( <> - + -
{children}
+
{children}
{ const textSteps = [...allTransferStages[transaction.type]]; - const hasError = transaction.status === "error"; const isTransparentTransfer = transparentTransferTypes.includes( transaction.type @@ -137,9 +136,13 @@ export const TransferTransactionTimeline = ({ initialImage.length ); + const filteredSteps = textSteps.filter( + (step) => step !== TransferStep.WaitingConfirmation + ); + const stepsWithDescription = buildStepEntries( - textSteps.filter((step) => step !== TransferStep.WaitingConfirmation), - currentStepIndex, + filteredSteps, + currentStepIndex + filteredSteps.length - textSteps.length, hasError, transaction, !isTransparentTransfer diff --git a/apps/namadillo/src/hooks/useScope.tsx b/apps/namadillo/src/hooks/useScope.tsx new file mode 100644 index 000000000..a189e1283 --- /dev/null +++ b/apps/namadillo/src/hooks/useScope.tsx @@ -0,0 +1,22 @@ +import { RefObject, useEffect } from "react"; + +type QueryFnOutput = ReturnType; +type CallbackFn = (query: string) => QueryFnOutput; +type CallbackProps = (callback: CallbackFn, container: HTMLElement) => void; + +export const useScope = ( + callback: CallbackProps, + scope: RefObject, + dependencies: unknown[] = [] +): void => { + useEffect(() => { + const queryFn = (query: string): QueryFnOutput => { + if (!scope.current) + throw "You must pass a valid scope for useAnimation hook"; + return scope.current.querySelectorAll(query); + }; + + if (!scope.current) return; + callback(queryFn, scope.current); + }, [...dependencies]); +}; diff --git a/yarn.lock b/yarn.lock index cbe90c8c7..ecf5bc0ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3671,6 +3671,7 @@ __metadata: "@testing-library/jest-dom": "npm:^6.5.0" "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.5.2" + "@types/animejs": "npm:^3.1.12" "@types/invariant": "npm:^2.2.37" "@types/jest": "npm:^29.5.12" "@types/lodash.debounce": "npm:^4.0.9" @@ -3682,6 +3683,7 @@ __metadata: "@types/styled-components": "npm:^5.1.22" "@types/traverse": "npm:^0.6.36" "@vitejs/plugin-react": "npm:^4.3.1" + animejs: "npm:^3.2.2" autoprefixer: "npm:^10.4.16" axios: "npm:^1.7.9" bignumber.js: "npm:^9.1.1" @@ -4875,6 +4877,13 @@ __metadata: languageName: node linkType: hard +"@types/animejs@npm:^3.1.12": + version: 3.1.12 + resolution: "@types/animejs@npm:3.1.12" + checksum: 21d10ed4fbaa99572e252b4d6e666b62a19089f63b13b0e9f8edf5fb0b708afc2b38c3055c52f6a4771bc71793d7a7aec83754c16d0e5606a0ad92b34778ba61 + languageName: node + linkType: hard + "@types/aria-query@npm:^5.0.1": version: 5.0.4 resolution: "@types/aria-query@npm:5.0.4" @@ -6360,6 +6369,13 @@ __metadata: languageName: node linkType: hard +"animejs@npm:^3.2.2": + version: 3.2.2 + resolution: "animejs@npm:3.2.2" + checksum: f43dfcc0c743a2774e76fbfcb16a22350da7104f413d9d1b85c48128b0c078090642809deb631e21dfa0a40651111be39d9d7f694c9c1b70d8637ce8b6d63116 + languageName: node + linkType: hard + "ansi-align@npm:^3.0.1": version: 3.0.1 resolution: "ansi-align@npm:3.0.1"