import * as React from "react"
import { IconQuote } from "@tabler/icons-react"
import { cn } from "@/registry/lib/utils"
import { Card } from "@/registry/ui/card"
/**
* Quote: something a person said, with who said it.
*
* variant="plain" - stands on any surface: a sign-in aside, a section, a
* sidebar. No box.
* variant="card" - one testimonial in a row of them. The surface is Card's,
* the quote takes the free height and the attribution sits
* at the bottom, so the names line up across the row.
* size="lg" - one featured pull quote in a band. Works with either
* variant, though it is almost always plain.
*
* Every part reads the variant and size off the root's data attributes, the
* way Lockup does, so the component ships with no context and no client
* boundary: a server page renders it directly.
*/
type QuoteVariant = "plain" | "card"
type QuoteSize = "default" | "lg"
// @use-when a quote from a person with their name and role under it: a
// testimonial in a row of cards (`variant="card"`), a quote in a sign-in aside
// or a section (`plain`), or one featured pull quote in a band (`size="lg"`).
// Not Prose, whose blockquote is a quotation inside long-form text.
function Quote({
variant = "plain",
size = "default",
mark = false,
className,
children,
...props
}: React.ComponentProps<"figure"> & {
/** `plain` for any surface, `card` for one quote in a row of them. */
variant?: QuoteVariant
/** `lg` for one featured pull quote in a band. */
size?: QuoteSize
/** Draws a quote mark above the text. Decoration, so it is hidden from
* screen readers and painted in a quiet neutral: accent marks what you can
* press, and a coloured mark competes with the words it introduces. */
mark?: boolean
}) {
const figure = (
<figure
data-slot="quote"
data-variant={variant}
data-size={size}
className={cn(
"group/quote flex flex-col",
// The quote to its attribution. Wider at `lg`, so the gap still beats
// the bigger text's own line spacing and reads as a break.
"data-[size=default]:gap-4 data-[size=lg]:gap-6",
// The same inset CardContent and CardFooter carry, so a quote card
// lines up with every other card in a grid. flex-1 fills the Card,
// which a grid row stretches to the tallest card.
"data-[variant=card]:flex-1 data-[variant=card]:px-4 data-[variant=card]:py-5",
variant === "plain" && className
)}
{...props}
>
{mark && (
<IconQuote
aria-hidden
data-slot="quote-mark"
className="shrink-0 text-subtle-foreground group-data-[size=default]/quote:size-5 group-data-[size=lg]/quote:size-8"
/>
)}
{children}
</figure>
)
// The card's className goes on the Card, since that is the box a caller
// sizes or places. Everything else stays on the figure.
if (variant === "card") {
return <Card className={className}>{figure}</Card>
}
return figure
}
/** The words. Pass the source URL as `cite` when there is one. */
function QuoteText({ className, ...props }: React.ComponentProps<"blockquote">) {
return (
<blockquote
data-slot="quote-text"
className={cn(
"text-pretty text-foreground",
"group-data-[size=default]/quote:text-base",
"group-data-[size=lg]/quote:text-2xl group-data-[size=lg]/quote:font-medium",
// In a card the words take the free height, so the attribution sits at
// the bottom and the names line up across a row of cards.
"group-data-[variant=card]/quote:flex-1",
className
)}
{...props}
/>
)
}
/** Who said it: a name, a role, and an optional avatar and company logo. */
function QuoteAuthor({
name,
role,
avatar,
logo,
className,
...props
}: Omit<React.ComponentProps<"figcaption">, "children"> & {
name: React.ReactNode
/** Their job and company, on the line under the name. */
role?: React.ReactNode
/** An `<Avatar>` element. Sized by you: `sm` or `default` for a quote,
* `lg` beside a `size="lg"` quote. */
avatar?: React.ReactNode
/** The company's logo, at the far end of the row. Give the image its own
* height (`h-5` or `h-6`) and alt text. */
logo?: React.ReactNode
}) {
return (
<figcaption
data-slot="quote-author"
className={cn("flex items-center gap-(--spacing-rhythm-media)", className)}
{...props}
>
{avatar}
<div className="flex min-w-0 flex-col gap-(--spacing-rhythm-pair)">
<span className="font-medium text-foreground group-data-[size=default]/quote:text-sm group-data-[size=lg]/quote:text-base">
{name}
</span>
{role && <span className="text-sm text-muted-foreground">{role}</span>}
</div>
{logo && <div className="ms-auto flex shrink-0 items-center">{logo}</div>}
</figcaption>
)
}
export { Quote, QuoteText, QuoteAuthor }