Skip to content

Commit

Permalink
Merge pull request #53 from getpingback/feat/drawer
Browse files Browse the repository at this point in the history
feat: create drawer component
  • Loading branch information
roger067 authored Jan 2, 2025
2 parents 319726a + d35b94c commit 931daa9
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 1 deletion.
63 changes: 63 additions & 0 deletions src/components/drawer/drawer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useState } from 'react';
import { Drawer } from './drawer';
import type { Meta, StoryObj } from '@storybook/react';
import { ArrowLeftIcon } from '@stash-ui/light-icons';
import { Button } from '../button/button';

const meta = {
title: 'Components/Drawer',
component: Drawer,
tags: ['autodocs']
} satisfies Meta<typeof Drawer>;

type Story = StoryObj<typeof meta>;

export default meta;

export const Default: Story = {
args: {
title: 'Drawer',
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.',
hasDivider: true,
open: true,
prefixIcon: <ArrowLeftIcon />,
children: (
<div className="flex flex-col gap-4">
<div>lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos</div>
<div>lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos</div>
<div>lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos</div>
<div>lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos</div>
<div>lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos</div>
<div>lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos</div>
</div>
),
footer: (
<div className="flex gap-4">
<Button variant="outline" className="w-full">
Cancel
</Button>
<Button variant="solid" className="w-full">
Save
</Button>
</div>
)
}
};

export const Behavior: Story = {
args: {
title: 'Drawer'
},
render: ({ title }) => {
const [open, setOpen] = useState(false);

return (
<>
<Button variant="solid" onClick={() => setOpen(true)}>
Open Drawer
</Button>
<Drawer title={title} open={open} onOpenChange={setOpen} />
</>
);
}
};
85 changes: 85 additions & 0 deletions src/components/drawer/drawer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { Drawer } from './drawer';
import * as React from 'react';

describe('Drawer', () => {
const defaultProps = {
title: 'Test Drawer',
open: true
};

it('should render drawer with title', () => {
render(
<Drawer {...defaultProps}>
<div>Drawer content</div>
</Drawer>
);

expect(screen.getByText('Test Drawer')).toBeInTheDocument();
});

it('should render drawer with description', () => {
render(
<Drawer {...defaultProps} description="Test description">
<div>Drawer content</div>
</Drawer>
);

expect(screen.getByText('Test description')).toBeInTheDocument();
});

it('should render drawer with preffix icon', () => {
const TestIcon = () => <div data-testid="test-icon">Icon</div>;

render(
<Drawer {...defaultProps} prefixIcon={<TestIcon />}>
<div>Drawer content</div>
</Drawer>
);

expect(screen.getByTestId('test-icon')).toBeInTheDocument();
});

it('should render drawer with divider when hasDivider is true', () => {
render(
<Drawer {...defaultProps} hasDivider>
<div>Drawer content</div>
</Drawer>
);

expect(screen.getByTestId('divider')).toBeInTheDocument();
});

it('should render drawer with footer content', () => {
render(
<Drawer {...defaultProps} footer={<div>Footer content</div>}>
<div>Drawer content</div>
</Drawer>
);

expect(screen.getByText('Footer content')).toBeInTheDocument();
});

it('should render children content', () => {
render(
<Drawer {...defaultProps}>
<div>Test children content</div>
</Drawer>
);

expect(screen.getByText('Test children content')).toBeInTheDocument();
});

it('should close drawer when clicking close button', () => {
const onOpenChange = jest.fn();

render(
<Drawer {...defaultProps} onOpenChange={onOpenChange}>
<div>Drawer content</div>
</Drawer>
);

fireEvent.click(screen.getByRole('button'));
expect(onOpenChange).toHaveBeenCalledWith(false);
});
});
42 changes: 42 additions & 0 deletions src/components/drawer/drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as Dialog from '@radix-ui/react-dialog';
import { TimesIcon } from '@stash-ui/light-icons';
import * as React from 'react';

interface DrawerProps extends Dialog.DialogProps {
title: string;
description?: string;
prefixIcon?: React.ReactNode;
hasDivider?: boolean;
footer?: React.ReactNode;
}

