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.
-
-
+
+ {/* 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
+}