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.
This commit is contained in:
Jacob Fletcher
2024-12-16 16:44:19 -05:00
committed by GitHub
parent fa49e04cf8
commit ed44ec0a9c
6 changed files with 367 additions and 177 deletions

View File

@@ -130,12 +130,14 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
}
header={
<div className={`${baseClass}__row-header`}>
<RowLabel
CustomComponent={CustomRowLabel}
label={fallbackLabel}
path={path}
rowNumber={rowIndex}
/>
{isLoading ? null : (
<RowLabel
CustomComponent={CustomRowLabel}
label={fallbackLabel}
path={path}
rowNumber={rowIndex}
/>
)}
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
</div>
}

View File

@@ -140,21 +140,23 @@ export const BlockRow: React.FC<BlocksFieldProps> = ({
: undefined
}
header={
Label || (
<div className={`${baseClass}__block-header`}>
<span className={`${baseClass}__block-number`}>
{String(rowIndex + 1).padStart(2, '0')}
</span>
<Pill
className={`${baseClass}__block-pill ${baseClass}__block-pill-${row.blockType}`}
pillStyle="white"
>
{getTranslation(block.labels.singular, i18n)}
</Pill>
<SectionTitle path={`${path}.blockName`} readOnly={readOnly} />
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
</div>
)
isLoading
? null
: Label || (
<div className={`${baseClass}__block-header`}>
<span className={`${baseClass}__block-number`}>
{String(rowIndex + 1).padStart(2, '0')}
</span>
<Pill
className={`${baseClass}__block-pill ${baseClass}__block-pill-${row.blockType}`}
pillStyle="white"
>
{getTranslation(block.labels.singular, i18n)}
</Pill>
<SectionTitle path={`${path}.blockName`} readOnly={readOnly} />
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
</div>
)
}
isCollapsed={row.collapsed}
key={row.id}

View File

@@ -25,6 +25,7 @@ export const RowLabelProvider: React.FC<Props<unknown>> = ({ children, path, row
const { getDataByPath, getSiblingData } = useWatchForm()
const collapsibleData = getSiblingData(path)
const arrayData = getDataByPath(path)
const data = arrayData || collapsibleData
return <RowLabel.Provider value={{ data, path, rowNumber }}>{children}</RowLabel.Provider>

View File

@@ -8,6 +8,8 @@ import React from 'react'
export const ArrayRowLabel: PayloadClientReactComponent<RowLabelComponent> = () => {
const { data } = useRowLabel<{ title: string }>()
return (
<div style={{ color: 'coral', textTransform: 'uppercase' }}>{data.title || 'Untitled'}</div>
<div id="custom-array-row-label" style={{ color: 'coral', textTransform: 'uppercase' }}>
{data.title || 'Untitled'}
</div>
)
}

View File

@@ -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')
})

View File

@@ -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<T extends boolean = true> {
blocks?:
| T
| {
content?: T | ContentBlockSelect<T>;
number?: T | NumberBlockSelect<T>;
subBlocks?: T | SubBlocksBlockSelect<T>;
tabs?: T | TabsBlockSelect<T>;
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<T>;
number?: T | NumberBlockSelect<T>;
subBlocks?: T | SubBlocksBlockSelect<T>;
tabs?: T | TabsBlockSelect<T>;
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<T>;
localizedNumber?: T | LocalizedNumberBlockSelect<T>;
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
localizedTabs?: T | LocalizedTabsBlockSelect<T>;
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<T>;
localizedNumber?: T | LocalizedNumberBlockSelect<T>;
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
localizedTabs?: T | LocalizedTabsBlockSelect<T>;
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<T>;
localizedNumber?: T | LocalizedNumberBlockSelect<T>;
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
localizedTabs?: T | LocalizedTabsBlockSelect<T>;
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<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "ContentBlock_select".
*/
export interface ContentBlockSelect<T extends boolean = true> {
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<T extends boolean = true> {
number?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "SubBlocksBlock_select".
*/
export interface SubBlocksBlockSelect<T extends boolean = true> {
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<T extends boolean = true> {
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<T extends boolean = true> {
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<T extends boolean = true> {
number?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localizedSubBlocksBlock_select".
*/
export interface LocalizedSubBlocksBlockSelect<T extends boolean = true> {
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<T extends boolean = true> {
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<T extends boolean = true> {
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<T extends boolean = true> {
blocks?:
| T
| {
content?: T | ContentBlockSelect<T>;
number?: T | NumberBlockSelect<T>;
subBlocks?: T | SubBlocksBlockSelect<T>;
tabs?: T | TabsBlockSelect<T>;
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<T extends boolean = true> {
textInRow?: T;
numberInRow?: T;
json?: T;
tab?: T | TabWithNameSelect<T>;
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<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "TabWithName_select".
*/
export interface TabWithNameSelect<T extends boolean = true> {
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".