) : null}
{/* Semantic mode hint */}
{searchCommand === "semantic" && searchPanelOpen ? (
-
+
Search by meaning and visual concepts
) : null}
{/* Command hints — only shown when no command is active */}
{showCommandHints ? (
-
+
{(
[
- { command: "tag" as SearchCommand, prefix: "/t", label: "Tags", description: "Search AI and user tags" },
- { command: "semantic" as SearchCommand, prefix: "/s", label: "Semantic", description: "Search by meaning" },
+ { command: "tag" as SearchCommand, prefix: "/t", label: "Tags", description: "Search user and AI tags" },
+ { command: "semantic" as SearchCommand, prefix: "/s", label: "Semantic", description: "Search by meaning, object, mood" },
] as const
).map((option) => (
{(["compact", "comfortable", "detail"] as const).map((preset, i) => (
- 0 ? "border-l border-white/8" : ""
- } ${
- zoomPreset === preset
- ? "bg-white/10 text-white light-theme:bg-gray-900 light-theme:text-white"
- : "text-gray-500 hover:text-gray-200 hover:bg-white/5 light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
- }`}
- title={`${tileSize}px tiles`}
- onClick={() => setZoomPreset(preset)}
- >
- {preset === "compact" ? "S" : preset === "comfortable" ? "M" : "L"}
-
+
+ 0 ? "border-l border-white/8" : ""
+ } ${
+ zoomPreset === preset
+ ? "bg-white/10 text-white light-theme:bg-gray-900 light-theme:text-white"
+ : "text-gray-500 hover:text-gray-200 hover:bg-white/5 light-theme:text-gray-600 light-theme:hover:bg-gray-900 light-theme:hover:text-white"
+ }`}
+ onClick={() => setZoomPreset(preset)}
+ >
+ {preset === "compact" ? "S" : preset === "comfortable" ? "M" : "L"}
+
+
))}
diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx
index c345c4a..6aee866 100644
--- a/src/components/Tooltip.tsx
+++ b/src/components/Tooltip.tsx
@@ -1,4 +1,5 @@
import { MouseEvent, ReactNode, useEffect, useRef, useState } from "react";
+import { createPortal } from "react-dom";
import { motion } from "framer-motion";
type TooltipSide = "top" | "bottom" | "left" | "right";
@@ -20,8 +21,8 @@ const STATIC_CLASSES = `${BASE_CLASSES} transition-opacity duration-100`;
/**
* Lightweight custom tooltip — fades in (faster than the native one) after a
* tunable hover `delay`, and hides instantly on leave or click. By default it
- * anchors to a `side` of the wrapper; with `followCursor` it tracks the pointer
- * (fixed-positioned, so it escapes scroll-container clipping).
+ * anchors to a `side` of the wrapper. `anchorToCursor` shows at the pointer's
+ * entry position; `followCursor` keeps tracking the pointer.
*/
export function Tooltip({
label,
@@ -29,6 +30,7 @@ export function Tooltip({
side = "bottom",
align = "center",
block = false,
+ anchorToCursor = false,
followCursor = false,
className = "",
children,
@@ -41,6 +43,8 @@ export function Tooltip({
align?: TooltipAlign;
/** Full-width block wrapper (e.g. wrapping a grid cell) instead of inline. */
block?: boolean;
+ /** Position at the cursor when shown, without following subsequent movement. */
+ anchorToCursor?: boolean;
/** Track the cursor (fixed position) instead of anchoring to a side. */
followCursor?: boolean;
/** Extra classes for the wrapper (e.g. layout). */
@@ -49,6 +53,7 @@ export function Tooltip({
}) {
const [visible, setVisible] = useState(false);
const [coords, setCoords] = useState({ x: 0, y: 0 });
+ const [mounted, setMounted] = useState(false);
const timer = useRef
| undefined>(undefined);
const frame = useRef(undefined);
const tooltipRef = useRef(null);
@@ -79,7 +84,7 @@ export function Tooltip({
};
const show = (event: MouseEvent) => {
clear();
- if (followCursor) place(event.clientX, event.clientY);
+ if (anchorToCursor || followCursor) place(event.clientX, event.clientY);
if (delay <= 0) {
setVisible(true);
return;
@@ -100,9 +105,31 @@ export function Tooltip({
});
};
- useEffect(() => clear, []);
+ useEffect(() => {
+ setMounted(true);
+ return clear;
+ }, []);
const Wrapper = block ? "div" : "span";
+ const cursorTooltip = (
+
+ {label}
+
+ );
return (
{children}
- {followCursor ? (
-
- {label}
-
+ {anchorToCursor || followCursor ? (
+ mounted ? createPortal(cursorTooltip, document.body) : null
) : (