Custom add and create flow polish
This commit is contained in:
@@ -35,6 +35,8 @@ export interface ApplicableScopeFieldProps {
|
||||
* Optional placeholder for the inline input. Defaults to `addLabel`.
|
||||
*/
|
||||
inputPlaceholder?: string;
|
||||
/** When true, scope chips and add affordance are non-interactive. */
|
||||
readOnly?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -46,6 +48,7 @@ function ApplicableScopeFieldComponent({
|
||||
onToggleScope,
|
||||
onAddScope,
|
||||
inputPlaceholder,
|
||||
readOnly = false,
|
||||
className = "",
|
||||
}: ApplicableScopeFieldProps) {
|
||||
const [draft, setDraft] = useState("");
|
||||
@@ -78,13 +81,13 @@ function ApplicableScopeFieldComponent({
|
||||
state={isSelected ? "selected" : "disabled"}
|
||||
palette="default"
|
||||
size="s"
|
||||
disabled={false}
|
||||
onClick={() => onToggleScope(scope)}
|
||||
disabled={readOnly}
|
||||
onClick={() => !readOnly && onToggleScope(scope)}
|
||||
ariaLabel={`${isSelected ? "Deselect" : "Select"} ${scope}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{isAdding ? (
|
||||
{readOnly ? null : isAdding ? (
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
|
||||
@@ -14,8 +14,8 @@ import { memo, useCallback, useRef, useState } from "react";
|
||||
import { useMessages, useTranslation } from "../../../contexts/MessagesContext";
|
||||
import Chip from "../../../components/controls/Chip";
|
||||
import IncrementerBlock from "../../../components/controls/IncrementerBlock";
|
||||
import InlineTextButton from "../../../components/buttons/InlineTextButton";
|
||||
import Upload from "../../../components/controls/Upload";
|
||||
import { getAssetPath } from "../../../../lib/assetUtils";
|
||||
import ApplicableScopeField from "./ApplicableScopeField";
|
||||
import InputLabel from "../../../components/type/InputLabel";
|
||||
import type { CustomMethodCardFieldBlock } from "../../../../lib/create/customMethodCardFieldBlocks";
|
||||
@@ -44,7 +44,9 @@ function CustomMethodCardUploadBlockRow({
|
||||
patch,
|
||||
uploadFileInputAriaLabel,
|
||||
uploadHint,
|
||||
clearFileLabel,
|
||||
clearPendingUploadAriaLabel,
|
||||
clearPendingUploadTooltip,
|
||||
uploadPreviewImageAlt,
|
||||
noFileChosen,
|
||||
}: {
|
||||
block: Extract<CustomMethodCardFieldBlock, { kind: "upload" }>;
|
||||
@@ -52,7 +54,9 @@ function CustomMethodCardUploadBlockRow({
|
||||
patch: (_next: CustomMethodCardFieldBlock[]) => void;
|
||||
uploadFileInputAriaLabel: string;
|
||||
uploadHint: string;
|
||||
clearFileLabel: string;
|
||||
clearPendingUploadAriaLabel: string;
|
||||
clearPendingUploadTooltip: string;
|
||||
uploadPreviewImageAlt: string;
|
||||
noFileChosen: string;
|
||||
}) {
|
||||
const uploadInputRef = useRef<HTMLInputElement | null>(null);
|
||||
@@ -60,8 +64,17 @@ function CustomMethodCardUploadBlockRow({
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const displayName = block.fileName?.trim() ? block.fileName : noFileChosen;
|
||||
const hasAsset = Boolean(block.assetUrl?.trim());
|
||||
const previewAlt = block.fileName?.trim() || block.blockTitle || noFileChosen;
|
||||
const assetUrlTrimmed = block.assetUrl?.trim() ?? "";
|
||||
const hasAsset = assetUrlTrimmed.length > 0;
|
||||
|
||||
const clearUpload = () =>
|
||||
patch(
|
||||
mapBlockById(blocks, block.id, (b) =>
|
||||
b.kind === "upload"
|
||||
? { ...b, fileName: undefined, assetUrl: undefined }
|
||||
: b,
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -76,14 +89,6 @@ function CustomMethodCardUploadBlockRow({
|
||||
{displayName}
|
||||
</p>
|
||||
) : null}
|
||||
{hasAsset ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element -- same-origin upload URL
|
||||
<img
|
||||
src={block.assetUrl!.trim()}
|
||||
alt={previewAlt}
|
||||
className="max-h-[160px] max-w-full rounded-[var(--measures-radius-200,8px)] object-contain"
|
||||
/>
|
||||
) : null}
|
||||
<input
|
||||
ref={uploadInputRef}
|
||||
type="file"
|
||||
@@ -123,13 +128,41 @@ function CustomMethodCardUploadBlockRow({
|
||||
})();
|
||||
}}
|
||||
/>
|
||||
<Upload
|
||||
active={!busy}
|
||||
hintText={busy ? tUpload("uploading") : uploadHint}
|
||||
onClick={() => {
|
||||
if (!busy) uploadInputRef.current?.click();
|
||||
}}
|
||||
/>
|
||||
{hasAsset ? (
|
||||
<div className="relative inline-block max-w-full">
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearUpload}
|
||||
className="absolute right-[8px] top-[8px] z-[1] flex h-[32px] w-[32px] cursor-pointer items-center justify-center rounded-full bg-[var(--color-surface-default-secondary)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface-default-primary)]"
|
||||
aria-label={clearPendingUploadAriaLabel}
|
||||
title={clearPendingUploadTooltip}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- matches ModalHeader close control */}
|
||||
<img
|
||||
src={getAssetPath("assets/Icon_Close.svg")}
|
||||
alt=""
|
||||
className="h-[16px] w-[16px]"
|
||||
style={{
|
||||
filter: "brightness(0) invert(1)",
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- same-origin upload URL */}
|
||||
<img
|
||||
src={assetUrlTrimmed}
|
||||
alt={uploadPreviewImageAlt}
|
||||
className="max-h-[160px] max-w-full rounded-[var(--measures-radius-200,8px)] object-contain"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Upload
|
||||
active={!busy}
|
||||
hintText={busy ? tUpload("uploading") : uploadHint}
|
||||
onClick={() => {
|
||||
if (!busy) uploadInputRef.current?.click();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{errorMessage ? (
|
||||
<p
|
||||
className="font-[family-name:var(--font-body)] text-[length:var(--font-size-body-s)] text-[var(--color-content-default-secondary)]"
|
||||
@@ -138,21 +171,6 @@ function CustomMethodCardUploadBlockRow({
|
||||
{errorMessage}
|
||||
</p>
|
||||
) : null}
|
||||
{block.fileName?.trim() || block.assetUrl?.trim() ? (
|
||||
<InlineTextButton
|
||||
onClick={() =>
|
||||
patch(
|
||||
mapBlockById(blocks, block.id, (b) =>
|
||||
b.kind === "upload"
|
||||
? { ...b, fileName: undefined, assetUrl: undefined }
|
||||
: b,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
{clearFileLabel}
|
||||
</InlineTextButton>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -297,7 +315,13 @@ function CustomMethodCardFieldBlocksSummaryComponent({
|
||||
patch={patch}
|
||||
uploadFileInputAriaLabel={fm.upload.uploadFileInputAriaLabel}
|
||||
uploadHint={fm.upload.uploadHint}
|
||||
clearFileLabel={em.clearFileLabel}
|
||||
clearPendingUploadAriaLabel={
|
||||
fm.upload.clearPendingUploadAriaLabel
|
||||
}
|
||||
clearPendingUploadTooltip={
|
||||
fm.upload.clearPendingUploadTooltip
|
||||
}
|
||||
uploadPreviewImageAlt={fm.upload.uploadPreviewImageAlt}
|
||||
noFileChosen={noFileChosen}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import ContentLockup from "../../../components/type/ContentLockup";
|
||||
import { useMessages } from "../../../contexts/MessagesContext";
|
||||
import type { CustomMethodCardFieldBlock } from "../../../../lib/create/customMethodCardFieldBlocks";
|
||||
import type { CreateFlowState } from "../types";
|
||||
import CustomMethodCardFieldBlocksSummary from "./CustomMethodCardFieldBlocksSummary";
|
||||
@@ -12,12 +14,22 @@ export default function CustomMethodCardModalBody({
|
||||
/** When set, used instead of `blocksById[cardId]` (e.g. final-review draft). */
|
||||
blocksOverride,
|
||||
onFieldBlocksChange,
|
||||
policyMeta,
|
||||
/**
|
||||
* When false, omit {@link ContentLockup} for title/description (Customize mode:
|
||||
* {@link MethodCardCustomizeModalHeader} already edits them). Summary line still shows.
|
||||
* @default true
|
||||
*/
|
||||
showPolicyContentLockupWhenNoBlocks = true,
|
||||
}: {
|
||||
cardId: string;
|
||||
blocksById: CreateFlowState["customMethodCardFieldBlocksById"];
|
||||
blocksOverride?: CustomMethodCardFieldBlock[] | null;
|
||||
onFieldBlocksChange?: (_blocks: CustomMethodCardFieldBlock[]) => void;
|
||||
policyMeta?: { label: string; supportText: string };
|
||||
showPolicyContentLockupWhenNoBlocks?: boolean;
|
||||
}) {
|
||||
const m = useMessages();
|
||||
const blocks = blocksOverride ?? blocksById?.[cardId];
|
||||
if (blocks && blocks.length > 0) {
|
||||
return (
|
||||
@@ -27,5 +39,30 @@ export default function CustomMethodCardModalBody({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const label = policyMeta?.label?.trim() ?? "";
|
||||
const support = policyMeta?.supportText?.trim() ?? "";
|
||||
if (label.length > 0 || support.length > 0) {
|
||||
const noFieldsHint = m.create.customRule.customMethodCardWizard.editModal
|
||||
.noCustomFieldsYet;
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{showPolicyContentLockupWhenNoBlocks ? (
|
||||
<ContentLockup
|
||||
title={label.length > 0 ? label : undefined}
|
||||
description={support.length > 0 ? support : undefined}
|
||||
variant="modal"
|
||||
alignment="left"
|
||||
/>
|
||||
) : null}
|
||||
{noFieldsHint.trim().length > 0 ? (
|
||||
<p className="font-[family-name:var(--font-body)] text-[length:var(--font-size-body-m,15px)] leading-[var(--line-height-body-m,22px)] text-[var(--color-content-default-secondary)]">
|
||||
{noFieldsHint}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <CustomMethodCardPresetEditPlaceholder />;
|
||||
}
|
||||
|
||||
+7
@@ -8,6 +8,7 @@ import {
|
||||
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
||||
import { CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS } from "../../../../../lib/create/customMethodCardWizardConstants";
|
||||
import type { AddCustomFieldType } from "../../../../components/controls/AddCustomField/AddCustomField.types";
|
||||
import type { ModalHeaderMenuItem } from "../../../../components/modals/ModalHeader/ModalHeader.types";
|
||||
import { CustomMethodCardWizardView } from "./CustomMethodCardWizard.view";
|
||||
import type { CustomMethodCardWizardProps } from "./CustomMethodCardWizard.types";
|
||||
|
||||
@@ -21,6 +22,7 @@ const CustomMethodCardWizardContainer = memo<CustomMethodCardWizardProps>(
|
||||
const t = useTranslation("common");
|
||||
const tUpload = useTranslation("create.upload");
|
||||
const w = m.create.customRule.customMethodCardWizard;
|
||||
const menuCopy = m.create.customRule.modalKebabMenu;
|
||||
|
||||
const copy = useMemo(
|
||||
() => ({
|
||||
@@ -228,6 +230,8 @@ const CustomMethodCardWizardContainer = memo<CustomMethodCardWizardProps>(
|
||||
dismiss();
|
||||
}, [dismiss, fieldTypeModal]);
|
||||
|
||||
const kebabMenuItems = useMemo<ModalHeaderMenuItem[]>(() => [], []);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
if (fieldTypeModal) {
|
||||
setFieldTypeModal(null);
|
||||
@@ -416,6 +420,9 @@ const CustomMethodCardWizardContainer = memo<CustomMethodCardWizardProps>(
|
||||
stepper={!fieldTypeModal}
|
||||
draftFieldBlocks={draftFieldBlocks}
|
||||
onDraftFieldBlocksReorder={setDraftFieldBlocks}
|
||||
kebabMoreOptionsAriaLabel={menuCopy.triggerAriaLabel}
|
||||
kebabMenuAriaLabel={menuCopy.menuAriaLabel}
|
||||
kebabMenuItems={kebabMenuItems}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { RefObject } from "react";
|
||||
import type { AddCustomFieldType } from "../../../../components/controls/AddCustomField/AddCustomField.types";
|
||||
import type { ModalHeaderMenuItem } from "../../../../components/modals/ModalHeader/ModalHeader.types";
|
||||
import type { CustomMethodCardFieldBlock } from "../../../../../lib/create/customMethodCardFieldBlocks";
|
||||
|
||||
export interface CustomMethodCardWizardFieldBodiesCopy {
|
||||
@@ -141,4 +142,7 @@ export interface CustomMethodCardWizardViewProps {
|
||||
onBack: () => void;
|
||||
onNext: () => void;
|
||||
stepper: boolean;
|
||||
kebabMoreOptionsAriaLabel: string;
|
||||
kebabMenuAriaLabel: string;
|
||||
kebabMenuItems: ModalHeaderMenuItem[];
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ function CustomMethodCardWizardViewComponent({
|
||||
stepper,
|
||||
draftFieldBlocks,
|
||||
onDraftFieldBlocksReorder,
|
||||
kebabMoreOptionsAriaLabel,
|
||||
kebabMenuAriaLabel,
|
||||
kebabMenuItems,
|
||||
}: CustomMethodCardWizardViewProps) {
|
||||
return (
|
||||
<Create
|
||||
@@ -52,6 +55,9 @@ function CustomMethodCardWizardViewComponent({
|
||||
totalSteps={3}
|
||||
stepper={stepper}
|
||||
backdropVariant="blurredYellow"
|
||||
kebabTriggerAriaLabel={kebabMoreOptionsAriaLabel}
|
||||
kebabMenuAriaLabel={kebabMenuAriaLabel}
|
||||
kebabMenuItems={kebabMenuItems}
|
||||
>
|
||||
{fieldTypeModal ? (
|
||||
<CustomMethodCardWizardFieldBodiesView
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Editable policy title + description for method-card Create modals in Customize mode.
|
||||
* View mode continues to use {@link ContentLockup} via the `Create` modal defaults.
|
||||
*/
|
||||
|
||||
import TextInput from "../../../components/controls/TextInput";
|
||||
import ModalTextAreaField from "./ModalTextAreaField";
|
||||
|
||||
export interface MethodCardCustomizeModalHeaderProps {
|
||||
titleLabel: string;
|
||||
descriptionLabel: string;
|
||||
titleValue: string;
|
||||
descriptionValue: string;
|
||||
onTitleChange: (_value: string) => void;
|
||||
onDescriptionChange: (_value: string) => void;
|
||||
/** @default 3 */
|
||||
descriptionRows?: number;
|
||||
/** When false, only the policy title row is rendered (core values rename). */
|
||||
showDescription?: boolean;
|
||||
}
|
||||
|
||||
export default function MethodCardCustomizeModalHeader({
|
||||
titleLabel,
|
||||
descriptionLabel,
|
||||
titleValue,
|
||||
descriptionValue,
|
||||
onTitleChange,
|
||||
onDescriptionChange,
|
||||
descriptionRows = 3,
|
||||
showDescription = true,
|
||||
}: MethodCardCustomizeModalHeaderProps) {
|
||||
return (
|
||||
<div className="bg-[var(--color-surface-default-primary)] flex shrink-0 flex-col gap-4 px-[24px] py-[12px]">
|
||||
<TextInput
|
||||
label={titleLabel}
|
||||
value={titleValue}
|
||||
onChange={(e) => onTitleChange(e.target.value)}
|
||||
inputSize="medium"
|
||||
/>
|
||||
{showDescription ? (
|
||||
<ModalTextAreaField
|
||||
label={descriptionLabel}
|
||||
value={descriptionValue}
|
||||
onChange={onDescriptionChange}
|
||||
rows={descriptionRows}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { ModalHeaderMenuItem } from "../../../components/modals/ModalHeader/ModalHeader.types";
|
||||
|
||||
export interface CustomRuleModalKebabMenuCopy {
|
||||
items: {
|
||||
customize: string;
|
||||
duplicate: string;
|
||||
remove: string;
|
||||
};
|
||||
saveEdits: string;
|
||||
}
|
||||
|
||||
export interface CustomRuleModalKebabHandlers {
|
||||
showCustomize?: boolean;
|
||||
onCustomize?: () => void;
|
||||
onDuplicate?: () => void;
|
||||
showRemove?: boolean;
|
||||
onRemove?: () => void;
|
||||
}
|
||||
|
||||
export function buildCustomRuleModalKebabMenu(
|
||||
copy: CustomRuleModalKebabMenuCopy,
|
||||
handlers: CustomRuleModalKebabHandlers,
|
||||
): ModalHeaderMenuItem[] {
|
||||
const items: ModalHeaderMenuItem[] = [];
|
||||
if (handlers.showCustomize && handlers.onCustomize) {
|
||||
items.push({
|
||||
id: "customize",
|
||||
label: copy.items.customize,
|
||||
leadingIcon: "custom",
|
||||
onClick: handlers.onCustomize,
|
||||
});
|
||||
}
|
||||
if (handlers.onDuplicate) {
|
||||
items.push({
|
||||
id: "duplicate",
|
||||
label: copy.items.duplicate,
|
||||
leadingIcon: "content_copy",
|
||||
onClick: handlers.onDuplicate,
|
||||
});
|
||||
}
|
||||
if (handlers.showRemove && handlers.onRemove) {
|
||||
items.push({
|
||||
id: "remove",
|
||||
label: copy.items.remove,
|
||||
leadingIcon: "warning",
|
||||
variant: "destructive",
|
||||
onClick: handlers.onRemove,
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
@@ -15,6 +15,8 @@ import type { CommunicationMethodDetailEntry } from "../../types";
|
||||
export interface CommunicationMethodEditFieldsProps {
|
||||
value: CommunicationMethodDetailEntry;
|
||||
onChange: (_next: CommunicationMethodDetailEntry) => void;
|
||||
/** When true, fields are not editable (view mode). */
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const FIELDS: ReadonlyArray<keyof CommunicationMethodDetailEntry> = [
|
||||
@@ -26,6 +28,7 @@ const FIELDS: ReadonlyArray<keyof CommunicationMethodDetailEntry> = [
|
||||
function CommunicationMethodEditFieldsComponent({
|
||||
value,
|
||||
onChange,
|
||||
readOnly = false,
|
||||
}: CommunicationMethodEditFieldsProps) {
|
||||
const m = useMessages();
|
||||
const t = m.create.customRule.communication;
|
||||
@@ -49,6 +52,7 @@ function CommunicationMethodEditFieldsComponent({
|
||||
rows={6}
|
||||
value={value[field]}
|
||||
onChange={(v) => patch(field, v)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -37,11 +37,13 @@ function conflictDetailWithScopeTextarea(
|
||||
export interface ConflictManagementEditFieldsProps {
|
||||
value: ConflictManagementDetailEntry;
|
||||
onChange: (_next: ConflictManagementDetailEntry) => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
function ConflictManagementEditFieldsComponent({
|
||||
value,
|
||||
onChange,
|
||||
readOnly = false,
|
||||
}: ConflictManagementEditFieldsProps) {
|
||||
const m = useMessages();
|
||||
const t = m.create.customRule.conflictManagement;
|
||||
@@ -62,6 +64,7 @@ function ConflictManagementEditFieldsComponent({
|
||||
label={t.sectionHeadings.corePrinciple}
|
||||
value={value.corePrinciple}
|
||||
onChange={(v) => patch("corePrinciple", v)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<ModalTextAreaField
|
||||
label={t.sectionHeadings.applicableScope}
|
||||
@@ -69,16 +72,19 @@ function ConflictManagementEditFieldsComponent({
|
||||
placeholder={t.applicableScopePlaceholder}
|
||||
onChange={(v) => onChange(conflictDetailWithScopeTextarea(value, v))}
|
||||
rows={4}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<ModalTextAreaField
|
||||
label={t.sectionHeadings.processProtocol}
|
||||
value={value.processProtocol}
|
||||
onChange={(v) => patch("processProtocol", v)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<ModalTextAreaField
|
||||
label={t.sectionHeadings.restorationFallbacks}
|
||||
value={value.restorationFallbacks}
|
||||
onChange={(v) => patch("restorationFallbacks", v)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,11 +15,14 @@ import type { CoreValueDetailEntry } from "../../types";
|
||||
export interface CoreValueEditFieldsProps {
|
||||
value: CoreValueDetailEntry;
|
||||
onChange: (_next: CoreValueDetailEntry) => void;
|
||||
/** View mode until the user taps **Customize**. */
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
function CoreValueEditFieldsComponent({
|
||||
value,
|
||||
onChange,
|
||||
readOnly = false,
|
||||
}: CoreValueEditFieldsProps) {
|
||||
const m = useMessages();
|
||||
const t = m.create.customRule.coreValues.detailModal;
|
||||
@@ -41,12 +44,14 @@ function CoreValueEditFieldsComponent({
|
||||
value={value.meaning}
|
||||
onChange={(v) => patch("meaning", v)}
|
||||
rows={4}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<ModalTextAreaField
|
||||
label={t.signalsLabel}
|
||||
value={value.signals}
|
||||
onChange={(v) => patch("signals", v)}
|
||||
rows={4}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { DecisionApproachDetailEntry } from "../../types";
|
||||
export interface DecisionApproachEditFieldsProps {
|
||||
value: DecisionApproachDetailEntry;
|
||||
onChange: (_next: DecisionApproachDetailEntry) => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const CONSENSUS_LEVEL_MIN = 0;
|
||||
@@ -26,6 +27,7 @@ const CONSENSUS_LEVEL_STEP = 5;
|
||||
function DecisionApproachEditFieldsComponent({
|
||||
value,
|
||||
onChange,
|
||||
readOnly = false,
|
||||
}: DecisionApproachEditFieldsProps) {
|
||||
const m = useMessages();
|
||||
const t = m.create.customRule.decisionApproaches;
|
||||
@@ -46,12 +48,14 @@ function DecisionApproachEditFieldsComponent({
|
||||
label={t.sectionHeadings.corePrinciple}
|
||||
value={value.corePrinciple}
|
||||
onChange={(v) => patch("corePrinciple", v)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<ApplicableScopeField
|
||||
label={t.sectionHeadings.applicableScope}
|
||||
addLabel={t.scopeAddButtonLabel}
|
||||
scopes={value.applicableScope}
|
||||
selectedScopes={value.selectedApplicableScope}
|
||||
readOnly={readOnly}
|
||||
onToggleScope={(scope) =>
|
||||
patch(
|
||||
"selectedApplicableScope",
|
||||
@@ -68,6 +72,7 @@ function DecisionApproachEditFieldsComponent({
|
||||
label={t.sectionHeadings.stepByStepInstructions}
|
||||
value={value.stepByStepInstructions}
|
||||
onChange={(v) => patch("stepByStepInstructions", v)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<IncrementerBlock
|
||||
label={t.sectionHeadings.consensusLevel}
|
||||
@@ -79,11 +84,13 @@ function DecisionApproachEditFieldsComponent({
|
||||
formatValue={(v) => `${v}%`}
|
||||
decrementAriaLabel="Decrease consensus level"
|
||||
incrementAriaLabel="Increase consensus level"
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<ModalTextAreaField
|
||||
label={t.sectionHeadings.objectionsDeadlocks}
|
||||
value={value.objectionsDeadlocks}
|
||||
onChange={(v) => patch("objectionsDeadlocks", v)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import type { MembershipMethodDetailEntry } from "../../types";
|
||||
export interface MembershipMethodEditFieldsProps {
|
||||
value: MembershipMethodDetailEntry;
|
||||
onChange: (_next: MembershipMethodDetailEntry) => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const FIELDS: ReadonlyArray<keyof MembershipMethodDetailEntry> = [
|
||||
@@ -26,6 +27,7 @@ const FIELDS: ReadonlyArray<keyof MembershipMethodDetailEntry> = [
|
||||
function MembershipMethodEditFieldsComponent({
|
||||
value,
|
||||
onChange,
|
||||
readOnly = false,
|
||||
}: MembershipMethodEditFieldsProps) {
|
||||
const m = useMessages();
|
||||
const t = m.create.customRule.membership;
|
||||
@@ -49,6 +51,7 @@ function MembershipMethodEditFieldsComponent({
|
||||
rows={6}
|
||||
value={value[field]}
|
||||
onChange={(v) => patch(field, v)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user