Skip to content

upgrade DropdowmMenuCopyButton by closing dropdown conditionally#346

Merged
chmurson merged 3 commits intomainfrom
upgrade-dropdownmenuitem-copybutton
Nov 20, 2025
Merged

upgrade DropdowmMenuCopyButton by closing dropdown conditionally#346
chmurson merged 3 commits intomainfrom
upgrade-dropdownmenuitem-copybutton

Conversation

@chmurson
Copy link
Collaborator

@chmurson chmurson commented Nov 20, 2025

Context

DropdownMenuItemCopyButton closes the dropdown after it is clicked and 2s delays passes.

What

The component - DropdownMenuItemCopyButton (actually not this component by its parent) - adds new condition before closing the dropdown which is - it checks whether mouse is over dropdown content or button that triggers the opening of the dropdown.

Summary by CodeRabbit

  • New Features

    • Added two-step confirmation for certain dropdown actions.
    • Added copy-initiated handling and proximity-aware behavior so copy actions better respect pointer location.
  • Bug Fixes

    • Dropdown now reliably remains open while hovering over content or trigger during copy, reducing accidental closures.

✏️ Tip: You can customize this high-level summary in your review settings.

@netlify
Copy link

netlify bot commented Nov 20, 2025

Deploy Preview for graypaper-reader ready!

Name Link
🔨 Latest commit ce79d5d
🔍 Latest deploy log https://app.netlify.com/projects/graypaper-reader/deploys/691f1d871b48db000839c26e
😎 Deploy Preview https://deploy-preview-346--graypaper-reader.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

Walkthrough

Replaced inline two-step confirm logic in NoteDropdown with a hook-based mouse-position tracking approach; added content/button refs and copy-initiation callback wiring. Introduced a new TwoStepDropdownMenuItem component and utilities (mouse-tracking hook and isMouseOverElement). DropdownMenuItemCopyButton now supports onCopyInitiated.

Changes

Cohort / File(s) Summary
NoteDropdown refactor
src/components/NoteManager/components/NoteDropdown.tsx
Replaced inline two-step confirmation with hook-based mouse-position tracking; added contentRef and buttonRef; integrated tracking to decide whether to close dropdown after copy; updated event handler types to local alias.
Copy button API update
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx
Added onCopyInitiated prop and new handleClick that calls onCopyInitiated before performing clipboard copy; preserves existing success/error handling and onCopyComplete behavior.
New confirmation component
src/components/NoteManager/components/SimpleComponents/TwoStepDropdownMenuItem.tsx
Added TwoStepDropdownMenuItem component implementing a two-step confirmation UI with an internal useBooleanStateWithAutoRevertToFalse hook (auto-reverts state after a delay).
New internal utilities / hooks
src/components/NoteManager/components/...
Added useToggleableMousePositionTracking hook (returns isTracked, setIsTracked, mousePositionRef) and isMouseOverElement utility; removed former in-file TwoStepDropdownMenuItem logic in favor of the hook-based approach.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CopyBtn as DropdownMenuItemCopyButton
    participant Track as MouseTrackingHook
    participant Dropdown as NoteDropdown

    User->>CopyBtn: Click "Copy"
    activate CopyBtn
    CopyBtn->>Track: onCopyInitiated()  -- enable tracking
    activate Track
    CopyBtn->>CopyBtn: Write URL to clipboard
    CopyBtn->>CopyBtn: set success state, schedule reset
    CopyBtn->>Dropdown: onCopyComplete()
    deactivate CopyBtn

    rect `#E8F3FF`
    Note over Track,Dropdown: Track checks mousePositionRef
    Track->>Dropdown: isMouseOverElement(buttonRef/contentRef)?
    end

    alt Mouse outside button/content
        Dropdown->>Dropdown: Dispatch Escape -> close dropdown
    else Mouse inside button/content
        Dropdown->>Dropdown: Keep dropdown open
    end

    deactivate Track
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review mouse-tracking hook semantics, ref handling, and cleanup.
  • Verify isMouseOverElement geometry checks and edge cases.
  • Confirm onCopyInitiated/onCopyComplete ordering and timeouts.
  • Ensure refs are forwarded correctly to the dropdown/backing DOM nodes.

Possibly related PRs

  • Notes dropdown #344 — Refactors the same NoteManager dropdown components and introduces related copy/confirmation changes.

Poem

🐰
I hopped upon a dropdown bright,
Tracked the cursor in the night,
A copy clicked, a gentle nudge,
Two steps confirm — no sudden judge,
Refined and quick, my whiskers twitch with delight.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: conditionally closing the dropdown in DropdownMenuItemCopyButton based on mouse position, which aligns with the PR's core objective.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch upgrade-dropdownmenuitem-copybutton

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/components/NoteManager/components/SimpleComponents/TwoStepDropdownMenuItem.tsx (1)

33-53: Optional: Simplify the ref pattern.

The delayInMsRef pattern is used but delayInMs appears to be constant throughout the hook's lifecycle. You can simplify by using the parameter directly in the timeout.

