diff --git a/demo/collections/AllFields.js b/demo/collections/AllFields.js index 509ef72179..2b3b1ba2c2 100644 --- a/demo/collections/AllFields.js +++ b/demo/collections/AllFields.js @@ -6,7 +6,7 @@ const AllFields = { }, useAsTitle: 'text', preview: (doc, token) => { - if (doc.text) { + if (doc && doc.text) { return `http://localhost:3000/previewable-posts/${doc.text.value}?preview=true&token=${token}`; } @@ -125,6 +125,12 @@ const AllFields = { }, ], }, + { + type: 'text', + name: 'repeaterText3', + label: 'Repeater Text 3', + readOnly: true, + }, ], }, ], diff --git a/demo/collections/CustomComponents/components/fields/Group/Field/index.js b/demo/collections/CustomComponents/components/fields/Group/Field/index.js new file mode 100644 index 0000000000..9bfd1b906e --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/Group/Field/index.js @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Group } from '../../../../../../../field-types'; + +const CustomGroup = (props) => { + return ( +
+ +
+ ); +}; + +CustomGroup.defaultProps = { + value: '', +}; + +CustomGroup.propTypes = { + value: PropTypes.string, +}; + +export default CustomGroup; diff --git a/demo/collections/CustomComponents/components/fields/NestedGroupCustomField/Field/index.js b/demo/collections/CustomComponents/components/fields/NestedGroupCustomField/Field/index.js new file mode 100644 index 0000000000..fa65c40c53 --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/NestedGroupCustomField/Field/index.js @@ -0,0 +1,5 @@ +import React from 'react'; + +const NestedGroupCustomField = () =>
Nested group custom field
; + +export default NestedGroupCustomField; diff --git a/demo/collections/CustomComponents/components/fields/NestedRepeaterCustomField/Field/index.js b/demo/collections/CustomComponents/components/fields/NestedRepeaterCustomField/Field/index.js new file mode 100644 index 0000000000..aeb55e6e99 --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/NestedRepeaterCustomField/Field/index.js @@ -0,0 +1,5 @@ +import React from 'react'; + +const NestedRepeaterCustomField = () =>
Nested repeater custom field
; + +export default NestedRepeaterCustomField; diff --git a/demo/collections/CustomComponents/components/fields/NestedText1/Field/index.js b/demo/collections/CustomComponents/components/fields/NestedText1/Field/index.js new file mode 100644 index 0000000000..87897b2c9f --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/NestedText1/Field/index.js @@ -0,0 +1,5 @@ +import React from 'react'; + +const NestedText1 = () =>
Nested Text 1
; + +export default NestedText1; diff --git a/demo/collections/CustomComponents/index.js b/demo/collections/CustomComponents/index.js index c3042bde7a..917be07003 100644 --- a/demo/collections/CustomComponents/index.js +++ b/demo/collections/CustomComponents/index.js @@ -35,6 +35,56 @@ module.exports = { filter: path.resolve(__dirname, 'components/fields/Description/Filter/index.js'), }, }, + { + name: 'repeater', + label: 'Repeater', + type: 'repeater', + fields: [ + { + type: 'text', + name: 'nestedRepeaterCustomField', + label: 'Nested Repeater Custom Field', + components: { + field: path.resolve(__dirname, 'components/fields/NestedRepeaterCustomField/Field/index.js'), + }, + }, + ], + }, + { + name: 'group', + label: 'Group', + type: 'group', + components: { + field: path.resolve(__dirname, 'components/fields/Group/Field/index.js'), + }, + fields: [ + { + type: 'text', + name: 'nestedGroupCustomField', + label: 'Nested Group Custom Field', + components: { + field: path.resolve(__dirname, 'components/fields/NestedGroupCustomField/Field/index.js'), + }, + }, + ], + }, + { + type: 'row', + fields: [ + { + name: 'nestedText1', + label: 'Nested Text 1', + type: 'text', + components: { + field: path.resolve(__dirname, 'components/fields/NestedText1/Field/index.js'), + }, + }, { + name: 'nestedText2', + label: 'Nested Text 2', + type: 'text', + }, + ], + }, ], timestamps: true, components: { diff --git a/demo/payload.public.config.js b/demo/payload.public.config.js index 9546451970..5bb3ad133f 100644 --- a/demo/payload.public.config.js +++ b/demo/payload.public.config.js @@ -83,4 +83,7 @@ module.exports = { console.error('global error config handler'); }, }, + webpack: (config) => { + return config; + }, }; diff --git a/field-types.js b/field-types.js index e294728706..d0aa223269 100644 --- a/field-types.js +++ b/field-types.js @@ -1,5 +1 @@ -import Group from './src/client/components/forms/field-types/Group'; - -export default { - Group, -}; +export { default as Group } from './src/client/components/forms/field-types/Group'; diff --git a/package.json b/package.json index 838352ca1c..8eef20eba6 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "react-datepicker": "^2.13.0", "react-document-meta": "^3.0.0-beta.2", "react-dom": "^16.13.0", + "react-hook-form": "^5.7.2", "react-redux": "^7.2.0", "react-router-dom": "^5.1.2", "react-select": "^3.0.8", diff --git a/src/client/components/customComponents.js b/src/client/components/customComponents.js index c6145c967c..ec5b42d434 100644 --- a/src/client/components/customComponents.js +++ b/src/client/components/customComponents.js @@ -12,26 +12,70 @@ function stringify(obj) { return `React.lazy(() => import('${obj}'))`; } -module.exports = function (config) { +function recursivelyAddFieldComponents(fields) { + if (fields) { + return fields.reduce((allFields, field) => { + const subFields = recursivelyAddFieldComponents(field.fields); + + if (!field.name && field.fields) { + return { + ...allFields, + ...subFields, + }; + } + + if (field.components || field.fields) { + const fieldComponents = { + ...(field.components || {}), + }; + + if (field.fields) { + fieldComponents.fields = subFields; + } + + const result = { + ...allFields, + [field.name]: { + ...fieldComponents, + }, + }; + + return result; + } + + return allFields; + }, {}); + } + + return {}; +} + +function customComponents(config) { const allCollectionComponents = config.collections.reduce((components, collection) => { const newComponents = { ...components }; newComponents[collection.slug] = { - fields: {}, + fields: recursivelyAddFieldComponents(collection.fields), ...(collection.components || {}), }; - collection.fields.forEach((field) => { - if (field.components) { - newComponents[collection.slug].fields[field.name] = field.components; - } - }); - return newComponents; }, {}); + const allGlobalComponents = config.globals ? config.globals.reduce((globals, global) => { + const newComponents = { ...globals }; + + newComponents[global.slug] = { + fields: recursivelyAddFieldComponents(global.fields), + ...(global.components || {}), + }; + + return newComponents; + }, {}) : {}; + const string = stringify({ ...(allCollectionComponents || {}), + ...(allGlobalComponents || {}), ...(config.components || {}), }).replace(/\\/g, '\\\\'); @@ -41,4 +85,6 @@ module.exports = function (config) { module.exports = ${string}; `, }; -}; +} + +module.exports = customComponents; diff --git a/src/client/components/elements/Eyebrow/index.scss b/src/client/components/elements/Eyebrow/index.scss index 84bbad9454..8bd61a7b0d 100644 --- a/src/client/components/elements/Eyebrow/index.scss +++ b/src/client/components/elements/Eyebrow/index.scss @@ -4,7 +4,7 @@ @include blur-bg; position: sticky; top: 0; - z-index: 2; + z-index: $z-nav; padding: base(1.5) 0; display: flex; align-items: center; diff --git a/src/client/components/elements/PreviewButton/index.js b/src/client/components/elements/PreviewButton/index.js index 4f1b2a1ceb..7174b26329 100644 --- a/src/client/components/elements/PreviewButton/index.js +++ b/src/client/components/elements/PreviewButton/index.js @@ -8,7 +8,8 @@ const baseClass = 'preview-btn'; const PreviewButton = ({ generatePreviewURL }) => { const { token } = useUser(); - const { fields } = useForm(); + const { getFields } = useForm(); + const fields = getFields(); const previewURL = (generatePreviewURL && typeof generatePreviewURL === 'function') ? generatePreviewURL(fields, token) : null; diff --git a/src/client/components/elements/ReactSelect/index.js b/src/client/components/elements/ReactSelect/index.js index d3782fd293..0c1b7963d5 100644 --- a/src/client/components/elements/ReactSelect/index.js +++ b/src/client/components/elements/ReactSelect/index.js @@ -80,6 +80,7 @@ ReactSelect.defaultProps = { disabled: false, formatValue: null, options: [], + onChange: () => { }, }; ReactSelect.propTypes = { @@ -88,7 +89,7 @@ ReactSelect.propTypes = { PropTypes.array, PropTypes.shape({}), ]), - onChange: PropTypes.func.isRequired, + onChange: PropTypes.func, disabled: PropTypes.bool, showError: PropTypes.bool, formatValue: PropTypes.func, diff --git a/src/client/components/elements/Tooltip/index.scss b/src/client/components/elements/Tooltip/index.scss index 37677540ab..9447ae5222 100644 --- a/src/client/components/elements/Tooltip/index.scss +++ b/src/client/components/elements/Tooltip/index.scss @@ -6,7 +6,7 @@ z-index: 2; bottom: 100%; left: 50%; - transform: translate3d(-50%, -120%, 0); + transform: translate3d(-50%, -20%, 0); padding: base(.2) base(.4); color: white; line-height: base(.75); diff --git a/src/client/components/forms/DraggableSection/index.js b/src/client/components/forms/DraggableSection/index.js index caf486024b..cb1b1c434b 100644 --- a/src/client/components/forms/DraggableSection/index.js +++ b/src/client/components/forms/DraggableSection/index.js @@ -18,15 +18,17 @@ const DraggableSection = (props) => { addRow, removeRow, rowIndex, - parentName, + parentPath, fieldSchema, - defaultValue, + initialData, dispatchCollapsibleStates, collapsibleStates, singularLabel, blockType, fieldTypes, + customComponentsPath, } = props; + const draggableRef = useRef(null); const handleCollapseClick = () => { @@ -75,7 +77,7 @@ const DraggableSection = (props) => { {blockType === 'flexible' && ( ) } @@ -104,14 +106,14 @@ const DraggableSection = (props) => { duration={0} > { - const fieldName = `${parentName}.${rowIndex}${field.name ? `.${field.name}` : ''}`; return ({ ...field, - name: fieldName, - defaultValue: field.name ? defaultValue?.[field.name] : defaultValue, + path: `${parentPath}.${rowIndex}${field.name ? `.${field.name}` : ''}`, }); })} /> @@ -125,25 +127,27 @@ const DraggableSection = (props) => { DraggableSection.defaultProps = { rowCount: null, - defaultValue: null, + initialData: undefined, collapsibleStates: [], singularLabel: '', blockType: '', + customComponentsPath: '', }; DraggableSection.propTypes = { addRow: PropTypes.func.isRequired, removeRow: PropTypes.func.isRequired, rowIndex: PropTypes.number.isRequired, - parentName: PropTypes.string.isRequired, + parentPath: PropTypes.string.isRequired, singularLabel: PropTypes.string, fieldSchema: PropTypes.arrayOf(PropTypes.shape({})).isRequired, rowCount: PropTypes.number, - defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.shape({})]), + initialData: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.shape({})]), dispatchCollapsibleStates: PropTypes.func.isRequired, collapsibleStates: PropTypes.arrayOf(PropTypes.bool), blockType: PropTypes.string, fieldTypes: PropTypes.shape({}).isRequired, + customComponentsPath: PropTypes.string, }; export default DraggableSection; diff --git a/src/client/components/forms/Form/index.js b/src/client/components/forms/Form/index.js index 71fcce8e89..d1f522cfbd 100644 --- a/src/client/components/forms/Form/index.js +++ b/src/client/components/forms/Form/index.js @@ -1,4 +1,4 @@ -import React, { useState, useReducer } from 'react'; +import React, { useState, useReducer, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import PropTypes from 'prop-types'; import { unflatten } from 'flatley'; @@ -16,14 +16,6 @@ import './index.scss'; const baseClass = 'form'; const Form = (props) => { - const [fields, dispatchFields] = useReducer(fieldReducer, {}); - const [submitted, setSubmitted] = useState(false); - const [processing, setProcessing] = useState(false); - const history = useHistory(); - const locale = useLocale(); - const { addStatus } = useStatusList(); - const { refreshToken } = useUser(); - const { onSubmit, ajax, @@ -34,9 +26,45 @@ const Form = (props) => { className, redirect, disableSuccessStatus, + onError, } = props; - const submit = (e) => { + const [fields, dispatchFields] = useReducer(fieldReducer, {}); + const [submitted, setSubmitted] = useState(false); + const [processing, setProcessing] = useState(false); + const history = useHistory(); + const locale = useLocale(); + const { addStatus } = useStatusList(); + const { refreshToken } = useUser(); + + const getFields = useCallback(() => { + return fields; + }, [fields]); + + const getField = useCallback((path) => { + return fields[path]; + }, [fields]); + + const countRows = useCallback((rowName) => { + const namePrefixToRemove = rowName.substring(0, rowName.lastIndexOf('.') + 1); + + const rows = Object.keys(fields).reduce((matchedRows, key) => { + if (key.indexOf(`${rowName}.`) === 0) { + return { + ...matchedRows, + [key.replace(namePrefixToRemove, '')]: fields[key], + }; + } + + return matchedRows; + }, {}); + + const unflattenedRows = unflatten(rows); + const rowCount = unflattenedRows[rowName.replace(namePrefixToRemove, '')]?.length || 0; + return rowCount; + }, [fields]); + + const submit = useCallback(async (e) => { setSubmitted(true); let isValid = true; @@ -69,58 +97,90 @@ const Form = (props) => { setProcessing(true); - // Make the API call from the action - requests[method.toLowerCase()](action, { - body: JSON.stringify(unflatten(data)), - headers: { - 'Content-Type': 'application/json', - }, - }).then( - (res) => { - if (res.status < 400) { - // If prop handleAjaxResponse is passed, pass it the response - if (handleAjaxResponse && typeof handleAjaxResponse === 'function') { - return handleAjaxResponse(res); - } + try { + // Make the API call from the action + const res = await requests[method.toLowerCase()](action, { + body: JSON.stringify(unflatten(data)), + headers: { + 'Content-Type': 'application/json', + }, + }); - if (redirect) { - return history.push(redirect, data); - } + if (res.status < 400) { + // If prop handleAjaxResponse is passed, pass it the response + if (typeof handleAjaxResponse === 'function') handleAjaxResponse(res); - setProcessing(false); + if (redirect) { + return history.push(redirect, data); + } - res.json().then((json) => { - if (!disableSuccessStatus) { - addStatus({ - message: json.message, - type: 'success', - }); - } - }); - } else { - res.json().then((json) => { - setProcessing(false); - json.errors.forEach((err) => { - addStatus({ - message: err.message, - type: 'error', - }); - }); + setProcessing(false); + + const json = await res.json(); + + if (!disableSuccessStatus) { + addStatus({ + message: json.message, + type: 'success', }); } - }, - (error) => { - setProcessing(false); + } else { + throw res; + } + } catch (error) { + setProcessing(false); + + if (typeof onError === 'function') onError(error); + + const json = await error.json(); + + if (json.message) { addStatus({ - message: error.message, + message: json.message, type: 'error', }); - }, - ); - } + } else if (Array.isArray(json.errors)) { + const [fieldErrors, nonFieldErrors] = json.errors.reduce(([fieldErrs, nonFieldErrs], err) => { + return err.field && err.message ? [[...fieldErrs, err], nonFieldErrs] : [fieldErrs, [...nonFieldErrs, err]]; + }, [[], []]); + fieldErrors.forEach((err) => { + dispatchFields({ + valid: false, + errorMessage: err.message, + path: err.field, + value: fields[err.field].value, + }); + }); + + nonFieldErrors.forEach((err) => { + addStatus({ + message: err.message || 'An unknown error occurred.', + type: 'error', + }); + }); + + if (fieldErrors.length > 0 && nonFieldErrors.length === 0) { + addStatus({ + message: 'Please correct the fields below.', + type: 'error', + }); + } + + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + } else { + addStatus({ + message: 'An unknown error occurred.', + type: 'error', + }); + } + } + } // If valid and not AJAX submit as usual - }; + }, [action, addStatus, ajax, disableSuccessStatus, fields, handleAjaxResponse, history, method, onSubmit, redirect, onError]); useThrottledEffect(() => { refreshToken(); @@ -141,31 +201,15 @@ const Form = (props) => { > { - const namePrefixToRemove = rowName.substring(0, rowName.lastIndexOf('.') + 1); - - const rows = Object.keys(fields).reduce((matchedRows, key) => { - if (key.indexOf(`${rowName}.`) === 0) { - return { - ...matchedRows, - [key.replace(namePrefixToRemove, '')]: fields[key], - }; - } - - return matchedRows; - }, {}); - - const unflattenedRows = unflatten(rows); - const rowCount = unflattenedRows[rowName.replace(namePrefixToRemove, '')]?.length || 0; - return rowCount; - }, + countRows, }} > {children} @@ -181,6 +225,7 @@ Form.defaultProps = { method: 'POST', action: '', handleAjaxResponse: null, + onError: null, className: '', disableSuccessStatus: false, }; @@ -192,6 +237,7 @@ Form.propTypes = { method: PropTypes.oneOf(['post', 'POST', 'get', 'GET', 'put', 'PUT', 'delete', 'DELETE']), action: PropTypes.string, handleAjaxResponse: PropTypes.func, + onError: PropTypes.func, children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), PropTypes.node, diff --git a/src/client/components/forms/Form/reducer.js b/src/client/components/forms/Form/reducer.js index e737b8d958..2802093850 100644 --- a/src/client/components/forms/Form/reducer.js +++ b/src/client/components/forms/Form/reducer.js @@ -2,20 +2,20 @@ import { unflatten, flatten } from 'flatley'; import flattenFilters from './flattenFilters'; // -const unflattenRowsFromState = (state, name) => { +const unflattenRowsFromState = (state, path) => { // Take a copy of state const remainingFlattenedState = { ...state }; const rowsFromStateObject = {}; - const namePrefixToRemove = name.substring(0, name.lastIndexOf('.') + 1); + const pathPrefixToRemove = path.substring(0, path.lastIndexOf('.') + 1); // Loop over all keys from state // If the key begins with the name of the parent field, // Add value to rowsFromStateObject and delete it from remaining state Object.keys(state).forEach((key) => { - if (key.indexOf(`${name}.`) === 0) { - rowsFromStateObject[key.replace(namePrefixToRemove, '')] = state[key]; + if (key.indexOf(`${path}.`) === 0) { + rowsFromStateObject[key.replace(pathPrefixToRemove, '')] = state[key]; delete remainingFlattenedState[key]; } }); @@ -23,7 +23,7 @@ const unflattenRowsFromState = (state, name) => { const unflattenedRows = unflatten(rowsFromStateObject); return { - unflattenedRows: unflattenedRows[name.replace(namePrefixToRemove, '')] || [], + unflattenedRows: unflattenedRows[path.replace(pathPrefixToRemove, '')] || [], remainingFlattenedState, }; }; @@ -36,12 +36,12 @@ function fieldReducer(state, action) { }; case 'REMOVE_ROW': { - const { rowIndex, name } = action; - const { unflattenedRows, remainingFlattenedState } = unflattenRowsFromState(state, name); + const { rowIndex, path } = action; + const { unflattenedRows, remainingFlattenedState } = unflattenRowsFromState(state, path); unflattenedRows.splice(rowIndex, 1); - const flattenedRowState = unflattenedRows.length > 0 ? flatten({ [name]: unflattenedRows }, { filters: flattenFilters }) : {}; + const flattenedRowState = unflattenedRows.length > 0 ? flatten({ [path]: unflattenedRows }, { filters: flattenFilters }) : {}; return { ...remainingFlattenedState, @@ -51,23 +51,40 @@ function fieldReducer(state, action) { case 'ADD_ROW': { const { - rowIndex, name, fieldSchema, blockType, + rowIndex, path, fieldSchema, blockType, } = action; - const { unflattenedRows, remainingFlattenedState } = unflattenRowsFromState(state, name); + const { unflattenedRows, remainingFlattenedState } = unflattenRowsFromState(state, path); - // Get names of sub fields + // Get paths of sub fields const subFields = fieldSchema.reduce((acc, field) => { if (field.type === 'flexible' || field.type === 'repeater') { return acc; } - return { - ...acc, - [field.name]: { - value: null, - valid: !field.required, - }, - }; + if (field.name) { + return { + ...acc, + [field.name]: { + value: undefined, + valid: !field.required, + }, + }; + } + + if (field.fields) { + return { + ...acc, + ...(field.fields.reduce((fields, subField) => ({ + ...fields, + [subField.name]: { + value: undefined, + valid: !field.required, + }, + }), {})), + }; + } + + return acc; }, {}); if (blockType) { @@ -82,15 +99,15 @@ function fieldReducer(state, action) { const newState = { ...remainingFlattenedState, - ...(flatten({ [name]: unflattenedRows }, { filters: flattenFilters })), + ...(flatten({ [path]: unflattenedRows }, { filters: flattenFilters })), }; return newState; } case 'MOVE_ROW': { - const { moveFromIndex, moveToIndex, name } = action; - const { unflattenedRows, remainingFlattenedState } = unflattenRowsFromState(state, name); + const { moveFromIndex, moveToIndex, path } = action; + const { unflattenedRows, remainingFlattenedState } = unflattenRowsFromState(state, path); // copy the row to move const copyOfMovingRow = unflattenedRows[moveFromIndex]; @@ -101,25 +118,26 @@ function fieldReducer(state, action) { return { ...remainingFlattenedState, - ...(flatten({ [name]: unflattenedRows }, { filters: flattenFilters })), + ...(flatten({ [path]: unflattenedRows }, { filters: flattenFilters })), }; } case 'REMOVE': { const newState = { ...state }; - delete newState[action.name]; + delete newState[action.path]; return newState; } - default: + default: { return { ...state, - [action.name]: { + [action.path]: { value: action.value, valid: action.valid, errorMessage: action.errorMessage, }, }; + } } } diff --git a/src/client/components/forms/RenderFields/context.js b/src/client/components/forms/RenderFields/context.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/client/components/forms/RenderFields/index.js b/src/client/components/forms/RenderFields/index.js index dd5e0eec32..8a190f2cf0 100644 --- a/src/client/components/forms/RenderFields/index.js +++ b/src/client/components/forms/RenderFields/index.js @@ -1,38 +1,48 @@ -import React from 'react'; +import React, { createContext, useContext } from 'react'; import PropTypes from 'prop-types'; +import RenderCustomComponent from '../../utilities/RenderCustomComponent'; import './index.scss'; +const RenderedFieldContext = createContext({}); + +export const useRenderedFields = () => useContext(RenderedFieldContext); + const RenderFields = ({ - fieldSchema, initialData, customComponents, fieldTypes, filter, + fieldSchema, initialData, customComponentsPath: customComponentsPathFromProps, fieldTypes, filter, }) => { + const { customComponentsPath: customComponentsPathFromContext } = useRenderedFields(); + + const customComponentsPath = customComponentsPathFromProps || customComponentsPathFromContext; + if (fieldSchema) { return ( - <> + {fieldSchema.map((field, i) => { if (field?.hidden !== 'api' && field?.hidden !== true) { if ((filter && typeof filter === 'function' && filter(field)) || !filter) { - let FieldComponent = field?.hidden === 'admin' ? fieldTypes.hidden : fieldTypes[field.type]; + const FieldComponent = field?.hidden === 'admin' ? fieldTypes.hidden : fieldTypes[field.type]; - if (customComponents?.[field.name]?.field) { - FieldComponent = customComponents[field.name].field; - } - - let { defaultValue } = field; + let initialFieldData; if (!field.name) { - defaultValue = initialData; - } else if (initialData[field.name]) { - defaultValue = initialData[field.name]; + initialFieldData = initialData; + } else if (initialData?.[field.name] !== undefined) { + initialFieldData = initialData[field.name]; } if (FieldComponent) { return ( - ); } @@ -56,7 +66,7 @@ const RenderFields = ({ return null; })} - + ); } @@ -65,7 +75,7 @@ const RenderFields = ({ RenderFields.defaultProps = { initialData: {}, - customComponents: {}, + customComponentsPath: '', filter: null, }; @@ -74,9 +84,9 @@ RenderFields.propTypes = { PropTypes.shape({}), ).isRequired, initialData: PropTypes.shape({}), - customComponents: PropTypes.shape({}), + customComponentsPath: PropTypes.string, fieldTypes: PropTypes.shape({ - hidden: PropTypes.func, + hidden: PropTypes.function, }).isRequired, filter: PropTypes.func, }; diff --git a/src/client/components/forms/field-types/Checkbox/index.js b/src/client/components/forms/field-types/Checkbox/index.js index 54f2c23f38..0b60c58276 100644 --- a/src/client/components/forms/field-types/Checkbox/index.js +++ b/src/client/components/forms/field-types/Checkbox/index.js @@ -11,23 +11,29 @@ import './index.scss'; const Checkbox = (props) => { const { name, + path: pathFromProps, required, defaultValue, + initialData, validate, style, width, label, + readOnly, } = props; + const path = pathFromProps || name; + const { value, showError, errorMessage, - onFieldChange, + setValue, formProcessing, } = useFieldType({ - name, + path, required, + initialData, defaultValue, validate, }); @@ -36,6 +42,7 @@ const Checkbox = (props) => { 'field-type', 'checkbox', showError && 'error', + readOnly && 'read-only', ].filter(Boolean).join(' '); return ( @@ -51,11 +58,11 @@ const Checkbox = (props) => { message={errorMessage} /> {label} @@ -66,16 +73,22 @@ const Checkbox = (props) => { Checkbox.defaultProps = { label: null, required: false, + readOnly: false, defaultValue: false, + initialData: false, validate: checkbox, width: undefined, style: {}, + path: '', }; Checkbox.propTypes = { + path: PropTypes.string, name: PropTypes.string.isRequired, + readOnly: PropTypes.bool, required: PropTypes.bool, defaultValue: PropTypes.bool, + initialData: PropTypes.bool, validate: PropTypes.func, width: PropTypes.string, style: PropTypes.shape({}), diff --git a/src/client/components/forms/field-types/DateTime/index.js b/src/client/components/forms/field-types/DateTime/index.js index 932987fa50..5321a2fe6f 100644 --- a/src/client/components/forms/field-types/DateTime/index.js +++ b/src/client/components/forms/field-types/DateTime/index.js @@ -16,24 +16,30 @@ const baseClass = 'date-time-field'; const DateTime = (props) => { const { + path: pathFromProps, name, required, defaultValue, + initialData, validate, style, width, errorMessage, label, + readOnly, } = props; + const path = pathFromProps || name; + const { value, showError, - onFieldChange, + setValue, formProcessing, } = useFieldType({ - name, + path, required, + initialData, defaultValue, validate, }); @@ -43,6 +49,7 @@ const DateTime = (props) => { baseClass, showError && `${baseClass}--has-error`, formProcessing && 'processing', + readOnly && 'read-only', ].filter(Boolean).join(' '); return ( @@ -58,14 +65,14 @@ const DateTime = (props) => { message={errorMessage} />