Merge pull request 'Give MultiSelect chips accessible names and start creation fields empty.' (#85) from adilallo/fix/CR-220-CR-209-chip-names-creation-modal into main
Reviewed-on: #85
This commit was merged in pull request #85.
This commit is contained in:
commit
2fc7fbf818
35 files changed
+498
-156
No files matched your search
@@ -35,6 +35,10 @@ import {
|
|||||||
conflictManagementPresetFor,
|
conflictManagementPresetFor,
|
||||||
coreValuePresetFor,
|
coreValuePresetFor,
|
||||||
decisionApproachPresetFor,
|
decisionApproachPresetFor,
|
||||||
|
emptyCommunicationDetails,
|
||||||
|
emptyConflictManagementDetails,
|
||||||
|
emptyDecisionApproachTextFields,
|
||||||
|
emptyMembershipDetails,
|
||||||
membershipPresetFor,
|
membershipPresetFor,
|
||||||
} from "../../../../lib/create/finalReviewChipPresets";
|
} from "../../../../lib/create/finalReviewChipPresets";
|
||||||
import { isCustomMethodCardId } from "../../../../lib/create/isCustomMethodCardId";
|
import { isCustomMethodCardId } from "../../../../lib/create/isCustomMethodCardId";
|
||||||
@@ -1129,6 +1133,7 @@ export function FinalReviewChipEditModal({
|
|||||||
<CoreValueEditFields
|
<CoreValueEditFields
|
||||||
value={draft.value}
|
value={draft.value}
|
||||||
onChange={(value) => setDraft({ groupKey: "coreValues", value })}
|
onChange={(value) => setDraft({ groupKey: "coreValues", value })}
|
||||||
|
generateDraft={coreValuePresetFor(target.overrideKey)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{draft?.groupKey === "communication" &&
|
{draft?.groupKey === "communication" &&
|
||||||
@@ -1156,6 +1161,7 @@ export function FinalReviewChipEditModal({
|
|||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setDraft({ groupKey: "communication", value })
|
setDraft({ groupKey: "communication", value })
|
||||||
}
|
}
|
||||||
|
generateDraft={communicationPresetFor(target.overrideKey)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{draft?.groupKey === "membership" &&
|
{draft?.groupKey === "membership" &&
|
||||||
@@ -1183,6 +1189,7 @@ export function FinalReviewChipEditModal({
|
|||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setDraft({ groupKey: "membership", value })
|
setDraft({ groupKey: "membership", value })
|
||||||
}
|
}
|
||||||
|
generateDraft={membershipPresetFor(target.overrideKey)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{draft?.groupKey === "decisionApproaches" &&
|
{draft?.groupKey === "decisionApproaches" &&
|
||||||
@@ -1210,6 +1217,7 @@ export function FinalReviewChipEditModal({
|
|||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setDraft({ groupKey: "decisionApproaches", value })
|
setDraft({ groupKey: "decisionApproaches", value })
|
||||||
}
|
}
|
||||||
|
generateDraft={decisionApproachPresetFor(target.overrideKey)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{draft?.groupKey === "conflictManagement" &&
|
{draft?.groupKey === "conflictManagement" &&
|
||||||
@@ -1237,6 +1245,7 @@ export function FinalReviewChipEditModal({
|
|||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setDraft({ groupKey: "conflictManagement", value })
|
setDraft({ groupKey: "conflictManagement", value })
|
||||||
}
|
}
|
||||||
|
generateDraft={conflictManagementPresetFor(target.overrideKey)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -1324,52 +1333,63 @@ function seedDraftForTarget(
|
|||||||
switch (target.groupKey) {
|
switch (target.groupKey) {
|
||||||
case "coreValues": {
|
case "coreValues": {
|
||||||
const saved = state.coreValueDetailsByChipId?.[target.overrideKey];
|
const saved = state.coreValueDetailsByChipId?.[target.overrideKey];
|
||||||
const preset = coreValuePresetFor(target.overrideKey);
|
if (saved) {
|
||||||
|
return {
|
||||||
|
groupKey: "coreValues",
|
||||||
|
value: {
|
||||||
|
meaning: saved.meaning,
|
||||||
|
signals: saved.signals,
|
||||||
|
...(saved.supportText ? { supportText: saved.supportText } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
groupKey: "coreValues",
|
groupKey: "coreValues",
|
||||||
value: {
|
value: { meaning: "", signals: "" },
|
||||||
meaning: saved?.meaning ?? preset.meaning,
|
|
||||||
signals: saved?.signals ?? preset.signals,
|
|
||||||
...(saved?.supportText ? { supportText: saved.supportText } : {}),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "communication": {
|
case "communication": {
|
||||||
const saved =
|
const saved = state.communicationMethodDetailsById?.[target.overrideKey];
|
||||||
state.communicationMethodDetailsById?.[target.overrideKey] ??
|
return {
|
||||||
communicationPresetFor(target.overrideKey);
|
groupKey: "communication",
|
||||||
return { groupKey: "communication", value: { ...saved } };
|
value: { ...(saved ?? emptyCommunicationDetails()) },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
case "membership": {
|
case "membership": {
|
||||||
const saved =
|
const saved = state.membershipMethodDetailsById?.[target.overrideKey];
|
||||||
state.membershipMethodDetailsById?.[target.overrideKey] ??
|
return {
|
||||||
membershipPresetFor(target.overrideKey);
|
groupKey: "membership",
|
||||||
return { groupKey: "membership", value: { ...saved } };
|
value: { ...(saved ?? emptyMembershipDetails()) },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
case "decisionApproaches": {
|
case "decisionApproaches": {
|
||||||
const saved =
|
const saved = state.decisionApproachDetailsById?.[target.overrideKey];
|
||||||
state.decisionApproachDetailsById?.[target.overrideKey] ??
|
const value = saved
|
||||||
decisionApproachPresetFor(target.overrideKey);
|
? {
|
||||||
|
...saved,
|
||||||
|
applicableScope: [...saved.applicableScope],
|
||||||
|
selectedApplicableScope: [...saved.selectedApplicableScope],
|
||||||
|
}
|
||||||
|
: emptyDecisionApproachTextFields(
|
||||||
|
decisionApproachPresetFor(target.overrideKey),
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
groupKey: "decisionApproaches",
|
groupKey: "decisionApproaches",
|
||||||
value: {
|
value,
|
||||||
...saved,
|
|
||||||
applicableScope: [...saved.applicableScope],
|
|
||||||
selectedApplicableScope: [...saved.selectedApplicableScope],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "conflictManagement": {
|
case "conflictManagement": {
|
||||||
const saved =
|
const saved = state.conflictManagementDetailsById?.[target.overrideKey];
|
||||||
state.conflictManagementDetailsById?.[target.overrideKey] ??
|
const value = saved
|
||||||
conflictManagementPresetFor(target.overrideKey);
|
? {
|
||||||
|
...saved,
|
||||||
|
applicableScope: [...saved.applicableScope],
|
||||||
|
selectedApplicableScope: [...saved.selectedApplicableScope],
|
||||||
|
}
|
||||||
|
: emptyConflictManagementDetails();
|
||||||
return {
|
return {
|
||||||
groupKey: "conflictManagement",
|
groupKey: "conflictManagement",
|
||||||
value: {
|
value,
|
||||||
...saved,
|
|
||||||
applicableScope: [...saved.applicableScope],
|
|
||||||
selectedApplicableScope: [...saved.selectedApplicableScope],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared "labelled text area" field used by every create flow modal section.
|
* Shared labelled text area for create-flow modal sections.
|
||||||
* Pairs an `InputLabel` with a `TextArea` set to the embedded appearance —
|
* Figma: "Step Modal" Text Area Section (23746:49780) when `generateDraftText`
|
||||||
* matching the Figma "Control / Text Area" pattern. Section help marks stay
|
* is set — tall compose field + Generate draft control. Otherwise the
|
||||||
* off until tooltip behavior ships.
|
* embedded Control / Text Area pattern.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { memo, useId } from "react";
|
import { memo, useId } from "react";
|
||||||
@@ -26,9 +26,40 @@ export interface ModalTextAreaFieldProps {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
/** Disable the field. */
|
/** Disable the field. */
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
/**
|
||||||
|
* Catalog/preset copy inserted by Generate draft. When a non-empty string
|
||||||
|
* is passed, the Figma compose field + ghost control are shown.
|
||||||
|
*/
|
||||||
|
generateDraftText?: string;
|
||||||
|
/** Visible label for the Generate draft control. */
|
||||||
|
generateDraftLabel?: string;
|
||||||
|
/** Hint beside Generate draft. */
|
||||||
|
generateDraftHint?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PlusGlyph() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-hidden
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="shrink-0 text-[var(--color-content-default-brand-primary,#fefcc9)]"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7 3V11M3 7H11"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ModalTextAreaFieldComponent({
|
function ModalTextAreaFieldComponent({
|
||||||
label,
|
label,
|
||||||
helpIcon = false,
|
helpIcon = false,
|
||||||
@@ -37,12 +68,21 @@ function ModalTextAreaFieldComponent({
|
|||||||
rows = 4,
|
rows = 4,
|
||||||
placeholder,
|
placeholder,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
generateDraftText,
|
||||||
|
generateDraftLabel,
|
||||||
|
generateDraftHint,
|
||||||
className = "",
|
className = "",
|
||||||
}: ModalTextAreaFieldProps) {
|
}: ModalTextAreaFieldProps) {
|
||||||
const labelId = useId();
|
const labelId = useId();
|
||||||
|
const draftSource = generateDraftText?.trim() ?? "";
|
||||||
|
const showGenerateDraft =
|
||||||
|
draftSource.length > 0 && Boolean(generateDraftLabel);
|
||||||
|
const compose = showGenerateDraft;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col gap-2 ${className}`.trim()}>
|
<div
|
||||||
|
className={`flex flex-col ${compose ? "gap-[12px]" : "gap-2"} ${className}`.trim()}
|
||||||
|
>
|
||||||
<div id={labelId}>
|
<div id={labelId}>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
label={label}
|
label={label}
|
||||||
@@ -56,12 +96,29 @@ function ModalTextAreaFieldComponent({
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
size="large"
|
size="large"
|
||||||
rows={rows}
|
rows={compose ? 12 : rows}
|
||||||
appearance="embedded"
|
appearance={compose ? "compose" : "embedded"}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
aria-labelledby={labelId}
|
aria-labelledby={labelId}
|
||||||
/>
|
/>
|
||||||
|
{showGenerateDraft && !disabled ? (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onChange(draftSource)}
|
||||||
|
className="flex shrink-0 items-center justify-center gap-[var(--measures-spacing-050,2px)] overflow-clip rounded-[var(--measures-radius-full,9999px)] px-[var(--space-250,10px)] py-[var(--measures-spacing-200,8px)] text-x-small-label text-[color:var(--color-content-default-primary,white)] hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-default-primary)] focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
|
||||||
|
>
|
||||||
|
<PlusGlyph />
|
||||||
|
{generateDraftLabel}
|
||||||
|
</button>
|
||||||
|
{generateDraftHint ? (
|
||||||
|
<p className="text-[length:var(--sizing-300,12px)] leading-normal text-[color:var(--color-content-default-tertiary,#b4b4b4)]">
|
||||||
|
{generateDraftHint}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export interface CommunicationMethodEditFieldsProps {
|
|||||||
onChange: (_next: CommunicationMethodDetailEntry) => void;
|
onChange: (_next: CommunicationMethodDetailEntry) => void;
|
||||||
/** When true, fields are not editable (view mode). */
|
/** When true, fields are not editable (view mode). */
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
/** Catalog copy inserted by Generate draft. */
|
||||||
|
generateDraft?: CommunicationMethodDetailEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIELDS: ReadonlyArray<keyof CommunicationMethodDetailEntry> = [
|
const FIELDS: ReadonlyArray<keyof CommunicationMethodDetailEntry> = [
|
||||||
@@ -29,9 +31,11 @@ function CommunicationMethodEditFieldsComponent({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
generateDraft,
|
||||||
}: CommunicationMethodEditFieldsProps) {
|
}: CommunicationMethodEditFieldsProps) {
|
||||||
const m = useMessages();
|
const m = useMessages();
|
||||||
const t = m.create.customRule.communication;
|
const t = m.create.customRule.communication;
|
||||||
|
const draftField = m.create.draftField;
|
||||||
|
|
||||||
const patch = useCallback(
|
const patch = useCallback(
|
||||||
<K extends keyof CommunicationMethodDetailEntry>(
|
<K extends keyof CommunicationMethodDetailEntry>(
|
||||||
@@ -53,6 +57,10 @@ function CommunicationMethodEditFieldsComponent({
|
|||||||
value={value[field]}
|
value={value[field]}
|
||||||
onChange={(v) => patch(field, v)}
|
onChange={(v) => patch(field, v)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.[field]}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,15 +38,19 @@ export interface ConflictManagementEditFieldsProps {
|
|||||||
value: ConflictManagementDetailEntry;
|
value: ConflictManagementDetailEntry;
|
||||||
onChange: (_next: ConflictManagementDetailEntry) => void;
|
onChange: (_next: ConflictManagementDetailEntry) => void;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
/** Catalog copy inserted by Generate draft. */
|
||||||
|
generateDraft?: ConflictManagementDetailEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConflictManagementEditFieldsComponent({
|
function ConflictManagementEditFieldsComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
generateDraft,
|
||||||
}: ConflictManagementEditFieldsProps) {
|
}: ConflictManagementEditFieldsProps) {
|
||||||
const m = useMessages();
|
const m = useMessages();
|
||||||
const t = m.create.customRule.conflictManagement;
|
const t = m.create.customRule.conflictManagement;
|
||||||
|
const draftField = m.create.draftField;
|
||||||
|
|
||||||
const patch = useCallback(
|
const patch = useCallback(
|
||||||
<K extends keyof ConflictManagementDetailEntry>(
|
<K extends keyof ConflictManagementDetailEntry>(
|
||||||
@@ -65,6 +69,10 @@ function ConflictManagementEditFieldsComponent({
|
|||||||
value={value.corePrinciple}
|
value={value.corePrinciple}
|
||||||
onChange={(v) => patch("corePrinciple", v)}
|
onChange={(v) => patch("corePrinciple", v)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.corePrinciple}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
<ModalTextAreaField
|
<ModalTextAreaField
|
||||||
label={t.sectionHeadings.applicableScope}
|
label={t.sectionHeadings.applicableScope}
|
||||||
@@ -73,18 +81,35 @@ function ConflictManagementEditFieldsComponent({
|
|||||||
onChange={(v) => onChange(conflictDetailWithScopeTextarea(value, v))}
|
onChange={(v) => onChange(conflictDetailWithScopeTextarea(value, v))}
|
||||||
rows={4}
|
rows={4}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
generateDraftText={
|
||||||
|
generateDraft && generateDraft.applicableScope.length > 0
|
||||||
|
? generateDraft.applicableScope.join("\n")
|
||||||
|
: generateDraft
|
||||||
|
? conflictScopeTextareaValue(generateDraft)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
<ModalTextAreaField
|
<ModalTextAreaField
|
||||||
label={t.sectionHeadings.processProtocol}
|
label={t.sectionHeadings.processProtocol}
|
||||||
value={value.processProtocol}
|
value={value.processProtocol}
|
||||||
onChange={(v) => patch("processProtocol", v)}
|
onChange={(v) => patch("processProtocol", v)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.processProtocol}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
<ModalTextAreaField
|
<ModalTextAreaField
|
||||||
label={t.sectionHeadings.restorationFallbacks}
|
label={t.sectionHeadings.restorationFallbacks}
|
||||||
value={value.restorationFallbacks}
|
value={value.restorationFallbacks}
|
||||||
onChange={(v) => patch("restorationFallbacks", v)}
|
onChange={(v) => patch("restorationFallbacks", v)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.restorationFallbacks}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,15 +17,19 @@ export interface CoreValueEditFieldsProps {
|
|||||||
onChange: (_next: CoreValueDetailEntry) => void;
|
onChange: (_next: CoreValueDetailEntry) => void;
|
||||||
/** Disable meaning/signals. Facet and final-review callers omit this so fields stay editable on open. */
|
/** Disable meaning/signals. Facet and final-review callers omit this so fields stay editable on open. */
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
/** Catalog copy inserted by Generate draft. */
|
||||||
|
generateDraft?: CoreValueDetailEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CoreValueEditFieldsComponent({
|
function CoreValueEditFieldsComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
generateDraft,
|
||||||
}: CoreValueEditFieldsProps) {
|
}: CoreValueEditFieldsProps) {
|
||||||
const m = useMessages();
|
const m = useMessages();
|
||||||
const t = m.create.customRule.coreValues.detailModal;
|
const t = m.create.customRule.coreValues.detailModal;
|
||||||
|
const draftField = m.create.draftField;
|
||||||
|
|
||||||
const patch = useCallback(
|
const patch = useCallback(
|
||||||
<K extends keyof CoreValueDetailEntry>(
|
<K extends keyof CoreValueDetailEntry>(
|
||||||
@@ -45,6 +49,10 @@ function CoreValueEditFieldsComponent({
|
|||||||
onChange={(v) => patch("meaning", v)}
|
onChange={(v) => patch("meaning", v)}
|
||||||
rows={4}
|
rows={4}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.meaning}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
<ModalTextAreaField
|
<ModalTextAreaField
|
||||||
label={t.signalsLabel}
|
label={t.signalsLabel}
|
||||||
@@ -52,6 +60,10 @@ function CoreValueEditFieldsComponent({
|
|||||||
onChange={(v) => patch("signals", v)}
|
onChange={(v) => patch("signals", v)}
|
||||||
rows={4}
|
rows={4}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.signals}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export interface DecisionApproachEditFieldsProps {
|
|||||||
value: DecisionApproachDetailEntry;
|
value: DecisionApproachDetailEntry;
|
||||||
onChange: (_next: DecisionApproachDetailEntry) => void;
|
onChange: (_next: DecisionApproachDetailEntry) => void;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
/** Catalog copy inserted by Generate draft on text fields. */
|
||||||
|
generateDraft?: DecisionApproachDetailEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONSENSUS_LEVEL_MIN = 0;
|
const CONSENSUS_LEVEL_MIN = 0;
|
||||||
@@ -29,9 +31,11 @@ function DecisionApproachEditFieldsComponent({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
generateDraft,
|
||||||
}: DecisionApproachEditFieldsProps) {
|
}: DecisionApproachEditFieldsProps) {
|
||||||
const m = useMessages();
|
const m = useMessages();
|
||||||
const t = m.create.customRule.decisionApproaches;
|
const t = m.create.customRule.decisionApproaches;
|
||||||
|
const draftField = m.create.draftField;
|
||||||
|
|
||||||
const scopes = useMemo(
|
const scopes = useMemo(
|
||||||
() => withDecisionApproachKeyResourceScopes(value.applicableScope),
|
() => withDecisionApproachKeyResourceScopes(value.applicableScope),
|
||||||
@@ -55,6 +59,10 @@ function DecisionApproachEditFieldsComponent({
|
|||||||
value={value.corePrinciple}
|
value={value.corePrinciple}
|
||||||
onChange={(v) => patch("corePrinciple", v)}
|
onChange={(v) => patch("corePrinciple", v)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.corePrinciple}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
<ApplicableScopeField
|
<ApplicableScopeField
|
||||||
label={t.sectionHeadings.applicableScope}
|
label={t.sectionHeadings.applicableScope}
|
||||||
@@ -79,6 +87,10 @@ function DecisionApproachEditFieldsComponent({
|
|||||||
value={value.stepByStepInstructions}
|
value={value.stepByStepInstructions}
|
||||||
onChange={(v) => patch("stepByStepInstructions", v)}
|
onChange={(v) => patch("stepByStepInstructions", v)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.stepByStepInstructions}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
{value.consensusLevel !== undefined ? (
|
{value.consensusLevel !== undefined ? (
|
||||||
<IncrementerBlock
|
<IncrementerBlock
|
||||||
@@ -100,6 +112,10 @@ function DecisionApproachEditFieldsComponent({
|
|||||||
value={value.objectionsDeadlocks}
|
value={value.objectionsDeadlocks}
|
||||||
onChange={(v) => patch("objectionsDeadlocks", v)}
|
onChange={(v) => patch("objectionsDeadlocks", v)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.objectionsDeadlocks}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export interface MembershipMethodEditFieldsProps {
|
|||||||
value: MembershipMethodDetailEntry;
|
value: MembershipMethodDetailEntry;
|
||||||
onChange: (_next: MembershipMethodDetailEntry) => void;
|
onChange: (_next: MembershipMethodDetailEntry) => void;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
/** Catalog copy inserted by Generate draft. */
|
||||||
|
generateDraft?: MembershipMethodDetailEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIELDS: ReadonlyArray<keyof MembershipMethodDetailEntry> = [
|
const FIELDS: ReadonlyArray<keyof MembershipMethodDetailEntry> = [
|
||||||
@@ -28,9 +30,11 @@ function MembershipMethodEditFieldsComponent({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
generateDraft,
|
||||||
}: MembershipMethodEditFieldsProps) {
|
}: MembershipMethodEditFieldsProps) {
|
||||||
const m = useMessages();
|
const m = useMessages();
|
||||||
const t = m.create.customRule.membership;
|
const t = m.create.customRule.membership;
|
||||||
|
const draftField = m.create.draftField;
|
||||||
|
|
||||||
const patch = useCallback(
|
const patch = useCallback(
|
||||||
<K extends keyof MembershipMethodDetailEntry>(
|
<K extends keyof MembershipMethodDetailEntry>(
|
||||||
@@ -52,6 +56,10 @@ function MembershipMethodEditFieldsComponent({
|
|||||||
value={value[field]}
|
value={value[field]}
|
||||||
onChange={(v) => patch(field, v)}
|
onChange={(v) => patch(field, v)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
placeholder={draftField.placeholder}
|
||||||
|
generateDraftText={generateDraft?.[field]}
|
||||||
|
generateDraftLabel={draftField.generateDraft}
|
||||||
|
generateDraftHint={draftField.generateDraftHint}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import InlineTextButton from "../../../../components/buttons/InlineTextButton";
|
|||||||
import { CommunicationMethodEditFields } from "../../components/methodEditFields";
|
import { CommunicationMethodEditFields } from "../../components/methodEditFields";
|
||||||
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
||||||
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
||||||
import { communicationPresetFor } from "../../../../../lib/create/finalReviewChipPresets";
|
import { communicationPresetFor, emptyCommunicationDetails } from "../../../../../lib/create/finalReviewChipPresets";
|
||||||
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
||||||
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
|
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
|
||||||
import { moveFacetSelectionIdToFront } from "../../../../../lib/create/methodCardSelectionOrder";
|
import { moveFacetSelectionIdToFront } from "../../../../../lib/create/methodCardSelectionOrder";
|
||||||
@@ -146,7 +146,7 @@ export function CommunicationMethodsScreen() {
|
|||||||
if (saved) {
|
if (saved) {
|
||||||
return { ...saved };
|
return { ...saved };
|
||||||
}
|
}
|
||||||
return communicationPresetFor(id);
|
return emptyCommunicationDetails();
|
||||||
},
|
},
|
||||||
[state.communicationMethodDetailsById],
|
[state.communicationMethodDetailsById],
|
||||||
);
|
);
|
||||||
@@ -756,6 +756,7 @@ export function CommunicationMethodsScreen() {
|
|||||||
<CommunicationMethodEditFields
|
<CommunicationMethodEditFields
|
||||||
value={pendingDraft}
|
value={pendingDraft}
|
||||||
onChange={handleDraftChange}
|
onChange={handleDraftChange}
|
||||||
|
generateDraft={communicationPresetFor(pendingCardId)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import InlineTextButton from "../../../../components/buttons/InlineTextButton";
|
|||||||
import { ConflictManagementEditFields } from "../../components/methodEditFields";
|
import { ConflictManagementEditFields } from "../../components/methodEditFields";
|
||||||
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
||||||
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
||||||
import { conflictManagementPresetFor } from "../../../../../lib/create/finalReviewChipPresets";
|
import { conflictManagementPresetFor, emptyConflictManagementDetails } from "../../../../../lib/create/finalReviewChipPresets";
|
||||||
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
||||||
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
|
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
|
||||||
import { moveFacetSelectionIdToFront } from "../../../../../lib/create/methodCardSelectionOrder";
|
import { moveFacetSelectionIdToFront } from "../../../../../lib/create/methodCardSelectionOrder";
|
||||||
@@ -147,7 +147,7 @@ export function ConflictManagementScreen() {
|
|||||||
selectedApplicableScope: [...saved.selectedApplicableScope],
|
selectedApplicableScope: [...saved.selectedApplicableScope],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return conflictManagementPresetFor(id);
|
return emptyConflictManagementDetails();
|
||||||
},
|
},
|
||||||
[state.conflictManagementDetailsById],
|
[state.conflictManagementDetailsById],
|
||||||
);
|
);
|
||||||
@@ -757,6 +757,7 @@ export function ConflictManagementScreen() {
|
|||||||
<ConflictManagementEditFields
|
<ConflictManagementEditFields
|
||||||
value={pendingDraft}
|
value={pendingDraft}
|
||||||
onChange={handleDraftChange}
|
onChange={handleDraftChange}
|
||||||
|
generateDraft={conflictManagementPresetFor(pendingCardId)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import InlineTextButton from "../../../../components/buttons/InlineTextButton";
|
|||||||
import { MembershipMethodEditFields } from "../../components/methodEditFields";
|
import { MembershipMethodEditFields } from "../../components/methodEditFields";
|
||||||
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
||||||
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
||||||
import { membershipPresetFor } from "../../../../../lib/create/finalReviewChipPresets";
|
import { membershipPresetFor, emptyMembershipDetails } from "../../../../../lib/create/finalReviewChipPresets";
|
||||||
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
||||||
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
|
import { mergePresetMethodsWithCustom } from "../../../../../lib/create/mergePresetMethodsWithCustom";
|
||||||
import { moveFacetSelectionIdToFront } from "../../../../../lib/create/methodCardSelectionOrder";
|
import { moveFacetSelectionIdToFront } from "../../../../../lib/create/methodCardSelectionOrder";
|
||||||
@@ -144,7 +144,7 @@ export function MembershipMethodsScreen() {
|
|||||||
if (saved) {
|
if (saved) {
|
||||||
return { ...saved };
|
return { ...saved };
|
||||||
}
|
}
|
||||||
return membershipPresetFor(id);
|
return emptyMembershipDetails();
|
||||||
},
|
},
|
||||||
[state.membershipMethodDetailsById],
|
[state.membershipMethodDetailsById],
|
||||||
);
|
);
|
||||||
@@ -750,6 +750,7 @@ export function MembershipMethodsScreen() {
|
|||||||
<MembershipMethodEditFields
|
<MembershipMethodEditFields
|
||||||
value={pendingDraft}
|
value={pendingDraft}
|
||||||
onChange={handleDraftChange}
|
onChange={handleDraftChange}
|
||||||
|
generateDraft={membershipPresetFor(pendingCardId)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import { CreateFlowTwoColumnSelectShell } from "../../components/CreateFlowTwoCo
|
|||||||
import { DecisionApproachEditFields } from "../../components/methodEditFields";
|
import { DecisionApproachEditFields } from "../../components/methodEditFields";
|
||||||
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
import CustomMethodCardWizard from "../../components/CustomMethodCardWizard";
|
||||||
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
import { uploadCreateFlowFile } from "../../../../../lib/create/uploadToServer";
|
||||||
import { decisionApproachPresetFor } from "../../../../../lib/create/finalReviewChipPresets";
|
import { decisionApproachPresetFor, emptyDecisionApproachTextFields } from "../../../../../lib/create/finalReviewChipPresets";
|
||||||
import {
|
import {
|
||||||
applyDecisionApproachKeyResources,
|
applyDecisionApproachKeyResources,
|
||||||
decisionApproachKeyResourceCheckboxIds,
|
decisionApproachKeyResourceCheckboxIds,
|
||||||
@@ -186,7 +186,7 @@ export function DecisionApproachesScreen() {
|
|||||||
if (saved) {
|
if (saved) {
|
||||||
return structuredClone(saved);
|
return structuredClone(saved);
|
||||||
}
|
}
|
||||||
return decisionApproachPresetFor(id);
|
return emptyDecisionApproachTextFields(decisionApproachPresetFor(id));
|
||||||
},
|
},
|
||||||
[state.decisionApproachDetailsById],
|
[state.decisionApproachDetailsById],
|
||||||
);
|
);
|
||||||
@@ -840,6 +840,7 @@ export function DecisionApproachesScreen() {
|
|||||||
<DecisionApproachEditFields
|
<DecisionApproachEditFields
|
||||||
value={pendingDraft}
|
value={pendingDraft}
|
||||||
onChange={handleDraftChange}
|
onChange={handleDraftChange}
|
||||||
|
generateDraft={decisionApproachPresetFor(pendingCardId)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -183,16 +183,16 @@ export function CoreValuesSelectScreen() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getInitialTexts = useCallback(
|
const getInitialTexts = useCallback(
|
||||||
(chipId: string, valueLabel: string): CoreValueDetailEntry => {
|
(chipId: string, _valueLabel: string): CoreValueDetailEntry => {
|
||||||
const saved = state.coreValueDetailsByChipId?.[chipId];
|
const saved = state.coreValueDetailsByChipId?.[chipId];
|
||||||
const preset = getPresetTexts(valueLabel);
|
if (!saved) return EMPTY_DETAIL;
|
||||||
return {
|
return {
|
||||||
meaning: saved?.meaning ?? preset.meaning,
|
meaning: saved.meaning,
|
||||||
signals: saved?.signals ?? preset.signals,
|
signals: saved.signals,
|
||||||
...(saved?.supportText ? { supportText: saved.supportText } : {}),
|
...(saved.supportText ? { supportText: saved.supportText } : {}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[state.coreValueDetailsByChipId, getPresetTexts],
|
[state.coreValueDetailsByChipId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const openModal = useCallback(
|
const openModal = useCallback(
|
||||||
@@ -799,6 +799,7 @@ export function CoreValuesSelectScreen() {
|
|||||||
<CoreValueEditFields
|
<CoreValueEditFields
|
||||||
value={draft}
|
value={draft}
|
||||||
onChange={handleDraftChange}
|
onChange={handleDraftChange}
|
||||||
|
generateDraft={getPresetTexts(modalChipLabel)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Create>
|
</Create>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const ChipContainer = memo<ChipProps>(
|
|||||||
onInputChange={isCustom ? setInputValue : undefined}
|
onInputChange={isCustom ? setInputValue : undefined}
|
||||||
onInputKeyDown={isCustom ? handleKeyDown : undefined}
|
onInputKeyDown={isCustom ? handleKeyDown : undefined}
|
||||||
inputRef={isCustom ? inputRef : undefined}
|
inputRef={isCustom ? inputRef : undefined}
|
||||||
ariaLabel={ariaLabel}
|
ariaLabel={ariaLabel ?? (isCustom ? undefined : label)}
|
||||||
confirmAriaLabel={t("chipConfirm")}
|
confirmAriaLabel={t("chipConfirm")}
|
||||||
typeToAddPlaceholder={t("chipTypeToAdd")}
|
typeToAddPlaceholder={t("chipTypeToAdd")}
|
||||||
closeAriaLabel={t("chipClose")}
|
closeAriaLabel={t("chipClose")}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ function ChipView({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sharedA11y = {
|
const sharedA11y = {
|
||||||
"aria-label": ariaLabel,
|
...(ariaLabel ? { "aria-label": ariaLabel } : {}),
|
||||||
...(isToggle ? { "aria-pressed": isSelected } : {}),
|
...(isToggle ? { "aria-pressed": isSelected } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ const MultiSelectContainer = memo<MultiSelectProps>(
|
|||||||
addButtonAriaLabel={
|
addButtonAriaLabel={
|
||||||
addButtonText || t("multiSelectAddFallback")
|
addButtonText || t("multiSelectAddFallback")
|
||||||
}
|
}
|
||||||
|
chipSelectAriaLabel={t("chipSelect")}
|
||||||
|
chipDeselectAriaLabel={t("chipDeselect")}
|
||||||
formHeader={formHeader}
|
formHeader={formHeader}
|
||||||
onCustomChipConfirm={onCustomChipConfirm}
|
onCustomChipConfirm={onCustomChipConfirm}
|
||||||
onCustomChipClose={onCustomChipClose}
|
onCustomChipClose={onCustomChipClose}
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ export interface MultiSelectViewProps {
|
|||||||
addButton: boolean;
|
addButton: boolean;
|
||||||
addButtonText?: string;
|
addButtonText?: string;
|
||||||
addButtonAriaLabel: string;
|
addButtonAriaLabel: string;
|
||||||
|
chipSelectAriaLabel: string;
|
||||||
|
chipDeselectAriaLabel: string;
|
||||||
formHeader: boolean;
|
formHeader: boolean;
|
||||||
onCustomChipConfirm?: (chipId: string, value: string) => void;
|
onCustomChipConfirm?: (chipId: string, value: string) => void;
|
||||||
onCustomChipClose?: (chipId: string) => void;
|
onCustomChipClose?: (chipId: string) => void;
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ function MultiSelectView({
|
|||||||
addButton,
|
addButton,
|
||||||
addButtonText,
|
addButtonText,
|
||||||
addButtonAriaLabel,
|
addButtonAriaLabel,
|
||||||
|
chipSelectAriaLabel,
|
||||||
|
chipDeselectAriaLabel,
|
||||||
formHeader = true,
|
formHeader = true,
|
||||||
onCustomChipConfirm,
|
onCustomChipConfirm,
|
||||||
onCustomChipClose,
|
onCustomChipClose,
|
||||||
@@ -88,6 +90,14 @@ function MultiSelectView({
|
|||||||
palette={palette}
|
palette={palette}
|
||||||
size={chipSize}
|
size={chipSize}
|
||||||
disabled={chipDisabled}
|
disabled={chipDisabled}
|
||||||
|
ariaLabel={
|
||||||
|
isCustom || !option.label
|
||||||
|
? undefined
|
||||||
|
: (isSelected
|
||||||
|
? chipDeselectAriaLabel
|
||||||
|
: chipSelectAriaLabel
|
||||||
|
).replace("{label}", option.label)
|
||||||
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isCustom && !chipDisabled && onChipClick) {
|
if (!isCustom && !chipDisabled && onChipClick) {
|
||||||
onChipClick(option.id);
|
onChipClick(option.id);
|
||||||
|
|||||||
@@ -86,6 +86,21 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|||||||
textarea: string;
|
textarea: string;
|
||||||
label: string;
|
label: string;
|
||||||
} => {
|
} => {
|
||||||
|
if (appearance === "compose") {
|
||||||
|
if (disabled) {
|
||||||
|
return {
|
||||||
|
textarea:
|
||||||
|
"border-0 bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-tertiary,#b4b4b4)] cursor-not-allowed opacity-60 min-h-[356px] h-[356px] px-[var(--measures-spacing-300,12px)] py-[var(--space-250,10px)]",
|
||||||
|
label: "text-[var(--color-content-default-secondary)]",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
textarea:
|
||||||
|
"border-0 bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] min-h-[356px] h-[356px] px-[var(--measures-spacing-300,12px)] py-[var(--space-250,10px)] focus:outline-none focus:ring-0",
|
||||||
|
label: "text-[var(--color-content-default-secondary)]",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (appearance === "embedded") {
|
if (appearance === "embedded") {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return {
|
return {
|
||||||
@@ -161,8 +176,8 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|||||||
|
|
||||||
const textareaClasses = `
|
const textareaClasses = `
|
||||||
scrollbar-design w-full transition-all duration-200 ease-in-out resize-none
|
scrollbar-design w-full transition-all duration-200 ease-in-out resize-none
|
||||||
${appearance === "embedded" ? "rounded-[var(--radius-300,12px)]" : "border"}
|
${appearance === "embedded" || appearance === "compose" ? "rounded-[var(--radius-300,12px)]" : "border"}
|
||||||
${currentSize.textarea}
|
${appearance === "compose" ? "text-medium-paragraph" : currentSize.textarea}
|
||||||
${stateStyles.textarea}
|
${stateStyles.textarea}
|
||||||
${className}
|
${className}
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import type { InputStateValue } from "../../../../lib/propNormalization";
|
import type {
|
||||||
|
InputStateValue,
|
||||||
|
TextAreaAppearanceValue,
|
||||||
|
} from "../../../../lib/propNormalization";
|
||||||
|
|
||||||
export type TextAreaSizeValue = "small" | "medium" | "large";
|
export type TextAreaSizeValue = "small" | "medium" | "large";
|
||||||
export type TextAreaLabelVariantValue = "default" | "horizontal";
|
export type TextAreaLabelVariantValue = "default" | "horizontal";
|
||||||
|
|
||||||
export type TextAreaAppearanceValue = "default" | "embedded";
|
export type { TextAreaAppearanceValue };
|
||||||
|
|
||||||
export interface TextAreaProps extends Omit<
|
export interface TextAreaProps extends Omit<
|
||||||
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||||
@@ -49,7 +52,8 @@ export interface TextAreaProps extends Omit<
|
|||||||
/**
|
/**
|
||||||
* Visual appearance. "embedded" matches Create modal sections (Figma 20736-12668):
|
* Visual appearance. "embedded" matches Create modal sections (Figma 20736-12668):
|
||||||
* borderless, darker grey background, tertiary text in default (primary on
|
* borderless, darker grey background, tertiary text in default (primary on
|
||||||
* focus). "default" is the standard bordered input.
|
* focus). "compose" is the tall empty creation field (Figma 23746:49780).
|
||||||
|
* "default" is the standard bordered input.
|
||||||
* @default "default"
|
* @default "default"
|
||||||
*/
|
*/
|
||||||
appearance?: TextAreaAppearanceValue;
|
appearance?: TextAreaAppearanceValue;
|
||||||
@@ -79,7 +83,7 @@ export interface TextAreaViewProps {
|
|||||||
textHint?: boolean | string;
|
textHint?: boolean | string;
|
||||||
formHeader?: boolean;
|
formHeader?: boolean;
|
||||||
showHelpIcon?: boolean;
|
showHelpIcon?: boolean;
|
||||||
appearance?: "default" | "embedded";
|
appearance?: TextAreaAppearanceValue;
|
||||||
helpIconAlt: string;
|
helpIconAlt: string;
|
||||||
hintDefault: string;
|
hintDefault: string;
|
||||||
}
|
}
|
||||||
@@ -14,11 +14,10 @@ import type {
|
|||||||
} from "../../app/(app)/create/types";
|
} from "../../app/(app)/create/types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-method preset defaults shipped in `messages/en/create/customRule/*.json`.
|
* Catalog copy for method/value sections. Generate draft inserts these into
|
||||||
* Used to seed the final-review edit modal when the user has no saved
|
* empty creation-modal fields. Publish still merges them onto saved overrides
|
||||||
* override yet, and to merge onto overrides when emitting the published rule
|
* so every method carries a complete section payload when the author only
|
||||||
* document so every method carries a complete section payload even if the
|
* edited a subset of fields.
|
||||||
* author only edited a subset of fields.
|
|
||||||
*/
|
*/
|
||||||
type CustomRuleMethodRow = {
|
type CustomRuleMethodRow = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -96,6 +95,10 @@ export function communicationPresetFor(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emptyCommunicationDetails(): CommunicationMethodDetailEntry {
|
||||||
|
return { corePrinciple: "", logisticsAdmin: "", codeOfConduct: "" };
|
||||||
|
}
|
||||||
|
|
||||||
export function membershipPresetFor(id: string): MembershipMethodDetailEntry {
|
export function membershipPresetFor(id: string): MembershipMethodDetailEntry {
|
||||||
const method = findMethod(membershipMessages, id);
|
const method = findMethod(membershipMessages, id);
|
||||||
const s = method?.sections ?? {};
|
const s = method?.sections ?? {};
|
||||||
@@ -106,6 +109,10 @@ export function membershipPresetFor(id: string): MembershipMethodDetailEntry {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emptyMembershipDetails(): MembershipMethodDetailEntry {
|
||||||
|
return { eligibility: "", joiningProcess: "", expectations: "" };
|
||||||
|
}
|
||||||
|
|
||||||
/** Default consensus level used when a **catalog** preset omits a value. */
|
/** Default consensus level used when a **catalog** preset omits a value. */
|
||||||
export const DECISION_CONSENSUS_LEVEL_DEFAULT = 75;
|
export const DECISION_CONSENSUS_LEVEL_DEFAULT = 75;
|
||||||
|
|
||||||
@@ -132,6 +139,20 @@ export function decisionApproachPresetFor(
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Catalog scopes/consensus kept; text fields start empty for Generate draft. */
|
||||||
|
export function emptyDecisionApproachTextFields(
|
||||||
|
preset: DecisionApproachDetailEntry,
|
||||||
|
): DecisionApproachDetailEntry {
|
||||||
|
return {
|
||||||
|
...preset,
|
||||||
|
corePrinciple: "",
|
||||||
|
stepByStepInstructions: "",
|
||||||
|
objectionsDeadlocks: "",
|
||||||
|
applicableScope: [...preset.applicableScope],
|
||||||
|
selectedApplicableScope: [...preset.selectedApplicableScope],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function conflictManagementPresetFor(
|
export function conflictManagementPresetFor(
|
||||||
id: string,
|
id: string,
|
||||||
): ConflictManagementDetailEntry {
|
): ConflictManagementDetailEntry {
|
||||||
@@ -146,11 +167,19 @@ export function conflictManagementPresetFor(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emptyConflictManagementDetails(): ConflictManagementDetailEntry {
|
||||||
|
return {
|
||||||
|
corePrinciple: "",
|
||||||
|
applicableScope: [],
|
||||||
|
selectedApplicableScope: [],
|
||||||
|
processProtocol: "",
|
||||||
|
restorationFallbacks: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preset meaning/signals for a core value chip. Mirrors `CoreValuesSelectScreen`'s
|
* Preset meaning/signals for a core value chip. Used by Generate draft and
|
||||||
* `getInitialTexts` so the final-review edit modal opens with the same
|
* by publish/display merge when the author has not saved an override.
|
||||||
* preset copy the select screen would have shown — without requiring the
|
|
||||||
* user to have opened the select-screen modal first.
|
|
||||||
*
|
*
|
||||||
* Lookup order:
|
* Lookup order:
|
||||||
* 1. Numeric chip id → 1-based index into `coreValues.json` `values[]`
|
* 1. Numeric chip id → 1-based index into `coreValues.json` `values[]`
|
||||||
|
|||||||
@@ -89,46 +89,6 @@ function clampPercent(n: number): number {
|
|||||||
return Math.min(100, Math.max(1, Math.round(n)));
|
return Math.min(100, Math.max(1, Math.round(n)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function facetPrefillHasContent(prefill: MethodCardWizardFacetPrefill): boolean {
|
|
||||||
switch (prefill.group) {
|
|
||||||
case "communication":
|
|
||||||
return (
|
|
||||||
hasTrimmedText(prefill.draft.corePrinciple) ||
|
|
||||||
hasTrimmedText(prefill.draft.logisticsAdmin) ||
|
|
||||||
hasTrimmedText(prefill.draft.codeOfConduct)
|
|
||||||
);
|
|
||||||
case "membership":
|
|
||||||
return (
|
|
||||||
hasTrimmedText(prefill.draft.eligibility) ||
|
|
||||||
hasTrimmedText(prefill.draft.joiningProcess) ||
|
|
||||||
hasTrimmedText(prefill.draft.expectations)
|
|
||||||
);
|
|
||||||
case "decisionApproaches":
|
|
||||||
return (
|
|
||||||
hasTrimmedText(prefill.draft.corePrinciple) ||
|
|
||||||
hasTrimmedText(prefill.draft.stepByStepInstructions) ||
|
|
||||||
hasTrimmedText(prefill.draft.objectionsDeadlocks) ||
|
|
||||||
prefill.draft.applicableScope.length > 0 ||
|
|
||||||
prefill.draft.selectedApplicableScope.length > 0
|
|
||||||
);
|
|
||||||
case "conflictManagement":
|
|
||||||
return (
|
|
||||||
hasTrimmedText(prefill.draft.corePrinciple) ||
|
|
||||||
hasTrimmedText(prefill.draft.processProtocol) ||
|
|
||||||
hasTrimmedText(prefill.draft.restorationFallbacks) ||
|
|
||||||
formatConflictApplicableScopeForTextarea(
|
|
||||||
prefill.draft.selectedApplicableScope,
|
|
||||||
prefill.draft.applicableScope,
|
|
||||||
).trim().length > 0
|
|
||||||
);
|
|
||||||
case "coreValues":
|
|
||||||
return (
|
|
||||||
hasTrimmedText(prefill.draft.meaning) ||
|
|
||||||
hasTrimmedText(prefill.draft.signals)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapFacetPrefillToWizardFieldBlocks(
|
function mapFacetPrefillToWizardFieldBlocks(
|
||||||
prefill: MethodCardWizardFacetPrefill,
|
prefill: MethodCardWizardFacetPrefill,
|
||||||
): CustomMethodCardFieldBlock[] {
|
): CustomMethodCardFieldBlock[] {
|
||||||
@@ -198,7 +158,11 @@ function mapFacetPrefillToWizardFieldBlocks(
|
|||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
typeof prefill.draft.consensusLevel === "number" &&
|
typeof prefill.draft.consensusLevel === "number" &&
|
||||||
facetPrefillHasContent(prefill)
|
(hasTrimmedText(prefill.draft.corePrinciple) ||
|
||||||
|
hasTrimmedText(prefill.draft.stepByStepInstructions) ||
|
||||||
|
hasTrimmedText(prefill.draft.objectionsDeadlocks) ||
|
||||||
|
prefill.draft.applicableScope.length > 0 ||
|
||||||
|
prefill.draft.selectedApplicableScope.length > 0)
|
||||||
) {
|
) {
|
||||||
blocks.push({
|
blocks.push({
|
||||||
kind: "proportion",
|
kind: "proportion",
|
||||||
@@ -260,14 +224,12 @@ function mapFacetPrefillToWizardFieldBlocks(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Map a method card's facet editors onto wizard field blocks so Customize
|
* Map a method card's facet editors onto wizard field blocks so Customize
|
||||||
* can show and edit the same sections on step 3.
|
* can show and edit the same sections on step 3. Empty copy still yields
|
||||||
|
* the catalog headings so authors can fill them in the wizard.
|
||||||
*/
|
*/
|
||||||
export function facetDetailsToWizardFieldBlocks(
|
export function facetDetailsToWizardFieldBlocks(
|
||||||
prefill: MethodCardWizardFacetPrefill,
|
prefill: MethodCardWizardFacetPrefill,
|
||||||
): CustomMethodCardFieldBlock[] {
|
): CustomMethodCardFieldBlock[] {
|
||||||
if (!facetPrefillHasContent(prefill)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return mapFacetPrefillToWizardFieldBlocks(prefill);
|
return mapFacetPrefillToWizardFieldBlocks(prefill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,11 @@ export type ToggleGroupPositionValue =
|
|||||||
export const LABEL_VARIANT_OPTIONS = ["default", "horizontal"] as const;
|
export const LABEL_VARIANT_OPTIONS = ["default", "horizontal"] as const;
|
||||||
export type LabelVariantValue = (typeof LABEL_VARIANT_OPTIONS)[number];
|
export type LabelVariantValue = (typeof LABEL_VARIANT_OPTIONS)[number];
|
||||||
|
|
||||||
export const TEXT_AREA_APPEARANCE_OPTIONS = ["default", "embedded"] as const;
|
export const TEXT_AREA_APPEARANCE_OPTIONS = [
|
||||||
|
"default",
|
||||||
|
"embedded",
|
||||||
|
"compose",
|
||||||
|
] as const;
|
||||||
export type TextAreaAppearanceValue =
|
export type TextAreaAppearanceValue =
|
||||||
(typeof TEXT_AREA_APPEARANCE_OPTIONS)[number];
|
(typeof TEXT_AREA_APPEARANCE_OPTIONS)[number];
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
"chipConfirm": "Confirm",
|
"chipConfirm": "Confirm",
|
||||||
"chipTypeToAdd": "Type to add",
|
"chipTypeToAdd": "Type to add",
|
||||||
"chipClose": "Close",
|
"chipClose": "Close",
|
||||||
|
"chipSelect": "Select {label}",
|
||||||
|
"chipDeselect": "Deselect {label}",
|
||||||
"selectPlaceholder": "Choose an option",
|
"selectPlaceholder": "Choose an option",
|
||||||
"selectAriaLabel": "Select an option",
|
"selectAriaLabel": "Select an option",
|
||||||
"multiSelectAddDefault": "Add organization type",
|
"multiSelectAddDefault": "Add organization type",
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Generate-draft control on creation-modal text fields (Figma Step Modal 23746:49780).",
|
||||||
|
"generateDraft": "Generate draft",
|
||||||
|
"generateDraftHint": "Get a first draft for this section",
|
||||||
|
"placeholder": "Describe how this method should work"
|
||||||
|
}
|
||||||
@@ -69,6 +69,7 @@ import createTopNav from "./create/topNav.json";
|
|||||||
import createDraftHydration from "./create/draftHydration.json";
|
import createDraftHydration from "./create/draftHydration.json";
|
||||||
import createTemplateReview from "./create/templateReview.json";
|
import createTemplateReview from "./create/templateReview.json";
|
||||||
import createUpload from "./create/upload.json";
|
import createUpload from "./create/upload.json";
|
||||||
|
import createDraftField from "./create/draftField.json";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
common,
|
common,
|
||||||
@@ -138,6 +139,7 @@ export default {
|
|||||||
draftHydration: createDraftHydration,
|
draftHydration: createDraftHydration,
|
||||||
templateReview: createTemplateReview,
|
templateReview: createTemplateReview,
|
||||||
upload: createUpload,
|
upload: createUpload,
|
||||||
|
draftField: createDraftField,
|
||||||
},
|
},
|
||||||
navigation,
|
navigation,
|
||||||
metadata,
|
metadata,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default {
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
component:
|
component:
|
||||||
"Shared 'labelled text area' field used by every create-flow modal section. Pairs `InputLabel` with a `TextArea` set to the `embedded` appearance. Section help marks stay off until tooltip behavior ships.",
|
"Shared labelled text area for create-flow modal sections. With `generateDraftText`, matches Figma Step Modal (23746:49780): tall compose field plus Generate draft.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -58,6 +58,25 @@ export const WithValue = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GenerateDraft = {
|
||||||
|
render: () => {
|
||||||
|
const [value, setValue] = React.useState("");
|
||||||
|
return (
|
||||||
|
<div className="w-[520px]">
|
||||||
|
<ModalTextAreaField
|
||||||
|
label="What does this value mean to your group?"
|
||||||
|
value={value}
|
||||||
|
onChange={setValue}
|
||||||
|
placeholder="Describe how this method should work"
|
||||||
|
generateDraftText="We design spaces and tools that everyone can use regardless of ability or background."
|
||||||
|
generateDraftLabel="Generate draft"
|
||||||
|
generateDraftHint="Get a first draft for this section"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const Disabled = {
|
export const Disabled = {
|
||||||
render: () => (
|
render: () => (
|
||||||
<div className="w-[520px]">
|
<div className="w-[520px]">
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ const config: ComponentTestSuiteConfig<Props> = {
|
|||||||
describe("Chip", () => {
|
describe("Chip", () => {
|
||||||
componentTestSuite<Props>(config);
|
componentTestSuite<Props>(config);
|
||||||
|
|
||||||
|
it("uses the visible label as the accessible name when ariaLabel is omitted", () => {
|
||||||
|
render(<Chip label="Worker cooperative" state="unselected" />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Worker cooperative" }),
|
||||||
|
).toHaveAttribute("aria-label", "Worker cooperative");
|
||||||
|
});
|
||||||
|
|
||||||
it("exposes aria-pressed false when unselected", () => {
|
it("exposes aria-pressed false when unselected", () => {
|
||||||
render(<Chip label="Worker cooperative" state="unselected" />);
|
render(<Chip label="Worker cooperative" state="unselected" />);
|
||||||
const chip = screen.getByRole("button", { name: "Worker cooperative" });
|
const chip = screen.getByRole("button", { name: "Worker cooperative" });
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function ScreenWithStateProbe({
|
|||||||
* and `buildPublishPayload` for the read side.
|
* and `buildPublishPayload` for the read side.
|
||||||
*/
|
*/
|
||||||
describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||||
it("seeds the modal from preset and persists edits + selection on Confirm", async () => {
|
it("starts empty and Generate draft inserts the catalog preset", async () => {
|
||||||
let latest: CreateFlowState = {};
|
let latest: CreateFlowState = {};
|
||||||
render(
|
render(
|
||||||
<ScreenWithStateProbe
|
<ScreenWithStateProbe
|
||||||
@@ -82,9 +82,16 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
|||||||
const textboxes = within(dialog).getAllByRole("textbox");
|
const textboxes = within(dialog).getAllByRole("textbox");
|
||||||
expect(textboxes.length).toBe(3);
|
expect(textboxes.length).toBe(3);
|
||||||
const corePrincipleField = textboxes[0] as HTMLTextAreaElement;
|
const corePrincipleField = textboxes[0] as HTMLTextAreaElement;
|
||||||
// Preset corePrinciple must seed into the first body textarea so the user
|
expect(corePrincipleField.value).toBe("");
|
||||||
// edits a real starting point rather than an empty field.
|
|
||||||
expect(corePrincipleField.value.length).toBeGreaterThan(0);
|
const generateButtons = within(dialog).getAllByRole("button", {
|
||||||
|
name: "Generate draft",
|
||||||
|
});
|
||||||
|
expect(generateButtons.length).toBe(3);
|
||||||
|
fireEvent.click(generateButtons[0]);
|
||||||
|
expect(corePrincipleField.value).toBe(
|
||||||
|
communicationPresetFor("signal").corePrinciple,
|
||||||
|
);
|
||||||
|
|
||||||
fireEvent.change(corePrincipleField, { target: { value: "Custom principle" } });
|
fireEvent.change(corePrincipleField, { target: { value: "Custom principle" } });
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
@@ -466,6 +473,11 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
|||||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
for (const button of within(dialog).getAllByRole("button", {
|
||||||
|
name: "Generate draft",
|
||||||
|
})) {
|
||||||
|
fireEvent.click(button);
|
||||||
|
}
|
||||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,18 @@ describe("CommunityStructureSelectScreen", () => {
|
|||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(screen.queryByAltText("Help")).not.toBeInTheDocument();
|
expect(screen.queryByAltText("Help")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("gives structure chips Select/Deselect accessible names", () => {
|
||||||
|
renderWithProviders(<CommunityStructureSelectScreen />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Select Worker cooperative" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Select Local" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Select Early stage" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -41,6 +41,26 @@ describe("CoreValuesSelectScreen", () => {
|
|||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("gives value chips Select/Deselect accessible names", () => {
|
||||||
|
renderWithProviders(<CoreValuesSelectScreen />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Select Accessibility" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts the value modal empty and Generate draft inserts catalog copy", async () => {
|
||||||
|
renderWithProviders(<CoreValuesSelectScreen />);
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Select Accessibility" }));
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
const fields = within(dialog).getAllByRole("textbox") as HTMLTextAreaElement[];
|
||||||
|
expect(fields[0].value).toBe("");
|
||||||
|
expect(fields[1].value).toBe("");
|
||||||
|
fireEvent.click(
|
||||||
|
within(dialog).getAllByRole("button", { name: "Generate draft" })[0],
|
||||||
|
);
|
||||||
|
expect(fields[0].value).toMatch(/everyone can use/i);
|
||||||
|
});
|
||||||
|
|
||||||
it("closes a pending value without confirm when fields are unchanged", async () => {
|
it("closes a pending value without confirm when fields are unchanged", async () => {
|
||||||
renderWithProviders(<CoreValuesSelectScreen />);
|
renderWithProviders(<CoreValuesSelectScreen />);
|
||||||
fireEvent.click(screen.getByText("Accessibility"));
|
fireEvent.click(screen.getByText("Accessibility"));
|
||||||
@@ -289,12 +309,10 @@ describe("CoreValuesSelectScreen", () => {
|
|||||||
*/
|
*/
|
||||||
function countCustomChips(label: string) {
|
function countCustomChips(label: string) {
|
||||||
return screen
|
return screen
|
||||||
.queryAllByRole("button", { name: label })
|
.queryAllByRole("button", {
|
||||||
.filter(
|
name: new RegExp(`^(Select|Deselect) ${label}$`),
|
||||||
(el) =>
|
})
|
||||||
!el.closest('[role="dialog"]') &&
|
.filter((el) => !el.closest('[role="dialog"]')).length;
|
||||||
(el.getAttribute("aria-label") ?? "") !== `Remove ${label}`,
|
|
||||||
).length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function completeAddValueWizard(label: string) {
|
async function completeAddValueWizard(label: string) {
|
||||||
@@ -417,7 +435,7 @@ describe("CoreValuesSelectScreen", () => {
|
|||||||
),
|
),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole("button", { name: "Anti-oppression" }),
|
screen.getByRole("button", { name: "Select Anti-oppression" }),
|
||||||
).toBeDisabled();
|
).toBeDisabled();
|
||||||
expect(screen.getByRole("button", { name: "Add value" })).toBeDisabled();
|
expect(screen.getByRole("button", { name: "Add value" })).toBeDisabled();
|
||||||
expect(screen.getByRole("button", { name: /^add$/i })).toBeDisabled();
|
expect(screen.getByRole("button", { name: /^add$/i })).toBeDisabled();
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import { FinalReviewScreen } from "../../app/(app)/create/screens/review/FinalRe
|
|||||||
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";
|
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";
|
||||||
import type { CreateFlowState } from "../../app/(app)/create/types";
|
import type { CreateFlowState } from "../../app/(app)/create/types";
|
||||||
|
|
||||||
|
function catalogChipName(label: string) {
|
||||||
|
return new RegExp(`^(Select|Deselect) ${label}$`);
|
||||||
|
}
|
||||||
|
|
||||||
async function confirmDiscardCustomizeEdits() {
|
async function confirmDiscardCustomizeEdits() {
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Discard" }),
|
await screen.findByRole("button", { name: "Discard" }),
|
||||||
@@ -232,7 +236,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
it("opens the chip modal when a chip is clicked, matching the preset copy", async () => {
|
it("opens the chip modal when a chip is clicked, matching the preset copy", async () => {
|
||||||
render(<FinalReviewWithCustomizeSelections />);
|
render(<FinalReviewWithCustomizeSelections />);
|
||||||
|
|
||||||
const signalChip = await screen.findByRole("button", { name: "Signal" });
|
const signalChip = await screen.findByRole("button", { name: catalogChipName("Signal") });
|
||||||
fireEvent.click(signalChip);
|
fireEvent.click(signalChip);
|
||||||
|
|
||||||
// Modal subtitle is the `supportText` from communication.json for the
|
// Modal subtitle is the `supportText` from communication.json for the
|
||||||
@@ -273,7 +277,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Video Meetings" }),
|
await screen.findByRole("button", { name: catalogChipName("Video Meetings") }),
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
expect(within(dialog).getAllByRole("textbox")).toHaveLength(3);
|
expect(within(dialog).getAllByRole("textbox")).toHaveLength(3);
|
||||||
@@ -287,7 +291,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
|
|
||||||
it("method chip modal kebab offers Customize but not Duplicate", async () => {
|
it("method chip modal kebab offers Customize but not Duplicate", async () => {
|
||||||
render(<FinalReviewWithCustomizeSelections />);
|
render(<FinalReviewWithCustomizeSelections />);
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
within(dialog).getByRole("button", { name: /more options/i }),
|
within(dialog).getByRole("button", { name: /more options/i }),
|
||||||
@@ -317,7 +321,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
}
|
}
|
||||||
render(<CoreValuesHarness />);
|
render(<CoreValuesHarness />);
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Accessibility" }),
|
await screen.findByRole("button", { name: catalogChipName("Accessibility") }),
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
@@ -348,7 +352,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
}
|
}
|
||||||
render(<CoreValuesHarness />);
|
render(<CoreValuesHarness />);
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Accessibility" }),
|
await screen.findByRole("button", { name: catalogChipName("Accessibility") }),
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
@@ -366,7 +370,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
it("opens method chip modal editable, with Save disabled until a field changes", async () => {
|
it("opens method chip modal editable, with Save disabled until a field changes", async () => {
|
||||||
render(<FinalReviewWithCustomizeSelections />);
|
render(<FinalReviewWithCustomizeSelections />);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
|
||||||
const saveButton = within(dialog).getByRole("button", { name: "Save" });
|
const saveButton = within(dialog).getByRole("button", { name: "Save" });
|
||||||
@@ -400,7 +404,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
}
|
}
|
||||||
render(<CoreValuesHarness />);
|
render(<CoreValuesHarness />);
|
||||||
|
|
||||||
const chip = await screen.findByRole("button", { name: "Accessibility" });
|
const chip = await screen.findByRole("button", { name: catalogChipName("Accessibility") });
|
||||||
fireEvent.click(chip);
|
fireEvent.click(chip);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -430,7 +434,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
render(<CoreValuesHarness />);
|
render(<CoreValuesHarness />);
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Accessibility" }),
|
await screen.findByRole("button", { name: catalogChipName("Accessibility") }),
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
expect(
|
expect(
|
||||||
@@ -468,7 +472,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
render(<UseWithoutChangesHarness />);
|
render(<UseWithoutChangesHarness />);
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Accessibility" }),
|
await screen.findByRole("button", { name: catalogChipName("Accessibility") }),
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
expect(
|
expect(
|
||||||
@@ -514,7 +518,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Accessibility" }),
|
await screen.findByRole("button", { name: catalogChipName("Accessibility") }),
|
||||||
);
|
);
|
||||||
const valuesDialog = await screen.findByRole("dialog");
|
const valuesDialog = await screen.findByRole("dialog");
|
||||||
expect(within(valuesDialog).getAllByRole("textbox").length).toBeGreaterThan(
|
expect(within(valuesDialog).getAllByRole("textbox").length).toBeGreaterThan(
|
||||||
@@ -542,7 +546,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const methodDialog = await screen.findByRole("dialog");
|
const methodDialog = await screen.findByRole("dialog");
|
||||||
expect(within(methodDialog).getAllByRole("textbox").length).toBeGreaterThan(
|
expect(within(methodDialog).getAllByRole("textbox").length).toBeGreaterThan(
|
||||||
0,
|
0,
|
||||||
@@ -567,7 +571,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
expect(
|
expect(
|
||||||
within(dialog).getByRole("button", { name: "Remove" }),
|
within(dialog).getByRole("button", { name: "Remove" }),
|
||||||
@@ -595,7 +599,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
fireEvent.click(within(dialog).getByRole("button", { name: "Remove" }));
|
fireEvent.click(within(dialog).getByRole("button", { name: "Remove" }));
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -633,7 +637,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
);
|
);
|
||||||
void latest;
|
void latest;
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
const saveButton = within(dialog).getByRole("button", { name: "Save" });
|
const saveButton = within(dialog).getByRole("button", { name: "Save" });
|
||||||
expect(saveButton).toBeDisabled();
|
expect(saveButton).toBeDisabled();
|
||||||
@@ -663,7 +667,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
const principleField = within(dialog).getByRole("textbox", {
|
const principleField = within(dialog).getByRole("textbox", {
|
||||||
name: /core principle/i,
|
name: /core principle/i,
|
||||||
@@ -691,7 +695,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
const principleField = within(dialog).getByRole("textbox", {
|
const principleField = within(dialog).getByRole("textbox", {
|
||||||
name: /core principle/i,
|
name: /core principle/i,
|
||||||
@@ -719,7 +723,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
expect(dispatchBeforeUnload()).toBe(false);
|
expect(dispatchBeforeUnload()).toBe(false);
|
||||||
const principleField = within(dialog).getByRole("textbox", {
|
const principleField = within(dialog).getByRole("textbox", {
|
||||||
@@ -750,7 +754,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Custom Comm" }),
|
await screen.findByRole("button", { name: catalogChipName("Custom Comm") }),
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
expect(
|
expect(
|
||||||
@@ -784,9 +788,9 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await screen.findByRole("button", { name: "Garden IRC" }),
|
await screen.findByRole("button", { name: catalogChipName("Garden IRC") }),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(screen.getByRole("button", { name: "Signal" })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: catalogChipName("Signal") })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows editable field blocks for user-authored communication chips when configured", async () => {
|
it("shows editable field blocks for user-authored communication chips when configured", async () => {
|
||||||
@@ -818,7 +822,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Custom Comm" }),
|
await screen.findByRole("button", { name: catalogChipName("Custom Comm") }),
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
expect(
|
expect(
|
||||||
@@ -861,7 +865,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
await screen.findByRole("button", { name: "Custom Comm" }),
|
await screen.findByRole("button", { name: catalogChipName("Custom Comm") }),
|
||||||
);
|
);
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
const notesField = within(dialog).getByRole("textbox", { name: /notes/i });
|
const notesField = within(dialog).getByRole("textbox", { name: /notes/i });
|
||||||
@@ -887,7 +891,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
within(dialog).getByRole("button", { name: /more options/i }),
|
within(dialog).getByRole("button", { name: /more options/i }),
|
||||||
@@ -921,7 +925,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
within(dialog).getByRole("button", { name: /more options/i }),
|
within(dialog).getByRole("button", { name: /more options/i }),
|
||||||
@@ -957,7 +961,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
fireEvent.click(await screen.findByRole("button", { name: catalogChipName("Signal") }));
|
||||||
const dialog = await screen.findByRole("dialog");
|
const dialog = await screen.findByRole("dialog");
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
within(dialog).getByRole("button", { name: /more options/i }),
|
within(dialog).getByRole("button", { name: /more options/i }),
|
||||||
|
|||||||
@@ -110,4 +110,44 @@ describe("ModalTextAreaField behavior", () => {
|
|||||||
|
|
||||||
expect(screen.getByAltText("Help")).toBeInTheDocument();
|
expect(screen.getByAltText("Help")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("starts empty and Generate draft inserts the provided catalog copy", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onChange = vi.fn();
|
||||||
|
renderWithProviders(
|
||||||
|
<ModalTextAreaField
|
||||||
|
label="Core principle"
|
||||||
|
value=""
|
||||||
|
onChange={onChange}
|
||||||
|
generateDraftText="Catalog first draft"
|
||||||
|
generateDraftLabel="Generate draft"
|
||||||
|
generateDraftHint="Get a first draft for this section"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("textbox", { name: /Core principle/i })).toHaveValue(
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByText("Get a first draft for this section"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
await user.click(screen.getByRole("button", { name: "Generate draft" }));
|
||||||
|
expect(onChange).toHaveBeenCalledWith("Catalog first draft");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hides Generate draft when there is no catalog copy", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<ModalTextAreaField
|
||||||
|
label="Core principle"
|
||||||
|
value=""
|
||||||
|
onChange={() => {}}
|
||||||
|
generateDraftText=""
|
||||||
|
generateDraftLabel="Generate draft"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByRole("button", { name: "Generate draft" }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -62,6 +62,16 @@ describe("MultiSelect – behaviour specifics", () => {
|
|||||||
expect(screen.getByText("Option 2")).toBeInTheDocument();
|
expect(screen.getByText("Option 2")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("names toggle chips Select/Deselect so they are not unnamed buttons", () => {
|
||||||
|
render(<MultiSelect options={defaultChipOptions} />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Select Option 1" }),
|
||||||
|
).toHaveAttribute("aria-pressed", "false");
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Deselect Option 2" }),
|
||||||
|
).toHaveAttribute("aria-pressed", "true");
|
||||||
|
});
|
||||||
|
|
||||||
it("calls onChipClick when a chip is clicked", async () => {
|
it("calls onChipClick when a chip is clicked", async () => {
|
||||||
const handleChipClick = vi.fn();
|
const handleChipClick = vi.fn();
|
||||||
render(
|
render(
|
||||||
@@ -151,11 +161,11 @@ describe("MultiSelect – behaviour specifics", () => {
|
|||||||
|
|
||||||
expect(screen.getByText("2 of 2")).toBeInTheDocument();
|
expect(screen.getByText("2 of 2")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Limit reached")).toBeInTheDocument();
|
expect(screen.getByText("Limit reached")).toBeInTheDocument();
|
||||||
expect(screen.getByRole("button", { name: "One" })).toBeEnabled();
|
expect(screen.getByRole("button", { name: "Deselect One" })).toBeEnabled();
|
||||||
expect(screen.getByRole("button", { name: "Three" })).toBeDisabled();
|
expect(screen.getByRole("button", { name: "Select Three" })).toBeDisabled();
|
||||||
expect(screen.getByRole("button", { name: "Add option" })).toBeDisabled();
|
expect(screen.getByRole("button", { name: "Add option" })).toBeDisabled();
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: "Three" }));
|
await userEvent.click(screen.getByRole("button", { name: "Select Three" }));
|
||||||
await userEvent.click(screen.getByRole("button", { name: "Add option" }));
|
await userEvent.click(screen.getByRole("button", { name: "Add option" }));
|
||||||
expect(handleChipClick).not.toHaveBeenCalled();
|
expect(handleChipClick).not.toHaveBeenCalled();
|
||||||
expect(handleAddClick).not.toHaveBeenCalled();
|
expect(handleAddClick).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -18,4 +18,14 @@ describe("CommunitySizeSelectScreen", () => {
|
|||||||
expect(screen.getByText("1 member")).toBeInTheDocument();
|
expect(screen.getByText("1 member")).toBeInTheDocument();
|
||||||
expect(screen.getByText("2-5 members")).toBeInTheDocument();
|
expect(screen.getByText("2-5 members")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("gives size chips Select/Deselect accessible names", () => {
|
||||||
|
render(<CommunitySizeSelectScreen />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Select 1 member" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Select 2-5 members" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -177,7 +177,7 @@ describe("buildMethodCardWizardInitialValues", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("facetDetailsToWizardFieldBlocks", () => {
|
describe("facetDetailsToWizardFieldBlocks", () => {
|
||||||
it("returns no blocks when facet copy is empty", () => {
|
it("keeps catalog field headings when facet copy is empty", () => {
|
||||||
expect(
|
expect(
|
||||||
facetDetailsToWizardFieldBlocks({
|
facetDetailsToWizardFieldBlocks({
|
||||||
group: "communication",
|
group: "communication",
|
||||||
@@ -188,7 +188,26 @@ describe("facetDetailsToWizardFieldBlocks", () => {
|
|||||||
},
|
},
|
||||||
headings: commHeadings,
|
headings: commHeadings,
|
||||||
}),
|
}),
|
||||||
).toEqual([]);
|
).toEqual([
|
||||||
|
{
|
||||||
|
kind: "text",
|
||||||
|
id: "facet-corePrinciple",
|
||||||
|
blockTitle: "Core Principle & Scope",
|
||||||
|
placeholderText: " ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "text",
|
||||||
|
id: "facet-logisticsAdmin",
|
||||||
|
blockTitle: "Logistics, Admin & Norms",
|
||||||
|
placeholderText: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "text",
|
||||||
|
id: "facet-codeOfConduct",
|
||||||
|
blockTitle: "Code of Conduct",
|
||||||
|
placeholderText: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("maps decision scope chips to a badge block", () => {
|
it("maps decision scope chips to a badge block", () => {
|
||||||
|
|||||||
Reference in new issue
Block a user