Implement use cases page

This commit is contained in:
adilallo
2026-05-17 21:41:54 -06:00
parent b6b9b63608
commit 450da4d8ab
78 changed files with 1870 additions and 118 deletions
@@ -32,6 +32,7 @@ const VARIANT_STYLES: Record<
},
};
/** Figma **Section/AskOrganizer** [18116:15960](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=18116-15960&m=dev) (`lg` shell + type + button). */
const AskOrganizerContainer = memo<AskOrganizerProps>(
({
title,
@@ -28,6 +28,7 @@ function AskOrganizerView({
aria-labelledby={labelledBy}
aria-label={labelledBy ? undefined : ariaLabel}
tabIndex={-1}
data-figma-node="18116-15960"
>
<div className={`flex flex-col ${contentGap}`}>
{/* Content Lockup */}
@@ -41,13 +42,15 @@ function AskOrganizerView({
/>
{/* Button */}
<div className={buttonContainerClass}>
<div
className={`${buttonContainerClass} flex-wrap gap-y-[var(--spacing-scale-016)]`}
>
<Button
{...(buttonHref ? { href: buttonHref } : {})}
size="large"
buttonType="filled"
palette={variant === "inverse" ? "inverse" : "default"}
className="md:!px-[var(--spacing-scale-020)] md:!py-[var(--spacing-scale-012)] md:!text-[24px] md:!leading-[28px]"
className="!px-[var(--spacing-scale-016)] !py-[var(--spacing-scale-012)]"
onClick={onContactClick}
ariaLabel={ariaLabel}
data-testid="ask-organizer-cta"
@@ -10,11 +10,17 @@ import type { GovernanceTemplateCatalogEntry } from "../../../../lib/templates/g
export interface GovernanceTemplateGridProps {
entries: GovernanceTemplateCatalogEntry[];
onTemplateClick: (_slug: string) => void;
/**
* When true, use project **`md`** (640px) for a 2-column grid (e.g. `/use-cases`).
* Default keeps the template shell break at **768px**.
*/
twoColumnsFromMd?: boolean;
}
export function GovernanceTemplateGrid({
entries,
onTemplateClick,
twoColumnsFromMd = false,
}: GovernanceTemplateGridProps) {
const [isMounted, setIsMounted] = useState(false);
@@ -44,13 +50,21 @@ export function GovernanceTemplateGrid({
: "M"
: "M";
return (
<div
className={`
const gridLayoutClasses = twoColumnsFromMd
? `
flex flex-col gap-[18px]
md:grid md:grid-cols-2 md:gap-[18px]
lg:gap-[24px]
`
: `
flex flex-col gap-[18px]
min-[768px]:grid min-[768px]:grid-cols-2 min-[768px]:gap-[18px]
min-[1024px]:gap-[24px]
`}
`;
return (
<div
className={gridLayoutClasses}
>
{entries.map((card) => (
<Rule
@@ -58,19 +72,20 @@ export function GovernanceTemplateGrid({
title={card.title}
description={card.description}
recommended={card.recommended === true}
templateGridFigmaShell
size={cardSize}
className={`
select-none
cursor-pointer
max-[639px]:rounded-[var(--measures-radius-200,8px)]
min-[640px]:max-[1023px]:rounded-[var(--measures-radius-300,12px)]
min-[1024px]:rounded-[var(--radius-measures-radius-small)]
min-[1024px]:rounded-[var(--radius-measures-radius-large)]
max-[639px]:pb-[24px] max-[639px]:pt-[12px] max-[639px]:px-[12px]
min-[640px]:max-[1023px]:p-[24px]
min-[1024px]:max-[1439px]:p-[16px]
min-[1024px]:max-[1439px]:p-[24px]
min-[1440px]:p-[24px]
max-[1023px]:gap-[18px]
min-[1024px]:max-[1439px]:gap-[12px]
min-[1024px]:max-[1439px]:gap-[10px]
min-[1440px]:gap-[10px]
`}
icon={
@@ -84,7 +99,7 @@ export function GovernanceTemplateGrid({
cursor-inherit
max-[639px]:w-[40px] max-[639px]:h-[40px]
min-[640px]:max-[1023px]:w-[56px] min-[640px]:max-[1023px]:h-[56px]
min-[1024px]:max-[1439px]:w-[56px] min-[1024px]:max-[1439px]:h-[56px]
min-[1024px]:max-[1439px]:w-[90px] min-[1024px]:max-[1439px]:h-[90px]
min-[1440px]:w-[90px] min-[1440px]:h-[90px]
"
/>
@@ -1,14 +1,28 @@
/**
* Placeholder grid matching GovernanceTemplateGrid layout (loading state).
*/
export function GovernanceTemplateGridSkeleton({ count }: { count: number }) {
return (
<div
className="
export function GovernanceTemplateGridSkeleton({
count,
twoColumnsFromMd = false,
}: {
count: number;
twoColumnsFromMd?: boolean;
}) {
const gridLayoutClasses = twoColumnsFromMd
? `
flex flex-col gap-[18px]
md:grid md:grid-cols-2 md:gap-[18px]
lg:gap-[24px]
`
: `
flex flex-col gap-[18px]
min-[768px]:grid min-[768px]:grid-cols-2 min-[768px]:gap-[18px]
min-[1024px]:gap-[24px]
"
`;
return (
<div
className={gridLayoutClasses}
aria-busy
aria-label="Loading templates"
>
@@ -0,0 +1,27 @@
"use client";
import { memo, useId } from "react";
import GroupsView from "./Groups.view";
import type { GroupsProps } from "./Groups.types";
/**
* Figma: **Section** instance [**22085-860411**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-860411&m=dev) (`xl`: **Scale/160** horizontal padding);
* Card group ref [**22084-859062**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22084-859062&m=dev); legacy **22084-859470**.
*/
const GroupsContainer = memo<GroupsProps>(({ title, items, className = "" }) => {
const reactId = useId();
const headingId = `${reactId}-groups-heading`;
return (
<GroupsView
title={title}
items={items}
headingId={headingId}
className={className}
/>
);
});
GroupsContainer.displayName = "Groups";
export default GroupsContainer;
@@ -0,0 +1,17 @@
import type { ReactNode } from "react";
export interface GroupsItem {
icon: ReactNode;
title: string;
description: string;
}
export interface GroupsProps {
title: string;
items: GroupsItem[];
className?: string;
}
export interface GroupsViewProps extends GroupsProps {
headingId: string;
}
@@ -0,0 +1,44 @@
"use client";
import { memo } from "react";
import Icon from "../../cards/Icon";
import type { GroupsViewProps } from "./Groups.types";
function GroupsView({ title, items, headingId, className = "" }: GroupsViewProps) {
return (
<section
data-figma-node="22085-860411"
aria-labelledby={headingId}
className={`bg-transparent px-0 py-[var(--spacing-scale-064)] lg:px-[var(--spacing-scale-064)] xl:px-[var(--spacing-scale-160)] ${className}`.trim()}
>
<div className="mx-auto flex w-full max-w-[560px] flex-col items-center gap-[var(--spacing-scale-032)] md:max-w-[1280px] md:gap-[var(--spacing-scale-048)]">
<h2
id={headingId}
className="w-full shrink-0 text-center font-bricolage-grotesque text-[28px] font-bold leading-9 text-[var(--color-content-default-primary)] md:text-[32px] md:leading-10 lg:text-[40px] lg:leading-[52px]"
>
{title}
</h2>
<div className="flex w-full shrink-0 flex-col bg-[var(--color-surface-default-primary)] max-md:[&>*+*]:-mt-px md:grid md:grid-cols-2 md:gap-px md:bg-[var(--color-border-default-primary)] md:[&>*+*]:mt-0 lg:grid-cols-4">
{items.map((item, index) => (
<div
key={`${item.title}-${index}`}
className="flex min-h-[350px] min-w-0 shrink-0 justify-center bg-[var(--color-surface-default-primary)] md:justify-stretch"
>
<Icon
icon={item.icon}
title={item.title}
description={item.description}
interactive={false}
className="w-full min-w-0 max-w-none shrink-0"
/>
</div>
))}
</div>
</div>
</section>
);
}
GroupsView.displayName = "GroupsView";
export default memo(GroupsView);
+2
View File
@@ -0,0 +1,2 @@
export { default } from "./Groups.container";
export type { GroupsProps, GroupsItem } from "./Groups.types";
@@ -5,7 +5,7 @@ import { logger } from "../../../../lib/logger";
import QuoteBlockView from "./QuoteBlock.view";
import type { QuoteBlockProps, VariantConfig } from "./QuoteBlock.types";
/** Figma: portrait variants standard | compact | extended; statement = Section/Quote (22137:890679, copy scale 22135:889716 from md). */
/** Figma: portrait variants standard | compact | extended; **`statement`** = Section/Quote (22137890679; **`lg`** single paragraph **2196724638** — About + use cases). */
const QuoteBlockContainer = memo<QuoteBlockProps>(
({
variant: variantProp = "standard",
@@ -19,7 +19,6 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
fallbackAvatarSrc = "/assets/Quote_Avatar.svg",
onError,
}) => {
const variant = variantProp;
const [imageError, setImageError] = useState(false);
const [imageLoading, setImageLoading] = useState(true);
@@ -86,12 +85,12 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
},
};
const config = variants[variant] || variants.standard;
const config = variants[variantProp] || variants.standard;
// Use provided ID or generate a stable one based on content
const baseId =
id ||
(variant === "statement"
(variantProp === "statement"
? "statement-quote"
: `quote-${author.toLowerCase().replace(/\s+/g, "-")}`);
const quoteId = `${baseId}-content`;
@@ -124,7 +123,7 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
};
// Validate required props
if (variant === "statement") {
if (variantProp === "statement") {
if (!quote?.trim() || !quoteSecondary?.trim()) {
logger.error(
"QuoteBlock: statement variant requires non-empty quote and quoteSecondary",
@@ -134,7 +133,7 @@ const QuoteBlockContainer = memo<QuoteBlockProps>(
type: "missing_props",
message:
"QuoteBlock statement variant requires quote and quoteSecondary",
quote: !!quote?.trim() && !!quoteSecondary?.trim(),
quote: !!(quote?.trim() && quoteSecondary?.trim()),
});
}
return null;
@@ -5,13 +5,11 @@ export type QuoteBlockVariantValue =
| "statement";
export interface QuoteBlockProps {
/** Default `standard` (home portrait quote). `statement` is About-only dual-paragraph layout; isolated branch in QuoteBlock.view. */
/** Default `standard` (home portrait quote). **`statement`** = yellow Section / Quote (**About** + **`/use-cases`** — two paragraphs below **`lg`**, one paragraph from **`lg`**; [21967-24638](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21967-24638&m=dev)). */
variant?: QuoteBlockVariantValue;
className?: string;
quote?: string;
/**
* Second paragraph for **`statement`** variant (Figma Section/Quote 22137:890679).
*/
/** Second paragraph for **`statement`** (Section/Quote); merged into one `<p>` from **`lg`**. */
quoteSecondary?: string;
author?: string;
source?: string;
@@ -39,7 +37,7 @@ export interface VariantConfig {
source: string;
showDecor: boolean;
/**
* When true, render Figma **Section/Quote** layout (yellow surface, dual paragraphs, no attribution).
* When true, render Figma **Section/Quote** layout (yellow surface; stacked copy below **`lg`**, single paragraph from **`lg`**; no attribution).
*/
statementLayout?: boolean;
}
@@ -26,15 +26,12 @@ function QuoteBlockView({
const avatarAlt = t("avatarAlt").replace("{author}", author);
if (config.statementLayout) {
if (!quoteSecondary?.trim()) {
return null;
}
const statementTextClass =
"font-bricolage-grotesque text-[28px] font-bold leading-9 tracking-[var(--text-xx-large-heading--letter-spacing)] text-[var(--color-surface-default-tertiary)] md:text-[length:var(--text-xx-large-heading)] md:leading-[length:var(--text-xx-large-heading--line-height)]";
return (
<section
data-figma-node="21967-24638"
className={`${config.container} ${className}`.trim()}
aria-labelledby={quoteId}
role="region"
@@ -43,12 +40,18 @@ function QuoteBlockView({
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(--spacing-scale-020)] bg-[var(--color-surface-invert-brand-primary,#fefcc9)] px-[var(--spacing-scale-032)] py-[var(--spacing-scale-048)] 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 gap-9 text-center md:gap-[length:var(--text-xx-large-heading--line-height)]">
<p id={quoteId} className={`${statementTextClass} mb-0 w-full min-w-0`}>
{quote}
</p>
<p className={`${statementTextClass} mb-0 w-full min-w-0`}>
{quoteSecondary}
<div className="relative z-10 flex w-full min-w-0 shrink-0 flex-col items-center text-center">
<p
id={quoteId}
className={`${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="block lg:inline">{quote}</span>
{quoteSecondary ? (
<>
<span className="hidden lg:inline">{" "}</span>
<span className="block lg:inline">{quoteSecondary}</span>
</>
) : null}
</p>
</div>
</div>
@@ -3,7 +3,7 @@
import { memo } from "react";
import { getAssetPath, quoteStatementShapePath } from "../../../../lib/assetUtils";
/** Figma: Section / Quote — Shapes (22137:890679). Radial asset + horizontal gradient mask (side lobes only); grain matches QuoteBlock/HeroDecor. Background `cover` so wide banners still fill lateral mask stripes (square sized by panel height misses them when centered). */
/** Figma: Section / Quote — **`shape-qoute.svg`** (22137:890679). */
const EDGE_MASK =
"linear-gradient(to right, #fff 0%, #fff 14%, rgba(255,255,255,0) 30%, rgba(255,255,255,0) 70%, #fff 86%, #fff 100%)";
@@ -2,11 +2,18 @@
import { useState, useEffect, memo, useMemo, useCallback } from "react";
import { useIsMobile } from "../../../hooks";
import { useMessages } from "../../../contexts/MessagesContext";
import { RelatedArticlesView } from "./RelatedArticles.view";
import type { RelatedArticlesProps } from "./RelatedArticles.types";
const RelatedArticlesContainer = memo<RelatedArticlesProps>(
({ relatedPosts, currentPostSlug, slugOrder = [] }) => {
({
relatedPosts,
currentPostSlug,
slugOrder = [],
variant = "default",
}) => {
const messages = useMessages();
// Memoize filtered posts to prevent unnecessary re-computations
const filteredPosts = useMemo(
() => relatedPosts.filter((post) => post.slug !== currentPostSlug),
@@ -95,6 +102,11 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
return () => clearInterval(progressInterval);
}, [currentIndex, filteredPosts.length, isMobile]);
const useCasesHeadingLines =
variant === "useCases"
? messages.pages.useCases.relatedArticles.title
: undefined;
return (
<RelatedArticlesView
filteredPosts={filteredPosts}
@@ -103,6 +115,8 @@ const RelatedArticlesContainer = memo<RelatedArticlesProps>(
transformStyle={transformStyle}
getProgressStyle={getProgressStyle}
onMouseDown={handleMouseDown}
variant={variant}
useCasesHeadingLines={useCasesHeadingLines}
/>
);
},
@@ -1,9 +1,17 @@
import type { BlogPost } from "../../../../lib/content";
export type RelatedArticlesVariant = "default" | "useCases";
export interface RelatedArticlesProps {
relatedPosts: BlogPost[];
currentPostSlug: string;
slugOrder?: string[];
/**
* **`useCases`**: Figma related section — baseline [**22112-872308**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22112-872308&m=dev),
* **`md`** [**22085-863216**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-863216&m=dev),
* **`lg`** [**20711-14231**](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=20711-14231&m=dev) (shell + card row gutter / padding).
*/
variant?: RelatedArticlesVariant;
}
export interface RelatedArticlesViewProps {
@@ -13,4 +21,7 @@ export interface RelatedArticlesViewProps {
transformStyle: React.CSSProperties;
getProgressStyle: (_index: number) => React.CSSProperties;
onMouseDown?: (_e: React.MouseEvent<HTMLDivElement>) => void;
variant?: RelatedArticlesVariant;
/** Stacked title lines (`pages.useCases.relatedArticles.title`) when `variant="useCases"`. */
useCasesHeadingLines?: readonly string[];
}
@@ -8,25 +8,66 @@ export function RelatedArticlesView({
transformStyle,
getProgressStyle,
onMouseDown,
variant = "default",
useCasesHeadingLines,
}: RelatedArticlesViewProps) {
if (filteredPosts.length === 0) {
return null;
}
const isUseCases = variant === "useCases";
return (
<section
className="py-[var(--spacing-scale-032)] lg:py-[var(--spacing-scale-064)]"
className={
isUseCases
? "px-[var(--spacing-scale-020)] py-[var(--spacing-scale-032)] md:px-[var(--spacing-scale-024)] md:py-[var(--spacing-scale-048)] lg:px-[var(--spacing-scale-120)] lg:py-[var(--spacing-scale-064)]"
: "py-[var(--spacing-scale-032)] lg:py-[var(--spacing-scale-064)]"
}
data-testid="related-articles"
{...(isUseCases ? { "data-figma-node": "20711-14231" } : {})}
>
<div className="flex flex-col gap-[var(--spacing-scale-032)] lg:gap-[51px]">
<h2 className="text-[32px] lg:text-[44px] leading-[110%] font-medium text-[var(--color-content-inverse-primary)] text-center">
Related Articles
</h2>
<div
className={
isUseCases
? "mx-auto flex w-full max-w-[1440px] flex-col items-center gap-[var(--spacing-scale-024)] md:gap-[var(--spacing-scale-032)]"
: "flex flex-col gap-[var(--spacing-scale-032)] lg:gap-[51px]"
}
>
{isUseCases && useCasesHeadingLines?.length ? (
<h2 className="mx-auto w-full min-w-0 max-w-[693px] text-center font-bricolage-grotesque text-[28px] font-bold leading-9 text-[var(--color-content-default-primary)] md:text-[32px] md:leading-[40px] lg:text-[40px] lg:leading-[52px]">
{/* Baseline 22112-872308: stacked lines; md+ single line; lg 20711-14231: 40/52, max 693px */}
<span className="flex flex-col md:hidden">
{useCasesHeadingLines.map((line, index) => (
<span key={`${index}-${line}`} className="block">
{line}
</span>
))}
</span>
<span className="hidden md:block">
{useCasesHeadingLines.join(" ")}
</span>
</h2>
) : (
<h2 className="text-center text-[32px] font-medium leading-[110%] text-[var(--color-content-inverse-primary)] lg:text-[44px]">
Related Articles
</h2>
)}
{/* Horizontal Articles Row - Carousel on mobile, Scrollable slider on desktop */}
<div className="flex justify-center overflow-hidden">
<div
className={
isUseCases
? "flex w-full max-w-[1440px] justify-center overflow-hidden"
: "flex justify-center overflow-hidden"
}
>
<div
className={`flex gap-0 transition-transform duration-500 ease-in-out ${
isUseCases
? "lg:gap-[var(--spacing-scale-012)] lg:pl-[var(--spacing-scale-024)]"
: ""
} ${
!isMobile
? "overflow-x-auto scrollbar-hide cursor-grab active:cursor-grabbing"
: ""
@@ -1,2 +1,5 @@
export { default } from "./RelatedArticles.container";
export type { RelatedArticlesProps } from "./RelatedArticles.types";
export type {
RelatedArticlesProps,
RelatedArticlesVariant,
} from "./RelatedArticles.types";
@@ -28,7 +28,7 @@ declare global {
}
const RuleStackContainer = memo<RuleStackProps>(
({ className = "", initialGridEntries }) => {
({ className = "", initialGridEntries, translationNamespace, twoColumnsFromMd }) => {
const router = useRouter();
const [gridEntries, setGridEntries] = useState<TemplateGridCardEntry[] | null>(
() => initialGridEntries ?? null,
@@ -103,6 +103,8 @@ const RuleStackContainer = memo<RuleStackProps>(
className={className}
onTemplateClick={handleTemplateClick}
gridEntries={gridEntries}
translationNamespace={translationNamespace ?? "pages.home.ruleStack"}
twoColumnsFromMd={twoColumnsFromMd}
/>
);
},
@@ -7,6 +7,15 @@ export interface RuleStackProps {
* the client skips the `/api/templates` request.
*/
initialGridEntries?: TemplateGridCardEntry[];
/**
* Prefix for `title`, `subtitle`, `button.seeAllTemplates` keys (default
* matches home: `pages.home.ruleStack`).
*/
translationNamespace?: string;
/**
* Use **`md`** (640px) for two template columns — `/use-cases` Rule Stack.
*/
twoColumnsFromMd?: boolean;
}
export interface RuleStackViewProps {
@@ -14,4 +23,6 @@ export interface RuleStackViewProps {
onTemplateClick: (_slug: string) => void;
/** `null` while loading curated templates from the API. */
gridEntries: TemplateGridCardEntry[] | null;
translationNamespace: string;
twoColumnsFromMd?: boolean;
}
@@ -7,16 +7,20 @@ import { GovernanceTemplateGrid } from "../GovernanceTemplateGrid";
import { GovernanceTemplateGridSkeleton } from "../GovernanceTemplateGrid/GovernanceTemplateGridSkeleton";
import type { RuleStackViewProps } from "./RuleStack.types";
/** Figma **Section / RuleStack** [22085:860413](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=22085-860413&m=dev). */
export function RuleStackView({
className,
onTemplateClick,
gridEntries,
translationNamespace,
twoColumnsFromMd = false,
}: RuleStackViewProps) {
const t = useTranslation("pages.home.ruleStack");
const t = useTranslation(translationNamespace);
const buttonText = t("button.seeAllTemplates");
return (
<section
data-figma-node="22085-860413"
className={`
w-full bg-transparent flex flex-col
px-[20px] py-[32px]
@@ -34,14 +38,19 @@ export function RuleStackView({
title={t("title")}
subtitle={t("subtitle")}
variant="multi-line"
ruleStackDesktopTypeScale
/>
{gridEntries === null ? (
<GovernanceTemplateGridSkeleton count={4} />
<GovernanceTemplateGridSkeleton
count={4}
twoColumnsFromMd={twoColumnsFromMd}
/>
) : (
<GovernanceTemplateGrid
entries={gridEntries}
onTemplateClick={onTemplateClick}
twoColumnsFromMd={twoColumnsFromMd}
/>
)}
@@ -0,0 +1,17 @@
"use client";
import { memo } from "react";
import UseCasesOrgsView from "./UseCasesOrgs.view";
import type { UseCasesOrgsProps } from "./UseCasesOrgs.types";
/**
* Figma: **Orgs** instance ([21993-33687](https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=21993-33687&m=dev)) —
* **305×305** `CaseStudy` tiles, **8px** gap, **24px** horizontal / **48px** bottom inset.
*/
const UseCasesOrgsContainer = memo<UseCasesOrgsProps>((props) => {
return <UseCasesOrgsView {...props} />;
});
UseCasesOrgsContainer.displayName = "UseCasesOrgs";
export default UseCasesOrgsContainer;
@@ -0,0 +1,8 @@
import type { ReactNode } from "react";
export interface UseCasesOrgsProps {
children: ReactNode;
className?: string;
}
export interface UseCasesOrgsViewProps extends UseCasesOrgsProps {}
@@ -0,0 +1,21 @@
"use client";
import { memo } from "react";
import type { UseCasesOrgsViewProps } from "./UseCasesOrgs.types";
function UseCasesOrgsView({ children, className = "" }: UseCasesOrgsViewProps) {
return (
<section
data-figma-node="21993-33687"
className={`bg-[var(--color-surface-default-primary)] px-[var(--spacing-scale-024)] pb-[var(--spacing-scale-048)] ${className}`.trim()}
>
<div className="mx-auto flex w-full max-w-[1440px] flex-wrap content-center items-center justify-center gap-[var(--spacing-scale-008)] lg:flex-nowrap">
{children}
</div>
</section>
);
}
UseCasesOrgsView.displayName = "UseCasesOrgsView";
export default memo(UseCasesOrgsView);
@@ -0,0 +1,2 @@
export { default } from "./UseCasesOrgs.container";
export type { UseCasesOrgsProps } from "./UseCasesOrgs.types";