Feature

Feature section component for home block.


Installation

This component is part of the Home block. Install the full block to use it:

CLI

npx shadcn@latest add "https://ui.xaclabs.dev/r/home.json"

Usage

import { Feature } from "@/registry/blocks/home/components/home-feature"
View component source
home-feature.tsx
tsx
"use client"

import Autoplay from "embla-carousel-autoplay"
import { motion } from "motion/react"
import type { ButtonProps } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"
import { cn, hlcn } from "@/lib/utils"
import { Container } from "@/registry/components/container"

export interface FeatureAction {
  title: string
  href: string
  variant?: ButtonProps["variant"]
  icon?: React.ComponentType<{ className?: string }>
}

export interface FeatureItem {
  title: string
  description: string
  cover?: string
  action: FeatureAction
  span?: 1 | 2 | 3
}

export interface HomeFeatureProps {
  feature: {
    title: string
    description: string
    variant?: "primary" | "default"
    layout: "bento" | "carousel"
    items: FeatureItem[]
  }
}

export function HomeFeature({ feature }: HomeFeatureProps) {
  const { variant, layout, title, description, items } = feature
  return (
    <div className={cn("mx-4", hlcn(variant), "rounded-2xl")}>
      <Container className="gap-16 py-16">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          transition={{ duration: 0.5 }}
          className="flex flex-col gap-4 py-8 px-2 basis-1/3"
        >
          <h1 className="text-3xl font-bold">{title}</h1>
          <p className="text-lg text-muted-foreground">{description}</p>
        </motion.div>
        {layout === "bento" && (
          <div className="grid grid-cols-3 gap-4">
            {items.map((item, index) => {
              const span = item.span || 3
              const spanVariants: Record<number, string> = {
                1: "lg:col-span-1",
                2: "lg:col-span-2",
                3: "lg:col-span-3",
              }
              return (
                <motion.a
                  key={item.title}
                  href={item.action.href}
                  className={cn(spanVariants[span], "col-span-3")}
                  initial={{ opacity: 0, y: 20 }}
                  whileInView={{ opacity: 1, y: 0 }}
                  viewport={{ once: true }}
                  transition={{ duration: 0.5, delay: index * 0.1 }}
                >
                  <Card className="h-full flex flex-col p-4 rounded-xl hover:scale-105 transition-all">
                    <CardHeader className="gap-2">
                      <CardTitle>{item.title}</CardTitle>
                      <CardDescription className="text-base text-foreground">
                        {item.description}
                      </CardDescription>
                    </CardHeader>
                    <CardContent className="flex-1 flex items-end">
                      {item.cover && (
                        <img
                          className="mx-auto max-h-48 w-full object-contain"
                          src={item.cover}
                          alt={item.title}
                        />
                      )}
                    </CardContent>
                  </Card>
                </motion.a>
              )
            })}
          </div>
        )}
        {layout === "carousel" && (
          <Carousel plugins={[Autoplay({ delay: 5000 })]} className="w-full">
            <CarouselContent>
              {items.map((item, index) => (
                <CarouselItem
                  key={item.title}
                  className="mx-auto md:basis-1/2 lg:basis-1/3"
                >
                  <motion.div
                    initial={{ opacity: 0 }}
                    whileInView={{ opacity: 1 }}
                    viewport={{ once: true }}
                    transition={{ duration: 0.5, delay: index * 0.1 }}
                  >
                    <a href={item.action.href}>
                      <Card className="h-full flex flex-col p-6 rounded-xl hover:scale-105 transition-all">
                        <CardHeader className="gap-2">
                          <CardTitle>{item.title}</CardTitle>
                          <CardDescription className="text-base text-foreground">
                            {item.description}
                          </CardDescription>
                        </CardHeader>
                        <CardContent className="flex-1 flex items-end">
                          {item.cover && (
                            <img
                              className="mx-auto max-h-48 w-full object-contain"
                              src={item.cover}
                              alt={item.title}
                            />
                          )}
                        </CardContent>
                      </Card>
                    </a>
                  </motion.div>
                </CarouselItem>
              ))}
            </CarouselContent>
            <CarouselPrevious />
            <CarouselNext />
          </Carousel>
        )}
      </Container>
    </div>
  )
}