EMAILOUT / frontend /src /components /workspace /MainTableWorkspace.jsx
Seth
update
401b968
import React from 'react';
import { Link } from 'react-router-dom';
import { Search, ChevronDown, ChevronRight, Plus } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
/**
* Shared “Deals-style” workspace: tabs, teal primary action, centered search, right actions,
* optional filters strip, collapsible section header, table area.
*/
export default function MainTableWorkspace({
tabs = [],
primaryAction = null,
search = null,
right = null,
filters = null,
/** 'top' = full-width strip above the table card; 'left' = narrow rail beside the table inside the card */
filtersPlacement = 'top',
sectionIcon: SectionIcon,
sectionTitle,
sectionCount,
sectionOpen,
onSectionToggle,
tableToolbar = null,
children,
}) {
return (
<div className="w-full min-w-0 space-y-4">
{tabs.length > 0 && (
<div className="flex flex-wrap gap-2 border-b border-slate-200 pb-3">
{tabs.map((t) => (
<button
key={t.label}
type="button"
onClick={t.onClick}
disabled={t.disabled}
className={cn(
'rounded-full px-4 py-1.5 text-sm font-medium transition-colors',
t.active
? 'bg-violet-100 text-violet-800'
: 'text-slate-400 hover:text-slate-600 px-3'
)}
>
{t.label}
</button>
))}
</div>
)}
<div className="flex w-full min-w-0 flex-col gap-3 lg:flex-row lg:items-center lg:gap-4">
<div className="shrink-0 order-1">
{primaryAction &&
(primaryAction.to ? (
<Button
asChild
size="sm"
className="bg-teal-600 hover:bg-teal-700 text-white shadow-sm"
>
<Link to={primaryAction.to} className="inline-flex items-center gap-1.5">
<Plus className="h-4 w-4" />
{primaryAction.label}
</Link>
</Button>
) : (
<Button
size="sm"
type="button"
disabled={primaryAction.disabled}
className="bg-teal-600 hover:bg-teal-700 text-white shadow-sm inline-flex items-center gap-1.5"
onClick={primaryAction.onClick}
>
<Plus className="h-4 w-4" />
{primaryAction.label}
</Button>
))}
</div>
{search && (
<div className="order-3 flex min-w-0 flex-1 justify-center lg:order-2">
<div className="relative w-full max-w-md lg:max-w-xl xl:max-w-2xl 2xl:max-w-3xl">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400 pointer-events-none" />
<Input
className="pl-9 border-slate-200 shadow-sm"
placeholder={search.placeholder}
value={search.value}
onChange={search.onChange}
/>
</div>
</div>
)}
<div className="shrink-0 flex flex-wrap gap-2 justify-end order-2 lg:order-3 lg:ml-auto">
{right}
</div>
</div>
{filtersPlacement === 'top' && filters && (
<div className="rounded-xl border border-slate-200 bg-white p-4 shadow-sm">{filters}</div>
)}
<div className="w-full min-w-0 overflow-visible rounded-xl border border-slate-200 bg-white shadow-sm">
<button
type="button"
onClick={onSectionToggle}
className="w-full flex items-center gap-2 px-4 py-3 text-left font-semibold text-slate-800 border-b border-slate-100 hover:bg-slate-50"
>
{sectionOpen ? (
<ChevronDown className="h-5 w-5 text-violet-600 shrink-0" />
) : (
<ChevronRight className="h-5 w-5 text-violet-600 shrink-0" />
)}
{SectionIcon ? <SectionIcon className="h-5 w-5 text-slate-500 shrink-0" /> : null}
<span className="truncate">{sectionTitle}</span>
<span className="text-slate-400 font-normal text-sm ml-2 shrink-0">
{sectionCount} total
</span>
</button>
{sectionOpen &&
(filtersPlacement === 'left' && filters ? (
<div className="flex flex-col gap-3 border-t border-slate-100 px-2 pb-3 pt-2 sm:px-4 md:flex-row md:items-start md:gap-3">
<aside className="w-full shrink-0 rounded-lg border border-slate-200 bg-slate-50/80 p-2.5 md:w-52 md:max-w-[13rem] overflow-y-auto max-h-[min(70vh,720px)]">
{filters}
</aside>
<div className="flex min-w-0 flex-1 flex-col gap-2">
{tableToolbar}
{children}
</div>
</div>
) : (
<>
{tableToolbar}
{children}
</>
))}
</div>
</div>
);
}