|
const { Component } = React; |
|
|
|
const ActionButtons = (props) => ( |
|
<div className="btn-group border-right" role="group" aria-label="Action buttons"> |
|
<button |
|
type="button" |
|
className="btn btn-primary" |
|
style={{ 'borderRadius': '0' }}> |
|
Action 1 |
|
</button> |
|
<button |
|
type="button" |
|
className="btn btn-secondary" |
|
style={{ 'borderRadius': '0' }}> |
|
Action 2 |
|
</button> |
|
<button |
|
type="button" |
|
className="btn btn-light" |
|
style={{ 'borderRadius': '0' }}> |
|
Action 3 |
|
</button> |
|
</div> |
|
); |
|
|
|
const SubActionButtons = (props) => ( |
|
<div className="col-12"> |
|
<div className="btn-group" role="group" aria-label="Sub-action buttons"> |
|
<button type="button" className="btn btn-success btn-sm"> |
|
Sub-action 1 |
|
</button> |
|
<button type="button" className="btn btn-danger btn-sm"> |
|
Sub-action 2 |
|
</button> |
|
</div> |
|
</div> |
|
); |
|
|
|
const Header = (props) => ( |
|
<div |
|
className="row border-bottom sticky-top" |
|
style={{ 'backgroundColor': 'Gainsboro' }}> |
|
<div className="col-sm" style={{ 'padding': '0' }}> |
|
<ActionButtons /> |
|
</div> |
|
<div className="col-sm"> |
|
<h4 className="text-center font-weight-light font-italic"> |
|
Project Title |
|
</h4> |
|
</div> |
|
<div className="col-sm"></div> |
|
</div> |
|
); |
|
|
|
const SidebarElement = (props) => { |
|
const headingHeight = `${!!props.subActionBtns ? 70 : 30}px`; |
|
return ( |
|
<div className="row d-flex flex-fill"> |
|
<div className="col border-left border-bottom position-relative"> |
|
<div |
|
className="row w-100 border-bottom position-fixed" |
|
style={{ 'backgroundColor': 'Azure', 'height': headingHeight }}> |
|
<div className="col-12"> |
|
<h5 className="font-weight-bold" style={{ 'marginBottom': '0.1rem' }}> |
|
{props.heading || 'Sidebar Heading'} |
|
</h5> |
|
</div> |
|
{!!props.subActionBtns ? <SubActionButtons /> : null} |
|
</div> |
|
<div |
|
className="row w-100 position-absolute" |
|
style={{ 'top': headingHeight, 'height': `calc(100% - ${headingHeight})` }}> |
|
<div className="col d-flex flex-column"> |
|
<div className="row d-flex flex-fill" style={{ 'overflow': 'auto' }}> |
|
<div style={{ 'padding': '0.5rem 1rem 1rem 1rem', 'height': '1000px' }}> |
|
{props.content || 'Sidebar Content'} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
const PrimaryContent = (props) => ( |
|
<div |
|
className="d-flex flex-fill border shadow" |
|
style={{ 'margin': '8px', 'borderRadius': '5px' }}> |
|
<h5 className="font-weight-bold" style={{ 'paddingLeft': '1rem' }}> |
|
Primary Component |
|
</h5> |
|
</div> |
|
); |
|
|
|
const secondaryComponentWidth = { |
|
square: 4, |
|
rectangle: 8, |
|
}; |
|
|
|
const SecondaryContent = (props) => ( |
|
<div |
|
className="d-flex flex-fill" |
|
style={{ |
|
'margin': '0rem 0.5rem 0.5rem 0.5rem', |
|
'padding': '0.25rem 0.5rem 0.5rem 0.5rem', |
|
'borderRadius': '5px', |
|
'backgroundColor': `${props.color || 'LightSkyBlue'}` |
|
}}> |
|
{`Secondary Content Color: ${props.color}` || 'Default Secondary Content'} |
|
</div> |
|
); |
|
|
|
const secondaryComponentColors = { |
|
square: [ |
|
'LightCoral', |
|
'LightPink', |
|
'LightSalmon', |
|
'MediumVioletRed', |
|
'OrangeRed', |
|
], |
|
rectangle: [ |
|
'SeaGreen', |
|
'MediumAquaMarine', |
|
'DarkCyan', |
|
'CadetBlue', |
|
'PaleGreen', |
|
], |
|
}; |
|
|
|
const secondaryComponentOptions = _.reduce( |
|
secondaryComponentColors, |
|
(acc, colors, key) => { |
|
acc[key] = _.map( |
|
colors, |
|
(color, index) => ({ |
|
heading: `${_.upperFirst(key)} ${index} Heading`, |
|
content: <SecondaryContent color={color} />, |
|
}) |
|
); |
|
return acc; |
|
}, |
|
{} |
|
); |
|
|
|
const SecondaryComponent = (props) => { |
|
const colWidth = `col-sm-${props.width + 2} col-md-${props.width}`; |
|
return ( |
|
<div |
|
className={`col-xs-12 ${colWidth} border shadow position-relative`} |
|
style={{ |
|
'borderRadius': '5px', |
|
}}> |
|
<div |
|
className="row w-100 position-absolute" |
|
style={{ 'overflow': 'hidden', 'height': '28px' }}> |
|
<h5 className="font-weight-bold" style={{ 'paddingLeft': '1rem' }}> |
|
{props.heading || 'Secondary Content Heading'} |
|
</h5> |
|
</div> |
|
<div |
|
className="row w-100 position-absolute" |
|
style={{ 'top': '28px', 'height': 'calc(100% - 28px)' }}> |
|
<div className="col d-flex flex-column"> |
|
<div className="row d-flex flex-fill" style={{ 'overflow': 'auto' }}> |
|
{!!props.content ? props.content : <SecondaryContent />} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
const SelectorSizeViz = (props) => ( |
|
<div className="row"> |
|
{_.map( |
|
_.range(12), |
|
(index) => ( |
|
<div |
|
key={index} |
|
className="col-1" |
|
style={{ |
|
'padding': '1px', |
|
'height': '50px', |
|
'borderColor': 'LightGray', |
|
'borderStyle': 'solid', |
|
'borderWidth': `${ |
|
index === 0 || index === props.size |
|
? '5px 0px 5px 5px' |
|
: index === 11 |
|
? '5px 5px 5px 0px' |
|
: '5px 0px 5px 0px' |
|
}`, |
|
'backgroundColor': `${index < props.size ? 'LightSlateGray' : ''}` |
|
}}> |
|
</div> |
|
) |
|
)} |
|
</div> |
|
); |
|
|
|
class SelectorWorkflow extends Component { |
|
constructor(props) { |
|
super(props); |
|
this.state = { |
|
secondaryComponentType: '', |
|
secondaryComponentTypeDisplay: '', |
|
secondaryComponent: {}, |
|
enableComponentSelector: false, |
|
enableComponentSetter: false, |
|
}; |
|
this.setSecondaryComponentType = this.setSecondaryComponentType.bind(this); |
|
this.setSecondaryComponent = this.setSecondaryComponent.bind(this); |
|
this.setEnabledStatus = this.setEnabledStatus.bind(this); |
|
this.clearSelection = this.clearSelection.bind(this); |
|
} |
|
|
|
setSecondaryComponentType(type) { |
|
this.setState({ |
|
secondaryComponentType: type, |
|
secondaryComponentTypeDisplay: type === 'square' |
|
? 'Square Component' |
|
: 'Rectangular Component', |
|
}); |
|
} |
|
|
|
setSecondaryComponent(secondaryComponent) { |
|
this.setState({ secondaryComponent: secondaryComponent }); |
|
} |
|
|
|
setEnabledStatus(statusUpdate) { |
|
this.setState({ [statusUpdate]: true }); |
|
} |
|
|
|
clearSelection() { |
|
this.setState({ |
|
secondaryComponentType: '', |
|
secondaryComponentTypeDisplay: '', |
|
secondaryComponent: {}, |
|
enableComponentSelector: false, |
|
enableComponentSetter: false, |
|
}); |
|
} |
|
|
|
render() { |
|
return ( |
|
<div> |
|
<div className="dropdown"> |
|
<button |
|
className="btn btn-secondary dropdown-toggle" |
|
type="button" |
|
id="dropdownMenuButton" |
|
data-toggle="dropdown" |
|
aria-haspopup="true" |
|
aria-expanded="false"> |
|
{this.state.secondaryComponentTypeDisplay || 'Select Component Type'} |
|
</button> |
|
<div className="dropdown-menu" aria-labelledby="dropdownMenuButton"> |
|
<div |
|
className="dropdown-item" |
|
style={{ |
|
'cursor': 'pointer', |
|
'width': '225px', |
|
}} |
|
onClick={() => { |
|
this.setSecondaryComponentType('square'); |
|
this.setEnabledStatus('enableComponentSelector'); |
|
}}> |
|
<SelectorSizeViz size={secondaryComponentWidth['square']} /> |
|
</div> |
|
{this.props.currentWidth <= 4 |
|
? <div |
|
className="dropdown-item" |
|
style={{ |
|
'cursor': 'pointer', |
|
'width': '225px' |
|
}} |
|
onClick={() => { |
|
this.setSecondaryComponentType('rectangle'); |
|
this.setEnabledStatus('enableComponentSelector'); |
|
}}> |
|
<SelectorSizeViz size={secondaryComponentWidth['rectangle']} /> |
|
</div> |
|
: null} |
|
</div> |
|
</div> |
|
<div className="d-flex flex-fill" style={{ 'paddingTop': '5px' }}> |
|
{this.state.enableComponentSelector |
|
? <div className="dropdown"> |
|
<button |
|
className="btn btn-secondary dropdown-toggle" |
|
type="button" |
|
id="dropdownMenuButton" |
|
data-toggle="dropdown" |
|
aria-haspopup="true" |
|
aria-expanded="false"> |
|
{this.state.secondaryComponent |
|
&& this.state.secondaryComponent.heading |
|
|| 'Select Component'} |
|
</button> |
|
<div className="dropdown-menu" aria-labelledby="dropdownMenuButton"> |
|
{_.map( |
|
secondaryComponentOptions[this.state.secondaryComponentType], |
|
(componentOption, index) => ( |
|
<div |
|
key={`${this.state.secondaryComponentType}_${index}`} |
|
className="dropdown-item" |
|
style={{ 'cursor': 'pointer' }} |
|
onClick={() => { |
|
this.setSecondaryComponent(componentOption); |
|
this.setEnabledStatus('enableComponentSetter'); |
|
}}> |
|
{componentOption.heading} |
|
</div> |
|
) |
|
)} |
|
</div> |
|
</div> |
|
: <div className="dropdown"> |
|
<button |
|
className="btn btn-secondary dropdown-toggle" |
|
type="button" |
|
id="dropdownMenuButton" |
|
data-toggle="dropdown" |
|
aria-haspopup="true" |
|
aria-expanded="false" |
|
disabled> |
|
Select Component |
|
</button> |
|
<div className="dropdown-menu" aria-labelledby="dropdownMenuButton"></div> |
|
</div>} |
|
</div> |
|
<div className="d-flex flex-fill" style={{ 'paddingTop': '5px' }}> |
|
{this.state.enableComponentSetter |
|
? <div className="btn-group" role="group" aria-label="Sub-action buttons"> |
|
<button |
|
type="button" |
|
className="btn btn-success" |
|
onClick={() => this.props.updateSecondaryComponents( |
|
this.state.secondaryComponentType, |
|
this.state.secondaryComponent |
|
)}> |
|
Add Component |
|
</button> |
|
<button |
|
type="button" |
|
className="btn btn-danger" |
|
onClick={this.clearSelection}> |
|
Clear |
|
</button> |
|
</div> |
|
: <div className="btn-group" role="group" aria-label="Sub-action buttons"> |
|
<button type="button" className="btn btn-success" disabled> |
|
Add Component |
|
</button> |
|
<button type="button" className="btn btn-danger" disabled> |
|
Clear |
|
</button> |
|
</div>} |
|
</div> |
|
</div> |
|
); |
|
} |
|
} |
|
|
|
const SelectorContent = (props) => { |
|
return ( |
|
<div style={{ 'padding': '0.25rem 0.5rem 0.5rem 1rem' }}> |
|
<div className="d-flex flex-fill" style={{ 'paddingTop': '5px' }}> |
|
<SelectorWorkflow {...props} /> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
const SecondaryComponentSelector = (props) => { |
|
return <SecondaryComponent |
|
width={secondaryComponentWidth['square']} |
|
heading={'Add New Component'} |
|
content={SelectorContent(props)} />; |
|
} |
|
|
|
const SecondaryComponentDeleter = (props) => ( |
|
<div className="position-fixed" style={{ 'bottom': '2px', 'left': '1px' }}> |
|
<div className="dropup"> |
|
<button |
|
className="btn btn-dark dropdown-toggle" |
|
type="button" |
|
id="dropdownMenuButton" |
|
data-toggle="dropdown" |
|
aria-haspopup="true" |
|
aria-expanded="false"> |
|
<i className="fa fa-trash" aria-hidden="true"></i> |
|
</button> |
|
<div className="dropdown-menu" aria-labelledby="dropdownMenuButton"> |
|
<div className="dropdown-item" style={{ 'width': '300px' }}> |
|
<div className="row"> |
|
{_.map( |
|
_.filter(props.widths), |
|
(width, index, widths) => { |
|
const isLast = index === widths.length - 1; |
|
return ( |
|
<div |
|
key={index} |
|
className={`col-${width} delete-mask`} |
|
style={{ |
|
'padding': '1px', |
|
'height': '75px', |
|
'borderColor': 'LightGray', |
|
'borderStyle': 'solid', |
|
'borderWidth': isLast ? '5px' : '5px 0px 5px 5px', |
|
'backgroundColor': 'LightSlateGray', |
|
'cursor': 'pointer', |
|
}} |
|
onClick={() => props.deleteSecondaryComponent(index)}> |
|
</div> |
|
); |
|
} |
|
)} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
|
|
class VariableGrid extends Component { |
|
constructor(props) { |
|
super(props); |
|
this.state = { |
|
componentCount: 0, |
|
secondaryComponents: [], |
|
showSelector: false, |
|
showDeleteOption: false, |
|
}; |
|
this.updateSecondaryComponents = this.updateSecondaryComponents.bind(this); |
|
this.deleteSecondaryComponent = this.deleteSecondaryComponent.bind(this); |
|
} |
|
|
|
updateSecondaryComponents(secondaryComponentType, secondaryComponent, components) { |
|
let secondaryComponents = components || _.cloneDeep(this.state.secondaryComponents); |
|
let componentCount = _.clone(this.state.componentCount); |
|
let visibleComponents = []; |
|
|
|
if (this.state.showSelector) { |
|
// dropRight to exclude the selector component |
|
secondaryComponents = _.dropRight(secondaryComponents); |
|
} |
|
|
|
if (!_.isEmpty(secondaryComponentType) && !_.isEmpty(secondaryComponent)) { |
|
// Add new component passed in from params |
|
componentCount = componentCount + 1; |
|
visibleComponents.push({ |
|
componentType: secondaryComponentType, |
|
component: |
|
<SecondaryComponent |
|
key={`secondary_${componentCount}`} |
|
width={secondaryComponentWidth[secondaryComponentType]} |
|
heading={secondaryComponent.heading} |
|
content={secondaryComponent.content} />, |
|
}); |
|
} |
|
|
|
// incoporate other visible (non-selector) components |
|
visibleComponents = _.flatMap(secondaryComponents.concat(visibleComponents)); |
|
|
|
const currentWidth = _.sumBy( |
|
visibleComponents, |
|
c => secondaryComponentWidth[c.componentType] |
|
); |
|
|
|
// a length of 8 or less means another component can be added (selector not counted) |
|
const showSelector = currentWidth <= 8; |
|
|
|
if (showSelector) { |
|
componentCount = componentCount + 1; |
|
visibleComponents.push({ |
|
componentType: 'square', |
|
component: |
|
<SecondaryComponentSelector |
|
width={0} |
|
currentWidth={currentWidth} |
|
key={`secondary_${componentCount}`} |
|
updateSecondaryComponents={this.updateSecondaryComponents} />, |
|
}); |
|
} |
|
|
|
this.setState({ |
|
componentCount: componentCount, |
|
secondaryComponents: visibleComponents, |
|
showSelector: showSelector, |
|
showDeleteOption: currentWidth >= 4, |
|
}); |
|
} |
|
|
|
deleteSecondaryComponent(componentIndex) { |
|
const currentComponents = _.cloneDeep(this.state.secondaryComponents); |
|
this.updateSecondaryComponents(null, null, _.reject( |
|
currentComponents, |
|
(obj, index) => index === componentIndex |
|
)); |
|
} |
|
|
|
componentDidMount() { |
|
this.updateSecondaryComponents(); |
|
} |
|
|
|
render() { |
|
return ( |
|
<div className="container-fluid h-100 d-flex flex-column"> |
|
<Header /> |
|
<div className="row d-flex flex-fill"> |
|
<div className="col-xs-12 col-sm-7 col-md-9 d-flex flex-column"> |
|
<div className="row d-flex flex-fill"> |
|
<PrimaryContent /> |
|
</div> |
|
<div className="row d-flex flex-fill" style={{ 'padding': '0px 8px 8px 8px' }}> |
|
{_.map(this.state.secondaryComponents, 'component')} |
|
</div> |
|
</div> |
|
<div className="col-xs-12 col-sm-5 col-md-3 border-left d-flex flex-column"> |
|
<SidebarElement |
|
heading={'Sidebar Heading 1'} |
|
content={'Sidebar Content 1'} /> |
|
<SidebarElement |
|
heading={'Sidebar Heading 2'} |
|
content={'Sidebar Content 2'} |
|
subActionBtns={true} /> |
|
</div> |
|
</div> |
|
{this.state.showDeleteOption |
|
? <SecondaryComponentDeleter |
|
widths={_.flatMap(this.state.secondaryComponents, 'component.props.width')} |
|
deleteSecondaryComponent={this.deleteSecondaryComponent} /> |
|
: null} |
|
</div> |
|
); |
|
} |
|
} |
|
|
|
ReactDOM.render(<VariableGrid />, document.getElementById('root')); |