815 lines
29 KiB
HTML
815 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>UI Hybrid 20 - Digital Minimalism Action Controller</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
:root {
|
|
--pure-white: #ffffff;
|
|
--soft-black: #0a0a0a;
|
|
--neutral-100: #f5f5f5;
|
|
--neutral-200: #e5e5e5;
|
|
--neutral-300: #d4d4d4;
|
|
--neutral-400: #a3a3a3;
|
|
--neutral-500: #737373;
|
|
--neutral-600: #525252;
|
|
--neutral-700: #404040;
|
|
--neutral-800: #262626;
|
|
--neutral-900: #171717;
|
|
|
|
--accent-primary: #0066ff;
|
|
--accent-success: #00d26a;
|
|
--accent-error: #ff3838;
|
|
--accent-warning: #ffab00;
|
|
|
|
--radius-small: 2px;
|
|
--radius-medium: 4px;
|
|
--radius-large: 8px;
|
|
|
|
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
--transition-medium: 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
--transition-slow: 600ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
background: var(--pure-white);
|
|
color: var(--soft-black);
|
|
line-height: 1.5;
|
|
font-size: 14px;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 60px 20px;
|
|
}
|
|
|
|
.header {
|
|
margin-bottom: 80px;
|
|
text-align: center;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 32px;
|
|
font-weight: 300;
|
|
letter-spacing: -0.02em;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.header p {
|
|
font-size: 16px;
|
|
color: var(--neutral-500);
|
|
font-weight: 400;
|
|
}
|
|
|
|
/* Action Controller Base */
|
|
.action-controller {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 120px;
|
|
height: 40px;
|
|
padding: 0 24px;
|
|
background: var(--pure-white);
|
|
border: 1px solid var(--neutral-300);
|
|
border-radius: var(--radius-medium);
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--soft-black);
|
|
cursor: pointer;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: all var(--transition-fast);
|
|
user-select: none;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
|
|
.action-controller:hover {
|
|
border-color: var(--neutral-400);
|
|
background: var(--neutral-100);
|
|
}
|
|
|
|
.action-controller:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
/* Primary Action */
|
|
.action-controller.primary {
|
|
background: var(--soft-black);
|
|
border-color: var(--soft-black);
|
|
color: var(--pure-white);
|
|
}
|
|
|
|
.action-controller.primary:hover {
|
|
background: var(--neutral-800);
|
|
border-color: var(--neutral-800);
|
|
}
|
|
|
|
/* Action States */
|
|
.action-controller .state-default,
|
|
.action-controller .state-loading,
|
|
.action-controller .state-confirm,
|
|
.action-controller .state-success,
|
|
.action-controller .state-error {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
transition: opacity var(--transition-fast), transform var(--transition-fast);
|
|
}
|
|
|
|
.action-controller[data-state="default"] .state-default {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.action-controller[data-state="loading"] .state-loading,
|
|
.action-controller[data-state="confirm"] .state-confirm,
|
|
.action-controller[data-state="success"] .state-success,
|
|
.action-controller[data-state="error"] .state-error {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.action-controller:not([data-state="default"]) .state-default,
|
|
.action-controller:not([data-state="loading"]) .state-loading,
|
|
.action-controller:not([data-state="confirm"]) .state-confirm,
|
|
.action-controller:not([data-state="success"]) .state-success,
|
|
.action-controller:not([data-state="error"]) .state-error {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
position: absolute;
|
|
}
|
|
|
|
/* Loading State */
|
|
.loading-spinner {
|
|
width: 16px;
|
|
height: 16px;
|
|
border: 2px solid var(--neutral-300);
|
|
border-top-color: var(--soft-black);
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
|
|
.action-controller.primary .loading-spinner {
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
border-top-color: var(--pure-white);
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Progress Bar */
|
|
.action-controller .progress-bar {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
height: 2px;
|
|
background: var(--accent-primary);
|
|
transform-origin: left;
|
|
transition: transform var(--transition-slow);
|
|
}
|
|
|
|
/* Success/Error States */
|
|
.action-controller[data-state="success"] {
|
|
border-color: var(--accent-success);
|
|
color: var(--accent-success);
|
|
}
|
|
|
|
.action-controller[data-state="error"] {
|
|
border-color: var(--accent-error);
|
|
color: var(--accent-error);
|
|
}
|
|
|
|
/* Icons */
|
|
.icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
stroke-width: 2;
|
|
stroke: currentColor;
|
|
fill: none;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Action Groups */
|
|
.action-group {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.action-group.vertical {
|
|
flex-direction: column;
|
|
max-width: 240px;
|
|
}
|
|
|
|
/* Demo Sections */
|
|
.demo-section {
|
|
margin-bottom: 80px;
|
|
}
|
|
|
|
.demo-section h2 {
|
|
font-size: 20px;
|
|
font-weight: 400;
|
|
margin-bottom: 24px;
|
|
color: var(--neutral-700);
|
|
}
|
|
|
|
.demo-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 40px;
|
|
}
|
|
|
|
/* Card Action */
|
|
.action-card {
|
|
background: var(--pure-white);
|
|
border: 1px solid var(--neutral-200);
|
|
border-radius: var(--radius-large);
|
|
padding: 24px;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.action-card:hover {
|
|
border-color: var(--neutral-300);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
.action-card h3 {
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.action-card p {
|
|
color: var(--neutral-600);
|
|
margin-bottom: 20px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* Confirmation Dialog */
|
|
.confirm-dialog {
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%) scale(0.9);
|
|
background: var(--pure-white);
|
|
border-radius: var(--radius-large);
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
|
|
padding: 32px;
|
|
max-width: 400px;
|
|
width: 90%;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: all var(--transition-medium);
|
|
z-index: 1000;
|
|
}
|
|
|
|
.confirm-dialog.active {
|
|
opacity: 1;
|
|
transform: translate(-50%, -50%) scale(1);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.confirm-dialog h3 {
|
|
font-size: 18px;
|
|
font-weight: 500;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.confirm-dialog p {
|
|
color: var(--neutral-600);
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.confirm-dialog .dialog-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
/* Overlay */
|
|
.overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.2);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity var(--transition-medium);
|
|
z-index: 999;
|
|
}
|
|
|
|
.overlay.active {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
/* Toast Notifications */
|
|
.toast-container {
|
|
position: fixed;
|
|
bottom: 24px;
|
|
right: 24px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
z-index: 1001;
|
|
}
|
|
|
|
.toast {
|
|
background: var(--pure-white);
|
|
border: 1px solid var(--neutral-300);
|
|
border-radius: var(--radius-medium);
|
|
padding: 16px 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
min-width: 280px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
transform: translateX(400px);
|
|
transition: transform var(--transition-medium);
|
|
}
|
|
|
|
.toast.show {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.toast.success {
|
|
border-color: var(--accent-success);
|
|
}
|
|
|
|
.toast.error {
|
|
border-color: var(--accent-error);
|
|
}
|
|
|
|
/* Minimal Animations */
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
.pulse {
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
padding: 40px 16px;
|
|
}
|
|
|
|
.header {
|
|
margin-bottom: 60px;
|
|
}
|
|
|
|
.demo-grid {
|
|
grid-template-columns: 1fr;
|
|
gap: 24px;
|
|
}
|
|
|
|
.action-group {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.toast-container {
|
|
left: 16px;
|
|
right: 16px;
|
|
bottom: 16px;
|
|
}
|
|
|
|
.toast {
|
|
min-width: auto;
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header class="header">
|
|
<h1>Minimal Action Controller</h1>
|
|
<p>Precise control states with pure geometric design</p>
|
|
</header>
|
|
|
|
<!-- Basic Actions -->
|
|
<section class="demo-section">
|
|
<h2>Primary Actions</h2>
|
|
<div class="action-group">
|
|
<button class="action-controller primary" data-state="default" onclick="handleAction(this, 'save')">
|
|
<span class="state-default">Save Changes</span>
|
|
<span class="state-loading">
|
|
<div class="loading-spinner"></div>
|
|
<span>Saving...</span>
|
|
</span>
|
|
<span class="state-success">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="20 6 9 17 4 12"></polyline>
|
|
</svg>
|
|
<span>Saved</span>
|
|
</span>
|
|
<div class="progress-bar"></div>
|
|
</button>
|
|
|
|
<button class="action-controller" data-state="default" onclick="handleAction(this, 'export')">
|
|
<span class="state-default">Export Data</span>
|
|
<span class="state-loading">
|
|
<div class="loading-spinner"></div>
|
|
<span>Exporting...</span>
|
|
</span>
|
|
<span class="state-success">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="7 10 12 15 17 10"></polyline>
|
|
<line x1="12" y1="15" x2="12" y2="3"></line>
|
|
</svg>
|
|
<span>Exported</span>
|
|
</span>
|
|
<div class="progress-bar"></div>
|
|
</button>
|
|
|
|
<button class="action-controller" data-state="default" onclick="handleAction(this, 'sync')">
|
|
<span class="state-default">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="23 4 23 10 17 10"></polyline>
|
|
<polyline points="1 20 1 14 7 14"></polyline>
|
|
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
|
</svg>
|
|
<span>Sync</span>
|
|
</span>
|
|
<span class="state-loading">
|
|
<div class="loading-spinner"></div>
|
|
<span>Syncing...</span>
|
|
</span>
|
|
<span class="state-success">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="20 6 9 17 4 12"></polyline>
|
|
</svg>
|
|
<span>Synced</span>
|
|
</span>
|
|
<div class="progress-bar"></div>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Confirmation Actions -->
|
|
<section class="demo-section">
|
|
<h2>Confirmation Actions</h2>
|
|
<div class="demo-grid">
|
|
<div class="action-card">
|
|
<h3>Delete Item</h3>
|
|
<p>This action requires confirmation before proceeding.</p>
|
|
<button class="action-controller" data-state="default" onclick="handleConfirmAction(this, 'delete')">
|
|
<span class="state-default">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="3 6 5 6 21 6"></polyline>
|
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
</svg>
|
|
<span>Delete</span>
|
|
</span>
|
|
<span class="state-confirm">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<circle cx="12" cy="12" r="10"></circle>
|
|
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
</svg>
|
|
<span>Confirm Delete?</span>
|
|
</span>
|
|
<span class="state-loading">
|
|
<div class="loading-spinner"></div>
|
|
<span>Deleting...</span>
|
|
</span>
|
|
<span class="state-success">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="20 6 9 17 4 12"></polyline>
|
|
</svg>
|
|
<span>Deleted</span>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="action-card">
|
|
<h3>Archive Project</h3>
|
|
<p>Move this project to archived items.</p>
|
|
<button class="action-controller" data-state="default" onclick="handleConfirmAction(this, 'archive')">
|
|
<span class="state-default">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="21 8 21 21 3 21 3 8"></polyline>
|
|
<rect x="1" y="3" width="22" height="5"></rect>
|
|
<line x1="10" y1="12" x2="14" y2="12"></line>
|
|
</svg>
|
|
<span>Archive</span>
|
|
</span>
|
|
<span class="state-confirm">
|
|
<span>Archive Project?</span>
|
|
</span>
|
|
<span class="state-loading">
|
|
<div class="loading-spinner"></div>
|
|
<span>Archiving...</span>
|
|
</span>
|
|
<span class="state-success">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="20 6 9 17 4 12"></polyline>
|
|
</svg>
|
|
<span>Archived</span>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="action-card">
|
|
<h3>Reset Settings</h3>
|
|
<p>Restore all settings to default values.</p>
|
|
<button class="action-controller" data-state="default" onclick="handleConfirmAction(this, 'reset')">
|
|
<span class="state-default">Reset to Default</span>
|
|
<span class="state-confirm">Reset All Settings?</span>
|
|
<span class="state-loading">
|
|
<div class="loading-spinner"></div>
|
|
<span>Resetting...</span>
|
|
</span>
|
|
<span class="state-success">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="20 6 9 17 4 12"></polyline>
|
|
</svg>
|
|
<span>Reset Complete</span>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Error States -->
|
|
<section class="demo-section">
|
|
<h2>Error Handling</h2>
|
|
<div class="action-group">
|
|
<button class="action-controller" data-state="default" onclick="handleErrorAction(this)">
|
|
<span class="state-default">Test Error State</span>
|
|
<span class="state-loading">
|
|
<div class="loading-spinner"></div>
|
|
<span>Processing...</span>
|
|
</span>
|
|
<span class="state-error">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
<span>Failed</span>
|
|
</span>
|
|
</button>
|
|
|
|
<button class="action-controller" data-state="default" onclick="handleRetryAction(this)">
|
|
<span class="state-default">Network Request</span>
|
|
<span class="state-loading">
|
|
<div class="loading-spinner"></div>
|
|
<span>Connecting...</span>
|
|
</span>
|
|
<span class="state-error">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<circle cx="12" cy="12" r="10"></circle>
|
|
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
</svg>
|
|
<span>Retry</span>
|
|
</span>
|
|
<span class="state-success">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="20 6 9 17 4 12"></polyline>
|
|
</svg>
|
|
<span>Connected</span>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Progress Actions -->
|
|
<section class="demo-section">
|
|
<h2>Progress Indicators</h2>
|
|
<div class="action-group vertical">
|
|
<button class="action-controller primary" data-state="default" onclick="handleProgressAction(this, 'upload')">
|
|
<span class="state-default">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="17 8 12 3 7 8"></polyline>
|
|
<line x1="12" y1="3" x2="12" y2="15"></line>
|
|
</svg>
|
|
<span>Upload File</span>
|
|
</span>
|
|
<span class="state-loading">
|
|
<span class="pulse">Uploading...</span>
|
|
</span>
|
|
<span class="state-success">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="20 6 9 17 4 12"></polyline>
|
|
</svg>
|
|
<span>Upload Complete</span>
|
|
</span>
|
|
<div class="progress-bar"></div>
|
|
</button>
|
|
|
|
<button class="action-controller" data-state="default" onclick="handleProgressAction(this, 'process')">
|
|
<span class="state-default">Process Data</span>
|
|
<span class="state-loading">Processing...</span>
|
|
<span class="state-success">
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="20 6 9 17 4 12"></polyline>
|
|
</svg>
|
|
<span>Processed</span>
|
|
</span>
|
|
<div class="progress-bar"></div>
|
|
</button>
|
|
|
|
<button class="action-controller" data-state="default" onclick="handleProgressAction(this, 'generate')">
|
|
<span class="state-default">Generate Report</span>
|
|
<span class="state-loading">Generating...</span>
|
|
<span class="state-success">View Report</span>
|
|
<div class="progress-bar"></div>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Confirmation Dialog -->
|
|
<div class="overlay" onclick="closeDialog()"></div>
|
|
<div class="confirm-dialog" id="confirmDialog">
|
|
<h3>Confirm Action</h3>
|
|
<p>Are you sure you want to proceed with this action?</p>
|
|
<div class="dialog-actions">
|
|
<button class="action-controller" onclick="closeDialog()">Cancel</button>
|
|
<button class="action-controller primary" id="confirmButton">Confirm</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toast Container -->
|
|
<div class="toast-container" id="toastContainer"></div>
|
|
|
|
<script>
|
|
// Action Controller Functions
|
|
function handleAction(button, type) {
|
|
if (button.dataset.state !== 'default') return;
|
|
|
|
button.dataset.state = 'loading';
|
|
|
|
// Simulate async operation
|
|
setTimeout(() => {
|
|
button.dataset.state = 'success';
|
|
showToast(`${type.charAt(0).toUpperCase() + type.slice(1)} completed successfully`, 'success');
|
|
|
|
// Reset after delay
|
|
setTimeout(() => {
|
|
button.dataset.state = 'default';
|
|
}, 2000);
|
|
}, 2000);
|
|
}
|
|
|
|
function handleConfirmAction(button, type) {
|
|
const currentState = button.dataset.state;
|
|
|
|
if (currentState === 'default') {
|
|
button.dataset.state = 'confirm';
|
|
|
|
// Auto-reset if no action taken
|
|
setTimeout(() => {
|
|
if (button.dataset.state === 'confirm') {
|
|
button.dataset.state = 'default';
|
|
}
|
|
}, 3000);
|
|
} else if (currentState === 'confirm') {
|
|
button.dataset.state = 'loading';
|
|
|
|
setTimeout(() => {
|
|
button.dataset.state = 'success';
|
|
showToast(`${type.charAt(0).toUpperCase() + type.slice(1)} completed`, 'success');
|
|
|
|
setTimeout(() => {
|
|
button.dataset.state = 'default';
|
|
}, 2000);
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
function handleErrorAction(button) {
|
|
if (button.dataset.state !== 'default') return;
|
|
|
|
button.dataset.state = 'loading';
|
|
|
|
setTimeout(() => {
|
|
button.dataset.state = 'error';
|
|
showToast('Operation failed. Please try again.', 'error');
|
|
|
|
setTimeout(() => {
|
|
button.dataset.state = 'default';
|
|
}, 3000);
|
|
}, 1500);
|
|
}
|
|
|
|
function handleRetryAction(button) {
|
|
const currentState = button.dataset.state;
|
|
|
|
if (currentState === 'default' || currentState === 'error') {
|
|
button.dataset.state = 'loading';
|
|
|
|
// Simulate network request with 50% failure rate
|
|
setTimeout(() => {
|
|
if (Math.random() > 0.5) {
|
|
button.dataset.state = 'success';
|
|
showToast('Connection established', 'success');
|
|
|
|
setTimeout(() => {
|
|
button.dataset.state = 'default';
|
|
}, 2000);
|
|
} else {
|
|
button.dataset.state = 'error';
|
|
showToast('Connection failed. Click to retry.', 'error');
|
|
}
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
function handleProgressAction(button, type) {
|
|
if (button.dataset.state !== 'default') return;
|
|
|
|
button.dataset.state = 'loading';
|
|
const progressBar = button.querySelector('.progress-bar');
|
|
|
|
// Animate progress
|
|
let progress = 0;
|
|
const interval = setInterval(() => {
|
|
progress += Math.random() * 20;
|
|
if (progress > 100) progress = 100;
|
|
|
|
progressBar.style.transform = `scaleX(${progress / 100})`;
|
|
|
|
if (progress >= 100) {
|
|
clearInterval(interval);
|
|
button.dataset.state = 'success';
|
|
showToast(`${type.charAt(0).toUpperCase() + type.slice(1)} completed`, 'success');
|
|
|
|
setTimeout(() => {
|
|
button.dataset.state = 'default';
|
|
progressBar.style.transform = 'scaleX(0)';
|
|
}, 2000);
|
|
}
|
|
}, 200);
|
|
}
|
|
|
|
// Dialog Functions
|
|
function showDialog() {
|
|
document.getElementById('confirmDialog').classList.add('active');
|
|
document.querySelector('.overlay').classList.add('active');
|
|
}
|
|
|
|
function closeDialog() {
|
|
document.getElementById('confirmDialog').classList.remove('active');
|
|
document.querySelector('.overlay').classList.remove('active');
|
|
}
|
|
|
|
// Toast Functions
|
|
function showToast(message, type = 'default') {
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast ${type}`;
|
|
|
|
let icon = '';
|
|
if (type === 'success') {
|
|
icon = '<svg class="icon" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg>';
|
|
} else if (type === 'error') {
|
|
icon = '<svg class="icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>';
|
|
}
|
|
|
|
toast.innerHTML = `${icon}<span>${message}</span>`;
|
|
|
|
const container = document.getElementById('toastContainer');
|
|
container.appendChild(toast);
|
|
|
|
// Trigger show animation
|
|
setTimeout(() => toast.classList.add('show'), 10);
|
|
|
|
// Remove after delay
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Add click outside to close confirmation states
|
|
document.addEventListener('click', (e) => {
|
|
if (!e.target.closest('.action-controller')) {
|
|
document.querySelectorAll('.action-controller[data-state="confirm"]').forEach(btn => {
|
|
btn.dataset.state = 'default';
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |