# Dropdown Portal / Teleport Pattern (ShopProQuote)

## Problem

The `#service-search-results` dropdown lives inside `#services-content` (a collapsible accordion section). When the accordion uses `overflow: hidden` or any ancestor creates a stacking context, the absolute-positioned dropdown gets clipped — it appears behind the Quote Summary section or is truncated.

CSS `z-index: 99999 !important` does NOT fix this because z-index is scoped within the parent stacking context.

## Fix: Teleport to `#dropdown-root`

All ShopProQuote pages have `<div id="dropdown-root"></div>` at the end of `<body>` (in `index.html`, `dashboard.html`, `repair-orders.html`, `customers.html`, `appointments.html`). Use this as a portal target.

### Implementation (Vanilla JS)

In `renderServiceResults()` in both `main.js` and `quote-tab-manager.js`:

```javascript
// 1. Move dropdown to portal root (only on first render)
const dropdownRoot = document.getElementById('dropdown-root');
if (dropdownRoot && serviceSearchResults.parentElement !== dropdownRoot) {
    dropdownRoot.appendChild(serviceSearchResults);
}

// 2. Position relative to the search input using getBoundingClientRect
const rect = serviceSearchInput.getBoundingClientRect();
serviceSearchResults.style.position = 'fixed';
serviceSearchResults.style.top = (rect.bottom + 4) + 'px';
serviceSearchResults.style.left = rect.left + 'px';
serviceSearchResults.style.width = rect.width + 'px';

// 3. Add scroll/resize repositioning
const repositionDropdown = () => {
    if (serviceSearchResults.classList.contains('hidden')) return;
    const rect = serviceSearchInput.getBoundingClientRect();
    serviceSearchResults.style.top = (rect.bottom + 4) + 'px';
    serviceSearchResults.style.left = rect.left + 'px';
    serviceSearchResults.style.width = rect.width + 'px';
};
window.addEventListener('scroll', repositionDropdown, { passive: true });
window.addEventListener('resize', repositionDropdown);
```

### Why This Works

- `appendChild` to `dropdown-root` (direct child of `<body>`) escapes ALL ancestor stacking contexts
- `position: fixed` with `getBoundingClientRect()` positions the dropdown relative to the viewport, anchored to the input
- Scroll/resize listener keeps it positioned correctly as the user scrolls

### Files to Modify

- `main.js`: `renderServiceResults()` function
- `quote-tab-manager.js`: `renderServiceResults()` function AND `initializeServiceSearch()` (for the scroll listener)
- BOTH files have their own service search implementation — both need the fix

### React Equivalent (spq-v2)

In the React rewrite, use a portal:

```tsx
import { createPortal } from 'react-dom';

{open && createPortal(
  <div className="fixed z-50 ..." style={{ top: rect.bottom + 4, left: rect.left, width: rect.width }}>
    {/* dropdown items */}
  </div>,
  document.getElementById('dropdown-root')!
)}
```
