Skip to content

Commit

Permalink
Added: Drawer component added.
Browse files Browse the repository at this point in the history
  • Loading branch information
Arifulislam5577 committed Apr 1, 2024
1 parent f744266 commit e761944
Show file tree
Hide file tree
Showing 16 changed files with 410 additions and 9 deletions.
2 changes: 1 addition & 1 deletion app/docs.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,5 @@ body::-webkit-scrollbar-thumb {
}

.docs-page {
@apply fixed left-0 right-0 top-24 -z-0 mx-auto max-w-[1240px] rounded-2xl bg-gradient-to-t from-primary-25 to-white xl:!h-2/4 2xl:top-40 2xl:!h-3/4 2xl:max-w-[1560px] 4k:max-w-[2000px] dark:from-metal-900 dark:to-metal-800/50;
@apply fixed left-0 right-0 top-24 -z-0 mx-auto max-w-[1240px] rounded-2xl bg-gradient-to-t from-primary-25 to-white dark:from-metal-900 dark:to-metal-800/50 xl:!h-2/4 2xl:top-40 2xl:!h-3/4 2xl:max-w-[1560px] 4k:max-w-[2000px];
}
32 changes: 32 additions & 0 deletions app/docs/components/drawer/Drawer.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { DefaultDrawer, DefaultDrawerCode } from './variant/DefaultDrawer'
import { DrawerPosition, DrawerPositionCode } from './variant/DrawerPosition'

import { drawerAPIData } from './DrawerApi'
import CodePreview from '../../../components/CodePreview'
import ComponentApi from '../../../components/ComponentApi'

## Table of Contents

The Drawer component provides a flexible and customizable way to create drawers that can slide in from different directions (left, right, top, or bottom) within your React application. It supports features like animation, keyboard accessibility (ESC key to close), and clicking outside the drawer to close it.

## Default Drawer

The Default Drawer component showcases a simple content inside the drawer.

<CodePreview github="Drawer" code={DefaultDrawerCode}>
<DefaultDrawer />
</CodePreview>

## Drawer Position

You can position the drawer in a specific direction, such as `left`, `right`, `top`, or `bottom`.

<CodePreview github="Drawer" code={DrawerPositionCode}>
<DrawerPosition />
</CodePreview>

## API Reference

Here is a list of the props that you can pass to the drawer component:

<ComponentApi data={drawerAPIData} />
23 changes: 23 additions & 0 deletions app/docs/components/drawer/DrawerApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const drawerAPIData = [
{
id: 1,
propsName: 'isOpen',
propsType: 'string',
propsDescription: 'Drawer show or not?',
default: 'false',
},
{
id: 2,
propsName: 'position',
propsType: ['left', 'top', 'right', 'bottom'],
propsDescription: 'Drawer position',
default: 'bottom',
},
{
id: 3,
propsName: 'onClose',
propsType: 'Function',
propsDescription: 'Drawer close function',
default: 'None',
},
]
7 changes: 7 additions & 0 deletions app/docs/components/drawer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client'
import type { FC } from 'react'
import DrawerDocsContent from './Drawer.mdx'

const DrawerDocs: FC = () => <DrawerDocsContent />

export default DrawerDocs
19 changes: 19 additions & 0 deletions app/docs/components/drawer/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Metadata, NextPage } from 'next'
import DrawerDocs from '.'
import { DocsContentLayout } from '../../../components/DocsContentLayout'

export const metadata: Metadata = {
description:
'The Drawer component provides a flexible and customizable way to create drawers that can slide in from different directions (left, right, top, or bottom) within your React application. It supports features like animation, keyboard accessibility (ESC key to close), and clicking outside the drawer to close it.',
title: 'Drawer - Keep React',
}

const page: NextPage = () => {
return (
<DocsContentLayout description={`${metadata.description}`} title={`${metadata.title}`}>
<DrawerDocs />
</DocsContentLayout>
)
}

export default page
61 changes: 61 additions & 0 deletions app/docs/components/drawer/variant/DefaultDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client'
import { useState } from 'react'
import { Button, Drawer } from '../../../../src'

const DefaultDrawer = () => {
const [isOpen, setIsOpen] = useState<boolean>(false)
return (
<>
<Button onClick={() => setIsOpen(!isOpen)} className="bg-metal-900" color="secondary">
Drawer
</Button>
<Drawer isOpen={isOpen} onClose={() => setIsOpen(false)}>
<Drawer.Content className="flex items-center justify-center">
<div className="mx-auto max-w-lg space-y-3 px-6 py-5 lg:px-0">
<h1 className="text-heading-3 font-bold text-metal-900 lg:text-heading-2">Keep React</h1>
<p className="text-body-3 font-normal text-metal-600">
Elevate your web projects with Keep React&apos;s 40+ customizable components. Access open-source resources
for efficient development and bring your ideas to life with ease.
</p>
<Button onClick={() => setIsOpen(false)} className="bg-metal-900" color="secondary">
Explore Now
</Button>
</div>
</Drawer.Content>
</Drawer>
</>
)
}

