refactor: merges the FlexibleRow and RepeaterRow into a single DraggableSection component
This commit is contained in:
@@ -3,16 +3,26 @@ import PropTypes from 'prop-types';
|
||||
import AnimateHeight from 'react-animate-height';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
|
||||
import RenderFields from '../../../RenderFields';
|
||||
import IconButton from '../../../../controls/IconButton';
|
||||
import RenderFields from '../RenderFields'; // eslint-disable-line import/no-cycle
|
||||
import IconButton from '../../controls/IconButton';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'repeater-row';
|
||||
const baseClass = 'draggable-section';
|
||||
|
||||
const DraggableSection = (props) => {
|
||||
const {
|
||||
addRow,
|
||||
removeRow,
|
||||
rowIndex,
|
||||
parentName,
|
||||
renderFields,
|
||||
defaultValue,
|
||||
dispatchCollapsibleStates,
|
||||
collapsibleStates,
|
||||
singularName,
|
||||
} = props;
|
||||
|
||||
const RepeaterRow = ({
|
||||
addRow, removeRow, rowIndex, parentName, fields, defaultValue, dispatchCollapsibleStates, collapsibleStates,
|
||||
}) => {
|
||||
const handleCollapseClick = () => {
|
||||
dispatchCollapsibleStates({
|
||||
type: 'UPDATE_COLLAPSIBLE_STATUS',
|
||||
@@ -34,12 +44,15 @@ const RepeaterRow = ({
|
||||
>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<div
|
||||
{...providedDrag.dragHandleProps}
|
||||
className={`${baseClass}__header__drag-handle`}
|
||||
{...providedDrag.dragHandleProps}
|
||||
onClick={handleCollapseClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
/>
|
||||
|
||||
<div className={`${baseClass}__header__row-index`}>
|
||||
{`${rowIndex + 1 > 9 ? rowIndex + 1 : `0${rowIndex + 1}`}`}
|
||||
{`${singularName} ${rowIndex + 1}`}
|
||||
</div>
|
||||
|
||||
<div className={`${baseClass}__header__controls`}>
|
||||
@@ -55,13 +68,6 @@ const RepeaterRow = ({
|
||||
onClick={removeRow}
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={`${baseClass}__collapse__icon ${baseClass}__collapse__icon--${collapsibleStates[rowIndex] ? 'open' : 'closed'}`}
|
||||
iconName="arrow"
|
||||
onClick={handleCollapseClick}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +78,7 @@ const RepeaterRow = ({
|
||||
>
|
||||
<RenderFields
|
||||
key={rowIndex}
|
||||
fields={fields.map((field) => {
|
||||
fields={renderFields.map((field) => {
|
||||
const fieldName = `${parentName}.${rowIndex}.${field.name}`;
|
||||
return ({
|
||||
...field,
|
||||
@@ -89,22 +95,24 @@ const RepeaterRow = ({
|
||||
);
|
||||
};
|
||||
|
||||
RepeaterRow.defaultProps = {
|
||||
DraggableSection.defaultProps = {
|
||||
rowCount: null,
|
||||
defaultValue: null,
|
||||
collapsibleStates: [],
|
||||
singularName: '',
|
||||
};
|
||||
|
||||
RepeaterRow.propTypes = {
|
||||
DraggableSection.propTypes = {
|
||||
addRow: PropTypes.func.isRequired,
|
||||
removeRow: PropTypes.func.isRequired,
|
||||
rowIndex: PropTypes.number.isRequired,
|
||||
parentName: PropTypes.string.isRequired,
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
singularName: PropTypes.string,
|
||||
renderFields: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
rowCount: PropTypes.number,
|
||||
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.shape({})]),
|
||||
dispatchCollapsibleStates: PropTypes.func.isRequired,
|
||||
collapsibleStates: PropTypes.arrayOf(PropTypes.bool),
|
||||
};
|
||||
|
||||
export default RepeaterRow;
|
||||
export default DraggableSection;
|
||||
@@ -1,11 +1,17 @@
|
||||
@import '../../../../../scss/styles.scss';
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
.flexible-row {
|
||||
.draggable-section {
|
||||
background: $light-gray;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
margin-top: base(.5);
|
||||
|
||||
.field-type,
|
||||
.missing-field {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include shadow-sm;
|
||||
}
|
||||
@@ -74,7 +80,18 @@
|
||||
@include color-svg($primary);
|
||||
|
||||
&:hover {
|
||||
background: lighten($primary, 50%);
|
||||
background: $primary;
|
||||
@include color-svg($black);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button--crossOut {
|
||||
border-color: $black;
|
||||
@include color-svg($black);
|
||||
|
||||
&:hover {
|
||||
background: $black;
|
||||
@include color-svg(white);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AnimateHeight from 'react-animate-height';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import RenderFields from '../../../RenderFields';
|
||||
import IconButton from '../../../../controls/IconButton';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'flexible-row';
|
||||
|
||||
const FlexibleRow = (props) => {
|
||||
const {
|
||||
addRow,
|
||||
removeRow,
|
||||
rowIndex,
|
||||
parentName,
|
||||
block,
|
||||
defaultValue,
|
||||
dispatchCollapsibleStates,
|
||||
collapsibleStates,
|
||||
} = props;
|
||||
|
||||
const handleCollapseClick = () => {
|
||||
dispatchCollapsibleStates({
|
||||
type: 'UPDATE_COLLAPSIBLE_STATUS',
|
||||
collapsibleIndex: rowIndex,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
draggableId={`row-${rowIndex}`}
|
||||
index={rowIndex}
|
||||
>
|
||||
{(providedDrag) => {
|
||||
return (
|
||||
<div
|
||||
ref={providedDrag.innerRef}
|
||||
className={baseClass}
|
||||
{...providedDrag.draggableProps}
|
||||
>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<div
|
||||
{...providedDrag.dragHandleProps}
|
||||
className={`${baseClass}__header__drag-handle`}
|
||||
/>
|
||||
|
||||
<div className={`${baseClass}__header__row-index`}>
|
||||
{`${block.labels.singular} ${rowIndex + 1 > 9 ? rowIndex + 1 : `0${rowIndex + 1}`}`}
|
||||
</div>
|
||||
|
||||
<div className={`${baseClass}__header__controls`}>
|
||||
|
||||
<IconButton
|
||||
iconName="crosshair"
|
||||
onClick={addRow}
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
iconName="crossOut"
|
||||
onClick={removeRow}
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={`${baseClass}__collapse__icon ${baseClass}__collapse__icon--${collapsibleStates[rowIndex] ? 'open' : 'closed'}`}
|
||||
iconName="arrow"
|
||||
onClick={handleCollapseClick}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimateHeight
|
||||
className={`${baseClass}__content`}
|
||||
height={collapsibleStates[rowIndex] ? 'auto' : 0}
|
||||
duration={0}
|
||||
>
|
||||
<RenderFields
|
||||
key={rowIndex}
|
||||
fields={block.fields.map((field) => {
|
||||
const fieldName = `${parentName}.${rowIndex}.${field.name}`;
|
||||
return ({
|
||||
...field,
|
||||
name: fieldName,
|
||||
defaultValue: defaultValue?.[field.name],
|
||||
});
|
||||
})}
|
||||
/>
|
||||
</AnimateHeight>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
FlexibleRow.defaultProps = {
|
||||
defaultValue: null,
|
||||
collapsibleStates: [],
|
||||
};
|
||||
|
||||
FlexibleRow.propTypes = {
|
||||
block: PropTypes.shape({
|
||||
labels: PropTypes.shape({
|
||||
singular: PropTypes.string,
|
||||
}),
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
slug: PropTypes.string,
|
||||
}).isRequired,
|
||||
addRow: PropTypes.func.isRequired,
|
||||
removeRow: PropTypes.func.isRequired,
|
||||
rowIndex: PropTypes.number.isRequired,
|
||||
parentName: PropTypes.string.isRequired,
|
||||
fieldState: PropTypes.shape({}).isRequired,
|
||||
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.shape({})]),
|
||||
dispatchCollapsibleStates: PropTypes.func.isRequired,
|
||||
collapsibleStates: PropTypes.arrayOf(PropTypes.bool),
|
||||
};
|
||||
|
||||
export default FlexibleRow;
|
||||
@@ -7,9 +7,9 @@ import { ModalContext } from '@trbl/react-modal';
|
||||
|
||||
import FormContext from '../../Form/Context';
|
||||
import Section from '../../../layout/Section';
|
||||
import FlexibleRow from './FlexibleRow'; // eslint-disable-line import/no-cycle
|
||||
import AddRowModal from './AddRowModal';
|
||||
import collapsibleReducer from './reducer';
|
||||
import DraggableSection from '../../DraggableSection'; // eslint-disable-line import/no-cycle
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -113,14 +113,14 @@ const Flexible = (props) => {
|
||||
const blockToRender = blocks.find(block => block.slug === blockType);
|
||||
|
||||
return (
|
||||
<FlexibleRow
|
||||
<DraggableSection
|
||||
key={rowIndex}
|
||||
parentName={name}
|
||||
addRow={() => openAddRowModal(rowIndex)}
|
||||
removeRow={() => removeRow(rowIndex)}
|
||||
rowIndex={rowIndex}
|
||||
fieldState={fieldState}
|
||||
block={blockToRender}
|
||||
renderFields={blockToRender.fields}
|
||||
defaultValue={defaultValue[rowIndex]}
|
||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||
collapsibleStates={collapsibleStates}
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
@import '../../../../../scss/styles.scss';
|
||||
|
||||
.repeater-row {
|
||||
background: $light-gray;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
margin-top: base(.5);
|
||||
|
||||
&:hover {
|
||||
@include shadow-sm;
|
||||
}
|
||||
|
||||
&__collapse__icon {
|
||||
&--open {
|
||||
svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&--closed {
|
||||
svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
padding: base(.75) base(1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&__drag-handle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// elements above the drag handle
|
||||
&__controls,
|
||||
&__header__row-index {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__row-index {
|
||||
font-family: $font-body;
|
||||
font-size: base(.5);
|
||||
}
|
||||
|
||||
&__heading {
|
||||
font-family: $font-body;
|
||||
margin: 0;
|
||||
font-size: base(.65);
|
||||
}
|
||||
|
||||
&__controls {
|
||||
margin-left: auto;
|
||||
|
||||
.btn {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.icon-button--crossOut,
|
||||
.icon-button--crosshair {
|
||||
margin-right: base(.25);
|
||||
}
|
||||
|
||||
.icon-button--crosshair {
|
||||
border-color: $primary;
|
||||
@include color-svg($primary);
|
||||
|
||||
&:hover {
|
||||
background: lighten($primary, 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
box-shadow: inset 0px 1px 0px white;
|
||||
|
||||
> div {
|
||||
padding: base(.75) base(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||
|
||||
import FormContext from '../../Form/Context';
|
||||
import Section from '../../../layout/Section';
|
||||
import RepeaterRow from './RepeaterRow'; // eslint-disable-line import/no-cycle
|
||||
import DraggableSection from '../../DraggableSection'; // eslint-disable-line import/no-cycle
|
||||
import collapsibleReducer from './reducer';
|
||||
|
||||
import './index.scss';
|
||||
@@ -24,6 +24,7 @@ const Repeater = (props) => {
|
||||
name,
|
||||
fields,
|
||||
defaultValue,
|
||||
singularName,
|
||||
} = props;
|
||||
|
||||
const addRow = (rowIndex) => {
|
||||
@@ -82,7 +83,6 @@ const Repeater = (props) => {
|
||||
<div className={baseClass}>
|
||||
<Section
|
||||
heading={label}
|
||||
className="repeater"
|
||||
rowCount={rowCount}
|
||||
addRow={() => addRow(0)}
|
||||
useAddRowButton
|
||||
@@ -94,22 +94,24 @@ const Repeater = (props) => {
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{rowCount !== 0
|
||||
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||
return (
|
||||
<RepeaterRow
|
||||
key={rowIndex}
|
||||
parentName={name}
|
||||
addRow={() => addRow(rowIndex)}
|
||||
removeRow={() => removeRow(rowIndex)}
|
||||
rowIndex={rowIndex}
|
||||
fields={fields}
|
||||
rowCount={rowCount}
|
||||
defaultValue={defaultValue[rowIndex]}
|
||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||
collapsibleStates={collapsibleStates}
|
||||
/>
|
||||
);
|
||||
})
|
||||
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||
return (
|
||||
<DraggableSection
|
||||
key={rowIndex}
|
||||
parentName={name}
|
||||
singularName={singularName}
|
||||
addRow={() => addRow(rowIndex)}
|
||||
removeRow={() => removeRow(rowIndex)}
|
||||
rowIndex={rowIndex}
|
||||
fieldState={fieldState}
|
||||
renderFields={fields}
|
||||
rowCount={rowCount}
|
||||
defaultValue={defaultValue[rowIndex]}
|
||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||
collapsibleStates={collapsibleStates}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
@@ -124,6 +126,7 @@ const Repeater = (props) => {
|
||||
|
||||
Repeater.defaultProps = {
|
||||
label: '',
|
||||
singularName: '',
|
||||
defaultValue: [],
|
||||
};
|
||||
|
||||
@@ -135,6 +138,7 @@ Repeater.propTypes = {
|
||||
PropTypes.shape({}),
|
||||
).isRequired,
|
||||
label: PropTypes.string,
|
||||
singularName: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.field-type.repeater {
|
||||
background: white;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import AnimateHeight from 'react-animate-height';
|
||||
|
||||
import './index.scss';
|
||||
import IconButton from '../../controls/IconButton';
|
||||
import Button from '../../controls/Button';
|
||||
|
||||
const baseClass = 'section';
|
||||
|
||||
@@ -28,25 +29,13 @@ const Section = (props) => {
|
||||
<section className={classes}>
|
||||
{heading
|
||||
&& (
|
||||
<header>
|
||||
<header
|
||||
className={`${baseClass}__collapsible-header`}
|
||||
onClick={() => setIsSectionOpen(state => !state)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<h2 className={`${baseClass}__heading`}>{heading}</h2>
|
||||
<div className={`${baseClass}__controls`}>
|
||||
{(rowCount === 0 && useAddRowButton)
|
||||
&& (
|
||||
<IconButton
|
||||
className={`${baseClass}__add-row-button`}
|
||||
size="small"
|
||||
iconName="crosshair"
|
||||
onClick={() => addInitialRow()}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
className={`${baseClass}__collapse-icon ${baseClass}__collapse-icon--${isSectionOpen ? 'open' : 'closed'}`}
|
||||
size="small"
|
||||
iconName="arrow"
|
||||
onClick={() => setIsSectionOpen(state => !state)}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
{children
|
||||
@@ -54,8 +43,19 @@ const Section = (props) => {
|
||||
<AnimateHeight
|
||||
className={`${baseClass}__content ${baseClass}__content--is-${isSectionOpen ? 'open' : 'closed'}`}
|
||||
height={isSectionOpen ? 'auto' : 0}
|
||||
duration={150}
|
||||
duration={0}
|
||||
>
|
||||
{(rowCount === 0 && useAddRowButton)
|
||||
&& (
|
||||
<div className={`${baseClass}__add-button-wrap`}>
|
||||
<Button
|
||||
onClick={addInitialRow}
|
||||
type="secondary"
|
||||
>
|
||||
Add Row
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</AnimateHeight>
|
||||
)}
|
||||
|
||||
@@ -3,18 +3,35 @@
|
||||
section.section {
|
||||
@include shadow;
|
||||
margin: base(1) 0;
|
||||
transition: 300ms ease;
|
||||
|
||||
header {
|
||||
&:hover {
|
||||
box-shadow: 0 22px 65px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.section__collapsible-header {
|
||||
border-bottom: 1px solid $light-gray;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@include pad;
|
||||
outline: 0;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
* {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.section__add-button-wrap {
|
||||
.btn {
|
||||
margin: 0;
|
||||
margin-top: base(.5);
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user