Skip to content

Commit

Permalink
feat: side panel
Browse files Browse the repository at this point in the history
  • Loading branch information
gndz07 authored Dec 13, 2024
1 parent 3a475d4 commit bec2646
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 13 deletions.
127 changes: 127 additions & 0 deletions components/SidePanel/SidePanel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Meta, StoryFn } from '@storybook/react/*';
import { useState } from 'react';

import { Box } from '../Box';
import { Button } from '../Button';
import { Dialog, DialogContent, DialogOverlay, DialogPortal } from '../Dialog';
import { Text } from '../Text';
import { SidePanel } from './SidePanel';

const Component: Meta<typeof SidePanel> = {
title: 'Components/SidePanel',
component: SidePanel,
argTypes: {
side: {
options: ['right', 'left', 'top', 'bottom'],
control: { type: 'radio' },
},
},
};

const Content = ({ onClickBtn, ctaLabel }: { onClickBtn?: () => void; ctaLabel?: string }) => {
const [count, setCount] = useState(1);

return (
<>
<Text size={2} as="h3" css={{ mb: '$3' }}>
Hello, World!
</Text>
{[...Array(count)].map((_, i) => (
<Text key={i} css={{ mb: '$1' }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</Text>
))}
<Button
css={{ mt: '$3' }}
onClick={() => (onClickBtn ? onClickBtn() : setCount((c) => c + 1))}
>
{ctaLabel || 'Do some actions'}
</Button>
</>
);
};

export const Basic: StoryFn<typeof SidePanel> = (args) => {
const [open, setOpen] = useState(false);

return (
<>
<Box>
<Button onClick={() => setOpen(true)}>Open side panel</Button>
<Box css={{ mt: '$4' }}>
{[...Array(10)].map((_, i) => (
<Text key={i} css={{ my: '$1' }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</Text>
))}
</Box>
</Box>

<SidePanel {...args} open={open} onOpenChange={(isOpen) => setOpen(isOpen)}>
<Content />
</SidePanel>
</>
);
};

Basic.args = {
noOverlay: false,
side: 'right',
noCloseIcon: false,
};

export const CombinedWithModal: StoryFn<typeof SidePanel> = (args) => {
const [open, setOpen] = useState(false);
const [isModalOpen, setModalOpen] = useState(false);

return (
<>
<Dialog open={isModalOpen} onOpenChange={(isModalOpen) => setModalOpen(isModalOpen)}>
<DialogPortal>
<DialogOverlay />
<DialogContent>
<Text css={{ m: '$2', mt: '$4' }}>This is a modal from the side panel</Text>
</DialogContent>
</DialogPortal>
</Dialog>

<SidePanel {...args} open={open} onOpenChange={(isOpen) => setOpen(isOpen)}>
<Content ctaLabel="Open modal" onClickBtn={() => setModalOpen(true)} />
</SidePanel>

<Box>
<Button onClick={() => setOpen(true)}>Open side panel</Button>
<Box css={{ mt: '$4' }}>
{[...Array(10)].map((_, i) => (
<Text key={i} css={{ my: '$1' }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</Text>
))}
</Box>
</Box>
</>
);
};

CombinedWithModal.args = {
noOverlay: true,
side: 'right',
noCloseIcon: true,
};

export default Component;
19 changes: 19 additions & 0 deletions components/SidePanel/SidePanel.themes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Property } from '@stitches/react/types/css';

import { ColorInfo } from '../../utils/getPrimaryColorInfo';

export namespace Theme {
type Colors = {
sidePanelBackground: Property.Color;
};

type Factory = (primaryColor?: ColorInfo) => Colors;

export const getLight: Factory = () => ({
sidePanelBackground: '$deepBlue2',
});

export const getDark: Factory = () => ({
sidePanelBackground: '$deepBlue3',
});
}
152 changes: 152 additions & 0 deletions components/SidePanel/SidePanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Cross1Icon } from '@radix-ui/react-icons';
import React, { ComponentProps } from 'react';

import { CSS, keyframes, styled, VariantProps } from '../../stitches.config';
import { IconButton } from '../IconButton';
import { overlayStyles } from '../Overlay';

const fadeIn = keyframes({
from: { opacity: '0' },
to: { opacity: '1' },
});

const fadeOut = keyframes({
from: { opacity: '1' },
to: { opacity: '0' },
});

const SidePanelOverlay = styled(DialogPrimitive.Overlay, overlayStyles, {
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0,

'&[data-state="open"]': {
animation: `${fadeIn} 150ms cubic-bezier(0.22, 1, 0.36, 1)`,
},

'&[data-state="closed"]': {
animation: `${fadeOut} 150ms cubic-bezier(0.22, 1, 0.36, 1)`,
},
});

const slideIn = keyframes({
from: { transform: '$$transformValue' },
to: { transform: 'translate3d(0,0,0)' },
});

const slideOut = keyframes({
from: { transform: 'translate3d(0,0,0)' },
to: { transform: '$$transformValue' },
});

const StyledContent = styled(DialogPrimitive.Content, {
backgroundColor: '$sidePanelBackground',
boxShadow: 'inset 0 0 0 1px $colors$deepBlue4',
position: 'fixed',
top: 0,
bottom: 0,
width: 375,
p: '$4',

'&[data-state="open"]': {
animation: `${slideIn} 150ms cubic-bezier(0.22, 1, 0.36, 1)`,
},

'&[data-state="closed"]': {
animation: `${slideOut} 150ms cubic-bezier(0.22, 1, 0.36, 1)`,
},

variants: {
side: {
top: {
$$transformValue: 'translate3d(0,-100%,0)',
width: '100%',
height: 250,
bottom: 'auto',
},
right: {
$$transformValue: 'translate3d(100%,0,0)',
right: 0,
maxWidth: '50%',
},
bottom: {
$$transformValue: 'translate3d(0,100%,0)',
width: '100%',
height: 250,
bottom: 0,
top: 'auto',
},
left: {
$$transformValue: 'translate3d(-100%,0,0)',
left: 0,
maxWidth: '50%',
},
},
},

defaultVariants: {
side: 'right',
},
});

const StyledCloseButton = styled(DialogPrimitive.Close, {
position: 'absolute',
top: '$2',
right: '$2',
cursor: 'pointer',
});

interface SidePanelCloseButtonProps
extends VariantProps<typeof IconButton>,
ComponentProps<typeof IconButton> {}

const SidePanelCloseIconButton = React.forwardRef<
React.ElementRef<typeof IconButton>,
SidePanelCloseButtonProps
>((props, forwardedRef) => (
<StyledCloseButton asChild>
<IconButton ref={forwardedRef} css={{ color: '$hiContrast' }} {...props}>
<Cross1Icon />
</IconButton>
</StyledCloseButton>
));

type SidePanelContentVariants = VariantProps<typeof StyledContent>;
type SidePanelContentPrimitiveProps = React.ComponentProps<typeof DialogPrimitive.Content>;
type SidePanelProps = SidePanelContentPrimitiveProps &
SidePanelContentVariants & {
css?: CSS;
noOverlay?: boolean;
noCloseIcon?: boolean;
open?: boolean;
defaultOpen?: boolean;
onOpenChange(open: boolean): void;
};

export const SidePanel = React.forwardRef<React.ElementRef<typeof StyledContent>, SidePanelProps>(
(
{
noOverlay = false,
noCloseIcon = false,
children,
onOpenChange,
open = false,
defaultOpen = false,
...props
},
forwardedRef,
) => (
<DialogPrimitive.Root open={open} onOpenChange={onOpenChange} defaultOpen={defaultOpen}>
<DialogPrimitive.Portal>
{!noOverlay && <SidePanelOverlay />}
<StyledContent {...props} ref={forwardedRef}>
{children}
{!noCloseIcon && <SidePanelCloseIconButton />}
</StyledContent>
</DialogPrimitive.Portal>
</DialogPrimitive.Root>
),
);
1 change: 1 addition & 0 deletions components/SidePanel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SidePanel';
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export {
RadioAccordionTrigger,
} from './components/RadioAccordion';
export { Select } from './components/Select';
export { SidePanel } from './components/SidePanel';
export { Skeleton } from './components/Skeleton';
export { Switch } from './components/Switch';
export { Caption, Table, Tbody, Td, Tfoot, Th, Thead, Tr } from './components/Table';
Expand Down
28 changes: 15 additions & 13 deletions stitches.config.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import type * as Stitches from '@stitches/react';
import { createStitches, CSS as StitchesCSS } from '@stitches/react';
import { Property } from '@stitches/react/types/css';

import { darkColors, lightColors } from './colors';
import { Theme as AccordionTheme } from './components/Accordion/Accordion.themes';
import { Theme as BadgeTheme } from './components/Badge/Badge.themes';
import { Theme as ButtonTheme } from './components/Button/Button.themes';
import { Theme as ButtonSwitchTheme } from './components/ButtonSwitch/ButtonSwitch.themes';
import { Theme as IconButtonTheme } from './components/IconButton/IconButton.themes';
import { Theme as SwitchTheme } from './components/Switch/Switch.themes';
import { Theme as CardTheme } from './components/Card/Card.themes';
import { Theme as CheckboxTheme } from './components/Checkbox/Checkbox.themes';
import { Theme as DialogTheme } from './components/Dialog/Dialog.themes';
import { Theme as HeadingTheme } from './components/Heading/Heading.themes';
import { Theme as IconButtonTheme } from './components/IconButton/IconButton.themes';
import { Theme as InputTheme } from './components/Input/Input.themes';
import { Theme as LinkTheme } from './components/Link/Link.themes';
import { Theme as ListTheme } from './components/List/List.themes';
import { Theme as NavigationTheme } from './components/Navigation/Navigation.themes';
import { Theme as RadioTheme } from './components/Radio/Radio.themes';
import { Theme as TextTheme } from './components/Text/Text.themes';
import { Theme as InputTheme } from './components/Input/Input.themes';
import { Theme as TableTheme } from './components/Table/Table.themes';
import { Theme as SelectTheme } from './components/Select/Select.themes';
import { Theme as SidePanelTheme } from './components/SidePanel/SidePanel.themes';
import { Theme as SkeletonTheme } from './components/Skeleton/Skeleton.themes';
import { Theme as DialogTheme } from './components/Dialog/Dialog.themes';
import { Theme as NavigationTheme } from './components/Navigation/Navigation.themes';
import { Theme as TooltipTheme } from './components/Tooltip/Tooltip.themes';
import { Theme as SwitchTheme } from './components/Switch/Switch.themes';
import { Theme as TableTheme } from './components/Table/Table.themes';
import { Theme as TextTheme } from './components/Text/Text.themes';
import { Theme as TextareaTheme } from './components/Textarea/Textarea.themes';
import { Theme as HeadingTheme } from './components/Heading/Heading.themes';

import { lightColors, darkColors } from './colors';
import { Theme as TooltipTheme } from './components/Tooltip/Tooltip.themes';
import getPrimaryColorInfo from './utils/getPrimaryColorInfo';
import { Property } from '@stitches/react/types/css';

export type { VariantProps } from '@stitches/react';

Expand Down Expand Up @@ -74,6 +73,7 @@ const stitches = createStitches({
...TooltipTheme.getLight(defaultPrimaryColor),
...TextareaTheme.getLight(defaultPrimaryColor),
...HeadingTheme.getLight(defaultPrimaryColor),
...SidePanelTheme.getLight(defaultPrimaryColor),
},
fonts: {
rubik:
Expand Down Expand Up @@ -307,6 +307,7 @@ export const darkTheme = (primary: PrimaryColor) => {
...TooltipTheme.getDark(darkPrimaryColor),
...TextareaTheme.getDark(darkPrimaryColor),
...HeadingTheme.getDark(darkPrimaryColor),
...SidePanelTheme.getDark(darkPrimaryColor),
},
});
};
Expand Down Expand Up @@ -338,6 +339,7 @@ export const lightTheme = (primary: PrimaryColor) => {
...TooltipTheme.getLight(lightPrimaryColor),
...TextareaTheme.getLight(lightPrimaryColor),
...HeadingTheme.getLight(lightPrimaryColor),
...SidePanelTheme.getLight(lightPrimaryColor),
},
});
};

0 comments on commit bec2646

Please sign in to comment.