mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
fix(ui): dismiss annotation toolbars on outside click and Escape (#182)
* fix: close annotation toolbars on outside click and escape * refactor: share dismiss logic for annotation toolbars
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "plannotator",
|
||||
"dependencies": {
|
||||
"@pierre/diffs": "^1.0.4",
|
||||
"diff": "^8.0.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"happy-dom": "^20.5.0",
|
||||
@@ -52,7 +54,7 @@
|
||||
},
|
||||
"apps/opencode-plugin": {
|
||||
"name": "@plannotator/opencode",
|
||||
"version": "0.8.5",
|
||||
"version": "0.9.2",
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "^1.1.10",
|
||||
},
|
||||
@@ -65,7 +67,7 @@
|
||||
},
|
||||
"apps/pi-extension": {
|
||||
"name": "@plannotator/pi-extension",
|
||||
"version": "0.8.5",
|
||||
"version": "0.9.2",
|
||||
"peerDependencies": {
|
||||
"@mariozechner/pi-coding-agent": ">=0.53.0",
|
||||
},
|
||||
@@ -132,7 +134,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@plannotator/server",
|
||||
"version": "0.8.5",
|
||||
"version": "0.9.2",
|
||||
"peerDependencies": {
|
||||
"bun": ">=1.0.0",
|
||||
},
|
||||
|
||||
+2
-1
@@ -29,7 +29,8 @@
|
||||
"test": "bun test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pierre/diffs": "^1.0.4"
|
||||
"@pierre/diffs": "^1.0.4",
|
||||
"diff": "^8.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"happy-dom": "^20.5.0"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
||||
import { PatchDiff } from '@pierre/diffs/react';
|
||||
import { CodeAnnotation, CodeAnnotationType, SelectedLineRange, DiffAnnotationMetadata } from '@plannotator/ui/types';
|
||||
import { useDismissOnOutsideAndEscape } from '@plannotator/ui/hooks/useDismissOnOutsideAndEscape';
|
||||
import { useTheme } from '@plannotator/ui/components/ThemeProvider';
|
||||
|
||||
interface DiffViewerProps {
|
||||
@@ -39,6 +40,7 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||
const [toolbarState, setToolbarState] = useState<ToolbarState | null>(null);
|
||||
const [commentText, setCommentText] = useState('');
|
||||
const [suggestedCode, setSuggestedCode] = useState('');
|
||||
@@ -140,6 +142,12 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
||||
onLineSelection(null);
|
||||
}, [onLineSelection]);
|
||||
|
||||
useDismissOnOutsideAndEscape({
|
||||
enabled: !!toolbarState,
|
||||
ref: toolbarRef,
|
||||
onDismiss: handleCancel,
|
||||
});
|
||||
|
||||
// Render annotation in diff - returns React element
|
||||
const renderAnnotation = useCallback((annotation: { side: string; lineNumber: number; metadata?: DiffAnnotationMetadata }) => {
|
||||
if (!annotation.metadata) return null;
|
||||
@@ -294,6 +302,7 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
||||
{/* Annotation toolbar - single-step comment input */}
|
||||
{toolbarState && (
|
||||
<div
|
||||
ref={toolbarRef}
|
||||
className="review-toolbar"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from "react";
|
||||
import { AnnotationType, type ImageAttachment } from "../types";
|
||||
import { createPortal } from "react-dom";
|
||||
import { AttachmentsButton } from "./AttachmentsButton";
|
||||
import { useDismissOnOutsideAndEscape } from "../hooks/useDismissOnOutsideAndEscape";
|
||||
|
||||
type PositionMode = 'center-above' | 'top-right';
|
||||
|
||||
@@ -54,6 +55,7 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
|
||||
const [position, setPosition] = useState<{ top: number; left?: number; right?: number } | null>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleCopy = async () => {
|
||||
// Use provided copyText, or fall back to code element / element text
|
||||
@@ -158,6 +160,12 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [step, onClose]);
|
||||
|
||||
useDismissOnOutsideAndEscape({
|
||||
enabled: true,
|
||||
ref: toolbarRef,
|
||||
onDismiss: onClose,
|
||||
});
|
||||
|
||||
if (!position) return null;
|
||||
|
||||
const handleTypeSelect = (type: AnnotationType) => {
|
||||
@@ -191,6 +199,7 @@ export const AnnotationToolbar: React.FC<AnnotationToolbarProps> = ({
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
ref={toolbarRef}
|
||||
className="annotation-toolbar fixed z-[100] bg-popover border border-border rounded-lg shadow-2xl"
|
||||
style={style}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function useDismissOnOutsideAndEscape({
|
||||
enabled,
|
||||
ref,
|
||||
onDismiss,
|
||||
}: {
|
||||
enabled: boolean;
|
||||
ref: React.RefObject<HTMLElement>;
|
||||
onDismiss: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
|
||||
const handlePointerDown = (event: PointerEvent) => {
|
||||
const target = event.target as Node | null;
|
||||
if (!target) return;
|
||||
if (ref.current && ref.current.contains(target)) {
|
||||
return;
|
||||
}
|
||||
onDismiss();
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
onDismiss();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("pointerdown", handlePointerDown, true);
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("pointerdown", handlePointerDown, true);
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [enabled, ref, onDismiss]);
|
||||
}
|
||||
Reference in New Issue
Block a user