5a43894875
Four fixes: 1. Mobile edit fullscreen scroll: the Sheet primitive's data-[side=bottom]:h-auto was overriding our h-[100dvh] on SheetForm, preventing scroll. Added data-[side=bottom]:h-[100dvh] to the SheetForm className to win the specificity battle. 2. Direct-edit close showed list view: when opened via editWidgetId (the hover edit button), saving or canceling called reset() which showed the widget list instead of closing the dialog. Now derives directEdit from editWidgetId — when true, reset() calls onClose() to close entirely. 3. Copy button missing on mobile: MobileWidgetSections didn't pass onCopy to its WidgetInstanceCard instances. Now accepts and wires onCopyWidget, so referenced widgets show the copy/detach button on mobile too. 4. Service enabled badge stale: ServiceConfigEditor showed instance.enabled (the initial prop) instead of the local enabled state. Now reads the local enabled variable so the badge updates when the user toggles the switch. 128 tests pass; 0 lint errors; build clean.
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Settings2, Copy } from "lucide-react";
|
|
import { useServiceInstances } from "../hooks/useServices";
|
|
import { resolveWidget } from "../integrations/registry";
|
|
import type { WidgetInstance } from "../types";
|
|
import { SectionCard } from "./SectionCard";
|
|
|
|
interface Props {
|
|
widget: WidgetInstance;
|
|
/** When provided, an edit button appears in the top-right corner (hover on desktop, always on mobile). */
|
|
onEdit?: (widgetId: string) => void;
|
|
/** When provided, a copy/detach button appears next to edit (for referenced widgets). */
|
|
onCopy?: (widgetId: string) => void;
|
|
}
|
|
|
|
export function WidgetInstanceCard({ widget, onEdit, onCopy }: Props) {
|
|
const { data: services = [] } = useServiceInstances();
|
|
const resolved = resolveWidget(widget, services);
|
|
|
|
// Edit + copy buttons: always visible on mobile (below md), hover-reveal on desktop.
|
|
const actionButtons = (
|
|
<div className="absolute right-2 top-2 z-10 flex items-center gap-1">
|
|
{onCopy ? (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
className="opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100 mobile-touch-target"
|
|
onClick={() => onCopy(widget.id)}
|
|
aria-label="Create independent copy"
|
|
>
|
|
<Copy className="h-4 w-4" />
|
|
</Button>
|
|
) : null}
|
|
{onEdit ? (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
className="opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100 mobile-touch-target"
|
|
onClick={() => onEdit(widget.id)}
|
|
aria-label="Edit widget"
|
|
>
|
|
<Settings2 className="h-4 w-4" />
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
);
|
|
|
|
if (!resolved) {
|
|
const label = widget.service_id
|
|
? `Unknown widget: ${widget.widget_kind} (service-bound)`
|
|
: `Unknown widget: ${widget.widget_kind} (built-in)`;
|
|
return (
|
|
<div className="group relative">
|
|
{onEdit || onCopy ? actionButtons : null}
|
|
<SectionCard title={widget.title}>
|
|
<Alert>
|
|
<AlertDescription>{label}</AlertDescription>
|
|
</Alert>
|
|
</SectionCard>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const Component = resolved.component;
|
|
return (
|
|
<div className="group relative">
|
|
{onEdit || onCopy ? actionButtons : null}
|
|
<Component
|
|
widget={widget}
|
|
refreshIntervalMs={resolved.refreshIntervalMs}
|
|
description={resolved.description}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|