Standardize create-flow option steps on one question-and-options layout and Selection sizes.

Method cards, chips, and the right rail now share the two-column shell; last options clear the footer fade, See all stays under the stack, and the left heading no longer jumps when the list expands.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilalloandCursor committed 2026-10-01 17:18:04 -06:00
1 parent 2fc7fbf818
commit 24a9615c0c
26 files changed
+354 -245

No files matched your search

@@ -2,11 +2,22 @@
import { memo } from "react";
import { SelectionView } from "./Selection.view";
import type { SelectionProps } from "./Selection.types";
import type { SelectionProps, SelectionSizeValue } from "./Selection.types";
function resolveSelectionSize(
size: SelectionProps["size"],
orientation: SelectionProps["orientation"],
): SelectionSizeValue {
if (size) return size;
if (orientation === "vertical") return "l";
return "m";
}
/**
* Figma: "Card / CardSelection" — stacked tile e.g. `16775:28762` (recommended + label + supportText).
* `orientation="horizontal"` selects that vertical stack; `"vertical"` is label + tag on the right (CardStack `singleStack` / right-rail). Helper `?` stays off until tooltip behavior ships.
* Size `s` is Control / Chip; `m` is the compact tile (`orientation="horizontal"`); `l` is the
* full-width row (`orientation="vertical"`, CardStack `singleStack` / right-rail). Helper `?`
* stays off until tooltip behavior ships.
*/
const SelectionContainer = memo<SelectionProps>(
({
@@ -14,12 +25,15 @@ const SelectionContainer = memo<SelectionProps>(
supportText = "",
recommended = false,
selected = false,
orientation = "horizontal",
size: sizeProp,
orientation,
showInfoIcon = false,
id,
className = "",
onClick,
}) => {
const size = resolveSelectionSize(sizeProp, orientation);
const handleClick = () => {
if (onClick) onClick();
};
@@ -37,7 +51,7 @@ const SelectionContainer = memo<SelectionProps>(
supportText={supportText}
recommended={recommended}
selected={selected}
orientation={orientation}
size={size}
showInfoIcon={showInfoIcon}
id={id}
className={className}
@@ -1,9 +1,28 @@
import type {
SelectionOrientationValue,
SelectionSizeValue,
} from "../../../../lib/propNormalization";
export type { SelectionOrientationValue, SelectionSizeValue };
export interface SelectionProps {
label: string;
supportText?: string;
recommended?: boolean;
selected?: boolean;
orientation: "horizontal" | "vertical";
/**
* Size of the selectable option:
* - `s` — chip (label only)
* - `m` — compact tile (label + support text stacked)
* - `l` — full-width row (label + support text, tag at the end)
*
* When omitted, `orientation` maps `horizontal` → `m` and `vertical` → `l`.
*/
size?: SelectionSizeValue;
/**
* Legacy layout flag. Prefer `size`. `horizontal` is size `m`; `vertical` is size `l`.
*/
orientation?: SelectionOrientationValue;
/** Off in product until tooltip behavior ships. */
showInfoIcon?: boolean;
/** Optional id for the root (e.g. `data-card-id` for focus after modal close). */
@@ -17,7 +36,7 @@ export interface SelectionViewProps {
supportText: string;
recommended: boolean;
selected: boolean;
orientation: "horizontal" | "vertical";
size: SelectionSizeValue;
showInfoIcon: boolean;
id: string | undefined;
className: string;
@@ -1,5 +1,6 @@
"use client";
import Chip from "../../controls/Chip";
import Tag from "../../utility/Tag";
import type { SelectionViewProps } from "./Selection.types";
@@ -26,34 +27,54 @@ function SelectionTag({
return null;
}
function optionAriaLabel(label: string, supportText: string): string {
return supportText ? `${label}: ${supportText}` : label;
}
export function SelectionView({
label,
supportText,
recommended,
selected,
orientation,
size,
showInfoIcon,
id: selectionId,
className,
onClick,
onKeyDown,
}: SelectionViewProps) {
const ariaLabel = optionAriaLabel(label, supportText);
if (size === "s") {
return (
<Chip
label={label}
state={selected ? "selected" : "unselected"}
size="m"
ariaLabel={ariaLabel}
className={className}
onClick={() => onClick()}
/>
);
}
const borderClass = "border border-[var(--color-border-default-primary)]";
const selectedBorder = selected
? "outline outline-4 outline-dashed outline-black outline-offset-[-4px]"
: "";
const cardIdProp = selectionId ? { "data-card-id": selectionId } : {};
// Figma: "Card / CardSelection" vertical stack — node `16775:28762` (dev).
// Prop `orientation="horizontal"` is this stacked layout (historical naming).
if (orientation === "horizontal") {
if (size === "m") {
const baseClasses = `select-none rounded-[var(--measures-radius-200,8px)] bg-[var(--color-gray-000)] px-4 py-3 transition-[border-color,box-shadow,outline] duration-200 cursor-pointer ${borderClass} ${selectedBorder} ${className}`;
return (
<div
{...(selectionId ? { "data-card-id": selectionId } : {})}
{...cardIdProp}
role="button"
tabIndex={0}
aria-label={supportText ? `${label}: ${supportText}` : label}
aria-label={ariaLabel}
aria-pressed={selected}
data-selection-size="m"
className={`${baseClasses} flex min-h-0 w-full flex-col items-start justify-center gap-1`}
onClick={onClick}
onKeyDown={onKeyDown}
@@ -75,10 +96,12 @@ export function SelectionView({
return (
<div
{...(selectionId ? { "data-card-id": selectionId } : {})}
{...cardIdProp}
role="button"
tabIndex={0}
aria-label={supportText ? `${label}: ${supportText}` : label}
aria-label={ariaLabel}
aria-pressed={selected}
data-selection-size="l"
className={`${baseClasses} flex flex-row items-center justify-between gap-4`}
onClick={onClick}
onKeyDown={onKeyDown}