Add files via upload
This commit is contained in:
@@ -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)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
@@ -20,36 +25,36 @@ export default function ContextMenu({ x, y, items, onClose }) {
|
|||||||
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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user