Merge pull request #14130 from strapi/custom-fields/cm-input

[Custom fields] Add custom field input component in CM edit view
This commit is contained in:
Rémi de Juvigny 2022-08-19 10:43:35 +02:00 committed by GitHub
commit 752b671487
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 157 additions and 97 deletions

View File

@ -133,7 +133,10 @@
},
"custom_field": {
"type": "customField",
"customField": "plugin::mycustomfields.color"
"customField": "plugin::mycustomfields.color",
"options": {
"format": "hex"
}
}
}
}

View File

@ -1,7 +1,37 @@
import React from 'react';
import { Stack } from '@strapi/design-system/Stack';
import { Field, FieldHint, FieldError, FieldLabel } from '@strapi/design-system/Field';
import { useIntl } from 'react-intl';
const ColorPickerInput = () => {
return <div>TODO: Color Picker Input Component</div>;
const ColorPickerInput = ({
attribute,
description,
error,
hint,
id,
intlLabel,
name,
onChange,
required,
value,
}) => {
const { formatMessage } = useIntl();
return (
<Stack spacing={1}>
<Field
name={name}
id={name}
error={error && formatMessage(error)}
hint={description && formatMessage(description)}
>
<FieldLabel required={required}>{formatMessage(intlLabel)}</FieldLabel>
<input type="color" id={name} name={name} value={value || ''} onChange={onChange} />
<FieldHint />
<FieldError />
</Field>
</Stack>
);
};
export default ColorPickerInput;

View File

@ -5,7 +5,7 @@ import get from 'lodash/get';
import omit from 'lodash/omit';
import take from 'lodash/take';
import isEqual from 'react-fast-compare';
import { GenericInput, NotAllowedInput, useLibrary } from '@strapi/helper-plugin';
import { GenericInput, NotAllowedInput, useLibrary, useCustomFields } from '@strapi/helper-plugin';
import { useContentTypeLayout } from '../../hooks';
import { getFieldName } from '../../utils';
import Wysiwyg from '../Wysiwyg';
@ -39,9 +39,10 @@ function Inputs({
const { fields } = useLibrary();
const { formatMessage } = useIntl();
const { contentType: currentContentTypeLayout } = useContentTypeLayout();
const customFieldsRegistry = useCustomFields();
const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]);
const type = fieldSchema.type;
const { type, customField: customFieldUid } = fieldSchema;
const error = get(formErrors, [keys], null);
const fieldName = useMemo(() => {
@ -164,6 +165,19 @@ function Inputs({
const { label, description, placeholder, visible } = metadatas;
// Memoize the component to avoid remounting it and losing state
const CustomFieldInput = useMemo(() => {
if (customFieldUid) {
const customField = customFieldsRegistry.get(customFieldUid);
const CustomFieldInput = React.lazy(customField.components.Input);
return CustomFieldInput;
}
// Not a custom field, component won't be used
return null;
}, [customFieldUid, customFieldsRegistry]);
if (visible === false) {
return null;
}
@ -217,6 +231,18 @@ function Inputs({
);
}
const customInputs = {
json: InputJSON,
uid: InputUID,
media: fields.media,
wysiwyg: Wysiwyg,
...fields,
};
if (customFieldUid) {
customInputs[customFieldUid] = CustomFieldInput;
}
return (
<GenericInput
attribute={fieldSchema}
@ -229,13 +255,7 @@ function Inputs({
error={error}
labelAction={labelAction}
contentTypeUID={currentContentTypeLayout.uid}
customInputs={{
json: InputJSON,
uid: InputUID,
media: fields.media,
wysiwyg: Wysiwyg,
...fields,
}}
customInputs={customInputs}
multiple={fieldSchema.multiple || false}
name={keys}
onChange={onChange}
@ -243,7 +263,7 @@ function Inputs({
placeholder={placeholder ? { id: placeholder, defaultMessage: placeholder } : null}
required={fieldSchema.required || false}
step={step}
type={inputType}
type={customFieldUid || inputType}
// validations={validations}
value={inputValue}
withDefaultValue={false}

View File

@ -1,7 +1,12 @@
import React, { memo, useCallback, useMemo } from 'react';
import React, { Suspense, memo, useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import get from 'lodash/get';
import { CheckPermissions, useTracking, LinkButton } from '@strapi/helper-plugin';
import {
CheckPermissions,
LoadingIndicatorPage,
useTracking,
LinkButton,
} from '@strapi/helper-plugin';
import { useIntl } from 'react-intl';
import { ContentLayout } from '@strapi/design-system/Layout';
import { Box } from '@strapi/design-system/Box';
@ -131,99 +136,101 @@ const EditView = ({
<ContentLayout>
<Grid gap={4}>
<GridItem col={9} s={12}>
<Stack spacing={6}>
{formattedContentTypeLayout.map((row, index) => {
if (isDynamicZone(row)) {
const {
0: {
0: { name, fieldSchema, metadatas, labelAction },
},
} = row;
<Suspense fallback={<LoadingIndicatorPage />}>
<Stack spacing={6}>
{formattedContentTypeLayout.map((row, index) => {
if (isDynamicZone(row)) {
const {
0: {
0: { name, fieldSchema, metadatas, labelAction },
},
} = row;
return (
<Box key={index}>
<Grid gap={4}>
<GridItem col={12} s={12} xs={12}>
<DynamicZone
name={name}
fieldSchema={fieldSchema}
labelAction={labelAction}
metadatas={metadatas}
/>
</GridItem>
</Grid>
</Box>
);
}
return (
<Box key={index}>
<Grid gap={4}>
<GridItem col={12} s={12} xs={12}>
<DynamicZone
name={name}
fieldSchema={fieldSchema}
labelAction={labelAction}
metadatas={metadatas}
/>
</GridItem>
</Grid>
</Box>
);
}
<Box
key={index}
hasRadius
background="neutral0"
shadow="tableShadow"
paddingLeft={6}
paddingRight={6}
paddingTop={6}
paddingBottom={6}
borderColor="neutral150"
>
<Stack spacing={6}>
{row.map((grid, gridIndex) => {
return (
<Grid gap={4} key={gridIndex}>
{grid.map(
({ fieldSchema, labelAction, metadatas, name, size }) => {
const isComponent = fieldSchema.type === 'component';
return (
<Box
key={index}
hasRadius
background="neutral0"
shadow="tableShadow"
paddingLeft={6}
paddingRight={6}
paddingTop={6}
paddingBottom={6}
borderColor="neutral150"
>
<Stack spacing={6}>
{row.map((grid, gridIndex) => {
return (
<Grid gap={4} key={gridIndex}>
{grid.map(
({ fieldSchema, labelAction, metadatas, name, size }) => {
const isComponent = fieldSchema.type === 'component';
if (isComponent) {
const {
component,
max,
min,
repeatable = false,
required = false,
} = fieldSchema;
if (isComponent) {
const {
component,
max,
min,
repeatable = false,
required = false,
} = fieldSchema;
return (
<GridItem col={size} s={12} xs={12} key={component}>
<FieldComponent
componentUid={component}
labelAction={labelAction}
isRepeatable={repeatable}
intlLabel={{
id: metadatas.label,
defaultMessage: metadatas.label,
}}
max={max}
min={min}
name={name}
required={required}
/>
</GridItem>
);
}
return (
<GridItem col={size} s={12} xs={12} key={component}>
<FieldComponent
componentUid={component}
<GridItem col={size} key={name} s={12} xs={12}>
<Inputs
fieldSchema={fieldSchema}
keys={name}
labelAction={labelAction}
isRepeatable={repeatable}
intlLabel={{
id: metadatas.label,
defaultMessage: metadatas.label,
}}
max={max}
min={min}
name={name}
required={required}
metadatas={metadatas}
/>
</GridItem>
);
}
return (
<GridItem col={size} key={name} s={12} xs={12}>
<Inputs
fieldSchema={fieldSchema}
keys={name}
labelAction={labelAction}
metadatas={metadatas}
/>
</GridItem>
);
}
)}
</Grid>
);
})}
</Stack>
</Box>
);
})}
</Stack>
)}
</Grid>
);
})}
</Stack>
</Box>
);
})}
</Stack>
</Suspense>
</GridItem>
<GridItem col={3} s={12}>
<Stack spacing={2}>