Files
kamidso-front/src/components/ui/Drawer.tsx
T

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>
)
}