diff --git a/examples/getstarted/components/default/openingtimes.json b/examples/getstarted/components/default/openingtimes.json index 410222eb44..82277c19c7 100755 --- a/examples/getstarted/components/default/openingtimes.json +++ b/examples/getstarted/components/default/openingtimes.json @@ -11,6 +11,11 @@ }, "time": { "type": "string" + }, + "dish": { + "component": "default.dish", + "type": "component", + "repeatable": true } } } diff --git a/packages/strapi-plugin-content-manager/admin/src/components/FieldComponent/index.js b/packages/strapi-plugin-content-manager/admin/src/components/FieldComponent/index.js index 3bb24b3bc6..a7ba596562 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/FieldComponent/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/FieldComponent/index.js @@ -6,20 +6,14 @@ import pluginId from '../../pluginId'; import useDataManager from '../../hooks/useDataManager'; import useEditView from '../../hooks/useEditView'; import ComponentInitializer from '../ComponentInitializer'; -import AddFieldButton from './AddFieldButton'; -import EmptyComponent from './EmptyComponent'; +import NonRepeatableComponent from '../NonRepeatableComponent'; +import RepeatableComponent from '../RepeatableComponent'; import Label from './Label'; - import Reset from './ResetComponent'; import Wrapper from './Wrapper'; -import NonRepeatableComponent from '../NonRepeatableComponent'; const FieldComponent = ({ componentUid, isRepeatable, label, name }) => { - const { - addRepeatableComponentToField, - modifiedData, - removeComponentFromField, - } = useDataManager(); + const { modifiedData, removeComponentFromField } = useDataManager(); const { allLayoutData } = useEditView(); const componentValue = get(modifiedData, name, null); const componentValueLength = size(componentValue); @@ -31,7 +25,6 @@ const FieldComponent = ({ componentUid, isRepeatable, label, name }) => { {} ); const displayedFields = get(currentComponentSchema, ['layouts', 'edit'], []); - console.log({ componentValue }); return ( @@ -61,27 +54,14 @@ const FieldComponent = ({ componentUid, isRepeatable, label, name }) => { /> )} {isRepeatable && ( -
- {componentValueLength === 0 && ( - - - {msg =>

{msg}

} -
-
- )} - { - // TODO min max validations - // TODO add componentUID - addRepeatableComponentToField(name); - }} - > - - - -
+ )}
); diff --git a/packages/strapi-plugin-content-manager/admin/src/components/NonRepeatableComponent/index.js b/packages/strapi-plugin-content-manager/admin/src/components/NonRepeatableComponent/index.js index f6fb7ff2fa..5aaef97b1f 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/NonRepeatableComponent/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/NonRepeatableComponent/index.js @@ -28,9 +28,10 @@ const NonRepeatableComponent = ({ fields, name, schema }) => { return ( ); } diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/AddFieldButton.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/AddFieldButton.js new file mode 100644 index 0000000000..20e7067684 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/AddFieldButton.js @@ -0,0 +1,39 @@ +import styled, { css } from 'styled-components'; + +const Button = styled.button` + width: 100%; + height: 37px; + margin-bottom: 27px; + text-align: center; + border: 1px solid rgba(227, 233, 243, 0.75); + border-top: 1px solid + ${({ doesPreviousFieldContainErrorsAndIsClosed }) => + doesPreviousFieldContainErrorsAndIsClosed + ? '#FFA784' + : 'rgba(227, 233, 243, 0.75)'}; + + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + ${({ withBorderRadius }) => { + if (withBorderRadius) { + return css` + border-radius: 2px; + `; + } + }} + + color: #007eff; + font-size: 12px; + font-weight: 700; + -webkit-font-smoothing: antialiased; + line-height: 37px; + cursor: pointer; + text-transform: uppercase; + letter-spacing: 0.5px; + background-color: #fff; + > i { + margin-right: 10px; + } +`; + +export default Button; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/Banner.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/Banner.js new file mode 100644 index 0000000000..fd39d63127 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/Banner.js @@ -0,0 +1,54 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import PropTypes from 'prop-types'; +import { Grab } from '@buffetjs/icons'; +import pluginId from '../../pluginId'; +import BannerWrapper from './BannerWrapper'; +import CarretTop from './CarretTop'; + +const Banner = ({ displayedValue, isOpen, onClickToggle }) => { + return ( + + + + + + + {msg => { + return {displayedValue || msg}; + }} + +
+ + + + + + +
+
+ ); +}; + +Banner.defaultProps = { + displayedValue: null, + isOpen: false, +}; + +Banner.propTypes = { + displayedValue: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.object, + ]), + isOpen: PropTypes.bool, + onClickToggle: PropTypes.func.isRequired, +}; + +export default Banner; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/BannerWrapper.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/BannerWrapper.js new file mode 100644 index 0000000000..c6a3a9f808 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/BannerWrapper.js @@ -0,0 +1,106 @@ +import styled from 'styled-components'; + +const BannerWrapper = styled.button` + display: flex; + height: 36px; + width: 100%; + padding: 0 15px; + border: 1px solid + ${({ hasErrors, isOpen }) => { + if (hasErrors) { + return '#FFA784'; + } else if (isOpen) { + return '#AED4FB'; + } else { + return 'rgba(227, 233, 243, 0.75)'; + } + }}; + + ${({ isFirst }) => { + if (isFirst) { + return ` + border-top-right-radius: 2px; + border-top-left-radius: 2px; + `; + } + }} + border-bottom: 0; + line-height: 36px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + + background-color: ${({ hasErrors, isOpen }) => { + if (hasErrors && isOpen) { + return '#FFE9E0'; + } else if (isOpen) { + return '#E6F0FB'; + } else { + return '#ffffff'; + } + }}; + + ${({ hasErrors, isOpen }) => { + if (hasErrors) { + return ` + color: #f64d0a; + font-weight: 600; + `; + } + if (isOpen) { + return ` + color: #007eff; + font-weight: 600; + `; + } + }} + + ${({ isOpen }) => { + if (isOpen) { + return ` + &.trash-icon i { + color: #007eff; + } + `; + } + }} + span, div, button { + line-height: 34px; + } + + .img-wrapper { + width: 19px; + height: 19px; + align-self: center; + margin-right: 19px; + border-radius: 50%; + background-color: ${({ hasErrors, isOpen }) => { + if (hasErrors) { + return '#FAA684'; + } else if (isOpen) { + return '#AED4FB'; + } else { + return '#F3F4F4'; + } + }} + text-align: center; + line-height: 19px; + + ${({ isOpen }) => !isOpen && 'transform: rotate(180deg)'} + } + + .cta-wrapper { + margin-left: auto; + > button { + padding: 0; + } + + .grab { + cursor: move; + } + } + + webkit-font-smoothing: antialiased; +`; + +export default BannerWrapper; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/CarretTop.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/CarretTop.js new file mode 100644 index 0000000000..e7b6708382 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/CarretTop.js @@ -0,0 +1,15 @@ +import React from 'react'; + +const CarretTop = () => { + return ( + + + + ); +}; + +export default CarretTop; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/DraggedItem.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/DraggedItem.js new file mode 100644 index 0000000000..e39084536c --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/DraggedItem.js @@ -0,0 +1,99 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { get } from 'lodash'; +import { Collapse } from 'reactstrap'; +import useDataManager from '../../hooks/useDataManager'; +import Inputs from '../Inputs'; +import FieldComponent from '../FieldComponent'; +import Banner from './Banner'; +import FormWrapper from './FormWrapper'; + +const DraggedItem = ({ + componentFieldName, + fields, + isOpen, + onClickToggle, + schema, +}) => { + const { modifiedData } = useDataManager(); + const mainField = get(schema, ['settings', 'mainField'], 'id'); + const displayedValue = get( + modifiedData, + [...componentFieldName.split('.'), mainField], + null + ); + const getField = fieldName => + get(schema, ['schema', 'attributes', fieldName], {}); + const getMeta = fieldName => + get(schema, ['metadatas', fieldName, 'edit'], {}); + + console.log({ fields }); + + return ( + <> + + + + {fields.map((fieldRow, key) => { + return ( +
+ {fieldRow.map(field => { + const currentField = getField(field.name); + const isComponent = + get(currentField, 'type', '') === 'component'; + const keys = `${componentFieldName}.${field.name}`; + + if (isComponent) { + const componentUid = currentField.component; + const metas = getMeta(field.name); + console.log({ componentUid, currentField }); + return ( + + ); + } + + return ( +
+ {}} + /> +
+ ); + })} +
+ ); + })} +
+
+ + ); +}; + +DraggedItem.defaultProps = { + fields: [], + isOpen: false, +}; + +DraggedItem.propTypes = { + componentFieldName: PropTypes.string.isRequired, + fields: PropTypes.array, + isOpen: PropTypes.bool, + onClickToggle: PropTypes.func.isRequired, + schema: PropTypes.object.isRequired, +}; + +export default DraggedItem; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/EmptyComponent.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/EmptyComponent.js new file mode 100644 index 0000000000..d2f941a64d --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/EmptyComponent.js @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +const EmptyComponent = styled.div` + height: 72px; + border: 1px solid rgba(227, 233, 243, 0.75); + border-top-left-radius: 2px; + border-top-right-radius: 2px; + border-bottom: 0; + line-height: 73px; + text-align: center; + background-color: #fff; + + > p { + color: #9ea7b8; + font-size: 13px; + font-weight: 500; + } +`; + +export default EmptyComponent; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/FormWrapper.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/FormWrapper.js new file mode 100644 index 0000000000..7c18665cd5 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/FormWrapper.js @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +const FormWrapper = styled.div` + padding-top: 27px; + padding-left: 20px; + padding-right: 20px; + padding-bottom: 8px; + border-top: 1px solid + ${({ hasErrors, isOpen }) => { + if (hasErrors) { + return '#ffa784'; + } else if (isOpen) { + return '#AED4FB'; + } else { + return 'rgba(227, 233, 243, 0.75)'; + } + }}; +`; + +export default FormWrapper; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/index.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/index.js new file mode 100644 index 0000000000..7334baf8b1 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/index.js @@ -0,0 +1,96 @@ +import React, { useReducer } from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +// import { get } from 'lodash'; +import pluginId from '../../pluginId'; +import useDataManager from '../../hooks/useDataManager'; +import Button from './AddFieldButton'; +import DraggedItem from './DraggedItem'; +import EmptyComponent from './EmptyComponent'; +import init from './init'; +import reducer, { initialState } from './reducer'; + +const RepeatableComponent = ({ + componentValue, + componentValueLength, + // componentUid, + fields, + name, + schema, +}) => { + const { + addRepeatableComponentToField, + // modifiedData, + // removeComponentFromField, + } = useDataManager(); + const [state, dispatch] = useReducer(reducer, initialState, () => + init(initialState, componentValue) + ); + const { collapses } = state.toJS(); + + console.log({ state: state.toJS(), fields, schema }); + + return ( +
+ {componentValueLength === 0 && ( + + + {msg =>

{msg}

} +
+
+ )} + {componentValueLength > 0 && + componentValue.map((data, index) => { + const componentFieldName = `${name}.${index}`; + console.log({ componentFieldName }); + + return ( + { + dispatch({ + type: 'TOGGLE_COLLAPSE', + index, + }); + }} + schema={schema} + componentFieldName={componentFieldName} + /> + ); + })} + +
+ ); +}; + +RepeatableComponent.defaultProps = { + componentValue: null, + componentValueLength: 0, + fields: [], +}; + +RepeatableComponent.propTypes = { + componentValue: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), + componentValueLength: PropTypes.number, + fields: PropTypes.array, + name: PropTypes.string.isRequired, + schema: PropTypes.object.isRequired, +}; + +export default RepeatableComponent; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/init.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/init.js new file mode 100644 index 0000000000..afa9c7637f --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/init.js @@ -0,0 +1,20 @@ +import { fromJS } from 'immutable'; +import { isArray } from 'lodash'; + +// Initialize all the fields of the component is the isOpen key to false +// The key will be used to control the open close state of the banner +function init(initialState, componentValue) { + return initialState.update('collapses', list => { + if (isArray(componentValue)) { + return fromJS( + componentValue.map(() => ({ + isOpen: false, + })) + ); + } + + return list; + }); +} + +export default init; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/reducer.js b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/reducer.js new file mode 100644 index 0000000000..78361e0936 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/RepeatableComponent/reducer.js @@ -0,0 +1,29 @@ +import { fromJS } from 'immutable'; + +const initialState = fromJS({ collapses: [] }); + +const reducer = (state, action) => { + switch (action.type) { + case 'ADD_NEW_FIELD': + return state.update('collapses', list => { + return list + .map(obj => obj.update('isOpen', () => false)) + .push(fromJS({ isOpen: true })); + }); + case 'TOGGLE_COLLAPSE': + return state.update('collapses', list => { + return list.map((obj, index) => { + if (index === action.index) { + return obj.update('isOpen', v => !v); + } + + return obj.update('isOpen', () => false); + }); + }); + default: + return state; + } +}; + +export default reducer; +export { initialState }; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/index.js index 4d6fef5122..e2863c2d27 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditViewDataManagerProvider/index.js @@ -189,6 +189,8 @@ const EditViewDataManagerProvider = ({ const showLoader = !isCreatingEntry && isLoading; + console.log({ modifiedData }); + return (