steveruizok
1/9/2019 - 4:07 PM

Layout.tsx

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>
		)
	}
}