Implement use cases page
This commit is contained in:
@@ -110,7 +110,7 @@ const ContentLockupContainer = memo<ContentLockupProps>(
|
||||
titleGroup: "flex flex-col gap-[var(--spacing-scale-008)]",
|
||||
titleContainer: "flex gap-[var(--spacing-scale-008)] items-center",
|
||||
title:
|
||||
"font-bricolage-grotesque font-medium text-[36px] leading-[110%] tracking-[0] md:text-[52px] md:leading-[110%] text-[var(--color-content-default-brand-primary)]",
|
||||
"font-bricolage-grotesque font-medium text-[36px] leading-[110%] tracking-[0] lg:text-[44px] lg:leading-[1.1] text-[var(--color-content-default-brand-primary)]",
|
||||
subtitle:
|
||||
"font-inter font-normal text-[18px] leading-[130%] tracking-[0] md:text-[24px] md:leading-[32px] text-[var(--color-content-default-primary)]",
|
||||
shape:
|
||||
@@ -122,7 +122,7 @@ const ContentLockupContainer = memo<ContentLockupProps>(
|
||||
titleGroup: "flex flex-col gap-[var(--spacing-scale-008)]",
|
||||
titleContainer: "flex gap-[var(--spacing-scale-008)] items-center",
|
||||
title:
|
||||
"font-bricolage-grotesque font-medium text-[36px] leading-[110%] tracking-[0] md:text-[52px] md:leading-[110%] text-[var(--color-content-inverse-primary)]",
|
||||
"font-bricolage-grotesque font-medium text-[36px] leading-[110%] tracking-[0] lg:text-[44px] lg:leading-[1.1] text-[var(--color-content-inverse-primary)]",
|
||||
subtitle:
|
||||
"font-inter font-normal text-[18px] leading-[130%] tracking-[0] md:text-[24px] md:leading-[32px] text-[var(--color-content-inverse-primary)]",
|
||||
shape:
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { memo, useId } from "react";
|
||||
import PageHeaderView from "./PageHeader.view";
|
||||
import type { PageHeaderProps } from "./PageHeader.types";
|
||||
|
||||
/**
|
||||
* Figma: "Type / PageHeader" (21004-15902).
|
||||
* Minimal headline-only: Section/PageHeader (22112-871523); md density **22085-862431** when `sectionMinimal` is set;
|
||||
* Use cases **`lg`** single-line title **21004-24825** when `singleLineTitleFromLg` is set;
|
||||
* **`xl`** headline scale **22085-860408** when `sectionMinimal` (X Large/Display / `--sizing-1600`).
|
||||
*/
|
||||
const PageHeaderContainer = memo<PageHeaderProps>(
|
||||
({
|
||||
title,
|
||||
description,
|
||||
ctaText,
|
||||
ctaHref,
|
||||
headingAlign = "start",
|
||||
sectionMinimal = false,
|
||||
singleLineTitleFromLg = false,
|
||||
titleId: titleIdProp,
|
||||
className = "",
|
||||
}) => {
|
||||
const reactId = useId();
|
||||
const titleId = titleIdProp ?? `${reactId}-page-header-title`;
|
||||
|
||||
return (
|
||||
<PageHeaderView
|
||||
title={title}
|
||||
description={description}
|
||||
ctaText={ctaText}
|
||||
ctaHref={ctaHref}
|
||||
headingAlign={headingAlign}
|
||||
sectionMinimal={sectionMinimal}
|
||||
singleLineTitleFromLg={singleLineTitleFromLg}
|
||||
titleId={titleId}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
PageHeaderContainer.displayName = "PageHeader";
|
||||
|
||||
export default PageHeaderContainer;
|
||||
@@ -0,0 +1,24 @@
|
||||
export interface PageHeaderProps {
|
||||
/** Single line or stacked lines inside one `<h1>` (matches Figma line breaks when centered). */
|
||||
title: string | readonly string[];
|
||||
description?: string;
|
||||
ctaText?: string;
|
||||
ctaHref?: string;
|
||||
/** `center` stacks and centers the headline (Section/PageHeader minimal / use cases). */
|
||||
headingAlign?: "start" | "center";
|
||||
/**
|
||||
* Section/PageHeader minimal density ([22085-862431](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-862431&m=dev)):
|
||||
* md+ **52px** display type and **56px** vertical padding (with **64px** horizontal).
|
||||
*/
|
||||
sectionMinimal?: boolean;
|
||||
/**
|
||||
* When `title` is multiple lines, use one centered line from **`lg`** ([21004-24825](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21004-24825&m=dev)).
|
||||
*/
|
||||
singleLineTitleFromLg?: boolean;
|
||||
titleId?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export type PageHeaderViewProps = Omit<PageHeaderProps, "titleId"> & {
|
||||
titleId: string;
|
||||
};
|
||||
@@ -0,0 +1,106 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment, memo } from "react";
|
||||
import Button from "../../buttons/Button";
|
||||
import type { PageHeaderViewProps } from "./PageHeader.types";
|
||||
|
||||
function PageHeaderView({
|
||||
title,
|
||||
description,
|
||||
ctaText,
|
||||
ctaHref,
|
||||
headingAlign = "start",
|
||||
sectionMinimal = false,
|
||||
singleLineTitleFromLg = false,
|
||||
titleId,
|
||||
className = "",
|
||||
}: PageHeaderViewProps) {
|
||||
const hasCta = Boolean(ctaText?.trim() && ctaHref?.trim());
|
||||
const hasDescription = Boolean(description?.trim());
|
||||
const isCenter = headingAlign === "center";
|
||||
const titleLines = typeof title === "string" ? [title] : title;
|
||||
const collapseTitleAtLg =
|
||||
singleLineTitleFromLg && titleLines.length > 1;
|
||||
|
||||
const lockupAlign = isCenter
|
||||
? "items-center text-center"
|
||||
: "items-start text-[var(--color-content-default-primary)]";
|
||||
const h1Align = isCenter ? "text-center" : "";
|
||||
|
||||
const sectionPadding = sectionMinimal
|
||||
? "py-[var(--spacing-scale-032)] md:py-[var(--spacing-scale-056)]"
|
||||
: "py-[var(--spacing-scale-032)] md:py-[var(--spacing-scale-032)]";
|
||||
|
||||
const titleTypeClasses = sectionMinimal
|
||||
? "font-bricolage-grotesque text-[32px] font-medium leading-[1.1] text-[var(--color-content-default-primary)] md:text-[52px] md:leading-[1.1] lg:text-[52px] lg:leading-[1.1] xl:text-[length:var(--sizing-1600)] xl:leading-[1.1]"
|
||||
: "font-bricolage-grotesque text-[32px] font-medium leading-[1.1] text-[var(--color-content-default-primary)] md:text-[44px] md:leading-[110%] lg:text-[52px]";
|
||||
|
||||
const sectionFigmaNode =
|
||||
sectionMinimal && collapseTitleAtLg
|
||||
? "21004-24825"
|
||||
: sectionMinimal
|
||||
? "22085-862431"
|
||||
: "21004-22394";
|
||||
|
||||
return (
|
||||
<section
|
||||
data-figma-node={sectionFigmaNode}
|
||||
className={`bg-[var(--color-surface-default-primary)] px-[var(--spacing-scale-020)] md:px-[var(--spacing-scale-064)] ${sectionPadding} ${className}`.trim()}
|
||||
>
|
||||
<div
|
||||
className={`mx-auto flex w-full max-w-[1440px] flex-col gap-[var(--spacing-scale-024)] ${isCenter ? "items-center" : ""}`}
|
||||
>
|
||||
<div
|
||||
className={`flex w-full flex-col gap-[var(--spacing-scale-020)] ${lockupAlign}`}
|
||||
>
|
||||
<h1
|
||||
id={titleId}
|
||||
className={`${titleTypeClasses} ${h1Align}${collapseTitleAtLg ? " lg:whitespace-nowrap" : ""}`.trim()}
|
||||
>
|
||||
{titleLines.length === 1 ? (
|
||||
titleLines[0]
|
||||
) : collapseTitleAtLg ? (
|
||||
titleLines.map((line, index) => (
|
||||
<Fragment key={`${index}-${line}`}>
|
||||
{index > 0 ? (
|
||||
<span className="hidden lg:inline">{" "}</span>
|
||||
) : null}
|
||||
<span className="block lg:inline">{line}</span>
|
||||
</Fragment>
|
||||
))
|
||||
) : (
|
||||
titleLines.map((line, index) => (
|
||||
<span key={`${index}-${line}`} className="block">
|
||||
{line}
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
</h1>
|
||||
{hasDescription ? (
|
||||
<p className="font-inter text-[18px] font-normal leading-[28px] text-[var(--color-content-default-primary)] lg:text-[24px] lg:leading-[28px]">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{hasCta ? (
|
||||
<div
|
||||
className={`flex ${isCenter ? "justify-center" : "justify-start"}`}
|
||||
>
|
||||
<Button
|
||||
href={ctaHref}
|
||||
buttonType="filled"
|
||||
palette="inverse"
|
||||
size="large"
|
||||
>
|
||||
{ctaText}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
PageHeaderView.displayName = "PageHeaderView";
|
||||
|
||||
export default memo(PageHeaderView);
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./PageHeader.container";
|
||||
export type { PageHeaderProps } from "./PageHeader.types";
|
||||
@@ -10,6 +10,11 @@ interface SectionHeaderProps {
|
||||
variant?: SectionHeaderVariantValue;
|
||||
/** When set with `variant="multi-line"`, large screens show three title lines (Figma SectionCardSteps). */
|
||||
stackedDesktopLines?: readonly [string, string, string];
|
||||
/**
|
||||
* With `variant="multi-line"`, keep **Rule stack** desktop type: title **32/40** at `lg`, **40/52** at `xl`;
|
||||
* subtitle **18 / 1.3** at `lg`, **24/32** at `xl`, **left-aligned** in its column from `lg` (Figma **22085:860413**).
|
||||
*/
|
||||
ruleStackDesktopTypeScale?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,6 +28,7 @@ const SectionHeader = memo<SectionHeaderProps>(
|
||||
titleLg,
|
||||
variant: variantProp = "default",
|
||||
stackedDesktopLines,
|
||||
ruleStackDesktopTypeScale = false,
|
||||
}) => {
|
||||
const variant = variantProp;
|
||||
const useStackedDesktop =
|
||||
@@ -47,7 +53,9 @@ const SectionHeader = memo<SectionHeaderProps>(
|
||||
<h2
|
||||
className={
|
||||
variant === "multi-line"
|
||||
? "font-bricolage-grotesque font-bold text-[28px] leading-[36px] md:text-[32px] md:leading-[40px] lg:w-[410px] lg:text-left xl:text-[40px] xl:leading-[52px] text-[var(--color-content-default-primary)]"
|
||||
? ruleStackDesktopTypeScale
|
||||
? "font-bricolage-grotesque font-bold text-[28px] leading-[36px] md:text-[32px] md:leading-[40px] lg:w-full lg:max-w-none lg:text-left lg:text-[32px] lg:leading-[40px] xl:text-[40px] xl:leading-[52px] text-[var(--color-content-default-primary)]"
|
||||
: "font-bricolage-grotesque font-bold text-[28px] leading-[36px] md:text-[32px] md:leading-[40px] lg:w-[410px] lg:text-left xl:text-[40px] xl:leading-[52px] text-[var(--color-content-default-primary)]"
|
||||
: "font-bricolage-grotesque font-bold text-[28px] leading-[36px] sm:text-[32px] sm:leading-[40px] lg:text-[32px] lg:leading-[40px] lg:w-[369px] lg:pr-[var(--spacing-scale-096)] xl:text-[40px] xl:leading-[52px] xl:w-[452px] xl:pr-[var(--spacing-scale-096)] text-[var(--color-content-default-primary)]"
|
||||
}
|
||||
>
|
||||
@@ -68,14 +76,18 @@ const SectionHeader = memo<SectionHeaderProps>(
|
||||
<div
|
||||
className={
|
||||
variant === "multi-line"
|
||||
? "lg:w-[50%] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-end lg:ml-[var(--spacing-scale-016)] xl:ml-0 xl:w-[50%] xl:h-[156px] xl:flex xl:items-center xl:justify-end"
|
||||
? ruleStackDesktopTypeScale
|
||||
? "lg:w-[50%] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-start lg:ml-[var(--spacing-scale-016)] xl:ml-0 xl:w-[50%] xl:h-[156px] xl:flex xl:items-center xl:justify-start"
|
||||
: "lg:w-[50%] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-end lg:ml-[var(--spacing-scale-016)] xl:ml-0 xl:w-[50%] xl:h-[156px] xl:flex xl:items-center xl:justify-end"
|
||||
: "lg:w-[928px] lg:h-[var(--spacing-scale-120)] lg:flex lg:items-center lg:justify-end xl:h-[156px] xl:flex xl:items-center xl:justify-end"
|
||||
}
|
||||
>
|
||||
<p
|
||||
className={
|
||||
variant === "multi-line"
|
||||
? "font-inter font-normal text-[14px] leading-[20px] md:text-[18px] md:leading-[130%] xl:text-[24px] xl:leading-[32px] text-[var(--color-content-default-tertiary)] lg:text-right"
|
||||
? ruleStackDesktopTypeScale
|
||||
? "font-inter font-normal text-[14px] leading-[20px] md:text-[18px] md:leading-[130%] lg:text-left lg:text-[18px] lg:leading-[130%] text-[var(--color-content-default-tertiary)] xl:text-[24px] xl:leading-[32px]"
|
||||
: "font-inter font-normal text-[14px] leading-[20px] md:text-[18px] md:leading-[130%] xl:text-[24px] xl:leading-[32px] text-[var(--color-content-default-tertiary)] lg:text-right"
|
||||
: "font-inter font-normal text-[18px] leading-[130%] sm:text-[18px] sm:leading-[32px] lg:text-[24px] lg:leading-[32px] xl:text-[32px] xl:leading-[40px] xl:text-right text-[#484848] sm:text-[var(--color-content-default-tertiary)] lg:text-[var(--color-content-default-tertiary)] xl:text-[var(--color-content-default-tertiary)] tracking-[0px]"
|
||||
}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { memo, useId } from "react";
|
||||
import TripleStepView from "./TripleStep.view";
|
||||
import type { TripleStepProps } from "./TripleStep.types";
|
||||
|
||||
/**
|
||||
* Figma: **Section / Triple Step** ([22084-859405](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22084-859405&m=dev)); type baseline ([22112-871527](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22112-871527&m=dev)); **md+** two-column + **`triple-step.svg`**.
|
||||
*/
|
||||
const TripleStepContainer = memo<TripleStepProps>((props) => {
|
||||
const reactId = useId();
|
||||
const headingId = `${reactId}-triple-step-heading`;
|
||||
|
||||
return <TripleStepView {...props} headingId={headingId} />;
|
||||
});
|
||||
|
||||
TripleStepContainer.displayName = "TripleStep";
|
||||
|
||||
export default TripleStepContainer;
|
||||
@@ -0,0 +1,16 @@
|
||||
export interface TripleStepStep {
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface TripleStepProps {
|
||||
heading: string;
|
||||
steps: TripleStepStep[];
|
||||
ctaText: string;
|
||||
ctaHref: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface TripleStepViewProps extends TripleStepProps {
|
||||
headingId: string;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { memo } from "react";
|
||||
import { getAssetPath } from "../../../../lib/assetUtils";
|
||||
import AssetIcon from "../../asset/icon";
|
||||
import Button from "../../buttons/Button";
|
||||
import type { TripleStepViewProps } from "./TripleStep.types";
|
||||
|
||||
const TRIPLE_STEP_NUMERIC_ICONS = [
|
||||
"numeric_1_circle",
|
||||
"numeric_2_circle",
|
||||
"numeric_3_circle",
|
||||
] as const;
|
||||
|
||||
function TripleStepView({
|
||||
heading,
|
||||
steps,
|
||||
ctaText,
|
||||
ctaHref,
|
||||
headingId,
|
||||
className = "",
|
||||
}: TripleStepViewProps) {
|
||||
/** Decorative column art — `public/assets/shapes/triple-step.svg` (288×576 viewBox). */
|
||||
const shapeSrc = getAssetPath("assets/shapes/triple-step.svg");
|
||||
|
||||
return (
|
||||
<section
|
||||
data-figma-node="22084-859405"
|
||||
aria-labelledby={headingId}
|
||||
className={`bg-transparent p-[var(--spacing-scale-032)] md:px-[var(--spacing-scale-032)] md:py-[var(--spacing-scale-048)] lg:px-[var(--spacing-scale-064)] lg:py-[var(--spacing-scale-048)] ${className}`.trim()}
|
||||
>
|
||||
<div className="mx-auto grid w-full min-w-0 max-w-[560px] grid-cols-1 gap-[var(--spacing-scale-032)] md:max-w-[1400px] md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] md:items-stretch md:gap-[var(--spacing-scale-060)] lg:items-center">
|
||||
<div className="flex w-full min-w-0 flex-col gap-[var(--spacing-scale-040)] break-words md:self-start lg:self-center">
|
||||
<h2
|
||||
id={headingId}
|
||||
className="font-bricolage-grotesque text-[length:var(--text-medium-heading)] font-bold leading-[length:var(--text-medium-heading--line-height)] text-[var(--color-content-default-primary)] md:text-[length:var(--text-large-heading)] md:leading-[length:var(--text-large-heading--line-height)]"
|
||||
>
|
||||
{heading}
|
||||
</h2>
|
||||
{steps.map((step, index) => (
|
||||
<div
|
||||
key={step.title}
|
||||
className="flex flex-col items-start gap-[var(--spacing-scale-016)]"
|
||||
>
|
||||
<AssetIcon
|
||||
name={
|
||||
TRIPLE_STEP_NUMERIC_ICONS[Math.min(index, 2)]
|
||||
}
|
||||
size={32}
|
||||
className="shrink-0"
|
||||
/>
|
||||
<div className="flex min-w-0 flex-col gap-1 text-[var(--color-content-default-primary)]">
|
||||
<p className="font-bricolage-grotesque text-[18px] font-medium leading-[22px]">
|
||||
{step.title}
|
||||
</p>
|
||||
<p className="font-inter text-[length:var(--text-small-paragraph)] font-normal leading-[length:var(--text-small-paragraph--line-height)]">
|
||||
{step.body}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex justify-start">
|
||||
<Button
|
||||
href={ctaHref}
|
||||
buttonType="outline"
|
||||
palette="default"
|
||||
size="medium"
|
||||
className="max-md:!px-[var(--spacing-scale-012)] max-md:!py-[var(--spacing-scale-010)] max-md:!text-[14px] max-md:!leading-[18px] md:!p-[var(--spacing-scale-012)] md:!text-[16px] md:!leading-5 md:!gap-[var(--spacing-scale-006)]"
|
||||
>
|
||||
{ctaText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="hidden min-h-0 min-w-0 w-full md:flex md:items-center md:justify-center"
|
||||
aria-hidden
|
||||
>
|
||||
<Image
|
||||
src={shapeSrc}
|
||||
alt=""
|
||||
width={288}
|
||||
height={576}
|
||||
unoptimized
|
||||
className="pointer-events-none h-auto w-full max-w-full min-w-0 select-none object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
TripleStepView.displayName = "TripleStepView";
|
||||
|
||||
export default memo(TripleStepView);
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default } from "./TripleStep.container";
|
||||
export type { TripleStepProps, TripleStepStep } from "./TripleStep.types";
|
||||
@@ -5,7 +5,8 @@ import TripleTextBlockView from "./TripleTextBlock.view";
|
||||
import type { TripleTextBlockProps } from "./TripleTextBlock.types";
|
||||
|
||||
/**
|
||||
* Figma: "Type / TripleTextBlock" stacked 22137:890676; lg 22128:888715; xl 22135:889705.
|
||||
* Figma: "Type / TripleTextBlock" — use cases **`lg` 22037-26994**, **`xl` 22085-860414**;
|
||||
* **`md` 22085-862437**; stacked 22137:890676; lg 22128:888715; xl 22135:889705 (default).
|
||||
*/
|
||||
const TripleTextBlockContainer = memo<TripleTextBlockProps>((props) => {
|
||||
const headingId = useId();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export interface TripleTextBlockColumn {
|
||||
title: string;
|
||||
description: string;
|
||||
/** Optional second paragraph under `description` (e.g. use cases baseline multi-paragraph lockup). */
|
||||
descriptionSecondary?: string;
|
||||
/**
|
||||
* lg+ three-column layout (Figma 22128:888715). When either `lgTitle` or `lgDescription`
|
||||
* is set, stacked breakpoints use `title`/`description` and lg uses these (missing side falls back).
|
||||
@@ -16,6 +18,12 @@ export interface TripleTextBlockProps {
|
||||
ctaText?: string;
|
||||
ctaHref?: string;
|
||||
className?: string;
|
||||
/**
|
||||
* `useCases`: Figma use cases TripleText **`lg`** ([22037-26994](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22037-26994&m=dev));
|
||||
* **`xl`** ([22085-860414](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-860414&m=dev));
|
||||
* `md` ([22085-862437](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-862437&m=dev)); lg 3-col **22128-888715**.
|
||||
*/
|
||||
layoutPreset?: "default" | "useCases";
|
||||
}
|
||||
|
||||
export interface TripleTextBlockViewProps extends TripleTextBlockProps {
|
||||
|
||||
@@ -12,11 +12,35 @@ function columnUsesLargeBreakpointCopy(column: TripleTextBlockColumn): boolean {
|
||||
return column.lgTitle !== undefined || column.lgDescription !== undefined;
|
||||
}
|
||||
|
||||
function TripleTextUseCasesColumn({ column }: { column: TripleTextBlockColumn }) {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-[var(--spacing-scale-020)] lg:gap-0 xl:gap-[var(--spacing-scale-020)]">
|
||||
<div className="flex flex-col gap-[var(--spacing-scale-008)] lg:gap-[var(--spacing-scale-004)] xl:gap-[var(--spacing-scale-008)]">
|
||||
<h3 className="text-left font-bricolage-grotesque text-[24px] font-medium leading-8 text-[var(--color-content-default-primary,white)] md:text-[32px] md:leading-[1.1] lg:text-[18px] lg:leading-[var(--spacing-scale-022)] xl:text-[32px] xl:leading-[1.1]">
|
||||
{column.title}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-[var(--spacing-scale-024)] font-inter text-[16px] font-normal leading-6 text-[var(--color-content-default-secondary)] md:text-[24px] md:leading-8 lg:gap-[var(--spacing-scale-020)] lg:text-[14px] lg:leading-5 xl:gap-[var(--spacing-scale-032)] xl:text-[24px] xl:leading-8">
|
||||
<p>{column.description}</p>
|
||||
{column.descriptionSecondary ? (
|
||||
<p>{column.descriptionSecondary}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TripleTextBlockColumnLockup({
|
||||
column,
|
||||
layoutPreset,
|
||||
}: {
|
||||
column: TripleTextBlockColumn;
|
||||
layoutPreset: "default" | "useCases";
|
||||
}) {
|
||||
if (layoutPreset === "useCases") {
|
||||
return <TripleTextUseCasesColumn column={column} />;
|
||||
}
|
||||
|
||||
const dual = columnUsesLargeBreakpointCopy(column);
|
||||
const lgSubtitle = column.lgTitle ?? column.title;
|
||||
const lgBody = column.lgDescription ?? column.description;
|
||||
@@ -55,7 +79,11 @@ function TripleTextBlockColumnLockup({
|
||||
}
|
||||
|
||||
/**
|
||||
* Figma: "Type / TripleTextBlock" stacked **22137:890676**; lg 3-col **22128-888715**; xl typography + horizontal inset scale/160 **22135:889705** (Subtitle 32 Small/Display, Body X Large/Paragraph 24 / lh 32; section px scale/160, py scale/064).
|
||||
* Section horizontal padding adds **+ Scale/096** below `xl` (outer frame inset); **use cases `xl`** uses **Scale/160** only ([22085:860414](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-860414&m=dev)).
|
||||
*
|
||||
* Figma: use cases **`lg`** [22037:26994](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22037-26994&m=dev);
|
||||
* **`md`** [22085:862437](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-862437&m=dev); stacked **22137:890676**;
|
||||
* lg 3-col **22128:888715**; xl **22135:889705** (default preset).
|
||||
*/
|
||||
function TripleTextBlockView({
|
||||
title = "",
|
||||
@@ -64,39 +92,71 @@ function TripleTextBlockView({
|
||||
ctaHref,
|
||||
headingId,
|
||||
className = "",
|
||||
layoutPreset = "default",
|
||||
}: TripleTextBlockViewProps) {
|
||||
const sectionTitle = title.trim();
|
||||
const hasSectionTitle = sectionTitle.length > 0;
|
||||
const isUseCases = layoutPreset === "useCases";
|
||||
|
||||
return (
|
||||
<section
|
||||
{...(isUseCases ? { "data-figma-node": "22085-860414" } : {})}
|
||||
aria-labelledby={hasSectionTitle ? headingId : undefined}
|
||||
className={`bg-black px-[var(--spacing-scale-032)] py-[var(--spacing-scale-064)] md:px-[var(--spacing-scale-096)] md:py-[var(--spacing-scale-064)] xl:px-[var(--spacing-scale-160)] ${className}`.trim()}
|
||||
className={`bg-black px-[calc(var(--spacing-scale-032)+var(--spacing-scale-096))] py-[var(--spacing-scale-064)] md:px-[calc(var(--spacing-scale-096)+var(--spacing-scale-096))] md:py-[var(--spacing-scale-064)] lg:px-[calc(var(--spacing-scale-096)+var(--spacing-scale-096))] lg:py-[var(--spacing-scale-064)] ${
|
||||
isUseCases
|
||||
? "xl:px-[var(--spacing-scale-160)]"
|
||||
: "xl:px-[calc(var(--spacing-scale-160)+var(--spacing-scale-096))]"
|
||||
} xl:py-[var(--spacing-scale-064)] ${className}`.trim()}
|
||||
>
|
||||
<div className="mx-auto flex w-full max-w-[1440px] flex-col items-start gap-[var(--spacing-scale-032)]">
|
||||
<div
|
||||
className={
|
||||
isUseCases
|
||||
? "mx-auto flex w-full max-w-[1440px] flex-col items-start gap-[var(--spacing-scale-032)] md:gap-[var(--spacing-scale-048)] lg:items-center lg:gap-[var(--spacing-scale-064)]"
|
||||
: "mx-auto flex w-full max-w-[1440px] flex-col items-start gap-[var(--spacing-scale-032)]"
|
||||
}
|
||||
>
|
||||
{hasSectionTitle ? (
|
||||
<h2
|
||||
id={headingId}
|
||||
className="w-full text-left font-bricolage-grotesque text-[32px] font-medium leading-[1.1] text-[var(--color-content-default-primary,white)]"
|
||||
className={
|
||||
isUseCases
|
||||
? "w-full text-left font-bricolage-grotesque text-[28px] font-bold leading-9 text-[var(--color-content-default-primary,white)] md:text-[32px] md:leading-[40px] lg:mx-auto lg:max-w-[693px] lg:text-center lg:text-[36px] lg:font-extrabold lg:leading-[44px] xl:text-[40px] xl:leading-[52px] xl:font-bold"
|
||||
: "w-full text-left font-bricolage-grotesque text-[32px] font-medium leading-[1.1] text-[var(--color-content-default-primary,white)]"
|
||||
}
|
||||
>
|
||||
{sectionTitle}
|
||||
</h2>
|
||||
) : null}
|
||||
<div className="flex w-full flex-col gap-[var(--spacing-scale-032)] lg:flex-row lg:items-start lg:gap-[var(--spacing-scale-032)]">
|
||||
<div
|
||||
className={
|
||||
isUseCases
|
||||
? "flex w-full flex-col gap-[var(--spacing-scale-048)] lg:flex-row lg:items-start lg:gap-[var(--spacing-scale-032)]"
|
||||
: "flex w-full flex-col gap-[var(--spacing-scale-032)] lg:flex-row lg:items-start lg:gap-[var(--spacing-scale-032)]"
|
||||
}
|
||||
>
|
||||
{columns.map((column, index) => (
|
||||
<div
|
||||
key={`${column.title}-${column.lgTitle ?? ""}-${index}`}
|
||||
className="w-full min-w-0 lg:flex-1"
|
||||
>
|
||||
<TripleTextBlockColumnLockup column={column} />
|
||||
<TripleTextBlockColumnLockup
|
||||
column={column}
|
||||
layoutPreset={layoutPreset}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{ctaText ? (
|
||||
<div className="flex w-full justify-start">
|
||||
<div
|
||||
className={
|
||||
isUseCases
|
||||
? "flex w-full justify-start lg:justify-center"
|
||||
: "flex w-full justify-start"
|
||||
}
|
||||
>
|
||||
<Button
|
||||
buttonType="filled"
|
||||
palette="inverse"
|
||||
buttonType={isUseCases ? "outline" : "filled"}
|
||||
palette={isUseCases ? "default" : "inverse"}
|
||||
size="large"
|
||||
href={ctaHref}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user