diff --git a/example/index.story.js b/example/index.story.js index 285c253..49ce949 100644 --- a/example/index.story.js +++ b/example/index.story.js @@ -25,7 +25,7 @@ storiesOf('React StreamField demo', module) ], value: [{ type: 'title', value: 'Wagtail is awesome!' }] }; - return ; + return ; }) .add('1 open block type', () => { const props = { @@ -41,7 +41,7 @@ storiesOf('React StreamField demo', module) ], value: [{ type: 'title', value: 'Wagtail is awesome!' }] }; - return ; + return ; }) .add('1 static block type', () => { const props = { @@ -55,7 +55,7 @@ storiesOf('React StreamField demo', module) ], value: [{ type: 'static' }] }; - return ; + return ; }) .add('1 block type, default value', () => { const props = { @@ -71,7 +71,7 @@ storiesOf('React StreamField demo', module) ], value: [{ type: 'title', value: 'Wagtail is awesome!' }] }; - return ; + return ; }) .add('1 block type, custom per-value HTML', () => { const props = { @@ -94,7 +94,7 @@ storiesOf('React StreamField demo', module) { type: 'title', value: 'This time, no custom HTML.' } ] }; - return ; + return ; }) .add('2 block types', () => { const props = { @@ -118,7 +118,7 @@ storiesOf('React StreamField demo', module) { type: 'text', value: 'And itโ€™s always getting better ๐Ÿ˜ƒ' } ] }; - return ; + return ; }) .add('List block, 1 child block type', () => { const props = { @@ -142,7 +142,7 @@ storiesOf('React StreamField demo', module) ] }; - return ; + return ; }) .add('List block, 1 child block type, default value', () => { const props = { @@ -161,7 +161,7 @@ storiesOf('React StreamField demo', module) ], value: [] }; - return ; + return ; }) .add('List block, 1 child block type, custom HTML', () => { const props = { @@ -181,7 +181,7 @@ storiesOf('React StreamField demo', module) ], value: [] }; - return ; + return ; }) .add('List block, 2 children block types with groups', () => { const props = { @@ -228,7 +228,7 @@ storiesOf('React StreamField demo', module) } ] }; - return ; + return ; }) .add('Simple block layout', () => { const props = { @@ -261,7 +261,7 @@ storiesOf('React StreamField demo', module) { type: 'static' } ] }; - return ; + return ; }) .add('Mixed block layouts', () => { const props = { @@ -293,7 +293,20 @@ storiesOf('React StreamField demo', module) { type: 'static' } ] }; - return ; + return ; + }) + .add('Gutter of add buttons', () => { + const props = { + required: true, + gutteredAdd: true, + blockDefinitions: [ + { + key: 'text', + } + ], + value: [] + }; + return }) .add('Maximum number of blocks', () => { const props = { @@ -319,7 +332,7 @@ storiesOf('React StreamField demo', module) } ] }; - return ; + return ; }) .add('Error in one of the nested blocks', () => { const props = { @@ -345,7 +358,7 @@ storiesOf('React StreamField demo', module) } ] }; - return ; + return ; }) .add('Struct block', () => { const props = { @@ -366,7 +379,7 @@ storiesOf('React StreamField demo', module) ], value: [] }; - return ; + return ; }) .add('Struct block with default value', () => { const props = { @@ -390,7 +403,7 @@ storiesOf('React StreamField demo', module) value: [] }; - return ; + return ; }) .add('Struct block with custom HTML', () => { const props = { @@ -413,7 +426,7 @@ storiesOf('React StreamField demo', module) ], value: [] }; - return ; + return ; }) .add('Struct block as a struct block field', () => { const props = { @@ -449,7 +462,7 @@ storiesOf('React StreamField demo', module) } ] }; - return ; + return ; }) .add('StructBlock as a list block child', () => { const props = { @@ -477,10 +490,10 @@ storiesOf('React StreamField demo', module) ], value: [] }; - return ; + return ; }) .add('Complex nested StreamField', () => { - return ; + return ; }) .add('Custom action icons', () => { const props = { @@ -504,7 +517,7 @@ storiesOf('React StreamField demo', module) value: [{ type: 'title', value: 'Wagtail is awesome!' }] }; - return ; + return ; }) .add('JavaScript widget', () => { const props = { @@ -520,5 +533,5 @@ storiesOf('React StreamField demo', module) ], value: [], }; - return ; + return ; }); diff --git a/src/Block.js b/src/Block.js index 637e030..6ed21ff 100644 --- a/src/Block.js +++ b/src/Block.js @@ -171,15 +171,13 @@ class Block extends React.Component { ); } return ( - <> - - {this.wrapSortable(blockContent)} - + + {this.wrapSortable(blockContent)} - + ); } } diff --git a/src/BlocksContainer.js b/src/BlocksContainer.js index 513d696..e239021 100644 --- a/src/BlocksContainer.js +++ b/src/BlocksContainer.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; import {connect} from 'react-redux'; import {Droppable} from 'react-beautiful-dnd'; import Block from './Block'; @@ -31,6 +32,7 @@ import {getNestedBlockDefinition, isNA} from './processing/utils'; } return { minNum, maxNum, + gutteredAdd: fieldData.gutteredAdd, blocksIds: blocksIds, }; }) @@ -52,16 +54,8 @@ class BlocksContainer extends React.Component { ); } - static getClassName(snapshot) { - let className = 'children-container'; - if (snapshot.isDraggingOver) { - className += ' is-dragging'; - } - return className; - } - render() { - const {fieldId, id, blocksIds, maxNum} = this.props; + const {fieldId, id, blocksIds, maxNum, gutteredAdd} = this.props; const droppableId = `${fieldId}-${id}`; const num = blocksIds.length; const canAdd = num < maxNum; @@ -69,7 +63,10 @@ class BlocksContainer extends React.Component { {(provided, snapshot) => (
+ className={classNames( + 'children-container', + snapshot.isDraggingOver && 'is-dragging', + gutteredAdd && 'guttered-add')}> {blocksIds.map(blockId => this.renderBlock(blockId, canAdd))} diff --git a/src/StreamField.js b/src/StreamField.js index e2357ad..3deef57 100644 --- a/src/StreamField.js +++ b/src/StreamField.js @@ -112,6 +112,7 @@ class StreamField extends React.Component { duplicate: PropTypes.string, delete: PropTypes.string, }), + gutteredAdd: PropTypes.bool, blockDefinitions: PropTypes.arrayOf(BlockDefinitionType).isRequired, value: PropTypes.arrayOf(BlockValueType).isRequired, }; @@ -145,15 +146,15 @@ class StreamField extends React.Component { componentDidMount() { const { - initializeStreamField, required, minNum, maxNum, blockDefinitions, value, + initializeStreamField, required, minNum, maxNum, gutteredAdd, + blockDefinitions, value, } = this.props; const defaultProps = StreamFieldDefaultProps; const icons = {...defaultProps.icons, ...this.props.icons}; const labels = {...defaultProps.labels, ...this.props.labels}; initializeStreamField({ - required, minNum, maxNum, icons, labels, blockDefinitions, - isMobile: getIsMobile(), - value, + required, minNum, maxNum, icons, labels, gutteredAdd, + blockDefinitions, isMobile: getIsMobile(), value, }); window.addEventListener('resize', this.onWindowResize); } diff --git a/src/index.scss b/src/index.scss index 64b32b7..64a242d 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,7 +1,7 @@ $grid-gutter-width: 30px; $header-padding: 8px; -$block-vertical-margin: 3px; -$block-full-margin: $block-vertical-margin 0; +$block-vertical-padding: 3px; +$block-full-padding: $block-vertical-padding 0; $add-button-size: 34px; $add-button-font-size: 28px; $children-container-padding: $add-button-size / 2; @@ -21,18 +21,25 @@ $error-border-color: #dbc7c8; $error-border-color-focus: #cdb2b3; $error-background-color: #fbefef; $screen-xs-max: 799px; +$screen-sm-min: 800px; .children-container { position: relative; padding: $children-container-padding 0 - $children-container-padding $add-button-size; + $children-container-padding 0; transition: padding $hover-transition-duration ease-in-out; - @media (max-width: $screen-xs-max) { - padding-left: 0; - padding-top: $children-container-padding + $add-button-size; + &.guttered-add { + @media (min-width: $screen-sm-min) { + padding-left: $add-button-size; + button.add { + position: absolute; + width: $add-button-size; + transform: translate(-100%, -50%); + } + } } &.is-dragging { - > button.add.visible { + button.add.visible { opacity: 0; pointer-events: none; } @@ -40,10 +47,7 @@ $screen-xs-max: 799px; .block { position: relative; display: flex; - margin: $block-full-margin; - @media (max-width: $screen-xs-max) { - margin-bottom: $block-vertical-margin + $add-button-size; - } + padding: $block-full-padding; &.has-error { > .block-container { border-color: $error-border-color; @@ -209,15 +213,13 @@ $screen-xs-max: 799px; } } button.add { - position: absolute; - width: $add-button-size; + width: 100%; height: $add-button-size; appearance: none; border: none; color: $teal; font-weight: bold; background: none; - transform: translate(-100%, -50%); padding: 0; cursor: pointer; outline: none; @@ -231,10 +233,6 @@ $screen-xs-max: 799px; opacity: 1; pointer-events: unset; } - @media (max-width: $screen-xs-max) { - width: 100%; - transform: translate(0, -100%); - } i { display: block; transition: transform $add-transition-duration $bounce-transition-timing; diff --git a/src/reducer.js b/src/reducer.js index b4a624d..fcbc8be 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -21,13 +21,14 @@ export default (state=initialState, action) => { case 'INITIALIZE_STREAM_FIELD': { const data = deepCopy(action.data); const { - required, minNum, maxNum, icons, labels, blockDefinitions, isMobile, - value, + required, minNum, maxNum, icons, labels, gutteredAdd, + blockDefinitions, isMobile, value, } = data; state = { ...state, [action.id]: { - required, minNum, maxNum, icons, labels, blockDefinitions, isMobile, + required, minNum, maxNum, icons, labels, gutteredAdd, + blockDefinitions, isMobile, }, }; return valueToState(state, action.id, value);