diff --git a/frontend/src/components/designer/ComponentPalette.tsx b/frontend/src/components/designer/ComponentPalette.tsx
index f33cf477..5c18a94f 100644
--- a/frontend/src/components/designer/ComponentPalette.tsx
+++ b/frontend/src/components/designer/ComponentPalette.tsx
@@ -112,6 +112,42 @@ export function ComponentPalette({
);
}
+export function ComponentDetails({
+ component,
+ showImage = true,
+}: {
+ component: DesignerComponentEntry;
+ showImage?: boolean;
+}) {
+ const imageUrl = getComponentImageUrl(component.id);
+ return (
+ <>
+ {showImage && (
+ imageUrl ? (
+
+ ) : (
+
+ {component.componentType.slice(0, 3).toUpperCase()}
+
+ )
+ )}
+
+
{component.name}
+
Mass {component.mass} kt
+ {primaryStat(component) ? (
+
{primaryStat(component)}
+ ) : null}
+
+ >
+ );
+}
+
function PaletteItem({
component,
active,
@@ -125,7 +161,6 @@ function PaletteItem({
onSelectComponent?: (componentId: string) => void;
readOnly: boolean;
}) {
- const imageUrl = getComponentImageUrl(component.id);
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
id: `palette-${component.id}`,
data: { kind: "palette", componentId: component.id },
@@ -156,26 +191,7 @@ function PaletteItem({
: { touchAction: "none" }
}
>
- {imageUrl ? (
-
- ) : (
-
- {component.componentType.slice(0, 3).toUpperCase()}
-
- )}
-
-
{component.name}
-
Mass {component.mass} kt
- {primaryStat(component) ? (
-
{primaryStat(component)}
- ) : null}
-
+
);
}
diff --git a/frontend/src/components/designer/DragAndDropFitter.tsx b/frontend/src/components/designer/DragAndDropFitter.tsx
index 40efd710..04e80ebe 100644
--- a/frontend/src/components/designer/DragAndDropFitter.tsx
+++ b/frontend/src/components/designer/DragAndDropFitter.tsx
@@ -8,7 +8,8 @@ import {
useSensors,
type DragEndEvent,
} from "@dnd-kit/core";
-import { useMemo, useState, type ReactNode } from "react";
+import { useMemo, useRef, useState, type ReactNode } from "react";
+import { createPortal } from "react-dom";
import type {
DesignerComponentEntry,
HullDefinition,
@@ -21,7 +22,7 @@ import {
} from "../../lib/designerFit";
import { getComponentImageUrl } from "../../lib/componentImages";
import { computeDerivedStats } from "../../lib/designerStats";
-import { ComponentPalette } from "./ComponentPalette";
+import { ComponentDetails, ComponentPalette } from "./ComponentPalette";
import { HullLayout } from "./HullLayout";
import { StatsPanel } from "./StatsPanel";
@@ -114,14 +115,15 @@ export function DragAndDropFitter({
{actions}
-
-
-
+ {!readOnly && (
+
+
+
+ )}
);
@@ -152,6 +154,20 @@ function DroppableSlot({
data: { kind: "slot", slotNumber: slot.slotNumber, slotCategories: slot.slotCategories },
disabled: readOnly,
});
+ const hoverTimerRef = useRef | null>(null);
+ const [hoverRect, setHoverRect] = useState(null);
+
+ function handleMouseEnter(event: React.MouseEvent) {
+ if (!component) return;
+ const rect = event.currentTarget.getBoundingClientRect();
+ hoverTimerRef.current = setTimeout(() => setHoverRect(rect), 300);
+ }
+
+ function handleMouseLeave() {
+ if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
+ setHoverRect(null);
+ }
+
return (
+ {hoverRect && component &&
+ createPortal(
+
+
+
,
+ document.body,
+ )}
{componentImageUrl ? (
![]()