import * as React from 'react'
import { PropertyControls, ControlType, FrameProperties, Frame } from 'framer'
/**
* Default content, displayed when component has no connected frames
*
*/
const DefaultContent = () => {
return (
<div
style={{
height: '100%',
display: 'grid',
gridTemplateRows: '1fr min-content min-content min-content 1fr',
gridGap: '8px',
alignItems: 'center',
color: '#8855FF',
padding: '0 16px',
background: 'rgba(136, 85, 255, 0.1)',
overflow: 'hidden',
flexDirection: 'column',
}}
>
<div />
<div>Connect your child frames</div>
<div>Set your layout's template sizes</div>
<div>Set your layout's auto sizes</div>
<div />
</div>
)
}
interface Props extends FrameProperties {
direction: string
template: string[]
auto: string[]
gap: string
frames: any[]
}
/**
* A single-column (or single-row) layout engine, driven by CSS Grids
*
* @export
* @class Layout
* @extends {React.Component<Props>}
*/
export class SingleLayout extends React.Component<Props> {
containerRef = React.createRef() as React.RefObject<HTMLDivElement>
// Set default properties
static defaultProps = {
direction: 'column',
template: ['1fr'],
auto: ['1fr'],
gap: '0px',
frames: [],
}
// Items shown in property panel
static propertyControls: PropertyControls = {
direction: {
type: ControlType.SegmentedEnum,
title: 'Direction',
options: ['column', 'row'],
optionTitles: ['Column', 'Row'],
defaultValue: 'column',
},
template: {
type: ControlType.Array,
title: 'Template',
propertyControl: { type: ControlType.String, defaultValue: '1fr' },
defaultValue: ['1fr'],
},
auto: {
type: ControlType.Array,
title: 'Auto',
propertyControl: { type: ControlType.String, defaultValue: '1fr' },
defaultValue: ['1fr'],
},
gap: {
type: ControlType.String,
title: 'Gap',
defaultValue: '0',
},
frames: {
type: ControlType.Array,
title: 'Frames',
propertyControl: { type: ControlType.ComponentInstance },
defaultValue: [],
},
}
state = {
frame: 0,
mappedFrames: [],
}
componentWillReceiveProps() {
setTimeout(this.mapFramesOntoDivs, 36)
}
componentDidMount() {
setTimeout(this.mapFramesOntoDivs, 36)
}
mapFramesOntoDivs = () => {
const { frames } = this.props
const parent = this.containerRef.current as HTMLDivElement
const nodes = Array.from(parent.childNodes)
const mappedFrames = nodes.map((node, i) => {
const div = node as HTMLDivElement
const frame = frames[i]
const props = {
top: div.offsetTop,
left: div.offsetLeft,
right: div.offsetLeft + div.offsetWidth,
width: div.offsetWidth,
height: div.offsetHeight,
}
return (
frame && (
<Frame key={'_parent' + frame.props.id} {...props}>
{React.cloneElement(frame, {
...frame.props,
key: 'node_' + frame.props.id,
top: 0,
left: 0,
width: '100%',
height: '100%',
aspectRatio: null,
})}
</Frame>
)
)
})
this.setState({ frames, mappedFrames })
}
render() {
const { containerRef } = this
const { mappedFrames } = this.state
const {
frames,
direction,
template,
auto,
gap,
height,
width,
name,
} = this.props
const toString: (array: string[]) => string = array =>
array.map(r => r).join(' ')
const directionProps =
direction === 'row'
? {
gridAutoFlow: 'column',
gridTemplateColumns: toString(template),
gridAutoColumns: toString(auto),
}
: {
gridAutoFlow: 'row',
gridTemplateRows: toString(template),
gridAutoRows: toString(auto),
}
return (
<div
ref={containerRef}
style={{
position: 'relative',
height: height + 'px',
width: width + 'px',
overflow: 'hidden',
display: 'grid',
gridGap: gap,
backgroundColor: 'none',
...directionProps,
}}
>
{frames.length === 0 ? (
<DefaultContent />
) : (
frames.map((n, i) => (
<div key={`${name}_template_${i}`} style={{ opacity: 0 }} />
))
)}
{mappedFrames}
</div>
)
}
}