feat(Repeater): updates styles, enables header as the draggable point, moves collapsible state up to the repeater
This commit is contained in:
@@ -1,29 +1,23 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
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 Pill from '../../../../modules/Pill';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'repeater-row';
|
||||
|
||||
const RepeaterRow = ({
|
||||
addRow, removeRow, rowIndex, parentName, fields, fieldState, newRowIndex, rowCount,
|
||||
addRow, removeRow, rowIndex, parentName, fields, defaultValue, dispatchCollapsibleStates, collapsibleStates,
|
||||
}) => {
|
||||
const [isRowOpen, setIsRowOpen] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (rowCount === 1) setIsRowOpen(true);
|
||||
else if (newRowIndex + 1 === rowIndex) setIsRowOpen(true);
|
||||
else setIsRowOpen(false);
|
||||
}, [newRowIndex, rowCount, rowIndex]);
|
||||
|
||||
const handleCollapseClick = () => {
|
||||
setIsRowOpen(state => !state);
|
||||
dispatchCollapsibleStates({
|
||||
type: 'UPDATE_COLLAPSIBLE_STATUS',
|
||||
collapsibleIndex: rowIndex,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -39,18 +33,13 @@ const RepeaterRow = ({
|
||||
{...providedDrag.draggableProps}
|
||||
>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<Pill>
|
||||
{parentName}
|
||||
</Pill>
|
||||
<h4 className={`${baseClass}__header__heading`}>Title Goes Here</h4>
|
||||
<div
|
||||
{...providedDrag.dragHandleProps}
|
||||
className={`${baseClass}__header__drag-handle`}
|
||||
/>
|
||||
|
||||
<div className={`${baseClass}__header__drag-handle__wrap`}>
|
||||
<div
|
||||
{...providedDrag.dragHandleProps}
|
||||
className={`${baseClass}__header__drag-handle`}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
/>
|
||||
<div className={`${baseClass}__header__row-index`}>
|
||||
{`${rowIndex + 1 > 9 ? rowIndex + 1 : `0${rowIndex + 1}`}`}
|
||||
</div>
|
||||
|
||||
<div className={`${baseClass}__header__controls`}>
|
||||
@@ -68,7 +57,7 @@ const RepeaterRow = ({
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
className={`${baseClass}__collapse__icon ${baseClass}__collapse__icon--${isRowOpen ? 'open' : 'closed'}`}
|
||||
className={`${baseClass}__collapse__icon ${baseClass}__collapse__icon--${collapsibleStates[rowIndex] ? 'open' : 'closed'}`}
|
||||
iconName="arrow"
|
||||
onClick={handleCollapseClick}
|
||||
size="small"
|
||||
@@ -78,8 +67,8 @@ const RepeaterRow = ({
|
||||
|
||||
<AnimateHeight
|
||||
className={`${baseClass}__content`}
|
||||
height={isRowOpen ? 'auto' : 0}
|
||||
duration={150}
|
||||
height={collapsibleStates[rowIndex] ? 'auto' : 0}
|
||||
duration={0}
|
||||
>
|
||||
<RenderFields
|
||||
key={rowIndex}
|
||||
@@ -88,7 +77,7 @@ const RepeaterRow = ({
|
||||
return ({
|
||||
...field,
|
||||
name: fieldName,
|
||||
defaultValue: fieldState?.[fieldName]?.value,
|
||||
defaultValue: defaultValue?.[field.name],
|
||||
});
|
||||
})}
|
||||
/>
|
||||
@@ -101,8 +90,9 @@ const RepeaterRow = ({
|
||||
};
|
||||
|
||||
RepeaterRow.defaultProps = {
|
||||
newRowIndex: null,
|
||||
rowCount: null,
|
||||
defaultValue: null,
|
||||
collapsibleStates: [],
|
||||
};
|
||||
|
||||
RepeaterRow.propTypes = {
|
||||
@@ -112,8 +102,10 @@ RepeaterRow.propTypes = {
|
||||
parentName: PropTypes.string.isRequired,
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
fieldState: PropTypes.shape({}).isRequired,
|
||||
newRowIndex: PropTypes.number,
|
||||
rowCount: PropTypes.number,
|
||||
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.shape({})]),
|
||||
dispatchCollapsibleStates: PropTypes.func.isRequired,
|
||||
collapsibleStates: PropTypes.arrayOf(PropTypes.bool),
|
||||
};
|
||||
|
||||
export default RepeaterRow;
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
background: $light-gray;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
border: $stroke-width solid lighten($gray, 20%);
|
||||
margin-top: base(.5);
|
||||
|
||||
&__collapse__icon {
|
||||
svg {
|
||||
transition: 150ms linear;
|
||||
}
|
||||
&:hover {
|
||||
@include shadow-sm;
|
||||
}
|
||||
|
||||
&__collapse__icon {
|
||||
&--open {
|
||||
svg {
|
||||
transform: rotate(90deg);
|
||||
@@ -29,9 +28,26 @@
|
||||
padding: base(.75) base(1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.pill {
|
||||
margin-right: base(.5);
|
||||
&__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 {
|
||||
@@ -40,28 +56,6 @@
|
||||
font-size: base(.65);
|
||||
}
|
||||
|
||||
&__drag-handle__wrap {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__drag-handle {
|
||||
max-width: 100px;
|
||||
height: 15px;
|
||||
margin: 0 base(1) 0 auto;
|
||||
background: repeating-linear-gradient(
|
||||
90deg,
|
||||
$gray,
|
||||
$gray 4px,
|
||||
transparent 4px,
|
||||
transparent 10px,
|
||||
);
|
||||
opacity: .5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
margin-left: auto;
|
||||
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import React, {
|
||||
useContext, useState, useEffect, useReducer,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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 collapsibleReducer from './reducer';
|
||||
|
||||
const baseClass = 'field-repeater';
|
||||
|
||||
const Repeater = (props) => {
|
||||
const [newRowIndex, setNewRowIndex] = useState(null);
|
||||
const [rowCount, setRowCount] = useState(0);
|
||||
const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []);
|
||||
const formContext = useContext(FormContext);
|
||||
const [rowCount, setRowCount] = useState(0);
|
||||
const { fields: fieldState, dispatchFields } = formContext;
|
||||
|
||||
const {
|
||||
@@ -26,7 +29,10 @@ const Repeater = (props) => {
|
||||
type: 'ADD_ROW', rowIndex, name, fields,
|
||||
});
|
||||
|
||||
setNewRowIndex(rowIndex);
|
||||
dispatchCollapsibleStates({
|
||||
type: 'ADD_COLLAPSIBLE', collapsibleIndex: rowIndex,
|
||||
});
|
||||
|
||||
setRowCount(rowCount + 1);
|
||||
};
|
||||
|
||||
@@ -35,6 +41,11 @@ const Repeater = (props) => {
|
||||
type: 'REMOVE_ROW', rowIndex, name, fields,
|
||||
});
|
||||
|
||||
dispatchCollapsibleStates({
|
||||
type: 'REMOVE_COLLAPSIBLE',
|
||||
collapsibleIndex: rowIndex,
|
||||
});
|
||||
|
||||
setRowCount(rowCount - 1);
|
||||
};
|
||||
|
||||
@@ -42,14 +53,20 @@ const Repeater = (props) => {
|
||||
dispatchFields({
|
||||
type: 'MOVE_ROW', moveFromIndex, moveToIndex, name,
|
||||
});
|
||||
|
||||
dispatchCollapsibleStates({
|
||||
type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setRowCount(defaultValue.length);
|
||||
}, [defaultValue]);
|
||||
|
||||
const onBeforeCapture = () => {
|
||||
};
|
||||
dispatchCollapsibleStates({
|
||||
type: 'SET_ALL_COLLAPSIBLES',
|
||||
payload: Array.from(Array(defaultValue.length).keys()).reduce(acc => ([...acc, true]), []), // sets all collapsibles to open on first load
|
||||
});
|
||||
}, [defaultValue]);
|
||||
|
||||
const onDragEnd = (result) => {
|
||||
if (!result.destination) return;
|
||||
@@ -59,10 +76,7 @@ const Repeater = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DragDropContext
|
||||
onDragEnd={onDragEnd}
|
||||
onBeforeCapture={onBeforeCapture}
|
||||
>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div className={baseClass}>
|
||||
<Section
|
||||
heading={label}
|
||||
@@ -78,7 +92,7 @@ const Repeater = (props) => {
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{rowCount !== 0
|
||||
&& (Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||
return (
|
||||
<RepeaterRow
|
||||
key={rowIndex}
|
||||
@@ -88,11 +102,13 @@ const Repeater = (props) => {
|
||||
rowIndex={rowIndex}
|
||||
fieldState={fieldState}
|
||||
fields={fields}
|
||||
newRowIndex={newRowIndex}
|
||||
rowCount={rowCount}
|
||||
defaultValue={defaultValue[rowIndex]}
|
||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||
collapsibleStates={collapsibleStates}
|
||||
/>
|
||||
);
|
||||
}))
|
||||
})
|
||||
}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
|
||||
35
src/client/components/forms/field-types/Repeater/reducer.js
Normal file
35
src/client/components/forms/field-types/Repeater/reducer.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const collapsibleReducer = (currentState, action) => {
|
||||
const {
|
||||
type, collapsibleIndex, moveToIndex, payload,
|
||||
} = action;
|
||||
|
||||
const stateCopy = [...currentState];
|
||||
const movingCollapsibleState = stateCopy[collapsibleIndex];
|
||||
|
||||
switch (type) {
|
||||
case 'SET_ALL_COLLAPSIBLES':
|
||||
return payload;
|
||||
|
||||
case 'ADD_COLLAPSIBLE':
|
||||
stateCopy.splice(collapsibleIndex + 1, 0, true);
|
||||
return stateCopy;
|
||||
|
||||
case 'REMOVE_COLLAPSIBLE':
|
||||
stateCopy.splice(collapsibleIndex, 1);
|
||||
return stateCopy;
|
||||
|
||||
case 'UPDATE_COLLAPSIBLE_STATUS':
|
||||
stateCopy[collapsibleIndex] = !movingCollapsibleState;
|
||||
return stateCopy;
|
||||
|
||||
case 'MOVE_COLLAPSIBLE':
|
||||
stateCopy.splice(collapsibleIndex, 1);
|
||||
stateCopy.splice(moveToIndex, 0, movingCollapsibleState);
|
||||
return stateCopy;
|
||||
|
||||
default:
|
||||
return currentState;
|
||||
}
|
||||
};
|
||||
|
||||
export default collapsibleReducer;
|
||||
Reference in New Issue
Block a user