Open each core value as a card of meaning and signals steps, so groups write their own words from an empty field with Generate draft, skip values without blocking the flow, and Customize without keeping helper text as the description.
This commit is contained in:
1 parent
2a7b9e6da7
commit
64a542f456
25 files changed
+877
-354
No files matched your search
@@ -0,0 +1,181 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Figma: Overlay / Modal / Card (24973:1852) with Has Text off and Has Steps
|
||||
* on. A value opens a card of meaning + signals rows; each row opens that
|
||||
* field's Step Modal (23746:49780) with Generate draft on an empty field.
|
||||
*/
|
||||
|
||||
import { memo, useCallback, useEffect, useState, type ReactNode } from "react";
|
||||
import Create from "../../../components/modals/Create";
|
||||
import { useMessages } from "../../../contexts/MessagesContext";
|
||||
import type { ModalHeaderMenuItem } from "../../../components/modals/ModalHeader/ModalHeader.types";
|
||||
import type { CoreValueDetailEntry } from "../types";
|
||||
import ModalTextAreaField from "./ModalTextAreaField";
|
||||
import { ValueDialogStepCard } from "./ValueDialogStepCard";
|
||||
|
||||
export type ValueDialogSession = "pending" | "editing";
|
||||
export type ValueDialogStep = "meaning" | "signals";
|
||||
|
||||
export type ValueDialogProps = {
|
||||
isOpen: boolean;
|
||||
valueLabel: string;
|
||||
session: ValueDialogSession | null;
|
||||
draft: CoreValueDetailEntry;
|
||||
onDraftChange: (_next: CoreValueDetailEntry) => void;
|
||||
generateDraft?: CoreValueDetailEntry;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
onDeselect?: () => void;
|
||||
nextButtonDisabled?: boolean;
|
||||
kebabTriggerAriaLabel?: string;
|
||||
kebabMenuAriaLabel?: string;
|
||||
kebabMenuItems?: ModalHeaderMenuItem[];
|
||||
ariaLabel?: string;
|
||||
/** Customized field-blocks body. Replaces the meaning/signals step cards. */
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
function ValueDialogComponent({
|
||||
isOpen,
|
||||
valueLabel,
|
||||
session,
|
||||
draft,
|
||||
onDraftChange,
|
||||
generateDraft,
|
||||
onClose,
|
||||
onConfirm,
|
||||
onDeselect,
|
||||
nextButtonDisabled = false,
|
||||
kebabTriggerAriaLabel,
|
||||
kebabMenuAriaLabel,
|
||||
kebabMenuItems,
|
||||
ariaLabel,
|
||||
children,
|
||||
}: ValueDialogProps) {
|
||||
const m = useMessages();
|
||||
const t = m.create.customRule.coreValues.detailModal;
|
||||
const draftField = m.create.draftField;
|
||||
const [activeStep, setActiveStep] = useState<ValueDialogStep | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setActiveStep(null);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleRequestClose = useCallback(() => {
|
||||
if (activeStep != null) {
|
||||
setActiveStep(null);
|
||||
return;
|
||||
}
|
||||
onClose();
|
||||
}, [activeStep, onClose]);
|
||||
|
||||
const meaningDone = draft.meaning.trim().length > 0;
|
||||
const signalsDone = draft.signals.trim().length > 0;
|
||||
const useCustomBody = Boolean(children);
|
||||
const showSteps = !useCustomBody && activeStep == null;
|
||||
const editing = session === "editing";
|
||||
const primaryLabel = editing ? t.save : t.addToRule;
|
||||
|
||||
const stepTitle =
|
||||
activeStep === "meaning"
|
||||
? t.meaningStepTitle.replace("{value}", valueLabel)
|
||||
: activeStep === "signals"
|
||||
? t.signalsLabel
|
||||
: valueLabel;
|
||||
const stepDescription =
|
||||
activeStep === "meaning"
|
||||
? t.meaningDescription
|
||||
: activeStep === "signals"
|
||||
? t.signalsStepDescription.replace("{value}", valueLabel)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Create
|
||||
isOpen={isOpen}
|
||||
onClose={handleRequestClose}
|
||||
title={stepTitle}
|
||||
description={activeStep != null ? stepDescription : undefined}
|
||||
showBackButton={false}
|
||||
showCancelButton
|
||||
showNextButton
|
||||
showRemoveButton={editing && activeStep == null}
|
||||
onCancel={handleRequestClose}
|
||||
onNext={onConfirm}
|
||||
onRemove={onDeselect}
|
||||
cancelButtonText={t.cancel}
|
||||
nextButtonText={primaryLabel}
|
||||
removeButtonText={t.deselect}
|
||||
nextButtonDisabled={nextButtonDisabled}
|
||||
stepper={false}
|
||||
backdropVariant="blurredYellow"
|
||||
kebabTriggerAriaLabel={kebabTriggerAriaLabel}
|
||||
kebabMenuAriaLabel={kebabMenuAriaLabel}
|
||||
kebabMenuItems={
|
||||
activeStep == null && kebabMenuItems && kebabMenuItems.length > 0
|
||||
? kebabMenuItems
|
||||
: undefined
|
||||
}
|
||||
ariaLabel={ariaLabel || valueLabel}
|
||||
>
|
||||
{useCustomBody ? (
|
||||
children
|
||||
) : showSteps ? (
|
||||
<div className="flex w-full flex-col gap-[8px]">
|
||||
<ValueDialogStepCard
|
||||
stepNumber={1}
|
||||
stepBadgeLabel={t.stepBadge}
|
||||
title={t.meaningLabel}
|
||||
description={
|
||||
meaningDone ? draft.meaning : t.meaningDescription
|
||||
}
|
||||
done={meaningDone}
|
||||
onClick={() => setActiveStep("meaning")}
|
||||
/>
|
||||
<ValueDialogStepCard
|
||||
stepNumber={2}
|
||||
stepBadgeLabel={t.stepBadge}
|
||||
title={t.signalsLabel}
|
||||
description={
|
||||
signalsDone ? draft.signals : t.signalsDescription
|
||||
}
|
||||
done={signalsDone}
|
||||
onClick={() => setActiveStep("signals")}
|
||||
/>
|
||||
</div>
|
||||
) : activeStep === "meaning" ? (
|
||||
<ModalTextAreaField
|
||||
hideLabel
|
||||
label={t.meaningLabel}
|
||||
value={draft.meaning}
|
||||
onChange={(meaning) => onDraftChange({ ...draft, meaning })}
|
||||
placeholder={t.placeholder}
|
||||
generateDraftText={generateDraft?.meaning}
|
||||
generateDraftLabel={draftField.generateDraft}
|
||||
generateDraftHint={t.generateDraftHint}
|
||||
filledHint={t.filledHint}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<ModalTextAreaField
|
||||
hideLabel
|
||||
label={t.signalsLabel}
|
||||
value={draft.signals}
|
||||
onChange={(signals) => onDraftChange({ ...draft, signals })}
|
||||
placeholder={t.placeholder}
|
||||
generateDraftText={generateDraft?.signals}
|
||||
generateDraftLabel={draftField.generateDraft}
|
||||
generateDraftHint={t.generateDraftHint}
|
||||
filledHint={t.filledHint}
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
</Create>
|
||||
);
|
||||
}
|
||||
|
||||
ValueDialogComponent.displayName = "ValueDialog";
|
||||
|
||||
export const ValueDialog = memo(ValueDialogComponent);
|
||||
Reference in new issue
Block a user