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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+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 (