Files
fusion/packages/dashboard/app/hooks/useOverlayDismiss.ts
gsxdsm b196ab42b4 fix(dashboard): no resize-drag dismiss across all resizable modals + Claude CLI in Authenticated group
When a user drags the native CSS resize grip from inside a modal and
releases the mouse over the overlay, the synthesised click event
targets the common ancestor (the overlay) — fooling the existing
e.target === e.currentTarget dismiss check. Audited every modal with
`resize: both` and switched them to a shared mousedown→mouseup tracking
pattern (new useOverlayDismiss hook) so dismiss only fires when both
events land on the overlay.

Modals fixed: TaskDetail, Settings, FileBrowser, GitHubImport,
GitManager, ScheduledTasks, Scripts, WorkflowStepManager. (Terminal
and AgentDetail were already fixed in 95566795e; PlanningModeModal
already had the right pattern inline.)

Also: in Settings → Authentication, the "Anthropic via Claude CLI"
card now lives inside the Authenticated group when authenticated and
the Available group otherwise, instead of floating at the top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:36:24 -07:00

39 lines
1.4 KiB
TypeScript

import { useCallback, useRef } from "react";
/**
* Returns props for a modal-overlay element that dismisses only when a real
* overlay click happens — i.e. both mousedown AND mouseup land on the overlay
* itself.
*
* This avoids a subtle dismiss-during-resize bug: when a user drags the
* native CSS `resize: both` grip from inside a modal and releases the mouse
* over the overlay, the synthesised click event targets the common ancestor
* (the overlay). A naive `onClick` handler that checks `e.target === e.currentTarget`
* is fooled and closes the modal mid-resize.
*
* Spread the returned props on the overlay element. The inner modal element
* does NOT need to stopPropagation — mousedown on the modal sets the ref to
* `false`, so the overlay's mouseup handler bails.
*/
export function useOverlayDismiss(onClose: () => void): {
onMouseDown: (e: React.MouseEvent) => void;
onMouseUp: (e: React.MouseEvent) => void;
} {
const startedOnOverlayRef = useRef(false);
const onMouseDown = useCallback((e: React.MouseEvent) => {
startedOnOverlayRef.current = e.target === e.currentTarget;
}, []);
const onMouseUp = useCallback(
(e: React.MouseEvent) => {
const shouldClose = startedOnOverlayRef.current && e.target === e.currentTarget;
startedOnOverlayRef.current = false;
if (shouldClose) onClose();
},
[onClose],
);
return { onMouseDown, onMouseUp };
}