function Drawer({ children, title, description, prefixIcon, hasDivider, footer, ...props }: DrawerProps) {
return (
<Dialog.Root {...props}>
<Dialog.Portal>
<Dialog.Overlay className=" z-50 fixed inset-0 bg-[#00000011] w-screen h-screen backdrop-blur-[1px] animate-fade-in" />
<Dialog.Content className="z-50 flex flex-col bg-[#FFFFFF] shadow-drawer rounded-xl w-[400px] border border-border-card fixed right-6 top-6 h-[calc(100vh-48px)] data-[state=open]:animate-drawer-slide-in data-[state=closed]:animate-drawer-slide-out">
<div className="flex justify-between gap-2 p-6 pb-4">
<div className="flex gap-2">
{prefixIcon && <div className="h-fit">{prefixIcon}</div>}
<div className="flex flex-col gap-1">
<Dialog.Title className="text-lg font-bold leading-6 text-primary-foreground">{title}</Dialog.Title>
{description && (
<Dialog.Description className="text-xs font-normal text-primary-foreground opacity-65">{description}</Dialog.Description>
)}
</div>
</div>
<Dialog.Close className="h-fit">
<TimesIcon />
</Dialog.Close>
</div>
{hasDivider && <div className="h-px w-full bg-[#0000000A]" data-testid="divider" />}
<div className="flex flex-col flex-1 overflow-y-auto p-6">{children}</div>
{footer && <div className="p-6 border-t border-[#0000000A]">{footer}</div>}
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}

export { Drawer };
1 change: 1 addition & 0 deletions src/components/drawer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './drawer';
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './badge';
export * from './drawer';
export * from './navigation';
export * from './sidebar';
export * from './dropdown';
Expand Down
2 changes: 2 additions & 0 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

--bottom_sheet-2: 0px 0px 1px 1px rgba(0, 0, 0, 0.04), 0px 4px 16px -2px rgba(0, 0, 0, 0.08);
--modals-shadow: 0px 0px 1px 1px rgba(0, 0, 0, 0.04), 0px -6px 24px -3px rgba(0, 0, 0, 0.08);
--drawer-shadow: -2px 0px 24px -4px #0000001f;
--dropdown-shadow: 0px 0px 10px 3px rgba(0, 0, 0, 0.04), 0px 10px 40px -5px rgba(0, 0, 0, 0.08);
--switch-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.161) inset;

Expand Down Expand Up @@ -118,6 +119,7 @@

--bottom_sheet-2: 0px 0px 1px 1px #0000007a, 0px -4px 16px -2px #0000007a;
--modals-shadow: 0px 0px 1px 1px rgba(0, 0, 0, 0.04), 0px -6px 24px -3px rgba(0, 0, 0, 0.08);
--drawer-shadow: -2px 0px 24px -4px #0000001f;
--button-solid-shadow: 0px 0px 0px 3px #9061f940;
--dropdown-shadow: 0px 0px 10px 3px rgba(0, 0, 0, 0.04), 0px 10px 40px -5px rgba(0, 0, 0, 0.08);

Expand Down
18 changes: 17 additions & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const config = {
boxShadow: {
'bottom_sheet-2': 'var(--bottom_sheet-2)',
modal: 'var(--modals-shadow)',
drawer: 'var(--drawer-shadow)',
custom: '0px 0px 0px 1px rgba(0,0,0,0.15)',
solid: 'var(--button-solid-shadow)',
dropdown: 'var(--dropdown-shadow)',
Expand Down Expand Up @@ -168,13 +169,28 @@ const config = {
opacity: 0
},
to: { transform: 'translateX(0%)', opacity: 1 }
},
drawerSlideIn: {
from: { transform: 'translateX(100%)' },
to: { transform: 'translateX(0%)' }
},
drawerSlideOut: {
from: { transform: 'translateX(0%)' },
to: { transform: 'translateX(100%)' }
},
'fade-in': {
from: { opacity: 0 },
to: { opacity: 1 }
}
},
animation: {
'slide-up': 'accordionSlideUp 300ms cubic-bezier(0.87, 0, 0.13, 1) forwards',
'slide-down': 'accordionSlideDown 300ms cubic-bezier(0.87, 0, 0.13, 1) forwards',
'slide-left': 'slideLeft 300ms cubic-bezier(0.83, 0, 0.17, 1)',
'slide-right': 'slideRight 300ms cubic-bezier(0.83, 0, 0.17, 1)'
'slide-right': 'slideRight 300ms cubic-bezier(0.83, 0, 0.17, 1)',
'drawer-slide-in': 'drawerSlideIn 300ms cubic-bezier(0.83, 0, 0.17, 1)',
'drawer-slide-out': 'drawerSlideOut 300ms cubic-bezier(0.83, 0, 0.17, 1)',
'fade-in': 'fadeIn 300ms cubic-bezier(0.83, 0, 0.17, 1)'
},
colors: {
success: {
Expand Down

0 comments on commit 931daa9

Please sign in to comment.