Show seven cards on the four method decks and open each as a routing overlay of its parts, so groups write from empty fields with Generate draft, search the full deck, and see unselected scope chips with enough contrast.

This commit is contained in:
adilallo committed 2026-11-05 22:43:30 -07:00
1 parent dea7dd7fbd
commit 46656f52d3
28 files changed
+1349 -318

No files matched your search

@@ -8,11 +8,11 @@
* ({@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
* primary action is **Add Platform** for an unselected card and **Save** for a
* selected card. **Remove** is a danger footer button on selected cards (and
* remains in the kebab) via {@link removeMethodCardFromFacetSelection}.
* Card click opens Overlay / Modal / Card as a routing view of its parts
* ({@link MethodCardPolicyDialog}). The primary action is **Add to Rule** for
* an unselected card and **Save** for a selected card. **Deselect** is the
* footer on selected cards (**Remove** stays in the kebab) via
* {@link removeMethodCardFromFacetSelection}.
*/
import { useState, useCallback, useMemo, useRef } from "react";
@@ -24,9 +24,9 @@ import { useMethodCardDeckOrdering } from "../../hooks/useMethodCardDeckOrdering
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";
import { CommunicationMethodEditFields } from "../../components/methodEditFields";
import { MethodCardPolicyDialog } from "../../components/MethodCardPolicyDialog";
import { useCommunicationPolicySteps } from "../../components/methodCardPolicySteps";
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
import { communicationPresetFor, emptyCommunicationDetails } from "../../../../../lib/create/finalReviewChipPresets";
@@ -105,7 +105,8 @@ export function CommunicationMethodsScreen() {
[comm.methods, selectedIds, state.customMethodCardMetaById],
);
const { sampleCards, compactCardIds, methodById, recommendationsReady } =
const rec = m.create.customRule.recommendations;
const { sampleCards, compactCardIds, methodById, recommendationsReady, showDeckLine, compactLimit } =
useMethodCardDeckOrdering(
"communication",
mergedMethods,
@@ -188,10 +189,14 @@ export function CommunicationMethodsScreen() {
},
[markCreateFlowInteraction],
);
const communicationSteps = useCommunicationPolicySteps(
pendingDraft ?? emptyCommunicationDetails(),
handleDraftChange,
pendingCardId ? communicationPresetFor(pendingCardId) : undefined,
);
const isSelectedCardModal =
pendingCardId !== null && selectedIds.includes(pendingCardId);
const showMethodModalPrimary = true;
const customFacetDetailsMatchPreset = useMemo(() => {
if (!pendingCardId || !pendingDraft) return false;
@@ -712,57 +717,52 @@ export function CommunicationMethodsScreen() {
}}
hasMore={true}
toggleLabel={comm.page.seeAllLink}
compactRecommendedLimit={5}
compactRecommendedLimit={compactLimit}
compactCardIds={compactCardIds}
layout="singleStack"
deckLine={showDeckLine ? rec.deckLine : undefined}
searchPlaceholder={rec.searchPlaceholder}
alternativePrefix={rec.alternative}
onAlternativeSelect={handleCardClick}
className="w-full lg:h-full lg:min-h-0 lg:flex-1"
/>
)}
</div>
</CreateFlowTwoColumnSelectShell>
<Create
<MethodCardPolicyDialog
isOpen={createModalOpen}
onClose={handleCreateModalClose}
onNext={handleCreateModalPrimary}
title={modalConfig.title}
cardLabel={modalConfig.title}
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
showNextButton={showMethodModalPrimary}
showRemoveButton={isSelectedCardModal}
onRemove={handleRemoveSelectedFromModal}
backdropVariant="blurredYellow"
editing={isSelectedCardModal}
steps={communicationSteps}
onClose={handleCreateModalClose}
onConfirm={handleCreateModalPrimary}
onDeselect={
isSelectedCardModal ? handleRemoveSelectedFromModal : undefined
}
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
kebabMenuAriaLabel={modalKebabMenu.menuAriaLabel}
kebabMenuItems={kebabMenuItems}
>
{pendingCardId && pendingDraft ? (
modalUsesWizardFieldBlocksBody ? (
<CustomMethodCardModalBody
cardId={pendingCardId}
blocksById={state.customMethodCardFieldBlocksById}
blocksOverride={
draftFieldBlocks !== null ? draftFieldBlocks : undefined
}
policyMeta={state.customMethodCardMetaById?.[pendingCardId]}
showPolicyContentLockupWhenNoBlocks={
draftFieldBlocks === null || draftFieldBlocks.length === 0
}
onFieldBlocksChange={
draftFieldBlocks === null
? undefined
: (next) => setDraftFieldBlocks(next)
}
/>
) : (
<CommunicationMethodEditFields
value={pendingDraft}
onChange={handleDraftChange}
generateDraft={communicationPresetFor(pendingCardId)}
/>
)
{pendingCardId && modalUsesWizardFieldBlocksBody ? (
<CustomMethodCardModalBody
cardId={pendingCardId}
blocksById={state.customMethodCardFieldBlocksById}
blocksOverride={
draftFieldBlocks !== null ? draftFieldBlocks : undefined
}
policyMeta={state.customMethodCardMetaById?.[pendingCardId]}
showPolicyContentLockupWhenNoBlocks={
draftFieldBlocks === null || draftFieldBlocks.length === 0
}
onFieldBlocksChange={
draftFieldBlocks === null
? undefined
: (next) => setDraftFieldBlocks(next)
}
/>
) : null}
</Create>
</MethodCardPolicyDialog>
<CustomMethodCardWizard
isOpen={addCustomWizardOpen}
onClose={handleCloseAddWizard}
@@ -7,12 +7,9 @@
* Page chrome is the shared question-and-options shell
* (`CreateFlowTwoColumnSelectShell`); options are CardStack `singleStack`.
*
* Card click opens the Figma "Add Approach" create modal (node `20874-172292`)
* with four controls rendered by {@link ConflictManagementEditFields}: Core
* Principle, Applicable Scope (text area), Process Protocol, and Restoration
* & Fallbacks. The same field set is reused on `/create/final-review` — see
* `FinalReviewChipEditModal`. Confirm persists both the chip selection and
* any user edits as a `conflictManagementDetailsById[id]` override.
* Card click opens Overlay / Modal / Card as a routing view of its parts
* ({@link MethodCardPolicyDialog}). Confirm persists the chip selection and
* any edits as a `conflictManagementDetailsById[id]` override.
*/
import { useState, useCallback, useMemo, useRef } from "react";
@@ -24,9 +21,9 @@ import { useMethodCardDeckOrdering } from "../../hooks/useMethodCardDeckOrdering
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";
import { ConflictManagementEditFields } from "../../components/methodEditFields";
import { MethodCardPolicyDialog } from "../../components/MethodCardPolicyDialog";
import { useConflictPolicySteps } from "../../components/methodCardPolicySteps";
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
import { conflictManagementPresetFor, emptyConflictManagementDetails } from "../../../../../lib/create/finalReviewChipPresets";
@@ -105,7 +102,8 @@ export function ConflictManagementScreen() {
[cm.methods, selectedIds, state.customMethodCardMetaById],
);
const { sampleCards, compactCardIds, methodById, recommendationsReady } =
const rec = m.create.customRule.recommendations;
const { sampleCards, compactCardIds, methodById, recommendationsReady, showDeckLine, compactLimit } =
useMethodCardDeckOrdering(
"conflictManagement",
mergedMethods,
@@ -192,10 +190,14 @@ export function ConflictManagementScreen() {
},
[markCreateFlowInteraction],
);
const conflictSteps = useConflictPolicySteps(
pendingDraft ?? emptyConflictManagementDetails(),
handleDraftChange,
pendingCardId ? conflictManagementPresetFor(pendingCardId) : undefined,
);
const isSelectedCardModal =
pendingCardId !== null && selectedIds.includes(pendingCardId);
const showMethodModalPrimary = true;
const customFacetDetailsMatchPreset = useMemo(() => {
if (!pendingCardId || !pendingDraft) return false;
@@ -716,57 +718,52 @@ export function ConflictManagementScreen() {
}}
hasMore={true}
toggleLabel={cm.page.seeAllLink}
compactRecommendedLimit={5}
compactRecommendedLimit={compactLimit}
compactCardIds={compactCardIds}
layout="singleStack"
deckLine={showDeckLine ? rec.deckLine : undefined}
searchPlaceholder={rec.searchPlaceholder}
alternativePrefix={rec.alternative}
onAlternativeSelect={handleCardClick}
className="w-full lg:h-full lg:min-h-0 lg:flex-1"
/>
)}
</div>
</CreateFlowTwoColumnSelectShell>
<Create
<MethodCardPolicyDialog
isOpen={createModalOpen}
onClose={handleCreateModalClose}
onNext={handleCreateModalPrimary}
title={modalConfig.title}
cardLabel={modalConfig.title}
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
showNextButton={showMethodModalPrimary}
showRemoveButton={isSelectedCardModal}
onRemove={handleRemoveSelectedFromModal}
backdropVariant="blurredYellow"
editing={isSelectedCardModal}
steps={conflictSteps}
onClose={handleCreateModalClose}
onConfirm={handleCreateModalPrimary}
onDeselect={
isSelectedCardModal ? handleRemoveSelectedFromModal : undefined
}
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
kebabMenuAriaLabel={modalKebabMenu.menuAriaLabel}
kebabMenuItems={kebabMenuItems}
>
{pendingCardId && pendingDraft ? (
modalUsesWizardFieldBlocksBody ? (
<CustomMethodCardModalBody
cardId={pendingCardId}
blocksById={state.customMethodCardFieldBlocksById}
blocksOverride={
draftFieldBlocks !== null ? draftFieldBlocks : undefined
}
policyMeta={state.customMethodCardMetaById?.[pendingCardId]}
showPolicyContentLockupWhenNoBlocks={
draftFieldBlocks === null || draftFieldBlocks.length === 0
}
onFieldBlocksChange={
draftFieldBlocks === null
? undefined
: (next) => setDraftFieldBlocks(next)
}
/>
) : (
<ConflictManagementEditFields
value={pendingDraft}
onChange={handleDraftChange}
generateDraft={conflictManagementPresetFor(pendingCardId)}
/>
)
{pendingCardId && modalUsesWizardFieldBlocksBody ? (
<CustomMethodCardModalBody
cardId={pendingCardId}
blocksById={state.customMethodCardFieldBlocksById}
blocksOverride={
draftFieldBlocks !== null ? draftFieldBlocks : undefined
}
policyMeta={state.customMethodCardMetaById?.[pendingCardId]}
showPolicyContentLockupWhenNoBlocks={
draftFieldBlocks === null || draftFieldBlocks.length === 0
}
onFieldBlocksChange={
draftFieldBlocks === null
? undefined
: (next) => setDraftFieldBlocks(next)
}
/>
) : null}
</Create>
</MethodCardPolicyDialog>
<CustomMethodCardWizard
isOpen={addCustomWizardOpen}
onClose={handleCloseAddWizard}
@@ -7,13 +7,9 @@
* Page chrome is the shared question-and-options shell
* (`CreateFlowTwoColumnSelectShell`); options are CardStack `singleStack`.
*
* Card click opens the Figma create modal (node `20858-13948`) with three
* editable sections rendered by {@link MembershipMethodEditFields}. The same
* field set is reused on `/create/final-review` — see `FinalReviewChipEditModal`.
* Confirm persists both the chip selection and any user edits as a
* `membershipMethodDetailsById[id]` override; section defaults come from
* `messages/en/create/customRule/membership.json` and will be replaced with
* DB-driven content.
* Card click opens Overlay / Modal / Card as a routing view of its parts
* ({@link MethodCardPolicyDialog}). Confirm persists the chip selection and
* any edits as a `membershipMethodDetailsById[id]` override.
*/
import { useState, useCallback, useMemo, useRef } from "react";
@@ -25,9 +21,9 @@ import { useMethodCardDeckOrdering } from "../../hooks/useMethodCardDeckOrdering
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";
import { MembershipMethodEditFields } from "../../components/methodEditFields";
import { MethodCardPolicyDialog } from "../../components/MethodCardPolicyDialog";
import { useMembershipPolicySteps } from "../../components/methodCardPolicySteps";
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
import { membershipPresetFor, emptyMembershipDetails } from "../../../../../lib/create/finalReviewChipPresets";
@@ -106,7 +102,8 @@ export function MembershipMethodsScreen() {
[mem.methods, selectedIds, state.customMethodCardMetaById],
);
const { sampleCards, compactCardIds, methodById, recommendationsReady } =
const rec = m.create.customRule.recommendations;
const { sampleCards, compactCardIds, methodById, recommendationsReady, showDeckLine, compactLimit } =
useMethodCardDeckOrdering(
"membership",
mergedMethods,
@@ -189,10 +186,14 @@ export function MembershipMethodsScreen() {
},
[markCreateFlowInteraction],
);
const membershipSteps = useMembershipPolicySteps(
pendingDraft ?? emptyMembershipDetails(),
handleDraftChange,
pendingCardId ? membershipPresetFor(pendingCardId) : undefined,
);
const isSelectedCardModal =
pendingCardId !== null && selectedIds.includes(pendingCardId);
const showMethodModalPrimary = true;
const customFacetDetailsMatchPreset = useMemo(() => {
if (!pendingCardId || !pendingDraft) return false;
@@ -709,57 +710,52 @@ export function MembershipMethodsScreen() {
}}
hasMore={true}
toggleLabel={mem.page.seeAllLink}
compactRecommendedLimit={5}
compactRecommendedLimit={compactLimit}
compactCardIds={compactCardIds}
layout="singleStack"
deckLine={showDeckLine ? rec.deckLine : undefined}
searchPlaceholder={rec.searchPlaceholder}
alternativePrefix={rec.alternative}
onAlternativeSelect={handleCardClick}
className="w-full lg:h-full lg:min-h-0 lg:flex-1"
/>
)}
</div>
</CreateFlowTwoColumnSelectShell>
<Create
<MethodCardPolicyDialog
isOpen={createModalOpen}
onClose={handleCreateModalClose}
onNext={handleCreateModalPrimary}
title={modalConfig.title}
cardLabel={modalConfig.title}
description={modalConfig.description}
nextButtonText={modalConfig.nextButtonText}
showBackButton={false}
showNextButton={showMethodModalPrimary}
showRemoveButton={isSelectedCardModal}
onRemove={handleRemoveSelectedFromModal}
backdropVariant="blurredYellow"
editing={isSelectedCardModal}
steps={membershipSteps}
onClose={handleCreateModalClose}
onConfirm={handleCreateModalPrimary}
onDeselect={
isSelectedCardModal ? handleRemoveSelectedFromModal : undefined
}
kebabTriggerAriaLabel={modalKebabMenu.triggerAriaLabel}
kebabMenuAriaLabel={modalKebabMenu.menuAriaLabel}
kebabMenuItems={kebabMenuItems}
>
{pendingCardId && pendingDraft ? (
modalUsesWizardFieldBlocksBody ? (
<CustomMethodCardModalBody
cardId={pendingCardId}
blocksById={state.customMethodCardFieldBlocksById}
blocksOverride={
draftFieldBlocks !== null ? draftFieldBlocks : undefined
}
policyMeta={state.customMethodCardMetaById?.[pendingCardId]}
showPolicyContentLockupWhenNoBlocks={
draftFieldBlocks === null || draftFieldBlocks.length === 0
}
onFieldBlocksChange={
draftFieldBlocks === null
? undefined
: (next) => setDraftFieldBlocks(next)
}
/>
) : (
<MembershipMethodEditFields
value={pendingDraft}
onChange={handleDraftChange}
generateDraft={membershipPresetFor(pendingCardId)}
/>
)
{pendingCardId && modalUsesWizardFieldBlocksBody ? (
<CustomMethodCardModalBody
cardId={pendingCardId}
blocksById={state.customMethodCardFieldBlocksById}
blocksOverride={
draftFieldBlocks !== null ? draftFieldBlocks : undefined
}
policyMeta={state.customMethodCardMetaById?.[pendingCardId]}
showPolicyContentLockupWhenNoBlocks={
draftFieldBlocks === null || draftFieldBlocks.length === 0
}
onFieldBlocksChange={
draftFieldBlocks === null
? undefined
: (next) => setDraftFieldBlocks(next)
}
/>
) : null}
</Create>
</MethodCardPolicyDialog>
<CustomMethodCardWizard
isOpen={addCustomWizardOpen}
onClose={handleCloseAddWizard}