QA pass: layout, create-flow, and About books #68
+2
@@ -388,6 +388,7 @@ const CustomMethodCardWizardContainer = memo<CustomMethodCardWizardProps>(
|
||||
|
||||
const handleSelectFieldType = useCallback((ft: AddCustomFieldType) => {
|
||||
setEditingBlockId(null);
|
||||
setAddFieldExpanded(false);
|
||||
resetFieldTypeDrafts();
|
||||
setFieldTypeModal(ft);
|
||||
fieldModalSnapshotRef.current = JSON.stringify({
|
||||
@@ -531,6 +532,7 @@ const CustomMethodCardWizardContainer = memo<CustomMethodCardWizardProps>(
|
||||
});
|
||||
setFieldTypeModal(null);
|
||||
setEditingBlockId(null);
|
||||
setAddFieldExpanded(false);
|
||||
fieldModalSnapshotRef.current = null;
|
||||
}, [
|
||||
badgeBlockTitle,
|
||||
|
||||
@@ -90,7 +90,7 @@ function CustomMethodCardWizardViewComponent({
|
||||
/>
|
||||
) : null}
|
||||
{!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 ? (
|
||||
<CustomMethodCardWizardBlocksList
|
||||
blocks={draftFieldBlocks}
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ function CustomMethodCardWizardBlocksListViewComponent({
|
||||
}: CustomMethodCardWizardBlocksListViewProps) {
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
{blocks.map((block, index) => {
|
||||
|
||||
+3
-5
@@ -53,11 +53,9 @@ function CustomMethodCardWizardFieldBodiesViewComponent({
|
||||
maxLength={CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<InputLabel
|
||||
label={copy.text.placeholderLabel}
|
||||
size="s"
|
||||
palette="default"
|
||||
/>
|
||||
<label className="text-[14px] leading-[20px] font-medium text-[var(--color-content-default-primary)]">
|
||||
{copy.text.placeholderLabel}
|
||||
</label>
|
||||
<TextArea
|
||||
formHeader={false}
|
||||
appearance="embedded"
|
||||
|
||||
@@ -28,7 +28,7 @@ function VerticalComponent({
|
||||
"data-testid": dataTestId,
|
||||
}: VerticalProps) {
|
||||
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 (
|
||||
<button
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { AddCustomFieldProps, AddCustomFieldType } from "./AddCustomField.t
|
||||
|
||||
/**
|
||||
* 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>(
|
||||
({ active, onPressAdd, onSelectFieldType, className = "" }) => {
|
||||
|
||||
@@ -11,7 +11,7 @@ export const ADD_CUSTOM_FIELD_TYPE_ICONS = {
|
||||
} as const satisfies Record<AddCustomFieldType, IconName>;
|
||||
|
||||
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;
|
||||
onPressAdd?: () => 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({
|
||||
active,
|
||||
onPressAdd,
|
||||
@@ -55,17 +46,12 @@ function AddCustomFieldViewComponent({
|
||||
fieldTypeLabels,
|
||||
className,
|
||||
}: AddCustomFieldViewProps) {
|
||||
const shellStyle = {
|
||||
minHeight: ADD_CUSTOM_FIELD_SHELL_MIN_H_PX,
|
||||
} as const;
|
||||
|
||||
if (!active) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onPressAdd}
|
||||
style={shellStyle}
|
||||
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()}
|
||||
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()}
|
||||
>
|
||||
<span className="flex items-center gap-[var(--spacing-scale-006)] rounded-full px-4 py-3">
|
||||
<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(" ")
|
||||
.trim();
|
||||
|
||||
return (
|
||||
<div className={expandedShellClasses} style={shellStyle}>
|
||||
<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="flex w-full flex-row flex-nowrap justify-center gap-3 overflow-x-auto max-sm:justify-start">
|
||||
<div className={expandedShellClasses}>
|
||||
<div className="grid w-full grid-cols-4 gap-3">
|
||||
<FieldTypeButton
|
||||
type="text"
|
||||
label={fieldTypeLabels.text}
|
||||
@@ -119,7 +104,6 @@ function AddCustomFieldViewComponent({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export function CreateView({
|
||||
{headerContent !== undefined ? (
|
||||
<div className="shrink-0">{headerContent}</div>
|
||||
) : 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
|
||||
title={title}
|
||||
description={description}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"dragHandleAriaLabel": "Drag to reorder this field"
|
||||
},
|
||||
"footer": {
|
||||
"finalize": "Finalize"
|
||||
"finalize": "Finalize policy"
|
||||
},
|
||||
"editModal": {
|
||||
"noCustomFieldsYet": "No custom fields yet.",
|
||||
@@ -36,7 +36,7 @@
|
||||
"clearFileLabel": "Clear file"
|
||||
},
|
||||
"addCustomField": {
|
||||
"cta": "Add",
|
||||
"cta": "Add Custom Field",
|
||||
"fieldTypes": {
|
||||
"text": "Text",
|
||||
"badges": "Badges",
|
||||
|
||||
@@ -442,7 +442,7 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Core Principle & Scope" }),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||
|
||||
await waitFor(() => {
|
||||
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: "Finalize" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(latest.customMethodCardMetaById?.signal?.label).toBe(
|
||||
@@ -546,7 +546,7 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
fireEvent.dragStart(rows[0], { dataTransfer });
|
||||
fireEvent.drop(rows[2], { dataTransfer });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
|
||||
@@ -235,7 +235,7 @@ describe("CoreValuesSelectScreen", () => {
|
||||
fireEvent.dragStart(rows[0], { dataTransfer });
|
||||
fireEvent.drop(rows[1], { dataTransfer });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
@@ -288,9 +288,53 @@ describe("CoreValuesSelectScreen", () => {
|
||||
target: { value: "A community-authored value." },
|
||||
});
|
||||
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 () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
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.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(() => {
|
||||
expect(latest.customMethodCardMetaById?.signal?.label).toBe(
|
||||
@@ -883,7 +883,7 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
fireEvent.dragStart(rows[0], { dataTransfer });
|
||||
fireEvent.drop(rows[2], { dataTransfer });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
|
||||
Reference in New Issue
Block a user