dramaling-vocab-learning/docs/02_design/component-library/components/02-input/forms.html

1270 lines
36 KiB
HTML

<!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);
}
/* ========================================
表單容器樣式
======================================== */
.form-container {
background: var(--card-background);
border-radius: var(--radius-xl);
padding: var(--space-8);
border: 1px solid var(--divider);
}
.form-container.horizontal .form-row {
display: grid;
grid-template-columns: 150px 1fr;
gap: var(--space-4);
align-items: center;
}
.form-container.horizontal .input-label {
text-align: right;
margin-bottom: 0;
}
.form-row {
margin-bottom: var(--space-4);
}
.form-actions {
display: flex;
gap: var(--space-3);
justify-content: flex-end;
margin-top: var(--space-6);
padding-top: var(--space-4);
border-top: 1px solid var(--divider);
}
/* ========================================
選擇器元件 (Select)
======================================== */
.select-wrapper {
position: relative;
width: 100%;
}
.select-field {
width: 100%;
padding: var(--space-3) var(--space-10) var(--space-3) var(--space-4);
background: var(--background-secondary);
border: 2px solid var(--divider);
border-radius: var(--radius-lg);
font-size: var(--text-base);
color: var(--text-primary);
cursor: pointer;
appearance: none;
transition: all 0.3s ease;
font-family: inherit;
}
.select-field:focus {
outline: none;
background: var(--card-background);
border-color: var(--primary-teal);
box-shadow: var(--focus-ring);
}
.select-arrow {
position: absolute;
right: var(--space-4);
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: var(--text-tertiary);
transition: transform 0.3s ease;
}
.select-wrapper.open .select-arrow {
transform: translateY(-50%) rotate(180deg);
}
/* 自定義下拉選單 */
.select-dropdown {
position: absolute;
top: calc(100% + var(--space-2));
left: 0;
right: 0;
background: var(--card-background);
border: 1px solid var(--divider);
border-radius: var(--radius-lg);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
max-height: 300px;
overflow-y: auto;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.3s ease;
z-index: 100;
}
.select-wrapper.open .select-dropdown {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.select-search {
position: sticky;
top: 0;
padding: var(--space-3);
background: var(--card-background);
border-bottom: 1px solid var(--divider);
}
.select-search input {
width: 100%;
padding: var(--space-2) var(--space-3);
background: var(--background-secondary);
border: 1px solid var(--divider);
border-radius: var(--radius-md);
font-size: var(--text-sm);
color: var(--text-primary);
}
.select-option {
padding: var(--space-3) var(--space-4);
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: var(--space-2);
}
.select-option:hover {
background: var(--background-secondary);
}
.select-option.selected {
background: linear-gradient(135deg, rgba(0, 229, 204, 0.1), rgba(0, 229, 204, 0.05));
color: var(--primary-teal);
font-weight: 600;
}
.select-option.disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* 多選下拉 */
.multi-select-tags {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
margin-bottom: var(--space-2);
}
.select-tag {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2);
background: var(--primary-teal);
color: var(--background-dark);
border-radius: var(--radius-sm);
font-size: var(--text-xs);
font-weight: 600;
}
.select-tag-remove {
cursor: pointer;
padding: 2px;
border-radius: var(--radius-sm);
transition: background 0.2s ease;
}
.select-tag-remove:hover {
background: rgba(0, 0, 0, 0.2);
}
/* ========================================
複選框與單選框
======================================== */
.checkbox-group,
.radio-group {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.checkbox-wrapper,
.radio-wrapper {
display: flex;
align-items: center;
gap: var(--space-3);
cursor: pointer;
transition: transform 0.2s ease;
}
.checkbox-wrapper:hover,
.radio-wrapper:hover {
transform: translateX(4px);
}
/* 自定義複選框 */
.checkbox {
position: relative;
width: 20px;
height: 20px;
flex-shrink: 0;
}
.checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
}
.checkbox-mark {
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 20px;
background: var(--background-secondary);
border: 2px solid var(--divider);
border-radius: var(--radius-sm);
transition: all 0.3s ease;
}
.checkbox input:checked ~ .checkbox-mark {
background: var(--primary-teal);
border-color: var(--primary-teal);
}
.checkbox-mark::after {
content: '';
position: absolute;
display: none;
left: 6px;
top: 2px;
width: 6px;
height: 12px;
border: solid var(--background-dark);
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.checkbox input:checked ~ .checkbox-mark::after {
display: block;
}
.checkbox input:disabled ~ .checkbox-mark {
opacity: 0.5;
cursor: not-allowed;
}
/* 不確定狀態 */
.checkbox input:indeterminate ~ .checkbox-mark {
background: var(--primary-teal);
border-color: var(--primary-teal);
}
.checkbox input:indeterminate ~ .checkbox-mark::after {
display: block;
width: 10px;
height: 2px;
left: 3px;
top: 7px;
border: none;
background: var(--background-dark);
transform: none;
}
/* 自定義單選框 */
.radio {
position: relative;
width: 20px;
height: 20px;
flex-shrink: 0;
}
.radio input {
position: absolute;
opacity: 0;
cursor: pointer;
}
.radio-mark {
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 20px;
background: var(--background-secondary);
border: 2px solid var(--divider);
border-radius: var(--radius-full);
transition: all 0.3s ease;
}
.radio input:checked ~ .radio-mark {
border-color: var(--primary-teal);
}
.radio-mark::after {
content: '';
position: absolute;
display: none;
top: 4px;
left: 4px;
width: 8px;
height: 8px;
background: var(--primary-teal);
border-radius: var(--radius-full);
}
.radio input:checked ~ .radio-mark::after {
display: block;
}
/* ========================================
開關元件 (Toggle)
======================================== */
.toggle-switch {
position: relative;
display: inline-block;
width: 48px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--text-tertiary);
border-radius: var(--radius-full);
transition: all 0.3s ease;
}
.toggle-slider::before {
position: absolute;
content: '';
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background: white;
border-radius: var(--radius-full);
transition: all 0.3s ease;
}
.toggle-switch input:checked + .toggle-slider {
background: var(--primary-teal);
}
.toggle-switch input:checked + .toggle-slider::before {
transform: translateX(24px);
}
.toggle-switch input:disabled + .toggle-slider {
opacity: 0.5;
cursor: not-allowed;
}
/* 帶標籤的開關 */
.toggle-wrapper {
display: flex;
align-items: center;
gap: var(--space-3);
}
.toggle-label {
font-size: var(--text-sm);
color: var(--text-primary);
cursor: pointer;
}
/* 大小變化 */
.toggle-switch.toggle-sm {
width: 36px;
height: 18px;
}
.toggle-switch.toggle-sm .toggle-slider::before {
height: 14px;
width: 14px;
left: 2px;
bottom: 2px;
}
.toggle-switch.toggle-sm input:checked + .toggle-slider::before {
transform: translateX(18px);
}
.toggle-switch.toggle-lg {
width: 60px;
height: 30px;
}
.toggle-switch.toggle-lg .toggle-slider::before {
height: 24px;
width: 24px;
left: 3px;
bottom: 3px;
}
.toggle-switch.toggle-lg input:checked + .toggle-slider::before {
transform: translateX(30px);
}
/* ========================================
滑塊元件 (Slider)
======================================== */
.slider-container {
padding: var(--space-4) 0;
}
.slider-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-3);
}
.slider-label {
font-size: var(--text-sm);
color: var(--text-primary);
font-weight: 500;
}
.slider-value {
font-size: var(--text-sm);
color: var(--primary-teal);
font-weight: 600;
padding: var(--space-1) var(--space-2);
background: var(--background-secondary);
border-radius: var(--radius-sm);
}
.slider-track {
position: relative;
width: 100%;
height: 6px;
background: var(--divider);
border-radius: var(--radius-full);
cursor: pointer;
}
.slider-fill {
position: absolute;
height: 100%;
background: linear-gradient(90deg, var(--primary-teal), var(--primary-teal-light));
border-radius: var(--radius-full);
transition: width 0.1s ease;
}
.slider-thumb {
position: absolute;
width: 20px;
height: 20px;
background: white;
border: 3px solid var(--primary-teal);
border-radius: var(--radius-full);
top: 50%;
transform: translate(-50%, -50%);
cursor: grab;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.slider-thumb:hover {
transform: translate(-50%, -50%) scale(1.2);
box-shadow: 0 4px 12px rgba(0, 229, 204, 0.3);
}
.slider-thumb:active {
cursor: grabbing;
transform: translate(-50%, -50%) scale(1.1);
}
/* 範圍滑塊 */
.range-slider .slider-thumb.min {
z-index: 2;
}
.range-slider .slider-thumb.max {
z-index: 1;
}
.range-slider .slider-fill {
left: 20%;
width: 40%;
}
/* 步進標記 */
.slider-steps {
display: flex;
justify-content: space-between;
margin-top: var(--space-2);
}
.slider-step {
font-size: var(--text-xs);
color: var(--text-tertiary);
}
/* ========================================
檔案上傳
======================================== */
.file-upload {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-8);
background: var(--background-secondary);
border: 2px dashed var(--divider);
border-radius: var(--radius-xl);
cursor: pointer;
transition: all 0.3s ease;
}
.file-upload:hover {
background: var(--card-background);
border-color: var(--primary-teal);
}
.file-upload.dragging {
background: linear-gradient(135deg, rgba(0, 229, 204, 0.05), rgba(0, 229, 204, 0.1));
border-color: var(--primary-teal);
}
.file-upload input {
position: absolute;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.file-upload-icon {
font-size: 48px;
margin-bottom: var(--space-3);
color: var(--text-tertiary);
}
.file-upload-text {
font-size: var(--text-base);
color: var(--text-primary);
margin-bottom: var(--space-2);
}
.file-upload-hint {
font-size: var(--text-sm);
color: var(--text-tertiary);
}
.file-list {
margin-top: var(--space-4);
width: 100%;
}
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-3);
background: var(--card-background);
border-radius: var(--radius-lg);
margin-bottom: var(--space-2);
}
.file-info {
display: flex;
align-items: center;
gap: var(--space-3);
}
.file-icon {
font-size: 24px;
}
.file-name {
font-size: var(--text-sm);
color: var(--text-primary);
}
.file-size {
font-size: var(--text-xs);
color: var(--text-tertiary);
}
.file-remove {
background: transparent;
border: none;
color: var(--text-tertiary);
cursor: pointer;
padding: var(--space-1);
transition: color 0.2s ease;
}
.file-remove:hover {
color: var(--error-red);
}
/* ========================================
表單驗證狀態
======================================== */
.form-field-error {
border-color: var(--error-red) !important;
background: rgba(231, 76, 60, 0.05) !important;
}
.form-field-success {
border-color: var(--success-green) !important;
background: rgba(76, 175, 80, 0.05) !important;
}
.form-error-message {
display: flex;
align-items: center;
gap: var(--space-2);
margin-top: var(--space-2);
font-size: var(--text-xs);
color: var(--error-red);
}
.form-success-message {
display: flex;
align-items: center;
gap: var(--space-2);
margin-top: var(--space-2);
font-size: var(--text-xs);
color: var(--success-green);
}
/* 返回按鈕 */
.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">完整表單範例</h2>
<div class="component-showcase">
<form class="form-container">
<h3 style="margin-bottom: var(--space-6); color: var(--text-primary);">用戶註冊表單</h3>
<div class="form-row">
<label class="input-label required">姓名</label>
<input type="text" class="input-field" placeholder="請輸入您的姓名" required>
</div>
<div class="form-row">
<label class="input-label required">電子郵件</label>
<input type="email" class="input-field" placeholder="example@email.com" required>
<span class="input-hint">我們不會分享您的電子郵件</span>
</div>
<div class="form-row">
<label class="input-label">選擇興趣</label>
<div class="select-wrapper" id="multiSelect">
<div class="multi-select-tags">
<span class="select-tag">
詞彙學習
<span class="select-tag-remove"></span>
</span>
<span class="select-tag">
口說練習
<span class="select-tag-remove"></span>
</span>
</div>
<div class="select-field" onclick="toggleSelect('multiSelect')">選擇多個選項...</div>
<span class="select-arrow"></span>
<div class="select-dropdown">
<div class="select-option">📚 詞彙學習</div>
<div class="select-option">🗣️ 口說練習</div>
<div class="select-option">💬 對話練習</div>
<div class="select-option">📖 閱讀理解</div>
</div>
</div>
</div>
<div class="form-row">
<label class="input-label">接收通知</label>
<div class="checkbox-group">
<label class="checkbox-wrapper">
<div class="checkbox">
<input type="checkbox" checked>
<span class="checkbox-mark"></span>
</div>
<span>電子郵件通知</span>
</label>
<label class="checkbox-wrapper">
<div class="checkbox">
<input type="checkbox">
<span class="checkbox-mark"></span>
</div>
<span>推播通知</span>
</label>
<label class="checkbox-wrapper">
<div class="checkbox">
<input type="checkbox" disabled>
<span class="checkbox-mark"></span>
</div>
<span>簡訊通知(不可用)</span>
</label>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary">取消</button>
<button type="submit" class="btn btn-primary">提交表單</button>
</div>
</form>
</div>
</section>
<!-- 選擇器元件 -->
<section class="demo-section">
<h2 class="section-title">選擇器 Select</h2>
<!-- 基礎選擇器 -->
<div class="component-showcase">
<div class="showcase-preview">
<div class="input-group" style="max-width: 300px;">
<label class="input-label">選擇等級</label>
<div class="select-wrapper" id="basicSelect">
<select class="select-field">
<option>初級 Beginner</option>
<option selected>中級 Intermediate</option>
<option>高級 Advanced</option>
<option>專家 Expert</option>
</select>
<span class="select-arrow"></span>
</div>
</div>
</div>
<div class="showcase-code">
<button class="copy-button" onclick="copyCode(this)">複製</button>
<pre><code>&lt;div class="select-wrapper"&gt;
&lt;select class="select-field"&gt;
&lt;option&gt;初級 Beginner&lt;/option&gt;
&lt;option selected&gt;中級 Intermediate&lt;/option&gt;
&lt;option&gt;高級 Advanced&lt;/option&gt;
&lt;option&gt;專家 Expert&lt;/option&gt;
&lt;/select&gt;
&lt;span class="select-arrow"&gt;&lt;/span&gt;
&lt;/div&gt;</code></pre>
</div>
</div>
<!-- 搜尋下拉 -->
<div class="component-showcase">
<h3 class="component-subtitle">搜尋下拉選單</h3>
<div class="showcase-preview">
<div class="input-group" style="max-width: 300px;">
<label class="input-label">選擇國家</label>
<div class="select-wrapper" id="searchSelect">
<div class="select-field" onclick="toggleSelect('searchSelect')">
請選擇國家...
</div>
<span class="select-arrow"></span>
<div class="select-dropdown">
<div class="select-search">
<input type="text" placeholder="搜尋國家..." onkeyup="filterOptions(this)">
</div>
<div class="select-option">🇹🇼 台灣 Taiwan</div>
<div class="select-option">🇯🇵 日本 Japan</div>
<div class="select-option">🇰🇷 韓國 Korea</div>
<div class="select-option">🇺🇸 美國 USA</div>
<div class="select-option">🇬🇧 英國 UK</div>
<div class="select-option">🇫🇷 法國 France</div>
<div class="select-option">🇩🇪 德國 Germany</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 複選框與單選框 -->
<section class="demo-section">
<h2 class="section-title">複選框與單選框</h2>
<div class="demo-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6);">
<!-- 複選框 -->
<div class="component-showcase">
<h3 class="component-subtitle">複選框 Checkbox</h3>
<div class="showcase-preview" style="flex-direction: column; align-items: flex-start;">
<div class="checkbox-group">
<label class="checkbox-wrapper">
<div class="checkbox">
<input type="checkbox" checked>
<span class="checkbox-mark"></span>
</div>
<span>已選中</span>
</label>
<label class="checkbox-wrapper">
<div class="checkbox">
<input type="checkbox">
<span class="checkbox-mark"></span>
</div>
<span>未選中</span>
</label>
<label class="checkbox-wrapper">
<div class="checkbox">
<input type="checkbox" id="indeterminate">
<span class="checkbox-mark"></span>
</div>
<span>不確定狀態</span>
</label>
<label class="checkbox-wrapper">
<div class="checkbox">
<input type="checkbox" disabled checked>
<span class="checkbox-mark"></span>
</div>
<span>禁用狀態</span>
</label>
</div>
</div>
</div>
<!-- 單選框 -->
<div class="component-showcase">
<h3 class="component-subtitle">單選框 Radio</h3>
<div class="showcase-preview" style="flex-direction: column; align-items: flex-start;">
<div class="radio-group">
<label class="radio-wrapper">
<div class="radio">
<input type="radio" name="demo-radio" checked>
<span class="radio-mark"></span>
</div>
<span>選項一</span>
</label>
<label class="radio-wrapper">
<div class="radio">
<input type="radio" name="demo-radio">
<span class="radio-mark"></span>
</div>
<span>選項二</span>
</label>
<label class="radio-wrapper">
<div class="radio">
<input type="radio" name="demo-radio">
<span class="radio-mark"></span>
</div>
<span>選項三</span>
</label>
<label class="radio-wrapper">
<div class="radio">
<input type="radio" name="demo-radio" disabled>
<span class="radio-mark"></span>
</div>
<span>禁用選項</span>
</label>
</div>
</div>
</div>
</div>
</section>
<!-- 開關元件 -->
<section class="demo-section">
<h2 class="section-title">開關 Toggle Switch</h2>
<div class="component-showcase">
<div class="showcase-preview" style="flex-direction: column; gap: var(--space-4);">
<!-- 基礎開關 -->
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox" checked>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">基礎開關(開啟)</span>
</div>
<!-- 小型開關 -->
<div class="toggle-wrapper">
<label class="toggle-switch toggle-sm">
<input type="checkbox">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">小型開關</span>
</div>
<!-- 大型開關 -->
<div class="toggle-wrapper">
<label class="toggle-switch toggle-lg">
<input type="checkbox" checked>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">大型開關</span>
</div>
<!-- 禁用開關 -->
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox" disabled>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">禁用開關</span>
</div>
</div>
<div class="showcase-code">
<button class="copy-button" onclick="copyCode(this)">複製</button>
<pre><code>&lt;label class="toggle-switch"&gt;
&lt;input type="checkbox" checked&gt;
&lt;span class="toggle-slider"&gt;&lt;/span&gt;
&lt;/label&gt;</code></pre>
</div>
</div>
</section>
<!-- 滑塊元件 -->
<section class="demo-section">
<h2 class="section-title">滑塊 Slider</h2>
<div class="component-showcase">
<div class="showcase-preview" style="flex-direction: column; width: 100%;">
<!-- 單點滑塊 -->
<div class="slider-container" style="width: 100%; max-width: 400px;">
<div class="slider-header">
<span class="slider-label">音量控制</span>
<span class="slider-value" id="sliderValue1">50%</span>
</div>
<div class="slider-track" onclick="updateSlider(event, 'slider1')">
<div class="slider-fill" id="sliderFill1" style="width: 50%;"></div>
<div class="slider-thumb" id="sliderThumb1" style="left: 50%;"
onmousedown="startDrag(event, 'slider1')"></div>
</div>
</div>
<!-- 步進滑塊 -->
<div class="slider-container" style="width: 100%; max-width: 400px;">
<div class="slider-header">
<span class="slider-label">難度等級</span>
<span class="slider-value">中級</span>
</div>
<div class="slider-track">
<div class="slider-fill" style="width: 50%;"></div>
<div class="slider-thumb" style="left: 50%;"></div>
</div>
<div class="slider-steps">
<span class="slider-step">初級</span>
<span class="slider-step">中級</span>
<span class="slider-step">高級</span>
<span class="slider-step">專家</span>
</div>
</div>
<!-- 範圍滑塊 -->
<div class="slider-container range-slider" style="width: 100%; max-width: 400px;">
<div class="slider-header">
<span class="slider-label">價格範圍</span>
<span class="slider-value">$20 - $60</span>
</div>
<div class="slider-track">
<div class="slider-fill"></div>
<div class="slider-thumb min" style="left: 20%;"></div>
<div class="slider-thumb max" style="left: 60%;"></div>
</div>
</div>
</div>
</div>
</section>
<!-- 檔案上傳 -->
<section class="demo-section">
<h2 class="section-title">檔案上傳 File Upload</h2>
<div class="component-showcase">
<div class="showcase-preview" style="width: 100%;">
<div class="file-upload" id="fileUpload"
ondrop="handleDrop(event)"
ondragover="handleDragOver(event)"
ondragleave="handleDragLeave(event)">
<input type="file" multiple onchange="handleFiles(this.files)">
<div class="file-upload-icon">📁</div>
<div class="file-upload-text">點擊或拖曳檔案到此處</div>
<div class="file-upload-hint">支援 JPG, PNG, PDF (最大 10MB)</div>
</div>
<div class="file-list" id="fileList" style="display: none;">
<!-- 檔案列表將動態生成 -->
</div>
</div>
</div>
</section>
<!-- 表單驗證狀態 -->
<section class="demo-section">
<h2 class="section-title">表單驗證狀態</h2>
<div class="component-showcase">
<div class="showcase-preview" style="flex-direction: column; align-items: stretch; gap: var(--space-4);">
<!-- 成功狀態 -->
<div class="input-group">
<label class="input-label">成功狀態</label>
<input type="text" class="input-field form-field-success" value="正確的輸入">
<div class="form-success-message">
<span></span>
<span>輸入格式正確</span>
</div>
</div>
<!-- 錯誤狀態 -->
<div class="input-group">
<label class="input-label">錯誤狀態</label>
<input type="text" class="input-field form-field-error" value="錯誤的輸入">
<div class="form-error-message">
<span></span>
<span>請輸入有效的內容</span>
</div>
</div>
</div>
</div>
</section>
<!-- 水平表單布局 -->
<section class="demo-section">
<h2 class="section-title">水平表單布局</h2>
<div class="component-showcase">
<form class="form-container horizontal">
<div class="form-row">
<label class="input-label">用戶名稱</label>
<input type="text" class="input-field" placeholder="請輸入用戶名稱">
</div>
<div class="form-row">
<label class="input-label">電子郵件</label>
<input type="email" class="input-field" placeholder="example@email.com">
</div>
<div class="form-row">
<label class="input-label">通知設定</label>
<div class="toggle-wrapper">
<label class="toggle-switch">
<input type="checkbox" checked>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">接收電子郵件通知</span>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">儲存設定</button>
</div>
</form>
</div>
</section>
</div>
<!-- 返回連結 -->
<a href="../../index.html" class="back-link">← 返回元件庫</a>
<script>
// 設置不確定狀態
document.getElementById('indeterminate').indeterminate = true;
// 切換選擇器
function toggleSelect(id) {
const wrapper = document.getElementById(id);
wrapper.classList.toggle('open');
}
// 篩選選項
function filterOptions(input) {
const filter = input.value.toLowerCase();
const options = input.closest('.select-dropdown').querySelectorAll('.select-option');
options.forEach(option => {
const text = option.textContent.toLowerCase();
option.style.display = text.includes(filter) ? 'flex' : 'none';
});
}
// 滑塊拖動功能
let isDragging = false;
let currentSlider = null;
function startDrag(e, sliderId) {
isDragging = true;
currentSlider = sliderId;
e.preventDefault();
}
function updateSlider(e, sliderId) {
if (!isDragging && e.type === 'click' || isDragging && currentSlider === sliderId) {
const track = e.currentTarget || document.querySelector(`#${sliderId}`).closest('.slider-track');
const rect = track.getBoundingClientRect();
const percent = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100));
const fill = document.getElementById(`sliderFill${sliderId.slice(-1)}`);
const thumb = document.getElementById(`sliderThumb${sliderId.slice(-1)}`);
const value = document.getElementById(`sliderValue${sliderId.slice(-1)}`);
if (fill) fill.style.width = percent + '%';
if (thumb) thumb.style.left = percent + '%';
if (value) value.textContent = Math.round(percent) + '%';
}
}
document.addEventListener('mousemove', (e) => {
if (isDragging && currentSlider) {
updateSlider(e, currentSlider);
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
currentSlider = null;
});
// 檔案上傳功能
function handleDragOver(e) {
e.preventDefault();
e.currentTarget.classList.add('dragging');
}
function handleDragLeave(e) {
e.currentTarget.classList.remove('dragging');
}
function handleDrop(e) {
e.preventDefault();
e.currentTarget.classList.remove('dragging');
handleFiles(e.dataTransfer.files);
}
function handleFiles(files) {
const fileList = document.getElementById('fileList');
fileList.style.display = 'block';
fileList.innerHTML = '';
Array.from(files).forEach(file => {
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
const fileIcon = file.type.startsWith('image/') ? '🖼️' : '📄';
const fileSize = (file.size / 1024).toFixed(1) + ' KB';
fileItem.innerHTML = `
<div class="file-info">
<span class="file-icon">${fileIcon}</span>
<div>
<div class="file-name">${file.name}</div>
<div class="file-size">${fileSize}</div>
</div>
</div>
<button class="file-remove" onclick="this.parentElement.remove()">✕</button>
`;
fileList.appendChild(fileItem);
});
}
// 複製代碼功能
function copyCode(button) {
const codeBlock = button.nextElementSibling.querySelector('code');
const textArea = document.createElement('textarea');
textArea.value = codeBlock.textContent;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
button.textContent = '已複製!';
button.classList.add('copied');
setTimeout(() => {
button.textContent = '複製';
button.classList.remove('copied');
}, 2000);
}
// 關閉選擇器當點擊外部
document.addEventListener('click', (e) => {
if (!e.target.closest('.select-wrapper')) {
document.querySelectorAll('.select-wrapper').forEach(wrapper => {
wrapper.classList.remove('open');
});
}
});
</script>
</body>
</html>