diff --git a/.gitignore b/.gitignore
index 211dab2..a88924a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ dist
.env.development.local
.env.test.local
.env.production.local
+*~
diff --git a/src/ColumnsBlock/ColumnsBlockEdit.jsx b/src/ColumnsBlock/ColumnsBlockEdit.jsx
new file mode 100644
index 0000000..d14ad0d
--- /dev/null
+++ b/src/ColumnsBlock/ColumnsBlockEdit.jsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import { Grid } from 'semantic-ui-react';
+import { isEmpty } from 'lodash';
+import { SidebarPortal, InlineForm, BlocksForm } from '@plone/volto/components';
+import { emptyBlocksForm } from '@plone/volto/helpers';
+
+import { ColumnsBlockSchema } from './schema';
+import { getColumns, empty } from './utils';
+
+import './styles.less';
+
+const ColumnsBlockEdit = (props) => {
+ const {
+ block,
+ data,
+ onChangeBlock,
+ onChangeField,
+ pathname,
+ selected,
+ } = props;
+
+ React.useEffect(() => {
+ if (!data.coldata) {
+ onChangeBlock(block, { ...data, coldata: empty() });
+ }
+ });
+
+ const [colSelections, setColSelections] = React.useState({});
+
+ const { coldata = empty() } = data;
+ const columnList = getColumns(coldata);
+
+ return (
+ <>
+
+ {/* {
{data.block_title}
} */}
+
+ {columnList.map(([colId, column], index) => {
+ return (
+
+ {/* {`Column ${index}`}
*/}
+
+ setColSelections({
+ // this invalidates selection in all other columns
+ [colId]: id,
+ })
+ }
+ onChangeFormData={(newFormData) => {
+ onChangeBlock(block, {
+ ...data,
+ coldata: {
+ ...coldata,
+ columns: {
+ ...coldata.columns,
+ [colId]: newFormData,
+ },
+ },
+ });
+ }}
+ onChangeField={(id, value) => {
+ if (['blocks', 'blocks_layout'].indexOf(id) > -1) {
+ onChangeBlock(block, {
+ ...data,
+ coldata: {
+ ...coldata,
+ columns: {
+ ...coldata.columns,
+ [colId]: {
+ ...coldata.columns?.[colId],
+ [id]: value,
+ },
+ },
+ },
+ });
+ } else {
+ onChangeField(id, value);
+ }
+ }}
+ pathname={pathname}
+ />
+
+ );
+ })}
+
+
+
+ {
+ onChangeBlock(block, {
+ ...data,
+ [id]: value,
+ });
+ }}
+ formData={data}
+ />
+
+ >
+ );
+};
+
+export default ColumnsBlockEdit;
diff --git a/src/ColumnsBlock/ColumnsBlockView.jsx b/src/ColumnsBlock/ColumnsBlockView.jsx
new file mode 100644
index 0000000..233ccbc
--- /dev/null
+++ b/src/ColumnsBlock/ColumnsBlockView.jsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Grid } from 'semantic-ui-react';
+import { renderBlocks } from '@plone/volto/helpers';
+import { getColumns, empty } from './utils';
+
+const ColumnsBlockView = (props) => {
+ const { coldata = empty(), block_title } = props.data;
+ const columnList = getColumns(coldata);
+ return (
+
+ {block_title ?
{block_title}
: ''}
+
+ {columnList.map(([id, column], index) => {
+ return (
+
+ {`Column ${index}`}
+ {renderBlocks(column, props)}
+
+ );
+ })}
+
+
+ );
+};
+
+export default ColumnsBlockView;
diff --git a/src/ColumnsBlock/index.js b/src/ColumnsBlock/index.js
new file mode 100644
index 0000000..0959b48
--- /dev/null
+++ b/src/ColumnsBlock/index.js
@@ -0,0 +1,2 @@
+export ColumnsBlockView from './ColumnsBlockView';
+export ColumnsBlockEdit from './ColumnsBlockEdit';
diff --git a/src/ColumnsBlock/schema.js b/src/ColumnsBlock/schema.js
new file mode 100644
index 0000000..bc75d50
--- /dev/null
+++ b/src/ColumnsBlock/schema.js
@@ -0,0 +1,21 @@
+export const ColumnsBlockSchema = {
+ title: 'Columns block',
+ fieldsets: [
+ {
+ id: 'default',
+ title: 'Default',
+ fields: ['block_title', 'coldata'], // 'nrColumns'
+ },
+ ],
+ properties: {
+ block_title: {
+ title: 'Block title',
+ default: 'Columns',
+ },
+ coldata: {
+ title: 'Columns',
+ type: 'columns',
+ },
+ },
+ required: ['title'],
+};
diff --git a/src/ColumnsBlock/styles.less b/src/ColumnsBlock/styles.less
new file mode 100644
index 0000000..c668d5f
--- /dev/null
+++ b/src/ColumnsBlock/styles.less
@@ -0,0 +1,28 @@
+.columns-block {
+ .block-column {
+ padding: 0.3em;
+ }
+}
+
+.drag-drop-list-widget {
+ .columns-area {
+ padding: 1em 0em;
+
+ [data-rbd-draggable-context-id] {
+ margin-bottom: 0.3em;
+ }
+
+ .column-area {
+ display: flex;
+
+ .label {
+ flex-grow: 2;
+ padding-left: 1em;
+ }
+
+ button {
+ flex-grow: 0;
+ }
+ }
+ }
+}
diff --git a/src/ColumnsBlock/utils.js b/src/ColumnsBlock/utils.js
new file mode 100644
index 0000000..667eaf4
--- /dev/null
+++ b/src/ColumnsBlock/utils.js
@@ -0,0 +1,19 @@
+import { v4 as uuid } from 'uuid';
+import { emptyBlocksForm } from '@plone/volto/helpers';
+
+export const getColumns = (coldata) => {
+ return (coldata?.columns_layout?.items || []).map((id) => [
+ id,
+ coldata.columns?.[id],
+ ]);
+};
+
+export const empty = () => {
+ const id = uuid();
+ return {
+ columns: { [id]: emptyBlocksForm() },
+ columns_layout: {
+ items: [id],
+ },
+ };
+};
diff --git a/src/ColumnsWidget/ColumnsWidget.jsx b/src/ColumnsWidget/ColumnsWidget.jsx
new file mode 100644
index 0000000..5a4375d
--- /dev/null
+++ b/src/ColumnsWidget/ColumnsWidget.jsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import { v4 as uuid } from 'uuid';
+import { omit, without } from 'lodash';
+import move from 'lodash-move';
+import { DragDropList, Icon, FormFieldWrapper } from '@plone/volto/components';
+import { emptyBlocksForm } from '@plone/volto/helpers';
+
+import dragSVG from '@plone/volto/icons/drag.svg';
+import trashSVG from '@plone/volto/icons/delete.svg';
+import plusSVG from '@plone/volto/icons/circle-plus.svg';
+
+export function moveColumn(formData, source, destination) {
+ return {
+ ...formData,
+ columns_layout: {
+ items: move(formData.columns_layout?.items, source, destination),
+ },
+ };
+}
+
+const empty = () => {
+ return [uuid(), emptyBlocksForm()];
+};
+
+const ColumnsWidget = (props) => {
+ const { value = {}, id, onChange } = props;
+ const { columns = {} } = value;
+ const columnsList = (value.columns_layout?.items || []).map((id) => [
+ id,
+ columns[id],
+ ]);
+ return (
+
+
+
{
+ const { source, destination } = result;
+ if (!destination) {
+ return;
+ }
+ const newFormData = moveColumn(
+ value,
+ source.index,
+ destination.index,
+ );
+ onChange(id, newFormData);
+ return true;
+ }}
+ renderChild={(child, childId, index, draginfo) => (
+
+
+
+
+
+
+
Column {index}
+
+
+
+
+ )}
+ />
+
+
+
+ );
+};
+
+export default ColumnsWidget;
diff --git a/src/ColumnsWidget/index.js b/src/ColumnsWidget/index.js
new file mode 100644
index 0000000..b00aba5
--- /dev/null
+++ b/src/ColumnsWidget/index.js
@@ -0,0 +1,2 @@
+import ColumnsWidget from './ColumnsWidget';
+export default ColumnsWidget;
diff --git a/src/index.js b/src/index.js
index cb042f0..49bc7d1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,27 @@
-const applyConfig = (config) => {
- return config;
-};
+import codeSVG from '@plone/volto/icons/code.svg';
+
+import { ColumnsBlockView, ColumnsBlockEdit } from './ColumnsBlock';
+import ColumnsWidget from './ColumnsWidget';
+
+export default function install(config) {
+ config.blocks.blocksConfig.columnsBlock = {
+ id: 'columnsBlock',
+ title: 'Columns',
+ icon: codeSVG,
+ group: 'common',
+ view: ColumnsBlockView,
+ edit: ColumnsBlockEdit,
+ restricted: false,
+ mostUsed: true,
+ blockHasOwnFocusManagement: true,
+ sidebarTab: 1,
+ security: {
+ addPermission: [],
+ view: [],
+ },
+ };
-export default applyConfig;
+ config.widgets.type.columns = ColumnsWidget;
+
+ return config;
+}