Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions submissions/examples/toast/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Toast Notification Component

A fixed-position toast notification system with slide-in/slide-out animations, 4 color variants (success, danger, warning, info), dismiss close button, and 4 position options (top-right, top-left, bottom-right, bottom-left). Includes inline JavaScript for dynamic show/dismiss.

## Classes

| Class | Description |
|---|---|
| `ease-toast-container` | Fixed container for stacking toasts |
| `ease-toast-container--top-left` | Top-left position |
| `ease-toast-container--bottom-right` | Bottom-right position |
| `ease-toast-container--bottom-left` | Bottom-left position |
| `ease-toast` | Individual toast notification |
| `ease-toast--success` | Green accent (success) |
| `ease-toast--danger` | Red accent (error/danger) |
| `ease-toast--warning` | Amber accent (warning) |
| `ease-toast--info` | Blue accent (info) |
| `ease-toast-icon` | Icon circle |
| `ease-toast-msg` | Notification message |
| `ease-toast-close` | Dismiss button |
| `ease-toast--exit` | Exit animation state (added via JS) |

## Usage

```html
<div class="ease-toast-container" id="toast-container">
<div class="ease-toast ease-toast--success" role="alert">
<span class="ease-toast-icon">&#10003;</span>
<span class="ease-toast-msg">File uploaded successfully</span>
<button class="ease-toast-close" aria-label="Dismiss">&times;</button>
</div>
</div>
```

See `demo.html` for the full JS setup (dynamic creation, auto-dismiss after 4s, close button handler).

## Why it fits EaseMotion CSS

Smooth slide-in entrance animation (`ease-kf-toast-in`) and fade-out exit animation, 4 color variants with accent borders and icon backgrounds, position modifiers for all 4 corners, and stacking container. Respects `prefers-reduced-motion`.
86 changes: 86 additions & 0 deletions submissions/examples/toast/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Toast Notifications β€” EaseMotion CSS</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>

<div style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; padding: 2rem;">
<button class="toast-btn" data-variant="success">Show Success</button>
<button class="toast-btn" data-variant="danger">Show Danger</button>
<button class="toast-btn" data-variant="warning">Show Warning</button>
<button class="toast-btn" data-variant="info">Show Info</button>
</div>

<div class="ease-toast-container" id="toast-container">
<div class="ease-toast ease-toast--success" role="alert">
<span class="ease-toast-icon">&#10003;</span>
<span class="ease-toast-msg">File uploaded successfully</span>
<button class="ease-toast-close" aria-label="Dismiss">&times;</button>
</div>
<div class="ease-toast ease-toast--danger" role="alert">
<span class="ease-toast-icon">&#10007;</span>
<span class="ease-toast-msg">Unable to save changes</span>
<button class="ease-toast-close" aria-label="Dismiss">&times;</button>
</div>
<div class="ease-toast ease-toast--warning" role="alert">
<span class="ease-toast-icon">&#9888;</span>
<span class="ease-toast-msg">Session expires in 5 minutes</span>
<button class="ease-toast-close" aria-label="Dismiss">&times;</button>
</div>
<div class="ease-toast ease-toast--info" role="alert">
<span class="ease-toast-icon">&#8505;</span>
<span class="ease-toast-msg">New update available</span>
<button class="ease-toast-close" aria-label="Dismiss">&times;</button>
</div>
</div>

<script>
const container = document.getElementById('toast-container');

document.querySelectorAll('.toast-btn').forEach(btn => {
btn.addEventListener('click', () => {
const variant = btn.dataset.variant;
const messages = {
success: 'File uploaded successfully',
danger: 'Unable to save changes',
warning: 'Session expires in 5 minutes',
info: 'New update available'
};
const icons = {
success: '&#10003;',
danger: '&#10007;',
warning: '&#9888;',
info: '&#8505;'
};

const toast = document.createElement('div');
toast.className = `ease-toast ease-toast--${variant}`;
toast.setAttribute('role', 'alert');
toast.innerHTML =
`<span class="ease-toast-icon">${icons[variant]}</span>` +
`<span class="ease-toast-msg">${messages[variant]}</span>` +
`<button class="ease-toast-close" aria-label="Dismiss">&times;</button>`;

container.appendChild(toast);

toast.querySelector('.ease-toast-close').addEventListener('click', () => {
dismissToast(toast);
});

setTimeout(() => dismissToast(toast), 4000);
});
});

