diff --git a/src/components/DocFeedbackFloatingButton.tsx b/src/components/DocFeedbackFloatingButton.tsx index ecb90a408..802c18d42 100644 --- a/src/components/DocFeedbackFloatingButton.tsx +++ b/src/components/DocFeedbackFloatingButton.tsx @@ -1,5 +1,11 @@ import * as React from 'react' import { twMerge } from 'tailwind-merge' +import { + Dropdown, + DropdownTrigger, + DropdownContent, + DropdownItem, +} from './Dropdown' import { LightbulbIcon, ChatCenteredIcon, @@ -14,6 +20,7 @@ interface DocFeedbackFloatingButtonProps { hasNote?: boolean hasImprovement?: boolean onShowNote?: () => void + isVisible?: boolean isMenuOpen?: boolean onMenuOpenChange?: (isOpen: boolean) => void } @@ -26,6 +33,7 @@ export function DocFeedbackFloatingButton({ hasNote = false, hasImprovement = false, onShowNote, + isVisible = true, isMenuOpen: controlledIsMenuOpen, onMenuOpenChange, }: DocFeedbackFloatingButtonProps) { @@ -39,99 +47,59 @@ export function DocFeedbackFloatingButton({ setInternalIsMenuOpen(value) } } - const buttonRef = React.useRef(null) - - // Close menu when clicking outside or pressing Escape - React.useEffect(() => { - if (!isMenuOpen) return - - const handleClickOutside = (e: MouseEvent) => { - if (buttonRef.current && !buttonRef.current.contains(e.target as Node)) { - setIsMenuOpen(false) - } - } - - const handleEscape = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - setIsMenuOpen(false) - } - } - - document.addEventListener('mousedown', handleClickOutside) - document.addEventListener('keydown', handleEscape) - return () => { - document.removeEventListener('mousedown', handleClickOutside) - document.removeEventListener('keydown', handleEscape) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isMenuOpen]) - - const handleButtonClick = (e: React.MouseEvent) => { - e.preventDefault() - e.stopPropagation() - if (hasNote && onShowNote) { - // If there's a note, expand it and show menu - onShowNote() - } - setIsMenuOpen(!isMenuOpen) - } - - const handleNoteClick = (e: React.MouseEvent) => { - e.preventDefault() - e.stopPropagation() - setIsMenuOpen(false) - onAddNote() - } - - const handleFeedbackClick = (e: React.MouseEvent) => { - e.preventDefault() - e.stopPropagation() - setIsMenuOpen(false) - onAddFeedback() - } - return ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -
e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} + { + if (open && hasNote) onShowNote?.() + setIsMenuOpen(open) + }} > - {/* Button - neutral with shadow */} - + {/* Button - neutral with shadow */} + + + - {/* Context Menu */} - {isMenuOpen && ( -
+ {/* Context Menu */} + {!hasNote && ( <> -
- + {!hasImprovement && (
)} )} {!hasImprovement && ( -
- + )} - - )} - + + + ) } diff --git a/src/components/DocFeedbackProvider.tsx b/src/components/DocFeedbackProvider.tsx index 26125bcba..58dce6e95 100644 --- a/src/components/DocFeedbackProvider.tsx +++ b/src/components/DocFeedbackProvider.tsx @@ -390,18 +390,23 @@ function BlockButton({ // Don't show button if block has both note and improvement (nothing left to add) if (hasNote && hasImprovement && !isMenuOpen) return null - // Only show button if hovered or menu is open - if (!isHovered && !isMenuOpen) return null - // Create portal container for button positioned at top-right of block let portalContainer = block.querySelector( `[data-button-portal="${blockId}"]`, ) + // Keep an existing trigger mounted so closing the menu can restore focus. + if (!portalContainer && !isHovered && !isMenuOpen) return null + if (!portalContainer) { portalContainer = document.createElement('div') portalContainer.setAttribute('data-button-portal', blockId) - portalContainer.className = 'absolute top-0 right-0 -translate-y-full z-20' + // Keep the trigger inside the scrollable code block, below its header. + const isCodeBlock = block.tagName === 'PRE' + portalContainer.className = twMerge( + 'absolute right-0 -translate-y-full z-20', + isCodeBlock ? 'top-6' : 'top-0', + ) portalContainer.style.position = 'absolute' // Make the block relatively positioned if not already @@ -421,6 +426,7 @@ function BlockButton({ hasImprovement={hasImprovement} onShowNote={onShowNote} isMenuOpen={isMenuOpen} + isVisible={isHovered || isMenuOpen} onMenuOpenChange={onMenuOpenChange} />, portalContainer,