Align custom policy details with the Figma create modal so the add-field control, copy, and labels match the intended styling.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
@@ -388,6 +388,7 @@ const CustomMethodCardWizardContainer = memo<CustomMethodCardWizardProps>(
|
|||||||
|
|
||||||
const handleSelectFieldType = useCallback((ft: AddCustomFieldType) => {
|
const handleSelectFieldType = useCallback((ft: AddCustomFieldType) => {
|
||||||
setEditingBlockId(null);
|
setEditingBlockId(null);
|
||||||
|
setAddFieldExpanded(false);
|
||||||
resetFieldTypeDrafts();
|
resetFieldTypeDrafts();
|
||||||
setFieldTypeModal(ft);
|
setFieldTypeModal(ft);
|
||||||
fieldModalSnapshotRef.current = JSON.stringify({
|
fieldModalSnapshotRef.current = JSON.stringify({
|
||||||
@@ -531,6 +532,7 @@ const CustomMethodCardWizardContainer = memo<CustomMethodCardWizardProps>(
|
|||||||
});
|
});
|
||||||
setFieldTypeModal(null);
|
setFieldTypeModal(null);
|
||||||
setEditingBlockId(null);
|
setEditingBlockId(null);
|
||||||
|
setAddFieldExpanded(false);
|
||||||
fieldModalSnapshotRef.current = null;
|
fieldModalSnapshotRef.current = null;
|
||||||
}, [
|
}, [
|
||||||
badgeBlockTitle,
|
badgeBlockTitle,
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ function CustomMethodCardWizardViewComponent({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{!fieldTypeModal && wizardStep === 3 ? (
|
{!fieldTypeModal && wizardStep === 3 ? (
|
||||||
<div className="flex w-full flex-col gap-4 pt-1">
|
<div className="flex w-full flex-col gap-6">
|
||||||
{draftFieldBlocks.length > 0 ? (
|
{draftFieldBlocks.length > 0 ? (
|
||||||
<CustomMethodCardWizardBlocksList
|
<CustomMethodCardWizardBlocksList
|
||||||
blocks={draftFieldBlocks}
|
blocks={draftFieldBlocks}
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ function CustomMethodCardWizardBlocksListViewComponent({
|
|||||||
}: CustomMethodCardWizardBlocksListViewProps) {
|
}: CustomMethodCardWizardBlocksListViewProps) {
|
||||||
return (
|
return (
|
||||||
<ul
|
<ul
|
||||||
className="flex list-none flex-col gap-2 p-0 pt-1"
|
className="flex list-none flex-col gap-2 p-0"
|
||||||
aria-label={listLabel}
|
aria-label={listLabel}
|
||||||
>
|
>
|
||||||
{blocks.map((block, index) => {
|
{blocks.map((block, index) => {
|
||||||
|
|||||||
+3
-5
@@ -53,11 +53,9 @@ function CustomMethodCardWizardFieldBodiesViewComponent({
|
|||||||
maxLength={CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS}
|
maxLength={CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<InputLabel
|
<label className="text-[14px] leading-[20px] font-medium text-[var(--color-content-default-primary)]">
|
||||||
label={copy.text.placeholderLabel}
|
{copy.text.placeholderLabel}
|
||||||
size="s"
|
</label>
|
||||||
palette="default"
|
|
||||||
/>
|
|
||||||
<TextArea
|
<TextArea
|
||||||
formHeader={false}
|
formHeader={false}
|
||||||
appearance="embedded"
|
appearance="embedded"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function VerticalComponent({
|
|||||||
"data-testid": dataTestId,
|
"data-testid": dataTestId,
|
||||||
}: VerticalProps) {
|
}: VerticalProps) {
|
||||||
const base =
|
const base =
|
||||||
"box-border flex w-[90px] shrink-0 cursor-pointer flex-col items-center gap-[var(--spacing-scale-008)] rounded-[var(--spacing-scale-004)] border border-solid border-[var(--color-border-default-brand-primary)] bg-transparent px-[var(--spacing-scale-008)] py-[var(--spacing-scale-012)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary)] disabled:cursor-not-allowed disabled:opacity-60";
|
"box-border flex w-full min-w-[90px] cursor-pointer flex-col items-center gap-[var(--spacing-scale-008)] rounded-[var(--spacing-scale-004)] border border-solid border-[var(--color-border-default-brand-primary)] bg-transparent px-[var(--spacing-scale-008)] py-[var(--spacing-scale-012)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary)] disabled:cursor-not-allowed disabled:opacity-60";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { AddCustomFieldProps, AddCustomFieldType } from "./AddCustomField.t
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Figma: "Add Custom Field" control — Community Rule System (`20235:12994`).
|
* Figma: "Add Custom Field" control — Community Rule System (`20235:12994`).
|
||||||
* Collapsed CTA expands to a 2×2 field-type picker (per-type modals deferred).
|
* Collapsed CTA expands to a four-tile field-type picker.
|
||||||
*/
|
*/
|
||||||
const AddCustomFieldContainer = memo<AddCustomFieldProps>(
|
const AddCustomFieldContainer = memo<AddCustomFieldProps>(
|
||||||
({ active, onPressAdd, onSelectFieldType, className = "" }) => {
|
({ active, onPressAdd, onSelectFieldType, className = "" }) => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const ADD_CUSTOM_FIELD_TYPE_ICONS = {
|
|||||||
} as const satisfies Record<AddCustomFieldType, IconName>;
|
} as const satisfies Record<AddCustomFieldType, IconName>;
|
||||||
|
|
||||||
export interface AddCustomFieldProps {
|
export interface AddCustomFieldProps {
|
||||||
/** When true, show the 2×2 field-type grid; when false, show the primary CTA. */
|
/** When true, show the four field-type tiles; when false, show the primary CTA. */
|
||||||
active: boolean;
|
active: boolean;
|
||||||
onPressAdd?: () => void;
|
onPressAdd?: () => void;
|
||||||
onSelectFieldType?: (type: AddCustomFieldType) => void;
|
onSelectFieldType?: (type: AddCustomFieldType) => void;
|
||||||
|
|||||||
@@ -38,15 +38,6 @@ function FieldTypeButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stable block height for collapsed vs expanded so the Create dialog (`top-1/2 -translate-y-1/2`)
|
|
||||||
* does not shrink and re-center when toggling `active`.
|
|
||||||
*
|
|
||||||
* - Collapsed CTA: `py-12` (48+48) + inner row (`py-3` + 20px icon/line) ≈ 140px border-box.
|
|
||||||
* - Expanded: inner `p-4` (32) + Vertical tile (py 12+12, gap 8, 32px icon, 18px label) ≈ 114px — shorter without this floor.
|
|
||||||
*/
|
|
||||||
const ADD_CUSTOM_FIELD_SHELL_MIN_H_PX = 140;
|
|
||||||
|
|
||||||
function AddCustomFieldViewComponent({
|
function AddCustomFieldViewComponent({
|
||||||
active,
|
active,
|
||||||
onPressAdd,
|
onPressAdd,
|
||||||
@@ -55,17 +46,12 @@ function AddCustomFieldViewComponent({
|
|||||||
fieldTypeLabels,
|
fieldTypeLabels,
|
||||||
className,
|
className,
|
||||||
}: AddCustomFieldViewProps) {
|
}: AddCustomFieldViewProps) {
|
||||||
const shellStyle = {
|
|
||||||
minHeight: ADD_CUSTOM_FIELD_SHELL_MIN_H_PX,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
if (!active) {
|
if (!active) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onPressAdd}
|
onClick={onPressAdd}
|
||||||
style={shellStyle}
|
className={`flex h-[88px] w-full shrink-0 cursor-pointer items-center justify-center rounded-[var(--measures-radius-medium,8px)] bg-[var(--color-surface-default-secondary)] px-6 text-medium-label text-[var(--color-content-default-primary)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary)] ${className ?? ""}`.trim()}
|
||||||
className={`flex w-full shrink-0 cursor-pointer items-center justify-center rounded-[var(--measures-radius-medium,8px)] bg-[var(--color-surface-default-secondary)] px-6 py-12 text-medium-label text-[var(--color-content-default-primary)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-invert-primary)] ${className ?? ""}`.trim()}
|
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-[var(--spacing-scale-006)] rounded-full px-4 py-3">
|
<span className="flex items-center gap-[var(--spacing-scale-006)] rounded-full px-4 py-3">
|
||||||
<svg
|
<svg
|
||||||
@@ -89,14 +75,13 @@ function AddCustomFieldViewComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const expandedShellClasses = ["flex w-full shrink-0 flex-col", className ?? ""]
|
const expandedShellClasses = ["flex w-full shrink-0", className ?? ""]
|
||||||
.join(" ")
|
.join(" ")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={expandedShellClasses} style={shellStyle}>
|
<div className={expandedShellClasses}>
|
||||||
<div className="flex w-full flex-col gap-3 rounded-[var(--measures-radius-medium,8px)] bg-[var(--color-surface-default-secondary)] p-4">
|
<div className="grid w-full grid-cols-4 gap-3">
|
||||||
<div className="flex w-full flex-row flex-nowrap justify-center gap-3 overflow-x-auto max-sm:justify-start">
|
|
||||||
<FieldTypeButton
|
<FieldTypeButton
|
||||||
type="text"
|
type="text"
|
||||||
label={fieldTypeLabels.text}
|
label={fieldTypeLabels.text}
|
||||||
@@ -119,7 +104,6 @@ function AddCustomFieldViewComponent({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function CreateView({
|
|||||||
{headerContent !== undefined ? (
|
{headerContent !== undefined ? (
|
||||||
<div className="shrink-0">{headerContent}</div>
|
<div className="shrink-0">{headerContent}</div>
|
||||||
) : title || description ? (
|
) : title || description ? (
|
||||||
<div className="bg-[var(--color-surface-default-primary)] px-[24px] py-[12px] shrink-0">
|
<div className="bg-[var(--color-surface-default-primary)] px-[24px] shrink-0">
|
||||||
<ContentLockup
|
<ContentLockup
|
||||||
title={title}
|
title={title}
|
||||||
description={description}
|
description={description}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"dragHandleAriaLabel": "Drag to reorder this field"
|
"dragHandleAriaLabel": "Drag to reorder this field"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"finalize": "Finalize"
|
"finalize": "Finalize policy"
|
||||||
},
|
},
|
||||||
"editModal": {
|
"editModal": {
|
||||||
"noCustomFieldsYet": "No custom fields yet.",
|
"noCustomFieldsYet": "No custom fields yet.",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"clearFileLabel": "Clear file"
|
"clearFileLabel": "Clear file"
|
||||||
},
|
},
|
||||||
"addCustomField": {
|
"addCustomField": {
|
||||||
"cta": "Add",
|
"cta": "Add Custom Field",
|
||||||
"fieldTypes": {
|
"fieldTypes": {
|
||||||
"text": "Text",
|
"text": "Text",
|
||||||
"badges": "Badges",
|
"badges": "Badges",
|
||||||
|
|||||||
@@ -442,7 +442,7 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
|||||||
expect(
|
expect(
|
||||||
screen.getByRole("button", { name: "Core Principle & Scope" }),
|
screen.getByRole("button", { name: "Core Principle & Scope" }),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(latest.customMethodCardMetaById?.[customId]?.label).toBe(
|
expect(latest.customMethodCardMetaById?.[customId]?.label).toBe(
|
||||||
@@ -493,7 +493,7 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
|||||||
});
|
});
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(latest.customMethodCardMetaById?.signal?.label).toBe(
|
expect(latest.customMethodCardMetaById?.signal?.label).toBe(
|
||||||
@@ -546,7 +546,7 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
|||||||
fireEvent.dragStart(rows[0], { dataTransfer });
|
fireEvent.dragStart(rows[0], { dataTransfer });
|
||||||
fireEvent.drop(rows[2], { dataTransfer });
|
fireEvent.drop(rows[2], { dataTransfer });
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ describe("CoreValuesSelectScreen", () => {
|
|||||||
fireEvent.dragStart(rows[0], { dataTransfer });
|
fireEvent.dragStart(rows[0], { dataTransfer });
|
||||||
fireEvent.drop(rows[1], { dataTransfer });
|
fireEvent.drop(rows[1], { dataTransfer });
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(
|
expect(
|
||||||
@@ -288,9 +288,53 @@ describe("CoreValuesSelectScreen", () => {
|
|||||||
target: { value: "A community-authored value." },
|
target: { value: "A community-authored value." },
|
||||||
});
|
});
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||||
fireEvent.click(await screen.findByRole("button", { name: "Finalize" }));
|
expect(
|
||||||
|
await screen.findByText("Custom policy details"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Add Custom Field" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
fireEvent.click(await screen.findByRole("button", { name: "Finalize policy" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it("collapses the field-type picker after adding a custom field", async () => {
|
||||||
|
renderWithProviders(<CoreValuesSelectScreen />);
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
|
||||||
|
fireEvent.change(await screen.findByPlaceholderText("Policy name"), {
|
||||||
|
target: { value: CUSTOM_LABEL },
|
||||||
|
});
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||||
|
fireEvent.change(
|
||||||
|
await screen.findByPlaceholderText("Policy description"),
|
||||||
|
{ target: { value: "A community-authored value." } },
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||||
|
expect(
|
||||||
|
await screen.findByText("Custom policy details"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
const wizard = screen.getByRole("dialog");
|
||||||
|
fireEvent.click(
|
||||||
|
within(wizard).getByRole("button", { name: "Add Custom Field" }),
|
||||||
|
);
|
||||||
|
fireEvent.click(within(wizard).getByRole("button", { name: "Text" }));
|
||||||
|
fireEvent.change(
|
||||||
|
await screen.findByPlaceholderText("Add your text block title"),
|
||||||
|
{ target: { value: "Policy title" } },
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Add field" }));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await within(wizard).findByRole("button", { name: "Add Custom Field" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
within(wizard).queryByRole("button", { name: "Text" }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
within(wizard).getByRole("button", { name: "Policy title" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("opens the empty custom-policy wizard from Add value", async () => {
|
it("opens the empty custom-policy wizard from Add value", async () => {
|
||||||
renderWithProviders(<CoreValuesSelectScreen />);
|
renderWithProviders(<CoreValuesSelectScreen />);
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
|
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
|
||||||
|
|||||||
@@ -829,7 +829,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
fireEvent.change(nameInput, { target: { value: "Custom Signal header" } });
|
fireEvent.change(nameInput, { target: { value: "Custom Signal header" } });
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(latest.customMethodCardMetaById?.signal?.label).toBe(
|
expect(latest.customMethodCardMetaById?.signal?.label).toBe(
|
||||||
@@ -883,7 +883,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
|||||||
fireEvent.dragStart(rows[0], { dataTransfer });
|
fireEvent.dragStart(rows[0], { dataTransfer });
|
||||||
fireEvent.drop(rows[2], { dataTransfer });
|
fireEvent.drop(rows[2], { dataTransfer });
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
Reference in New Issue
Block a user