# S05 Research — Admin Dropdown Hover on Desktop

## Summary

Straightforward interaction change. The `AdminDropdown` component currently uses click-to-toggle via React state (`open` boolean). Menu is conditionally rendered (`{open && <div>}`), so CSS-only `:hover` is not viable — JS hover handlers are needed. Mobile styles already enforce 44px touch targets at `max-width: 768px`.

**Requirement:** R036 — Admin Dropdown Hover on Desktop

## Recommendation

Add `onMouseEnter`/`onMouseLeave` handlers to the `.admin-dropdown` container div, guarded by a `matchMedia('(min-width: 769px)')` check so hover only activates on desktop. Keep click-to-toggle for mobile/touch.

Add a ~150ms leave delay (cleared on re-enter) to bridge the `0.5rem` gap between trigger and menu — standard dropdown hover UX pattern.

## Implementation Landscape

### Files to Modify

| File | What changes |
|------|-------------|
| `frontend/src/components/AdminDropdown.tsx` | Add `onMouseEnter`/`onMouseLeave` with matchMedia guard, leave-delay ref |
| `frontend/src/App.css` | No CSS changes needed — existing styles handle positioning and visibility |

### Current State of `AdminDropdown.tsx` (73 lines)

- **State:** `open` boolean via `useState(false)`
- **Close behaviors:** Outside click (mousedown listener), Escape key listener
- **Toggle:** `onClick={() => setOpen(prev => !prev)}` on trigger button
- **Menu rendering:** Conditional `{open && <div className="admin-dropdown__menu">}`
- **Accessibility:** `aria-expanded`, `aria-haspopup="true"`, `role="menu"`, `role="menuitem"`
- **Refs:** `dropdownRef` for outside-click detection

### Existing CSS

- Desktop: `.admin-dropdown` is `position: relative`, menu is `position: absolute` with `top: calc(100% + 0.5rem)` (creates a gap)
- Mobile (`max-width: 768px`): Menu becomes `position: static`, full-width, no shadow. Trigger has `min-height: 44px` for touch targets.

### Implementation Detail

```
// Inside component:
const isDesktop = useRef(window.matchMedia('(min-width: 769px)'));
const leaveTimer = useRef<ReturnType<typeof setTimeout>>();

const handleMouseEnter = () => {
  if (!isDesktop.current.matches) return;
  clearTimeout(leaveTimer.current);
  setOpen(true);
};

const handleMouseLeave = () => {
  if (!isDesktop.current.matches) return;
  leaveTimer.current = setTimeout(() => setOpen(false), 150);
};

// On the container div:
<div className="admin-dropdown" ref={dropdownRef}
     onMouseEnter={handleMouseEnter}
     onMouseLeave={handleMouseLeave}>
```

The `isDesktop` ref should listen for changes (`matchMedia.addEventListener('change', ...)`) to handle window resizing, though this is minor for a single-admin tool.

### Gap bridging

The `0.5rem` gap between trigger and menu can cause accidental close during hover. The 150ms delay handles this. Alternative: add an invisible pseudo-element bridge in CSS (`.admin-dropdown__menu::before` with negative top margin). The timer approach is simpler and covers diagonal mouse movement too.

### Verification

1. Build frontend: `cd frontend && npm run build` — no TypeScript errors
2. Deploy: rebuild Docker image, verify at `http://ub01:8096`
3. Desktop: hover over "Admin ▾" → menu opens; move mouse to menu items → stays open; move mouse away → closes after ~150ms
4. Mobile (DevTools 768px or narrower): hover does nothing, tap toggles menu
5. Escape key still closes menu
6. Outside click still closes menu

### Risks

None. Single-file change, well-understood pattern, no backend involvement.
