Blocks

Links 6

Link Pages
Text Align Center1 ColumnImage/Video CenterModalButtons
Links 6

Code

relume-links6.tsx
import { 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,
  XLogo,
  YoutubeLogo,
} from "relume-icons";

type ImageProps = {
  src: string;
  alt?: string;
};

type AuthorDetailsProps = {
  avatar: ImageProps;
  fullName: string;
  position: string;
  location: string;
};

type CategoryProps = {
  heading?: string;
  links: (ButtonProps & {
    url: string;
  })[];
};

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 = {
  image: ImageProps;
  author: AuthorDetailsProps;
  categories: CategoryProps[];
  newsLetterCard: NewsletterCard;
  newsLetterDialog: NewsletterDialog;
  socialLinks: SocialLinkProps[];
};

export type Links6Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;

export const Links6 = (props: Links6Props) => {
  const { author, image, categories, newsLetterCard, newsLetterDialog, socialLinks } = {
    ...Links6Defaults,
    ...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>
            <img src={image.src} alt={image.alt} className="aspect-[3/2] size-full object-cover" />
          </div>
        </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) => (
        <Button key={index} {...link} asChild>
          <a href={link.url}>{link.title}</a>
        </Button>
      ))}
    </div>
  );
};

export const Links6Defaults: Props = {
  image: {
    src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-landscape.svg",
    alt: "Relume placeholder image",
  },
  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",
          variant: "secondary",
        },
      ],
    },
    {
      heading: "Category name",
      links: [
        {
          url: "#",
          title: "Link two",
          variant: "secondary",
        },
        {
          url: "#",
          title: "Link three",
          variant: "secondary",
        },
        {
          url: "#",
          title: "Link four",
          variant: "secondary",
        },
      ],
    },
    {
      heading: "Category name",
      links: [
        {
          url: "#",
          title: "Link five",
          variant: "secondary",
        },
        {
          url: "#",
          title: "Link six",
          variant: "secondary",
        },
      ],
    },
  ],
  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