Refine use cases rule examples

This commit is contained in:
adilallo
2026-05-19 22:16:08 -06:00
parent 7c46cbd87b
commit 2f2b5d0dc2
65 changed files with 3129 additions and 252 deletions
@@ -0,0 +1,109 @@
"use client";
/**
* Edit published rule: community name with the same 48-char limit as
* {@link CreateFlowTextFieldScreen} `community-name` step.
*/
import { useEffect, useMemo, useRef, useState } from "react";
import Create from "../../../components/modals/Create";
import TextInput from "../../../components/controls/TextInput";
import ContentLockup from "../../../components/type/ContentLockup";
import { useTranslation } from "../../../contexts/MessagesContext";
/** Matches `community-name` step (`CreateFlowTextFieldScreen` `maxLength={48}`). */
export const COMMUNITY_TITLE_FIELD_MAX_LENGTH = 48;
export interface FinalReviewTitleEditModalProps {
isOpen: boolean;
onClose: () => void;
initialValue: string;
onSave: (_value: string) => void;
}
export function FinalReviewTitleEditModal({
isOpen,
onClose,
initialValue,
onSave,
}: FinalReviewTitleEditModalProps) {
const tModal = useTranslation(
"create.reviewAndComplete.finalReview.titleEditModal",
);
const tField = useTranslation("create.community.communityName");
const tSave = useTranslation(
"create.reviewAndComplete.finalReview.chipEditModal",
);
const [draft, setDraft] = useState("");
const initialRef = useRef("");
const seededOpenRef = useRef(false);
useEffect(() => {
if (!isOpen) {
seededOpenRef.current = false;
return;
}
if (seededOpenRef.current) return;
seededOpenRef.current = true;
const seed = initialValue;
setDraft(seed);
initialRef.current = seed;
}, [isOpen, initialValue]);
const isDirty = useMemo(() => draft !== initialRef.current, [draft]);
const trimmedDraft = draft.trim();
const canSave = isDirty && trimmedDraft.length > 0;
const characterHint = tField("characterCountTemplate")
.replace("{current}", String(draft.length))
.replace("{max}", String(COMMUNITY_TITLE_FIELD_MAX_LENGTH));
const handleSave = () => {
if (!canSave) return;
const capped = trimmedDraft.slice(0, COMMUNITY_TITLE_FIELD_MAX_LENGTH);
onSave(capped);
onClose();
};
return (
<Create
isOpen={isOpen}
onClose={onClose}
backdropVariant="blurredYellow"
headerContent={
<div className="bg-[var(--color-surface-default-primary)] px-[24px] py-[12px] shrink-0">
<ContentLockup
title={tModal("title")}
description={tModal("description")}
variant="modal"
alignment="left"
/>
</div>
}
showBackButton={false}
showNextButton
nextButtonText={tSave("saveButton")}
nextButtonDisabled={!canSave}
onNext={handleSave}
ariaLabel={tModal("title")}
>
<div className="pb-2">
<TextInput
className="!transition-none"
type="text"
placeholder={tField("placeholder")}
value={draft}
onChange={(e) => {
setDraft(e.target.value);
}}
inputSize="medium"
formHeader={false}
textHint={characterHint}
maxLength={COMMUNITY_TITLE_FIELD_MAX_LENGTH}
/>
</div>
</Create>
);
}
@@ -0,0 +1,20 @@
"use client";
import { useEffect, useState } from "react";
import { useMediaQuery } from "../../../hooks/useMediaQuery";
/** `--breakpoint-sm2` (440px); pairs with Tailwind `sm2:` on create-flow chrome. */
const CREATE_FLOW_MIN_WIDTH_SM2 = "(min-width: 440px)";
/** True at viewport ≥440px. */
export function useCreateFlowSm2Up(): boolean {
const [isMounted, setIsMounted] = useState(false);
const isSm2OrLarger = useMediaQuery(CREATE_FLOW_MIN_WIDTH_SM2);
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect -- defer until mount for SSR/first-paint alignment
setIsMounted(true);
}, []);
return !isMounted || isSm2OrLarger;
}
@@ -163,10 +163,10 @@ export function CompletedScreen() {
<>
<div className="flex min-h-0 w-full flex-1 flex-col overflow-hidden bg-[var(--color-teal-teal50,#c9fef9)] md:h-full">
<div
className={`mx-auto grid min-h-0 w-full grid-cols-1 gap-4 px-5 max-md:max-w-[639px] max-md:pt-[var(--space-800)] max-md:pb-8 md:h-full md:grid-cols-2 md:justify-items-center md:gap-[var(--measures-spacing-1200,48px)] md:overflow-hidden md:px-12 md:py-0 ${CREATE_FLOW_TWO_COLUMN_MAX_WIDTH_CLASS}`}
className={`mx-auto grid min-h-0 w-full grid-cols-1 gap-4 px-5 max-md:max-w-[639px] max-md:overflow-y-auto max-md:overscroll-y-contain max-md:pt-[var(--space-800)] max-md:pb-8 md:h-full md:grid-cols-2 md:grid-rows-1 md:items-stretch md:justify-items-center md:gap-[var(--measures-spacing-1200,48px)] md:overflow-hidden md:px-12 md:py-0 ${CREATE_FLOW_TWO_COLUMN_MAX_WIDTH_CLASS}`}
>
<div
className={`flex flex-col justify-start overflow-hidden md:justify-center md:pb-8 ${CREATE_FLOW_MD_UP_GRID_CELL_CLASS}`}
className={`flex flex-col justify-start max-md:min-h-min max-md:overflow-visible min-h-0 overflow-hidden md:justify-center md:pb-8 ${CREATE_FLOW_MD_UP_GRID_CELL_CLASS}`}
>
<CreateFlowHeaderLockup
title={headerTitle}
@@ -177,7 +177,7 @@ export function CompletedScreen() {
/>
</div>
<div
className={`scrollbar-hide relative flex min-h-0 flex-col overflow-x-hidden md:overflow-y-auto ${CREATE_FLOW_MD_UP_GRID_CELL_CLASS}`}
className={`scrollbar-hide relative flex min-h-0 flex-col self-stretch overflow-x-hidden md:max-h-full md:overflow-y-auto ${CREATE_FLOW_MD_UP_GRID_CELL_CLASS}`}
>
<div
className="pointer-events-none sticky top-0 z-10 hidden h-5 shrink-0 bg-gradient-to-b from-[var(--color-teal-teal50,#c9fef9)]/55 from-0% via-[var(--color-teal-teal50,#c9fef9)]/20 via-50% to-transparent md:block"
@@ -25,6 +25,7 @@ import {
type FinalReviewChipEditTarget,
} from "../../components/FinalReviewChipEditModal";
import { FinalReviewCommunityContextEditModal } from "../../components/FinalReviewCommunityContextEditModal";
import { FinalReviewTitleEditModal } from "../../components/FinalReviewTitleEditModal";
import { useCreateFlowNavigation } from "../../hooks/useCreateFlowNavigation";
import { createFlowStepForFacetGroup } from "../../utils/facetGroupToCreateFlowStep";
import {
@@ -114,6 +115,7 @@ export function FinalReviewScreen({
useState<TemplateChipDetail | null>(null);
const [communityContextModalOpen, setCommunityContextModalOpen] =
useState(false);
const [titleModalOpen, setTitleModalOpen] = useState(false);
const handleSave = useCallback(
(patch: FinalReviewChipEditPatch) => {
@@ -225,6 +227,9 @@ export function FinalReviewScreen({
const rawCommunityContextForModal =
typeof state.communityContext === "string" ? state.communityContext : "";
const rawTitleForModal =
typeof state.title === "string" ? state.title : "";
const descriptionEmptyHint =
variant === "editPublished" ? t("communityContextEditModal.emptyHint") : undefined;
@@ -242,6 +247,16 @@ export function FinalReviewScreen({
<Rule
title={ruleCardTitle}
description={ruleCardDescription}
onTitleClick={
variant === "editPublished"
? () => setTitleModalOpen(true)
: undefined
}
titleEditAriaLabel={
variant === "editPublished"
? t("titleEditModal.ariaEditTitle")
: undefined
}
onDescriptionClick={
variant === "editPublished"
? () => setCommunityContextModalOpen(true)
@@ -278,15 +293,26 @@ export function FinalReviewScreen({
detail={activeReadOnlyDetail}
/>
{variant === "editPublished" ? (
<FinalReviewCommunityContextEditModal
isOpen={communityContextModalOpen}
onClose={() => setCommunityContextModalOpen(false)}
initialValue={rawCommunityContextForModal}
onSave={(value) => {
markCreateFlowInteraction();
updateState({ communityContext: value, summary: value });
}}
/>
<>
<FinalReviewTitleEditModal
isOpen={titleModalOpen}
onClose={() => setTitleModalOpen(false)}
initialValue={rawTitleForModal}
onSave={(value) => {
markCreateFlowInteraction();
updateState({ title: value });
}}
/>
<FinalReviewCommunityContextEditModal
isOpen={communityContextModalOpen}
onClose={() => setCommunityContextModalOpen(false)}
initialValue={rawCommunityContextForModal}
onSave={(value) => {
markCreateFlowInteraction();
updateState({ communityContext: value, summary: value });
}}
/>
</>
) : null}
</CreateFlowLockupCardStepShell>
);