1270 lines
36 KiB
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><div class="select-wrapper">
|
|
<select class="select-field">
|
|
<option>初級 Beginner</option>
|
|
<option selected>中級 Intermediate</option>
|
|
<option>高級 Advanced</option>
|
|
<option>專家 Expert</option>
|
|
</select>
|
|
<span class="select-arrow">▼</span>
|
|
</div></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><label class="toggle-switch">
|
|
<input type="checkbox" checked>
|
|
<span class="toggle-slider"></span>
|
|
</label></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> |