Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

et-501: create drawer component #156

Merged
merged 2 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './ui/badge';
export * from './ui/button';
export * from './ui/card';
export * from './ui/divider';
export * from './ui/drawer';
export * from './ui/dropdown';
export * from './ui/form/checkbox';
export * from './ui/form/form-section';
Expand Down
30 changes: 30 additions & 0 deletions src/components/ui/drawer/__snapshots__/drawer.spec.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Drawer > matches snapshot 1`] = `
<div>
<div
class="absolute top-0 h-full right-0"
style="width: 460px;"
>
<div
class="absolute inset-0 overflow-hidden"
>
<div
class="absolute h-full w-full top-0 right-0 bg-white transition-transform duration-300 ease-in-out translate-x-0"
>
<div
class="w-full h-full relative"
>
<div
class="h-full"
>
<div>
Drawer Content
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
55 changes: 55 additions & 0 deletions src/components/ui/drawer/drawer.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { expect, it, describe } from 'vitest';
import { render } from '@testing-library/react';

import { Drawer } from './drawer';
import React from 'react';

describe('Drawer', () => {
const subject = (props = {}) => {
const defaultProps = {
isOpen: true,
children: <div>Drawer Content</div>,
drawerWidth: 460,
side: 'right' as const
};
return render(<Drawer {...defaultProps} {...props} />);
};

it('matches snapshot', () => {
expect(subject().container).toMatchSnapshot();
});

it('renders with correct width', () => {
const { container } = subject({ drawerWidth: 500 });
const drawer = container.firstChild as HTMLElement;
expect(drawer).toHaveStyle({ width: '500px' });
});

it('positions on the right by default', () => {
const { container } = subject();
const drawer = container.firstChild as HTMLElement;
expect(drawer).toHaveClass('right-0');
expect(drawer).not.toHaveClass('left-0');
});

it('positions on the left when specified', () => {
const { container } = subject({ side: 'left' });
const drawer = container.firstChild as HTMLElement;
expect(drawer).toHaveClass('left-0');
expect(drawer).not.toHaveClass('right-0');
});

it('applies correct transform class when open', () => {
const { container } = subject({ isOpen: true });
const slidePanel = container.querySelector('[class*="translate"]');
expect(slidePanel).toHaveClass('translate-x-0');
expect(slidePanel).not.toHaveClass('translate-x-full');
});

it('applies correct transform class when closed', () => {
const { container } = subject({ isOpen: false });
const slidePanel = container.querySelector('[class*="translate"]');
expect(slidePanel).toHaveClass('translate-x-full');
expect(slidePanel).not.toHaveClass('translate-x-0');
});
});
45 changes: 45 additions & 0 deletions src/components/ui/drawer/drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ReactNode, type FC } from 'react';
import clsx from 'clsx';

interface DrawerProps {
isOpen: boolean
children: ReactNode
drawerWidth?: number
side?: 'left' | 'right'
}


export const Drawer: FC<DrawerProps> = ({ isOpen, children, drawerWidth = 460, side = 'right' }) => {

return (
<div className={
clsx(
'absolute top-0 h-full',
side === 'left' ? 'left-0' : 'right-0'
)
}
style={{ width: `${drawerWidth}px` }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion
What if I want to pass width in '%' or 'rem' ? 🤔 Maybe it make sense to change this to be more generic and allow to pass tailwind class instead of absolute value.

>
<div className="absolute inset-0 overflow-hidden">
<div
className={clsx(
'absolute h-full w-full top-0',
side === 'left' ? 'left-0' : 'right-0',
'bg-white',
'transition-transform duration-300 ease-in-out',
{
'translate-x-0': isOpen && side === 'right',
'translate-x-full': !isOpen && side === 'right',
'-translate-x-0': isOpen && side === 'left',
'-translate-x-full': !isOpen && side === 'left',
}
)}
>
<div className='w-full h-full relative'>
<div className='h-full'>{children}</div>
</div>
</div>
</div>
</div>
);
};
2 changes: 2 additions & 0 deletions src/components/ui/drawer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export { Drawer } from './drawer'
6 changes: 4 additions & 2 deletions src/components/ui/icon/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ import {
RiArticleLine,
RiBillLine,
RiCalendar2Line,
RiMailLine
RiMailLine,
RiHistoryLine,
} from '@remixicon/react';

export const ICONS = {
Expand Down Expand Up @@ -225,7 +226,8 @@ export const ICONS = {
RiArticleLine,
RiBillLine,
RiCalendar2Line,
RiMailLine
RiMailLine,
RiHistoryLine
};

export enum IconSize {
Expand Down
31 changes: 31 additions & 0 deletions src/stories/Drawer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Drawer } from '../components';

const meta = {
component: Drawer
} satisfies Meta<typeof Drawer>;

export default meta;

type Story = StoryObj<typeof Drawer>;
export const ControlledDrawer = {
args: {
children: <div>hello</div>,
isOpen: false,
side: 'right',
drawerWidth: 460
},
render: (args) => (
<div className='grid grid-rows-[4rem_1fr] h-screen'>
<div className='bg-blue-100'>
the app header
</div>
<main className='relative overflow-hidden'>
<div className='bg-red-100 w-full h-full'>
this is the main content
</div>
<Drawer {...args} />
</main>
</div>
)
} satisfies Story;
75 changes: 75 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,13 @@ html {
background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
}

.drawer {
position: relative;
display: grid;
grid-auto-columns: max-content auto;
width: 100%;
}

.dropdown {
position: relative;
display: inline-block;
Expand Down Expand Up @@ -3455,6 +3462,10 @@ input.tab:checked + .tab-content,
display: table-row;
}

.grid {
display: grid;
}

.h-1\.5 {
height: 0.375rem;
}
Expand Down Expand Up @@ -3515,6 +3526,14 @@ input.tab:checked + .tab-content,
height: 500px;
}

.h-full {
height: 100%;
}

.h-screen {
height: 100vh;
}

.min-h-8 {
min-height: 2rem;
}
Expand Down Expand Up @@ -3599,6 +3618,10 @@ input.tab:checked + .tab-content,
width: 400px;
}

.w-\[460px\] {
width: 460px;
}

.w-\[46px\] {
width: 46px;
}
Expand Down Expand Up @@ -3643,6 +3666,26 @@ input.tab:checked + .tab-content,
caption-side: bottom;
}

.translate-x-0 {
--tw-translate-x: 0px;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.translate-x-full {
--tw-translate-x: 100%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.-translate-x-0 {
--tw-translate-x: -0px;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.-translate-x-full {
--tw-translate-x: -100%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
Expand All @@ -3657,6 +3700,10 @@ input.tab:checked + .tab-content,
appearance: none;
}

.grid-rows-\[4rem_1fr\] {
grid-template-rows: 4rem 1fr;
}

.flex-row {
flex-direction: row;
}
Expand Down Expand Up @@ -4405,6 +4452,12 @@ input.tab:checked + .tab-content,
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-lg {
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-none {
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
Expand Down Expand Up @@ -4434,6 +4487,20 @@ input.tab:checked + .tab-content,
transition-duration: 150ms;
}

.transition-transform {
transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}

.duration-300 {
transition-duration: 300ms;
}

.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

@keyframes enter {
from {
opacity: var(--tw-enter-opacity, 1);
Expand All @@ -4448,6 +4515,14 @@ input.tab:checked + .tab-content,
}
}

.duration-300 {
animation-duration: 300ms;
}

.ease-in-out {
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.\[--tglbg\:\#2563EB\] {
--tglbg: #2563EB;
}
Expand Down