From 738523b7214497043dbfc44c0e9ba42ce33aa02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 21 May 2026 21:41:30 -0400 Subject: [PATCH] feat(studio): add styled Tooltip component to tabs and panels Create a Tooltip component with styled popover (dark bg, border, shadow) that appears on hover with a 400ms delay. Applied to: - Left sidebar tabs: Code, Comps, Assets, Catalog - Right panel tabs: Design, Layers, Motion, Renders Replaces native title attributes with proper styled tooltips. --- .../src/components/StudioRightPanel.tsx | 87 ++++++++++--------- .../src/components/sidebar/LeftSidebar.tsx | 87 ++++++++++--------- packages/studio/src/components/ui/Tooltip.tsx | 61 +++++++++++++ packages/studio/src/components/ui/index.ts | 1 + 4 files changed, 154 insertions(+), 82 deletions(-) create mode 100644 packages/studio/src/components/ui/Tooltip.tsx diff --git a/packages/studio/src/components/StudioRightPanel.tsx b/packages/studio/src/components/StudioRightPanel.tsx index 90b72920e..f009e6426 100644 --- a/packages/studio/src/components/StudioRightPanel.tsx +++ b/packages/studio/src/components/StudioRightPanel.tsx @@ -1,3 +1,4 @@ +import { Tooltip } from "./ui"; import { PropertyPanel } from "./editor/PropertyPanel"; import { MotionPanel } from "./editor/MotionPanel"; import { LayersPanel } from "./editor/LayersPanel"; @@ -106,58 +107,62 @@ export function StudioRightPanel({
{STUDIO_INSPECTOR_PANELS_ENABLED && ( <> - - - {STUDIO_MOTION_PANEL_ENABLED && ( + + + + + + {STUDIO_MOTION_PANEL_ENABLED && ( + + + )} )} - + + +
{rightPanelTab === "block-params" && activeBlockParams ? ( diff --git a/packages/studio/src/components/sidebar/LeftSidebar.tsx b/packages/studio/src/components/sidebar/LeftSidebar.tsx index a7b9fe167..28ea6ccae 100644 --- a/packages/studio/src/components/sidebar/LeftSidebar.tsx +++ b/packages/studio/src/components/sidebar/LeftSidebar.tsx @@ -13,6 +13,7 @@ import { trackStudioEvent } from "../../utils/studioTelemetry"; import { BlocksTab, type BlockPreviewInfo } from "./BlocksTab"; import { FileTree } from "../editor/FileTree"; import { STUDIO_BLOCKS_PANEL_ENABLED } from "../editor/manualEditingAvailability"; +import { Tooltip } from "../ui"; export type SidebarTab = "compositions" | "assets" | "code" | "blocks"; @@ -124,55 +125,59 @@ export const LeftSidebar = memo( : "1fr 1fr 1fr", }} > - - - - {STUDIO_BLOCKS_PANEL_ENABLED && ( + + + + + + + + + {STUDIO_BLOCKS_PANEL_ENABLED && ( + + + )}
{onToggleCollapse && ( diff --git a/packages/studio/src/components/ui/Tooltip.tsx b/packages/studio/src/components/ui/Tooltip.tsx new file mode 100644 index 000000000..f5ab4739d --- /dev/null +++ b/packages/studio/src/components/ui/Tooltip.tsx @@ -0,0 +1,61 @@ +import { useState, useRef, useCallback, type ReactNode } from "react"; +import { createPortal } from "react-dom"; + +interface TooltipProps { + label: string; + children: ReactNode; + delay?: number; + side?: "top" | "bottom"; +} + +export function Tooltip({ label, children, delay = 400, side = "top" }: TooltipProps) { + const [visible, setVisible] = useState(false); + const [pos, setPos] = useState({ x: 0, y: 0 }); + const timerRef = useRef | null>(null); + const triggerRef = useRef(null); + + const show = useCallback(() => { + timerRef.current = setTimeout(() => { + const el = triggerRef.current; + if (!el) return; + const rect = el.getBoundingClientRect(); + setPos({ + x: rect.left + rect.width / 2, + y: side === "top" ? rect.top - 6 : rect.bottom + 6, + }); + setVisible(true); + }, delay); + }, [delay, side]); + + const hide = useCallback(() => { + if (timerRef.current) { + clearTimeout(timerRef.current); + timerRef.current = null; + } + setVisible(false); + }, []); + + return ( + <> + + {children} + + {visible && + createPortal( +
+
+ {label} +
+
, + document.body, + )} + + ); +} diff --git a/packages/studio/src/components/ui/index.ts b/packages/studio/src/components/ui/index.ts index 9623c7362..0a6987e69 100644 --- a/packages/studio/src/components/ui/index.ts +++ b/packages/studio/src/components/ui/index.ts @@ -2,3 +2,4 @@ export { Button, IconButton } from "./Button"; export { HyperframesLoader, StatusFrame } from "./HyperframesLoader"; export type { HyperframesLoaderProps } from "./HyperframesLoader"; +export { Tooltip } from "./Tooltip";