Add custom intervention modals

This commit is contained in:
adilallo
2026-05-01 22:05:05 -06:00
parent 58d0e33500
commit dee2dd800e
67 changed files with 3480 additions and 197 deletions
@@ -32,6 +32,10 @@ const CardStackContainer = memo<CardStackProps>(
headerLockupSize,
toggleAlignment = "center",
className = "",
showAddCard = false,
addCardLabel = "",
addCardAriaLabel = "",
onAddCard,
}) => {
const [internalExpanded, setInternalExpanded] = useState(false);
const [internalSelectedIds, setInternalSelectedIds] = useState<string[]>(
@@ -90,6 +94,10 @@ const CardStackContainer = memo<CardStackProps>(
headerLockupSize={headerLockupSize}
toggleAlignment={toggleAlignment}
className={className}
showAddCard={showAddCard}
addCardLabel={addCardLabel}
addCardAriaLabel={addCardAriaLabel}
onAddCard={onAddCard}
/>
);
},
@@ -1,3 +1,4 @@
import type { ReactNode } from "react";
import type { HeaderLockupSizeValue } from "../../type/HeaderLockup/HeaderLockup.types";
export interface CardStackItem {
@@ -18,7 +19,7 @@ export interface CardStackProps {
toggleLabel?: string;
showLessLabel?: string;
title?: string;
description?: string;
description?: ReactNode;
/** "default" = compact grid/column + expanded grid; "singleStack" = always one column, expand shows more in same stack */
layout?: "default" | "singleStack";
/**
@@ -45,6 +46,11 @@ export interface CardStackProps {
/** Alignment of the expand/collapse control in `singleStack` layout (Figma right-rail: end). */
toggleAlignment?: "center" | "end";
className?: string;
/** Optional “Add” entry (e.g. custom method card wizard). */
showAddCard?: boolean;
addCardLabel?: string;
addCardAriaLabel?: string;
onAddCard?: () => void;
}
export interface CardStackViewProps {
@@ -57,7 +63,7 @@ export interface CardStackViewProps {
toggleLabel: string;
showLessLabel: string;
title: string;
description: string;
description: ReactNode;
layout: "default" | "singleStack";
compactRecommendedLimit: number;
compactCardIds: string[] | undefined;
@@ -65,4 +71,8 @@ export interface CardStackViewProps {
headerLockupSize: HeaderLockupSizeValue | undefined;
toggleAlignment: "center" | "end";
className: string;
showAddCard: boolean;
addCardLabel: string;
addCardAriaLabel: string;
onAddCard?: () => void;
}
+121 -20
View File
@@ -1,9 +1,77 @@
"use client";
import type { ReactNode } from "react";
import HeaderLockup from "../../type/HeaderLockup";
import type { HeaderLockupSizeValue } from "../../type/HeaderLockup/HeaderLockup.types";
import Selection from "../Selection";
import type { CardStackViewProps } from "./CardStack.types";
function CardStackHeaderLockup({
title,
description,
justification,
size,
wrapperClassName,
}: {
title: string;
description: ReactNode;
justification: "center" | "left";
size: HeaderLockupSizeValue;
wrapperClassName?: string;
}) {
if (!title && !description) {
return null;
}
return (
<div className={wrapperClassName ?? "min-w-0"}>
<HeaderLockup
title={title}
description={description}
justification={justification}
size={size}
/>
</div>
);
}
function CardStackAddCardButton({
label,
ariaLabel,
onClick,
}: {
label: string;
ariaLabel: string;
onClick: () => void;
}) {
return (
<button
type="button"
onClick={onClick}
aria-label={ariaLabel}
className="flex min-h-[88px] w-full shrink-0 items-center justify-center rounded-[var(--measures-radius-medium,8px)] bg-[var(--color-surface-default-secondary)] font-inter text-base font-medium text-[var(--color-content-default-primary)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary)]"
>
<span className="flex items-center gap-2">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden
>
<path
d="M12 5v14M5 12h14"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
{label}
</span>
</button>
);
}
export function CardStackView({
cards,
selectedIds,
@@ -22,7 +90,19 @@ export function CardStackView({
headerLockupSize,
toggleAlignment,
className,
showAddCard,
addCardLabel,
addCardAriaLabel,
onAddCard,
}: CardStackViewProps) {
const addTile =
showAddCard && onAddCard && addCardLabel.length > 0 ? (
<CardStackAddCardButton
label={addCardLabel}
ariaLabel={addCardAriaLabel || addCardLabel}
onClick={onAddCard}
/>
) : null;
const lockupSize = headerLockupSize ?? "L";
const isSelected = (id: string) => selectedIds.includes(id);
// Compact: explicit `compactCardIds` (caller-driven, used by create-flow
@@ -47,16 +127,13 @@ export function CardStackView({
const displayedCards = expanded ? cards : compactCards;
return (
<div className={`flex w-full flex-col gap-6 min-w-0 ${className}`}>
{title || description ? (
<div className="min-w-0 shrink-0">
<HeaderLockup
title={title}
description={description}
justification="center"
size={lockupSize}
/>
</div>
) : null}
<CardStackHeaderLockup
title={title}
description={description}
justification="center"
size={lockupSize}
wrapperClassName="min-w-0 shrink-0"
/>
<div className="flex w-full min-w-0 flex-col gap-2">
{displayedCards.map((item) => (
<Selection
@@ -71,6 +148,7 @@ export function CardStackView({
onClick={() => onCardSelect(item.id)}
/>
))}
{addTile}
</div>
{hasMore ? (
<button
@@ -89,16 +167,12 @@ export function CardStackView({
return (
<div className={`flex w-full flex-col gap-6 min-w-0 ${className}`}>
{title || description ? (
<div className="min-w-0">
<HeaderLockup
title={title}
description={description}
justification="center"
size={lockupSize}
/>
</div>
) : null}
<CardStackHeaderLockup
title={title}
description={description}
justification="center"
size={lockupSize}
/>
{expanded ? (
<div className="mx-auto grid w-full max-w-[min(100%,860px)] grid-cols-1 gap-x-4 gap-y-6 md:grid-cols-2">
@@ -115,6 +189,9 @@ export function CardStackView({
onClick={() => onCardSelect(item.id)}
/>
))}
{addTile ? (
<div className="min-w-0 md:col-span-2">{addTile}</div>
) : null}
</div>
) : compactDesktopLayout === "pyramidFive" ? (
<>
@@ -133,6 +210,7 @@ export function CardStackView({
onClick={() => onCardSelect(item.id)}
/>
))}
{addTile}
</div>
<div className="mx-auto hidden w-full max-w-[min(100%,860px)] md:block">
{/*
@@ -228,6 +306,11 @@ export function CardStackView({
) : null}
</div>
</div>
{addTile ? (
<div className="mx-auto hidden w-full max-w-[min(100%,860px)] md:block">
{addTile}
</div>
) : null}
</>
) : compactDesktopLayout === "flexWrap" ? (
<>
@@ -246,6 +329,7 @@ export function CardStackView({
onClick={() => onCardSelect(item.id)}
/>
))}
{addTile}
</div>
{/* mdlg: pyramid (2 + 1), each row centered; lg+: one centered row (not edge-to-edge in a 2-col grid) */}
{compactCards.length === 3 ? (
@@ -280,6 +364,9 @@ export function CardStackView({
onClick={() => onCardSelect(compactCards[2].id)}
/>
</div>
{addTile ? (
<div className="flex w-full justify-center px-2">{addTile}</div>
) : null}
</div>
<div className="mx-auto hidden w-full max-w-[min(100%,860px)] flex-wrap justify-center gap-2 lg:flex">
{compactCards.map((item) => (
@@ -297,6 +384,11 @@ export function CardStackView({
/>
))}
</div>
{addTile ? (
<div className="mx-auto hidden w-full max-w-[min(100%,860px)] lg:flex lg:justify-center">
<div className="w-full min-w-[281px] max-w-[574px]">{addTile}</div>
</div>
) : null}
</>
) : (
<div className="mx-auto hidden w-full max-w-[min(100%,860px)] flex-wrap justify-center gap-2 md:flex">
@@ -318,6 +410,11 @@ export function CardStackView({
/>
</div>
))}
{addTile ? (
<div className="flex w-full min-w-0 shrink-0 justify-center md:w-[281px] md:max-w-[574px] md:flex-[1_1_100%]">
{addTile}
</div>
) : null}
</div>
)}
</>
@@ -338,6 +435,7 @@ export function CardStackView({
onClick={() => onCardSelect(item.id)}
/>
))}
{addTile}
</div>
{/* Compact 640+: 6-col grid so each card spans 2; second row centered (cols 23 and 45) */}
<div className="hidden md:grid grid-cols-6 gap-x-4 gap-y-6 w-full">
@@ -365,6 +463,9 @@ export function CardStackView({
</div>
);
})}
{addTile ? (
<div className="col-span-6 min-w-0">{addTile}</div>
) : null}
</div>
</>
)}