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

@@ -17,10 +17,15 @@ export default function DeliverableCard({ deliverable, isActive, index, projectC
x: e.clientX, x: e.clientX,
y: e.clientY, y: e.clientY,
items: [ items: [
{ icon: '\u270e', label: 'Edit Deliverable', highlight: true, action: () => onEdit(deliverable) }, {
icon: '✎',
label: 'Edit Deliverable',
highlight: true,
action: () => onEdit(deliverable),
},
{ separator: true }, { separator: true },
...STATUS_OPTIONS.map(s => ({ ...STATUS_OPTIONS.map(s => ({
icon: s.value === deliverable.status ? '\u25cf' : '\u25cb', icon: s.value === deliverable.status ? '' : '',
label: `Mark ${s.label}`, label: `Mark ${s.label}`,
action: async () => { action: async () => {
storeUpdate(await updateDeliverable(deliverable.id, { status: s.value })) storeUpdate(await updateDeliverable(deliverable.id, { status: s.value }))
@@ -28,7 +33,7 @@ export default function DeliverableCard({ deliverable, isActive, index, projectC
})), })),
{ separator: true }, { separator: true },
{ {
icon: '\u2715', icon: '',
label: 'Delete Deliverable', label: 'Delete Deliverable',
danger: true, danger: true,
action: async () => { action: async () => {
@@ -60,7 +65,6 @@ export default function DeliverableCard({ deliverable, isActive, index, projectC
Selected Selected
</div> </div>
)} )}
<div className="text-[10px] text-text-muted/50 font-mono">Deliverable {index + 1}</div> <div className="text-[10px] text-text-muted/50 font-mono">Deliverable {index + 1}</div>
<div className="text-sm font-semibold text-text-primary leading-snug line-clamp-3">{deliverable.title}</div> <div className="text-sm font-semibold text-text-primary leading-snug line-clamp-3">{deliverable.title}</div>
<div className="text-xs text-text-muted/70 mt-auto pt-1">{formatDate(deliverable.due_date)}</div> <div className="text-xs text-text-muted/70 mt-auto pt-1">{formatDate(deliverable.due_date)}</div>
@@ -68,7 +72,12 @@ export default function DeliverableCard({ deliverable, isActive, index, projectC
</div> </div>
{ctxMenu && ( {ctxMenu && (
<ContextMenu x={ctxMenu.x} y={ctxMenu.y} items={ctxMenu.items} onClose={() => setCtxMenu(null)} /> <ContextMenu
x={ctxMenu.x}
y={ctxMenu.y}
items={ctxMenu.items}
onClose={() => setCtxMenu(null)}
/>
)} )}
</> </>
) )

View File

@@ -1,11 +1,16 @@
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import { createPortal } from 'react-dom'
export default function ContextMenu({ x, y, items, onClose }) { export default function ContextMenu({ x, y, items, onClose }) {
const ref = useRef(null) const ref = useRef(null)
useEffect(() => { useEffect(() => {
const onMouseDown = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose() } const onMouseDown = (e) => {
const onKey = (e) => { if (e.key === 'Escape') onClose() } if (ref.current && !ref.current.contains(e.target)) onClose()
}
const onKey = (e) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('mousedown', onMouseDown) document.addEventListener('mousedown', onMouseDown)
document.addEventListener('keydown', onKey) document.addEventListener('keydown', onKey)
return () => { return () => {
@@ -15,41 +20,41 @@ export default function ContextMenu({ x, y, items, onClose }) {
}, [onClose]) }, [onClose])
// Keep menu inside viewport // Keep menu inside viewport
const W = 192 const W = 192
const H = items.length * 34 const H = items.length * 34
const adjX = Math.min(x, window.innerWidth - W - 8) const adjX = Math.min(x, window.innerWidth - W - 8)
const adjY = Math.min(y, window.innerHeight - H - 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 <div
ref={ref} ref={ref}
style={{ left: adjX, top: adjY }} style={{ position: 'fixed', 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" className="z-[9999] bg-surface-elevated border border-surface-border rounded-xl shadow-2xl py-1.5 min-w-[192px]"
> >
{items.map((item, i) => {items.map((item, i) =>
item.separator ? ( 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 <button
key={i} key={i}
disabled={item.disabled} onClick={() => { item.action?.(); onClose() }}
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 ${
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.danger item.highlight ? 'text-gold hover:bg-gold/10' :
? 'text-red-400 hover:bg-red-500/10' 'text-text-primary hover:bg-surface-border/40'
: 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 className="text-[11px] opacity-70 w-3.5 flex-shrink-0">{item.icon}</span>
<span>{item.label}</span> <span className="flex-1">{item.label}</span>
{item.shortcut && ( {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> </button>
) )
)} )}
</div> </div>,
document.body
) )
} }