Initial commit — Dossier Rhino 8 Plugin
OpenStudio-Suite Architektur-Plugin fuer Rhino 8 (Mac): - Smart-Elemente: Wand, Decke, Dach (Pult/Sattel/Walm/Mansarde), Oeffnungen (Fenster/Tueren mit Rahmen + Sims + Glas + Fluegel), Treppen (gerade · L · Wendel mit Schrittmass-Validierung) - Live-Previews mit Step-Lines + Soll-Range-Clamping - Bidirektionale Selection-Sync zwischen Source-Linie und Volume - Geschoss-/Ebenen-Verwaltung mit OKFF-Persistenz - Layouts mit PDF-Export - Ausschnitte / Massstab / Override-Regeln - Petrol-Gruen Theme (Rapport-konform) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Icon from './Icon'
|
||||
|
||||
export default function ContextMenu({ x, y, items, onClose }) {
|
||||
const ref = useRef(null)
|
||||
const [pos, setPos] = useState({ left: x, top: y })
|
||||
|
||||
// Falls Menue rechts/unten ueberlaufen wuerde, links/oben verschieben
|
||||
useEffect(() => {
|
||||
if (!ref.current) return
|
||||
const rect = ref.current.getBoundingClientRect()
|
||||
const vw = window.innerWidth
|
||||
const vh = window.innerHeight
|
||||
let left = x, top = y
|
||||
if (left + rect.width > vw - 4) left = vw - rect.width - 4
|
||||
if (top + rect.height > vh - 4) top = vh - rect.height - 4
|
||||
if (left < 4) left = 4
|
||||
if (top < 4) top = 4
|
||||
setPos({ left, top })
|
||||
}, [x, y])
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (ev) => {
|
||||
if (ref.current && !ref.current.contains(ev.target)) onClose()
|
||||
}
|
||||
const handleKey = (ev) => { if (ev.key === 'Escape') onClose() }
|
||||
const handleContext = (ev) => { ev.preventDefault(); onClose() }
|
||||
const t = setTimeout(() => {
|
||||
document.addEventListener('mousedown', handleClick)
|
||||
document.addEventListener('keydown', handleKey)
|
||||
document.addEventListener('contextmenu', handleContext)
|
||||
}, 0)
|
||||
return () => {
|
||||
clearTimeout(t)
|
||||
document.removeEventListener('mousedown', handleClick)
|
||||
document.removeEventListener('keydown', handleKey)
|
||||
document.removeEventListener('contextmenu', handleContext)
|
||||
}
|
||||
}, [onClose])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: pos.top, left: pos.left,
|
||||
background: 'var(--bg-dialog)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r)',
|
||||
boxShadow: 'var(--shadow-3)',
|
||||
padding: '4px 0',
|
||||
minWidth: 180,
|
||||
zIndex: 300,
|
||||
}}
|
||||
>
|
||||
{items.map((it, i) => (
|
||||
it.divider ? (
|
||||
<div key={i} style={{ height: 1, background: 'var(--border-light)', margin: '4px 0' }} />
|
||||
) : (
|
||||
<button
|
||||
key={i}
|
||||
disabled={it.disabled}
|
||||
onClick={() => { if (!it.disabled) { it.onClick(); onClose() } }}
|
||||
onMouseEnter={(ev) => { if (!it.disabled) ev.currentTarget.style.background = 'var(--overlay-hover)' }}
|
||||
onMouseLeave={(ev) => { ev.currentTarget.style.background = 'transparent' }}
|
||||
style={{
|
||||
width: '100%', textAlign: 'left',
|
||||
padding: '6px 14px', fontSize: 11,
|
||||
color: it.disabled ? 'var(--text-muted)'
|
||||
: it.danger ? 'var(--danger)'
|
||||
: 'var(--text-primary)',
|
||||
display: 'flex', alignItems: 'center', gap: 10,
|
||||
borderRadius: 0,
|
||||
cursor: it.disabled ? 'default' : 'pointer',
|
||||
background: 'transparent',
|
||||
}}
|
||||
>
|
||||
{it.icon
|
||||
? <Icon name={it.icon} size={14} style={{ color: it.disabled ? 'var(--text-muted)' : it.danger ? 'var(--danger)' : 'var(--text-secondary)' }} />
|
||||
: <span style={{ width: 14 }} />}
|
||||
<span style={{ flex: 1 }}>{it.label}</span>
|
||||
{it.shortcut && <span style={{ fontSize: 9, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{it.shortcut}</span>}
|
||||
</button>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user