Links 14
Link PagesText Align Center1 ColumnImage/Video CenterModalButtons

Code
relume-links14.tsx
import React, { useState } from "react";
import { Button, type ButtonProps } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import {
FacebookLogo,
InstagramLogo,
LinkedinLogo,
LocationOn,
Mail,
RelumeIcon,
XLogo,
YoutubeLogo,
} from "relume-icons";
type ImageProps = {
src: string;
alt?: string;
};
type AuthorDetailsProps = {
avatar: ImageProps;
fullName: string;
position: string;
location: string;
};
type CategoryLinkProps = {
url: string;
title: string;
};
type CategoryProps = {
heading?: string;
links: CategoryLinkProps[];
};
type SocialLinkProps = {
href: string;
icon: React.ReactNode;
};
type NewsletterDialog = {
heading: string;
description: string;
inputPlaceholder?: string;
submitButton: ButtonProps;
termsAndConditions: string;
};
type NewsletterCard = {
icon: React.ReactNode;
title: string;
description: string;
};
type Props = {
author: AuthorDetailsProps;
categories: CategoryProps[];
newsLetterCard: NewsletterCard;
newsLetterDialog: NewsletterDialog;
socialLinks: SocialLinkProps[];
};
export type Links14Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
export const Links14 = (props: Links14Props) => {
const { author, categories, newsLetterCard, newsLetterDialog, socialLinks } = {
...Links14Defaults,
...props,
};
const [emailInput, setEmailInput] = useState<string>("");
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log({
emailInput,
});
};
return (
<section className="px-[5%] py-16 md:py-24 lg:py-28">
<div className="container max-w-md">
<div className="mb-10 flex flex-col items-center text-center md:mb-14 lg:mb-16">
<div className="mx-auto mb-5 size-24 min-h-24 min-w-24 overflow-hidden rounded-full md:mb-6">
<img
src={author.avatar.src}
alt={author.avatar.alt}
className="size-full object-cover"
/>
</div>
<h2 className="mb-2 text-h5 font-bold">{author.fullName}</h2>
<p>{author.position}</p>
<div className="mt-3 flex items-center justify-center gap-2 md:mt-4">
<LocationOn className="size-5 text-scheme-text" />
<p>{author.location}</p>
</div>
</div>
<div className="space-y-8">
<div className="flex flex-wrap justify-center gap-3">
{socialLinks.map((link, index) => (
<a key={index} href={link.href}>
{link.icon}
</a>
))}
</div>
{categories.map((category, index) => (
<Category key={index} {...category} />
))}
<Dialog>
<DialogTrigger asChild>
<Card className="flex items-center gap-4 p-4">
<span>{newsLetterCard.icon}</span>
<span className="flex grow flex-col items-start">
<span className="text-medium font-semibold">{newsLetterCard.title}</span>
<span className="text-small">{newsLetterCard.description} </span>
</span>
</Card>
</DialogTrigger>
<DialogContent
closeIconPosition="inside"
overlayClassName="bg-neutral-darkest/25"
className="flex size-full flex-col justify-center bg-white px-[5%] pt-12 pb-28 md:size-auto md:w-[90%] md:px-12 md:py-12 lg:max-w-sm"
>
<div className="mb-8 text-center md:mb-12">
<h2 className="mb-3 text-h3 font-bold md:mb-4">{newsLetterDialog.heading}</h2>
<p>{newsLetterDialog.description}</p>
</div>
<form className="mb-4 flex flex-col gap-y-4" onSubmit={handleSubmit}>
<Input
id="email"
type="email"
placeholder={newsLetterDialog.inputPlaceholder}
value={emailInput}
onChange={(e) => setEmailInput(e.target.value)}
/>
<Button {...newsLetterDialog.submitButton}>
{newsLetterDialog.submitButton.title}
</Button>
</form>
<div dangerouslySetInnerHTML={{ __html: newsLetterDialog.termsAndConditions }} />
</DialogContent>
</Dialog>
</div>
</div>
</section>
);
};
const Category = (category: CategoryProps) => {
return (
<div className="flex flex-col gap-4 text-center">
{category.heading && <h3 className="text-h6 font-bold">{category.heading}</h3>}
{category.links.map((link, index) => (
<Card key={index} className="p-3">
<a href={link.url} className="flex items-center gap-x-2">
<div>
<RelumeIcon className="size-8 text-scheme-text" />
</div>
<div className="grow pr-14 text-center">
<p>{link.title}</p>
</div>
</a>
</Card>
))}
</div>
);
};
export const Links14Defaults: Props = {
author: {
avatar: {
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-small.svg",
alt: "Relume placeholder avatar",
},
fullName: "Name Surname",
position: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
location: "Location",
},
categories: [
{
links: [
{
url: "#",
title: "Link one",
},
],
},
{
heading: "Category name",
links: [
{
url: "#",
title: "Link two",
},
{
url: "#",
title: "Link three",
},
{
url: "#",
title: "Link four",
},
],
},
{
heading: "Category name",
links: [
{
url: "#",
title: "Link five",
},
{
url: "#",
title: "Link six",
},
],
},
],
newsLetterCard: {
icon: <Mail className="size-8 text-scheme-text" />,
title: "Join our newsletter",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
},
newsLetterDialog: {
heading: "Join our newsletter",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
inputPlaceholder: "Enter your email",
submitButton: {
title: "Subscribe",
},
termsAndConditions: `
<p class='text-tiny'>
By subscribing you agree to with our
<a href='#' class='underline'>Privacy Policy</a> and provide consent to receive updates from our company.
</p>
`,
},
socialLinks: [
{ href: "#", icon: <FacebookLogo className="size-8 text-scheme-text" /> },
{ href: "#", icon: <InstagramLogo className="size-8 text-scheme-text" /> },
{ href: "#", icon: <XLogo className="size-8 p-0.5 text-scheme-text" /> },
{ href: "#", icon: <LinkedinLogo className="size-8 text-scheme-text" /> },
{ href: "#", icon: <YoutubeLogo className="size-8 text-scheme-text" /> },
],
};npm i @radix-ui/react-accordion @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-label @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slot @radix-ui/react-tooltip class-variance-authority@0.7.1 clsx@^2.1.1 embla-carousel-react@^8.5.2 motion@^12.15.0 relume-icons@1.3.0 tailwind-merge@^2.2.2


