dramaling-vocab-learning/docs/02_design/component-library/components/01-interactive/modals.html

730 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模態框元件 - Drama Ling</title>
<link rel="stylesheet" href="../../../design-system/tokens/design-tokens.css">
<link rel="stylesheet" href="../../assets/styles/base.css">
<link rel="stylesheet" href="../../assets/styles/components.css">
<style>
body {
background: var(--background-primary);
padding: var(--space-8);
min-height: 100vh;
}
.demo-container {
max-width: 1200px;
margin: 0 auto;
}
.demo-header {
text-align: center;
margin-bottom: var(--space-8);
}
.demo-title {
font-size: var(--text-3xl);
font-weight: 700;
color: var(--primary-teal);
margin-bottom: var(--space-4);
}
.demo-subtitle {
font-size: var(--text-lg);
color: var(--text-secondary);
}
.demo-section {
margin-bottom: var(--space-10);
}
.section-title {
font-size: var(--text-xl);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--space-4);
padding-bottom: var(--space-2);
border-bottom: 2px solid var(--primary-teal);
}
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--space-4);
margin-bottom: var(--space-6);
}
/* 模態框樣式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
.modal {
background: var(--card-background);
border-radius: var(--radius-2xl);
padding: var(--space-8);
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
transform: scale(0.9) translateY(20px);
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.modal-overlay.active .modal {
transform: scale(1) translateY(0);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-6);
}
.modal-title {
font-size: var(--text-xl);
font-weight: 700;
color: var(--text-primary);
}
.modal-close {
width: 32px;
height: 32px;
background: transparent;
border: none;
color: var(--text-tertiary);
cursor: pointer;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
font-size: 20px;
}
.modal-close:hover {
background: var(--background-secondary);
color: var(--text-primary);
}
.modal-body {
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: var(--space-6);
}
.modal-footer {
display: flex;
gap: var(--space-3);
justify-content: flex-end;
}
/* 成功模態框 */
.modal-success {
border-top: 4px solid var(--success-green);
}
.modal-success .modal-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(76, 175, 80, 0.05));
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto var(--space-6);
font-size: 40px;
}
/* 警告模態框 */
.modal-warning {
border-top: 4px solid var(--warning-yellow);
}
.modal-warning .modal-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, rgba(243, 156, 18, 0.1), rgba(243, 156, 18, 0.05));
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto var(--space-6);
font-size: 40px;
}
/* 確認模態框 */
.modal-confirm {
border-top: 4px solid var(--primary-teal);
}
/* 表單模態框 */
.modal-form .modal-body {
padding: 0;
}
/* 圖片模態框 */
.modal-image {
padding: 0;
background: transparent;
max-width: 90%;
}
.modal-image img {
width: 100%;
border-radius: var(--radius-2xl);
}
/* 底部抽屜 */
.drawer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: var(--card-background);
border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
padding: var(--space-6);
transform: translateY(100%);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 999;
box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.2);
}
.drawer.active {
transform: translateY(0);
}
.drawer-handle {
width: 40px;
height: 4px;
background: var(--divider);
border-radius: var(--radius-full);
margin: 0 auto var(--space-4);
}
/* Toast 通知 */
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.toast {
background: var(--card-background);
border-radius: var(--radius-lg);
padding: var(--space-4) var(--space-5);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
gap: var(--space-3);
min-width: 300px;
transform: translateX(400px);
opacity: 0;
transition: all 0.3s ease;
}
.toast.show {
transform: translateX(0);
opacity: 1;
}
.toast-icon {
flex-shrink: 0;
font-size: 24px;
}
.toast-content {
flex: 1;
}
.toast-title {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 2px;
}
.toast-message {
font-size: var(--text-sm);
color: var(--text-secondary);
}
.toast-close {
flex-shrink: 0;
background: transparent;
border: none;
color: var(--text-tertiary);
cursor: pointer;
padding: var(--space-1);
}
.toast-success {
border-left: 4px solid var(--success-green);
}
.toast-error {
border-left: 4px solid var(--error-red);
}
.toast-warning {
border-left: 4px solid var(--warning-yellow);
}
.toast-info {
border-left: 4px solid var(--info-cyan);
}
/* 彈出選單 */
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-menu {
position: absolute;
top: calc(100% + var(--space-2));
left: 0;
background: var(--card-background);
border: 1px solid var(--divider);
border-radius: var(--radius-lg);
padding: var(--space-2);
min-width: 200px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.2s ease;
z-index: 100;
}
.dropdown.active .dropdown-menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.dropdown-item {
display: block;
width: 100%;
padding: var(--space-3) var(--space-4);
background: transparent;
border: none;
border-radius: var(--radius-md);
text-align: left;
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s ease;
font-size: var(--text-sm);
}
.dropdown-item:hover {
background: var(--background-secondary);
transform: translateX(4px);
}
.dropdown-item.active {
background: linear-gradient(135deg, rgba(0, 229, 204, 0.1), rgba(0, 229, 204, 0.05));
color: var(--primary-teal);
font-weight: 600;
}
.dropdown-divider {
height: 1px;
background: var(--divider);
margin: var(--space-2) 0;
}
/* 工具提示 */
.tooltip-wrapper {
position: relative;
display: inline-block;
}
.tooltip {
position: absolute;
bottom: calc(100% + var(--space-2));
left: 50%;
transform: translateX(-50%);
background: var(--background-dark);
color: var(--text-primary);
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-md);
font-size: var(--text-xs);
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
pointer-events: none;
z-index: 1000;
}
.tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: var(--background-dark);
}
.tooltip-wrapper:hover .tooltip {
opacity: 1;
visibility: visible;
}
/* 返回按鈕 */
.back-link {
position: fixed;
bottom: 20px;
right: 20px;
background: var(--primary-teal);
color: var(--background-dark);
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-full);
text-decoration: none;
font-weight: 600;
box-shadow: 0 4px 16px rgba(0, 229, 204, 0.3);
z-index: 100;
transition: all 0.3s ease;
}
.back-link:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 229, 204, 0.4);
}
</style>
</head>
<body>
<div class="demo-container">
<!-- 頁面標題 -->
<div class="demo-header">
<h1 class="demo-title">🎭 互動元件展示</h1>
<p class="demo-subtitle">模態框、通知、下拉選單等互動元件</p>
</div>
<!-- 模態框示例 -->
<section class="demo-section">
<h2 class="section-title">模態框 Modals</h2>
<div class="demo-grid">
<button class="btn btn-primary" onclick="openModal('basicModal')">基礎模態框</button>
<button class="btn btn-success" onclick="openModal('successModal')">成功模態框</button>
<button class="btn btn-warning" onclick="openModal('warningModal')">警告模態框</button>
<button class="btn btn-secondary" onclick="openModal('formModal')">表單模態框</button>
</div>
</section>
<!-- Toast 通知示例 -->
<section class="demo-section">
<h2 class="section-title">Toast 通知</h2>
<div class="demo-grid">
<button class="btn btn-success" onclick="showToast('success')">成功通知</button>
<button class="btn btn-danger" onclick="showToast('error')">錯誤通知</button>
<button class="btn btn-warning" onclick="showToast('warning')">警告通知</button>
<button class="btn btn-primary" onclick="showToast('info')">資訊通知</button>
</div>
</section>
<!-- 下拉選單示例 -->
<section class="demo-section">
<h2 class="section-title">下拉選單 Dropdown</h2>
<div class="demo-grid">
<div class="dropdown">
<button class="btn btn-secondary" onclick="toggleDropdown(this.parentElement)">
選擇選項 ▼
</button>
<div class="dropdown-menu">
<button class="dropdown-item active">選項 1</button>
<button class="dropdown-item">選項 2</button>
<button class="dropdown-item">選項 3</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item">其他選項</button>
</div>
</div>
<div class="dropdown">
<button class="btn btn-primary" onclick="toggleDropdown(this.parentElement)">
用戶選單 ▼
</button>
<div class="dropdown-menu">
<button class="dropdown-item">👤 個人資料</button>
<button class="dropdown-item">⚙️ 設定</button>
<button class="dropdown-item">📊 統計</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item">🚪 登出</button>
</div>
</div>
</div>
</section>
<!-- 工具提示示例 -->
<section class="demo-section">
<h2 class="section-title">工具提示 Tooltips</h2>
<div class="demo-grid">
<div class="tooltip-wrapper">
<button class="btn btn-primary">懸停顯示提示</button>
<div class="tooltip">這是一個工具提示</div>
</div>
<div class="tooltip-wrapper">
<span class="badge badge-info">資訊徽章</span>
<div class="tooltip">點擊查看更多資訊</div>
</div>
<div class="tooltip-wrapper">
<button class="btn btn-icon btn-secondary"></button>
<div class="tooltip">需要幫助嗎?</div>
</div>
</div>
</section>
<!-- 底部抽屜示例 -->
<section class="demo-section">
<h2 class="section-title">底部抽屜 Drawer</h2>
<button class="btn btn-primary" onclick="toggleDrawer()">打開底部抽屜</button>
</section>
</div>
<!-- 基礎模態框 -->
<div class="modal-overlay" id="basicModal" onclick="closeModalOnOverlay(event)">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">基礎模態框</h3>
<button class="modal-close" onclick="closeModal('basicModal')"></button>
</div>
<div class="modal-body">
這是一個基礎的模態框範例。你可以在這裡放置任何內容,包括文字、圖片、表單等。
</div>
<div class="modal-footer">
<button class="btn btn-secondary btn-sm" onclick="closeModal('basicModal')">取消</button>
<button class="btn btn-primary btn-sm">確認</button>
</div>
</div>
</div>
<!-- 成功模態框 -->
<div class="modal-overlay" id="successModal" onclick="closeModalOnOverlay(event)">
<div class="modal modal-success">
<div class="modal-icon"></div>
<div class="modal-header" style="justify-content: center;">
<h3 class="modal-title">操作成功!</h3>
</div>
<div class="modal-body" style="text-align: center;">
你的操作已成功完成。所有變更都已儲存。
</div>
<div class="modal-footer" style="justify-content: center;">
<button class="btn btn-success" onclick="closeModal('successModal')">太棒了!</button>
</div>
</div>
</div>
<!-- 警告模態框 -->
<div class="modal-overlay" id="warningModal" onclick="closeModalOnOverlay(event)">
<div class="modal modal-warning">
<div class="modal-icon"></div>
<div class="modal-header" style="justify-content: center;">
<h3 class="modal-title">確認刪除?</h3>
</div>
<div class="modal-body" style="text-align: center;">
此操作無法復原。確定要刪除這個項目嗎?
</div>
<div class="modal-footer" style="justify-content: center;">
<button class="btn btn-secondary" onclick="closeModal('warningModal')">取消</button>
<button class="btn btn-danger">刪除</button>
</div>
</div>
</div>
<!-- 表單模態框 -->
<div class="modal-overlay" id="formModal" onclick="closeModalOnOverlay(event)">
<div class="modal modal-form">
<div class="modal-header">
<h3 class="modal-title">編輯個人資料</h3>
<button class="modal-close" onclick="closeModal('formModal')"></button>
</div>
<div class="modal-body">
<div class="input-group">
<label class="input-label">姓名</label>
<input type="text" class="input-field" placeholder="請輸入姓名" value="王小明">
</div>
<div class="input-group">
<label class="input-label">電子郵件</label>
<input type="email" class="input-field" placeholder="example@email.com" value="wang@example.com">
</div>
<div class="input-group">
<label class="input-label">簡介</label>
<textarea class="input-field textarea" placeholder="介紹一下自己...">我是一個熱愛學習的人!</textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary btn-sm" onclick="closeModal('formModal')">取消</button>
<button class="btn btn-primary btn-sm">儲存變更</button>
</div>
</div>
</div>
<!-- 底部抽屜 -->
<div class="drawer" id="bottomDrawer">
<div class="drawer-handle"></div>
<h3 style="margin-bottom: var(--space-4); color: var(--text-primary);">選擇學習模式</h3>
<div style="display: grid; gap: var(--space-3);">
<button class="btn btn-primary" style="width: 100%;">📖 詞彙學習</button>
<button class="btn btn-secondary" style="width: 100%;">🗣️ 口說練習</button>
<button class="btn btn-secondary" style="width: 100%;">💬 對話練習</button>
<button class="btn btn-text" style="width: 100%;" onclick="toggleDrawer()">取消</button>
</div>
</div>
<!-- Toast 容器 -->
<div class="toast-container" id="toastContainer"></div>
<!-- 返回連結 -->
<a href="../../index.html" class="back-link">← 返回元件庫</a>
<script>
// 開啟模態框
function openModal(modalId) {
const modal = document.getElementById(modalId);
modal.classList.add('active');
document.body.style.overflow = 'hidden';
}
// 關閉模態框
function closeModal(modalId) {
const modal = document.getElementById(modalId);
modal.classList.remove('active');
document.body.style.overflow = '';
}
// 點擊遮罩關閉
function closeModalOnOverlay(event) {
if (event.target.classList.contains('modal-overlay')) {
event.target.classList.remove('active');
document.body.style.overflow = '';
}
}
// 顯示 Toast
function showToast(type) {
const toastContainer = document.getElementById('toastContainer');
const toastData = {
success: { icon: '✓', title: '成功!', message: '操作已成功完成' },
error: { icon: '✕', title: '錯誤', message: '發生錯誤,請稍後再試' },
warning: { icon: '⚠', title: '警告', message: '請注意這個重要訊息' },
info: { icon: '', title: '提示', message: '這是一條有用的資訊' }
};
const data = toastData[type];
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.innerHTML = `
<span class="toast-icon">${data.icon}</span>
<div class="toast-content">
<div class="toast-title">${data.title}</div>
<div class="toast-message">${data.message}</div>
</div>
<button class="toast-close" onclick="removeToast(this.parentElement)">✕</button>
`;
toastContainer.appendChild(toast);
// 觸發動畫
setTimeout(() => {
toast.classList.add('show');
}, 10);
// 自動移除
setTimeout(() => {
removeToast(toast);
}, 5000);
}
// 移除 Toast
function removeToast(toast) {
toast.classList.remove('show');
setTimeout(() => {
toast.remove();
}, 300);
}
// 切換下拉選單
function toggleDropdown(dropdown) {
// 關閉其他下拉選單
document.querySelectorAll('.dropdown').forEach(d => {
if (d !== dropdown) {
d.classList.remove('active');
}
});
dropdown.classList.toggle('active');
}
// 切換底部抽屜
function toggleDrawer() {
const drawer = document.getElementById('bottomDrawer');
drawer.classList.toggle('active');
// 添加遮罩
if (drawer.classList.contains('active')) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay active';
overlay.id = 'drawerOverlay';
overlay.style.zIndex = '998';
overlay.onclick = toggleDrawer;
document.body.appendChild(overlay);
} else {
const overlay = document.getElementById('drawerOverlay');
if (overlay) {
overlay.remove();
}
}
}
// 點擊外部關閉下拉選單
document.addEventListener('click', (e) => {
if (!e.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown').forEach(d => {
d.classList.remove('active');
});
}
});
// ESC 鍵關閉模態框
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
document.querySelectorAll('.modal-overlay.active').forEach(modal => {
modal.classList.remove('active');
});
document.body.style.overflow = '';
}
});
</script>
</body>
</html>