"use client"; import {FC, useMemo, useRef} from "react"; import {Avatar, AvatarProps, Button, Spacer, Tooltip} from "@nextui-org/react"; import {clamp, get} from "lodash"; import {sectionWrapper, titleWrapper, title, subtitle} from "../primitives"; import {FeaturesGrid} from "./features-grid"; import {OpenCollectiveIcon, PatreonIcon, HeartBoldIcon, PlusLinearIcon} from "@/components/icons"; import {Sponsor, SPONSOR_TIERS, SPONSOR_COLORS, getTier} from "@/libs/docs/sponsors"; import {SonarPulse} from "@/components/sonar-pulse"; import {useIsMobile} from "@/hooks/use-media-query"; import {trackEvent} from "@/utils/va"; export interface SupportProps { sponsors: Sponsor[]; } const supportAccounts = [ { title: "Open Collective", description: "Sponsor the NextUI maintainers.", icon: , href: "https://opencollective.com/nextui", isExternal: true, }, { title: "Patreon", description: "Sponsor the creator, Junior Garcia.", icon: , href: "https://www.patreon.com/jrgarciadev?fan_landing=true", isExternal: true, }, ]; const SONAR_PULSE_SIZE = 80; const SONAR_PULSE_CIRCLES_COUNT = 4; const SONAR_PULSE_RADIUS = 130; const getSponsorName = (sponsor: Sponsor) => { if (!sponsor.name) { return ""; } return sponsor.name.slice(0, 2).toUpperCase(); }; const getSponsorSize = (sponsor: Sponsor, isMobile: boolean) => { let size: AvatarProps["size"] = "md"; const tier = sponsor.tier || getTier(sponsor.totalAmountDonated); switch (tier) { case SPONSOR_TIERS.BRONZE: size = isMobile ? "sm" : "md"; break; case SPONSOR_TIERS.SILVER: size = isMobile ? "sm" : "md"; break; case SPONSOR_TIERS.GOLD: size = isMobile ? "md" : "lg"; break; case SPONSOR_TIERS.PLATINUM: size = isMobile ? "md" : "lg"; break; default: size = isMobile ? "sm" : "md"; } return size; }; const getSponsorColor = (sponsor: Sponsor) => { const tier = sponsor.tier || getTier(sponsor.totalAmountDonated); return SPONSOR_COLORS[tier] || "default"; }; const getSponsorAvatarStyles = (index: number, sponsors: Sponsor[] = []) => { const angle = (index * 360) / sponsors.length; const radius = SONAR_PULSE_RADIUS; // position the avatar randomly inside the sonar pulse const randomRadius = clamp(Math.floor((index + 1) * radius), radius * 0.4, radius); const x = randomRadius * Math.cos((angle * Math.PI) / 180); const y = randomRadius * Math.sin((angle * Math.PI) / 180); return { transform: `translate(${x}px, ${y}px)`, }; }; export const Support: FC = ({sponsors = []}) => { const sonarRef = useRef(null); const isMobile = useIsMobile(); const handleExternalLinkClick = (href: string) => { if (!href) return; window.open(href, "_blank"); }; const handleBecomeSponsor = () => { trackEvent("Support - Become a sponsor", { action: "click", category: "landing-page", }); handleExternalLinkClick(supportAccounts[0].href); }; const renderSponsors = useMemo(() => { if (!sponsors.length) return null; return ( {sponsors.map((sponsor, index) => ( handleExternalLinkClick(get(sponsor, "website") || get(sponsor, "profile")) } /> ))} ); }, [isMobile, sponsors]); return ( Support NextUI Using NextUI in a profit-making product, as a freelancer, or for fun projects? Your contributions will help to make NextUI better. } playState="running" size={SONAR_PULSE_SIZE} > {renderSponsors} ); };
Using NextUI in a profit-making product, as a freelancer, or for fun projects? Your contributions will help to make NextUI better.