feat(Repeater): updates styles, enables header as the draggable point, moves collapsible state up to the repeater

This commit is contained in:
Jarrod Flesch
2020-03-20 12:31:46 -04:00
parent 39d2377078
commit 5304ab7332
4 changed files with 109 additions and 72 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>

View 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;