Apply this diff if you'd like to simplify:

 function useBooleanStateWithAutoRevertToFalse({ delayInMs }: { delayInMs: number }) {
   const [state, setState] = useState(false);
-  const delayInMsRef = useRef(delayInMs);
-  delayInMsRef.current = delayInMs;
 
   useEffect(() => {
     if (!state) {
       return;
     }
 
     const timeoutHandle = setTimeout(() => {
       setState(false);
-    }, delayInMsRef.current);
+    }, delayInMs);
 
     return () => {
       clearTimeout(timeoutHandle);
     };
-  }, [state]);
+  }, [state, delayInMs]);
 
   return [state, setState] as const;
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8073d20 and b675944.

📒 Files selected for processing (3)
  • src/components/NoteManager/components/NoteDropdown.tsx (5 hunks)
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (2 hunks)
  • src/components/NoteManager/components/SimpleComponents/TwoStepDropdownMenuItem.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

⚙️ CodeRabbit configuration file

When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.

Files:

  • src/components/NoteManager/components/SimpleComponents/TwoStepDropdownMenuItem.tsx
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx
  • src/components/NoteManager/components/NoteDropdown.tsx
🧠 Learnings (2)
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: Project targets React 19+; prefer passing refs as a regular prop in function components and avoid React.forwardRef in new code. File: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteDropdown.tsx
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: This repo targets React 19+; prefer passing refs as a normal prop to function components and avoid React.forwardRef in new code. File context: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteDropdown.tsx
🧬 Code graph analysis (1)
src/components/NoteManager/components/NoteDropdown.tsx (1)
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1)
  • DropdownMenuItemCopyButton (6-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: visual-tests

@github-actions
Copy link
Contributor

github-actions bot commented Nov 20, 2025

Visual Regression Test Report ✅ Passed

Github run id: 19539241593

🔗 Artifacts: Download

@chmurson chmurson force-pushed the upgrade-dropdownmenuitem-copybutton branch from 253aee8 to b675944 Compare November 20, 2025 12:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/components/NoteManager/components/SimpleComponents/TwoStepDropdownMenuItem.tsx (1)

21-29: Consider adding accessibility announcements for state transitions.

When the component transitions to confirmation state, users with screen readers may not be aware of the visual change from children to confirmationSlot. Consider adding an aria-live="polite" region or aria-describedby pointing to a descriptive element to announce the state change.

Example approach:

  return (
-   <DropdownMenuItem
+   <DropdownMenuItem
+     aria-live="polite"
+     aria-label={isConfirmation ? "Confirm action" : undefined}
      onClick={handleOnClick}
      className={cn(isConfirmation ? "text-destructive hover:bg-destructive/20 hover:text-destructive" : "")}
    >
      {!isConfirmation && children}
      {isConfirmation && confirmationSlot}
    </DropdownMenuItem>
  );
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b675944 and ce79d5d.

📒 Files selected for processing (3)
  • src/components/NoteManager/components/NoteDropdown.tsx (5 hunks)
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (2 hunks)
  • src/components/NoteManager/components/SimpleComponents/TwoStepDropdownMenuItem.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/NoteManager/components/NoteDropdown.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

⚙️ CodeRabbit configuration file

When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.

Files:

  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx
  • src/components/NoteManager/components/SimpleComponents/TwoStepDropdownMenuItem.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: visual-tests
🔇 Additional comments (3)
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1)

30-44: Async clipboard handling implemented correctly.

The previous review concern about awaiting the clipboard operation has been properly addressed. The implementation now:

  • Makes handleClick async
  • Awaits navigator.clipboard.writeText() before setting success state
  • Properly handles errors with try-catch

Calling onCopyInitiated() before the await (line 36) is appropriate given its name—it signals when the copy operation begins, while onCopyComplete (line 22) signals when the entire flow finishes.

src/components/NoteManager/components/SimpleComponents/TwoStepDropdownMenuItem.tsx (2)

11-19: Click handling logic is correct.

The two-step flow is well-implemented:

  • First click: prevents default behavior and enables confirmation state
  • Second click: executes the actual action

The preventDefault() and stopPropagation() on lines 13-14 correctly intercept the first click to show the confirmation UI without triggering the menu item's default behavior.


32-52: Hook implementation is solid.

The useBooleanStateWithAutoRevertToFalse hook correctly:

  • Uses a ref for delayInMs to avoid re-running the effect when only the delay changes
  • Sets up a timeout when state becomes true
  • Cleans up the timeout on unmount or state change
  • Returns a properly-typed tuple with as const

The ref pattern (lines 34-35) is a good practice here since delayInMs is hardcoded to 2000ms in the component. If this hook were extracted for reuse with dynamic delays, you might consider adding delayInMs to the effect dependencies to restart the timeout when the delay changes—but for the current usage, this implementation is appropriate.

@chmurson chmurson merged commit 3ce65be into main Nov 20, 2025
9 checks passed
@chmurson chmurson deleted the upgrade-dropdownmenuitem-copybutton branch November 20, 2025 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant