Files
c48a7e6f-931b-4375-9e8e-ae3…/src/components/sections/hero/HeroPersonalLinks.tsx
2026-04-15 15:17:34 +00:00

199 lines
7.6 KiB
TypeScript

"use client";
import Textbox from "@/components/Textbox";
import Button from "@/components/button/Button";
import MediaContent from "@/components/shared/MediaContent";
import HeroBackgrounds, { type HeroBackgroundVariantProps } from "@/components/background/HeroBackgrounds";
import { cls } from "@/lib/utils";
import { getButtonProps } from "@/lib/buttonUtils";
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
import { useButtonClick } from "@/components/button/useButtonClick";
import { useButtonAnimation } from "@/components/hooks/useButtonAnimation";
import type { LucideIcon } from "lucide-react";
import type { ButtonConfig, ButtonAnimationType } from "@/types/button";
type TitleSegment =
| { type: "text"; content: string }
| { type: "image"; src: string; alt?: string };
type HeroPersonalLinksBackgroundProps = Extract<
HeroBackgroundVariantProps,
| { variant: "plain" }
| { variant: "animated-grid" }
| { variant: "canvas-reveal" }
| { variant: "cell-wave" }
| { variant: "downward-rays-animated" }
| { variant: "downward-rays-animated-grid" }
| { variant: "downward-rays-static" }
| { variant: "downward-rays-static-grid" }
| { variant: "gradient-bars" }
| { variant: "radial-gradient" }
| { variant: "rotated-rays-animated" }
| { variant: "rotated-rays-animated-grid" }
| { variant: "rotated-rays-static" }
| { variant: "rotated-rays-static-grid" }
| { variant: "sparkles-gradient" }
>;
interface SocialLink {
icon: LucideIcon;
label: string;
href: string;
}
interface LinkCard {
icon?: LucideIcon;
imageSrc?: string;
videoSrc?: string;
imageAlt?: string;
videoAriaLabel?: string;
imageClassName?: string;
title: string;
description: string;
button: ButtonConfig;
}
interface HeroPersonalLinksProps {
background: HeroPersonalLinksBackgroundProps;
title: string;
titleSegments?: TitleSegment[];
socialLinks?: SocialLink[];
linkCards: LinkCard[];
buttonAnimation?: ButtonAnimationType;
ariaLabel?: string;
className?: string;
containerClassName?: string;
textboxClassName?: string;
titleClassName?: string;
titleImageWrapperClassName?: string;
titleImageClassName?: string;
socialLinksClassName?: string;
socialLinkClassName?: string;
linkCardsClassName?: string;
linkCardClassName?: string;
linkCardIconClassName?: string;
linkCardTitleClassName?: string;
linkCardDescriptionClassName?: string;
buttonClassName?: string;
buttonTextClassName?: string;
}
const SocialLinkButton = ({ social, className }: { social: SocialLink; className?: string }) => {
const handleClick = useButtonClick(social.href);
const Icon = social.icon;
return (
<button
type="button"
onClick={handleClick}
className={cls(
"flex items-center gap-2 px-4 py-2 rounded-theme card text-sm text-foreground hover:opacity-80 transition-opacity duration-300 ease-out cursor-pointer",
className
)}
>
<Icon className="h-[1em] w-auto aspect-square" />
<span>{social.label}</span>
</button>
);
};
const HeroPersonalLinks = ({
background = { variant: "plain" },
title,
titleSegments,
socialLinks,
linkCards,
buttonAnimation = "none",
ariaLabel = "Personal links section",
className = "",
containerClassName = "",
textboxClassName = "",
titleClassName = "",
titleImageWrapperClassName = "",
titleImageClassName = "",
socialLinksClassName = "",
socialLinkClassName = "",
linkCardsClassName = "",
linkCardClassName = "",
linkCardIconClassName = "",
linkCardTitleClassName = "",
linkCardDescriptionClassName = "",
buttonClassName = "",
buttonTextClassName = "",
}: HeroPersonalLinksProps) => {
const theme = useTheme();
const { containerRef: buttonContainerRef } = useButtonAnimation({ animationType: buttonAnimation });
return (
<section
aria-label={ariaLabel}
className={cls("relative w-full min-h-screen flex items-center justify-center py-20", className)}
>
<HeroBackgrounds {...background} />
<div className={cls("w-content-width md:w-35 mx-auto flex flex-col items-center gap-8 relative z-10", containerClassName)}>
<Textbox
title={title}
titleSegments={titleSegments}
description=""
textboxLayout="inline-image"
center
className={textboxClassName}
titleClassName={titleClassName}
titleImageWrapperClassName={titleImageWrapperClassName}
titleImageClassName={titleImageClassName}
/>
{socialLinks && socialLinks.length > 0 && (
<div className={cls("flex flex-wrap justify-center gap-3", socialLinksClassName)}>
{socialLinks.map((social) => (
<SocialLinkButton
key={social.label}
social={social}
className={socialLinkClassName}
/>
))}
</div>
)}
<div ref={buttonContainerRef} className={cls("w-full flex flex-col gap-4 mt-4", linkCardsClassName)}>
{linkCards.map((card) => (
<div
key={card.title}
className={cls("w-full card rounded-theme-capped p-5 flex items-center gap-5", linkCardClassName)}
>
<div className={cls("relative h-10 w-auto aspect-square card shadow rounded-theme flex items-center justify-center flex-shrink-0 overflow-hidden", linkCardIconClassName)}>
{card.videoSrc ? (
<MediaContent
videoSrc={card.videoSrc}
videoAriaLabel={card.videoAriaLabel}
imageClassName={cls("w-full h-full object-cover", card.imageClassName)}
/>
) : card.imageSrc ? (
<MediaContent
imageSrc={card.imageSrc}
imageAlt={card.imageAlt}
imageClassName={cls("w-full h-full object-cover", card.imageClassName)}
/>
) : card.icon ? (
<card.icon className="h-4/10 w-4/10 text-foreground" strokeWidth={1.5} />
) : null}
</div>
<div className="flex-1 min-w-0">
<h3 className={cls("font-medium text-foreground", linkCardTitleClassName)}>{card.title}</h3>
<p className={cls("text-sm text-foreground/60 truncate", linkCardDescriptionClassName)}>{card.description}</p>
</div>
<Button
{...getButtonProps(card.button, 0, theme.defaultButtonVariant, cls("flex-shrink-0", buttonClassName), buttonTextClassName)}
/>
</div>
))}
</div>
</div>
</section>
);
};
export default HeroPersonalLinks;