From 5f22fd48d195140401393fd25f468c07c9ac692d Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Tue, 11 Jun 2024 17:24:30 +0530 Subject: [PATCH] feat: add integrations animation on the homepage Signed-off-by: Adithya Krishna --- apps/marketing/package.json | 2 +- .../(marketing)/integrations-data-flow.tsx | 83 +++++++++ .../share-connect-paid-widget-bento.tsx | 12 +- apps/web/package.json | 2 +- package-lock.json | 37 ++-- packages/tailwind-config/index.cjs | 4 +- .../components/animate/animated-data-flow.tsx | 168 ++++++++++++++++++ packages/ui/package.json | 3 +- 8 files changed, 288 insertions(+), 23 deletions(-) create mode 100644 apps/marketing/src/components/(marketing)/integrations-data-flow.tsx create mode 100644 packages/ui/components/animate/animated-data-flow.tsx diff --git a/apps/marketing/package.json b/apps/marketing/package.json index eb36b71bb..82c4c51ab 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -36,7 +36,7 @@ "react-confetti": "^6.1.0", "react-dom": "18.2.0", "react-hook-form": "^7.43.9", - "react-icons": "^4.11.0", + "react-icons": "^5.2.1", "recharts": "^2.7.2", "sharp": "^0.33.1", "typescript": "5.2.2", diff --git a/apps/marketing/src/components/(marketing)/integrations-data-flow.tsx b/apps/marketing/src/components/(marketing)/integrations-data-flow.tsx new file mode 100644 index 000000000..0fcad6e5c --- /dev/null +++ b/apps/marketing/src/components/(marketing)/integrations-data-flow.tsx @@ -0,0 +1,83 @@ +'use client'; + +import React, { forwardRef, useRef } from 'react'; + +import { FileText, Hexagon, Shapes } from 'lucide-react'; +import { LiaGoogleDrive } from 'react-icons/lia'; +import { PiMicrosoftTeamsLogo } from 'react-icons/pi'; +import { TbBrandAirtable, TbBrandZapier } from 'react-icons/tb'; + +import { AnimatedDataFlow } from '@documenso/ui/components/animate/animated-data-flow'; +import { cn } from '@documenso/ui/lib/utils'; + +// eslint-disable-next-line react/display-name +const Circle = forwardRef( + ({ className, children }, ref) => { + return ( +
+ {children} +
+ ); + }, +); + +export function DocumensoIntegrationsDataFlow() { + const containerRef = useRef(null); + const div1Ref = useRef(null); + const div2Ref = useRef(null); + const div3Ref = useRef(null); + const div4Ref = useRef(null); + const div5Ref = useRef(null); + const div6Ref = useRef(null); + const div7Ref = useRef(null); + + return ( +
+
+
+ + + + + + + + + +
+
+ + + +
+
+ + + + + + + + + +
+
+ + + + + + + +
+ ); +} diff --git a/apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx b/apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx index ad86b8a4a..7a8d3ba80 100644 --- a/apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx +++ b/apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx @@ -3,13 +3,14 @@ import type { HTMLAttributes } from 'react'; import Image from 'next/image'; import backgroundPattern from '@documenso/assets/images/background-pattern.png'; -import cardConnectionsFigure from '@documenso/assets/images/card-connections-figure.png'; import cardPaidFigure from '@documenso/assets/images/card-paid-figure.png'; import cardSharingFigure from '@documenso/assets/images/card-sharing-figure.png'; import cardWidgetFigure from '@documenso/assets/images/card-widget-figure.png'; import { cn } from '@documenso/ui/lib/utils'; import { Card, CardContent } from '@documenso/ui/primitives/card'; +import { DocumensoIntegrationsDataFlow } from './integrations-data-flow'; + export type ShareConnectPaidWidgetBentoProps = HTMLAttributes; export const ShareConnectPaidWidgetBento = ({ @@ -56,12 +57,9 @@ export const ShareConnectPaidWidgetBento = ({ favorite tools.

-
- its fast +
+ {/* Add Animated Beam */} +
diff --git a/apps/web/package.json b/apps/web/package.json index 71eea5555..dda223871 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -46,7 +46,7 @@ "react-dropzone": "^14.2.3", "react-hook-form": "^7.43.9", "react-hotkeys-hook": "^4.4.1", - "react-icons": "^4.11.0", + "react-icons": "^5.2.1", "react-rnd": "^10.4.1", "remeda": "^1.27.1", "sharp": "^0.33.1", diff --git a/package-lock.json b/package-lock.json index ca9ebe172..20e3a9edc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,7 @@ "react-confetti": "^6.1.0", "react-dom": "18.2.0", "react-hook-form": "^7.43.9", - "react-icons": "^4.11.0", + "react-icons": "^5.2.1", "recharts": "^2.7.2", "sharp": "^0.33.1", "typescript": "5.2.2", @@ -78,6 +78,14 @@ "integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==", "dev": true }, + "apps/marketing/node_modules/react-icons": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", + "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", + "peerDependencies": { + "react": "*" + } + }, "apps/marketing/node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -127,7 +135,7 @@ "react-dropzone": "^14.2.3", "react-hook-form": "^7.43.9", "react-hotkeys-hook": "^4.4.1", - "react-icons": "^4.11.0", + "react-icons": "^5.2.1", "react-rnd": "^10.4.1", "remeda": "^1.27.1", "sharp": "^0.33.1", @@ -155,6 +163,14 @@ "integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==", "dev": true }, + "apps/web/node_modules/react-icons": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", + "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", + "peerDependencies": { + "react": "*" + } + }, "apps/web/node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -5836,6 +5852,14 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", + "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", @@ -20538,14 +20562,6 @@ "react-dom": ">=16.8.1" } }, - "node_modules/react-icons": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", - "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", - "peerDependencies": { - "react": "*" - } - }, "node_modules/react-immutable-proptypes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", @@ -27599,6 +27615,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-hover-card": "^1.0.5", + "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.1", "@radix-ui/react-menubar": "^1.0.2", "@radix-ui/react-navigation-menu": "^1.1.2", diff --git a/packages/tailwind-config/index.cjs b/packages/tailwind-config/index.cjs index ae36f7fcf..22faa9899 100644 --- a/packages/tailwind-config/index.cjs +++ b/packages/tailwind-config/index.cjs @@ -7,9 +7,6 @@ module.exports = { content: ['src/**/*.{ts,tsx}'], theme: { extend: { - screens: { - print: { raw: 'print' }, - }, fontFamily: { sans: ['var(--font-sans)', ...fontFamily.sans], signature: ['var(--font-signature)'], @@ -138,6 +135,7 @@ module.exports = { '3xl': '1920px', '4xl': '2560px', '5xl': '3840px', + print: { raw: 'print' }, }, }, }, diff --git a/packages/ui/components/animate/animated-data-flow.tsx b/packages/ui/components/animate/animated-data-flow.tsx new file mode 100644 index 000000000..97264d838 --- /dev/null +++ b/packages/ui/components/animate/animated-data-flow.tsx @@ -0,0 +1,168 @@ +'use client'; + +import { type RefObject, useEffect, useId, useState } from 'react'; + +import { motion } from 'framer-motion'; + +import { cn } from '../../lib/utils'; + +export interface AnimatedDataFlowProps { + className?: string; + containerRef: RefObject; + fromRef: RefObject; + toRef: RefObject; + curvature?: number; + reverse?: boolean; + pathColor?: string; + pathWidth?: number; + pathOpacity?: number; + gradientStartColor?: string; + gradientStopColor?: string; + delay?: number; + duration?: number; + startXOffset?: number; + startYOffset?: number; + endXOffset?: number; + endYOffset?: number; +} + +export const AnimatedDataFlow: React.FC = ({ + className, + containerRef, + fromRef, + toRef, + curvature = 0, + reverse = false, // Include the reverse prop + duration = 5, + delay = 0, + pathColor = 'gray', + pathWidth = 2, + pathOpacity = 0.2, + gradientStartColor = '#A2E771', + gradientStopColor = '#1F5200', + startXOffset = 0, + startYOffset = 0, + endXOffset = 0, + endYOffset = 0, +}) => { + const id = useId(); + const [pathD, setPathD] = useState(''); + const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 }); + + // Calculate the gradient coordinates based on the reverse prop + const gradientCoordinates = reverse + ? { + x1: ['90%', '-10%'], + x2: ['100%', '0%'], + y1: ['0%', '0%'], + y2: ['0%', '0%'], + } + : { + x1: ['10%', '110%'], + x2: ['0%', '100%'], + y1: ['0%', '0%'], + y2: ['0%', '0%'], + }; + + useEffect(() => { + const updatePath = () => { + if (containerRef.current && fromRef.current && toRef.current) { + const containerRect = containerRef.current.getBoundingClientRect(); + const rectA = fromRef.current.getBoundingClientRect(); + const rectB = toRef.current.getBoundingClientRect(); + + const svgWidth = containerRect.width; + const svgHeight = containerRect.height; + setSvgDimensions({ width: svgWidth, height: svgHeight }); + + const startX = rectA.left - containerRect.left + rectA.width / 2 + startXOffset; + const startY = rectA.top - containerRect.top + rectA.height / 2 + startYOffset; + const endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset; + const endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset; + + const controlY = startY - curvature; + const d = `M ${startX},${startY} Q ${(startX + endX) / 2},${controlY} ${endX},${endY}`; + setPathD(d); + } + }; + + // Initialize ResizeObserver + const resizeObserver = new ResizeObserver((entries) => { + // For all entries, recalculate the path + // eslint-disable-next-line unused-imports/no-unused-vars + for (const entry of entries) { + updatePath(); + } + }); + + // Observe the container element + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + // Call the updatePath initially to set the initial path + updatePath(); + + // Clean up the observer on component unmount + return () => { + resizeObserver.disconnect(); + }; + }, [containerRef, fromRef, toRef, curvature, startXOffset, startYOffset, endXOffset, endYOffset]); + + return ( + + + + + + + + + + + + + ); +}; diff --git a/packages/ui/package.json b/packages/ui/package.json index 964cd37d2..f1a244a88 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -38,6 +38,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-hover-card": "^1.0.5", + "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.1", "@radix-ui/react-menubar": "^1.0.2", "@radix-ui/react-navigation-menu": "^1.1.2", @@ -76,4 +77,4 @@ "ts-pattern": "^5.0.5", "zod": "^3.22.4" } -} \ No newline at end of file +}