feat/add support for setting collapsable fields (array, block, collapsable… (#1057)
Co-authored-by: liorix <liorix@gmail.com> Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
This commit is contained in:
@@ -35,10 +35,19 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
|
||||
### Admin Config
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------- |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
@@ -36,10 +36,18 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. || **`required`** | Require this field to have a value. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
|
||||
### Admin Config
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------- |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
|
||||
### Block configs
|
||||
|
||||
Blocks are defined as separate configs of their own.
|
||||
|
||||
@@ -13,13 +13,21 @@ keywords: row, fields, config, configuration, documentation, Content Management
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- | ----------- |
|
||||
| -------------- | ------------------------------------------------------------------------- |
|
||||
| **`label`** * | A label to render within the header of the collapsible component. |
|
||||
| **`fields`** * | Array of field types to nest within this Collapsible. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
|
||||
### Admin Config
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------- |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
@@ -42,6 +42,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
description,
|
||||
condition,
|
||||
initCollapsed,
|
||||
className,
|
||||
},
|
||||
} = props;
|
||||
@@ -128,14 +129,19 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
moveRow(sourceIndex, destinationIndex);
|
||||
}, [moveRow]);
|
||||
|
||||
const setCollapse = useCallback(async (rowID: string, collapsed: boolean) => {
|
||||
const setCollapse = useCallback(
|
||||
async (rowID: string, collapsed: boolean) => {
|
||||
dispatchRows({ type: 'SET_COLLAPSE', id: rowID, collapsed });
|
||||
|
||||
if (preferencesKey) {
|
||||
const preferencesToSet = await getPreference(preferencesKey) || { fields: {} };
|
||||
let newCollapsedState = preferencesToSet?.fields?.[path]?.collapsed
|
||||
.filter((filterID) => (rows.find((row) => row.id === filterID)))
|
||||
|| [];
|
||||
const preferencesToSet = (await getPreference(preferencesKey)) || { fields: {} };
|
||||
let newCollapsedState: string[] = preferencesToSet?.fields?.[path]?.collapsed;
|
||||
|
||||
if (initCollapsed && typeof newCollapsedState === 'undefined') {
|
||||
newCollapsedState = rows.map((row) => row.id);
|
||||
} else if (typeof newCollapsedState === 'undefined') {
|
||||
newCollapsedState = [];
|
||||
}
|
||||
|
||||
if (!collapsed) {
|
||||
newCollapsedState = newCollapsedState.filter((existingID) => existingID !== rowID);
|
||||
@@ -146,7 +152,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
setPreference(preferencesKey, {
|
||||
...preferencesToSet,
|
||||
fields: {
|
||||
...preferencesToSet?.fields || {},
|
||||
...(preferencesToSet?.fields || {}),
|
||||
[path]: {
|
||||
...preferencesToSet?.fields?.[path],
|
||||
collapsed: newCollapsedState,
|
||||
@@ -154,7 +160,9 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [preferencesKey, path, setPreference, rows, getPreference]);
|
||||
},
|
||||
[preferencesKey, getPreference, path, setPreference, initCollapsed, rows],
|
||||
);
|
||||
|
||||
const toggleCollapseAll = useCallback(async (collapse: boolean) => {
|
||||
dispatchRows({ type: 'SET_ALL_COLLAPSED', collapse });
|
||||
@@ -178,12 +186,12 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
useEffect(() => {
|
||||
const initializeRowState = async () => {
|
||||
const data = formContext.getDataByPath<Row[]>(path);
|
||||
const preferences = await getPreference(preferencesKey) || { fields: {} };
|
||||
dispatchRows({ type: 'SET_ALL', data: data || [], collapsedState: preferences?.fields?.[path]?.collapsed });
|
||||
const preferences = (await getPreference(preferencesKey)) || { fields: {} };
|
||||
dispatchRows({ type: 'SET_ALL', data: data || [], collapsedState: preferences?.fields?.[path]?.collapsed, initCollapsed });
|
||||
};
|
||||
|
||||
initializeRowState();
|
||||
}, [formContext, path, getPreference, preferencesKey]);
|
||||
}, [formContext, path, getPreference, preferencesKey, initCollapsed]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(rows?.length || 0, true);
|
||||
|
||||
@@ -54,6 +54,7 @@ const Index: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
description,
|
||||
condition,
|
||||
initCollapsed,
|
||||
className,
|
||||
},
|
||||
} = props;
|
||||
@@ -141,9 +142,13 @@ const Index: React.FC<Props> = (props) => {
|
||||
|
||||
if (preferencesKey) {
|
||||
const preferencesToSet = await getPreference(preferencesKey) || { fields: {} };
|
||||
let newCollapsedState = preferencesToSet?.fields?.[path]?.collapsed
|
||||
.filter((filterID) => (rows.find((row) => row.id === filterID)))
|
||||
|| [];
|
||||
let newCollapsedState: string[] = preferencesToSet?.fields?.[path]?.collapsed;
|
||||
|
||||
if (initCollapsed && typeof newCollapsedState === 'undefined') {
|
||||
newCollapsedState = rows.map((row) => row.id);
|
||||
} else if (typeof newCollapsedState === 'undefined') {
|
||||
newCollapsedState = [];
|
||||
}
|
||||
|
||||
if (!collapsed) {
|
||||
newCollapsedState = newCollapsedState.filter((existingID) => existingID !== rowID);
|
||||
@@ -162,7 +167,7 @@ const Index: React.FC<Props> = (props) => {
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [preferencesKey, getPreference, path, setPreference, rows]);
|
||||
}, [preferencesKey, getPreference, path, setPreference, initCollapsed, rows]);
|
||||
|
||||
const toggleCollapseAll = useCallback(async (collapse: boolean) => {
|
||||
dispatchRows({ type: 'SET_ALL_COLLAPSED', collapse });
|
||||
@@ -187,12 +192,12 @@ const Index: React.FC<Props> = (props) => {
|
||||
useEffect(() => {
|
||||
const initializeRowState = async () => {
|
||||
const data = formContext.getDataByPath<Row[]>(path);
|
||||
const preferences = await getPreference(preferencesKey) || { fields: {} };
|
||||
dispatchRows({ type: 'SET_ALL', data: data || [], collapsedState: preferences?.fields?.[path]?.collapsed });
|
||||
const preferences = (await getPreference(preferencesKey)) || { fields: {} };
|
||||
dispatchRows({ type: 'SET_ALL', data: data || [], collapsedState: preferences?.fields?.[path]?.collapsed, initCollapsed });
|
||||
};
|
||||
|
||||
initializeRowState();
|
||||
}, [formContext, path, getPreference, preferencesKey]);
|
||||
}, [formContext, path, getPreference, preferencesKey, initCollapsed]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(rows?.length || 0, true);
|
||||
|
||||
@@ -24,13 +24,14 @@ const CollapsibleField: React.FC<Props> = (props) => {
|
||||
admin: {
|
||||
readOnly,
|
||||
className,
|
||||
initCollapsed,
|
||||
description,
|
||||
},
|
||||
} = props;
|
||||
|
||||
const { getPreference, setPreference } = usePreferences();
|
||||
const { preferencesKey } = useDocumentInfo();
|
||||
const [initCollapsed, setInitCollapsed] = useState<boolean>();
|
||||
const [collapsedOnMount, setCollapsedOnMount] = useState<boolean>();
|
||||
const [fieldPreferencesKey] = useState(() => `collapsible-${toKebabCase(label)}`);
|
||||
|
||||
const onToggle = useCallback(async (newCollapsedState: boolean) => {
|
||||
@@ -51,18 +52,18 @@ const CollapsibleField: React.FC<Props> = (props) => {
|
||||
useEffect(() => {
|
||||
const fetchInitialState = async () => {
|
||||
const preferences = await getPreference(preferencesKey);
|
||||
setInitCollapsed(Boolean(preferences?.fields?.[fieldPreferencesKey]?.collapsed));
|
||||
setCollapsedOnMount(Boolean(preferences?.fields?.[fieldPreferencesKey]?.collapsed ?? initCollapsed));
|
||||
};
|
||||
|
||||
fetchInitialState();
|
||||
}, [getPreference, preferencesKey, fieldPreferencesKey]);
|
||||
}, [getPreference, preferencesKey, fieldPreferencesKey, initCollapsed]);
|
||||
|
||||
if (typeof initCollapsed !== 'boolean') return null;
|
||||
if (typeof collapsedOnMount !== 'boolean') return null;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Collapsible
|
||||
initCollapsed={initCollapsed}
|
||||
initCollapsed={collapsedOnMount}
|
||||
className={[
|
||||
'field-type',
|
||||
baseClass,
|
||||
|
||||
@@ -11,6 +11,7 @@ type SET_ALL = {
|
||||
data: { id?: string, blockType?: string }[]
|
||||
collapsedState?: string[]
|
||||
blockType?: string
|
||||
initCollapsed?: boolean
|
||||
}
|
||||
|
||||
type SET_COLLAPSE = {
|
||||
@@ -48,13 +49,13 @@ const reducer = (currentState: Row[], action: Action): Row[] => {
|
||||
|
||||
switch (action.type) {
|
||||
case 'SET_ALL': {
|
||||
const { data, collapsedState } = action;
|
||||
const { data, collapsedState, initCollapsed } = action;
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((dataRow, i) => {
|
||||
const row = {
|
||||
id: dataRow?.id || new ObjectID().toHexString(),
|
||||
collapsed: (collapsedState || []).includes(dataRow?.id),
|
||||
collapsed: Array.isArray(collapsedState) ? collapsedState.includes(dataRow?.id) : initCollapsed,
|
||||
blockType: dataRow?.blockType,
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export const baseAdminFields = joi.object().keys({
|
||||
style: joi.object().unknown(),
|
||||
className: joi.string(),
|
||||
readOnly: joi.boolean().default(false),
|
||||
initCollapsed: joi.boolean().default(false),
|
||||
hidden: joi.boolean().default(false),
|
||||
disabled: joi.boolean().default(false),
|
||||
condition: joi.func(),
|
||||
|
||||
@@ -184,6 +184,9 @@ export type CollapsibleField = Omit<FieldBase, 'name'> & {
|
||||
type: 'collapsible';
|
||||
label: string
|
||||
fields: Field[];
|
||||
admin?: Admin & {
|
||||
initCollapsed?: boolean | false;
|
||||
}
|
||||
}
|
||||
|
||||
export type TabsAdmin = Omit<Admin, 'description'>;
|
||||
@@ -327,6 +330,9 @@ export type ArrayField = FieldBase & {
|
||||
maxRows?: number;
|
||||
labels?: Labels;
|
||||
fields: Field[];
|
||||
admin?: Admin & {
|
||||
initCollapsed?: boolean | false;
|
||||
}
|
||||
}
|
||||
|
||||
export type RadioField = FieldBase & {
|
||||
@@ -352,6 +358,10 @@ export type BlockField = FieldBase & {
|
||||
blocks: Block[];
|
||||
defaultValue?: unknown
|
||||
labels?: Labels
|
||||
admin?: Admin & {
|
||||
initCollapsed?: boolean | false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type PointField = FieldBase & {
|
||||
|
||||
@@ -23,6 +23,20 @@ const ArrayFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'collapsedArray',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'localized',
|
||||
type: 'array',
|
||||
@@ -92,6 +106,11 @@ export const arrayDoc = {
|
||||
text: 'sixth row',
|
||||
},
|
||||
],
|
||||
collapsedArray: [
|
||||
{
|
||||
text: 'initialize collapsed',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default ArrayFields;
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
import type { CollectionConfig } from '../../../../src/collections/config/types';
|
||||
import { Field } from '../../../../src/fields/config/types';
|
||||
|
||||
export const blocksFieldSeedData = [
|
||||
{
|
||||
blockName: 'First block',
|
||||
blockType: 'text',
|
||||
text: 'first block',
|
||||
richText: [],
|
||||
},
|
||||
{
|
||||
blockName: 'Second block',
|
||||
blockType: 'number',
|
||||
number: 342,
|
||||
},
|
||||
{
|
||||
blockName: 'Sub-block demonstration',
|
||||
blockType: 'subBlocks',
|
||||
subBlocks: [
|
||||
{
|
||||
blockName: 'First sub block',
|
||||
blockType: 'number',
|
||||
number: 123,
|
||||
},
|
||||
{
|
||||
blockName: 'Second sub block',
|
||||
blockType: 'text',
|
||||
text: 'second sub block',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const blocksField: Field = {
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
@@ -104,12 +134,21 @@ export const blocksField: Field = {
|
||||
],
|
||||
},
|
||||
],
|
||||
defaultValue: blocksFieldSeedData,
|
||||
};
|
||||
|
||||
const BlockFields: CollectionConfig = {
|
||||
slug: 'block-fields',
|
||||
fields: [
|
||||
blocksField,
|
||||
{
|
||||
...blocksField,
|
||||
name: 'collapsedByDefaultBlocks',
|
||||
localized: true,
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
...blocksField,
|
||||
name: 'localizedBlocks',
|
||||
@@ -118,36 +157,6 @@ const BlockFields: CollectionConfig = {
|
||||
],
|
||||
};
|
||||
|
||||
export const blocksFieldSeedData = [
|
||||
{
|
||||
blockName: 'First block',
|
||||
blockType: 'text',
|
||||
text: 'first block',
|
||||
richText: [],
|
||||
},
|
||||
{
|
||||
blockName: 'Second block',
|
||||
blockType: 'number',
|
||||
number: 342,
|
||||
},
|
||||
{
|
||||
blockName: 'Sub-block demonstration',
|
||||
blockType: 'subBlocks',
|
||||
subBlocks: [
|
||||
{
|
||||
blockName: 'First sub block',
|
||||
blockType: 'number',
|
||||
number: 123,
|
||||
},
|
||||
{
|
||||
blockName: 'Second sub block',
|
||||
blockType: 'text',
|
||||
text: 'second sub block',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const blocksDoc = {
|
||||
blocks: blocksFieldSeedData,
|
||||
localizedBlocks: blocksFieldSeedData,
|
||||
|
||||
@@ -9,6 +9,7 @@ const CollapsibleFields: CollectionConfig = {
|
||||
type: 'collapsible',
|
||||
admin: {
|
||||
description: 'This is a collapsible field.',
|
||||
initCollapsed: false,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
@@ -38,6 +39,40 @@ const CollapsibleFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Collapsible Field - Collapsed by Default',
|
||||
type: 'collapsible',
|
||||
admin: {
|
||||
description: 'This is a collapsible field.',
|
||||
initCollapsed: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'someText',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'textWithinGroup',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'subGroup',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'textWithinSubGroup',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user