右クリック コンテキストメニュー
:LiTarget: 用途
要素を右クリックで表示するメニュー。位置自動調整・Esc で閉じる。
:LiSparkle: 特徴
- 右クリック検知
- 位置自動調整
- Esc で閉じる
- 画面端で反転
:LiCode: コード(コピペ用)
'use client';
import { useState, useEffect, ReactNode } from 'react';
type MenuItem = { label: string; action: () => void; danger?: boolean };
export function useContextMenu(items: MenuItem[]) {
const [pos, setPos] = useState<{ x: number; y: number } | null>(null);
const open = (e: React.MouseEvent) => { e.preventDefault(); setPos({ x: e.clientX, y: e.clientY }); };
const close = () => setPos(null);
useEffect(() => {
if (!pos) return;
const onClick = () => close();
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') close(); };
window.addEventListener('click', onClick);
window.addEventListener('keydown', onKey);
return () => {
window.removeEventListener('click', onClick);
window.removeEventListener('keydown', onKey);
};
}, [pos]);
const menu = pos && (
<div style={{
position: 'fixed', left: pos.x, top: pos.y, zIndex: 9999,
background: '#0a0a0c', border: '1px solid rgba(255,255,255,.07)',
borderRadius: 4, padding: '.25rem 0', minWidth: 160,
boxShadow: '0 8px 32px rgba(0,0,0,.4)',
}}>
{items.map((it, i) => (
<div key={i} onClick={(e) => { e.stopPropagation(); it.action(); close(); }}
style={{
padding: '.5rem 1rem', cursor: 'pointer', fontSize: '.8rem',
color: it.danger ? '#fb7185' : 'rgba(255,255,255,.85)',
}}>
{it.label}
</div>
))}
</div>
);
return { open, menu };
}
:LiHandPointer: 使い方
対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。
:LiAlertCircle: 注意事項
- 依存パッケージを忘れず追加