92 lines
2.6 KiB
TypeScript
92 lines
2.6 KiB
TypeScript
import { useEffect, useRef, type PropsWithChildren, type ReactNode } from 'react'
|
|
import { X } from 'lucide-react'
|
|
import { useBodyScrollLock, useFocusTrap } from './useFocusTrap'
|
|
|
|
type Props = PropsWithChildren<{
|
|
open: boolean
|
|
title: string
|
|
description?: string
|
|
/** Logical edge: `end` is the left side in RTL. Defaults to `end`. */
|
|
side?: 'start' | 'end'
|
|
width?: string
|
|
footer?: ReactNode
|
|
headerExtra?: ReactNode
|
|
onClose: () => void
|
|
}>
|
|
|
|
export function Drawer({
|
|
open,
|
|
title,
|
|
description,
|
|
side = 'end',
|
|
width = 'w-full max-w-md',
|
|
footer,
|
|
headerExtra,
|
|
onClose,
|
|
children,
|
|
}: Props) {
|
|
const panelRef = useRef<HTMLDivElement | null>(null)
|
|
useFocusTrap(panelRef, open)
|
|
useBodyScrollLock(open)
|
|
|
|
useEffect(() => {
|
|
if (!open) return
|
|
function onKeyDown(event: KeyboardEvent) {
|
|
if (event.key === 'Escape') onClose()
|
|
}
|
|
document.addEventListener('keydown', onKeyDown)
|
|
return () => document.removeEventListener('keydown', onKeyDown)
|
|
}, [open, onClose])
|
|
|
|
if (!open) return null
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 print:hidden">
|
|
<button
|
|
type="button"
|
|
aria-label="بستن"
|
|
onClick={onClose}
|
|
className="absolute inset-0 bg-ink/40 backdrop-blur-[2px]"
|
|
/>
|
|
<div
|
|
ref={panelRef}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label={title}
|
|
tabIndex={-1}
|
|
className={`drawer-enter absolute inset-y-0 ${
|
|
side === 'end' ? 'end-0' : 'start-0'
|
|
} flex ${width} flex-col bg-panel shadow-2xl outline-none`}
|
|
>
|
|
<div className="flex shrink-0 items-start justify-between gap-3 border-b border-line px-5 py-4">
|
|
<div className="min-w-0">
|
|
<h2 className="truncate text-base font-semibold text-ink">{title}</h2>
|
|
{description ? (
|
|
<p className="mt-1 text-xs leading-5 text-ink-muted">{description}</p>
|
|
) : null}
|
|
</div>
|
|
<div className="flex shrink-0 items-center gap-1">
|
|
{headerExtra}
|
|
<button
|
|
type="button"
|
|
aria-label="بستن"
|
|
onClick={onClose}
|
|
className="inline-flex h-8 w-8 items-center justify-center rounded-lg text-ink-muted transition hover:bg-surface-subtle hover:text-ink"
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">{children}</div>
|
|
|
|
{footer ? (
|
|
<div className="shrink-0 border-t border-line bg-surface-subtle/60 px-5 py-3">
|
|
{footer}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|