Create flow centralization and cleanup

This commit is contained in:
adilallo
2026-04-30 08:11:55 -06:00
parent a37a72c71d
commit b7446873cd
26 changed files with 709 additions and 361 deletions
+32 -36
View File
@@ -20,12 +20,20 @@ import {
getNextStep,
getStepIndex,
parseReviewReturnSearchParam,
CREATE_FLOW_REVIEW_RETURN_QUERY_KEY,
createFlowStepUsesSelectSplitScroll,
TEMPLATES_FACET_RECOMMEND_QUERY,
TEMPLATES_FACET_RECOMMEND_VALUE,
TEMPLATE_REVIEW_FROM_CREATE_FLOW_QUERY,
TEMPLATE_REVIEW_FROM_CREATE_FLOW_VALUE,
} from "./utils/flowSteps";
import {
CREATE_FLOW_SYNC_DRAFT_QUERY,
CREATE_FLOW_SYNC_DRAFT_VALUE,
CREATE_ROUTES,
createFlowStepPath,
createFlowStepPathAfterStrippingReviewReturn,
createFlowStepPathWithSyncDraft,
} from "./utils/createFlowPaths";
import { getProportionBarProgressForCreateFlowStep } from "./utils/createFlowProportionProgress";
import {
createFlowStepUsesCenteredTextLayout,
@@ -42,12 +50,12 @@ import {
clearAnonymousCreateFlowStorage,
setTransferPendingFlag,
} from "./utils/anonymousDraftStorage";
import type { CreateFlowMethodCardFacetSection } from "./types";
import {
createFlowStateFromPublishedRule,
isPublishedRuleSelectionMissing,
methodSectionsPinsFromPublishedHydratePatch,
} from "../../../lib/create/publishedDocumentToCreateFlowState";
import { METHOD_FACET_API_SECTION_IDS } from "../../../lib/create/customRuleFacets";
import { readLastPublishedRule } from "../../../lib/create/lastPublishedRule";
import { deleteServerDraft } from "../../../lib/create/api";
import messages from "../../../messages/en/index";
@@ -193,8 +201,8 @@ function CreateFlowLayoutContent({
const loginReturnPath =
currentStep === "edit-rule"
? "/create/edit-rule?syncDraft=1"
: "/create/final-review?syncDraft=1";
? createFlowStepPathWithSyncDraft("edit-rule")
: createFlowStepPathWithSyncDraft("final-review");
const {
publishBannerMessage,
@@ -248,7 +256,7 @@ function CreateFlowLayoutContent({
if (sessionUser) {
void deleteServerDraft();
}
router.push("/");
router.push(CREATE_ROUTES.root);
return;
}
@@ -262,7 +270,7 @@ function CreateFlowLayoutContent({
variant: "saveProgress",
nextPath:
returnToTemplateReview ??
`${pathname ?? "/create"}?syncDraft=1`,
`${pathname != null && pathname.length > 0 ? pathname : CREATE_ROUTES.createRoot}?${CREATE_FLOW_SYNC_DRAFT_QUERY}=${CREATE_FLOW_SYNC_DRAFT_VALUE}`,
backdropVariant: "blurredYellow",
});
return;
@@ -278,7 +286,7 @@ function CreateFlowLayoutContent({
sessionUser &&
currentStep === "community-save"
) {
router.replace("/create/review");
router.replace(CREATE_ROUTES.review);
}
}, [sessionResolved, sessionUser, currentStep, router]);
@@ -294,12 +302,12 @@ function CreateFlowLayoutContent({
if (currentStep !== "edit-rule") return;
const last = readLastPublishedRule();
if (!last) {
router.replace("/create/completed");
router.replace(CREATE_ROUTES.completed);
return;
}
const editingId = state.editingPublishedRuleId?.trim() ?? "";
if (editingId.length > 0 && editingId !== last.id) {
router.replace("/create/completed");
router.replace(CREATE_ROUTES.completed);
return;
}
const titleOk =
@@ -307,9 +315,7 @@ function CreateFlowLayoutContent({
const sectionsClear = (state.sections?.length ?? 0) === 0;
const patch = createFlowStateFromPublishedRule(last);
const pinPatch = methodSectionsPinsFromPublishedHydratePatch(patch);
const METHOD_CARD_PIN_FACETS: readonly CreateFlowMethodCardFacetSection[] =
["communication", "membership", "decisionApproaches", "conflictManagement"];
const needsPinMerge = METHOD_CARD_PIN_FACETS.some(
const needsPinMerge = METHOD_FACET_API_SECTION_IDS.some(
(key) =>
pinPatch[key] === true &&
state.methodSectionsPinCommitted?.[key] !== true,
@@ -406,11 +412,9 @@ function CreateFlowLayoutContent({
currentStep === "final-review" || currentStep === "edit-rule";
const isCardLayoutStep = createFlowStepUsesCardLayout(currentStep);
/** Two-column select / right-rail: below `lg` main scrolls; at `lg+` only the right column scrolls. */
const isSelectSplitScrollStep =
currentStep === "community-size" ||
currentStep === "community-structure" ||
currentStep === "core-values" ||
currentStep === "decision-approaches";
const isSelectSplitScrollStep = createFlowStepUsesSelectSplitScroll(
currentStep,
);
const stepIdx = currentStep != null ? getStepIndex(currentStep) : -1;
/** At `md+`, main cross-axis: center by default; exceptions stay top-aligned (see product spec). */
@@ -586,7 +590,7 @@ function CreateFlowLayoutContent({
editingPublishedRuleId: last.id,
sections: [],
});
router.push("/create/edit-rule");
router.push(createFlowStepPath("edit-rule"));
}
: undefined
}
@@ -738,15 +742,11 @@ function CreateFlowLayoutContent({
setMethodSectionsPinCommitted(facet, true);
}
if (reviewReturnTarget) {
const params = new URLSearchParams(
searchParams?.toString() ?? "",
);
params.delete(CREATE_FLOW_REVIEW_RETURN_QUERY_KEY);
const qs = params.toString();
router.push(
qs.length > 0
? `/create/${reviewReturnTarget}?${qs}`
: `/create/${reviewReturnTarget}`,
createFlowStepPathAfterStrippingReviewReturn(
reviewReturnTarget,
searchParams,
),
);
return;
}
@@ -784,20 +784,16 @@ function CreateFlowLayoutContent({
? () =>
router.push(
templateReviewFooterBackToCreateReview
? "/create/review"
: "/",
? CREATE_ROUTES.review
: CREATE_ROUTES.root,
)
: reviewReturnTarget
? () => {
const params = new URLSearchParams(
searchParams?.toString() ?? "",
);
params.delete(CREATE_FLOW_REVIEW_RETURN_QUERY_KEY);
const qs = params.toString();
router.push(
qs.length > 0
? `/create/${reviewReturnTarget}?${qs}`
: `/create/${reviewReturnTarget}`,
createFlowStepPathAfterStrippingReviewReturn(
reviewReturnTarget,
searchParams,
),
);
}
: previousStep
@@ -185,8 +185,8 @@ export function CreateFlowProvider({
clearCoreValueDetailsLocalStorage();
}, []);
// Keys produced by the Create Custom stage screens + `buildTemplateCustomizePrefill`.
// Kept in sync with `CreateFlowState` comments marked "Create Custom —".
// Keys cleared here match `STRIP_CUSTOM_RULE_SELECTION_STATE_KEYS` from
// `lib/create/customRuleFacets.ts` (CUSTOM_RULE_FACETS / CR-92).
const resetCustomRuleSelections = useCallback(() => {
setState((prev) => stripCustomRuleSelectionFields(prev));
// Effect on `state.coreValueDetailsByChipId` clears its dedicated
@@ -10,6 +10,7 @@ import {
CREATE_FLOW_COMPLETED_CELEBRATE_QUERY,
CREATE_FLOW_COMPLETED_CELEBRATE_VALUE,
} from "../utils/flowSteps";
import { createFlowStepPath } from "../utils/createFlowPaths";
type AppRouterLike = { push: (_href: string) => void };
@@ -80,7 +81,7 @@ export function useCreateFlowFinalize({
document: ruleDocument,
});
updateState({ editingPublishedRuleId: undefined });
router.push("/create/completed");
router.push(createFlowStepPath("completed"));
return;
}
if (updateResult.status === 401) {
@@ -113,7 +114,10 @@ export function useCreateFlowFinalize({
document: ruleDocument,
});
router.push(
`/create/completed?${CREATE_FLOW_COMPLETED_CELEBRATE_QUERY}=${CREATE_FLOW_COMPLETED_CELEBRATE_VALUE}`,
createFlowStepPath("completed", {
[CREATE_FLOW_COMPLETED_CELEBRATE_QUERY]:
CREATE_FLOW_COMPLETED_CELEBRATE_VALUE,
}),
);
return;
}
@@ -2,16 +2,14 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { buildFacetQueryString } from "../../../../lib/create/buildFacetQueryString";
import type { MethodFacetApiSectionId } from "../../../../lib/create/customRuleFacets";
import { useCreateFlow } from "../context/CreateFlowContext";
/**
* Card-deck section ids served by `/api/create-flow/methods` (CR-88 §9.2).
* Same tuple as {@link METHOD_FACET_API_SECTION_IDS} (`CUSTOM_RULE_FACETS`, CR-92).
*/
export type RecommendationSection =
| "communication"
| "membership"
| "decisionApproaches"
| "conflictManagement";
export type RecommendationSection = MethodFacetApiSectionId;
export type FacetRecommendationsResult = {
/** `true` once the network call completes (or short-circuits with no facets). */
@@ -2,20 +2,7 @@
import type { ReactNode } from "react";
import type { CreateFlowStep } from "../types";
import { InformationalScreen } from "./informational/InformationalScreen";
import { CreateFlowTextFieldScreen } from "./text/CreateFlowTextFieldScreen";
import { CommunitySizeSelectScreen } from "./select/CommunitySizeSelectScreen";
import { CommunityStructureSelectScreen } from "./select/CommunityStructureSelectScreen";
import { CoreValuesSelectScreen } from "./select/CoreValuesSelectScreen";
import { ConfirmStakeholdersScreen } from "./select/ConfirmStakeholdersScreen";
import { CommunityUploadScreen } from "./upload/CommunityUploadScreen";
import { CommunityReviewScreen } from "./review/CommunityReviewScreen";
import { FinalReviewScreen } from "./review/FinalReviewScreen";
import { CommunicationMethodsScreen } from "./card/CommunicationMethodsScreen";
import { MembershipMethodsScreen } from "./card/MembershipMethodsScreen";
import { ConflictManagementScreen } from "./card/ConflictManagementScreen";
import { DecisionApproachesScreen } from "./right-rail/DecisionApproachesScreen";
import { CompletedScreen } from "./completed/CompletedScreen";
import { renderCreateFlowScreen } from "./createFlowScreenComponents";
/**
* Maps each wizard `screenId` to its screen component.
@@ -23,73 +10,14 @@ import { CompletedScreen } from "./completed/CompletedScreen";
* **Folder rule (Figma):** subfolders match `CREATE_FLOW_SCREEN_REGISTRY[].layoutKind`
* — `select/` (two-column chip flows), `card/` (compact card-stack steps), `text/`, etc.
* The URL segment (`communication-methods`) is not the folder name; see `createFlowScreenRegistry.ts`.
*
* Implementation lives in {@link renderCreateFlowScreen} (`createFlowScreenComponents.tsx`)
* so the registry metadata and this router stay easier to keep in sync (CR-92 §3).
*/
export function CreateFlowScreenView({
screenId,
}: {
screenId: CreateFlowStep;
}): ReactNode {
switch (screenId) {
case "informational":
return <InformationalScreen />;
case "community-name":
return (
<CreateFlowTextFieldScreen
messageNamespace="create.community.communityName"
stateField="title"
maxLength={48}
/>
);
case "community-structure":
return <CommunityStructureSelectScreen />;
case "community-context":
return (
<CreateFlowTextFieldScreen
messageNamespace="create.community.communityContext"
stateField="communityContext"
maxLength={200}
mainAlign="center"
/>
);
case "community-size":
return <CommunitySizeSelectScreen />;
case "community-upload":
return <CommunityUploadScreen />;
case "community-save":
return (
<CreateFlowTextFieldScreen
messageNamespace="create.community.communitySave"
stateField="communitySaveEmail"
maxLength={254}
mainAlign="center"
inputType="email"
showCharacterCount={false}
headerJustification="center"
/>
);
case "review":
return <CommunityReviewScreen />;
case "core-values":
return <CoreValuesSelectScreen />;
case "communication-methods":
return <CommunicationMethodsScreen />;
case "membership-methods":
return <MembershipMethodsScreen />;
case "decision-approaches":
return <DecisionApproachesScreen />;
case "conflict-management":
return <ConflictManagementScreen />;
case "confirm-stakeholders":
return <ConfirmStakeholdersScreen />;
case "final-review":
return <FinalReviewScreen />;
case "edit-rule":
return <FinalReviewScreen variant="editPublished" />;
case "completed":
return <CompletedScreen />;
default: {
const _exhaustive: never = screenId;
return _exhaustive;
}
}
return renderCreateFlowScreen(screenId);
}
@@ -0,0 +1,88 @@
/**
* Step → screen component map (Linear CR-92 §3). Keeps {@link CreateFlowScreenView}
* thin; pair with {@link CREATE_FLOW_SCREEN_REGISTRY} metadata in tests/docs so
* new steps do not drift.
*/
import type { ReactNode } from "react";
import type { CreateFlowStep } from "../types";
import { InformationalScreen } from "./informational/InformationalScreen";
import { CreateFlowTextFieldScreen } from "./text/CreateFlowTextFieldScreen";
import { CommunitySizeSelectScreen } from "./select/CommunitySizeSelectScreen";
import { CommunityStructureSelectScreen } from "./select/CommunityStructureSelectScreen";
import { CoreValuesSelectScreen } from "./select/CoreValuesSelectScreen";
import { ConfirmStakeholdersScreen } from "./select/ConfirmStakeholdersScreen";
import { CommunityUploadScreen } from "./upload/CommunityUploadScreen";
import { CommunityReviewScreen } from "./review/CommunityReviewScreen";
import { FinalReviewScreen } from "./review/FinalReviewScreen";
import { CommunicationMethodsScreen } from "./card/CommunicationMethodsScreen";
import { MembershipMethodsScreen } from "./card/MembershipMethodsScreen";
import { ConflictManagementScreen } from "./card/ConflictManagementScreen";
import { DecisionApproachesScreen } from "./right-rail/DecisionApproachesScreen";
import { CompletedScreen } from "./completed/CompletedScreen";
export function renderCreateFlowScreen(screenId: CreateFlowStep): ReactNode {
switch (screenId) {
case "informational":
return <InformationalScreen />;
case "community-name":
return (
<CreateFlowTextFieldScreen
messageNamespace="create.community.communityName"
stateField="title"
maxLength={48}
/>
);
case "community-structure":
return <CommunityStructureSelectScreen />;
case "community-context":
return (
<CreateFlowTextFieldScreen
messageNamespace="create.community.communityContext"
stateField="communityContext"
maxLength={200}
mainAlign="center"
/>
);
case "community-size":
return <CommunitySizeSelectScreen />;
case "community-upload":
return <CommunityUploadScreen />;
case "community-save":
return (
<CreateFlowTextFieldScreen
messageNamespace="create.community.communitySave"
stateField="communitySaveEmail"
maxLength={254}
mainAlign="center"
inputType="email"
showCharacterCount={false}
headerJustification="center"
/>
);
case "review":
return <CommunityReviewScreen />;
case "core-values":
return <CoreValuesSelectScreen />;
case "communication-methods":
return <CommunicationMethodsScreen />;
case "membership-methods":
return <MembershipMethodsScreen />;
case "decision-approaches":
return <DecisionApproachesScreen />;
case "conflict-management":
return <ConflictManagementScreen />;
case "confirm-stakeholders":
return <ConfirmStakeholdersScreen />;
case "final-review":
return <FinalReviewScreen />;
case "edit-rule":
return <FinalReviewScreen variant="editPublished" />;
case "completed":
return <CompletedScreen />;
default: {
const _exhaustive: never = screenId;
return _exhaustive;
}
}
}
@@ -17,19 +17,7 @@ import {
vectorMarkPath,
} from "../../../../../lib/assetUtils";
import { methodSectionsPinsForHydratedSelections } from "../../../../../lib/create/publishedDocumentToCreateFlowState";
/**
* Targets for a `pendingTemplateAction` redirect. Customize resumes the
* custom-rule stage with chips already prefilled; useWithoutChanges jumps to
* the review-and-complete stage since the template body is already in state.
*/
const PENDING_TEMPLATE_REDIRECT_TARGET: Record<
"customize" | "useWithoutChanges",
string
> = {
customize: "/create/core-values",
useWithoutChanges: "/create/confirm-stakeholders",
};
import { createFlowStepPath } from "../../utils/createFlowPaths";
/** Create Community review — Figma `19706:12135` (`/create/review`; two columns from `lg:`; column caps in `createFlowLayoutTokens`). */
export function CommunityReviewScreen() {
@@ -56,8 +44,10 @@ export function CommunityReviewScreen() {
if (firedRedirectRef.current) return;
const pending = state.pendingTemplateAction;
if (!pending) return;
const target = PENDING_TEMPLATE_REDIRECT_TARGET[pending.mode];
if (!target) return;
const target =
pending.mode === "customize"
? createFlowStepPath("core-values")
: createFlowStepPath("confirm-stakeholders");
firedRedirectRef.current = true;
const pinMerge =
pending.mode === "customize"
+7 -6
View File
@@ -5,6 +5,8 @@
* including step types, state management, and context interfaces.
*/
import type { MethodFacetApiSectionId } from "../../../lib/create/customRuleFacets";
/**
* Valid step IDs for the create rule flow (URL segment after `/create/`).
* Create Community order matches Figma; `review` closes that stage per design.
@@ -36,12 +38,11 @@ export type CreateFlowTextStateField =
| "communityContext"
| "communitySaveEmail";
/** Facet-backed method card stacks (`GET /api/create-flow/methods?section=`). */
export type CreateFlowMethodCardFacetSection =
| "communication"
| "membership"
| "decisionApproaches"
| "conflictManagement";
/**
* Facet-backed method card stacks (`GET /api/create-flow/methods?section=`).
* Canonical ids: {@link METHOD_FACET_API_SECTION_IDS} in `lib/create/customRuleFacets.ts`.
*/
export type CreateFlowMethodCardFacetSection = MethodFacetApiSectionId;
/**
* Serialized chip row for `community-structure` (preset + custom labels).
+78
View File
@@ -0,0 +1,78 @@
/**
* Central `/create/...` path builders (Linear CR-92 §2).
* Prefer these over string literals so layout, redirects, hooks, and tests stay aligned.
*/
import type { CreateFlowStep } from "../types";
import { CREATE_FLOW_REVIEW_RETURN_QUERY_KEY } from "./flowSteps";
export const CREATE_ROUTES = {
root: "/",
createRoot: "/create",
/** First step resolves via redirect from `/create`. */
createFirstStep: "/create",
review: "/create/review",
finalReview: "/create/final-review",
completed: "/create/completed",
editRule: "/create/edit-rule",
} as const;
/**
* Post-login return and session-gate paths on wizard steps.
* (Also used when `pathname` is unknown but `syncDraft` must be appended.)
*/
export const CREATE_FLOW_SYNC_DRAFT_QUERY = "syncDraft" as const;
export const CREATE_FLOW_SYNC_DRAFT_VALUE = "1" as const;
export function createFlowStepPathWithSyncDraft(step: CreateFlowStep): string {
return createFlowStepPath(step, {
[CREATE_FLOW_SYNC_DRAFT_QUERY]: CREATE_FLOW_SYNC_DRAFT_VALUE,
});
}
export type CreateFlowPathQuery = Record<
string,
string | number | boolean | undefined
>;
/**
* Path for a wizard step: `/create/{screenId}` with optional query string.
*/
export function createFlowStepPath(
step: CreateFlowStep,
query?: CreateFlowPathQuery,
): string {
const base = `/create/${step}`;
if (query == null || Object.keys(query).length === 0) return base;
const sp = new URLSearchParams();
for (const [k, v] of Object.entries(query)) {
if (v === undefined) continue;
sp.set(k, String(v));
}
const q = sp.toString();
return q.length > 0 ? `${base}?${q}` : base;
}
export function createCompletedPath(query?: CreateFlowPathQuery): string {
return createFlowStepPath("completed", query);
}
/**
* Navigate back from a facet step to final-review / edit-rule, dropping
* `reviewReturn` from the current query while preserving other params.
*/
export function createFlowStepPathAfterStrippingReviewReturn(
step: CreateFlowStep,
searchParams: URLSearchParams | null | undefined,
): string {
const params = new URLSearchParams(searchParams?.toString() ?? "");
params.delete(CREATE_FLOW_REVIEW_RETURN_QUERY_KEY);
const query: CreateFlowPathQuery = {};
params.forEach((value, key) => {
query[key] = value;
});
return createFlowStepPath(
step,
Object.keys(query).length > 0 ? query : undefined,
);
}
@@ -1,3 +1,4 @@
import { CUSTOM_RULE_FACETS } from "../../../../lib/create/customRuleFacets";
import type {
CreateFlowMethodCardFacetSection,
CreateFlowState,
@@ -11,9 +12,9 @@ type FooterMessageKey = keyof typeof footerMessages;
* Binding for each Custom Rule stage step whose footer primary button
* gates the user on "has at least one chip selected?". All five screens
* render the same `<Button …>`; only the disable predicate and the
* footer message differ — this table is the single source of truth for
* both, so `CreateFlowLayoutClient` can render one JSX block for the
* whole group.
* footer message differ — rows are derived from {@link CUSTOM_RULE_FACETS}
* (Linear CR-92) so `CreateFlowLayoutClient` stays aligned with template
* prefill, strip keys, and API section ids.
*
* `selectionIds` returns the currently-selected ids array from flow
* state for that step (empty array when nothing has been selected or
@@ -39,33 +40,11 @@ export type CustomRuleConfirmFooterStep = {
};
export const CUSTOM_RULE_CONFIRM_FOOTER_STEPS: readonly CustomRuleConfirmFooterStep[] =
[
{
step: "core-values",
footerMessageKey: "confirmCoreValues",
selectionIds: (s) => s.selectedCoreValueIds ?? [],
},
{
step: "communication-methods",
footerMessageKey: "confirmCommunication",
selectionIds: (s) => s.selectedCommunicationMethodIds ?? [],
},
{
step: "membership-methods",
footerMessageKey: "confirmMembership",
selectionIds: (s) => s.selectedMembershipMethodIds ?? [],
},
{
step: "decision-approaches",
footerMessageKey: "confirmDecisionApproaches",
selectionIds: (s) => s.selectedDecisionApproachIds ?? [],
},
{
step: "conflict-management",
footerMessageKey: "confirmConflictManagement",
selectionIds: (s) => s.selectedConflictManagementIds ?? [],
},
] as const;
CUSTOM_RULE_FACETS.filter((r) => r.footerMessageKey != null).map((r) => ({
step: r.createFlowStep as CustomRuleConfirmFooterStep["step"],
footerMessageKey: r.footerMessageKey as FooterMessageKey,
selectionIds: r.selectionIds,
}));
export const CUSTOM_RULE_CONFIRM_FOOTER_STEP_BY_STEP: ReadonlyMap<
CreateFlowStep,
@@ -79,16 +58,9 @@ export const CUSTOM_RULE_CONFIRM_FOOTER_STEP_BY_STEP: ReadonlyMap<
export function methodCardFacetSectionForConfirmStep(
step: CustomRuleConfirmFooterStep["step"],
): CreateFlowMethodCardFacetSection | undefined {
switch (step) {
case "communication-methods":
return "communication";
case "membership-methods":
return "membership";
case "decision-approaches":
return "decisionApproaches";
case "conflict-management":
return "conflictManagement";
default:
return undefined;
const row = CUSTOM_RULE_FACETS.find((r) => r.createFlowStep === step);
if (row == null || row.kind !== "method" || row.apiMethodSectionId == null) {
return undefined;
}
return row.apiMethodSectionId;
}
@@ -1,19 +1,13 @@
import type { TemplateFacetGroupKey } from "../../../../lib/create/templateReviewMapping";
import { createFlowStepForCustomRuleFacetGroup } from "../../../../lib/create/customRuleFacets";
import type { CreateFlowStep } from "../types";
const MAP: Record<TemplateFacetGroupKey, CreateFlowStep> = {
coreValues: "core-values",
communication: "communication-methods",
membership: "membership-methods",
decisionApproaches: "decision-approaches",
conflictManagement: "conflict-management",
};
/**
* Custom-rule URL segment for a final-review category row (`+` navigation).
* Source: {@link CUSTOM_RULE_FACETS} (CR-92).
*/
export function createFlowStepForFacetGroup(
groupKey: TemplateFacetGroupKey,
): CreateFlowStep {
return MAP[groupKey];
return createFlowStepForCustomRuleFacetGroup(groupKey);
}
+20
View File
@@ -117,6 +117,26 @@ export function getStepIndex(step: CreateFlowStep | null | undefined): number {
return FLOW_STEP_ORDER.indexOf(step);
}
/**
* Steps where below `lg` the main column scrolls with split layout
* (`CreateFlowLayoutClient` — Linear CR-92 §4).
*/
export const CREATE_FLOW_SELECT_SPLIT_SCROLL_STEPS: readonly CreateFlowStep[] = [
"community-size",
"community-structure",
"core-values",
"decision-approaches",
] as const;
export function createFlowStepUsesSelectSplitScroll(
step: CreateFlowStep | null | undefined,
): boolean {
if (!step) return false;
return (CREATE_FLOW_SELECT_SPLIT_SCROLL_STEPS as readonly string[]).includes(
step,
);
}
/**
* Whether the given string is a valid create flow step
*/
+1 -1
View File
@@ -62,7 +62,7 @@ export const ShareView = memo(function ShareView({
onSlackShare,
onDiscordShare,
className = "",
backdropVariant = "default",
backdropVariant = "blurredYellow",
dialogRef,
overlayRef,
titleId,