From a820739d077fb73c7cc6b9ca310b0c94042c6839 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:59:53 -0600 Subject: [PATCH 1/4] Open create-flow method modals with editable fields and tertiary default copy. Seeded section text should look like the form default state, not locked primary, and Customize stays on the kebab instead of unlocking the body. Co-authored-by: Cursor --- .../methodEditFields/CoreValueEditFields.tsx | 2 +- .../card/CommunicationMethodsScreen.tsx | 6 +-- .../screens/card/ConflictManagementScreen.tsx | 2 +- .../screens/card/MembershipMethodsScreen.tsx | 2 +- .../right-rail/DecisionApproachesScreen.tsx | 2 +- .../controls/TextArea/TextArea.container.tsx | 7 ++-- .../controls/TextArea/TextArea.types.ts | 3 +- lib/create/usesWizardFieldBlocksModalBody.ts | 3 ++ .../ConflictManagementScreen.test.tsx | 37 +++++++++++++++++++ .../MembershipMethodsScreen.test.tsx | 37 +++++++++++++++++++ tests/components/TextArea.test.tsx | 13 +++++++ tests/pages/communication-methods.test.jsx | 27 ++++++++++++++ tests/pages/decision-approaches.test.jsx | 23 ++++++++++++ 13 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 tests/components/ConflictManagementScreen.test.tsx create mode 100644 tests/components/MembershipMethodsScreen.test.tsx diff --git a/app/(app)/create/components/methodEditFields/CoreValueEditFields.tsx b/app/(app)/create/components/methodEditFields/CoreValueEditFields.tsx index 2f331f1..2fc8d23 100644 --- a/app/(app)/create/components/methodEditFields/CoreValueEditFields.tsx +++ b/app/(app)/create/components/methodEditFields/CoreValueEditFields.tsx @@ -15,7 +15,7 @@ import type { CoreValueDetailEntry } from "../../types"; export interface CoreValueEditFieldsProps { value: CoreValueDetailEntry; onChange: (_next: CoreValueDetailEntry) => void; - /** Disable meaning/signals. Create-flow core-values omits this; final-review locks until Customize. */ + /** Disable meaning/signals. Facet and final-review callers omit this so fields stay editable on open. */ readOnly?: boolean; } diff --git a/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx b/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx index e70fd2c..7a46781 100644 --- a/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx +++ b/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx @@ -10,8 +10,8 @@ * * Card click opens the Figma create modal (node `20246-15829`) with three * editable sections rendered by {@link CommunicationMethodEditFields}. The primary - * action is **Add Platform** for an unselected card; a selected card in view mode has - * no footer primary — **Remove** is available from the kebab (same behavior as legacy + * action is **Add Platform** for an unselected card and **Save** for a selected + * card. **Remove** is available from the kebab (same behavior as legacy * footer remove via {@link removeMethodCardFromFacetSelection}). */ @@ -216,7 +216,7 @@ export function CommunicationMethodsScreen() { methodId: pendingCardId, meta: state.customMethodCardMetaById, fieldBlocksById: state.customMethodCardFieldBlocksById, - modalEditUnlocked: false, + modalEditUnlocked: true, draftFieldBlocks, customFacetDetailsMatchPreset, }), diff --git a/app/(app)/create/screens/card/ConflictManagementScreen.tsx b/app/(app)/create/screens/card/ConflictManagementScreen.tsx index 0aa9337..972ce08 100644 --- a/app/(app)/create/screens/card/ConflictManagementScreen.tsx +++ b/app/(app)/create/screens/card/ConflictManagementScreen.tsx @@ -217,7 +217,7 @@ export function ConflictManagementScreen() { methodId: pendingCardId, meta: state.customMethodCardMetaById, fieldBlocksById: state.customMethodCardFieldBlocksById, - modalEditUnlocked: false, + modalEditUnlocked: true, draftFieldBlocks, customFacetDetailsMatchPreset, }), diff --git a/app/(app)/create/screens/card/MembershipMethodsScreen.tsx b/app/(app)/create/screens/card/MembershipMethodsScreen.tsx index 78532c8..1a3dd30 100644 --- a/app/(app)/create/screens/card/MembershipMethodsScreen.tsx +++ b/app/(app)/create/screens/card/MembershipMethodsScreen.tsx @@ -214,7 +214,7 @@ export function MembershipMethodsScreen() { methodId: pendingCardId, meta: state.customMethodCardMetaById, fieldBlocksById: state.customMethodCardFieldBlocksById, - modalEditUnlocked: false, + modalEditUnlocked: true, draftFieldBlocks, customFacetDetailsMatchPreset, }), diff --git a/app/(app)/create/screens/right-rail/DecisionApproachesScreen.tsx b/app/(app)/create/screens/right-rail/DecisionApproachesScreen.tsx index adb23da..7b71481 100644 --- a/app/(app)/create/screens/right-rail/DecisionApproachesScreen.tsx +++ b/app/(app)/create/screens/right-rail/DecisionApproachesScreen.tsx @@ -250,7 +250,7 @@ export function DecisionApproachesScreen() { methodId: pendingCardId, meta: state.customMethodCardMetaById, fieldBlocksById: state.customMethodCardFieldBlocksById, - modalEditUnlocked: false, + modalEditUnlocked: true, draftFieldBlocks, customFacetDetailsMatchPreset, }), diff --git a/app/components/controls/TextArea/TextArea.container.tsx b/app/components/controls/TextArea/TextArea.container.tsx index 3c6f59f..044ee1a 100644 --- a/app/components/controls/TextArea/TextArea.container.tsx +++ b/app/components/controls/TextArea/TextArea.container.tsx @@ -80,7 +80,8 @@ const TextAreaContainer = forwardRef( }, }; - // State styles (embedded: Figma 20736-12668 – borderless, darker grey block, white text) + // Embedded (Figma 20736-12668): borderless grey block; default copy is + // tertiary, primary on focus so seeded modal fields stay muted until edit. const getStateStyles = (): { textarea: string; label: string; @@ -89,13 +90,13 @@ const TextAreaContainer = forwardRef( if (disabled) { return { textarea: - "border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] cursor-not-allowed opacity-60", + "border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-tertiary,#b4b4b4)] cursor-not-allowed opacity-60", label: "text-[var(--color-content-default-secondary)]", }; } return { textarea: - "border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-default-tertiary)] focus:ring-inset", + "border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-tertiary,#b4b4b4)] placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] focus:text-[var(--color-content-default-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-default-tertiary)] focus:ring-inset", label: "text-[var(--color-content-default-secondary)]", }; } diff --git a/app/components/controls/TextArea/TextArea.types.ts b/app/components/controls/TextArea/TextArea.types.ts index 5b8d98a..812a35a 100644 --- a/app/components/controls/TextArea/TextArea.types.ts +++ b/app/components/controls/TextArea/TextArea.types.ts @@ -48,7 +48,8 @@ export interface TextAreaProps extends Omit< showHelpIcon?: boolean; /** * Visual appearance. "embedded" matches Create modal sections (Figma 20736-12668): - * borderless, darker grey background, white text. "default" is standard bordered input. + * borderless, darker grey background, tertiary text in default (primary on + * focus). "default" is the standard bordered input. * @default "default" */ appearance?: TextAreaAppearanceValue; diff --git a/lib/create/usesWizardFieldBlocksModalBody.ts b/lib/create/usesWizardFieldBlocksModalBody.ts index 4afc432..ea6ac57 100644 --- a/lib/create/usesWizardFieldBlocksModalBody.ts +++ b/lib/create/usesWizardFieldBlocksModalBody.ts @@ -29,6 +29,9 @@ import { isCustomMethodCardId } from "./isCustomMethodCardId"; * so meta-only wizard cards show policy copy instead of empty section editors. * Pass `customFacetDetailsMatchPreset: false` when the caller knows facet details * were edited or cloned from a filled preset. + * + * Create-flow facet screens and final-review pass `modalEditUnlocked: true` so + * section fields stay editable on open (Customize is the kebab wizard). */ export function usesWizardFieldBlocksModalBody(args: { methodId: string; diff --git a/tests/components/ConflictManagementScreen.test.tsx b/tests/components/ConflictManagementScreen.test.tsx new file mode 100644 index 0000000..2d2a68b --- /dev/null +++ b/tests/components/ConflictManagementScreen.test.tsx @@ -0,0 +1,37 @@ +import { describe, it, expect, afterEach } from "vitest"; +import { + renderWithProviders as render, + screen, + cleanup, + within, +} from "../utils/test-utils"; +import { fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom/vitest"; +import { ConflictManagementScreen } from "../../app/(app)/create/screens/card/ConflictManagementScreen"; + +afterEach(() => { + cleanup(); +}); + +describe("ConflictManagementScreen", () => { + it("opens section fields editable without Customize", async () => { + render(); + fireEvent.click( + screen.getAllByRole("button", { + name: /Peer Mediation: Trained members/, + })[0], + ); + const dialog = await screen.findByRole("dialog"); + const fields = within(dialog).getAllByRole("textbox"); + expect(fields.length).toBeGreaterThan(0); + for (const field of fields) { + expect(field).toBeEnabled(); + } + fireEvent.click( + within(dialog).getByRole("button", { name: "More options" }), + ); + expect( + screen.getByRole("menuitem", { name: "Customize" }), + ).toBeInTheDocument(); + }); +}); diff --git a/tests/components/MembershipMethodsScreen.test.tsx b/tests/components/MembershipMethodsScreen.test.tsx new file mode 100644 index 0000000..c10e7c7 --- /dev/null +++ b/tests/components/MembershipMethodsScreen.test.tsx @@ -0,0 +1,37 @@ +import { describe, it, expect, afterEach } from "vitest"; +import { + renderWithProviders as render, + screen, + cleanup, + within, +} from "../utils/test-utils"; +import { fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom/vitest"; +import { MembershipMethodsScreen } from "../../app/(app)/create/screens/card/MembershipMethodsScreen"; + +afterEach(() => { + cleanup(); +}); + +describe("MembershipMethodsScreen", () => { + it("opens section fields editable without Customize", async () => { + render(); + fireEvent.click( + screen.getAllByRole("button", { + name: /Open Access: Maximum inclusion/, + })[0], + ); + const dialog = await screen.findByRole("dialog"); + const fields = within(dialog).getAllByRole("textbox"); + expect(fields.length).toBeGreaterThan(0); + for (const field of fields) { + expect(field).toBeEnabled(); + } + fireEvent.click( + within(dialog).getByRole("button", { name: "More options" }), + ); + expect( + screen.getByRole("menuitem", { name: "Customize" }), + ).toBeInTheDocument(); + }); +}); diff --git a/tests/components/TextArea.test.tsx b/tests/components/TextArea.test.tsx index 61588b2..96bce2b 100644 --- a/tests/components/TextArea.test.tsx +++ b/tests/components/TextArea.test.tsx @@ -42,4 +42,17 @@ describe("TextArea appearance", () => { expect(textarea).toBeInTheDocument(); expect(textarea).toHaveClass("border-0"); }); + + it("uses tertiary text in the embedded default state and primary on focus", () => { + renderWithProviders( +