const DefaultDrawerCode = `
'use client'
import { useState } from 'react'
import { Button, Drawer } from 'keep-react'
export const DrawerComponent = () => {
const [isOpen, setIsOpen] = useState(false)
return (
<>
<Button onClick={() => setIsOpen(!isOpen)} className="bg-metal-900" color="secondary">
Toggle Drawer
</Button>
<Drawer isOpen={isOpen} onClose={() => setIsOpen(false)}>
<Drawer.Content className="flex items-center justify-center">
<div className="mx-auto max-w-lg space-y-3 px-6 py-5 lg:px-0">
<h1 className="text-heading-3 font-bold text-metal-900 lg:text-heading-2">Keep React</h1>
<p className="text-body-3 font-normal text-metal-600">
Elevate your web projects with Keep React&apos;s 40+ customizable components. Access open-source resources
for efficient development and bring your ideas to life with ease.
</p>
<Button onClick={() => setIsOpen(false)} className="bg-metal-900" color="secondary">
Explore Now
</Button>
</div>
</Drawer.Content>
</Drawer>
</>
)
}
`

export { DefaultDrawer, DefaultDrawerCode }
84 changes: 84 additions & 0 deletions app/docs/components/drawer/variant/DrawerPosition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use client'
import { useState } from 'react'
import { Button, Drawer } from '../../../../src'

const DrawerPosition = () => {
const [isOpen, setIsOpen] = useState<boolean>(false)
const [position, setPosition] = useState<'bottom' | 'top' | 'left' | 'right'>('bottom')
return (
<>
<div className="flex items-center gap-3">
{['bottom', 'top', 'left', 'right'].map((position) => (
<Button
key={position}
onClick={() => {
setIsOpen(!isOpen)
setPosition(position as 'bottom' | 'top' | 'left' | 'right')
}}
color="secondary"
className="bg-metal-900 capitalize">
{position}
</Button>
))}
</div>
<Drawer position={position} isOpen={isOpen} onClose={() => setIsOpen(false)}>
<Drawer.Content className="flex items-center justify-center">
<div className="mx-auto max-w-lg space-y-3 px-6 py-5 lg:px-0">
<h1 className="text-heading-3 font-bold text-metal-900 lg:text-heading-2">Keep React</h1>
<p className="text-body-3 font-normal text-metal-600">
Elevate your web projects with Keep React&apos;s 40+ customizable components. Access open-source resources
for efficient development and bring your ideas to life with ease.
</p>
<Button onClick={() => setIsOpen(false)} className="bg-metal-900" color="secondary">
Explore Now
</Button>
</div>
</Drawer.Content>
</Drawer>
</>
)
}

const DrawerPositionCode = `
'use client'
import { useState } from 'react'
import { Button, Drawer } from '../../../../src'
export const DrawerComponent = () => {
const [isOpen, setIsOpen] = useState(false)
const [position, setPosition] = useState('bottom')
return (
<>
<div className="flex items-center gap-3">
{['bottom', 'top', 'left', 'right'].map((position) => (
<Button
key={position}
onClick={() => {
setIsOpen(!isOpen)
setPosition(position)
}}
color="secondary"
className="bg-metal-900 capitalize">
{position}
</Button>
))}
</div>
<Drawer position={position} isOpen={isOpen} onClose={() => setIsOpen(false)}>
<Drawer.Content className="flex items-center justify-center">
<div className="mx-auto max-w-lg space-y-3 px-6 py-5 lg:px-0">
<h1 className="text-heading-3 font-bold text-metal-900 lg:text-heading-2">Keep React</h1>
<p className="text-body-3 font-normal text-metal-600">
Elevate your web projects with Keep React&apos;s 40+ customizable components. Access open-source resources
for efficient development and bring your ideas to life with ease.
</p>
<Button onClick={() => setIsOpen(false)} className="bg-metal-900" color="secondary">
Explore Now
</Button>
</div>
</Drawer.Content>
</Drawer>
</>
)
}
`
export { DrawerPosition, DrawerPositionCode }
23 changes: 23 additions & 0 deletions app/src/components/Drawer/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'
import { HTMLAttributes, Ref, forwardRef } from 'react'
import { cn } from '../../helpers/cn'
import { useDrawerContext } from './Context'
import { keepDrawerTheme } from './theme'

