Skip to content

Instantly share code, notes, and snippets.

@hipstersmoothie
Last active March 27, 2022 07:46
Show Gist options
  • Save hipstersmoothie/e14962361163896e0202505d064619b7 to your computer and use it in GitHub Desktop.
Save hipstersmoothie/e14962361163896e0202505d064619b7 to your computer and use it in GitHub Desktop.
Grid Template Component
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