.claude/skills/popup-menus/SKILL.md
```skill --- name: popup-menus description: Pattern for creating popup menus anchored to icon buttons --- # Instructions ## Overview Popup menus are icon-triggered dropdown menus that appear over the page content, anchored below the button the user clicked. They automatically adjust position to avoid being clipped by viewport edges. Clicking a menu item fires a callback and dismisses the menu. Clicking outside or pressing Escape also dismisses it. ## Architecture: Two Layers ### 1. Base Com
npx skillsauth add clay-ferguson/mkbrowser .claude/skills/popup-menusInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
---
name: popup-menus
description: Pattern for creating popup menus anchored to icon buttons
---
# Instructions
## Overview
Popup menus are icon-triggered dropdown menus that appear over the page content, anchored below the button the user clicked. They automatically adjust position to avoid being clipped by viewport edges. Clicking a menu item fires a callback and dismisses the menu. Clicking outside or pressing Escape also dismisses it.
## Architecture: Two Layers
### 1. Base Component — `src/components/PopupMenu.tsx`
This is the shared, reusable foundation. **Do not duplicate this logic.** It provides:
- **`PopupMenu`** — wrapper that handles positioning, click-outside dismiss, Escape key dismiss, and viewport edge-clipping. It accepts an `anchorRef` (ref to the trigger button), `onClose` callback, and `children`.
- **`PopupMenuItem`** — a single clickable menu row. Props: `label` (string), `onClick` (callback), `disabled?` (boolean).
- **`PopupMenuDivider`** — a horizontal separator line between menu items.
### 2. Specific Menu Component — e.g. `src/components/ToolsPopupMenu.tsx`
Each menu gets its own component file in `src/components/`. It composes `PopupMenu`, `PopupMenuItem`, and `PopupMenuDivider` to define the menu's items. See `ToolsPopupMenu.tsx` as the reference example.
## Creating a New Popup Menu
### Step 1: Create the menu component
Create `src/components/YourPopupMenu.tsx`:
```tsx
import type { RefObject } from 'react';
import PopupMenu, { PopupMenuItem, PopupMenuDivider } from './PopupMenu';
interface YourPopupMenuProps {
anchorRef: RefObject<HTMLElement | null>;
onClose: () => void;
// Add a callback prop for each menu action:
onSomeAction: () => void;
onAnotherAction: () => void;
}
export default function YourPopupMenu({
anchorRef,
onClose,
onSomeAction,
onAnotherAction,
}: YourPopupMenuProps) {
return (
<PopupMenu anchorRef={anchorRef} onClose={onClose}>
<PopupMenuItem
label="Some Action"
onClick={() => { onSomeAction(); onClose(); }}
/>
<PopupMenuDivider />
<PopupMenuItem
label="Another Action"
onClick={() => { onAnotherAction(); onClose(); }}
/>
</PopupMenu>
);
}
Key rule: Every PopupMenuItem onClick handler must call both the action callback AND onClose() so the menu dismisses after the user clicks.
In the parent (typically src/App.tsx):
Import the menu component and an icon (from @heroicons/react/24/outline or /24/solid).
Add state and ref:
const [showYourMenu, setShowYourMenu] = useState(false);
const yourButtonRef = useRef<HTMLButtonElement>(null);
Add the trigger button (follows the standard icon button pattern):
<button
ref={yourButtonRef}
onClick={() => setShowYourMenu(prev => !prev)}
className="p-2 text-slate-400 hover:bg-slate-700 rounded-lg transition-colors"
title="Your Menu"
>
<YourIcon className="w-5 h-5" />
</button>
Conditionally render the menu (place it near the other dialogs/menus at the bottom of the JSX return):
{showYourMenu && (
<YourPopupMenu
anchorRef={yourButtonRef}
onClose={() => setShowYourMenu(false)}
onSomeAction={() => { /* handler logic */ }}
onAnotherAction={() => { /* handler logic */ }}
/>
)}
The base PopupMenu component handles all positioning automatically:
fixed positioning with getBoundingClientRect() for viewport-relative coordinates.visibility: hidden) until position is calculated to prevent flicker.Handled automatically by PopupMenu:
mousedown listener on document checks if click target is outside both the menu and the anchor button.keydown listener on document.PopupMenuItem's onClick should call onClose().All popup menus use these consistent Tailwind classes (defined in PopupMenu.tsx):
fixed z-50 bg-slate-800 border border-slate-600 rounded-lg shadow-xl py-1 min-w-[180px]w-full text-left px-4 py-2 text-sm text-slate-200 hover:bg-slate-700text-slate-500 cursor-not-allowedborder-t border-slate-700 my-1p-2 text-slate-400 hover:bg-slate-700 rounded-lg transition-colors with w-5 h-5 iconToolsPopupMenu.tsx is the canonical reference implementation. It provides three menu items (Folder Analysis, Re-Number Files, Export) with dividers between them, triggered by a WrenchIcon button in the data-id="browser-header-actions" div in App.tsx.
testing
Pattern for writing Playwright E2E tests that capture screenshots and narration for GIF/MP4 generation
tools
Pattern for application pages
tools
Pattern for making popup modal dialogs
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.