From f4f524a16e075e8d52948ec67e22d9d16e933b8f Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Fri, 1 Nov 2024 23:16:02 -0600 Subject: [PATCH] feat: add landing page finished --- apps/website/app/[locale]/page.tsx | 3 +- apps/website/components/CallToAction.tsx | 2 +- apps/website/components/Header.tsx | 18 +- apps/website/components/Hero.tsx | 282 +++++++++--------- apps/website/components/sponsors.tsx | 40 ++- apps/website/components/stats.tsx | 172 +++++++++++ .../components/ui/animated-grid-pattern.tsx | 150 ++++++++++ apps/website/components/ui/number-ticker.tsx | 58 ++++ apps/website/components/ui/ripple.tsx | 264 +++++++++++++--- apps/website/components/ui/tooltip.tsx | 30 ++ apps/website/package.json | 1 + apps/website/public/supafort.png | Bin 0 -> 5375 bytes pnpm-lock.yaml | 85 ++++++ 13 files changed, 915 insertions(+), 190 deletions(-) create mode 100644 apps/website/components/stats.tsx create mode 100644 apps/website/components/ui/animated-grid-pattern.tsx create mode 100644 apps/website/components/ui/number-ticker.tsx create mode 100644 apps/website/components/ui/tooltip.tsx create mode 100644 apps/website/public/supafort.png diff --git a/apps/website/app/[locale]/page.tsx b/apps/website/app/[locale]/page.tsx index c3b5ce1..3faac8b 100644 --- a/apps/website/app/[locale]/page.tsx +++ b/apps/website/app/[locale]/page.tsx @@ -6,6 +6,7 @@ import { Testimonials } from "@/components/Testimonials"; import { FeaturesSectionDemo } from "@/components/features"; import { Pricing } from "@/components/pricing"; import { RippleDemo } from "@/components/sponsors"; +import { StatsSection } from "@/components/stats"; export default function Home() { return ( @@ -14,12 +15,12 @@ export default function Home() { +
- diff --git a/apps/website/components/CallToAction.tsx b/apps/website/components/CallToAction.tsx index 8c1d797..4f95f63 100644 --- a/apps/website/components/CallToAction.tsx +++ b/apps/website/components/CallToAction.tsx @@ -9,7 +9,7 @@ export function CallToAction() { return (
{t("navigation.docs")} + +
+ {t("navigation.dashboard")} + +
+ + + @@ -152,7 +166,7 @@ export function Header() { -
+
*/} -
*/} +
+
+
+ +
+ + 🎉
{" "} + -
-
-
- -
+ + + + {t("hero.deploy")}{" "} + + + {t("hero.anywhere")} + {" "} + {t("hero.with")} + + + {t("hero.des")} + + +
+
+ + curl -sSL https://dokploy.com/install.sh | sh + + {isCopied ? ( + + ) : ( + + )} + + +
+
+ + +
+
+
+ +
+
- -
- -
-
+
- + + {/* */} +
); } diff --git a/apps/website/components/sponsors.tsx b/apps/website/components/sponsors.tsx index 2d83582..8f2e7aa 100644 --- a/apps/website/components/sponsors.tsx +++ b/apps/website/components/sponsors.tsx @@ -1,12 +1,20 @@ +"use client"; import { PlusCircleIcon } from "lucide-react"; import { useTranslations } from "next-intl"; -import { Button } from "./ui/button"; +import Link from "next/link"; +import { buttonVariants } from "./ui/button"; import Ripple from "./ui/ripple"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "./ui/tooltip"; export const RippleDemo = () => { const t = useTranslations("HomePage"); return ( -
+

{t("hero.sponsors.title")} @@ -15,13 +23,27 @@ export const RippleDemo = () => { {t("hero.sponsors.description")}

-
-

- - {/* */} -

+
+ + + + + + + + + Become a sponsor 🤑 + + +
diff --git a/apps/website/components/stats.tsx b/apps/website/components/stats.tsx new file mode 100644 index 0000000..e3a6f2f --- /dev/null +++ b/apps/website/components/stats.tsx @@ -0,0 +1,172 @@ +import { GithubIcon, HandCoins, Users } from "lucide-react"; +import React from "react"; +import { useId } from "react"; +import NumberTicker from "./ui/number-ticker"; + +export function StatsSection() { + return ( +
+
+

+ Stats You Didn’t Ask For (But Secretly Love to See) +

+

+ Just a few numbers to show we’re not *completely* making this up. + Turns out, Dokploy has actually helped a few people—who knew? +

+
+
+ {grid.map((feature, index) => ( +
+ + +

+ {feature.title} + {feature.icon} +

+

+ {feature.description} +

+ {feature.component} +
+ ))} +
+
+ ); +} + +const grid = [ + { + title: "GitHub Stars", + description: + "With over 8.3k stars on GitHub, Dokploy is trusted by developers worldwide. Explore our repositories and join our community!", + icon: ( + + ), + component: ( +

+ + +

+ ), + }, + { + title: "DockerHub Downloads", + description: + "Downloaded over 700,000 times, Dokploy has become a go-to solution for seamless deployments. Discover our presence on DockerHub.", + icon: ( + + + + ), + component: ( +

+ + +

+ ), + }, + { + title: "Community Contributors", + description: + "Thanks to a growing base of over 65 contributors, Dokploy continues to thrive with valuable contributions from developers around the world.", + icon: , + component: ( +

+ + +

+ ), + }, + { + title: "Sponsors", + description: + "More than 35 companies/individuals have sponsored Dokploy, ensuring a steady flow of support and resources. Join our community!", + icon: , + component: ( +

+ + +

+ ), + }, +]; + +export const Grid = ({ + pattern, + size, +}: { + pattern?: number[][]; + size?: number; +}) => { + const p = pattern ?? [ + [Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1], + [Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1], + [Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1], + [Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1], + [Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1], + ]; + return ( +
+
+ +
+
+ ); +}; + +export function GridPattern({ width, height, x, y, squares, ...props }: any) { + const patternId = useId(); + + return ( + + ); +} diff --git a/apps/website/components/ui/animated-grid-pattern.tsx b/apps/website/components/ui/animated-grid-pattern.tsx new file mode 100644 index 0000000..c31c9fa --- /dev/null +++ b/apps/website/components/ui/animated-grid-pattern.tsx @@ -0,0 +1,150 @@ +"use client"; + +import { motion } from "framer-motion"; +import { useEffect, useId, useRef, useState } from "react"; + +import { cn } from "@/lib/utils"; + +interface GridPatternProps { + width?: number; + height?: number; + x?: number; + y?: number; + strokeDasharray?: any; + numSquares?: number; + className?: string; + maxOpacity?: number; + duration?: number; + repeatDelay?: number; +} + +export function GridPattern({ + width = 40, + height = 40, + x = -1, + y = -1, + strokeDasharray = 0, + numSquares = 50, + className, + maxOpacity = 0.5, + duration = 4, + repeatDelay = 0.5, + ...props +}: GridPatternProps) { + const id = useId(); + const containerRef = useRef(null); + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); + const [squares, setSquares] = useState(() => generateSquares(numSquares)); + + function getPos() { + return [ + Math.floor((Math.random() * dimensions.width) / width), + Math.floor((Math.random() * dimensions.height) / height), + ]; + } + + // Adjust the generateSquares function to return objects with an id, x, and y + function generateSquares(count: number) { + return Array.from({ length: count }, (_, i) => ({ + id: i, + pos: getPos(), + })); + } + + // Function to update a single square's position + const updateSquarePosition = (id: number) => { + setSquares((currentSquares) => + currentSquares.map((sq) => + sq.id === id + ? { + ...sq, + pos: getPos(), + } + : sq, + ), + ); + }; + + // Update squares to animate in + useEffect(() => { + if (dimensions.width && dimensions.height) { + setSquares(generateSquares(numSquares)); + } + }, [dimensions, numSquares]); + + // Resize observer to update container dimensions + useEffect(() => { + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + setDimensions({ + width: entry.contentRect.width, + height: entry.contentRect.height, + }); + } + }); + + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => { + if (containerRef.current) { + resizeObserver.unobserve(containerRef.current); + } + }; + }, [containerRef]); + + return ( + + ); +} + +export default GridPattern; diff --git a/apps/website/components/ui/number-ticker.tsx b/apps/website/components/ui/number-ticker.tsx new file mode 100644 index 0000000..118d30f --- /dev/null +++ b/apps/website/components/ui/number-ticker.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { useInView, useMotionValue, useSpring } from "framer-motion"; +import { useEffect, useRef } from "react"; + +import { cn } from "@/lib/utils"; + +export default function NumberTicker({ + value, + direction = "up", + delay = 0, + className, + decimalPlaces = 0, +}: { + value: number; + direction?: "up" | "down"; + className?: string; + delay?: number; // delay in s + decimalPlaces?: number; +}) { + const ref = useRef(null); + const motionValue = useMotionValue(direction === "down" ? value : 0); + const springValue = useSpring(motionValue, { + damping: 60, + stiffness: 100, + }); + const isInView = useInView(ref, { once: true, margin: "0px" }); + + useEffect(() => { + isInView && + setTimeout(() => { + motionValue.set(direction === "down" ? 0 : value); + }, delay * 1000); + }, [motionValue, isInView, delay, value, direction]); + + useEffect( + () => + springValue.on("change", (latest) => { + if (ref.current) { + ref.current.textContent = Intl.NumberFormat("en-US", { + minimumFractionDigits: decimalPlaces, + maximumFractionDigits: decimalPlaces, + }).format(Number(latest.toFixed(decimalPlaces))); + } + }), + [springValue, decimalPlaces], + ); + + return ( + + ); +} diff --git a/apps/website/components/ui/ripple.tsx b/apps/website/components/ui/ripple.tsx index 7898b2a..0e69681 100644 --- a/apps/website/components/ui/ripple.tsx +++ b/apps/website/components/ui/ripple.tsx @@ -1,8 +1,15 @@ "use client"; -import React, { type CSSProperties } from "react"; +import React from "react"; import { cn } from "@/lib/utils"; +import Link from "next/link"; import { Avatar, AvatarFallback, AvatarImage } from "./avatar"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "./tooltip"; interface RippleProps { mainCircleSize?: number; @@ -11,37 +18,79 @@ interface RippleProps { className?: string; } +type AvatarItem = { + name: string; + image: string; + link: string; + type: "hero" | "premium" | "elite" | "supporting" | "community"; +}; + const Ripple = React.memo(function Ripple({ mainCircleSize = 210, mainCircleOpacity = 0.24, numCircles = 8, className, }: RippleProps) { - const avatarsFirstRing = [ - "https://github.com/shadcn.png", // URLs de los avatares del primer aro - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", + const heroSponsors: AvatarItem[] = [ + { + name: "Hostinger", + image: "https://avatars.githubusercontent.com/u/2630767?s=200&v=4", + link: "https://www.hostinger.com/vps-hosting?ref=dokploy", + type: "hero", + }, + { + name: "Lxaer", + image: + "https://raw.githubusercontent.com/Dokploy/dokploy/canary/.github/sponsors/lxaer.png", + link: "https://www.lxaer.com?ref=dokploy", + type: "hero", + }, ]; - const avatarsSecondRing = [ - "https://github.com/shadcn.png", // URLs de los avatares del segundo aro - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", + const premiumSponsors = [ + { + name: "Supafort", + image: "supafort.png", + link: "https://supafort.com/?ref=dokploy", + type: "premium", + }, ]; - const avatarsThirdRing = [ - "https://github.com/shadcn.png", // URLs de los avatares del tercer aro - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", - "https://github.com/shadcn.png", + const eliteSponsors = []; + + const supportingSponsors = [ + { + name: "Lightspeed Run", + image: "https://github.com/lightspeedrun.png", + link: "https://lightspeed.run/?ref=dokploy", + type: "supporting", + }, + { + name: "Cloudblast", + image: "https://cloudblast.io/img/logo-icon.193cf13e.svg", + link: "https://cloudblast.io/?ref=dokploy", + type: "supporting", + }, + ]; + + const communitySponsors = [ + { + name: "Steamsets", + image: "https://avatars.githubusercontent.com/u/111978405?s=200&v=4", + link: "https://steamsets.com/?ref=dokploy", + type: "premium", + }, + { + name: "Rivo GG", + image: "https://avatars.githubusercontent.com/u/126797452?s=200&v=4", + link: "https://rivo.gg/?ref=dokploy", + type: "premium", + }, + { + name: "Photoquest", + image: "https://photoquest.wedding/favicon/android-chrome-512x512.png", + link: "https://photoquest.wedding/?ref=dokploy", + type: "premium", + }, ]; return ( @@ -75,14 +124,40 @@ const Ripple = React.memo(function Ripple({ left: "50%", transform: "translate(-50%, -50%) scale(1)", }} + /> + ); + })} + {Array.from({ length: numCircles }, (_, i) => { + const size = mainCircleSize + i * 70; + const opacity = mainCircleOpacity - i * 0.03; + const animationDelay = `${i * 0.06}s`; + const borderStyle = i === numCircles - 1 ? "dashed" : "solid"; + const borderOpacity = 5 + i * 5; + + return ( +
{i === 0 && (
- {avatarsFirstRing.map((src, index) => { - const angle = (360 / avatarsFirstRing.length) * index; + {heroSponsors.map((item, index) => { + const angle = (360 / heroSponsors.length) * index; const radius = mainCircleSize / 2; const x = radius * Math.cos((angle * Math.PI) / 180); const y = radius * Math.sin((angle * Math.PI) / 180); + const initials = item.name + .split(" ") + .map((n) => n[0]) + .join(""); return (
- - - CN - + + + + + + + {initials} + + + + +

+ {item.name} +

+
+
+
); })} @@ -106,12 +197,15 @@ const Ripple = React.memo(function Ripple({ {i === 1 && (
- {avatarsSecondRing.map((src, index) => { - const angle = (360 / avatarsSecondRing.length) * index; + {premiumSponsors.map((item, index) => { + const angle = (360 / premiumSponsors.length) * index; const radius = mainCircleSize / 2 + 70; // Radio mayor para el segundo aro const x = radius * Math.cos((angle * Math.PI) / 180); const y = radius * Math.sin((angle * Math.PI) / 180); - + const initials = item.name + .split(" ") + .map((n) => n[0]) + .join(""); return (
- - - CN - + + + + + + + {initials} + + + + +

+ {item.name} +

+
+
+
); })} @@ -134,12 +244,15 @@ const Ripple = React.memo(function Ripple({ {i === 3 && (
- {avatarsThirdRing.map((src, index) => { - const angle = (360 / avatarsThirdRing.length) * index; - const radius = mainCircleSize / 2 + 140; // Radio mayor para el tercer aro + {supportingSponsors.map((item, index) => { + const angle = (360 / supportingSponsors.length) * index; + const radius = mainCircleSize / 2 + 140; const x = radius * Math.cos((angle * Math.PI) / 180); const y = radius * Math.sin((angle * Math.PI) / 180); - + const initials = item.name + .split(" ") + .map((n) => n[0]) + .join(""); return (
- - - CN - + + + + + + + {initials} + + + + +

+ {item.name} +

+
+
+
+
+ ); + })} +
+ )} + + {i === 4 && ( +
+ {communitySponsors.map((item, index) => { + const angle = (360 / communitySponsors.length) * index; + const radius = mainCircleSize / 2 + 180; + const x = radius * Math.cos((angle * Math.PI) / 180); + const y = radius * Math.sin((angle * Math.PI) / 180); + const initials = item.name + .split(" ") + .map((n) => n[0]) + .join(""); + return ( +
+ + + + + + + {initials} + + + + +

+ {item.name} +

+
+
+
); })} diff --git a/apps/website/components/ui/tooltip.tsx b/apps/website/components/ui/tooltip.tsx new file mode 100644 index 0000000..3fe980f --- /dev/null +++ b/apps/website/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/apps/website/package.json b/apps/website/package.json index d5d5f0f..9fe1c58 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -21,6 +21,7 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "1.1.1", + "@radix-ui/react-tooltip": "^1.1.3", "@tabler/icons-react": "3.21.0", "@types/node": "20.4.6", "autoprefixer": "^10.4.12", diff --git a/apps/website/public/supafort.png b/apps/website/public/supafort.png new file mode 100644 index 0000000000000000000000000000000000000000..bc28a296026b1e290609011dcd118dcf780c085e GIT binary patch literal 5375 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 YsY6r7&C67t-@m*x)pgZMRICI42b=K=R{#J2 literal 0 HcmV?d00001 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83d6124..81800e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,6 +109,9 @@ importers: '@radix-ui/react-tabs': specifier: 1.1.1 version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-tooltip': + specifier: ^1.1.3 + version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@tabler/icons-react': specifier: 3.21.0 version: 3.21.0(react@18.2.0) @@ -782,6 +785,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dismissable-layer@1.1.1': + resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==} + peerDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.0.1': resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -896,6 +912,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.1.2': + resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==} + peerDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-presence@1.0.1': resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} peerDependencies: @@ -1070,6 +1099,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-tooltip@1.1.3': + resolution: {integrity: sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==} + peerDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.0.1': resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -3702,6 +3744,19 @@ snapshots: '@types/react': 18.3.5 '@types/react-dom': 18.3.0 + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.5)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + '@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.5)(react@18.3.1)': dependencies: '@babel/runtime': 7.25.0 @@ -3866,6 +3921,16 @@ snapshots: '@types/react': 18.3.5 '@types/react-dom': 18.3.0 + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + '@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.25.0 @@ -4091,6 +4156,26 @@ snapshots: '@types/react': 18.3.5 '@types/react-dom': 18.3.0 + '@radix-ui/react-tooltip@1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.5)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.5)(react@18.2.0) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.5)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.5)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.5 + '@types/react-dom': 18.3.0 + '@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.5)(react@18.3.1)': dependencies: '@babel/runtime': 7.25.0