diff --git a/demo/client/static/assets/images/generic-block-image.svg b/demo/client/static/assets/images/generic-block-image.svg
new file mode 100644
index 0000000000..eb8029428f
--- /dev/null
+++ b/demo/client/static/assets/images/generic-block-image.svg
@@ -0,0 +1,9 @@
+
diff --git a/demo/collections/AllFields.js b/demo/collections/AllFields.js
index afb7e31d22..7f0b2cc322 100644
--- a/demo/collections/AllFields.js
+++ b/demo/collections/AllFields.js
@@ -1,4 +1,8 @@
const checkRole = require('../policies/checkRole');
+const Email = require('../content-blocks/Email');
+const Quote = require('../content-blocks/Quote');
+const NumberBlock = require('../content-blocks/Number');
+const CallToAction = require('../content-blocks/CallToAction');
const AllFields = {
slug: 'all-fields',
@@ -164,6 +168,7 @@ const AllFields = {
required: true,
policies: {
read: ({ req: { user } }) => Boolean(user),
+ update: () => false,
},
},
],
@@ -176,6 +181,17 @@ const AllFields = {
},
],
},
+ {
+ type: 'flexible',
+ label: 'Flexible Content',
+ name: 'flexible',
+ minRows: 2,
+ singularLabel: 'Block',
+ blocks: [Email, NumberBlock, Quote, CallToAction],
+ localized: true,
+ required: true,
+ timestamps: true,
+ },
{
type: 'relationship',
label: 'Relationship to One Collection',
diff --git a/demo/collections/FlexibleContent.js b/demo/collections/FlexibleContent.js
index 6fb734f8bc..ec1c764554 100644
--- a/demo/collections/FlexibleContent.js
+++ b/demo/collections/FlexibleContent.js
@@ -1,6 +1,7 @@
const Email = require('../content-blocks/Email');
const Quote = require('../content-blocks/Quote');
const NumberBlock = require('../content-blocks/Number');
+const CallToAction = require('../content-blocks/CallToAction');
module.exports = {
slug: 'flexible-content',
@@ -14,7 +15,7 @@ module.exports = {
label: 'Layout Blocks',
singularLabel: 'Block',
type: 'flexible',
- blocks: [Email, NumberBlock, Quote],
+ blocks: [Email, NumberBlock, Quote, CallToAction],
localized: true,
required: true,
},
diff --git a/demo/collections/NestedRepeater.js b/demo/collections/NestedRepeater.js
index 83ad08e636..127cadeeca 100644
--- a/demo/collections/NestedRepeater.js
+++ b/demo/collections/NestedRepeater.js
@@ -12,6 +12,10 @@ const NestedRepeater = {
type: 'repeater',
label: 'Repeater',
name: 'repeater',
+ labels: {
+ singular: 'Parent Row',
+ plural: 'Parent Rows',
+ },
required: true,
minRows: 2,
maxRows: 4,
@@ -19,13 +23,17 @@ const NestedRepeater = {
{
name: 'parentIdentifier',
label: 'Parent Identifier',
- defaultValue: 'test',
+ defaultValue: '',
type: 'text',
required: true,
},
{
type: 'repeater',
name: 'nestedRepeater',
+ labels: {
+ singular: 'Child Row',
+ plural: 'Child Rows',
+ },
required: true,
fields: [
{
@@ -37,6 +45,10 @@ const NestedRepeater = {
{
type: 'repeater',
name: 'deeplyNestedRepeater',
+ labels: {
+ singular: 'Grandchild Row',
+ plural: 'Grandchild Rows',
+ },
required: true,
fields: [
{
diff --git a/demo/content-blocks/CallToAction.js b/demo/content-blocks/CallToAction.js
index 3cd21d18ae..b07dc60943 100644
--- a/demo/content-blocks/CallToAction.js
+++ b/demo/content-blocks/CallToAction.js
@@ -1,23 +1,23 @@
module.exports = {
- slug: 'cta',
- labels: {
- singular: 'Call to Action',
- plural: 'Calls to Action',
- },
- fields: [
- {
- name: 'label',
- label: 'Label',
- type: 'text',
- maxLength: 100,
- required: true,
- },
- {
- name: 'url',
- label: 'URL',
- type: 'text',
- height: 100,
- required: true,
- },
- ],
+ slug: 'cta',
+ labels: {
+ singular: 'Call to Action',
+ plural: 'Calls to Action',
+ },
+ fields: [
+ {
+ name: 'label',
+ label: 'Label',
+ type: 'text',
+ maxLength: 100,
+ required: true,
+ },
+ {
+ name: 'url',
+ label: 'URL',
+ type: 'text',
+ height: 100,
+ required: true,
+ },
+ ],
};
diff --git a/demo/content-blocks/Quote.js b/demo/content-blocks/Quote.js
index 3b6d2bb6bf..9d4d095a91 100644
--- a/demo/content-blocks/Quote.js
+++ b/demo/content-blocks/Quote.js
@@ -1,4 +1,5 @@
module.exports = {
+ blockImage: '/static/assets/images/generic-block-image.svg',
slug: 'quote',
labels: {
singular: 'Quote',
@@ -26,11 +27,5 @@ module.exports = {
maxLength: 7,
required: true,
},
- {
- name: 'photo',
- label: 'Photo',
- type: 'upload',
- relationTo: 'media',
- },
],
};
diff --git a/demo/server.js b/demo/server.js
index ad4a9a8ed7..4c5dcebc23 100644
--- a/demo/server.js
+++ b/demo/server.js
@@ -1,8 +1,11 @@
const express = require('express');
+const path = require('path');
const Payload = require('../src');
const expressApp = express();
+expressApp.use('/static', express.static(path.resolve(__dirname, 'client/static')));
+
const payload = new Payload({
email: {
provider: 'mock',
diff --git a/src/auth/operations/policies.js b/src/auth/operations/policies.js
index dfeffcdbe7..c6c43d823c 100644
--- a/src/auth/operations/policies.js
+++ b/src/auth/operations/policies.js
@@ -13,11 +13,11 @@ const policies = async (args) => {
const isLoggedIn = !!(user);
const userCollectionConfig = (user && user.collection) ? config.collections.find(collection => collection.slug === user.collection) : null;
- const createPolicyPromise = async (obj, policy, operation) => {
+ const createPolicyPromise = async (obj, policy, operation, disableWhere = false) => {
const updatedObj = obj;
const result = await policy({ req });
- if (typeof result === 'object') {
+ if (typeof result === 'object' && !disableWhere) {
updatedObj[operation] = {
permission: true,
where: result,
@@ -37,7 +37,7 @@ const policies = async (args) => {
if (!updatedObj[field.name]) updatedObj[field.name] = {};
if (field.policies && typeof field.policies[operation] === 'function') {
- promises.push(createPolicyPromise(updatedObj[field.name], field.policies[operation], operation));
+ promises.push(createPolicyPromise(updatedObj[field.name], field.policies[operation], operation, true));
} else {
updatedObj[field.name][operation] = {
permission: isLoggedIn,
diff --git a/src/client/assets/images/generic-block-image.svg b/src/client/assets/images/generic-block-image.svg
new file mode 100644
index 0000000000..eb8029428f
--- /dev/null
+++ b/src/client/assets/images/generic-block-image.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/client/components/elements/Popup/PopupButton/index.js b/src/client/components/elements/Popup/PopupButton/index.js
new file mode 100644
index 0000000000..8645d38e63
--- /dev/null
+++ b/src/client/components/elements/Popup/PopupButton/index.js
@@ -0,0 +1,56 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import './index.scss';
+
+const baseClass = 'popup-button';
+
+const PopupButton = (props) => {
+ const {
+ buttonType,
+ button,
+ setActive,
+ active,
+ } = props;
+
+ const classes = [
+ baseClass,
+ `${baseClass}--${buttonType}`,
+ ].filter(Boolean).join(' ');
+
+ if (buttonType === 'custom') {
+ return (
+
setActive(!active)}
+ className={classes}
+ >
+ {button}
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+PopupButton.defaultProps = {
+ buttonType: null,
+};
+
+PopupButton.propTypes = {
+ buttonType: PropTypes.oneOf(['custom', 'default']),
+ button: PropTypes.node.isRequired,
+ setActive: PropTypes.func.isRequired,
+ active: PropTypes.bool.isRequired,
+};
+
+export default PopupButton;
diff --git a/src/client/components/elements/Popup/PopupButton/index.scss b/src/client/components/elements/Popup/PopupButton/index.scss
new file mode 100644
index 0000000000..27c47e294e
--- /dev/null
+++ b/src/client/components/elements/Popup/PopupButton/index.scss
@@ -0,0 +1,5 @@
+@import '../../../../scss/styles.scss';
+
+.popup-button {
+ display: inline-flex;
+}
diff --git a/src/client/components/elements/Popup/index.js b/src/client/components/elements/Popup/index.js
index 72ca2f8de7..4482309df8 100644
--- a/src/client/components/elements/Popup/index.js
+++ b/src/client/components/elements/Popup/index.js
@@ -2,48 +2,27 @@ import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { useWindowInfo } from '@trbl/react-window-info';
import { useScrollInfo } from '@trbl/react-scroll-info';
+
import useThrottledEffect from '../../../hooks/useThrottledEffect';
+import PopupButton from './PopupButton';
import './index.scss';
const baseClass = 'popup';
-const ClickableButton = ({
- buttonType, button, setActive, active,
-}) => {
- if (buttonType === 'custom') {
- return (
- setActive(!active)}
- >
- {button}
-
- );
- }
-
- return (
-
- );
-};
-
const Popup = (props) => {
const {
- render, align, size, color, pointerAlignment, button, buttonType, children, showOnHover,
+ render, align, size, color, button, buttonType, children, showOnHover, horizontalAlign,
} = props;
- const [active, setActive] = useState(false);
- const [verticalAlign, setVerticalAlign] = useState('top');
- const { height: windowHeight } = useWindowInfo();
- const { y: scrollY } = useScrollInfo();
const buttonRef = useRef(null);
const contentRef = useRef(null);
+ const [active, setActive] = useState(false);
+ const [verticalAlign, setVerticalAlign] = useState('top');
+ const [forceHorizontalAlign, setForceHorizontalAlign] = useState(null);
+
+ const { y: scrollY } = useScrollInfo();
+ const { height: windowHeight } = useWindowInfo();
const handleClickOutside = (e) => {
if (contentRef.current.contains(e.target)) {
@@ -55,10 +34,29 @@ const Popup = (props) => {
useThrottledEffect(() => {
if (contentRef.current && buttonRef.current) {
- const { height: contentHeight } = contentRef.current.getBoundingClientRect();
- const { y: buttonOffsetTop } = buttonRef.current.getBoundingClientRect();
+ const {
+ height: contentHeight,
+ width: contentWidth,
+ right: contentRightEdge,
+ } = contentRef.current.getBoundingClientRect();
+ const { y: buttonYCoord } = buttonRef.current.getBoundingClientRect();
- if (buttonOffsetTop > contentHeight) {
+ const windowWidth = window.innerWidth;
+ const distanceToRightEdge = windowWidth - contentRightEdge;
+ const distanceToLeftEdge = contentRightEdge - contentWidth;
+
+ if (horizontalAlign === 'left' && distanceToRightEdge <= 0) {
+ setForceHorizontalAlign('right');
+ } else if (horizontalAlign === 'right' && distanceToLeftEdge <= 0) {
+ setForceHorizontalAlign('left');
+ } else if (horizontalAlign === 'center' && (distanceToLeftEdge <= contentWidth / 2 || distanceToRightEdge <= contentWidth / 2)) {
+ if (distanceToRightEdge > distanceToLeftEdge) setForceHorizontalAlign('left');
+ else setForceHorizontalAlign('right');
+ } else {
+ setForceHorizontalAlign(null);
+ }
+
+ if (buttonYCoord > contentHeight) {
setVerticalAlign('top');
} else {
setVerticalAlign('bottom');
@@ -83,14 +81,18 @@ const Popup = (props) => {
`${baseClass}--align-${align}`,
`${baseClass}--size-${size}`,
`${baseClass}--color-${color}`,
- `${baseClass}--pointer-alignment-${pointerAlignment}`,
- `${baseClass}--vertical-align-${verticalAlign}`,
+ `${baseClass}--v-align-${verticalAlign}`,
+ `${baseClass}--h-align-${horizontalAlign}`,
+ forceHorizontalAlign && `${baseClass}--force-h-align-${forceHorizontalAlign}`,
active && `${baseClass}--active`,
].filter(Boolean).join(' ');
return (
-
+
{showOnHover
? (
{
onMouseEnter={() => setActive(true)}
onMouseLeave={() => setActive(false)}
>
- {
)
: (
-
{
- const {
- addRow, removeRow, singularLabel, verticalAlignment,
- } = props;
-
- const classes = [
- baseClass,
- `${baseClass}--vertical-alignment-${verticalAlignment}`,
- ].filter(Boolean).join(' ');
-
- return (
-
-
-
-
removeRow()}
- />
- )}
- >
- Remove
- {singularLabel}
-
-
-
addRow()}
- />
- )}
- >
- Add
- {singularLabel}
-
-
-
-
- );
-};
-
-ActionHandle.defaultProps = {
- singularLabel: 'Row',
- verticalAlignment: 'center',
-};
-
-ActionHandle.propTypes = {
- singularLabel: PropTypes.string,
- addRow: PropTypes.func.isRequired,
- removeRow: PropTypes.func.isRequired,
- verticalAlignment: PropTypes.oneOf(['top', 'center', 'sticky']),
-};
-
-export default ActionHandle;
diff --git a/src/client/components/forms/DraggableSection/ActionPanel/index.js b/src/client/components/forms/DraggableSection/ActionPanel/index.js
new file mode 100644
index 0000000000..2ef155390c
--- /dev/null
+++ b/src/client/components/forms/DraggableSection/ActionPanel/index.js
@@ -0,0 +1,130 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Button from '../../../elements/Button';
+import Popup from '../../../elements/Popup';
+import BlockSelector from '../../field-types/Flexible/BlockSelector';
+
+import './index.scss';
+
+const baseClass = 'action-panel';
+
+const ActionPanel = (props) => {
+ const {
+ addRow,
+ removeRow,
+ singularLabel,
+ verticalAlignment,
+ blockType,
+ blocks,
+ rowIndex,
+ isHovered,
+ } = props;
+
+ const classes = [
+ baseClass,
+ `${baseClass}--vertical-alignment-${verticalAlignment}`,
+ ].filter(Boolean).join(' ');
+
+ return (
+
+
+
+
+ )}
+ >
+ Remove
+ {singularLabel}
+
+
+ {blockType === 'flexible'
+ ? (
+
+ )}
+ render={({ close }) => (
+
+ )}
+ />
+ )
+ : (
+
+ )}
+ >
+ Add
+ {singularLabel}
+
+ )
+ }
+
+
+
+ );
+};
+
+ActionPanel.defaultProps = {
+ singularLabel: 'Row',
+ verticalAlignment: 'center',
+ blockType: null,
+ isHovered: false,
+};
+
+ActionPanel.propTypes = {
+ singularLabel: PropTypes.string,
+ addRow: PropTypes.func.isRequired,
+ removeRow: PropTypes.func.isRequired,
+ blockType: PropTypes.oneOf(['flexible', 'repeater']),
+ verticalAlignment: PropTypes.oneOf(['top', 'center', 'sticky']),
+ isHovered: PropTypes.bool,
+ rowIndex: PropTypes.number.isRequired,
+};
+
+export default ActionPanel;
diff --git a/src/client/components/forms/DraggableSection/ActionHandle/index.scss b/src/client/components/forms/DraggableSection/ActionPanel/index.scss
similarity index 59%
rename from src/client/components/forms/DraggableSection/ActionHandle/index.scss
rename to src/client/components/forms/DraggableSection/ActionPanel/index.scss
index 2952b27525..2a771ad5f8 100644
--- a/src/client/components/forms/DraggableSection/ActionHandle/index.scss
+++ b/src/client/components/forms/DraggableSection/ActionPanel/index.scss
@@ -1,9 +1,13 @@
@import '../../../../scss/styles';
-.action-handle {
- padding: base(.5);
+.action-panel {
+ padding: 0 base(.5);
padding-left: base(.75);
- margin-bottom: base(.5);
+ margin-bottom: base(1);
+
+ &:hover {
+ z-index: $z-nav;
+ }
&__controls-container {
position: relative;
@@ -16,16 +20,18 @@
height: 100%;
flex-direction: column;
color: $color-gray;
+ opacity: 0;
+ visibility: hidden;
}
&--vertical-alignment-top {
- .action-handle__controls {
+ .action-panel__controls {
justify-content: flex-start;
}
}
&--vertical-alignment-sticky {
- .action-handle__controls {
+ .action-panel__controls {
position: sticky;
top: 120px;
height: unset;
@@ -34,11 +40,22 @@
&__remove-row {
margin: 0 0 base(.3);
- opacity: 0;
}
&__add-row {
margin: base(.3) 0 0;
- opacity: 0;
+ }
+
+ @include mid-break {
+ &__controls {
+ opacity: 1;
+ visibility: visible;
+ }
+
+ &--vertical-alignment-sticky {
+ .action-panel__controls {
+ top: 100px;
+ }
+ }
}
}
diff --git a/src/client/components/forms/DraggableSection/PositionHandle/index.js b/src/client/components/forms/DraggableSection/PositionPanel/index.js
similarity index 72%
rename from src/client/components/forms/DraggableSection/PositionHandle/index.js
rename to src/client/components/forms/DraggableSection/PositionPanel/index.js
index c126e783c2..8b8c54214b 100644
--- a/src/client/components/forms/DraggableSection/PositionHandle/index.js
+++ b/src/client/components/forms/DraggableSection/PositionPanel/index.js
@@ -5,11 +5,11 @@ import Button from '../../../elements/Button';
import './index.scss';
-const baseClass = 'position-handle';
+const baseClass = 'position-panel';
-const PositionHandle = (props) => {
+const PositionPanel = (props) => {
const {
- dragHandleProps, moveRow, positionIndex, verticalAlignment,
+ dragHandleProps, moveRow, positionIndex, verticalAlignment, rowCount,
} = props;
const adjustedIndex = positionIndex + 1;
@@ -26,8 +26,9 @@ const PositionHandle = (props) => {
>
+
);
};
-PositionHandle.defaultProps = {
+PositionPanel.defaultProps = {
verticalAlignment: 'center',
};
-PositionHandle.propTypes = {
+PositionPanel.propTypes = {
dragHandleProps: PropTypes.shape({}).isRequired,
positionIndex: PropTypes.number.isRequired,
moveRow: PropTypes.func.isRequired,
verticalAlignment: PropTypes.oneOf(['top', 'center', 'sticky']),
+ rowCount: PropTypes.number.isRequired,
};
-export default PositionHandle;
+export default PositionPanel;
diff --git a/src/client/components/forms/DraggableSection/PositionHandle/index.scss b/src/client/components/forms/DraggableSection/PositionPanel/index.scss
similarity index 54%
rename from src/client/components/forms/DraggableSection/PositionHandle/index.scss
rename to src/client/components/forms/DraggableSection/PositionPanel/index.scss
index 10ff121c22..61b92d3e12 100644
--- a/src/client/components/forms/DraggableSection/PositionHandle/index.scss
+++ b/src/client/components/forms/DraggableSection/PositionPanel/index.scss
@@ -1,17 +1,19 @@
@import '../../../../scss/styles';
-.position-handle {
+$controls-top-adjustment: base(.1);
+
+.position-panel {
padding-right: base(1);
- margin-bottom: base(.5);
+ margin-bottom: base(1);
&__controls-container {
position: relative;
- height: 100%;
+ min-height: 100%;
box-shadow: #{$style-stroke-width-s} 0px 0px 0px $color-light-gray;
}
&__controls {
- padding-right: base(.75);
+ padding-right: base(.65);
display: flex;
flex-direction: column;
justify-content: center;
@@ -19,13 +21,13 @@
}
&--vertical-alignment-top {
- .position-handle__controls {
+ .position-panel__controls {
justify-content: flex-start;
}
}
&--vertical-alignment-sticky {
- .position-handle__controls {
+ .position-panel__controls {
position: sticky;
top: 120px;
height: unset;
@@ -34,12 +36,12 @@
&__move-backward {
transform: rotate(.5turn);
- margin: 0 0 base(.25);
+ margin: 0;
opacity: 0;
}
&__move-forward {
- margin: base(.25) 0 0;
+ margin: 0;
opacity: 0;
}
@@ -47,4 +49,26 @@
text-align: center;
color: $color-gray;
}
+
+ @include large-break {
+ padding-right: base(1);
+
+ &__controls {
+ padding-right: base(.75);
+ }
+ }
+}
+
+
+// External scopes
+.field-type.flexible {
+ .position-panel {
+ &__controls-container {
+ min-height: calc(100% + #{$controls-top-adjustment});
+ }
+
+ &__controls {
+ margin-top: - $controls-top-adjustment;
+ }
+ }
}
diff --git a/src/client/components/forms/DraggableSection/index.js b/src/client/components/forms/DraggableSection/index.js
index eec6b1ad01..a5d33f5b7a 100644
--- a/src/client/components/forms/DraggableSection/index.js
+++ b/src/client/components/forms/DraggableSection/index.js
@@ -1,14 +1,15 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
-// import AnimateHeight from 'react-animate-height';
+import AnimateHeight from 'react-animate-height';
import { Draggable } from 'react-beautiful-dnd';
-import ActionHandle from './ActionHandle';
+import ActionPanel from './ActionPanel';
import SectionTitle from './SectionTitle';
-import PositionHandle from './PositionHandle';
+import PositionPanel from './PositionPanel';
import RenderFields from '../RenderFields';
import './index.scss';
+import Button from '../../elements/Button';
const baseClass = 'draggable-section';
@@ -18,35 +19,27 @@ const DraggableSection = (props) => {
addRow,
removeRow,
rowIndex,
+ rowCount,
parentPath,
fieldSchema,
initialData,
- // dispatchRows,
singularLabel,
blockType,
fieldTypes,
customComponentsPath,
isOpen,
id,
- positionHandleVerticalAlignment,
- actionHandleVerticalAlignment,
+ positionPanelVerticalAlignment,
+ actionPanelVerticalAlignment,
+ toggleRowCollapse,
permissions,
} = props;
- // const draggableRef = useRef(null);
const [isHovered, setIsHovered] = useState(false);
- // const handleCollapseClick = () => {
- // draggableRef.current.focus();
- // dispatchRows({
- // type: 'UPDATE_COLLAPSIBLE_STATUS',
- // index: rowIndex,
- // });
- // };
-
const classes = [
baseClass,
- isOpen && 'is-open',
+ isOpen ? 'is-open' : 'is-closed',
isHovered && 'is-hovered',
].filter(Boolean).join(' ');
@@ -60,50 +53,71 @@ const DraggableSection = (props) => {
setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
+ onMouseOver={() => setIsHovered(true)}
+ onFocus={() => setIsHovered(true)}
{...providedDrag.draggableProps}
>
-
+
+
-
+
- {blockType === 'flexible' && (
-
- )}
+ {blockType === 'flexible' && (
+
+
- {/* Render fields */}
- {
- return ({
- ...field,
- path: `${parentPath}.${rowIndex}${field.name ? `.${field.name}` : ''}`,
- });
- })}
+
+
+ )}
+
+
+ {
+ return ({
+ ...field,
+ path: `${parentPath}.${rowIndex}${field.name ? `.${field.name}` : ''}`,
+ });
+ })}
+ />
+
+
+
+
-
-
);
}}
@@ -112,34 +126,37 @@ const DraggableSection = (props) => {
};
DraggableSection.defaultProps = {
+ toggleRowCollapse: undefined,
rowCount: null,
initialData: undefined,
singularLabel: '',
blockType: '',
customComponentsPath: '',
isOpen: true,
- positionHandleVerticalAlignment: 'center',
- actionHandleVerticalAlignment: 'center',
+ positionPanelVerticalAlignment: 'sticky',
+ actionPanelVerticalAlignment: 'sticky',
+ permissions: {},
};
DraggableSection.propTypes = {
moveRow: PropTypes.func.isRequired,
addRow: PropTypes.func.isRequired,
removeRow: PropTypes.func.isRequired,
+ toggleRowCollapse: PropTypes.func,
rowIndex: PropTypes.number.isRequired,
parentPath: PropTypes.string.isRequired,
singularLabel: PropTypes.string,
fieldSchema: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
rowCount: PropTypes.number,
initialData: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.shape({})]),
- dispatchRows: PropTypes.func.isRequired,
isOpen: PropTypes.bool,
blockType: PropTypes.string,
fieldTypes: PropTypes.shape({}).isRequired,
customComponentsPath: PropTypes.string,
id: PropTypes.string.isRequired,
- positionHandleVerticalAlignment: PropTypes.oneOf(['top', 'center', 'sticky']),
- actionHandleVerticalAlignment: PropTypes.oneOf(['top', 'center', 'sticky']),
+ positionPanelVerticalAlignment: PropTypes.oneOf(['top', 'center', 'sticky']),
+ actionPanelVerticalAlignment: PropTypes.oneOf(['top', 'center', 'sticky']),
+ permissions: PropTypes.shape({}),
};
export default DraggableSection;
diff --git a/src/client/components/forms/DraggableSection/index.scss b/src/client/components/forms/DraggableSection/index.scss
index ede6cbab7d..2d56121e43 100644
--- a/src/client/components/forms/DraggableSection/index.scss
+++ b/src/client/components/forms/DraggableSection/index.scss
@@ -4,25 +4,25 @@
// HELPER MIXINS
//////////////////////
-@mixin realtively-position-handles {
- .position-handle {
+@mixin realtively-position-panels {
+ .position-panel {
position: relative;
right: 0;
}
- .action-handle {
+ .action-panel {
position: relative;
left: 0;
}
}
-@mixin absolutely-position-handles {
- .position-handle {
+@mixin absolutely-position-panels {
+ .position-panel {
position: absolute;
top: 0; right: 100%; bottom: 0;
}
- .action-handle {
+ .action-panel {
position: absolute;
top: 0; bottom: 0; left: 100%;
}
@@ -33,43 +33,96 @@
//////////////////////
.draggable-section {
- display: flex;
- position: relative;
- padding-top: base(.75);
- padding-bottom: base(.75);
- margin-bottom: base(.75);
+ padding-bottom: base(.5);
+
+ .draggable-section {
+ padding-bottom: 0;
+ }
+
+ &__content-wrapper {
+ display: flex;
+ position: relative;
+ padding-top: base(.75);
+ }
+
+ &.is-closed {
+ .draggable-section__content-wrapper {
+ margin-bottom: base(1.75);
+ }
+ }
+
+ &__section-header {
+ display: flex;
+
+ .toggle-collapse {
+ margin: 0 0 0 auto;
+ transform: rotate(.5turn);
+
+ &--is-closed {
+ transform: rotate(0turn);
+ }
+ }
+ }
&__render-fields-wrapper {
width: 100%;
}
- &.is-hovered,
- &:focus-within {
- > .position-handle {
- .position-handle__controls-container {
+ &.is-hovered > div {
+ > .position-panel {
+ .position-panel__controls-container {
box-shadow: #{$style-stroke-width-m} 0px 0px 0px $color-dark-gray;
}
- .position-handle__move-forward,
- .position-handle__move-backward {
+ .position-panel__move-forward,
+ .position-panel__move-backward {
opacity: 1;
+
+ &.first-row,
+ &.last-row {
+ opacity: .15;
+ pointer-events: none;
+ }
}
- .position-handle__current-position {
+ .position-panel__current-position {
color: $color-dark-gray;
}
}
- > .action-handle {
- .action-handle__add-row,
- .action-handle__remove-row {
+ > .action-panel {
+ .action-panel__controls {
opacity: 1;
+ visibility: visible;
+ z-index: $z-nav;
+ }
+ }
+
+ .toggle-collapse {
+ @include color-svg(white);
+
+ .btn__icon {
+ background-color: $color-gray;
+
+ &:hover {
+ background-color: $color-dark-gray;
+ }
}
}
}
+ label.field-label {
+ line-height: 1;
+ padding-bottom: base(.75)
+ }
+
@include mid-break {
- @include realtively-position-handles();
+ @include realtively-position-panels();
+
+ .position-panel__move-forward,
+ .position-panel__move-backward {
+ opacity: 1;
+ }
}
}
@@ -78,12 +131,23 @@
//////////////////////
.collection-edit {
- @include absolutely-position-handles();
+ @include absolutely-position-panels();
+
+ @include mid-break {
+ @include realtively-position-panels();
+ }
}
.field-type.repeater .field-type.repeater {
- @include realtively-position-handles();
+ @include realtively-position-panels();
}
-
-
+// remove padding above repeater rows to level
+// the line with the top of the input label
+.field-type.repeater {
+ .draggable-section {
+ &__content-wrapper {
+ padding-top: 0;
+ }
+ }
+}
diff --git a/src/client/components/forms/RenderFields/index.js b/src/client/components/forms/RenderFields/index.js
index 4bf6d9b991..de9e679dc4 100644
--- a/src/client/components/forms/RenderFields/index.js
+++ b/src/client/components/forms/RenderFields/index.js
@@ -17,16 +17,17 @@ const RenderFields = (props) => {
filter,
permissions,
readOnly: readOnlyOverride,
- operation,
+ operation: operationFromProps,
} = props;
- const { customComponentsPath: customComponentsPathFromContext } = useRenderedFields();
+ const { customComponentsPath: customComponentsPathFromContext, operation: operationFromContext } = useRenderedFields();
const customComponentsPath = customComponentsPathFromProps || customComponentsPathFromContext;
+ const operation = operationFromProps || operationFromContext;
if (fieldSchema) {
return (
-
+
{fieldSchema.map((field, i) => {
if (field?.hidden !== 'api' && field?.hidden !== true) {
if ((filter && typeof filter === 'function' && filter(field)) || !filter) {
@@ -117,7 +118,6 @@ RenderFields.propTypes = {
filter: PropTypes.func,
permissions: PropTypes.shape({}),
readOnly: PropTypes.bool,
- operation: PropTypes.oneOf(['create', 'read', 'update', 'delete']),
};
export default RenderFields;
diff --git a/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSearch/index.js b/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSearch/index.js
index d1b49fdfb9..0d2efb4f7f 100644
--- a/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSearch/index.js
+++ b/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSearch/index.js
@@ -1,22 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
+import SearchIcon from '../../../../../graphics/Search';
+
import './index.scss';
const baseClass = 'block-search';
const BlockSearch = (props) => {
- const {} = props;
+ const { setSearchTerm } = props;
+
+ const handleChange = (e) => {
+ setSearchTerm(e.target.value);
+ };
return (
- Search...
+
+
);
};
-BlockSearch.defaultProps = {};
-
-BlockSearch.propTypes = {};
+BlockSearch.propTypes = {
+ setSearchTerm: PropTypes.func.isRequired,
+};
export default BlockSearch;
diff --git a/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSearch/index.scss b/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSearch/index.scss
index aaee5a0a9d..ae0f2f8aa1 100644
--- a/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSearch/index.scss
+++ b/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSearch/index.scss
@@ -1 +1,30 @@
-@import '../../../../../../scss/styles';
+@import '../../../shared.scss';
+
+$icon-width: base(1);
+$icon-margin: base(.25);
+
+.block-search {
+ position: sticky;
+ top: 0;
+ display: flex;
+ align-items: center;
+ z-index: 1;
+
+ &__input {
+ @include formInput;
+ padding-right: calc(#{$icon-width} + #{$icon-margin} * 2);
+ }
+
+ .search {
+ position: absolute;
+ right: 0;
+ width: $icon-width;
+ margin: 0 $icon-margin;
+ }
+
+ @include mid-break {
+ &__input {
+ margin-bottom: 0;
+ }
+ }
+}
diff --git a/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSelection/index.js b/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSelection/index.js
new file mode 100644
index 0000000000..b077ec2d04
--- /dev/null
+++ b/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSelection/index.js
@@ -0,0 +1,67 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import DefaultBlockImage from '../../../../../graphics/DefaultBlockImage';
+
+import './index.scss';
+
+const baseClass = 'block-selection';
+
+const BlockSelection = (props) => {
+ const {
+ addRow, addRowIndex, block, close,
+ } = props;
+
+ const {
+ labels, slug, blockImage, blockImageAltText,
+ } = block;
+
+ const handleBlockSelection = () => {
+ console.log('adding');
+ close();
+ addRow(addRowIndex, slug);
+ };
+
+ return (
+
+
+ {blockImage
+ ? (
+

+ )
+ :
+ }
+
+
{labels.singular}
+
+ );
+};
+
+BlockSelection.defaultProps = {
+ addRow: undefined,
+ addRowIndex: 0,
+};
+
+BlockSelection.propTypes = {
+ addRow: PropTypes.func,
+ addRowIndex: PropTypes.number,
+ block: PropTypes.shape({
+ labels: PropTypes.shape({
+ singular: PropTypes.string,
+ }),
+ slug: PropTypes.string,
+ blockImage: PropTypes.string,
+ blockImageAltText: PropTypes.string,
+ }).isRequired,
+ close: PropTypes.func.isRequired,
+};
+
+export default BlockSelection;
diff --git a/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSelection/index.scss b/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSelection/index.scss
new file mode 100644
index 0000000000..50b5ae7fea
--- /dev/null
+++ b/src/client/components/forms/field-types/Flexible/BlockSelector/BlockSelection/index.scss
@@ -0,0 +1,33 @@
+@import '../../../../../../scss/styles';
+
+.block-selection {
+ display: inline-flex;
+ flex-direction: column;
+ flex-wrap: wrap;
+ width: 33%;
+ padding: base(.75) base(.5);
+ cursor: pointer;
+ align-items: center;
+
+ &:hover {
+ background-color: $color-background-gray;
+ }
+
+ &__image {
+ svg,
+ img {
+ max-width: 100%;
+ }
+ }
+
+ &__label {
+ margin-top: base(.25);
+ font-weight: 600;
+ text-align: center;
+ white-space: initial;
+ }
+
+ @include mid-break {
+ width: unset;
+ }
+}
diff --git a/src/client/components/forms/field-types/Flexible/BlockSelector/BlocksContainer/index.js b/src/client/components/forms/field-types/Flexible/BlockSelector/BlocksContainer/index.js
index 38c2f6f8f6..6ad791b401 100644
--- a/src/client/components/forms/field-types/Flexible/BlockSelector/BlocksContainer/index.js
+++ b/src/client/components/forms/field-types/Flexible/BlockSelector/BlocksContainer/index.js
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
-import SelectableBlock from '../SelectableBlock';
+import BlockSelection from '../BlockSelection';
import './index.scss';
@@ -14,14 +14,13 @@ const BlocksContainer = (props) => {
{blocks?.map((block, index) => {
return (
-
);
})}
- Blocks to choose from...
);
};
diff --git a/src/client/components/forms/field-types/Flexible/BlockSelector/BlocksContainer/index.scss b/src/client/components/forms/field-types/Flexible/BlockSelector/BlocksContainer/index.scss
index aaee5a0a9d..c3014d2f5d 100644
--- a/src/client/components/forms/field-types/Flexible/BlockSelector/BlocksContainer/index.scss
+++ b/src/client/components/forms/field-types/Flexible/BlockSelector/BlocksContainer/index.scss
@@ -1 +1,14 @@
@import '../../../../../../scss/styles';
+
+.blocks-container {
+ width: 100%;
+ margin-top: base(1);
+ margin-bottom: base(.5);
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ min-width: 450px;
+ max-width: 80vw;
+ max-height: 300px;
+ position: relative;
+}
diff --git a/src/client/components/forms/field-types/Flexible/BlockSelector/SelectableBlock/index.js b/src/client/components/forms/field-types/Flexible/BlockSelector/SelectableBlock/index.js
deleted file mode 100644
index b1bb343f96..0000000000
--- a/src/client/components/forms/field-types/Flexible/BlockSelector/SelectableBlock/index.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-const baseClass = 'selectable-block';
-
-const SelectableBlock = (props) => {
- const { addRow, addRowIndex, block } = props;
-
- const { labels, slug } = block;
-
- return (
- addRow(addRowIndex, slug)}
- >
- {labels.singular}
-
- );
-};
-
-SelectableBlock.defaultProps = {
- addRow: undefined,
- addRowIndex: 0,
-};
-
-SelectableBlock.propTypes = {
- addRow: PropTypes.func,
- addRowIndex: PropTypes.number,
-};
-
-export default SelectableBlock;
diff --git a/src/client/components/forms/field-types/Flexible/BlockSelector/index.js b/src/client/components/forms/field-types/Flexible/BlockSelector/index.js
index add1d0152e..ae5dca6a27 100644
--- a/src/client/components/forms/field-types/Flexible/BlockSelector/index.js
+++ b/src/client/components/forms/field-types/Flexible/BlockSelector/index.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import BlockSearch from './BlockSearch';
@@ -7,16 +7,54 @@ import BlocksContainer from './BlocksContainer';
const baseClass = 'block-selector';
const BlockSelector = (props) => {
+ const {
+ blocks, close, parentIsHovered, watchParentHover, ...remainingProps
+ } = props;
+
+ const [searchTerm, setSearchTerm] = useState('');
+ const [filteredBlocks, setFilteredBlocks] = useState(blocks);
+ const [isBlockSelectorHovered, setBlockSelectorHovered] = useState(false);
+
+ useEffect(() => {
+ const matchingBlocks = blocks.reduce((matchedBlocks, block) => {
+ if (block.slug.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1) matchedBlocks.push(block);
+ return matchedBlocks;
+ }, []);
+
+ setFilteredBlocks(matchingBlocks);
+ }, [searchTerm, blocks]);
+
+ useEffect(() => {
+ if (!parentIsHovered && !isBlockSelectorHovered && close && watchParentHover) close();
+ }, [isBlockSelectorHovered, parentIsHovered, close, watchParentHover]);
+
return (
-
-
-
+
setBlockSelectorHovered(true)}
+ onMouseLeave={() => setBlockSelectorHovered(false)}
+ >
+
+
);
};
-BlockSelector.defaultProps = {};
+BlockSelector.defaultProps = {
+ close: null,
+ parentIsHovered: false,
+ watchParentHover: false,
+};
-BlockSelector.propTypes = {};
+BlockSelector.propTypes = {
+ blocks: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
+ close: PropTypes.func,
+ watchParentHover: PropTypes.bool,
+ parentIsHovered: PropTypes.bool,
+};
export default BlockSelector;
diff --git a/src/client/components/forms/field-types/Flexible/BlockSelector/index.scss b/src/client/components/forms/field-types/Flexible/BlockSelector/index.scss
new file mode 100644
index 0000000000..6c5e738d1a
--- /dev/null
+++ b/src/client/components/forms/field-types/Flexible/BlockSelector/index.scss
@@ -0,0 +1,8 @@
+@import '../../../../../scss/styles.scss';
+
+.block-selector {
+
+ @include mid-break {
+ min-width: 80vw;
+ }
+}
diff --git a/src/client/components/forms/field-types/Flexible/index.js b/src/client/components/forms/field-types/Flexible/index.js
index 84a397d046..df27ef8dde 100644
--- a/src/client/components/forms/field-types/Flexible/index.js
+++ b/src/client/components/forms/field-types/Flexible/index.js
@@ -1,5 +1,5 @@
import React, {
- useEffect, useReducer, useState, useCallback,
+ useEffect, useReducer, useCallback,
} from 'react';
import PropTypes from 'prop-types';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
@@ -14,7 +14,7 @@ import { useRenderedFields } from '../../RenderFields';
import Error from '../../Error';
import useFieldType from '../../useFieldType';
import Popup from '../../../elements/Popup';
-import BlocksContainer from './BlockSelector/BlocksContainer';
+import BlockSelector from './BlockSelector';
import { flexible } from '../../../../../fields/validations';
import './index.scss';
@@ -65,14 +65,11 @@ const Flexible = (props) => {
});
const dataToInitialize = initialData || defaultValue;
- const [addRowIndex, setAddRowIndex] = useState(null);
const [rows, dispatchRows] = useReducer(reducer, []);
const { customComponentsPath } = useRenderedFields();
const { getDataByPath } = useForm();
const addRow = (index, blockType) => {
- setAddRowIndex(current => current + 1);
-
const data = getDataByPath(path);
dispatchRows({
@@ -102,6 +99,12 @@ const Flexible = (props) => {
});
};
+ const toggleCollapse = (index) => {
+ dispatchRows({
+ type: 'TOGGLE_COLLAPSE', index, rows,
+ });
+ };
+
const onDragEnd = (result) => {
if (!result.destination) return;
const sourceIndex = result.source.index;
@@ -121,102 +124,106 @@ const Flexible = (props) => {
},
]), []),
});
- }, [dataToInitialize, setValue]);
+ }, [dataToInitialize]);
return (
- <>
-
-
-
-
- {provided => (
-
- {rows.length > 0 && rows.map((row, i) => {
- let { blockType } = row.data;
+
+
+
+ {label}
- if (!blockType) {
- blockType = dataToInitialize?.[i]?.blockType;
- }
+
+
- const blockToRender = blocks.find(block => block.slug === blockType);
-
- if (blockToRender) {
- return (
- addRow(i, blockType)}
- removeRow={() => removeRow(i)}
- rowIndex={i}
- permissions={permissions.fields}
- fieldSchema={[
- ...blockToRender.fields,
- {
- name: 'blockType',
- type: 'text',
- hidden: 'admin',
- }, {
- name: 'blockName',
- type: 'text',
- hidden: 'admin',
- },
- ]}
- singularLabel={blockToRender?.labels?.singular}
- initialData={row.data}
- dispatchRows={dispatchRows}
- blockType="flexible"
- customComponentsPath={`${customComponentsPath}${name}.fields.`}
- positionHandleVerticalAlignment="sticky"
- actionHandleVerticalAlignment="sticky"
- />
- );
- }
-
- return null;
- })
- }
- {provided.placeholder}
-
- )}
-
-
-
-
- {`Add ${singularLabel}`}
-
- )}
+
+ {provided => (
+
- 0 && rows.map((row, i) => {
+ let { blockType } = row.data;
+
+ if (!blockType) {
+ blockType = dataToInitialize?.[i]?.blockType;
+ }
+
+ const blockToRender = blocks.find(block => block.slug === blockType);
+
+ if (blockToRender) {
+ return (
+ removeRow(i)}
+ moveRow={moveRow}
+ toggleRowCollapse={() => toggleCollapse(i)}
+ parentPath={path}
+ initialData={row.data}
+ customComponentsPath={`${customComponentsPath}${name}.fields.`}
+ fieldTypes={fieldTypes}
+ permissions={permissions.fields}
+ fieldSchema={[
+ ...blockToRender.fields,
+ {
+ name: 'blockType',
+ type: 'text',
+ hidden: 'admin',
+ }, {
+ name: 'blockName',
+ type: 'text',
+ hidden: 'admin',
+ },
+ ]}
+ />
+ );
+ }
+
+ return null;
+ })
+ }
+ {provided.placeholder}
+
+ )}
+
+
+
+
+ {`Add ${singularLabel}`}
+
+ )}
+ render={({ close }) => (
+
-
-
+ )}
+ />
-
- >
+
+
);
};
diff --git a/src/client/components/forms/field-types/Flexible/index.scss b/src/client/components/forms/field-types/Flexible/index.scss
index f380bd853f..8d5702e684 100644
--- a/src/client/components/forms/field-types/Flexible/index.scss
+++ b/src/client/components/forms/field-types/Flexible/index.scss
@@ -3,6 +3,7 @@
.field-type.flexible {
&__add-button-wrap {
+
.btn {
color: $color-gray;
margin: 0;
diff --git a/src/client/components/forms/field-types/Repeater/index.js b/src/client/components/forms/field-types/Repeater/index.js
index a0bef0d61a..f4464950f6 100644
--- a/src/client/components/forms/field-types/Repeater/index.js
+++ b/src/client/components/forms/field-types/Repeater/index.js
@@ -25,12 +25,15 @@ const Repeater = (props) => {
fields,
defaultValue,
initialData,
- singularLabel,
fieldTypes,
validate,
required,
maxRows,
minRows,
+ labels: {
+ singular: singularLabel,
+ },
+ permissions,
} = props;
const dataToInitialize = initialData || defaultValue;
@@ -129,23 +132,23 @@ const Repeater = (props) => {
{rows.length > 0 && rows.map((row, i) => {
return (
addRow(i)}
- moveRow={moveRow}
- removeRow={() => removeRow(i)}
+ isOpen={row.open}
+ rowCount={rows.length}
rowIndex={i}
- fieldSchema={fields}
+ addRow={() => addRow(i)}
+ removeRow={() => removeRow(i)}
+ moveRow={moveRow}
+ parentPath={path}
initialData={row.data}
initNull={row.initNull}
- dispatchRows={dispatchRows}
customComponentsPath={`${customComponentsPath}${name}.fields.`}
- positionHandleVerticalAlignment="sticky"
- actionHandleVerticalAlignment="sticky"
+ fieldTypes={fieldTypes}
+ fieldSchema={fields}
+ permissions={permissions.fields}
/>
);
})
@@ -173,13 +176,16 @@ const Repeater = (props) => {
Repeater.defaultProps = {
label: '',
- singularLabel: 'Row',
defaultValue: [],
initialData: [],
validate: repeater,
required: false,
maxRows: undefined,
minRows: undefined,
+ labels: {
+ singular: 'Row',
+ },
+ permissions: {},
};
Repeater.propTypes = {
@@ -193,7 +199,9 @@ Repeater.propTypes = {
PropTypes.shape({}),
).isRequired,
label: PropTypes.string,
- singularLabel: PropTypes.string,
+ labels: PropTypes.shape({
+ singular: PropTypes.string,
+ }),
name: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
fieldTypes: PropTypes.shape({}).isRequired,
@@ -201,6 +209,9 @@ Repeater.propTypes = {
required: PropTypes.bool,
maxRows: PropTypes.number,
minRows: PropTypes.number,
+ permissions: PropTypes.shape({
+ fields: PropTypes.shape({}),
+ }),
};
export default withCondition(Repeater);
diff --git a/src/client/components/forms/field-types/Repeater/index.scss b/src/client/components/forms/field-types/Repeater/index.scss
index f865bfafb0..1ea46f8133 100644
--- a/src/client/components/forms/field-types/Repeater/index.scss
+++ b/src/client/components/forms/field-types/Repeater/index.scss
@@ -4,7 +4,7 @@
background: white;
&__add-button-wrap {
- margin-left: 0;
+ margin-left: base(0);
.btn {
color: $color-gray;
@@ -28,7 +28,11 @@
.field-type.repeater {
.field-type.repeater__add-button-wrap {
- margin-left: base(2.25);
+ margin-left: base(2.65);
+ }
+
+ .field-type.repeater__header {
+ display: none;
}
}
}
diff --git a/src/client/components/forms/field-types/Row/index.js b/src/client/components/forms/field-types/Row/index.js
index 0edc2b4a10..13646dea09 100644
--- a/src/client/components/forms/field-types/Row/index.js
+++ b/src/client/components/forms/field-types/Row/index.js
@@ -7,14 +7,13 @@ import './index.scss';
const Row = (props) => {
const {
- fields, fieldTypes, initialData, path: pathFromProps, name,
+ fields, fieldTypes, initialData, path, permissions,
} = props;
- const path = pathFromProps || name;
-
return (
{
@@ -31,6 +30,7 @@ const Row = (props) => {
Row.defaultProps = {
path: '',
initialData: undefined,
+ permissions: {},
};
Row.propTypes = {
@@ -39,8 +39,8 @@ Row.propTypes = {
).isRequired,
fieldTypes: PropTypes.shape({}).isRequired,
path: PropTypes.string,
- name: PropTypes.string.isRequired,
initialData: PropTypes.shape({}),
+ permissions: PropTypes.shape({}),
};
export default withCondition(Row);
diff --git a/src/client/components/forms/field-types/Row/index.scss b/src/client/components/forms/field-types/Row/index.scss
index 7a2ba4a715..bf54b74058 100644
--- a/src/client/components/forms/field-types/Row/index.scss
+++ b/src/client/components/forms/field-types/Row/index.scss
@@ -9,7 +9,6 @@
> * {
margin-left: base(.5);
margin-right: base(.5);
- margin-bottom: 0;
}
@include mid-break {
diff --git a/src/client/components/forms/field-types/rowReducer.js b/src/client/components/forms/field-types/rowReducer.js
index 7f06517e71..4faeeb5ddc 100644
--- a/src/client/components/forms/field-types/rowReducer.js
+++ b/src/client/components/forms/field-types/rowReducer.js
@@ -11,6 +11,10 @@ const reducer = (currentState, action) => {
case 'SET_ALL':
return rows;
+ case 'TOGGLE_COLLAPSE':
+ stateCopy[index].open = !stateCopy[index].open;
+ return stateCopy;
+
case 'ADD':
stateCopy.splice(index + 1, 0, {
open: true,
@@ -33,10 +37,6 @@ const reducer = (currentState, action) => {
stateCopy.splice(index, 1);
return stateCopy;
- case 'UPDATE_COLLAPSIBLE_STATUS':
- stateCopy[index].open = !stateCopy[index].open;
- return stateCopy;
-
case 'MOVE': {
const stateCopyWithNewData = stateCopy.map((row, i) => {
return {
diff --git a/src/client/components/graphics/DefaultBlockImage/index.js b/src/client/components/graphics/DefaultBlockImage/index.js
new file mode 100644
index 0000000000..13ef649576
--- /dev/null
+++ b/src/client/components/graphics/DefaultBlockImage/index.js
@@ -0,0 +1,44 @@
+import React, { useState } from 'react';
+import { v4 as uuid } from 'uuid';
+
+const DefaultBlockImage = () => {
+ const [patternID] = useState(`pattern${uuid()}`);
+ const [imageID] = useState(`image${uuid()}`);
+
+ return (
+
+ );
+};
+
+export default DefaultBlockImage;
diff --git a/src/client/components/graphics/Search/index.js b/src/client/components/graphics/Search/index.js
new file mode 100644
index 0000000000..dc8e356d96
--- /dev/null
+++ b/src/client/components/graphics/Search/index.js
@@ -0,0 +1,34 @@
+import React from 'react';
+
+const Search = () => {
+ return (
+
+ );
+};
+
+export default Search;
diff --git a/src/client/components/views/collections/Edit/index.scss b/src/client/components/views/collections/Edit/index.scss
index 70259d03fe..daac97294b 100644
--- a/src/client/components/views/collections/Edit/index.scss
+++ b/src/client/components/views/collections/Edit/index.scss
@@ -136,6 +136,10 @@
}
@include mid-break {
+ &__sidebar {
+ width: unset;
+ }
+
&__form {
display: block;
}
diff --git a/src/client/scss/vars.scss b/src/client/scss/vars.scss
index 8cc940b3b2..aebc565b0f 100644
--- a/src/client/scss/vars.scss
+++ b/src/client/scss/vars.scss
@@ -61,6 +61,14 @@ $style-stroke-width-m : 2px;
box-shadow: 0 2px 3px 0 rgba(0, 2, 4, 0.1), 0 6px 4px -4px rgba(0, 2, 4, 0.02);
}
+@mixin shadow-lg {
+ box-shadow: 0 2px 20px 7px rgba(0, 2, 4, 0.1), 0 6px 4px -4px rgba(0, 2, 4, 0.02);
+}
+
+@mixin shadow-lg-top {
+ box-shadow: 0 -2px 20px 7px rgba(0, 2, 4, 0.1), 0 6px 4px -4px rgba(0, 2, 4, 0.02);
+}
+
@mixin shadow {
box-shadow: 0 12px 45px rgba(0,0,0,.03);