const Content = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ children, className, ...props }, ref: Ref<HTMLDivElement>) => {
const { position = 'bottom' } = useDrawerContext()
return (
<div
{...props}
ref={ref}
className={cn(keepDrawerTheme.content.base, keepDrawerTheme.content.position[position], className)}>
{children}
</div>
)
},
)

Content.displayName = 'Drawer.Content'

export { Content }
14 changes: 14 additions & 0 deletions app/src/components/Drawer/Context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createContext, useContext } from 'react'
import { DrawerProps } from './Drawer'

export const DrawerContext = createContext<DrawerProps | undefined>(undefined)

export function useDrawerContext(): DrawerProps {
const context = useContext(DrawerContext)

if (!context) {
throw new Error('useDrawerContext should be used within the DrawerContext provider!')
}

return context
}
79 changes: 79 additions & 0 deletions app/src/components/Drawer/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client'
import { ForwardedRef, HTMLAttributes, ReactNode, forwardRef, useCallback, useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import FocusLock from 'react-focus-lock'
import { RemoveScroll } from 'react-remove-scroll'
import { cn } from '../../helpers/cn'
import { Content } from './Content'
import { DrawerContext } from './Context'
import { keepDrawerTheme } from './theme'

export interface DrawerProps extends HTMLAttributes<HTMLDivElement> {
isOpen?: boolean
onClose?: () => void
position?: 'left' | 'right' | 'top' | 'bottom'
children?: ReactNode
}

const DrawerComponent = forwardRef<HTMLDivElement, DrawerProps>(
({ isOpen, onClose, position = 'bottom', children, className }: DrawerProps, ref: ForwardedRef<HTMLDivElement>) => {
const [isClosing, setIsClosing] = useState(false)

const handleClose = useCallback(() => {
setIsClosing(true)
setTimeout(() => {
onClose && onClose()
setIsClosing(false)
}, 300)
}, [onClose])

useEffect(() => {
const handleEscapeKeyPress = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
handleClose && handleClose()
}
}

const handleClickOutsideModal = (event: MouseEvent) => {
if (!(event.target as HTMLElement).closest('.drawer-content')) {
handleClose && handleClose()
}
}

if (isOpen) {
document.addEventListener('keydown', handleEscapeKeyPress)
document.addEventListener('mousedown', handleClickOutsideModal)
}

return () => {
document.removeEventListener('keydown', handleEscapeKeyPress)
document.removeEventListener('mousedown', handleClickOutsideModal)
}
}, [isOpen, handleClose])

return isOpen
? createPortal(
<RemoveScroll enabled={isOpen}>
<FocusLock disabled={!isOpen} returnFocus>
<div
ref={ref}
className={cn(
keepDrawerTheme.base,
isClosing ? keepDrawerTheme.isClosing.on : keepDrawerTheme.isClosing.off,
className,
)}>
<DrawerContext.Provider value={{ position }}>{children}</DrawerContext.Provider>
</div>
</FocusLock>
</RemoveScroll>,
document.body,
)
: null
},
)

DrawerComponent.displayName = 'Drawer'

export const Drawer = Object.assign(DrawerComponent, {
Content,
})
1 change: 1 addition & 0 deletions app/src/components/Drawer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Drawer } from './Drawer'
33 changes: 33 additions & 0 deletions app/src/components/Drawer/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
interface KeepDrawerTheme {
base: string
isClosing: {
on: string
off: string
}
content: {
base: string
position: {
top: string
bottom: string
left: string
right: string
}
}
}

export const keepDrawerTheme: KeepDrawerTheme = {
base: 'fixed inset-0 z-50 bg-metal-900 bg-opacity-50 backdrop-blur-[2px] transition-opacity',
isClosing: {
on: 'opacity-0',
off: 'opacity-100',
},
content: {
base: 'drawer-content absolute bg-white p-6 shadow-lg',
position: {
top: 'left-0 right-0 top-0 h-1/2 animate-keep-slide-down rounded-b-2xl',
bottom: 'bottom-0 left-0 right-0 h-1/2 animate-keep-slide-up rounded-t-2xl',
left: 'left-0 top-0 h-full w-3/4 animate-keep-slide-left rounded-r-2xl xl:w-1/4',
right: 'right-0 top-0 h-full w-3/4 animate-keep-slide-right rounded-l-2xl xl:w-1/4',
},
},
}
Loading

0 comments on commit e761944

Please sign in to comment.