EMAILOUT / frontend /src /components /workspace /SlideOverPanel.jsx
Seth
update
0ac8264
import React, { useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
/**
* Right-side slide-over (same pattern as Leads detail). Use on Leads, Contacts, and Deals.
*/
export default function SlideOverPanel({
open,
onClose,
title,
subtitle,
headerActions = null,
children,
widthClassName = 'max-w-lg',
}) {
useEffect(() => {
if (!open) return;
const onKey = (e) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [open, onClose]);
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex justify-end bg-black/30" role="dialog" aria-modal="true">
<button
type="button"
className="flex-1 cursor-default min-h-0"
aria-label="Close panel"
onClick={onClose}
/>
<div
className={cn(
'w-full bg-white shadow-xl h-full overflow-y-auto border-l border-slate-200 p-6',
widthClassName
)}
>
<div className="flex justify-between items-start gap-4 mb-6">
<div className="min-w-0 pr-2 flex-1">
{typeof title === 'string' || title == null ? (
<h3 className="text-lg font-bold text-slate-900 truncate">{title || ''}</h3>
) : (
<div className="w-full min-w-0">{title}</div>
)}
{subtitle ? (
<p className="text-sm text-slate-500 mt-1 break-words">{subtitle}</p>
) : null}
</div>
<div className="flex items-center gap-2 shrink-0">
{headerActions}
<Button variant="ghost" size="sm" onClick={onClose}>
Close
</Button>
</div>
</div>
{children}
</div>
</div>
);
}