initial commit
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# 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')!
|
||||
)}
|
||||
```
|
||||
Reference in New Issue
Block a user