Add files via upload

This commit is contained in:
jasonMPM
2026-03-06 00:38:31 -06:00
committed by GitHub
parent 72c5ebf312
commit bdbdff7842
2 changed files with 40 additions and 26 deletions

View File

@@ -1,11 +1,16 @@
import { useEffect, useRef } from 'react'
import { createPortal } from 'react-dom'
export default function ContextMenu({ x, y, items, onClose }) {
const ref = useRef(null)
useEffect(() => {
const onMouseDown = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose() }
const onKey = (e) => { if (e.key === 'Escape') onClose() }
const onMouseDown = (e) => {
if (ref.current && !ref.current.contains(e.target)) onClose()
}
const onKey = (e) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('mousedown', onMouseDown)
document.addEventListener('keydown', onKey)
return () => {
@@ -15,41 +20,41 @@ export default function ContextMenu({ x, y, items, onClose }) {
}, [onClose])
// Keep menu inside viewport
const W = 192
const H = items.length * 34
const W = 192
const H = items.length * 34
const adjX = Math.min(x, window.innerWidth - W - 8)
const adjY = Math.min(y, window.innerHeight - H - 8)
return (
// Portal to document.body — escapes any CSS transform stacking context
// (e.g. the Drawer slide-in animation uses translateX which traps fixed children)
return createPortal(
<div
ref={ref}
style={{ left: adjX, top: adjY }}
className="fixed z-[200] min-w-[192px] bg-surface-elevated border border-surface-border rounded-xl shadow-2xl py-1.5 overflow-hidden"
style={{ position: 'fixed', left: adjX, top: adjY }}
className="z-[9999] bg-surface-elevated border border-surface-border rounded-xl shadow-2xl py-1.5 min-w-[192px]"
>
{items.map((item, i) =>
item.separator ? (
<div key={i} className="h-px bg-surface-border my-1 mx-2" />
<div key={i} className="my-1 border-t border-surface-border/60" />
) : (
<button
key={i}
disabled={item.disabled}
onClick={() => { item.action(); onClose() }}
className={`w-full flex items-center gap-2.5 px-3 py-2 text-xs text-left transition-colors disabled:opacity-40 disabled:cursor-not-allowed
${item.danger
? 'text-red-400 hover:bg-red-500/10'
: item.highlight
? 'text-gold hover:bg-gold/10'
: 'text-text-primary hover:bg-surface-border/40'
}`}
onClick={() => { item.action?.(); onClose() }}
className={`w-full flex items-center gap-2.5 px-3 py-2 text-xs text-left transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${
item.danger ? 'text-red-400 hover:bg-red-500/10' :
item.highlight ? 'text-gold hover:bg-gold/10' :
'text-text-primary hover:bg-surface-border/40'
}`}
>
<span className="text-sm w-4 text-center leading-none flex-shrink-0">{item.icon}</span>
<span>{item.label}</span>
<span className="text-[11px] opacity-70 w-3.5 flex-shrink-0">{item.icon}</span>
<span className="flex-1">{item.label}</span>
{item.shortcut && (
<span className="ml-auto text-text-muted/50 text-[10px]">{item.shortcut}</span>
<span className="text-[9px] text-text-muted/40 font-mono ml-2">{item.shortcut}</span>
)}
</button>
)
)}
</div>
</div>,
document.body
)
}