Card Testimonial

PreviousNext

Card Testimonial, with rating, block quote, and author's informations.

Docs
systaliko-uiui

Preview

Loading preview…
registry/cards/card-testimonial/index.tsx
'use client';

import { cn } from '@/lib/utils';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/systaliko-ui/shadcn/avatar';
import { RatingStars } from '@/components/systaliko-ui/components/rating-stars';
import * as React from 'react';
import { Card, cardVariants } from '@/components/systaliko-ui/cards/card';
import { VariantProps } from 'class-variance-authority';

interface AuthorProps extends React.HTMLAttributes<HTMLDivElement> {
  authorName: string;
  avatarUrl?: string;
  description?: string;
}
interface CardTestimonialProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof cardVariants> {
  testimonialQuote: string;
  testimonialAuthor: AuthorProps;
  testimonialRating?: number;
}
interface TestimonialContextValue {
  testimonialQuote: string;
  testimonialAuthor: AuthorProps;
  testimonialRating?: number;
}

const CardTestimonialContext = React.createContext<
  TestimonialContextValue | undefined
>(undefined);

function useCardTestimonialContext() {
  const context = React.useContext(CardTestimonialContext);
  if (context === undefined) {
    throw new Error(
      'useCardTestimonialContext must be used within a CardTestimonialProvider',
    );
  }
  return context;
}

export const CardTestimonial = ({
  testimonialQuote,
  testimonialAuthor,
  testimonialRating,
  className,
  children,
  variant,
  ...props
}: CardTestimonialProps) => {
  return (
    <CardTestimonialContext.Provider
      value={{ testimonialQuote, testimonialAuthor, testimonialRating }}
    >
      <Card className={cn(cardVariants({ variant, className }))} {...props}>
        {children}
      </Card>
    </CardTestimonialContext.Provider>
  );
};

export const TestimonialAuthor = ({
  className,
  children,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => {
  const { testimonialAuthor } = useCardTestimonialContext();
  const { authorName, avatarUrl, description } = testimonialAuthor;
  return (
    <div className={className} {...props}>
      <Avatar className="size-10">
        <AvatarImage src={avatarUrl} alt={`Portrait of ${authorName}`} />
        <AvatarFallback>
          {authorName
            .split(' ')
            .map((n) => n[0])
            .join('')}
        </AvatarFallback>
      </Avatar>
      <div>
        <h4 className="block text-lg font-semibold tracking-tight md:text-xl">
          {authorName}
        </h4>
        <span className="block text-sm text-muted-foreground ">
          {description}
        </span>
      </div>
      {children}
    </div>
  );
};

export const TestimonialRating = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => {
  const { testimonialRating } = useCardTestimonialContext();
  return (
    <RatingStars
      className={className}
      rating={testimonialRating ?? 0}
      {...props}
    />
  );
};

export const TestimonialQuote = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLQuoteElement>) => {
  const { testimonialQuote } = useCardTestimonialContext();
  return (
    <blockquote className={className} {...props}>
      {testimonialQuote}
    </blockquote>
  );
};

Installation

npx shadcn@latest add @systaliko-ui/card-testimonial

Usage

import { CardTestimonial } from "@/components/ui/card-testimonial"
<CardTestimonial />