function dismissToast(toast) {
if (toast.classList.contains('ease-toast--exit')) return;
toast.classList.add('ease-toast--exit');
setTimeout(() => toast.remove(), 300);
}
</script>

</body>
</html>
195 changes: 195 additions & 0 deletions submissions/examples/toast/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/* ============================================================
EaseMotion CSS β€” Toast Notification Component
Issue #1184
============================================================ */

/* ── Container ────────────────────────────────────────────── */

.ease-toast-container {
position: fixed;
top: var(--ease-space-4, 1rem);
right: var(--ease-space-4, 1rem);
z-index: 9999;
display: flex;
flex-direction: column;
gap: var(--ease-space-3, 0.75rem);
pointer-events: none;
max-width: 420px;
width: calc(100% - 2rem);
}

/* ── Position variants ───────────────────────────────────── */

.ease-toast-container--top-left {
top: var(--ease-space-4, 1rem);
left: var(--ease-space-4, 1rem);
right: auto;
}

.ease-toast-container--bottom-right {
top: auto;
bottom: var(--ease-space-4, 1rem);
right: var(--ease-space-4, 1rem);
}

.ease-toast-container--bottom-left {
top: auto;
bottom: var(--ease-space-4, 1rem);
left: var(--ease-space-4, 1rem);
right: auto;
}

/* ── Individual toast ────────────────────────────────────── */

.ease-toast {
display: flex;
align-items: center;
gap: var(--ease-space-3, 0.75rem);
padding: 14px 18px;
background: #1a1a24;
border: 1.5px solid rgba(255, 255, 255, 0.08);
border-radius: var(--ease-radius-md, 0.625rem);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
pointer-events: auto;
min-width: 260px;
animation: ease-kf-toast-in 0.3s cubic-bezier(0.16, 1, 0.3, 1) both;
transition: opacity 0.3s ease, transform 0.3s ease;
}

/* ── Color variant: left accent border ────────────────────── */

.ease-toast--success {
border-left: 4px solid #22c55e;
}

.ease-toast--danger {
border-left: 4px solid #ef4444;
}

.ease-toast--warning {
border-left: 4px solid #f59e0b;
}

.ease-toast--info {
border-left: 4px solid #3b82f6;
}

/* ── Icon ─────────────────────────────────────────────────── */

.ease-toast-icon {
flex-shrink: 0;
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: var(--ease-text-xs, 0.75rem);
font-weight: 700;
line-height: 1;
}

.ease-toast--success .ease-toast-icon {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
}

.ease-toast--danger .ease-toast-icon {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}

.ease-toast--warning .ease-toast-icon {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
}

.ease-toast--info .ease-toast-icon {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}

/* ── Message ──────────────────────────────────────────────── */

.ease-toast-msg {
flex: 1;
font-size: var(--ease-text-sm, 0.875rem);
color: rgba(255, 255, 255, 0.85);
line-height: 1.4;
}

/* ── Close button ─────────────────────────────────────────── */

.ease-toast-close {
margin-left: auto;
flex-shrink: 0;
background: none;
border: none;
cursor: pointer;
padding: 2px 6px;
font-size: 1.25rem;
line-height: 1;
color: rgba(255, 255, 255, 0.3);
border-radius: var(--ease-radius-sm, 4px);
transition:
color var(--ease-speed-fast, 150ms) cubic-bezier(0.4, 0, 0.2, 1),
background var(--ease-speed-fast, 150ms) cubic-bezier(0.4, 0, 0.2, 1);
}

.ease-toast-close:hover {
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.7);
}

/* ── Exit animation ──────────────────────────────────────── */

.ease-toast--exit {
opacity: 0;
transform: translateX(100%);
}

.ease-toast-container--top-left .ease-toast--exit,
.ease-toast-container--bottom-left .ease-toast--exit {
transform: translateX(-100%);
}

/* ── Keyframes ────────────────────────────────────────────── */

@keyframes ease-kf-toast-in {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}

.ease-toast-container--top-left .ease-toast,
.ease-toast-container--bottom-left .ease-toast {
animation-name: ease-kf-toast-in-left;
}

@keyframes ease-kf-toast-in-left {
from {
opacity: 0;
transform: translateX(-100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}

/* ── Reduced motion ──────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
.ease-toast {
animation: none;
}
.ease-toast--exit {
opacity: 1;
transform: none;
}
}
Loading