Files
Nexus/apps/web/src/components/dashboard/WidgetContainer.tsx
T
Hartmut 2c65abd231 refactor: redesign widget container for dark theme consistency
WidgetContainer redesign:
- Remove grey bg-gray-50/50 header background (clashed with dark theme)
- Header now uses transparent background with subtle separator line
- Add description prop — shows catalog description under title
- Drag grip dots appear on hover (6-dot pattern, hidden by default)
- Remove button appears on hover with red highlight
- Dark theme: bg-gray-900, border-gray-700/60, gray-800 separator
- Drag state: ring-2 ring-brand-400/30 with scale effect
- Hover state: shadow-md with border color transition

DashboardClient: passes widget.description to WidgetContainer

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-22 21:55:17 +01:00

71 lines
2.8 KiB
TypeScript

"use client";
import { motion } from "framer-motion";
interface WidgetContainerProps {
title: string;
description?: string;
onRemove: () => void;
children: React.ReactNode;
isDragging?: boolean;
}
export function WidgetContainer({ title, description, onRemove, children, isDragging }: WidgetContainerProps) {
return (
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, ease: "easeOut" }}
className={`flex flex-col h-full rounded-xl border overflow-hidden transition-all duration-200 ${
isDragging
? "shadow-xl border-brand-400 dark:border-brand-500 scale-[1.01] ring-2 ring-brand-400/30"
: "bg-white dark:bg-gray-900 border-gray-200/80 dark:border-gray-700/60 shadow-sm hover:shadow-md hover:border-gray-300 dark:hover:border-gray-600"
}`}
>
{/* Header — clean, no background separation */}
<div className="flex items-center justify-between px-4 pt-3.5 pb-2 shrink-0 cursor-grab active:cursor-grabbing widget-drag-handle group">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
{/* Drag grip dots */}
<svg
className="w-3.5 h-5 text-gray-300 dark:text-gray-600 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
viewBox="0 0 14 20"
fill="currentColor"
>
<circle cx="4" cy="4" r="1.5" />
<circle cx="10" cy="4" r="1.5" />
<circle cx="4" cy="10" r="1.5" />
<circle cx="10" cy="10" r="1.5" />
<circle cx="4" cy="16" r="1.5" />
<circle cx="10" cy="16" r="1.5" />
</svg>
<span className="text-sm font-semibold text-gray-800 dark:text-gray-100 truncate">{title}</span>
</div>
{description && (
<p className="text-[11px] text-gray-400 dark:text-gray-500 truncate mt-0.5 ml-[22px]">{description}</p>
)}
</div>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
onRemove();
}}
className="ml-2 p-1.5 text-gray-300 dark:text-gray-600 hover:text-red-500 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/30 rounded-lg transition-colors shrink-0 opacity-0 group-hover:opacity-100"
title="Remove widget"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Subtle separator */}
<div className="mx-4 border-t border-gray-100 dark:border-gray-800" />
{/* Body */}
<div className="flex-1 overflow-auto p-4">{children}</div>
</motion.div>
);
}