Let decision-approaches wrap at narrow widths and sync key-resource scopes.
The info-box checkboxes overflowed in a squeezed column; wrapping them and offering those scopes as chips on every approach keeps the sidebar and Applicable Scope selection in sync without publishing unchecked keys as defaults. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -84,6 +84,7 @@ function ApplicableScopeFieldComponent({
|
||||
disabled={readOnly}
|
||||
onClick={() => !readOnly && onToggleScope(scope)}
|
||||
ariaLabel={`${isSelected ? "Deselect" : "Select"} ${scope}`}
|
||||
className="max-w-full"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -35,6 +35,10 @@ import {
|
||||
decisionApproachPresetFor,
|
||||
membershipPresetFor,
|
||||
} from "../../../../lib/create/finalReviewChipPresets";
|
||||
import {
|
||||
applyDecisionApproachKeyResources,
|
||||
selectedKeyResourceLabelsFromCheckedIds,
|
||||
} from "../../../../lib/create/decisionApproachKeyResources";
|
||||
import { isCustomMethodCardId } from "../../../../lib/create/isCustomMethodCardId";
|
||||
import { usesWizardFieldBlocksModalBody } from "../../../../lib/create/usesWizardFieldBlocksModalBody";
|
||||
import type { CustomMethodCardFieldBlock } from "../../../../lib/create/customMethodCardFieldBlocks";
|
||||
@@ -1266,6 +1270,7 @@ function facetSeedSignature(
|
||||
return JSON.stringify({
|
||||
meta: state.customMethodCardMetaById?.[id] ?? null,
|
||||
details: state.decisionApproachDetailsById?.[id] ?? null,
|
||||
keyResources: state.selectedDecisionKeyResourceIds ?? null,
|
||||
blocks: state.customMethodCardFieldBlocksById?.[id] ?? null,
|
||||
});
|
||||
case "conflictManagement":
|
||||
@@ -1330,12 +1335,18 @@ function seedDraftForTarget(
|
||||
const saved =
|
||||
state.decisionApproachDetailsById?.[target.overrideKey] ??
|
||||
decisionApproachPresetFor(target.overrideKey);
|
||||
const withKeys = applyDecisionApproachKeyResources(
|
||||
saved,
|
||||
selectedKeyResourceLabelsFromCheckedIds(
|
||||
state.selectedDecisionKeyResourceIds ?? [],
|
||||
),
|
||||
);
|
||||
return {
|
||||
groupKey: "decisionApproaches",
|
||||
value: {
|
||||
...saved,
|
||||
applicableScope: [...saved.applicableScope],
|
||||
selectedApplicableScope: [...saved.selectedApplicableScope],
|
||||
...withKeys,
|
||||
applicableScope: [...withKeys.applicableScope],
|
||||
selectedApplicableScope: [...withKeys.selectedApplicableScope],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
* `markCreateFlowInteraction` live in the parent.
|
||||
*/
|
||||
|
||||
import { memo, useCallback } from "react";
|
||||
import { memo, useCallback, useMemo } from "react";
|
||||
import { useMessages } from "../../../../contexts/MessagesContext";
|
||||
import ModalTextAreaField from "../ModalTextAreaField";
|
||||
import ApplicableScopeField from "../ApplicableScopeField";
|
||||
import IncrementerBlock from "../../../../components/controls/IncrementerBlock";
|
||||
import type { DecisionApproachDetailEntry } from "../../types";
|
||||
import { withDecisionApproachKeyResourceScopes } from "../../../../../lib/create/decisionApproachKeyResources";
|
||||
|
||||
export interface DecisionApproachEditFieldsProps {
|
||||
value: DecisionApproachDetailEntry;
|
||||
@@ -32,6 +33,11 @@ function DecisionApproachEditFieldsComponent({
|
||||
const m = useMessages();
|
||||
const t = m.create.customRule.decisionApproaches;
|
||||
|
||||
const scopes = useMemo(
|
||||
() => withDecisionApproachKeyResourceScopes(value.applicableScope),
|
||||
[value.applicableScope],
|
||||
);
|
||||
|
||||
const patch = useCallback(
|
||||
<K extends keyof DecisionApproachDetailEntry>(
|
||||
key: K,
|
||||
@@ -53,7 +59,7 @@ function DecisionApproachEditFieldsComponent({
|
||||
<ApplicableScopeField
|
||||
label={t.sectionHeadings.applicableScope}
|
||||
addLabel={t.scopeAddButtonLabel}
|
||||
scopes={value.applicableScope}
|
||||
scopes={scopes}
|
||||
selectedScopes={value.selectedApplicableScope}
|
||||
readOnly={readOnly}
|
||||
onToggleScope={(scope) =>
|
||||
|
||||
@@ -33,6 +33,13 @@ import { DecisionApproachEditFields } from "../../components/methodEditFields";
|
||||
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
||||
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
||||
import { decisionApproachPresetFor } from "../../../../../lib/create/finalReviewChipPresets";
|
||||
import {
|
||||
applyDecisionApproachKeyResources,
|
||||
decisionApproachKeyResourceIdsFromLabels,
|
||||
selectedKeyResourceLabelsFromCheckedIds,
|
||||
stringArraysEqual,
|
||||
syncDecisionApproachKeyResourceDetails,
|
||||
} from "../../../../../lib/create/decisionApproachKeyResources";
|
||||
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
||||
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
|
||||
import { moveFacetSelectionIdToFront } from "../../../../../lib/create/methodCardSelectionOrder";
|
||||
@@ -71,9 +78,6 @@ export function DecisionApproachesScreen() {
|
||||
const customizeSnapshotRef = useRef<
|
||||
MethodCardCustomizeSnapshot<DecisionApproachDetailEntry> | null
|
||||
>(null);
|
||||
const [messageBoxCheckedIds, setMessageBoxCheckedIds] = useState<string[]>(
|
||||
[],
|
||||
);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||
const [pendingCardId, setPendingCardId] = useState<string | null>(null);
|
||||
@@ -90,6 +94,11 @@ export function DecisionApproachesScreen() {
|
||||
>(null);
|
||||
|
||||
const selectedIds = state.selectedDecisionApproachIds ?? [];
|
||||
const messageBoxCheckedIds = state.selectedDecisionKeyResourceIds ?? [];
|
||||
const selectedKeyResourceLabels = useMemo(
|
||||
() => selectedKeyResourceLabelsFromCheckedIds(messageBoxCheckedIds),
|
||||
[messageBoxCheckedIds],
|
||||
);
|
||||
|
||||
const messageBoxItems: InfoMessageBoxItem[] = useMemo(
|
||||
() =>
|
||||
@@ -137,26 +146,52 @@ export function DecisionApproachesScreen() {
|
||||
const handleMessageBoxCheckboxChange = useCallback(
|
||||
(id: string, checked: boolean) => {
|
||||
markCreateFlowInteraction();
|
||||
setMessageBoxCheckedIds((prev) =>
|
||||
checked ? [...prev, id] : prev.filter((x) => x !== id),
|
||||
);
|
||||
const nextCheckedIds = checked
|
||||
? [...messageBoxCheckedIds, id]
|
||||
: messageBoxCheckedIds.filter((x) => x !== id);
|
||||
const nextLabels =
|
||||
selectedKeyResourceLabelsFromCheckedIds(nextCheckedIds);
|
||||
if (pendingDraft) {
|
||||
setPendingDraft(
|
||||
applyDecisionApproachKeyResources(pendingDraft, nextLabels),
|
||||
);
|
||||
}
|
||||
updateState({
|
||||
selectedDecisionKeyResourceIds: nextCheckedIds,
|
||||
decisionApproachDetailsById: syncDecisionApproachKeyResourceDetails(
|
||||
state.decisionApproachDetailsById,
|
||||
selectedIds,
|
||||
nextLabels,
|
||||
decisionApproachPresetFor,
|
||||
),
|
||||
});
|
||||
},
|
||||
[markCreateFlowInteraction],
|
||||
[
|
||||
markCreateFlowInteraction,
|
||||
messageBoxCheckedIds,
|
||||
pendingDraft,
|
||||
selectedIds,
|
||||
state.decisionApproachDetailsById,
|
||||
updateState,
|
||||
],
|
||||
);
|
||||
|
||||
const seedDraft = useCallback(
|
||||
(id: string): DecisionApproachDetailEntry => {
|
||||
const saved = state.decisionApproachDetailsById?.[id];
|
||||
if (saved) {
|
||||
return {
|
||||
...saved,
|
||||
applicableScope: [...saved.applicableScope],
|
||||
selectedApplicableScope: [...saved.selectedApplicableScope],
|
||||
};
|
||||
}
|
||||
return decisionApproachPresetFor(id);
|
||||
const base = saved
|
||||
? {
|
||||
...saved,
|
||||
applicableScope: [...saved.applicableScope],
|
||||
selectedApplicableScope: [...saved.selectedApplicableScope],
|
||||
}
|
||||
: decisionApproachPresetFor(id);
|
||||
return applyDecisionApproachKeyResources(
|
||||
base,
|
||||
selectedKeyResourceLabels,
|
||||
);
|
||||
},
|
||||
[state.decisionApproachDetailsById],
|
||||
[selectedKeyResourceLabels, state.decisionApproachDetailsById],
|
||||
);
|
||||
|
||||
const handleCardSelect = useCallback(
|
||||
@@ -202,8 +237,31 @@ export function DecisionApproachesScreen() {
|
||||
(next: DecisionApproachDetailEntry) => {
|
||||
markCreateFlowInteraction();
|
||||
setPendingDraft(next);
|
||||
const nextCheckedIds = decisionApproachKeyResourceIdsFromLabels(
|
||||
next.selectedApplicableScope,
|
||||
);
|
||||
if (stringArraysEqual(nextCheckedIds, messageBoxCheckedIds)) {
|
||||
return;
|
||||
}
|
||||
const nextLabels =
|
||||
selectedKeyResourceLabelsFromCheckedIds(nextCheckedIds);
|
||||
updateState({
|
||||
selectedDecisionKeyResourceIds: nextCheckedIds,
|
||||
decisionApproachDetailsById: syncDecisionApproachKeyResourceDetails(
|
||||
state.decisionApproachDetailsById,
|
||||
selectedIds,
|
||||
nextLabels,
|
||||
decisionApproachPresetFor,
|
||||
),
|
||||
});
|
||||
},
|
||||
[markCreateFlowInteraction],
|
||||
[
|
||||
markCreateFlowInteraction,
|
||||
messageBoxCheckedIds,
|
||||
selectedIds,
|
||||
state.decisionApproachDetailsById,
|
||||
updateState,
|
||||
],
|
||||
);
|
||||
|
||||
const isSelectedCardModal =
|
||||
@@ -594,7 +652,10 @@ export function DecisionApproachesScreen() {
|
||||
},
|
||||
decisionApproachDetailsById: {
|
||||
...(state.decisionApproachDetailsById ?? {}),
|
||||
[id]: decisionApproachPresetFor(id),
|
||||
[id]: applyDecisionApproachKeyResources(
|
||||
decisionApproachPresetFor(id),
|
||||
selectedKeyResourceLabels,
|
||||
),
|
||||
},
|
||||
customMethodCardFieldBlocksById: {
|
||||
...(state.customMethodCardFieldBlocksById ?? {}),
|
||||
@@ -606,6 +667,7 @@ export function DecisionApproachesScreen() {
|
||||
markCreateFlowInteraction,
|
||||
pendingDraft,
|
||||
selectedIds,
|
||||
selectedKeyResourceLabels,
|
||||
state.customMethodCardFieldBlocksById,
|
||||
state.customMethodCardMetaById,
|
||||
state.decisionApproachDetailsById,
|
||||
|
||||
@@ -165,6 +165,12 @@ export interface CreateFlowState {
|
||||
>;
|
||||
membershipMethodDetailsById?: Record<string, MembershipMethodDetailEntry>;
|
||||
decisionApproachDetailsById?: Record<string, DecisionApproachDetailEntry>;
|
||||
/**
|
||||
* Checked “key resource” ids from the decision-approaches InfoMessageBox
|
||||
* (`amend`, `finances`, `project`, `discipline`). Selecting one also selects
|
||||
* the matching Applicable Scope chip on every decision approach.
|
||||
*/
|
||||
selectedDecisionKeyResourceIds?: string[];
|
||||
conflictManagementDetailsById?: Record<
|
||||
string,
|
||||
ConflictManagementDetailEntry
|
||||
|
||||
@@ -84,14 +84,14 @@ export function SelectionView({
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-medium-label text-[var(--color-content-invert-secondary)]">
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
<span className="min-w-0 break-words text-medium-label text-[var(--color-content-invert-secondary)]">
|
||||
{label}
|
||||
</span>
|
||||
{showInfoIcon ? <InfoIcon /> : null}
|
||||
</div>
|
||||
{supportText ? (
|
||||
<p className="text-x-small-paragraph text-[var(--color-content-invert-tertiary)]">
|
||||
<p className="min-w-0 break-words text-x-small-paragraph text-[var(--color-content-invert-tertiary)]">
|
||||
{supportText}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function CheckboxView({
|
||||
}: CheckboxViewProps) {
|
||||
return (
|
||||
<label
|
||||
className={`inline-flex items-center gap-[8px] cursor-pointer select-none ${
|
||||
className={`inline-flex max-w-full min-w-0 items-center gap-[8px] cursor-pointer select-none ${
|
||||
disabled ? "opacity-60 cursor-not-allowed" : ""
|
||||
} ${className}`}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
@@ -51,7 +51,7 @@ export function CheckboxView({
|
||||
{label && (
|
||||
<span
|
||||
id={labelId}
|
||||
className="text-[14px] leading-[18px]"
|
||||
className="min-w-0 flex-1 text-[14px] leading-[18px] whitespace-normal break-words"
|
||||
style={{ color: labelColor }}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -99,6 +99,7 @@ function ChipView({
|
||||
|
||||
const baseClasses = `
|
||||
inline-flex
|
||||
max-w-full
|
||||
items-center
|
||||
justify-center
|
||||
rounded-[var(--measures-radius-full,9999px)]
|
||||
@@ -264,7 +265,7 @@ function ChipView({
|
||||
onClick={handleClick}
|
||||
{...sharedA11y}
|
||||
>
|
||||
<span className="flex items-center justify-center">{label}</span>
|
||||
<span className="min-w-0 truncate">{label}</span>
|
||||
{onRemove && !isDisabled && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -45,30 +45,30 @@ function InfoMessageBoxView({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col gap-[12px] p-[var(--spacing-measures-spacing-500,20px)] rounded-[var(--measures-radius-300,12px)] border-l-2 border-solid border-[var(--color-border-default-secondary,#1f1f1f)] bg-[var(--color-content-inverse-secondary,#1f1f1f)] w-full min-w-0 ${className}`}
|
||||
className={`flex w-full min-w-0 items-start gap-[var(--measures-spacing-200,8px)] p-[var(--spacing-measures-spacing-500,20px)] rounded-[var(--measures-radius-300,12px)] border-l-2 border-solid border-[var(--color-border-default-secondary,#1f1f1f)] bg-[var(--color-content-inverse-secondary,#1f1f1f)] ${className}`}
|
||||
role="region"
|
||||
aria-label={title}
|
||||
>
|
||||
<div className="flex items-center gap-[var(--measures-spacing-200,8px)] min-w-0">
|
||||
<div
|
||||
className="relative shrink-0 size-6 flex items-center justify-center"
|
||||
data-name="Asset / Icon / exclamation"
|
||||
>
|
||||
{icon ?? <ExclamationIconInline />}
|
||||
</div>
|
||||
<p className="text-small-label text-[var(--color-content-default-primary,white)] min-w-0">
|
||||
<div
|
||||
className="relative shrink-0 size-6 flex items-center justify-center"
|
||||
data-name="Asset / Icon / exclamation"
|
||||
>
|
||||
{icon ?? <ExclamationIconInline />}
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-[12px]">
|
||||
<p className="text-small-label text-[var(--color-content-default-primary,white)] min-w-0 break-words">
|
||||
{title}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-[12px] [&_label]:gap-[6px] [&_label_span]:text-x-small-paragraph [&_label_span]:opacity-80 pl-8">
|
||||
<CheckboxGroup
|
||||
mode="standard"
|
||||
value={checkedIds}
|
||||
onChange={handleChange}
|
||||
options={options}
|
||||
aria-label={title}
|
||||
className="flex flex-col gap-[12px] !space-y-0"
|
||||
/>
|
||||
<div className="flex min-w-0 w-full flex-col gap-[12px] [&_label]:w-full [&_label]:min-w-0 [&_label]:gap-[6px] [&_label_span]:text-x-small-paragraph [&_label_span]:opacity-80">
|
||||
<CheckboxGroup
|
||||
mode="standard"
|
||||
value={checkedIds}
|
||||
onChange={handleChange}
|
||||
options={options}
|
||||
aria-label={title}
|
||||
className="flex w-full min-w-0 flex-col gap-[12px] !space-y-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user