diff --git a/.cursor/rules/create-flow.mdc b/.cursor/rules/create-flow.mdc index 0d872bb..cb06545 100644 --- a/.cursor/rules/create-flow.mdc +++ b/.cursor/rules/create-flow.mdc @@ -15,7 +15,9 @@ alwaysApply: false - Step id ↔ layout kind mapping is declared in `app/(app)/create/utils/createFlowScreenRegistry.ts`. Never branch on layout kind inside a screen — pick the matching shell (`CreateFlowStepShell` / - `CreateFlowTwoColumnSelectShell`). + `CreateFlowTwoColumnSelectShell`). Question-and-options steps (select, card, + right-rail) all use `CreateFlowTwoColumnSelectShell` plus `Selection` size + variants (`s` chip / `m` tile / `l` row). - Keep create-flow step routing centralized in `app/(app)/create/utils/createFlowPaths.ts` (`createFlowStepPath`, `CREATE_ROUTES`) — do not introduce new hardcoded `/create/...` literals. diff --git a/app/(app)/create/CreateFlowLayoutClient.tsx b/app/(app)/create/CreateFlowLayoutClient.tsx index b682fde..b9c0ca0 100644 --- a/app/(app)/create/CreateFlowLayoutClient.tsx +++ b/app/(app)/create/CreateFlowLayoutClient.tsx @@ -41,10 +41,7 @@ import { createFlowStepPathWithSyncDraft, } from "./utils/createFlowPaths"; import { getProportionBarProgressForCreateFlowStep } from "./utils/createFlowProportionProgress"; -import { - createFlowStepUsesCenteredTextLayout, - createFlowStepUsesCardLayout, -} from "./utils/createFlowScreenRegistry"; +import { createFlowStepUsesCenteredTextLayout } from "./utils/createFlowScreenRegistry"; import Button from "../../components/buttons/Button"; import { isValidCreateFlowSaveEmail } from "../../../lib/create/isValidCreateFlowSaveEmail"; import { buildCreateFlowDraftPayload } from "../../../lib/create/buildCreateFlowDraftPayload"; @@ -557,20 +554,19 @@ function CreateFlowLayoutContent({ const isFinalReviewLike = currentStep === "final-review" || currentStep === "edit-rule"; const isEditRuleStep = currentStep === "edit-rule"; - const isCardLayoutStep = createFlowStepUsesCardLayout(currentStep); - /** Two-column select / right-rail: below `lg` main scrolls; at `lg+` only the right column scrolls. */ + /** Two-column question-and-options: below `lg` main scrolls; at `lg+` only the right column scrolls. */ const isSelectSplitScrollStep = createFlowStepUsesSelectSplitScroll( currentStep, ); - /** Lockup+card / card-stack: `items-start` + shell `my-auto` so overflow scrolls from the top. */ + /** Lockup+card review: `items-start` + shell `my-auto` so overflow scrolls from the top. */ const mainContentClass = isCompletedStep ? "items-stretch overflow-y-auto md:overflow-hidden" : isSelectSplitScrollStep ? "items-start justify-start overflow-y-auto max-lg:overflow-y-auto lg:min-h-0 lg:items-stretch lg:overflow-hidden" : isCreateTemplatesPickerRoute ? "items-start justify-start overflow-y-auto" - : isTemplateReviewRoute || isFinalReviewLike || isCardLayoutStep + : isTemplateReviewRoute || isFinalReviewLike ? CREATE_FLOW_MD_CENTERED_MAIN_CLASS : "items-start justify-center overflow-y-auto md:items-center"; diff --git a/app/(app)/create/components/CreateFlowCardStackStepShell.tsx b/app/(app)/create/components/CreateFlowCardStackStepShell.tsx deleted file mode 100644 index d777363..0000000 --- a/app/(app)/create/components/CreateFlowCardStackStepShell.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client"; - -import type { ReactNode } from "react"; -import { CreateFlowHeaderLockup } from "./CreateFlowHeaderLockup"; -import { CreateFlowStepShell } from "./CreateFlowStepShell"; -import { - CREATE_FLOW_CARD_STACK_AREA_MAX_CLASS, - CREATE_FLOW_MD_CENTERED_SHELL_CLASS, - CREATE_FLOW_MD_UP_COLUMN_MAX_CLASS, - CREATE_FLOW_WIDE_MAX_CLASS, -} from "./createFlowLayoutTokens"; - -type CreateFlowCardStackStepShellProps = { - lockupTitle: string; - lockupDescription?: ReactNode; - /** When true, the stack region exposes `aria-busy` while recommendations load. */ - stackBusy?: boolean; - children: ReactNode; -}; - -/** - * Centered lockup + card-stack layout shared by communication, membership, and - * conflict-management steps (Figma compact card stack). - */ -export function CreateFlowCardStackStepShell({ - lockupTitle, - lockupDescription, - stackBusy = false, - children, -}: CreateFlowCardStackStepShellProps) { - return ( - -
-
- -
-
- {children} -
-
-
- ); -} diff --git a/app/(app)/create/components/CreateFlowTwoColumnSelectShell.tsx b/app/(app)/create/components/CreateFlowTwoColumnSelectShell.tsx index b9d2c7b..2cf3e27 100644 --- a/app/(app)/create/components/CreateFlowTwoColumnSelectShell.tsx +++ b/app/(app)/create/components/CreateFlowTwoColumnSelectShell.tsx @@ -7,6 +7,7 @@ import { } from "./CreateFlowStepShell"; import { CREATE_FLOW_MD_UP_COLUMN_MAX_CLASS, + CREATE_FLOW_SCROLL_END_PAD_CLASS, CREATE_FLOW_SCROLL_REGION_CLASS, CREATE_FLOW_TWO_COLUMN_MAX_WIDTH_CLASS, } from "./createFlowLayoutTokens"; @@ -23,15 +24,17 @@ interface CreateFlowTwoColumnSelectShellProps { contentTopBelowMd?: CreateFlowContentTopBelowMd; /** * At `lg+`, layout variant: `"center"` = vertically centered pair (community size/structure). - * `"start"` = top-weighted layout with a scrollable right column (core values, right-rail): uses `items-stretch` - * so the right column gets a bounded height; `items-start` would grow with content and break scroll. + * `"start"` = full-height split with a scrollable right column (core values, method cards, + * right-rail). Both columns stretch so the left lockup stays in one place when the right + * list grows (Figma Flow — Right Rail / Expanded Card Stack). The lockup is centered in + * the left pane; `items-start` on the row would let the title track card-stack height. */ lgVerticalAlign?: CreateFlowSelectShellLgVerticalAlign; } /** * Question-and-options layout for create-flow select steps (community size/structure, - * core values, stakeholders) and {@link DecisionApproachesScreen}. + * core values, stakeholders), method-card steps, and {@link DecisionApproachesScreen}. * Below `lg` (1024px), one column uses available width and main scrolls. * At `lg+`, static header column + scrollable options column (`min-h-0` + overflow). */ @@ -45,9 +48,6 @@ export function CreateFlowTwoColumnSelectShell({ const rowLgCrossAlignClass = lgVerticalAlign === "start" ? "lg:items-stretch" : "lg:items-center"; - const leftLgMainJustifyClass = - lgVerticalAlign === "start" ? "lg:justify-start" : "lg:justify-center"; - return ( {header} @@ -76,7 +76,7 @@ export function CreateFlowTwoColumnSelectShell({
diff --git a/app/(app)/create/components/createFlowLayoutTokens.ts b/app/(app)/create/components/createFlowLayoutTokens.ts index 4371ddb..438fad4 100644 --- a/app/(app)/create/components/createFlowLayoutTokens.ts +++ b/app/(app)/create/components/createFlowLayoutTokens.ts @@ -31,6 +31,12 @@ export const CREATE_FLOW_SCROLL_REGION_CLASS = "scrollbar-design"; export const CREATE_FLOW_FOOTER_SCRIM_CLASS = "pointer-events-none absolute inset-x-0 top-0 z-[1] h-8 -translate-y-full bg-gradient-to-t from-[var(--color-surface-default-primary,#000)] to-transparent"; +/** + * Bottom padding in create-flow option scrollports so the last control can + * rest above the footer scrim (`h-8`) instead of sitting in the fade. + */ +export const CREATE_FLOW_SCROLL_END_PAD_CLASS = "pb-12"; + /** * Single column: full width below `lg` so tablet widths use available space; * 640px from `--breakpoint-lg` up (two-column layouts start at `lg`). @@ -46,7 +52,9 @@ export const CREATE_FLOW_MD_UP_GRID_CELL_CLASS = export const CREATE_FLOW_TWO_COLUMN_MAX_WIDTH_CLASS = "w-full min-w-0 lg:max-w-[1328px]"; -/** Review / card-stack frames (Figma Flow — Review max 1440). */ +/** + * Review frames (Figma Flow — Review max 1440). + */ export const CREATE_FLOW_WIDE_MAX_CLASS = "w-full min-w-0 lg:max-w-[1440px]"; /** @@ -62,14 +70,6 @@ export const CREATE_FLOW_MD_CENTERED_MAIN_CLASS = */ export const CREATE_FLOW_MD_CENTERED_SHELL_CLASS = "md:my-auto md:pt-0"; -/** - * Card-stack steps only (Figma compact card stack): wider than header lockup so the card grid / - * pyramid fits (max 860px). Header lockup stays {@link CREATE_FLOW_MD_UP_COLUMN_MAX_CLASS}. - * Card–card gap uses `gap-2` in `CardStack` (same on mobile and md+). - */ -export const CREATE_FLOW_CARD_STACK_AREA_MAX_CLASS = - "w-full min-w-0 md:max-w-[min(100%,860px)]"; - /** * Inner max-width for step content and the action bar, so the primary button * lines up with the nearest content column above 1440. @@ -88,11 +88,10 @@ export function getCreateFlowContentMaxClass(options: { switch (CREATE_FLOW_SCREEN_REGISTRY[options.step].layoutKind) { case "select": case "right-rail": + case "card": case "completed": case "review": return CREATE_FLOW_TWO_COLUMN_MAX_WIDTH_CLASS; - case "card": - return CREATE_FLOW_WIDE_MAX_CLASS; default: return CREATE_FLOW_MD_UP_COLUMN_MAX_CLASS; } diff --git a/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx b/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx index 543e058..3c67840 100644 --- a/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx +++ b/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx @@ -4,9 +4,9 @@ * `communication-methods` step — Figma “Flow — Compact Card Stack” (node `20246-15828`). * Registry: `layoutKind: "card"` (`CREATE_FLOW_SCREEN_REGISTRY["communication-methods"]`). * - * Lives under `screens/card/` (not `select/`): Figma **card stack** layout is a distinct shell from - * two-column chip **select** frames. Future card-stack steps get their own `*Screen.tsx` here and - * reuse `CardStack` / `CreateFlowCardStackStepShell` as needed. + * Page chrome is the shared question-and-options shell + * ({@link CreateFlowTwoColumnSelectShell}); options are {@link CardStack} with + * `layout="singleStack"` and {@link Selection} size `l`. * * Card click opens the Figma create modal (node `20246-15829`) with three * editable sections rendered by {@link CommunicationMethodEditFields}. The @@ -18,11 +18,11 @@ import { useState, useCallback, useMemo, useRef } from "react"; import { useMessages } from "../../../../contexts/MessagesContext"; import { useCreateFlow } from "../../context/CreateFlowContext"; -import { useCreateFlowMdUp } from "../../hooks/useCreateFlowMdUp"; import { useBeforeUnloadGuard } from "../../../../hooks/useBeforeUnloadGuard"; import { useDiscardCustomizeConfirm } from "../../hooks/useDiscardCustomizeConfirm"; import { useMethodCardDeckOrdering } from "../../hooks/useMethodCardDeckOrdering"; -import { CreateFlowCardStackStepShell } from "../../components/CreateFlowCardStackStepShell"; +import { CreateFlowHeaderLockup } from "../../components/CreateFlowHeaderLockup"; +import { CreateFlowTwoColumnSelectShell } from "../../components/CreateFlowTwoColumnSelectShell"; import CardStack from "../../../../components/cards/CardStack"; import Create from "../../../../components/modals/Create"; import InlineTextButton from "../../../../components/buttons/InlineTextButton"; @@ -61,7 +61,6 @@ export function CommunicationMethodsScreen() { const m = useMessages(); const comm = m.create.customRule.communication; const modalKebabMenu = m.create.customRule.modalKebabMenu; - const mdUp = useCreateFlowMdUp(); const { confirmDiscard, confirmDialog } = useDiscardCustomizeConfirm(); const { state, updateState, replaceState, markCreateFlowInteraction } = useCreateFlow(); @@ -120,17 +119,9 @@ export function CommunicationMethodsScreen() { setAddCustomWizardOpen(true); }, [markCreateFlowInteraction]); - const title = expanded ? comm.page.expandedTitle : comm.page.compactTitle; + const title = comm.page.compactTitle; - const description = expanded ? ( - <> - {comm.page.expandedDescriptionBefore} - - {comm.page.compactDescriptionLinkLabel} - - {comm.page.expandedDescriptionAfter} - - ) : ( + const description = ( <> {comm.page.compactDescriptionBefore} @@ -694,30 +685,41 @@ export function CommunicationMethodsScreen() { return ( <> - - {recommendationsReady && ( - { - markCreateFlowInteraction(); - setExpanded((prev) => !prev); - }} - hasMore={true} - toggleLabel={comm.page.seeAllLink} - compactRecommendedLimit={5} - compactCardIds={compactCardIds} - compactDesktopLayout="flexWrap" - headerLockupSize={mdUp ? "L" : "M"} + - )} - + } + > +
+ {recommendationsReady && ( + { + markCreateFlowInteraction(); + setExpanded((prev) => !prev); + }} + hasMore={true} + toggleLabel={comm.page.seeAllLink} + compactRecommendedLimit={5} + compactCardIds={compactCardIds} + layout="singleStack" + className="w-full lg:h-full lg:min-h-0 lg:flex-1" + /> + )} +
+ - {cm.page.expandedDescriptionBefore} - - {cm.page.compactDescriptionLinkLabel} - - {cm.page.expandedDescriptionAfter} - - ) : ( + const description = ( <> {cm.page.compactDescriptionBefore} @@ -695,30 +689,41 @@ export function ConflictManagementScreen() { return ( <> - - {recommendationsReady && ( - { - markCreateFlowInteraction(); - setExpanded((prev) => !prev); - }} - hasMore={true} - toggleLabel={cm.page.seeAllLink} - compactRecommendedLimit={5} - compactCardIds={compactCardIds} - compactDesktopLayout="pyramidFive" - headerLockupSize={mdUp ? "L" : "M"} + - )} - + } + > +
+ {recommendationsReady && ( + { + markCreateFlowInteraction(); + setExpanded((prev) => !prev); + }} + hasMore={true} + toggleLabel={cm.page.seeAllLink} + compactRecommendedLimit={5} + compactCardIds={compactCardIds} + layout="singleStack" + className="w-full lg:h-full lg:min-h-0 lg:flex-1" + /> + )} +
+ - {mem.page.expandedDescriptionBefore} - - {mem.page.compactDescriptionLinkLabel} - - {mem.page.expandedDescriptionAfter} - - ) : ( + const description = ( <> {mem.page.compactDescriptionBefore} @@ -688,30 +682,41 @@ export function MembershipMethodsScreen() { return ( <> - - {recommendationsReady && ( - { - markCreateFlowInteraction(); - setExpanded((prev) => !prev); - }} - hasMore={true} - toggleLabel={mem.page.seeAllLink} - compactRecommendedLimit={5} - compactCardIds={compactCardIds} - compactDesktopLayout="pyramidFive" - headerLockupSize={mdUp ? "L" : "M"} + - )} - + } + > +
+ {recommendationsReady && ( + { + markCreateFlowInteraction(); + setExpanded((prev) => !prev); + }} + hasMore={true} + toggleLabel={mem.page.seeAllLink} + compactRecommendedLimit={5} + compactCardIds={compactCardIds} + layout="singleStack" + className="w-full lg:h-full lg:min-h-0 lg:flex-1" + /> + )} +
+
{recommendationsReady && ( @@ -796,7 +796,7 @@ export function DecisionApproachesScreen() { layout="singleStack" compactRecommendedLimit={5} compactCardIds={compactCardIds} - className="w-full" + className="w-full lg:h-full lg:min-h-0 lg:flex-1" headerLockupSize={mdUp ? "L" : "M"} /> )} diff --git a/app/(app)/create/utils/createFlowScreenRegistry.ts b/app/(app)/create/utils/createFlowScreenRegistry.ts index f43e24a..82743c2 100644 --- a/app/(app)/create/utils/createFlowScreenRegistry.ts +++ b/app/(app)/create/utils/createFlowScreenRegistry.ts @@ -150,7 +150,7 @@ export function createFlowStepUsesCenteredTextLayout( return CREATE_FLOW_SCREEN_REGISTRY[step].centeredBodyBelowMd; } -/** Steps whose main area uses the CardStack-style layout (`layoutKind: "card"`). */ +/** Steps whose Figma frame is a compact card stack (`layoutKind: "card"`). */ export function createFlowStepUsesCardLayout( step: CreateFlowStep | null, ): boolean { diff --git a/app/(app)/create/utils/flowSteps.ts b/app/(app)/create/utils/flowSteps.ts index 916847c..47ec08c 100644 --- a/app/(app)/create/utils/flowSteps.ts +++ b/app/(app)/create/utils/flowSteps.ts @@ -201,7 +201,10 @@ export const CREATE_FLOW_SELECT_SPLIT_SCROLL_STEPS: readonly CreateFlowStep[] = "community-size", "community-structure", "core-values", + "communication-methods", + "membership-methods", "decision-approaches", + "conflict-management", "confirm-stakeholders", ] as const; diff --git a/app/components/cards/CardStack/CardStack.container.tsx b/app/components/cards/CardStack/CardStack.container.tsx index a7e5782..78d4097 100644 --- a/app/components/cards/CardStack/CardStack.container.tsx +++ b/app/components/cards/CardStack/CardStack.container.tsx @@ -9,10 +9,11 @@ const DEFAULT_TOGGLE_LABEL = "See all communication approaches"; /** * Figma: "Utility / CardStack"; canonical code under `cards/`. - * Selectable stack of cards with + * Selectable stack of {@link Selection} options (`size` `m` tiles or `l` rows) with * an optional "see all"/"show less" expand toggle. - * Expanded default layout matches Flow — Expanded Card Stack (`20768:14820`): - * 281×142 CardSelection tiles in two columns. + * Create-flow method steps use `layout="singleStack"` (size `l`) in the shared + * question-and-options shell. Expanded default layout still matches Flow — + * Expanded Card Stack (`20768:14820`): 281×142 size-`m` tiles in two columns. */ const CardStackContainer = memo( ({ diff --git a/app/components/cards/CardStack/CardStack.types.ts b/app/components/cards/CardStack/CardStack.types.ts index 2fc4b8d..68795e6 100644 --- a/app/components/cards/CardStack/CardStack.types.ts +++ b/app/components/cards/CardStack/CardStack.types.ts @@ -43,7 +43,10 @@ export interface CardStackProps { compactDesktopLayout?: "grid" | "flexWrap" | "pyramidFive"; /** Optional title/description lockup size (create-flow passes `md`-matched `L`/`M`). Defaults to `L`. */ headerLockupSize?: HeaderLockupSizeValue; - /** Alignment of the expand/collapse control in `singleStack` layout (Figma right-rail: end). */ + /** + * Alignment of the expand/collapse control in `singleStack` layout. + * Figma Flow — Compact Card Stack, Expanded Card Stack, and Right Rail center the control. + */ toggleAlignment?: "center" | "end"; className?: string; /** Optional “Add” entry (e.g. custom method card wizard). */ diff --git a/app/components/cards/CardStack/CardStack.view.tsx b/app/components/cards/CardStack/CardStack.view.tsx index 2874ea2..7c4e850 100644 --- a/app/components/cards/CardStack/CardStack.view.tsx +++ b/app/components/cards/CardStack/CardStack.view.tsx @@ -126,11 +126,15 @@ export function CardStackView({ .slice(0, compactRecommendedLimit); })(); - // Single stack: always one column; expand reveals more in same stack (scrollable) + // Single stack: always one column; expand reveals more in the same stack. + // At lg+ the cards scroll inside this column and the See all / Show less + // control stays pinned below (Figma Flow — Right Rail). if (layout === "singleStack") { const displayedCards = expanded ? cards : compactCards; return ( -
+
-
+
{displayedCards.map((item) => ( onCardSelect(item.id)} /> @@ -158,7 +162,7 @@ export function CardStackView({