99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
import { useEffect, type CSSProperties, type ReactNode } from 'react';
|
|
import { Maximize2, Minimize2, X } from 'lucide-react';
|
|
import { LucentIconButton } from './LucentIconButton';
|
|
|
|
export type LucentDrawerSize = 'default' | 'expand';
|
|
|
|
interface LucentDrawerProps {
|
|
open: boolean;
|
|
title: string;
|
|
description?: string;
|
|
size?: LucentDrawerSize;
|
|
onClose: () => void;
|
|
onToggleSize?: () => void;
|
|
children: ReactNode;
|
|
footer?: ReactNode;
|
|
headerActions?: ReactNode;
|
|
topOffset?: number;
|
|
panelClassName?: string;
|
|
bodyClassName?: string;
|
|
closeLabel?: string;
|
|
expandLabel?: string;
|
|
collapseLabel?: string;
|
|
}
|
|
|
|
export function LucentDrawer({
|
|
open,
|
|
title,
|
|
description,
|
|
size = 'default',
|
|
onClose,
|
|
onToggleSize,
|
|
children,
|
|
footer,
|
|
headerActions,
|
|
topOffset = 0,
|
|
panelClassName = '',
|
|
bodyClassName = '',
|
|
closeLabel = 'Close panel',
|
|
expandLabel = 'Expand panel',
|
|
collapseLabel = 'Collapse panel',
|
|
}: LucentDrawerProps) {
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
if (event.key === 'Escape') onClose();
|
|
};
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
}, [onClose, open]);
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
className={`lucent-drawer-mask ${open ? 'is-open' : ''} ${size === 'expand' ? 'is-expand' : ''}`}
|
|
onClick={open ? onClose : undefined}
|
|
aria-hidden="true"
|
|
/>
|
|
<aside
|
|
className={`lucent-drawer ${open ? 'is-open' : ''} is-${size}`}
|
|
style={{ '--lucent-drawer-top': `${topOffset}px` } as CSSProperties}
|
|
aria-hidden={!open}
|
|
>
|
|
<div className={`lucent-drawer-panel card ${panelClassName}`.trim()} onClick={(event) => event.stopPropagation()}>
|
|
<div className="lucent-drawer-header">
|
|
<div className="lucent-drawer-header-copy">
|
|
<div className="section-mini-title">{title}</div>
|
|
{description ? <div className="field-label">{description}</div> : null}
|
|
</div>
|
|
<div className="lucent-drawer-header-actions">
|
|
{headerActions}
|
|
{onToggleSize ? (
|
|
<LucentIconButton
|
|
className="btn btn-secondary btn-sm icon-btn"
|
|
onClick={onToggleSize}
|
|
tooltip={size === 'expand' ? collapseLabel : expandLabel}
|
|
aria-label={size === 'expand' ? collapseLabel : expandLabel}
|
|
>
|
|
{size === 'expand' ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
|
</LucentIconButton>
|
|
) : null}
|
|
<LucentIconButton
|
|
className="btn btn-secondary btn-sm icon-btn"
|
|
onClick={onClose}
|
|
tooltip={closeLabel}
|
|
aria-label={closeLabel}
|
|
>
|
|
<X size={14} />
|
|
</LucentIconButton>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={`lucent-drawer-body ${bodyClassName}`.trim()}>{children}</div>
|
|
{footer ? <div className="lucent-drawer-footer">{footer}</div> : null}
|
|
</div>
|
|
</aside>
|
|
</>
|
|
);
|
|
}
|