feat: simplifies collapsible label API, adds e2e tests
This commit is contained in:
@@ -4,24 +4,27 @@ import { useWatchForm } from '../Form/context';
|
||||
|
||||
const baseClass = 'row-label';
|
||||
|
||||
export const RowLabel: React.FC<Props> = (props) => {
|
||||
export const RowLabel: React.FC<Props> = ({ className, ...rest }) => {
|
||||
return (
|
||||
<span style={{
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
<span
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
className={[
|
||||
baseClass,
|
||||
className,
|
||||
].filter(Boolean).join(' ')}
|
||||
>
|
||||
<RowLabelContent {...props} />
|
||||
<RowLabelContent {...rest} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const RowLabelContent: React.FC<Props> = (props) => {
|
||||
const RowLabelContent: React.FC<Omit<Props, 'className'>> = (props) => {
|
||||
const {
|
||||
path,
|
||||
fallback,
|
||||
label,
|
||||
rowNumber,
|
||||
className,
|
||||
} = props;
|
||||
|
||||
const { getDataByPath, getSiblingData } = useWatchForm();
|
||||
@@ -35,37 +38,18 @@ const RowLabelContent: React.FC<Props> = (props) => {
|
||||
<Label
|
||||
data={data}
|
||||
path={path}
|
||||
fallback={fallback}
|
||||
index={rowNumber}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (label) {
|
||||
return (
|
||||
<span
|
||||
className={[
|
||||
baseClass,
|
||||
className,
|
||||
].filter(Boolean).join(' ')}
|
||||
>
|
||||
{typeof label === 'function' ? label({
|
||||
data,
|
||||
path,
|
||||
fallback,
|
||||
index: rowNumber,
|
||||
}) : label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (fallback) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{fallback}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
return (
|
||||
<React.Fragment>
|
||||
{typeof label === 'function' ? label({
|
||||
data,
|
||||
path,
|
||||
index: rowNumber,
|
||||
}) : label}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { Data } from '../Form/types';
|
||||
|
||||
export type Props = {
|
||||
fallback: string;
|
||||
path: string;
|
||||
label?: RowLabel;
|
||||
rowNumber?: number;
|
||||
@@ -12,8 +11,7 @@ export type Props = {
|
||||
export type RowLabelArgs = {
|
||||
data: Data,
|
||||
path: string,
|
||||
index: number,
|
||||
fallback: string
|
||||
index?: number,
|
||||
}
|
||||
|
||||
export type RowLabelFunction = (args: RowLabelArgs) => string
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useReducer, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useReducer } from 'react';
|
||||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import { useAuth } from '../../../utilities/Auth';
|
||||
import withCondition from '../../withCondition';
|
||||
@@ -50,8 +50,6 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
},
|
||||
} = props;
|
||||
|
||||
const RowLabelFromProps = components?.RowLabel || undefined;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
// Handle labeling for Arrays, Global Arrays, and Blocks
|
||||
@@ -65,6 +63,8 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
const label = props?.label ?? props?.labels?.singular;
|
||||
|
||||
const CustomRowLabel = components?.RowLabel || undefined;
|
||||
|
||||
const { preferencesKey } = useDocumentInfo();
|
||||
const { getPreference } = usePreferences();
|
||||
const { setPreference } = usePreferences();
|
||||
@@ -255,6 +255,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
>
|
||||
{rows.length > 0 && rows.map((row, i) => {
|
||||
const rowNumber = i + 1;
|
||||
const fallbackLabel = `${labels.singular} ${String(rowNumber).padStart(2, '0')}`;
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
@@ -278,8 +279,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
header={(
|
||||
<RowLabel
|
||||
path={`${path}.${i}`}
|
||||
label={RowLabelFromProps}
|
||||
fallback={`${labels.singular} ${String(rowNumber).padStart(2, '0')}`}
|
||||
label={CustomRowLabel || fallbackLabel}
|
||||
rowNumber={rowNumber}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.collapsible-field {
|
||||
margin: 0 0 base(2);
|
||||
|
||||
&__label {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import RenderFields from '../../RenderFields';
|
||||
import withCondition from '../../withCondition';
|
||||
import { Props } from './types';
|
||||
import { Collapsible } from '../../../elements/Collapsible';
|
||||
import toKebabCase from '../../../../../utilities/toKebabCase';
|
||||
import { usePreferences } from '../../../utilities/Preferences';
|
||||
import { DocumentPreferences } from '../../../../../preferences/types';
|
||||
import { useDocumentInfo } from '../../../utilities/DocumentInfo';
|
||||
@@ -28,45 +27,57 @@ const CollapsibleField: React.FC<Props> = (props) => {
|
||||
className,
|
||||
initCollapsed,
|
||||
description,
|
||||
components,
|
||||
},
|
||||
} = props;
|
||||
|
||||
const CollapsibleLabelFromProps = components?.CollapsibleLabel || undefined;
|
||||
|
||||
const { getPreference, setPreference } = usePreferences();
|
||||
const { preferencesKey } = useDocumentInfo();
|
||||
const [collapsedOnMount, setCollapsedOnMount] = useState<boolean>();
|
||||
const [fieldPreferencesKey] = useState(() => `collapsible-${toKebabCase(label)}`);
|
||||
const fieldPreferencesKey = `collapsible-${indexPath.replace(/\./gi, '__')}`;
|
||||
|
||||
const onToggle = useCallback(async (newCollapsedState: boolean) => {
|
||||
const existingPreferences: DocumentPreferences = await getPreference(preferencesKey);
|
||||
|
||||
setPreference(preferencesKey, {
|
||||
...existingPreferences,
|
||||
fields: {
|
||||
...existingPreferences?.fields || {},
|
||||
[fieldPreferencesKey]: {
|
||||
...existingPreferences?.fields?.[fieldPreferencesKey],
|
||||
collapsed: newCollapsedState,
|
||||
...path ? {
|
||||
fields: {
|
||||
...existingPreferences?.fields || {},
|
||||
[path]: {
|
||||
...existingPreferences?.fields?.[path],
|
||||
collapsed: newCollapsedState,
|
||||
},
|
||||
},
|
||||
} : {
|
||||
fields: {
|
||||
...existingPreferences?.fields || {},
|
||||
[fieldPreferencesKey]: {
|
||||
...existingPreferences?.fields?.[fieldPreferencesKey],
|
||||
collapsed: newCollapsedState,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [preferencesKey, fieldPreferencesKey, getPreference, setPreference]);
|
||||
}, [preferencesKey, fieldPreferencesKey, getPreference, setPreference, path]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchInitialState = async () => {
|
||||
const preferences = await getPreference(preferencesKey);
|
||||
setCollapsedOnMount(Boolean(preferences?.fields?.[fieldPreferencesKey]?.collapsed ?? initCollapsed));
|
||||
if (preferences) {
|
||||
const initCollapsedFromPref = path ? preferences?.fields?.[path]?.collapsed : preferences?.fields?.[fieldPreferencesKey]?.collapsed;
|
||||
setCollapsedOnMount(Boolean(initCollapsedFromPref));
|
||||
} else {
|
||||
setCollapsedOnMount(typeof initCollapsed === 'boolean' ? initCollapsed : false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialState();
|
||||
}, [getPreference, preferencesKey, fieldPreferencesKey, initCollapsed]);
|
||||
}, [getPreference, preferencesKey, fieldPreferencesKey, initCollapsed, path]);
|
||||
|
||||
if (typeof collapsedOnMount !== 'boolean') return null;
|
||||
|
||||
return (
|
||||
<div id={fieldPreferencesKey}>
|
||||
<div id={`field-${fieldPreferencesKey}${path ? `-${path.replace(/\./gi, '__')}` : ''}`}>
|
||||
<Collapsible
|
||||
initCollapsed={collapsedOnMount}
|
||||
className={[
|
||||
@@ -77,8 +88,7 @@ const CollapsibleField: React.FC<Props> = (props) => {
|
||||
header={(
|
||||
<RowLabel
|
||||
path={path}
|
||||
fallback={label}
|
||||
label={CollapsibleLabelFromProps}
|
||||
label={label}
|
||||
/>
|
||||
)}
|
||||
onToggle={onToggle}
|
||||
|
||||
@@ -183,14 +183,13 @@ export const row = baseField.keys({
|
||||
});
|
||||
|
||||
export const collapsible = baseField.keys({
|
||||
label: joi.string().required(),
|
||||
label: joi.alternatives().try(
|
||||
joi.string(),
|
||||
componentSchema,
|
||||
),
|
||||
type: joi.string().valid('collapsible').required(),
|
||||
fields: joi.array().items(joi.link('#field')),
|
||||
admin: baseAdminFields.keys({
|
||||
components: baseAdminComponentFields.keys({
|
||||
CollapsibleLabel: componentSchema,
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
admin: baseAdminFields.default(),
|
||||
});
|
||||
|
||||
const tab = baseField.keys({
|
||||
|
||||
@@ -186,15 +186,12 @@ export type RowField = Omit<FieldBase, 'admin' | 'name'> & {
|
||||
fields: Field[];
|
||||
}
|
||||
|
||||
export type CollapsibleField = Omit<FieldBase, 'name'> & {
|
||||
export type CollapsibleField = Omit<FieldBase, 'name' | 'label'> & {
|
||||
type: 'collapsible';
|
||||
label: string
|
||||
label: RowLabel
|
||||
fields: Field[];
|
||||
admin?: Admin & {
|
||||
initCollapsed?: boolean | false;
|
||||
components?: {
|
||||
CollapsibleLabel?: RowLabel
|
||||
} & Admin['components']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ import React from 'react';
|
||||
import { RowLabelComponent } from '../../../../src/admin/components/forms/RowLabel/types';
|
||||
|
||||
export const ArrayRowLabel: RowLabelComponent = (props) => {
|
||||
const { data, fallback } = props;
|
||||
return <div style={{ textTransform: 'uppercase' }}>{data.title || fallback}</div>;
|
||||
const { data } = props;
|
||||
return <div style={{ textTransform: 'uppercase' }}>{data.title || 'Untitled'}</div>;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,6 @@ import React from 'react';
|
||||
import { RowLabelComponent } from '../../../../src/admin/components/forms/RowLabel/types';
|
||||
|
||||
export const CollapsibleLabelComponent: RowLabelComponent = (props) => {
|
||||
const { data, fallback } = props;
|
||||
return <div style={{ textTransform: 'uppercase' }}>{data.componentTitleField || fallback}</div>;
|
||||
const { data } = props;
|
||||
return <div style={{ textTransform: 'uppercase' }}>{data.title || 'Untitled'}</div>;
|
||||
};
|
||||
|
||||
@@ -77,14 +77,11 @@ const CollapsibleFields: CollectionConfig = {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Collapsible Header Function',
|
||||
label: ({ data }) => data.functionTitleField || 'Custom Collapsible Label',
|
||||
type: 'collapsible',
|
||||
admin: {
|
||||
description: 'Collapsible label rendered from a function.',
|
||||
initCollapsed: true,
|
||||
components: {
|
||||
CollapsibleLabel: ({ data }) => data.functionTitleField || 'Untitled',
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
@@ -94,14 +91,10 @@ const CollapsibleFields: CollectionConfig = {
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Collapsible Header Component',
|
||||
label: ({ data }) => data?.componentTitleField || 'Untitled',
|
||||
type: 'collapsible',
|
||||
admin: {
|
||||
description: 'Collapsible label rendered as a react component.',
|
||||
initCollapsed: true,
|
||||
components: {
|
||||
CollapsibleLabel: CollapsibleLabelComponent,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
@@ -110,11 +103,26 @@ const CollapsibleFields: CollectionConfig = {
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Nested Collapsible',
|
||||
label: ({ data }) => data?.nestedTitle || 'Nested Collapsible',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedText',
|
||||
name: 'nestedTitle',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
label: CollapsibleLabelComponent,
|
||||
type: 'collapsible',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -67,32 +67,29 @@ describe('fields', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// describe('fields - collapsible', () => {
|
||||
// let url: AdminUrlUtil;
|
||||
// beforeAll(() => {
|
||||
// url = new AdminUrlUtil(serverURL, collapsibleFieldsSlug);
|
||||
// });
|
||||
describe('fields - collapsible', () => {
|
||||
let url: AdminUrlUtil;
|
||||
beforeAll(() => {
|
||||
url = new AdminUrlUtil(serverURL, collapsibleFieldsSlug);
|
||||
});
|
||||
|
||||
// test('should render CollapsibleLabel using a function', async () => {
|
||||
// await page.goto(url.create);
|
||||
// await page.locator('#field-collapsibleLabelAsFunction >> .array-field__add-button-wrap >> button').click();
|
||||
test('should render CollapsibleLabel using a function', async () => {
|
||||
await page.goto(url.create);
|
||||
await page.locator('#field-collapsible-3__1 >> #field-nestedTitle').fill('custom row label');
|
||||
await wait(100);
|
||||
const customCollapsibleLabel = await page.locator('#field-collapsible-3__1 >> .row-label');
|
||||
await expect(customCollapsibleLabel).toContainText('custom row label');
|
||||
});
|
||||
|
||||
// await page.locator('#field-collapsibleLabelAsFunction__0__title').fill('custom row label');
|
||||
// await wait(100);
|
||||
// const customCollapsibleLabel = await page.locator('#collapsibleLabelAsFunction-row-0 >> .row-label');
|
||||
// await expect(customCollapsibleLabel).toContainText('custom row label');
|
||||
// });
|
||||
|
||||
// test('should render CollapsibleLabel using a component', async () => {
|
||||
// await page.goto(url.create);
|
||||
// await page.locator('#field-collapsibleLabelAsComponent>> .array-field__add-button-wrap >> button').click();
|
||||
|
||||
// await page.locator('#field-collapsibleLabelAsComponent__0__title').fill('custom row label');
|
||||
// await wait(100);
|
||||
// const customCollapsibleLabel = await page.locator('#collapsibleLabelAsComponent-row-0 >> .row-label :text("custom row label")');
|
||||
// await expect(customCollapsibleLabel).toHaveCSS('text-transform', 'uppercase');
|
||||
// });
|
||||
// });
|
||||
test('should render CollapsibleLabel using a component', async () => {
|
||||
const customLabel = 'custom row label as component';
|
||||
await page.goto(url.create);
|
||||
await page.locator('#field-collapsible-4__0 >> #field-title').fill(customLabel);
|
||||
await wait(100);
|
||||
const customCollapsibleLabel = await page.locator(`#field-collapsible-4__0 >> .row-label :text("${customLabel}")`);
|
||||
await expect(customCollapsibleLabel).toHaveCSS('text-transform', 'uppercase');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fields - array', () => {
|
||||
let url: AdminUrlUtil;
|
||||
|
||||
Reference in New Issue
Block a user