From ed44ec0a9c2bcc0cae6fa15d090711556207dbd5 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 16 Dec 2024 16:44:19 -0500 Subject: [PATCH] fix(ui): does not render row labels until form state returns (#10002) Extension of #9933. Custom components are returned by form state, meaning that if we don't wait for form state to return before rendering row labels, the default row label component will render in briefly before being swapped by a custom component (if applicable). Using the new `isLoading` prop on array and block rows, we can conditionally render them just as we currently do for the row fields themselves. --- packages/ui/src/fields/Array/ArrayRow.tsx | 14 +- packages/ui/src/fields/Blocks/BlockRow.tsx | 32 +- .../ui/src/forms/RowLabel/Context/index.tsx | 1 + .../collections/Array/LabelComponent.tsx | 4 +- test/fields/collections/Array/e2e.spec.ts | 14 + test/fields/payload-types.ts | 479 ++++++++++++------ 6 files changed, 367 insertions(+), 177 deletions(-) diff --git a/packages/ui/src/fields/Array/ArrayRow.tsx b/packages/ui/src/fields/Array/ArrayRow.tsx index 2dfd4eabf4..e664940ac9 100644 --- a/packages/ui/src/fields/Array/ArrayRow.tsx +++ b/packages/ui/src/fields/Array/ArrayRow.tsx @@ -130,12 +130,14 @@ export const ArrayRow: React.FC = ({ } header={
- + {isLoading ? null : ( + + )} {fieldHasErrors && }
} diff --git a/packages/ui/src/fields/Blocks/BlockRow.tsx b/packages/ui/src/fields/Blocks/BlockRow.tsx index 84f4107fb8..d90271f9be 100644 --- a/packages/ui/src/fields/Blocks/BlockRow.tsx +++ b/packages/ui/src/fields/Blocks/BlockRow.tsx @@ -140,21 +140,23 @@ export const BlockRow: React.FC = ({ : undefined } header={ - Label || ( -
- - {String(rowIndex + 1).padStart(2, '0')} - - - {getTranslation(block.labels.singular, i18n)} - - - {fieldHasErrors && } -
- ) + isLoading + ? null + : Label || ( +
+ + {String(rowIndex + 1).padStart(2, '0')} + + + {getTranslation(block.labels.singular, i18n)} + + + {fieldHasErrors && } +
+ ) } isCollapsed={row.collapsed} key={row.id} diff --git a/packages/ui/src/forms/RowLabel/Context/index.tsx b/packages/ui/src/forms/RowLabel/Context/index.tsx index ab08192cc6..72e9cbd27d 100644 --- a/packages/ui/src/forms/RowLabel/Context/index.tsx +++ b/packages/ui/src/forms/RowLabel/Context/index.tsx @@ -25,6 +25,7 @@ export const RowLabelProvider: React.FC> = ({ children, path, row const { getDataByPath, getSiblingData } = useWatchForm() const collapsibleData = getSiblingData(path) const arrayData = getDataByPath(path) + const data = arrayData || collapsibleData return {children} diff --git a/test/fields/collections/Array/LabelComponent.tsx b/test/fields/collections/Array/LabelComponent.tsx index abbc6c94c2..2473988eab 100644 --- a/test/fields/collections/Array/LabelComponent.tsx +++ b/test/fields/collections/Array/LabelComponent.tsx @@ -8,6 +8,8 @@ import React from 'react' export const ArrayRowLabel: PayloadClientReactComponent = () => { const { data } = useRowLabel<{ title: string }>() return ( -
{data.title || 'Untitled'}
+
+ {data.title || 'Untitled'} +
) } diff --git a/test/fields/collections/Array/e2e.spec.ts b/test/fields/collections/Array/e2e.spec.ts index e9b5945a9b..ff4ab5f396 100644 --- a/test/fields/collections/Array/e2e.spec.ts +++ b/test/fields/collections/Array/e2e.spec.ts @@ -84,11 +84,25 @@ describe('Array', () => { await page.goto(url.create) await page.locator('#field-rowLabelAsComponent >> .array-field__add-row').click() + // ensure the default label does not blink in before form state returns + const defaultRowLabelWasAttached = await page + .waitForSelector('#field-rowLabelAsComponent .array-field__row-header .row-label', { + state: 'attached', + timeout: 100, // A small timeout to catch any transient rendering + }) + .catch(() => false) // If it doesn't appear, this resolves to `false` + + expect(defaultRowLabelWasAttached).toBeFalsy() + + await expect(page.locator('#field-rowLabelAsComponent #custom-array-row-label')).toBeVisible() + await page.locator('#field-rowLabelAsComponent__0__title').fill(label) await wait(100) + const customRowLabel = page.locator( '#rowLabelAsComponent-row-0 >> .array-field__row-header > :text("custom row label")', ) + await expect(customRowLabel).toHaveCSS('text-transform', 'uppercase') }) diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index 40cd06ddaf..33c5319405 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -868,6 +868,25 @@ export interface ConditionalLogic { text: string; toggleField?: boolean | null; fieldWithCondition?: string | null; + customFieldWithField?: string | null; + customFieldWithHOC?: string | null; + customClientFieldWithCondition?: string | null; + customServerFieldWithCondition?: string | null; + conditionalRichText?: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + } | null; userConditional?: string | null; parentGroup?: { enableParentGroupFields?: boolean | null; @@ -2052,42 +2071,257 @@ export interface BlockFieldsSelect { blocks?: | T | { - content?: T | ContentBlockSelect; - number?: T | NumberBlockSelect; - subBlocks?: T | SubBlocksBlockSelect; - tabs?: T | TabsBlockSelect; + content?: + | T + | { + text?: T; + richText?: T; + id?: T; + blockName?: T; + }; + number?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + subBlocks?: + | T + | { + subBlocks?: + | T + | { + text?: + | T + | { + text?: T; + id?: T; + blockName?: T; + }; + number?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + }; + id?: T; + blockName?: T; + }; + tabs?: + | T + | { + textInCollapsible?: T; + textInRow?: T; + id?: T; + blockName?: T; + }; }; duplicate?: | T | { - content?: T | ContentBlockSelect; - number?: T | NumberBlockSelect; - subBlocks?: T | SubBlocksBlockSelect; - tabs?: T | TabsBlockSelect; + content?: + | T + | { + text?: T; + richText?: T; + id?: T; + blockName?: T; + }; + number?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + subBlocks?: + | T + | { + subBlocks?: + | T + | { + text?: + | T + | { + text?: T; + id?: T; + blockName?: T; + }; + number?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + }; + id?: T; + blockName?: T; + }; + tabs?: + | T + | { + textInCollapsible?: T; + textInRow?: T; + id?: T; + blockName?: T; + }; }; collapsedByDefaultBlocks?: | T | { - localizedContent?: T | LocalizedContentBlockSelect; - localizedNumber?: T | LocalizedNumberBlockSelect; - localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect; - localizedTabs?: T | LocalizedTabsBlockSelect; + localizedContent?: + | T + | { + text?: T; + richText?: T; + id?: T; + blockName?: T; + }; + localizedNumber?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + localizedSubBlocks?: + | T + | { + subBlocks?: + | T + | { + text?: + | T + | { + text?: T; + id?: T; + blockName?: T; + }; + number?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + }; + id?: T; + blockName?: T; + }; + localizedTabs?: + | T + | { + textInCollapsible?: T; + textInRow?: T; + id?: T; + blockName?: T; + }; }; disableSort?: | T | { - localizedContent?: T | LocalizedContentBlockSelect; - localizedNumber?: T | LocalizedNumberBlockSelect; - localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect; - localizedTabs?: T | LocalizedTabsBlockSelect; + localizedContent?: + | T + | { + text?: T; + richText?: T; + id?: T; + blockName?: T; + }; + localizedNumber?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + localizedSubBlocks?: + | T + | { + subBlocks?: + | T + | { + text?: + | T + | { + text?: T; + id?: T; + blockName?: T; + }; + number?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + }; + id?: T; + blockName?: T; + }; + localizedTabs?: + | T + | { + textInCollapsible?: T; + textInRow?: T; + id?: T; + blockName?: T; + }; }; localizedBlocks?: | T | { - localizedContent?: T | LocalizedContentBlockSelect; - localizedNumber?: T | LocalizedNumberBlockSelect; - localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect; - localizedTabs?: T | LocalizedTabsBlockSelect; + localizedContent?: + | T + | { + text?: T; + richText?: T; + id?: T; + blockName?: T; + }; + localizedNumber?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + localizedSubBlocks?: + | T + | { + subBlocks?: + | T + | { + text?: + | T + | { + text?: T; + id?: T; + blockName?: T; + }; + number?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + }; + id?: T; + blockName?: T; + }; + localizedTabs?: + | T + | { + textInCollapsible?: T; + textInRow?: T; + id?: T; + blockName?: T; + }; }; i18nBlocks?: | T @@ -2225,116 +2459,6 @@ export interface BlockFieldsSelect { updatedAt?: T; createdAt?: T; } -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "ContentBlock_select". - */ -export interface ContentBlockSelect { - text?: T; - richText?: T; - id?: T; - blockName?: T; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "NumberBlock_select". - */ -export interface NumberBlockSelect { - number?: T; - id?: T; - blockName?: T; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "SubBlocksBlock_select". - */ -export interface SubBlocksBlockSelect { - subBlocks?: - | T - | { - text?: - | T - | { - text?: T; - id?: T; - blockName?: T; - }; - number?: - | T - | { - number?: T; - id?: T; - blockName?: T; - }; - }; - id?: T; - blockName?: T; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "TabsBlock_select". - */ -export interface TabsBlockSelect { - textInCollapsible?: T; - textInRow?: T; - id?: T; - blockName?: T; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "localizedContentBlock_select". - */ -export interface LocalizedContentBlockSelect { - text?: T; - richText?: T; - id?: T; - blockName?: T; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "localizedNumberBlock_select". - */ -export interface LocalizedNumberBlockSelect { - number?: T; - id?: T; - blockName?: T; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "localizedSubBlocksBlock_select". - */ -export interface LocalizedSubBlocksBlockSelect { - subBlocks?: - | T - | { - text?: - | T - | { - text?: T; - id?: T; - blockName?: T; - }; - number?: - | T - | { - number?: T; - id?: T; - blockName?: T; - }; - }; - id?: T; - blockName?: T; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "localizedTabsBlock_select". - */ -export interface LocalizedTabsBlockSelect { - textInCollapsible?: T; - textInRow?: T; - id?: T; - blockName?: T; -} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "checkbox-fields_select". @@ -2404,6 +2528,11 @@ export interface ConditionalLogicSelect { text?: T; toggleField?: T; fieldWithCondition?: T; + customFieldWithField?: T; + customFieldWithHOC?: T; + customClientFieldWithCondition?: T; + customServerFieldWithCondition?: T; + conditionalRichText?: T; userConditional?: T; parentGroup?: | T @@ -2892,10 +3021,53 @@ export interface TabsFieldsSelect { blocks?: | T | { - content?: T | ContentBlockSelect; - number?: T | NumberBlockSelect; - subBlocks?: T | SubBlocksBlockSelect; - tabs?: T | TabsBlockSelect; + content?: + | T + | { + text?: T; + richText?: T; + id?: T; + blockName?: T; + }; + number?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + subBlocks?: + | T + | { + subBlocks?: + | T + | { + text?: + | T + | { + text?: T; + id?: T; + blockName?: T; + }; + number?: + | T + | { + number?: T; + id?: T; + blockName?: T; + }; + }; + id?: T; + blockName?: T; + }; + tabs?: + | T + | { + textInCollapsible?: T; + textInRow?: T; + id?: T; + blockName?: T; + }; }; group?: | T @@ -2905,7 +3077,24 @@ export interface TabsFieldsSelect { textInRow?: T; numberInRow?: T; json?: T; - tab?: T | TabWithNameSelect; + tab?: + | T + | { + array?: + | T + | { + text?: T; + id?: T; + }; + text?: T; + defaultValue?: T; + arrayInRow?: + | T + | { + textInArrayInRow?: T; + id?: T; + }; + }; namedTabWithDefaultValue?: | T | { @@ -2955,26 +3144,6 @@ export interface TabsFieldsSelect { updatedAt?: T; createdAt?: T; } -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "TabWithName_select". - */ -export interface TabWithNameSelect { - array?: - | T - | { - text?: T; - id?: T; - }; - text?: T; - defaultValue?: T; - arrayInRow?: - | T - | { - textInArrayInRow?: T; - id?: T; - }; -} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "text-fields_select".