refactor(studio): extract shared timeline components and deduplicate code (#1329)

Extract shared utilities to reduce duplication across timeline components:

- PlayheadIndicator: shared playhead rendering (was duplicated in
  TimelineCanvas and TimelineEditorNotice)
- useContextMenuDismiss: outside-click/Escape dismiss pattern (was
  duplicated in ClipContextMenu and KeyframeDiamondContextMenu)
- TimelineCallbacks: shared callback interfaces for drop and edit
  operations (was duplicated in NLELayout and Timeline props)
- useTimelineZoom: consolidated zoom store selectors
- timelineElementSplit: shared canSplitElement, buildPatchTarget, and
  readFileContent utilities
- gsapParser.test-helpers: shared test utilities for parser specs
This commit is contained in:
Miguel Ángel
2026-06-10 23:45:03 -04:00
committed by GitHub
parent 06426b5014
commit ab08260201
12 changed files with 373 additions and 157 deletions
@@ -0,0 +1,29 @@
import { useCallback, useEffect, useRef, type RefObject } from "react";
/**
* Shared dismiss logic for context menus: closes on outside click or Escape.
* Returns a ref to attach to the menu container element.
*/
export function useContextMenuDismiss(onClose: () => void): RefObject<HTMLDivElement | null> {
const menuRef = useRef<HTMLDivElement>(null);
const dismiss = useCallback(
(e: MouseEvent | KeyboardEvent) => {
if (e instanceof KeyboardEvent && e.key !== "Escape") return;
if (e instanceof MouseEvent && menuRef.current?.contains(e.target as Node)) return;
onClose();
},
[onClose],
);
useEffect(() => {
document.addEventListener("mousedown", dismiss);
document.addEventListener("keydown", dismiss);
return () => {
document.removeEventListener("mousedown", dismiss);
document.removeEventListener("keydown", dismiss);
};
}, [dismiss]);
return menuRef;
}