Files
community-rule/app/components/sections/QuoteBlock/QuoteBlock.view.tsx
T
adilallo ca928e306b Match the 3 Nov color ramps and size-based strokes, and replace leftover palette hex with tokens.
Drop Cookies Settings from the footer, link the Home features that already have pages, and give leftover routes their own descriptions.
2026-11-05 15:37:55 -07:00

227 lines
8.4 KiB
TypeScript

"use client";
import { memo } from "react";
import Image from "next/image";
import QuoteDecor from "./QuoteDecor";
import QuoteStatementDecor from "./QuoteStatementDecor";
import type { QuoteBlockViewProps } from "./QuoteBlock.types";
function QuoteBlockView({
className,
quote,
quoteSecondary,
statementStacked,
author,
source,
quoteId,
authorId,
config,
imageError,
currentAvatarSrc,
onImageError,
}: QuoteBlockViewProps) {
if (config.citationLayout) {
return (
<section
data-figma-node="23983-53436"
className={`${config.container} ${className}`.trim()}
aria-labelledby={quoteId}
>
<figure className="relative mx-auto w-full max-w-[1216px] overflow-hidden rounded-[24px] bg-[var(--color-surface-invert-brand-lavender)] px-[var(--space-600)] py-[var(--space-1000)] md:px-[var(--space-1200)] md:py-[var(--space-1800)]">
<div
className="pointer-events-none absolute -right-10 -top-16 size-[280px] rounded-full bg-white/25"
aria-hidden
/>
<div
className="pointer-events-none absolute -bottom-20 right-16 size-[320px] rounded-full bg-white/20"
aria-hidden
/>
<blockquote id={quoteId} className="relative">
<p
className={`font-bricolage-grotesque font-medium text-[var(--color-content-invert-primary)] ${config.quote}`}
>
“{quote}”
</p>
</blockquote>
<figcaption className="relative mt-10 flex flex-col gap-1">
<cite
id={authorId}
className={`font-inter not-italic uppercase text-[var(--color-content-invert-primary)] ${config.author}`}
>
{author}
</cite>
{source ? (
<p
className={`font-inter uppercase text-[var(--color-content-invert-primary)] ${config.source}`}
>
{source}
</p>
) : null}
</figcaption>
</figure>
</section>
);
}
if (config.statementLayout) {
const statementTextClass =
"font-bricolage-grotesque text-medium-heading text-[var(--color-surface-default-tertiary)] md:text-xx-large-heading";
return (
<section
data-figma-node="21967-24638"
className={`${config.container} ${className}`.trim()}
aria-labelledby={quoteId}
role="region"
>
<div
className="relative box-border flex w-full max-w-[1440px] shrink-0 flex-col items-center justify-center gap-[var(--space-800)] overflow-hidden rounded-[var(--space-500)] bg-[var(--color-surface-invert-brand-primary)] px-[var(--space-800)] py-[var(--space-1200)] md:px-[var(--space-1800)] md:py-[var(--space-2400)]"
>
<QuoteStatementDecor />
<div className="relative z-10 flex w-full min-w-0 shrink-0 flex-col items-center text-center">
<p
id={quoteId}
className={
statementStacked
? `${statementTextClass} mb-0 flex w-full min-w-0 flex-col gap-[1lh] text-center`
: `${statementTextClass} mb-0 flex w-full min-w-0 flex-col gap-9 text-center md:gap-[length:var(--text-xx-large-heading--line-height)] lg:block lg:gap-0`
}
>
<span className={statementStacked ? "block" : "block lg:inline"}>
{quote}
</span>
{quoteSecondary ? (
<>
{statementStacked ? (
" "
) : (
<span className="hidden lg:inline">{" "}</span>
)}
<span className={statementStacked ? "block" : "block lg:inline"}>
{quoteSecondary}
</span>
</>
) : null}
</p>
</div>
</div>
</section>
);
}
return (
<section
className={`${config.container} ${className}`}
aria-labelledby={quoteId}
role="region"
>
<div
className={`${config.card} bg-[var(--color-surface-invert-brand-secondary)] relative overflow-hidden`}
>
{/* Background with noise texture */}
<div
className="absolute inset-0 bg-[var(--color-surface-invert-brand-secondary)]"
style={{
filter:
'url(\'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg"><defs><filter id="grain" filterUnits="objectBoundingBox" x="0" y="0" width="1" height="1" colorInterpolationFilters="sRGB"><feTurbulence type="fractalNoise" baseFrequency="0.4" numOctaves="3" seed="7" stitchTiles="stitch" result="noise"/><feColorMatrix in="noise" result="softNoise" type="matrix" values="0.8 0 0 0 0.3 0 0.6 0 0 0.2 0 0 1.0 0 0.4 0 0 0 0.25 0"/><feComposite in="softNoise" in2="SourceAlpha" operator="in" result="maskedNoise"/><feBlend in="SourceGraphic" in2="maskedNoise" mode="multiply"/></filter></defs></svg>#grain\')',
}}
/>
{/* DECORATIONS (behind content) */}
{config.showDecor && (
<QuoteDecor
className="pointer-events-none absolute z-0
left-0 top-0
w-full h-full"
aria-hidden="true"
/>
)}
<div className={`flex flex-col ${config.gap} relative z-10`}>
<div className={`flex flex-col ${config.avatarGap}`}>
<div className="relative">
{!imageError ? (
<Image
src={currentAvatarSrc}
alt=""
width={64}
height={64}
className={`filter sepia ${config.avatar}`}
loading="eager"
priority
onError={onImageError}
/>
) : (
<div
className={`flex items-center justify-center bg-[var(--color-gray-300)] rounded-full ${config.avatar} text-[var(--color-gray-600)] font-bold`}
>
<span className="text-sm md:text-base lg:text-lg xl:text-xl">
{author
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()}
</span>
</div>
)}
</div>
<blockquote
id={quoteId}
aria-labelledby={authorId}
className="relative"
>
<p
data-qopen="&ldquo;"
data-qclose="&rdquo;"
className={[
"font-bricolage-grotesque font-normal",
config.quote,
"text-[var(--color-content-invert-primary)]",
// give space for the hanging open-quote so it's not clipped:
"pl-[0.6em] -indent-[0.6em]",
// inject quotes
"relative before:content-[attr(data-qopen)] after:content-[attr(data-qclose)]",
// lock quote glyphs to your display face
"before:[font-family:var(--font-bricolage-grotesque)]",
"after:[font-family:var(--font-bricolage-grotesque)]",
].join(" ")}
>
{quote}
</p>
</blockquote>
</div>
<footer className="flex flex-col gap-[var(--space-200)] md:gap-[var(--space-300)] xl:gap-[var(--space-500)]">
<cite
id={authorId}
className={`font-normal ${config.author} text-[var(--color-content-invert-primary)] uppercase not-italic`}
>
{author}
</cite>
{source && (
<p
data-qopen="&ldquo;"
data-qclose="&rdquo;"
className={[
"font-normal",
config.source,
"text-[var(--color-content-invert-primary)] uppercase",
"pl-[0.6em] -indent-[0.6em]",
"relative before:content-[attr(data-qopen)] after:content-[attr(data-qclose)]",
"before:[font-family:var(--font-inter)] after:[font-family:var(--font-inter)]",
].join(" ")}
>
{source}
</p>
)}
</footer>
</div>
</div>
</section>
);
}
QuoteBlockView.displayName = "QuoteBlockView";
export default memo(QuoteBlockView);