モダンUIコンポーネントの実装パターン
実装ガイドReact、Vue、Angularで共通して使えるUIパターンを解説
ユーザーに重要な情報を伝えるための通知バナーコンポーネント。成功、エラー、警告、プロモーションなど様々な用途に対応
システムメンテナンスは本日午後10時から実施されます。
データの保存が完了しました。
お使いのブラウザのバージョンが古くなっています。
接続エラーが発生しました。再試行してください。
今なら全商品20%OFF。クーポンコード: SAVE20
新機能が追加されました。詳細をご確認ください。
セキュリティ向上のため、メールアドレスの確認をお願いします。
カスタムアイコンを使用したバナーの例です。
サイト全体のお知らせ: 新しいプライバシーポリシーが適用されました。
情報、成功、警告、エラー、プロモーションなど、用途に応じた多彩なスタイルを提供
ページ内配置、フルワイド、スティッキー表示など、様々な配置方法に対応
閉じるボタン、アクションボタン、カスタムアイコンによる豊かな対話性
プロンプト例:
GDPR準拠のCookie同意バナーを作成してください。必須Cookie、分析Cookie、マーケティングCookieの個別選択、詳細説明リンク、同意/拒否ボタンを含めてください。
プロンプト例:
災害や緊急事態用の通知バナーシステムを実装してください。優先度レベル、自動表示/非表示、音声読み上げ対応、多言語サポート、位置情報連動を含めてください。
プロンプト例:
マーケティング用のA/Bテスト可能なプロモーションバナーを開発してください。複数バリエーション管理、表示条件設定、CTR測定、コンバージョン追跡、レポート機能を実装してください。
import React, { useState } from 'react';
interface BannerProps {
type?: 'info' | 'success' | 'warning' | 'error' | 'promotion';
title?: string;
message: string;
closable?: boolean;
onClose?: () => void;
action?: {
label: string;
onClick: () => void;
};
icon?: React.ReactNode;
className?: string;
}
export const Banner: React.FC<BannerProps> = ({
type = 'info',
title,
message,
closable = true,
onClose,
action,
icon,
className = ''
}) => {
const [isVisible, setIsVisible] = useState(true);
const handleClose = () => {
setIsVisible(false);
onClose?.();
};
if (!isVisible) return null;
const typeStyles = {
info: {
bg: 'bg-blue-50 border-blue-200',
text: 'text-blue-800',
icon: 'text-blue-600',
button: 'bg-blue-600 hover:bg-blue-700 text-white',
closeButton: 'text-blue-600 hover:text-blue-800'
},
success: {
bg: 'bg-green-50 border-green-200',
text: 'text-green-800',
icon: 'text-green-600',
button: 'bg-green-600 hover:bg-green-700 text-white',
closeButton: 'text-green-600 hover:text-green-800'
},
warning: {
bg: 'bg-yellow-50 border-yellow-200',
text: 'text-yellow-800',
icon: 'text-yellow-600',
button: 'bg-yellow-600 hover:bg-yellow-700 text-white',
closeButton: 'text-yellow-600 hover:text-yellow-800'
},
error: {
bg: 'bg-red-50 border-red-200',
text: 'text-red-800',
icon: 'text-red-600',
button: 'bg-red-600 hover:bg-red-700 text-white',
closeButton: 'text-red-600 hover:text-red-800'
},
promotion: {
bg: 'bg-gradient-to-r from-purple-600 to-pink-600',
text: 'text-white',
icon: 'text-white',
button: 'bg-white text-purple-600 hover:bg-gray-100',
closeButton: 'text-white hover:text-gray-200'
}
};
const defaultIcons = {
info: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
success: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
warning: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
),
error: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
promotion: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
)
};
const styles = typeStyles[type];
const displayIcon = icon || defaultIcons[type];
return (
<div
className={`
relative px-4 py-3 border rounded-lg
${type === 'promotion' ? '' : 'border'}
${styles.bg}
${className}
`}
role="alert"
>
<div className="flex items-start">
{/* アイコン */}
<div className={`flex-shrink-0 ${styles.icon}`}>
{displayIcon}
</div>
{/* コンテンツ */}
<div className="ml-3 flex-1">
{title && (
<h3 className={`text-sm font-semibold ${styles.text} mb-1`}>
{title}
</h3>
)}
<p className={`text-sm ${styles.text}`}>
{message}
</p>
</div>
{/* アクションボタン */}
{action && (
<div className="ml-4 flex-shrink-0">
<button
onClick={action.onClick}
className={`
px-3 py-1 text-sm font-medium rounded-md
transition-colors duration-200
${styles.button}
`}
>
{action.label}
</button>
</div>
)}
{/* 閉じるボタン */}
{closable && (
<button
onClick={handleClose}
className={`
ml-4 flex-shrink-0 inline-flex rounded-md p-1.5
transition-colors duration-200
hover:bg-white/20
${styles.closeButton}
`}
aria-label="閉じる"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
)}
</div>
</div>
);
};
// フルワイドバナー
export const FullWidthBanner: React.FC<BannerProps> = (props) => {
return (
<div className="w-full">
<Banner {...props} className="rounded-none border-x-0" />
</div>
);
};
// スティッキーバナー
export const StickyBanner: React.FC<BannerProps & { position?: 'top' | 'bottom' }> = ({
position = 'top',
...props
}) => {
const positionClasses = position === 'top'
? 'top-0'
: 'bottom-0';
return (
<div className={`fixed ${positionClasses} left-0 right-0 z-50`}>
<FullWidthBanner {...props} />
</div>
);
};
// 基本的な使用例
import { Banner, FullWidthBanner, StickyBanner } from './banner';
function App() {
const [showBanner, setShowBanner] = useState(true);
return (
<div>
{/* 基本的なバナー */}
<Banner
type="info"
title="お知らせ"
message="システムメンテナンスは本日午後10時から実施されます。"
onClose={() => setShowBanner(false)}
/>
{/* 成功バナー */}
<Banner
type="success"
message="データが正常に保存されました。"
/>
{/* アクション付き警告バナー */}
<Banner
type="warning"
title="アカウントの確認が必要です"
message="セキュリティ向上のため、メールアドレスの確認をお願いします。"
action={{
label: '確認する',
onClick: handleVerify
}}
/>
{/* プロモーションバナー */}
<Banner
type="promotion"
title="期間限定オファー!"
message="今なら全商品20%OFF"
action={{
label: '今すぐ購入',
onClick: handleShop
}}
/>
{/* フルワイドバナー */}
<FullWidthBanner
type="info"
message="新しいプライバシーポリシーが適用されました。"
action={{
label: '詳細を確認',
onClick: handlePolicy
}}
/>
{/* スティッキーバナー(Cookie同意) */}
<StickyBanner
position="bottom"
type="info"
message="このサイトはCookieを使用しています。"
action={{
label: '同意する',
onClick: handleAcceptCookies
}}
closable={false}
/>
{/* カスタムアイコン */}
<Banner
type="info"
message="新機能がリリースされました!"
icon={<CustomIcon />}
/>
</div>
);
}