Skip to content

Commit

Permalink
feat: add Panel and ResizablePanel components (#536)
Browse files Browse the repository at this point in the history
  • Loading branch information
tenphi authored Dec 9, 2024
1 parent 792ec9f commit b10e55e
Show file tree
Hide file tree
Showing 7 changed files with 586 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-windows-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cube-dev/ui-kit': minor
---

Add Panel component.
5 changes: 5 additions & 0 deletions .changeset/silent-plants-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cube-dev/ui-kit': minor
---

Add ResizablePanel component.
193 changes: 193 additions & 0 deletions src/components/layout/Panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import {
createContext,
ForwardedRef,
forwardRef,
ReactNode,
useMemo,
} from 'react';

import {
BASE_STYLES,
BaseProps,
BaseStyleProps,
BLOCK_STYLES,
BlockStyleProps,
COLOR_STYLES,
ColorStyleProps,
DIMENSION_STYLES,
OUTER_STYLES,
OuterStyleProps,
Styles,
tasty,
} from '../../tasty';

export interface PanelContextData {
layout: 'grid' | 'flex';
}

export const PanelContext = createContext<PanelContextData>({
layout: 'grid',
});

const PanelElement = tasty({
as: 'section',
qa: 'Panel',
styles: {
position: {
'': 'relative',
'stretched | floating': 'absolute',
},
inset: {
'': 'initial',
stretched: true,
},
display: 'block',
radius: {
'': '0',
card: '1r',
},
border: {
'': '0',
card: '1bw',
},
flexGrow: 1,
},
});

const PanelInnerElement = tasty({
styles: {
position: 'absolute',
display: {
'': 'grid',
flex: 'flex',
},
top: 0,
left: 0,
right: 0,
bottom: 0,
overflow: 'auto',
styledScrollbar: true,
gridColumns: 'minmax(100%, 100%)',
gridRows: {
'': 'initial',
stretched: 'minmax(0, 1fr)',
},
radius: {
'': '0',
card: '(1r - 1bw)',
},
flow: 'row',
placeContent: 'start stretch',
},
styleProps: [...OUTER_STYLES, ...BASE_STYLES, ...COLOR_STYLES],
});

export interface CubePanelProps
extends OuterStyleProps,
BlockStyleProps,
BaseStyleProps,
ColorStyleProps,
BaseProps {
isStretched?: boolean;
isCard?: boolean;
isFloating?: boolean;
styles?: Styles;
innerStyles?: Styles;
placeContent?: Styles['placeContent'];
placeItems?: Styles['placeItems'];
gridColumns?: Styles['gridTemplateColumns'];
gridRows?: Styles['gridTemplateRows'];
flow?: Styles['flow'];
gap?: Styles['gap'];
isFlex?: boolean;
children?: ReactNode;
extra?: ReactNode;
}

const STYLES = [
'placeContent',
'placeItems',
'gridColumns',
'gridRows',
'flow',
'gap',
'padding',
'overflow',
'fill',
'color',
'preset',
] as const;

function Panel(props: CubePanelProps, ref: ForwardedRef<HTMLDivElement>) {
let {
qa,
mods,
isStretched,
isFloating,
isCard,
isFlex,
styles,
innerStyles,
children,
extra,
style,
...otherProps
} = props;

STYLES.forEach((style) => {
if (props[style]) {
innerStyles = { ...innerStyles, [style]: props[style] };
}
});

[
...OUTER_STYLES,
...BASE_STYLES,
...BLOCK_STYLES,
...COLOR_STYLES,
...DIMENSION_STYLES,
].forEach((style) => {
if (style in props) {
styles = { ...styles, [style]: props[style] };
}
});

const appliedMods = useMemo(
() => ({
floating: isFloating,
stretched: isStretched,
card: isCard,
flex: isFlex,
...mods,
}),
[isStretched, isCard, mods],
);

return (
<PanelContext.Provider
value={{
layout: isFlex ? 'flex' : 'grid',
}}
>
<PanelElement
ref={ref}
qa={qa}
mods={appliedMods}
styles={styles}
style={style}
{...otherProps}
>
<PanelInnerElement mods={appliedMods} styles={innerStyles}>
{children}
</PanelInnerElement>
{extra}
</PanelElement>
</PanelContext.Provider>
);
}

const _Panel = forwardRef(Panel);

_Panel.displayName = 'Panel';

export { _Panel as Panel };
88 changes: 88 additions & 0 deletions src/components/layout/ResizablePanel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Meta, StoryFn } from '@storybook/react';
import { useState } from 'react';

import { Panel } from './Panel';
import { ResizablePanel, CubeResizablePanelProps } from './ResizablePanel';

export default {
title: 'Layout/ResizablePanel',
component: ResizablePanel,
args: {},
} as Meta<CubeResizablePanelProps>;

const TemplateRight: StoryFn<CubeResizablePanelProps> = (args) => (
<Panel isFlex isStretched height="min 30x" fill="#white">
<ResizablePanel {...args} />
<Panel fill="#light"></Panel>
</Panel>
);

const TemplateLeft: StoryFn<CubeResizablePanelProps> = (args) => {
return (
<Panel isFlex isStretched height="min 30x" fill="#white">
<Panel fill="#light"></Panel>
<ResizablePanel {...args} />
</Panel>
);
};

const TemplateBottom: StoryFn<CubeResizablePanelProps> = (args) => (
<Panel isFlex isStretched flow="column" height="min 30x" fill="#white">
<ResizablePanel {...args} />
<Panel fill="#light"></Panel>
</Panel>
);

const TemplateTop: StoryFn<CubeResizablePanelProps> = (args) => {
return (
<Panel isFlex isStretched flow="column" height="min 30x" fill="#white">
<Panel fill="#light"></Panel>
<ResizablePanel {...args} />
</Panel>
);
};

const TemplateControllable: StoryFn<CubeResizablePanelProps> = (args) => {
const [size, setSize] = useState(200);

return (
<ResizablePanel
size={size}
flow="column"
height="min 30x"
fill="#light"
onSizeChange={(size) => setSize(Math.min(500, Math.max(100, size)))}
{...args}
></ResizablePanel>
);
};

export const ResizeRight = TemplateRight.bind({});
ResizeRight.args = {
direction: 'right',
};

export const ResizeLeft = TemplateLeft.bind({});
ResizeLeft.args = {
direction: 'left',
};

export const ResizeBottom = TemplateBottom.bind({});
ResizeBottom.args = {
direction: 'bottom',
};

export const ResizeTop = TemplateTop.bind({});
ResizeTop.args = {
direction: 'top',
};

export const Controllable = TemplateControllable.bind({});
Controllable.args = {
direction: 'right',
};

export const Disabled = TemplateRight.bind({});
Disabled.args = {
isDisabled: true,
};
Loading

0 comments on commit b10e55e

Please sign in to comment.