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>
This commit is contained in:
@@ -157,6 +157,7 @@ export function DashboardClient() {
|
|||||||
<div key={widget.id}>
|
<div key={widget.id}>
|
||||||
<WidgetContainer
|
<WidgetContainer
|
||||||
title={widget.title ?? getWidget(widget.type).label}
|
title={widget.title ?? getWidget(widget.type).label}
|
||||||
|
description={getWidget(widget.type).description}
|
||||||
onRemove={() => removeWidget(widget.id)}
|
onRemove={() => removeWidget(widget.id)}
|
||||||
>
|
>
|
||||||
{renderWidget(widget.type, widget.config, (update) =>
|
{renderWidget(widget.type, widget.config, (update) =>
|
||||||
|
|||||||
@@ -4,31 +4,54 @@ import { motion } from "framer-motion";
|
|||||||
|
|
||||||
interface WidgetContainerProps {
|
interface WidgetContainerProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
description?: string;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
isDragging?: boolean;
|
isDragging?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WidgetContainer({ title, onRemove, children, isDragging }: WidgetContainerProps) {
|
export function WidgetContainer({ title, description, onRemove, children, isDragging }: WidgetContainerProps) {
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 16 }}
|
initial={{ opacity: 0, y: 16 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.35, ease: "easeOut" }}
|
transition={{ duration: 0.35, ease: "easeOut" }}
|
||||||
className={`flex flex-col h-full bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden transition-colors duration-200 ${
|
className={`flex flex-col h-full rounded-xl border overflow-hidden transition-all duration-200 ${
|
||||||
isDragging ? "shadow-lg border-brand-300" : "hover:border-brand-200 dark:hover:border-brand-800"
|
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 */}
|
{/* Header — clean, no background separation */}
|
||||||
<div className="flex items-center justify-between px-4 py-2.5 border-b border-gray-100 bg-gray-50/50 shrink-0 cursor-grab active:cursor-grabbing widget-drag-handle">
|
<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">
|
||||||
<span className="text-sm font-semibold text-gray-700 truncate">{title}</span>
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onRemove();
|
onRemove();
|
||||||
}}
|
}}
|
||||||
className="ml-2 p-1 text-gray-400 hover:text-gray-700 hover:bg-gray-100 rounded transition-colors shrink-0"
|
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"
|
title="Remove widget"
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -37,6 +60,9 @@ export function WidgetContainer({ title, onRemove, children, isDragging }: Widge
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Subtle separator */}
|
||||||
|
<div className="mx-4 border-t border-gray-100 dark:border-gray-800" />
|
||||||
|
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="flex-1 overflow-auto p-4">{children}</div>
|
<div className="flex-1 overflow-auto p-4">{children}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
Reference in New Issue
Block a user