Spaces:
Running
Running
make this better, <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Toast Notification Playground</title> <script src="https://cdn.tailwindcss.com"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" /> <style> /* Animation Keyframes */ @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes bounceIn { 0% { transform: scale(0.8); opacity: 0; } 50% { transform: scale(1.05); opacity: 1; } 100% { transform: scale(1); opacity: 1; } } @keyframes bounceOut { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.05); opacity: 1; } 100% { transform: scale(0.8); opacity: 0; } } @keyframes flipIn { from { transform: perspective(400px) rotateY(90deg); opacity: 0; } to { transform: perspective(400px) rotateY(0); opacity: 1; } } @keyframes flipOut { from { transform: perspective(400px) rotateY(0); opacity: 1; } to { transform: perspective(400px) rotateY(90deg); opacity: 0; } } @keyframes zoomIn { from { transform: scale(0.5); opacity: 0; } to { transform: scale(1); opacity: 1; } } @keyframes zoomOut { from { transform: scale(1); opacity: 1; } to { transform: scale(0.5); opacity: 0; } } /* Toast Container Styles */ .toast-container { position: fixed; z-index: 9999; width: auto; max-width: 400px; pointer-events: none; } .toast-top-right { top: 1rem; right: 1rem; } .toast-top-left { top: 1rem; left: 1rem; } .toast-bottom-right { bottom: 1rem; right: 1rem; } .toast-bottom-left { bottom: 1rem; left: 1rem; } .toast-center { top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Toast Styles */ .toast { position: relative; margin-bottom: 1rem; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); display: flex; align-items: flex-start; overflow: hidden; transition: all 0.3s ease; pointer-events: auto; max-width: 100%; } /* Animation Classes */ .toast.slide-in { animation: slideIn 0.5s forwards; } .toast.slide-out { animation: slideOut 0.5s forwards; } .toast.fade-in { animation: fadeIn 0.3s forwards; } .toast.fade-out { animation: fadeOut 0.3s forwards; } .toast.bounce-in { animation: bounceIn 0.5s forwards; } .toast.bounce-out { animation: bounceOut 0.5s forwards; } .toast.flip-in { animation: flipIn 0.5s forwards; } .toast.flip-out { animation: flipOut 0.5s forwards; } .toast.zoom-in { animation: zoomIn 0.3s forwards; } .toast.zoom-out { animation: zoomOut 0.3s forwards; } /* Toast Elements */ .toast-progress { position: absolute; bottom: 0; left: 0; height: 4px; background-color: rgba(255, 255, 255, 0.2); width: 100%; border-radius: 0 0 0.5rem 0.5rem; } .toast-progress-bar { height: 100%; transition: width linear; border-radius: 0 0 0 0.5rem; } .toast-close { margin-left: 0.75rem; cursor: pointer; opacity: 0.7; transition: opacity 0.2s; flex-shrink: 0; } .toast-close:hover { opacity: 1; } .toast-icon { margin-right: 0.75rem; font-size: 1.25rem; flex-shrink: 0; margin-top: 0.125rem; } .toast-content { flex: 1; min-width: 0; } .toast-title { font-weight: 600; margin-bottom: 0.25rem; line-height: 1.25; } .toast-message { font-size: 0.875rem; opacity: 0.9; line-height: 1.4; } .toast-actions { margin-top: 0.75rem; display: flex; gap: 0.5rem; flex-wrap: wrap; } .toast-action-btn { padding: 0.375rem 0.75rem; border-radius: 0.375rem; font-size: 0.8125rem; font-weight: 600; cursor: pointer; transition: all 0.2s; border: none; outline: none; } /* Toast Variants */ .toast-success { background-color: #10b981; color: white; } .toast-error { background-color: #ef4444; color: white; } .toast-warning { background-color: #f59e0b; color: white; } .toast-info { background-color: #3b82f6; color: white; } .toast-custom { background-color: #8b5cf6; color: white; } .toast-glass { background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); color: white; } .toast-dark { background-color: #1f2937; color: white; } .toast-light { background-color: white; color: #1f2937; border: 1px solid #e5e7eb; } .toast-material { background-color: #ffffff; color: #1f2937; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1), 0 4px 5px rgba(0, 0, 0, 0.08); border-left: 4px solid; } .toast-material.toast-success { border-left-color: #10b981; } .toast-material.toast-error { border-left-color: #ef4444; } .toast-material.toast-warning { border-left-color: #f59e0b; } .toast-material.toast-info { border-left-color: #3b82f6; } .toast-material.toast-custom { border-left-color: #8b5cf6; } /* Playground Styles */ .playground-container { max-width: 1200px; margin: 0 auto; padding: 2rem 1rem; } .preview-area { min-height: 300px; border: 2px dashed #d1d5db; border-radius: 0.5rem; display: flex; align-items: center; justify-content: center; background-color: #f9fafb; transition: all 0.2s; } .preview-area:hover { border-color: #9ca3af; } .toast-preview { position: relative; margin: 0; max-width: 350px; width: 100%; } /* Tabs */ .tab-content { display: none; } .tab-content.active { display: block; } .tab-btn { position: relative; padding: 0.5rem 1rem; font-weight: 500; color: #6b7280; border: none; background: none; cursor: pointer; transition: all 0.2s; } .tab-btn.active { color: #3b82f6; } .tab-btn.active::after { content: ""; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: #3b82f6; } /* Ripple Effect */ .ripple { position: absolute; border-radius: 50%; background-color: rgba(255, 255, 255, 0.4); transform: scale(0); animation: ripple 0.6s linear; pointer-events: none; } @keyframes ripple { to { transform: scale(4); opacity: 0; } } /* Custom Scrollbar */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } ::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } /* Toast History */ .toast-history-item { padding: 0.75rem; border-radius: 0.375rem; margin-bottom: 0.5rem; cursor: pointer; transition: all 0.2s; border-left: 4px solid; } .toast-history-item:hover { transform: translateY(-2px); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); } /* Responsive Adjustments */ @media (max-width: 768px) { .toast { max-width: 90vw; } .grid-cols-1-md { grid-template-columns: 1fr; } .playground-container { padding: 1rem 0.5rem; } } /* Custom Checkbox */ .custom-checkbox { position: relative; display: inline-block; width: 18px; height: 18px; border: 2px solid #d1d5db; border-radius: 0.25rem; margin-right: 0.5rem; vertical-align: middle; transition: all 0.2s; } .custom-checkbox.checked { background-color: #3b82f6; border-color: #3b82f6; } .custom-checkbox.checked::after { content: ""; position: absolute; left: 5px; top: 1px; width: 5px; height: 10px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); } /* Range Slider */ input[type="range"] { -webkit-appearance: none; width: 100%; height: 6px; border-radius: 3px; background: #e5e7eb; outline: none; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: #3b82f6; cursor: pointer; transition: all 0.2s; } input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.1); } /* Toast Stack Effect */ .toast-stack { position: relative; } .toast-stack .toast { position: absolute; transition: all 0.3s ease; } .toast-stack .toast:nth-child(1) { transform: translateY(0); z-index: 3; } .toast-stack .toast:nth-child(2) { transform: translateY(10px) scale(0.95); opacity: 0.9; z-index: 2; } .toast-stack .toast:nth-child(3) { transform: translateY(20px) scale(0.9); opacity: 0.8; z-index: 1; } </style> </head> <body class="bg-gray-150 min-h-screen"> <!-- Toast Containers --> <div class="toast-container toast-top-right" id="toastTopRight"></div> <div class="toast-container toast-top-left" id="toastTopLeft"></div> <div class="toast-container toast-bottom-right" id="toastBottomRight"></div> <div class="toast-container toast-bottom-left" id="toastBottomLeft"></div> <div class="toast-container toast-center" id="toastCenter"></div> <div class="playground-container"> <!-- Header --> <header class="flex flex-col items-center mb-8"> <div class="flex items-center mb-4"> <h1 class="flex items-center justify-center text-3xl font-bold text-gray-800 mb-6" > <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 14 14" class="mr-2" > <g fill="#aa7942"> <path d="M9.5 4.5A2.5 2.5 0 0 0 7 2H2.5A2.5 2.5 0 0 0 1 6.5v5a1 1 0 0 0 1 1h5.5a1 1 0 0 0 1-1v-5a2.49 2.49 0 0 0 1-2" /> <path fill-rule="evenodd" d="M10.695 2.97a3.99 3.99 0 0 1-.226 3.525H13l.008-.001A2.49 2.49 0 0 0 14 4.5A2.5 2.5 0 0 0 11.5 2h-1.377c.235.294.428.62.572.97M13 7.743h-3V11.5a2.5 2.5 0 0 1-.209 1H12a1 1 0 0 0 1-1z" clip-rule="evenodd" /> </g> </svg> </h1> </div> <p class="text-gray-600 text-center max-w-2xl"> Customize and preview toast notifications in real-time. Adjust settings, see the preview, and copy the toast to use in your projects. </p> </header> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> <!-- Configuration Panel --> <div class="lg:col-span-2"> <div class="bg-white rounded-xl shadow-md overflow-hidden mb-6"> <div class="flex border-b"> <button class="tab-btn active px-4 py-3" data-tab="settings"> <i class="fas fa-cog mr-2"></i>Settings </button> <button class="tab-btn px-4 py-3" data-tab="appearance"> <i class="fas fa-paint-brush mr-2"></i>Appearance </button> <button class="tab-btn px-4 py-3" data-tab="actions"> <i class="fas fa-bolt mr-2"></i>Actions </button> </div> <div class="p-6"> <!-- Settings Tab --> <div class="tab-content active" id="settings"> <div class="space-y-5"> <div> <label class="block text-sm font-medium text-gray-700 mb-2" >Toast Type</label > <select id="toastType" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" > <option value="success">Success</option> <option value="error">Error</option> <option value="warning">Warning</option> <option value="info">Info</option> <option value="custom">Custom</option> <option value="glass">Glass</option> <option value="dark">Dark</option> <option value="light">Light</option> <option value="material">Material Design</option> </select> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2" >Position</label > <select id="toastPosition" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" > <option value="top-right">Top Right</option> <option value="top-left">Top Left</option> <option value="bottom-right">Bottom Right</option> <option value="bottom-left">Bottom Left</option> <option value="center">Center</option> </select> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2" >Animation</label > <select id="toastAnimation" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" > <option value="slide">Slide</option> <option value="fade">Fade</option> <option value="bounce">Bounce</option> <option value="flip">Flip</option> <option value="zoom">Zoom</option> </select> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2"> Duration: <span id="durationValue">5000</span>ms </label> <input type="range" id="toastDuration" min="1000" max="10000" value="5000" step="500" class="w-full" /> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2" >Title</label > <input type="text" id="toastTitle" value="Notification" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" /> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2" >Message</label > <textarea id="toastMessage" rows="3" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" > This is a toast notification message. You can customize it to show important information to your users.</textarea > </div> </div> </div> <!-- Appearance Tab --> <div class="tab-content" id="appearance"> <div class="space-y-5"> <div class="flex items-center"> <div id="showIconCheckbox" class="custom-checkbox checked" onclick="toggleCheckbox(this)" ></div> <input type="checkbox" id="showIcon" checked class="hidden" /> <label class="text-sm font-medium text-gray-700 cursor-pointer" onclick="toggleCheckbox(document.getElementById('showIconCheckbox'))" >Show Icon</label > </div> <div class="flex items-center"> <div id="showCloseCheckbox" class="custom-checkbox checked" onclick="toggleCheckbox(this)" ></div> <input type="checkbox" id="showClose" checked class="hidden" /> <label class="text-sm font-medium text-gray-700 cursor-pointer" onclick="toggleCheckbox(document.getElementById('showCloseCheckbox'))" >Show Close Button</label > </div> <div class="flex items-center"> <div id="showProgressCheckbox" class="custom-checkbox checked" onclick="toggleCheckbox(this)" ></div> <input type="checkbox" id="showProgress" checked class="hidden" /> <label class="text-sm font-medium text-gray-700 cursor-pointer" onclick="toggleCheckbox(document.getElementById('showProgressCheckbox'))" >Show Progress Bar</label > </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2" >Custom Icon</label > <select id="customIcon" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" > <option value="default">Default</option> <option value="check-circle">Check Circle</option> <option value="exclamation-triangle"> Exclamation Triangle </option> <option value="info-circle">Info Circle</option> <option value="times-circle">Times Circle</option> <option value="bell">Bell</option> <option value="star">Star</option> <option value="heart">Heart</option> <option value="thumbs-up">Thumbs Up</option> <option value="rocket">Rocket</option> </select> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2" >Max Toasts Displayed</label > <select id="maxToasts" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" > <option value="1">1</option> <option value="3" selected>3</option> <option value="5">5</option> <option value="10">10</option> <option value="0">Unlimited</option> </select> </div> </div> </div> <!-- Actions Tab --> <div class="tab-content" id="actions"> <div class="space-y-5"> <div> <div class="flex items-center mb-2"> <div id="showAction1Checkbox" class="custom-checkbox" onclick="toggleCheckbox(this)" ></div> <input type="checkbox" id="showAction1" class="hidden" /> <label class="text-sm font-medium text-gray-700 cursor-pointer" onclick="toggleCheckbox(document.getElementById('showAction1Checkbox'))" >Primary Action</label > </div> <div class="ml-8 space-y-2"> <div> <label class="block text-sm font-medium text-gray-700 mb-1" >Text</label > <input type="text" id="action1Text" value="Action" class="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" disabled /> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1" >Style</label > <select id="action1Style" class="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" disabled > <option value="primary">Primary</option> <option value="secondary">Secondary</option> <option value="outline">Outline</option> </select> </div> </div> </div> <div> <div class="flex items-center mb-2"> <div id="showAction2Checkbox" class="custom-checkbox" onclick="toggleCheckbox(this)" ></div> <input type="checkbox" id="showAction2" class="hidden" /> <label class="text-sm font-medium text-gray-700 cursor-pointer" onclick="toggleCheckbox(document.getElementById('showAction2Checkbox'))" >Secondary Action</label > </div> <div class="ml-8 space-y-2"> <div> <label class="block text-sm font-medium text-gray-700 mb-1" >Text</label > <input type="text" id="action2Text" value="Dismiss" class="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" disabled /> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1" >Style</label > <select id="action2Style" class="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" disabled > <option value="primary">Primary</option> <option value="secondary" selected>Secondary</option> <option value="outline">Outline</option> </select> </div> </div> </div> </div> </div> </div> <div class="px-6 pb-6"> <button id="showToastBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-4 rounded-lg transition duration-200 relative overflow-hidden" > <i class="fas fa-bell mr-2"></i> Show Toast Notification </button> </div> </div> <!-- Quick Presets --> <div class="bg-white rounded-xl shadow-md p-6"> <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center" > <i class="fas fa-magic mr-2 text-blue-500"></i> Quick Presets </h2> <div class="grid grid-cols-2 md:grid-cols-3 gap-3"> <button class="toast-preset-btn bg-green-50 text-green-800 p-3 rounded-lg flex items-center justify-center hover:bg-green-100 transition" data-preset="success" > <i class="fas fa-check-circle mr-2"></i> Success </button> <button class="toast-preset-btn bg-red-50 text-red-800 p-3 rounded-lg flex items-center justify-center hover:bg-red-100 transition" data-preset="error" > <i class="fas fa-times-circle mr-2"></i> Error </button> <button class="toast-preset-btn bg-yellow-50 text-yellow-800 p-3 rounded-lg flex items-center justify-center hover:bg-yellow-100 transition" data-preset="warning" > <i class="fas fa-exclamation-triangle mr-2"></i> Warning </button> <button class="toast-preset-btn bg-blue-50 text-blue-800 p-3 rounded-lg flex items-center justify-center hover:bg-blue-100 transition" data-preset="info" > <i class="fas fa-info-circle mr-2"></i> Info </button> <button class="toast-preset-btn bg-purple-50 text-purple-800 p-3 rounded-lg flex items-center justify-center hover:bg-purple-100 transition" data-preset="custom" > <i class="fas fa-star mr-2"></i> Custom </button> <button class="toast-preset-btn bg-gray-50 text-gray-800 p-3 rounded-lg flex items-center justify-center hover:bg-gray-100 transition" data-preset="glass" > <i class="fas fa-glass-whiskey mr-2"></i> Glass </button> </div> </div> </div> <!-- Preview Panel --> <div> <!-- Preview Area --> <div class="bg-white rounded-xl shadow-md p-6 mb-6"> <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center" > <i class="fas fa-eye mr-2 text-blue-500"></i>Preview </h2> <div class="preview-area"> <div id="toastPreview" class="toast-preview toast toast-success toast-slide-in" style="display: none" > <div class="toast-icon"> <i class="fas fa-check-circle"></i> </div> <div class="toast-content"> <div class="toast-title">Notification</div> <div class="toast-message"> This is a toast notification message. </div> </div> <div class="toast-close"> <i class="fas fa-times"></i> </div> <div class="toast-progress"> <div class="toast-progress-bar" style="width: 100%"></div> </div> </div> <p class="text-gray-500 text-center"> <i class="fas fa-arrow-left mr-2"></i> Configure your toast on the left to see a preview here </p> </div> </div> <!-- Generated Code --> <div class="bg-white rounded-xl shadow-md p-6 mb-6"> <div class="flex justify-between items-center mb-4"> <h2 class="text-xl font-semibold text-gray-800 flex items-center"> <img src="./toaster.png" width="50px" hieght="auto" /> </h2> <div class="flex gap-2"> <button id="copyCodeBtn" class="text-sm bg-blue-50 text-blue-600 hover:bg-blue-100 font-medium py-2 px-3 rounded-lg transition duration-200" > <i class="fas fa-copy mr-1"></i> Copy </button> <button id="downloadHtmlBtn" class="text-sm bg-green-50 text-green-600 hover:bg-green-100 font-medium py-2 px-3 rounded-lg transition duration-200" > <i class="fas fa-download mr-1"></i> Download </button> </div> </div> <div class="bg-gray-800 text-gray-100 p-4 rounded-lg font-mono text-sm overflow-x-auto max-h-64 overflow-y-auto" > <pre id="generatedCode"> <!-- Configure your toast to generate code --></pre > </div> </div> <script> // Download functionality document .getElementById("downloadHtmlBtn") .addEventListener("click", () => { // Get the raw HTML content inside the generatedCode div const codeContent = document.getElementById("generatedCode").innerHTML; // Create a Blob with the content, set the MIME type to 'text/html' const blob = new Blob([codeContent], { type: "text/html" }); // Create a URL for the Blob const url = URL.createObjectURL(blob); // Create an anchor element to trigger the download const a = document.createElement("a"); a.href = url; a.download = "generated-code.html"; // Set the file name a.click(); // Trigger the download URL.revokeObjectURL(url); // Revoke the URL once done }); </script> <!-- Toast History --> <div class="bg-white rounded-xl shadow-md p-6"> <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center" > <i class="fas fa-history mr-2 text-blue-500"></i> Toast History </h2> <div id="toastHistory" class="max-h-64 overflow-y-auto"> <p class="text-gray-500 text-sm"> Your recent toasts will appear here </p> </div> </div> </div> </div> </div> <script> // Toast history array const toastHistory = []; const maxHistoryItems = 10; // Toggle checkbox function function toggleCheckbox(element) { const checkboxId = element.id.replace("Checkbox", ""); const checkbox = document.getElementById(checkboxId); checkbox.checked = !checkbox.checked; element.classList.toggle("checked"); // Enable/disable associated inputs if (checkboxId === "showAction1") { document.getElementById("action1Text").disabled = !checkbox.checked; document.getElementById("action1Style").disabled = !checkbox.checked; } else if (checkboxId === "showAction2") { document.getElementById("action2Text").disabled = !checkbox.checked; document.getElementById("action2Style").disabled = !checkbox.checked; } updatePreview(); } document.addEventListener("DOMContentLoaded", function () { // Tab switching const tabBtns = document.querySelectorAll(".tab-btn"); tabBtns.forEach((btn) => { btn.addEventListener("click", function () { const tabId = this.getAttribute("data-tab"); // Remove active class from all tabs and buttons document.querySelectorAll(".tab-content").forEach((tab) => { tab.classList.remove("active"); }); document.querySelectorAll(".tab-btn").forEach((btn) => { btn.classList.remove("active"); }); // Add active class to current tab and button document.getElementById(tabId).classList.add("active"); this.classList.add("active"); }); }); // Duration slider const durationSlider = document.getElementById("toastDuration"); const durationValue = document.getElementById("durationValue"); durationSlider.addEventListener("input", function () { durationValue.textContent = this.value; updatePreview(); }); // Toggle action inputs document .getElementById("showAction1") .addEventListener("change", function () { document.getElementById("action1Text").disabled = !this.checked; document.getElementById("action1Style").disabled = !this.checked; updatePreview(); }); document .getElementById("showAction2") .addEventListener("change", function () { document.getElementById("action2Text").disabled = !this.checked; document.getElementById("action2Style").disabled = !this.checked; updatePreview(); }); // Preset buttons document.querySelectorAll(".toast-preset-btn").forEach((btn) => { btn.addEventListener("click", function () { const preset = this.getAttribute("data-preset"); switch (preset) { case "success": document.getElementById("toastType").value = "success"; document.getElementById("toastTitle").value = "Success!"; document.getElementById("toastMessage").value = "Your action was completed successfully."; document.getElementById("customIcon").value = "check-circle"; break; case "error": document.getElementById("toastType").value = "error"; document.getElementById("toastTitle").value = "Error!"; document.getElementById("toastMessage").value = "Something went wrong. Please try again."; document.getElementById("customIcon").value = "times-circle"; break; case "warning": document.getElementById("toastType").value = "warning"; document.getElementById("toastTitle").value = "Warning!"; document.getElementById("toastMessage").value = "This action cannot be undone."; document.getElementById("customIcon").value = "exclamation-triangle"; break; case "info": document.getElementById("toastType").value = "info"; document.getElementById("toastTitle").value = "Information"; document.getElementById("toastMessage").value = "Here is some information you might find useful."; document.getElementById("customIcon").value = "info-circle"; break; case "custom": document.getElementById("toastType").value = "custom"; document.getElementById("toastTitle").value = "Custom Toast"; document.getElementById("toastMessage").value = "This is a completely customizable toast notification."; document.getElementById("customIcon").value = "star"; break; case "glass": document.getElementById("toastType").value = "glass"; document.getElementById("toastTitle").value = "Glass Effect"; document.getElementById("toastMessage").value = "This toast has a modern glass morphism effect."; document.getElementById("customIcon").value = "bell"; break; } updatePreview(); }); }); // Update preview when any setting changes const inputs = document.querySelectorAll("select, input, textarea"); inputs.forEach((input) => { input.addEventListener("change", updatePreview); input.addEventListener("input", updatePreview); }); // Ripple effect for buttons document .getElementById("showToastBtn") .addEventListener("click", function (e) { createRipple(e, this); showToast(); }); document .getElementById("copyCodeBtn") .addEventListener("click", function (e) { createRipple(e, this); copyCode(); }); function createRipple(event, element) { const rect = element.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; const ripple = document.createElement("span"); ripple.classList.add("ripple"); ripple.style.left = `${x}px`; ripple.style.top = `${y}px`; element.appendChild(ripple); setTimeout(() => { ripple.remove(); }, 600); } // Update the preview toast function updatePreview() { const toastType = document.getElementById("toastType").value; const toastTitle = document.getElementById("toastTitle").value; const toastMessage = document.getElementById("toastMessage").value; const showIcon = document.getElementById("showIcon").checked; const showClose = document.getElementById("showClose").checked; const showProgress = document.getElementById("showProgress").checked; const customIcon = document.getElementById("customIcon").value; const showAction1 = document.getElementById("showAction1").checked; const action1Text = document.getElementById("action1Text").value; const action1Style = document.getElementById("action1Style").value; const showAction2 = document.getElementById("showAction2").checked; const action2Text = document.getElementById("action2Text").value; const action2Style = document.getElementById("action2Style").value; const preview = document.getElementById("toastPreview"); preview.className = `toast-preview toast toast-${toastType}`; // Set icon const icon = preview.querySelector(".toast-icon"); icon.style.display = showIcon ? "block" : "none"; let iconClass = "fas fa-"; switch (toastType) { case "success": iconClass += customIcon === "default" ? "check-circle" : customIcon; break; case "error": iconClass += customIcon === "default" ? "times-circle" : customIcon; break; case "warning": iconClass += customIcon === "default" ? "exclamation-triangle" : customIcon; break; case "info": iconClass += customIcon === "default" ? "info-circle" : customIcon; break; default: iconClass += customIcon === "default" ? "bell" : customIcon; } icon.innerHTML = `<i class="${iconClass}"></i>`; // Set content preview.querySelector(".toast-title").textContent = toastTitle; preview.querySelector(".toast-message").textContent = toastMessage; // Set close button const closeBtn = preview.querySelector(".toast-close"); closeBtn.style.display = showClose ? "block" : "none"; // Set progress bar const progressBar = preview.querySelector(".toast-progress"); progressBar.style.display = showProgress ? "block" : "none"; // Set actions let actionsHTML = ""; if (showAction1 || showAction2) { actionsHTML = '<div class="toast-actions">'; if (showAction1) { let btnClass = ""; switch (action1Style) { case "primary": btnClass = "bg-white text-blue-600 hover:bg-blue-50"; break; case "secondary": btnClass = "bg-blue-600 text-white hover:bg-blue-700"; break; case "outline": btnClass = "bg-transparent border border-white text-white hover:bg-white hover:bg-opacity-10"; break; } actionsHTML += `<button class="toast-action-btn ${btnClass}">${action1Text}</button>`; } if (showAction2) { let btnClass = ""; switch (action2Style) { case "primary": btnClass = "bg-white text-blue-600 hover:bg-blue-50"; break; case "secondary": btnClass = "bg-blue-600 text-white hover:bg-blue-700"; break; case "outline": btnClass = "bg-transparent border border-white text-white hover:bg-white hover:bg-opacity-10"; break; } actionsHTML += `<button class="toast-action-btn ${btnClass}">${action2Text}</button>`; } actionsHTML += "</div>"; } const contentDiv = preview.querySelector(".toast-content"); if (contentDiv.querySelector(".toast-actions")) { contentDiv.removeChild(contentDiv.querySelector(".toast-actions")); } if (actionsHTML) { contentDiv.insertAdjacentHTML("beforeend", actionsHTML); } // Show preview preview.style.display = "flex"; document.querySelector(".preview-area p").style.display = "none"; // Update generated code updateGeneratedCode(); } // Update the generated code display function updateGeneratedCode() { const toastType = document.getElementById("toastType").value; const toastTitle = document.getElementById("toastTitle").value; const toastMessage = document.getElementById("toastMessage").value; const toastPosition = document.getElementById("toastPosition").value; const toastAnimation = document.getElementById("toastAnimation").value; const toastDuration = document.getElementById("toastDuration").value; const showIcon = document.getElementById("showIcon").checked; const showClose = document.getElementById("showClose").checked; const showProgress = document.getElementById("showProgress").checked; const customIcon = document.getElementById("customIcon").value; const showAction1 = document.getElementById("showAction1").checked; const action1Text = document.getElementById("action1Text").value; const action1Style = document.getElementById("action1Style").value; const showAction2 = document.getElementById("showAction2").checked; const action2Text = document.getElementById("action2Text").value; const action2Style = document.getElementById("action2Style").value; const maxToasts = document.getElementById("maxToasts").value; let iconCode = ""; if (showIcon) { let iconClass = "fas fa-"; switch (toastType) { case "success": iconClass += customIcon === "default" ? "check-circle" : customIcon; break; case "error": iconClass += customIcon === "default" ? "times-circle" : customIcon; break; case "warning": iconClass += customIcon === "default" ? "exclamation-triangle" : customIcon; break; case "info": iconClass += customIcon === "default" ? "info-circle" : customIcon; break; default: iconClass += customIcon === "default" ? "bell" : customIcon; } iconCode = `\n <div class="toast-icon">\n <i class="${iconClass}"></i>\n </div>`; } let closeCode = ""; if (showClose) { closeCode = `\n <div class="toast-close">\n <i class="fas fa-times"></i>\n </div>`; } let progressCode = ""; if (showProgress) { progressCode = `\n <div class="toast-progress">\n <div class="toast-progress-bar" style="width: 100%;"></div>\n </div>`; } let actionsCode = ""; if (showAction1 || showAction2) { actionsCode = '\n <div class="toast-actions">'; if (showAction1) { let btnClass = ""; switch (action1Style) { case "primary": btnClass = "bg-white text-blue-600 hover:bg-blue-50"; break; case "secondary": btnClass = "bg-blue-600 text-white hover:bg-blue-700"; break; case "outline": btnClass = "bg-transparent border border-white text-white hover:bg-white hover:bg-opacity-10"; break; } actionsCode += `\n <button class="toast-action-btn ${btnClass}">${action1Text}</button>`; } if (showAction2) { let btnClass = ""; switch (action2Style) { case "primary": btnClass = "bg-white text-blue-600 hover:bg-blue-50"; break; case "secondary": btnClass = "bg-blue-600 text-white hover:bg-blue-700"; break; case "outline": btnClass = "bg-transparent border border-white text-white hover:bg-white hover:bg-opacity-10"; break; } actionsCode += `\n <button class="toast-action-btn ${btnClass}">${action2Text}</button>`; } actionsCode += "\n </div>"; } const positionId = `toast${toastPosition .split("-") .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) .join("")}`; const code = `// Create toast element const toast = document.createElement('div'); toast.className = 'toast toast-${toastType} toast-${toastAnimation}-in'; toast.innerHTML = \`${iconCode} <div class="toast-content"> <div class="toast-title">${toastTitle.replace(/'/g, "\\'")}</div> <div class="toast-message">${toastMessage.replace(/'/g, "\\'")}</div>${actionsCode} </div>${closeCode}${progressCode}\`; // Check max toasts const container = document.getElementById('${positionId}'); ${ maxToasts !== "0" ? `if (container.children.length >= ${maxToasts}) { container.removeChild(container.children[0]); }` : "" } // Add to container container.appendChild(toast); // Auto remove after duration const timeout = setTimeout(() => { toast.classList.remove('toast-${toastAnimation}-in'); toast.classList.add('toast-${toastAnimation}-out'); setTimeout(() => toast.remove(), 500); }, ${toastDuration}); // Close button event ${ showClose ? `toast.querySelector('.toast-close').addEventListener('click', () => { clearTimeout(timeout); toast.classList.remove('toast-${toastAnimation}-in'); toast.classList.add('toast-${toastAnimation}-out'); setTimeout(() => toast.remove(), 500); });` : "" } // Action button events ${ showAction1 ? `toast.querySelector('.toast-actions button:first-child').addEventListener('click', () => { clearTimeout(timeout); toast.classList.remove('toast-${toastAnimation}-in'); toast.classList.add('toast-${toastAnimation}-out'); setTimeout(() => toast.remove(), 500); // Your action logic here console.log('Primary action clicked'); });` : "" } ${ showAction2 ? `toast.querySelector('.toast-actions button:${showAction1 ? "last" : "first"}-child').addEventListener('click', () => { clearTimeout(timeout); toast.classList.remove('toast-${toastAnimation}-in'); toast.classList.add('toast-${toastAnimation}-out'); setTimeout(() => toast.remove(), 500); });` : "" } // Animate progress bar ${ showProgress ? `setTimeout(() => { toast.querySelector('.toast-progress-bar').style.transition = \`width ${toastDuration}ms linear\`; toast.querySelector('.toast-progress-bar').style.width = '0%'; }, 50);` : "" }`; document.getElementById("generatedCode").textContent = code; } // Show actual toast notification function showToast() { const toastType = document.getElementById("toastType").value; const toastTitle = document.getElementById("toastTitle").value; const toastMessage = document.getElementById("toastMessage").value; const toastPosition = document.getElementById("toastPosition").value; const toastAnimation = document.getElementById("toastAnimation").value; const toastDuration = parseInt( document.getElementById("toastDuration").value, ); const showIcon = document.getElementById("showIcon").checked; const showClose = document.getElementById("showClose").checked; const showProgress = document.getElementById("showProgress").checked; const customIcon = document.getElementById("customIcon").value; const showAction1 = document.getElementById("showAction1").checked; const action1Text = document.getElementById("action1Text").value; const action1Style = document.getElementById("action1Style").value; const showAction2 = document.getElementById("showAction2").checked; const action2Text = document.getElementById("action2Text").value; const action2Style = document.getElementById("action2Style").value; const maxToasts = parseInt( document.getElementById("maxToasts").value, ); // Create toast element const toast = document.createElement("div"); toast.className = `toast toast-${toastType} toast-${toastAnimation}-in`; // Build icon HTML let iconHTML = ""; if (showIcon) { let iconClass = "fas fa-"; switch (toastType) { case "success": iconClass += customIcon === "default" ? "check-circle" : customIcon; break; case "error": iconClass += customIcon === "default" ? "times-circle" : customIcon; break; case "warning": iconClass += customIcon === "default" ? "exclamation-triangle" : customIcon; break; case "info": iconClass += customIcon === "default" ? "info-circle" : customIcon; break; default: iconClass += customIcon === "default" ? "bell" : customIcon; } iconHTML = `<div class="toast-icon"> <i class="${iconClass}"></i> </div>`; } // Build close button HTML let closeHTML = ""; if (showClose) { closeHTML = `<div class="toast-close"> <i class="fas fa-times"></i> </div>`; } // Build progress bar HTML let progressHTML = ""; if (showProgress) { progressHTML = `<div class="toast-progress"> <div class="toast-progress-bar" style="width: 100%;"></div> </div>`; } // Build actions HTML let actionsHTML = ""; if (showAction1 || showAction2) { actionsHTML = '<div class="toast-actions">'; if (showAction1) { let btnClass = ""; switch (action1Style) { case "primary": btnClass = "bg-white text-blue-600 hover:bg-blue-50"; break; case "secondary": btnClass = "bg-blue-600 text-white hover:bg-blue-700"; break; case "outline": btnClass = "bg-transparent border border-white text-white hover:bg-white hover:bg-opacity-10"; break; } actionsHTML += `<button class="toast-action-btn ${btnClass}">${action1Text}</button>`; } if (showAction2) { let btnClass = ""; switch (action2Style) { case "primary": btnClass = "bg-white text-blue-600 hover:bg-blue-50"; break; case "secondary": btnClass = "bg-blue-600 text-white hover:bg-blue-700"; break; case "outline": btnClass = "bg-transparent border border-white text-white hover:bg-white hover:bg-opacity-10"; break; } actionsHTML += `<button class="toast-action-btn ${btnClass}">${action2Text}</button>`; } actionsHTML += "</div>"; } // Set toast HTML toast.innerHTML = `${iconHTML} <div class="toast-content"> <div class="toast-title">${toastTitle}</div> <div class="toast-message">${toastMessage}</div> ${actionsHTML} </div> ${closeHTML} ${progressHTML}`; // Add to container const containerId = `toast${toastPosition .split("-") .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) .join("")}`; const container = document.getElementById(containerId); // Check max toasts if (maxToasts > 0 && container.children.length >= maxToasts) { container.removeChild(container.children[0]); } container.appendChild(toast); // Add to history addToHistory(toastType, toastTitle, toastMessage); // Auto remove after duration const timeout = setTimeout(() => { toast.classList.remove(`toast-${toastAnimation}-in`); toast.classList.add(`toast-${toastAnimation}-out`); setTimeout(() => toast.remove(), 500); }, toastDuration); // Add close event if (showClose) { setTimeout(() => { toast .querySelector(".toast-close") .addEventListener("click", function () { clearTimeout(timeout); toast.classList.remove(`toast-${toastAnimation}-in`); toast.classList.add(`toast-${toastAnimation}-out`); setTimeout(() => toast.remove(), 500); }); }, 100); } // Animate progress bar if (showProgress) { setTimeout(() => { const progressBar = toast.querySelector(".toast-progress-bar"); progressBar.style.transition = `width ${toastDuration}ms linear`; progressBar.style.width = "0%"; }, 50); } // Add action button events if (showAction1) { setTimeout(() => { const actionBtn = toast.querySelector( ".toast-actions button:first-child", ); actionBtn.addEventListener("click", function () { clearTimeout(timeout); toast.classList.remove(`toast-${toastAnimation}-in`); toast.classList.add(`toast-${toastAnimation}-out`); setTimeout(() => toast.remove(), 500); // Show confirmation toast const confirmToast = document.createElement("div"); confirmToast.className = `toast toast-success toast-${toastAnimation}-in`; confirmToast.innerHTML = `<div class="toast-icon"> <i class="fas fa-check-circle"></i> </div> <div class="toast-content"> <div class="toast-title">Action Taken</div> <div class="toast-message">You clicked "${action1Text}"</div> </div> <div class="toast-close"> <i class="fas fa-times"></i> </div> <div class="toast-progress"> <div class="toast-progress-bar" style="width: 100%;"></div> </div>`; document.getElementById(containerId).appendChild(confirmToast); setTimeout(() => { confirmToast .querySelector(".toast-close") .addEventListener("click", function () { confirmToast.classList.remove( `toast-${toastAnimation}-in`, ); confirmToast.classList.add(`toast-${toastAnimation}-out`); setTimeout(() => confirmToast.remove(), 500); }); // Animate progress bar const progressBar = confirmToast.querySelector( ".toast-progress-bar", ); progressBar.style.transition = `width 3000ms linear`; progressBar.style.width = "0%"; }, 50); setTimeout(() => { confirmToast.classList.remove(`toast-${toastAnimation}-in`); confirmToast.classList.add(`toast-${toastAnimation}-out`); setTimeout(() => confirmToast.remove(), 500); }, 3000); }); }, 100); } if (showAction2) { setTimeout(() => { const dismissBtn = toast.querySelector( `.toast-actions button:${showAction1 ? "last" : "first"}-child`, ); dismissBtn.addEventListener("click", function () { clearTimeout(timeout); toast.classList.remove(`toast-${toastAnimation}-in`); toast.classList.add(`toast-${toastAnimation}-out`); setTimeout(() => toast.remove(), 500); }); }, 100); } } // Add toast to history function addToHistory(type, title, message) { // Add to beginning of array toastHistory.unshift({ type, title, message, timestamp: new Date(), }); // Keep only last maxHistoryItems if (toastHistory.length > maxHistoryItems) { toastHistory.pop(); } updateHistoryDisplay(); } // Update history display function updateHistoryDisplay() { const historyContainer = document.getElementById("toastHistory"); if (toastHistory.length === 0) { historyContainer.innerHTML = '<p class="text-gray-500 text-sm">Your recent toasts will appear here</p>'; return; } let historyHTML = ""; toastHistory.forEach((toast, index) => { let borderColor = ""; switch (toast.type) { case "success": borderColor = "border-green-500"; break; case "error": borderColor = "border-red-500"; break; case "warning": borderColor = "border-yellow-500"; break; case "info": borderColor = "border-blue-500"; break; default: borderColor = "border-purple-500"; } const timeString = toast.timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", }); historyHTML += `<div class="toast-history-item bg-white ${borderColor} shadow-sm" onclick="recreateToast(${index})"> <div class="flex justify-between items-start"> <div> <div class="font-medium text-gray-800">${toast.title}</div> <div class="text-sm text-gray-600 truncate">${toast.message}</div> </div> <div class="text-xs text-gray-400 ml-2 whitespace-nowrap">${timeString}</div> </div> </div>`; }); historyContainer.innerHTML = historyHTML; } // Copy code to clipboard function copyCode() { const code = document.getElementById("generatedCode").textContent; navigator.clipboard.writeText(code).then(() => { // Show copied notification const toast = document.createElement("div"); toast.className = "toast toast-success toast-slide-in"; toast.innerHTML = `<div class="toast-icon"> <i class="fas fa-check-circle"></i> </div> <div class="toast-content"> <div class="toast-title">Copied!</div> <div class="toast-message">The code has been copied to your clipboard.</div> </div> <div class="toast-close"> <i class="fas fa-times"></i> </div> <div class="toast-progress"> <div class="toast-progress-bar" style="width: 100%;"></div> </div>`; document.getElementById("toastTopRight").appendChild(toast); setTimeout(() => { toast .querySelector(".toast-close") .addEventListener("click", function () { toast.classList.remove("toast-slide-in"); toast.classList.add("toast-slide-out"); setTimeout(() => toast.remove(), 500); }); // Animate progress bar const progressBar = toast.querySelector(".toast-progress-bar"); progressBar.style.transition = "width 3000ms linear"; progressBar.style.width = "0%"; }, 50); setTimeout(() => { toast.classList.remove("toast-slide-in"); toast.classList.add("toast-slide-out"); setTimeout(() => toast.remove(), 500); }, 3000); }); } // Initialize preview updatePreview(); }); // Global function to recreate toast from history function recreateToast(index) { const toast = toastHistory[index]; if (!toast) return; // Set form values document.getElementById("toastType").value = toast.type; document.getElementById("toastTitle").value = toast.title; document.getElementById("toastMessage").value = toast.message; // Update preview updatePreview(); // Show toast button with ripple effect const btn = document.getElementById("showToastBtn"); const rect = btn.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const event = new MouseEvent("click", { view: window, bubbles: true, cancelable: true, clientX: x, clientY: y, }); btn.dispatchEvent(event); } </script> <footer class="bg-transparent text-amber-700 py-4 text-center"> <p><a href="https://JesseJesse.com" target="_blank" class="hover:underline">🍞 JesseJesse.com</a></p> </footer> </body> </html> |