Start organizational migration

This commit is contained in:
adilallo
2026-02-05 18:21:56 -07:00
parent 69074b23f3
commit db3c0274f6
161 changed files with 145 additions and 145 deletions
@@ -0,0 +1,136 @@
"use client";
import { memo } from "react";
import ContentLockupView from "./ContentLockup.view";
import type { ContentLockupProps, VariantStyle } from "./ContentLockup.types";
import { normalizeContentLockupVariant, normalizeAlignment } from "../../../../lib/propNormalization";
const ContentLockupContainer = memo<ContentLockupProps>(
({
title,
subtitle,
description,
ctaText,
buttonClassName = "",
variant: variantProp = "hero",
linkText,
linkHref,
alignment: alignmentProp = "center",
titleId,
}) => {
// Normalize props to handle both PascalCase (Figma) and lowercase (codebase)
const variant = normalizeContentLockupVariant(variantProp);
const alignment = normalizeAlignment(alignmentProp);
// Variant-specific styling
const variantStyles: Record<string, VariantStyle> = {
hero: {
container:
"flex flex-col gap-[var(--spacing-scale-006)] sm:gap-[var(--spacing-scale-012)] md:gap-[var(--spacing-scale-020)] lg:gap-[var(--spacing-scale-020)] relative z-10",
textContainer:
"flex flex-col md:gap-[var(--spacing-scale-004)] lg:gap-[var(--spacing-scale-008)] xl:gap-[var(--spacing-scale-020)]",
titleGroup: "flex flex-col xl:gap-0",
titleContainer:
"flex gap-[var(--spacing-scale-008)] xl:gap-[var(--spacing-scale-010)] items-center",
title:
"font-bricolage-grotesque font-medium text-[32px] leading-[32px] sm:text-[52px] sm:leading-[52px] md:text-[44px] md:leading-[44px] lg:text-[64px] lg:leading-[64px] xl:text-[96px] xl:leading-[110%] text-[var(--color-content-inverse-primary)]",
subtitle:
"font-bricolage-grotesque font-medium text-[32px] leading-[32px] sm:text-[52px] sm:leading-[52px] md:text-[44px] md:leading-[44px] lg:text-[64px] lg:leading-[64px] xl:text-[96px] xl:leading-[110%] text-[var(--color-content-inverse-primary)]",
description:
"font-inter font-normal text-[18px] leading-[130%] lg:text-[24px] lg:leading-[32px] xl:text-[32px] xl:leading-[40px] text-[var(--color-content-inverse-primary)]",
shape:
"w-[27.2px] h-[27.2px] md:w-[34px] md:h-[34px] lg:w-[50px] lg:h-[50px]",
},
feature: {
container: "flex flex-col gap-[var(--spacing-scale-012)] relative z-10",
textContainer: "flex flex-col gap-[var(--spacing-scale-012)]",
titleGroup: "flex flex-col gap-[var(--spacing-scale-012)]",
titleContainer: "flex gap-[var(--spacing-scale-008)] items-center",
title:
"font-bricolage-grotesque font-medium text-[32px] leading-[130%] tracking-[0] text-[var(--color-content-default-primary)]",
subtitle:
"font-space-grotesk font-normal text-[20px] leading-[130%] tracking-[0] text-[var(--color-content-default-primary)]",
description:
"font-inter font-normal text-[16px] leading-[140%] lg:text-[18px] lg:leading-[150%] xl:text-[20px] xl:leading-[160%] text-[var(--color-content-secondary)]",
shape:
"w-[20px] h-[20px] md:w-[24px] md:h-[24px] lg:w-[28px] lg:h-[28px]",
},
learn: {
container:
"flex flex-col gap-[var(--spacing-scale-012)] relative z-10 pt-[var(--spacing-scale-016)] pb-[var(--spacing-scale-016)] px-[var(--spacing-scale-020)] sm:pt-[var(--spacing-scale-040)] sm:pb-0 md:pt-[var(--spacing-scale-056)] md:px-[var(--spacing-scale-032)] lg:pt-[var(--spacing-scale-056)] lg:px-[var(--spacing-scale-064)]",
textContainer:
"flex flex-col gap-[var(--spacing-scale-012)] md:gap-[var(--spacing-scale-016)]",
titleGroup:
"flex flex-col gap-[var(--spacing-scale-012)] md:gap-[var(--spacing-scale-016)] lg:gap-[var(--spacing-scale-008)]",
titleContainer: "flex gap-[var(--spacing-scale-008)] items-center",
title:
"font-bricolage-grotesque font-medium text-[28px] leading-[36px] tracking-[0] md:text-[44px] md:leading-[110%] lg:text-[52px] text-[var(--color-content-default-primary)]",
subtitle:
"font-space-grotesk font-normal text-[16px] leading-[24px] tracking-[0] lg:text-[24px] lg:leading-[28px] text-[var(--color-content-default-primary)]",
description:
"font-inter font-normal text-[16px] leading-[140%] lg:text-[18px] lg:leading-[150%] xl:text-[20px] xl:leading-[160%] text-[var(--color-content-secondary)]",
shape:
"w-[20px] h-[20px] md:w-[24px] md:h-[24px] lg:w-[28px] lg:h-[28px]",
},
ask: {
container: "flex flex-col gap-[var(--spacing-scale-008)] relative z-10",
textContainer: "flex flex-col gap-[var(--spacing-scale-008)]",
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-[44px] md:leading-[110%] xl:text-[52px] xl:leading-[110%] 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:
"w-[16px] h-[16px] md:w-[20px] md:h-[20px] lg:w-[24px] lg:h-[24px]",
},
"ask-inverse": {
container: "flex flex-col gap-[var(--spacing-scale-008)] relative z-10",
textContainer: "flex flex-col gap-[var(--spacing-scale-008)]",
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-[44px] md:leading-[110%] xl:text-[52px] xl:leading-[110%] 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:
"w-[16px] h-[16px] md:w-[20px] md:h-[20px] lg:w-[24px] lg:h-[24px]",
},
modal: {
container:
"flex flex-col gap-[var(--spacing-scale-008)] items-start justify-center py-[12px] relative w-full",
textContainer: "flex flex-col gap-[var(--spacing-scale-008)] w-full",
titleGroup: "flex flex-col gap-[var(--spacing-scale-008)] w-full",
titleContainer: "flex items-center justify-start w-full",
title:
"font-bricolage-grotesque font-bold text-[28px] leading-[36px] tracking-[0] text-[var(--color-content-default-primary)] text-left",
subtitle:
"font-inter font-normal text-[16px] leading-[24px] tracking-[0] text-[var(--color-content-default-tertiary)] text-left",
description:
"font-inter font-normal text-[16px] leading-[24px] tracking-[0] text-[var(--color-content-default-tertiary)] text-left",
shape: "w-[16px] h-[16px]",
},
};
const styles = variantStyles[variant] || variantStyles.hero;
return (
<ContentLockupView
title={title}
subtitle={subtitle}
description={description}
ctaText={ctaText}
buttonClassName={buttonClassName}
variant={variant}
linkText={linkText}
linkHref={linkHref}
alignment={alignment}
titleId={titleId}
styles={styles}
/>
);
},
);
ContentLockupContainer.displayName = "ContentLockup";
export default ContentLockupContainer;
@@ -0,0 +1,67 @@
export type ContentLockupVariantValue =
| "hero"
| "feature"
| "learn"
| "ask"
| "ask-inverse"
| "modal"
| "Hero"
| "Feature"
| "Learn"
| "Ask"
| "Ask-Inverse"
| "Modal";
export type ContentLockupAlignmentValue = "center" | "left" | "Center" | "Left";
export interface ContentLockupProps {
title?: string;
subtitle?: string;
description?: string;
ctaText?: string;
ctaHref?: string;
buttonClassName?: string;
/**
* Content lockup variant. Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses PascalCase, codebase uses lowercase - both are supported.
*/
variant?: ContentLockupVariantValue;
linkText?: string;
linkHref?: string;
/**
* Text alignment. Accepts both lowercase and PascalCase (case-insensitive).
* Figma uses PascalCase, codebase uses lowercase - both are supported.
*/
alignment?: ContentLockupAlignmentValue;
/**
* Optional id to attach to the primary title heading.
* Useful when a parent section uses aria-labelledby.
*/
titleId?: string;
}
export interface VariantStyle {
container: string;
textContainer: string;
titleGroup: string;
titleContainer: string;
title: string;
subtitle: string;
description?: string;
shape: string;
}
export interface ContentLockupViewProps {
title?: string;
subtitle?: string;
description?: string;
ctaText?: string;
ctaHref?: string;
buttonClassName: string;
variant: "hero" | "feature" | "learn" | "ask" | "ask-inverse" | "modal";
linkText?: string;
linkHref?: string;
alignment: "center" | "left";
titleId?: string;
styles: VariantStyle;
}
@@ -0,0 +1,120 @@
"use client";
import { memo } from "react";
import Button from "../../buttons/Button";
import { getAssetPath } from "../../../../lib/assetUtils";
import type { ContentLockupViewProps } from "./ContentLockup.types";
function ContentLockupView({
title,
subtitle,
description,
ctaText,
buttonClassName,
variant,
linkText,
linkHref,
alignment,
titleId,
styles,
}: ContentLockupViewProps) {
return (
<div className={styles.container}>
{variant === "ask" || variant === "ask-inverse" || variant === "modal" ? (
/* Simplified structure for ask and modal variants */
<div
className={`${styles.titleGroup} ${
alignment === "left" || variant === "modal"
? "text-left"
: "text-center"
}`}
>
<div
className={`${styles.titleContainer} ${
alignment === "left" || variant === "modal"
? "justify-start"
: "justify-center"
}`}
>
{title ? (
<h1 id={titleId} className={styles.title}>
{title}
</h1>
) : null}
</div>
{subtitle ? <h2 className={styles.subtitle}>{subtitle}</h2> : null}
{variant === "modal" && description && (
<p className={styles.description}>{description}</p>
)}
</div>
) : (
/* Full structure for other variants */
<div className={styles.textContainer}>
{/* Title and subtitle group */}
<div className={styles.titleGroup}>
{/* Title container */}
<div className={styles.titleContainer}>
{title ? (
<h1 id={titleId} className={styles.title}>
{title}
</h1>
) : null}
{variant === "hero" && (
<img
src={getAssetPath("assets/Shapes_1.svg")}
alt=""
className={styles.shape}
role="presentation"
/>
)}
</div>
{/* Subtitle */}
{subtitle ? <h2 className={styles.subtitle}>{subtitle}</h2> : null}
</div>
{/* Description */}
{description && <p className={styles.description}>{description}</p>}
</div>
)}
{/* Link for feature variant */}
{variant === "feature" && linkText && (
<a
href={linkHref || "#"}
className="font-inter font-medium text-[16px] leading-[20px] underline text-[var(--color-content-default-primary)] hover:text-gray-300 transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-surface-default-brand-royal)] focus:ring-offset-2 focus:ring-offset-[#171717] rounded-sm px-1 py-0.5"
>
{linkText}
</a>
)}
{/* CTA Button */}
{ctaText && (
<div className="flex justify-start">
{/* Small button for xsm and sm breakpoints */}
<div className="block md:hidden">
<Button variant={variant === "hero" ? "filled" : "filled-inverse"} size="small">
{ctaText}
</Button>
</div>
{/* Large button for md and lg breakpoints */}
<div className="hidden md:block xl:hidden">
<Button variant={variant === "hero" ? "filled" : "filled-inverse"} size="large" className={buttonClassName}>
{ctaText}
</Button>
</div>
{/* XLarge button for xl breakpoint */}
<div className="hidden xl:block">
<Button variant={variant === "hero" ? "filled" : "filled-inverse"} size="xlarge">
{ctaText}
</Button>
</div>
</div>
)}
</div>
);
}
ContentLockupView.displayName = "ContentLockupView";
export default memo(ContentLockupView);
@@ -0,0 +1,2 @@
export { default } from "./ContentLockup.container";
export type { ContentLockupProps } from "./ContentLockup.types";