diff --git a/packages/web/src/components/ui/ContextMenuRenderer.tsx b/packages/web/src/components/ui/ContextMenuRenderer.tsx new file mode 100644 index 00000000..456f92d8 --- /dev/null +++ b/packages/web/src/components/ui/ContextMenuRenderer.tsx @@ -0,0 +1,552 @@ +import React, { useEffect, useLayoutEffect, useRef, useCallback, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { + useContextMenuStore, + filterMenuItems, + type ContextMenuItem, + type ContextMenuLeafItem, + type ContextMenuSubmenu, +} from '../../stores/contextMenuStore'; +import { useUIStore } from '../../stores/uiStore'; + +// ── Desktop item button ────────────────────────────────────────────────────── + +const ITEM_CLASS = + 'w-full text-left px-2 py-1.5 mx-1.5 text-sm rounded-sm flex items-center gap-2 text-txt-secondary hover:bg-accent-primary hover:text-white'; +const ITEM_DANGER_CLASS = + 'w-full text-left px-2 py-1.5 mx-1.5 text-sm rounded-sm flex items-center gap-2 text-txt-danger hover:bg-accent-rose hover:text-white'; +const ITEM_DISABLED_CLASS = + 'w-full text-left px-2 py-1.5 mx-1.5 text-sm rounded-sm flex items-center gap-2 text-txt-secondary opacity-50 cursor-default'; +const ITEM_STYLE: React.CSSProperties = { width: 'calc(100% - 12px)' }; + +// ── Mobile item button ─────────────────────────────────────────────────────── + +const MOBILE_ITEM_CLASS = 'w-full text-left px-5 py-3 text-sm flex items-center gap-3'; + +// ── Checkbox indicator ─────────────────────────────────────────────────────── + +function CheckboxIndicator({ checked }: { checked: boolean }) { + return ( +