Blocks

Portfolio 2

standard
1 ColumnText Align CenterText Align LeftImage/Video TopButtonsMultiple Images
Portfolio 2

Code

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

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 Portfolio2Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;

export const Portfolio2 = (props: Portfolio2Props) => {
  const { tagline, heading, description, projects, button } = {
    ...Portfolio2Defaults,
    ...props,
  };
  return (
    <section className="px-[5%] py-16 md:py-24 lg:py-28">
      <div className="container">
        <div className="mb-12 md:mb-18 lg:mb-20">
          <div className="mx-auto max-w-lg text-center">
            <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>
          </div>
        </div>
        <div className="grid grid-cols-1 gap-12 md:gap-16 lg:gap-20">
          {projects.map((project, index) => (
            <Project key={index} {...project} />
          ))}
        </div>
        <div className="mt-12 flex justify-center md:mt-18 lg:mt-20">
          <Button {...button}>{button.title}</Button>
        </div>
      </div>
    </section>
  );
};

const Project: React.FC<ProjectProps> = ({ title, description, image, url, tags }) => (
  <div>
    <div>
      <a href={url}>
        <img src={image.src} className="w-full rounded-image object-cover" alt={image.alt} />
      </a>
    </div>
    <div className="mt-5 grid grid-cols-1 items-start justify-between gap-6 md:mt-6 md:grid-cols-2 md: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>
  </div>
);

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 Portfolio2Defaults: 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