QA pass: layout, create-flow, and About books #68

Merged
an.di merged 38 commits from adilallo/fix/CR-129-create-rule-button into main 2026-08-26 15:46:56 +00:00
13 changed files with 88 additions and 60 deletions
Showing only changes of commit 712437edc7 - Show all commits
@@ -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}
@@ -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) => {
@@ -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"
+1 -1
View File
@@ -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,35 +75,33 @@ 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">
<FieldTypeButton
type="text"
label={fieldTypeLabels.text}
onSelect={onSelectFieldType}
/>
<FieldTypeButton
type="badges"
label={fieldTypeLabels.badges}
onSelect={onSelectFieldType}
/>
<FieldTypeButton
type="upload"
label={fieldTypeLabels.upload}
onSelect={onSelectFieldType}
/>
<FieldTypeButton
type="proportion"
label={fieldTypeLabels.proportion}
onSelect={onSelectFieldType}
/>
</div>
<div className={expandedShellClasses}>
<div className="grid w-full grid-cols-4 gap-3">
<FieldTypeButton
type="text"
label={fieldTypeLabels.text}
onSelect={onSelectFieldType}
/>
<FieldTypeButton
type="badges"
label={fieldTypeLabels.badges}
onSelect={onSelectFieldType}
/>
<FieldTypeButton
type="upload"
label={fieldTypeLabels.upload}
onSelect={onSelectFieldType}
/>
<FieldTypeButton
type="proportion"
label={fieldTypeLabels.proportion}
onSelect={onSelectFieldType}
/>
</div>
</div>
);
+1 -1
View File
@@ -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" }));
+2 -2
View File
@@ -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(