Diseno e implementacion de dialogos modales con Claude Code
Aprenda sobre el diseno e implementacion de dialogos modales usando Claude Code. Incluye ejemplos practicos de codigo.
モーダル・ダイアログの設計原則
モーダルはユーザーの注意を特定の操作に集中させるUIパターンです。しかし、フォーカス管理やキーボード操作、スクリーンリーダー対応を怠ると、アクセシビリティの問題を引き起こします。Claude Codeを使えば、WAI-ARIA準拠のモーダルを正しく実装できます。
HTML dialog要素を使った実装
> HTML標準のdialog要素を使ったモーダルコンポーネントを作って。
> アクセシビリティとアニメーションに対応して。
import { useRef, useEffect } from 'react';
interface DialogProps {
open: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}
function Dialog({ open, onClose, title, children }: DialogProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (open) {
dialog.showModal();
} else {
dialog.close();
}
}, [open]);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
const handleClose = () => onClose();
dialog.addEventListener('close', handleClose);
const handleClick = (e: MouseEvent) => {
if (e.target === dialog) onClose();
};
dialog.addEventListener('click', handleClick);
return () => {
dialog.removeEventListener('close', handleClose);
dialog.removeEventListener('click', handleClick);
};
}, [onClose]);
return (
<dialog
ref={dialogRef}
aria-labelledby="dialog-title"
className="rounded-xl shadow-2xl p-0 backdrop:bg-black/50 backdrop:backdrop-blur-sm
max-w-lg w-full open:animate-fadeIn"
>
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h2 id="dialog-title" className="text-xl font-bold">{title}</h2>
<button onClick={onClose} aria-label="Close"
className="rounded-full p-1 hover:bg-gray-100">✕</button>
</div>
{children}
</div>
</dialog>
);
}
確認ダイアログ
> 「Deleteしてよろしいですか?」のような確認ダイアログをPromiseベースで使えるようにして。
import { createRoot } from 'react-dom/client';
interface ConfirmOptions {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
variant?: 'danger' | 'default';
}
function confirm(options: ConfirmOptions): Promise<boolean> {
return new Promise((resolve) => {
const container = document.createElement('div');
document.body.appendChild(container);
const root = createRoot(container);
const handleClose = (result: boolean) => {
root.unmount();
container.remove();
resolve(result);
};
root.render(
<Dialog open={true} onClose={() => handleClose(false)} title={options.title}>
<p className="text-gray-600 mb-6">{options.message}</p>
<div className="flex justify-end gap-3">
<button onClick={() => handleClose(false)}
className="px-4 py-2 border rounded-lg hover:bg-gray-50">
{options.cancelLabel ?? 'キャンセル'}
</button>
<button onClick={() => handleClose(true)}
className={`px-4 py-2 rounded-lg text-white ${
options.variant === 'danger' ? 'bg-red-600 hover:bg-red-700' : 'bg-blue-600 hover:bg-blue-700'
}`}>
{options.confirmLabel ?? '確認'}
</button>
</div>
</Dialog>
);
});
}
// Usage example
async function handleDelete(id: string) {
const ok = await confirm({
title: 'Deleteの確認',
message: 'この項目をDeleteしてよろしいですか?この操作は取り消せません。',
confirmLabel: 'Deleteする',
variant: 'danger',
});
if (ok) await deleteItem(id);
}
コマンドパレット
function CommandPalette({ commands, onClose }: { commands: Command[]; onClose: () => void }) {
const [query, setQuery] = useState('');
const [activeIndex, setActiveIndex] = useState(0);
const filtered = commands.filter((c) =>
c.label.toLowerCase().includes(query.toLowerCase())
);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
setActiveIndex((prev) => Math.min(prev + 1, filtered.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setActiveIndex((prev) => Math.max(prev - 1, 0));
} else if (e.key === 'Enter' && filtered[activeIndex]) {
filtered[activeIndex].action();
onClose();
}
};
return (
<Dialog open={true} onClose={onClose} title="コマンドパレット">
<input
autoFocus
value={query}
onChange={(e) => { setQuery(e.target.value); setActiveIndex(0); }}
onKeyDown={handleKeyDown}
placeholder="コマンドを検索..."
className="w-full px-3 py-2 border rounded-lg mb-3"
role="combobox"
aria-expanded={true}
/>
<ul role="listbox" className="max-h-64 overflow-y-auto">
{filtered.map((cmd, i) => (
<li key={cmd.id} role="option" aria-selected={i === activeIndex}
onClick={() => { cmd.action(); onClose(); }}
className={`px-3 py-2 rounded cursor-pointer ${i === activeIndex ? 'bg-blue-100' : 'hover:bg-gray-50'}`}>
{cmd.label}
</li>
))}
</ul>
</Dialog>
);
}
Summary
Claude Codeを使えば、HTML dialog要素を活用したアクセシブルなモーダルから、確認ダイアログ、コマンドパレットまで効率的に構築できます。トースト通知との使い分けはトースト通知実装を、アクセシビリティの基本はアクセシビリティガイドを参照してください。
dialog要素の仕様はMDN Web Docs - dialogをご覧ください。
Related Posts
10 consejos para triplicar tu productividad con Claude Code
Descubre 10 consejos prácticos para sacar más provecho de Claude Code. Desde estrategias de prompts hasta atajos de flujo de trabajo, estas técnicas mejorarán tu eficiencia desde hoy.
Optimizacion de Canvas/WebGL con Claude Code
Aprende sobre la optimizacion de Canvas/WebGL usando Claude Code. Incluye consejos practicos y ejemplos de codigo.
Implementacion de Markdown con Claude Code
Aprenda sobre la implementacion de Markdown usando Claude Code. Incluye consejos practicos y ejemplos de codigo.