Last active
March 27, 2022 07:46
-
-
Save hipstersmoothie/e14962361163896e0202505d064619b7 to your computer and use it in GitHub Desktop.
Grid Template Component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
export default { | |
title: 'Components/Grid', | |
}; | |
type FilterString<S extends string, T extends string> = S extends T ? never : S; | |
type Split<S extends string, D extends string> = string extends S | |
? string[] | |
: S extends '' | |
? [] | |
: S extends `${infer T}${D}${infer U}` | |
? [T, ...Split<U, D>] | |
: [S]; | |
function useGridTemplate<T extends string>(template: T) { | |
type SplitLines = Split<T, '\n'>[number]; | |
type WithoutFourSpace = Split<SplitLines, ' '>[number]; | |
type WithoutTripleSpace = Split<WithoutFourSpace, ' '>[number]; | |
type WithoutDoubleSpace = Split<WithoutTripleSpace, ' '>[number]; | |
type SplitTokens = Split<WithoutDoubleSpace, ' '>[number]; | |
type AreaName = FilterString<SplitTokens, ''>; | |
const RootComponentRef = React.useRef( | |
({ | |
style, | |
columns, | |
rows, | |
...props | |
}: React.ComponentProps<'div'> & { | |
columns?: React.CSSProperties['gridTemplateColumns']; | |
rows?: React.CSSProperties['gridTemplateRows']; | |
}) => { | |
return ( | |
<div | |
{...props} | |
style={{ | |
...style, | |
display: 'grid', | |
gridTemplateColumns: columns, | |
gridTemplateRows: rows, | |
gridTemplateAreas: template | |
.trim() | |
.split('\n') | |
.map((area) => `"${area}"`) | |
.join('\n'), | |
}} | |
/> | |
); | |
}, | |
); | |
const ItemComponentRef = React.useRef( | |
React.forwardRef(function ItemComponentRef( | |
{ style, area, ...props }: { area: AreaName } & React.ComponentProps<'div'>, | |
ref?: React.Ref<HTMLDivElement>, | |
) { | |
return <div ref={ref} {...props} style={{ ...style, gridArea: area }} />; | |
}), | |
); | |
const Resizer = React.useRef( | |
({ | |
style, | |
area, | |
onResize, | |
dimension, | |
onResizeFinished, | |
...props | |
}: { | |
area: AreaName; | |
dimension: 'horizontal' | 'vertical'; | |
onResize: (amount: number) => void; | |
onResizeFinished?: () => void; | |
} & React.ComponentProps<'div'>) => { | |
return ( | |
// eslint-disable-next-line jsx-a11y/no-static-element-interactions | |
<div | |
{...props} | |
style={{ ...style, gridArea: area }} | |
onMouseDown={function onMouseDown() { | |
function onMouseMove(e: MouseEvent) { | |
onResize(dimension === 'horizontal' ? e.movementX : e.movementY); | |
} | |
function onMouseUp() { | |
document.removeEventListener('mousemove', onMouseMove); | |
onResizeFinished?.(); | |
} | |
document.addEventListener('mouseup', onMouseUp, { once: true }); | |
document.addEventListener('mousemove', onMouseMove); | |
}} | |
/> | |
); | |
}, | |
); | |
return { | |
Root: RootComponentRef.current, | |
Item: ItemComponentRef.current, | |
Resizer: Resizer.current, | |
}; | |
} | |
export const DescriptUI = () => { | |
const scriptRef = React.useRef<HTMLDivElement>(null); | |
const timelineRef = React.useRef<HTMLDivElement>(null); | |
const [scriptSize, scriptSizeSet] = React.useState(700); | |
const [timelineSize, timelineSizeSet] = React.useState(200); | |
const Grid = useGridTemplate(` | |
header header header header | |
script scriptResize video properties | |
timelineResize timelineResize timelineResize timelineResize | |
timeline timeline timeline timeline | |
`); | |
return ( | |
<Grid.Root | |
columns={`minmax(400px, ${scriptSize}px) 10px minmax(350px, 1fr) 254px`} | |
rows={`50px minmax(400px, 1fr) 10px minmax(150px, ${timelineSize}px)`} | |
style={{ height: 'calc(100vh - 30px)', width: '100vw' }} | |
> | |
<Grid.Item area="header" style={{ background: 'yellow' }}> | |
Header | |
</Grid.Item> | |
<Grid.Item ref={scriptRef} area="script" style={{ background: 'red' }}> | |
Script | |
</Grid.Item> | |
<Grid.Resizer | |
area="scriptResize" | |
dimension="horizontal" | |
style={{ background: 'grey', cursor: 'ew-resize' }} | |
onResize={(amount) => { | |
scriptSizeSet((s) => s + amount); | |
}} | |
onResizeFinished={() => { | |
if (!scriptRef.current) { | |
return; | |
} | |
scriptSizeSet(scriptRef.current.clientWidth); | |
}} | |
/> | |
<Grid.Item area="video" style={{ background: 'lightblue' }}> | |
Video | |
</Grid.Item> | |
<Grid.Item area="properties" style={{ background: 'green' }}> | |
Properties | |
</Grid.Item> | |
<Grid.Resizer | |
area="timelineResize" | |
dimension="vertical" | |
style={{ background: 'grey', cursor: 'ns-resize' }} | |
onResize={(amount) => { | |
timelineSizeSet((s) => s - amount); | |
}} | |
onResizeFinished={() => { | |
if (!timelineRef.current) { | |
return; | |
} | |
timelineSizeSet(timelineRef.current.clientHeight); | |
}} | |
/> | |
<Grid.Item ref={timelineRef} area="timeline" style={{ background: 'pink' }}> | |
Timeline | |
</Grid.Item> | |
</Grid.Root> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment