Blocks

Portfolio 4

Portfolio
1 ColumnText Align CenterText Align LeftImage/Video TopButtonsMultiple Images
Portfolio 4

Code

relume-portfolio4.tsx
import { Badge } from "@/components/ui/badge";
import { Button, type ButtonProps } from "@/components/ui/button";
import { Card } from "@/components/ui/card";

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

type Tag = {
  label: string;
  url: string;
};

type ProjectProps = {
  title: string;
  description: string;
  image: ImageProps;
  url: string;
  tags: Tag[];
};

type Props = {
  tagline: string;
  heading: string;
  description: string;
  projects: ProjectProps[];
  button: ButtonProps;
};

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

export const Portfolio4 = (props: Portfolio4Props) => {
  const { tagline, heading, description, projects, button } = {
    ...Portfolio4Defaults,
    ...props,
  };
  return (
    <section className="px-[5%] py-16 md:py-24 lg:py-28">
      <div className="container">
        <header className="mx-auto mb-12 max-w-lg text-center md:mb-18 lg:mb-20">
          <p className="mb-3 font-semibold md:mb-4">{tagline}</p>
          <h2 className="mb-5 text-h2 font-bold md:mb-6">{heading}</h2>
          <p className="text-medium">{description}</p>
        </header>
        <div className="grid grid-cols-1 gap-12 md:gap-16 lg:gap-20">
          {projects.map((project, index) => (
            <Project key={index} {...project} />
          ))}
        </div>
        <footer className="mt-12 flex justify-center md:mt-18 lg:mt-20">
          <Button {...button}>{button.title}</Button>
        </footer>
      </div>
    </section>
  );
};

const Project: React.FC<ProjectProps> = ({ title, description, image, url, tags }) => (
  <Card>
    <div>
      <a href={url}>
        <img src={image.src} className="size-full object-cover" alt={image.alt} />
      </a>
    </div>
    <div className="grid grid-cols-1 items-start justify-between gap-6 px-5 py-6 sm:px-6 md:grid-cols-2 md:gap-12 lg:gap-20">
      <div>
        <h3 className="text-h5 font-bold">
          <a href={url}>{title}</a>
        </h3>
        <div className="mt-3 flex flex-wrap gap-2 md:mt-4">
          {tags.map((tag, index) => (
            <Badge key={index}>
              <a href={tag.url}>{tag.label}</a>
            </Badge>
          ))}
        </div>
      </div>
      <div>
        <p>{description}</p>
      </div>
    </div>
  </Card>
);

const project: ProjectProps = {
  title: "Project name here",
  description:
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
  image: {
    src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image-landscape.svg",
    alt: "Relume placeholder image",
  },
  url: "#",
  tags: [
    {
      label: "Tag one",
      url: "#",
    },
    {
      label: "Tag two",
      url: "#",
    },
    {
      label: "Tag three",
      url: "#",
    },
  ],
};

export const Portfolio4Defaults: Props = {
  tagline: "Portfolio",
  heading: "Short heading goes here",
  description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
  projects: [project, project],
  button: {
    title: "View all",
    variant: "secondary",
  },
};

npm i @radix-ui/react-slot class-variance-authority@0.7.1 clsx@^2.1.1 relume-icons@1.3.0 tailwind-merge@^2.2.2