feat(ext): shared toast notification system

This commit is contained in:
adlee-was-taken
2026-05-03 21:11:24 -04:00
parent ad6e4a2cd9
commit 1ec8965910
4 changed files with 106 additions and 0 deletions

View File

@@ -3,6 +3,7 @@
/// to the detail view. /// to the detail view.
import { getState, setState, sendMessage, navigate, escapeHtml, openVaultTab } from '../../shared/state'; import { getState, setState, sendMessage, navigate, escapeHtml, openVaultTab } from '../../shared/state';
import { showToast } from '../../shared/toast';
import { import {
GLYPH_VAULT_TAB, GLYPH_VAULT_TAB,
GLYPH_DEVICES, GLYPH_LOCK, GLYPH_DEVICES, GLYPH_LOCK,
@@ -130,11 +131,14 @@ export function renderItemList(app: HTMLElement): void {
if (listResp.ok) { if (listResp.ok) {
const data = listResp.data as { items: Array<[ItemId, ManifestEntry]> }; const data = listResp.data as { items: Array<[ItemId, ManifestEntry]> };
setState({ entries: data.items, loading: false }); setState({ entries: data.items, loading: false });
showToast('Synced', 'success');
return; return;
} }
setState({ loading: false, error: listResp.error }); setState({ loading: false, error: listResp.error });
showToast(listResp.error ?? 'Sync failed', 'error');
} else { } else {
setState({ loading: false, error: resp.error }); setState({ loading: false, error: resp.error });
showToast(resp.error ?? 'Sync failed', 'error');
} }
}); });

View File

@@ -1660,4 +1660,42 @@ textarea {
.type-card__icon { font-size: 20px; margin-bottom: 4px; } .type-card__icon { font-size: 20px; margin-bottom: 4px; }
.type-card__label { font-size: 12px; font-weight: 600; } .type-card__label { font-size: 12px; font-weight: 600; }
/* Toast notifications */
.relicario-toast-container {
position: fixed;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
gap: 6px;
pointer-events: none;
z-index: 9999;
}
.vault-shell .relicario-toast-container {
left: auto;
right: 24px;
transform: none;
}
.relicario-toast {
padding: 8px 16px;
border-radius: 6px;
font-size: 12px;
opacity: 0;
transform: translateY(8px);
transition: opacity 0.2s, transform 0.2s;
pointer-events: none;
}
.relicario-toast--visible {
opacity: 1;
transform: translateY(0);
}
.relicario-toast--success { background: #1f4a24; color: #aff0b5; border: 1px solid #238636; }
.relicario-toast--error { background: #4a1f1f; color: #f0afaf; border: 1px solid #ab2b20; }
.relicario-toast--info { background: #1f2d4a; color: #afc8f0; border: 1px solid #1f6feb; }
.type-card__desc { font-size: 10px; color: var(--text-muted, #8b949e); margin-top: 2px; } .type-card__desc { font-size: 10px; color: var(--text-muted, #8b949e); margin-top: 2px; }

View File

@@ -0,0 +1,26 @@
export function showToast(
message: string,
type: 'success' | 'error' | 'info' = 'info',
durationMs = 2500,
): void {
let container = document.querySelector<HTMLElement>('.relicario-toast-container');
if (!container) {
container = document.createElement('div');
container.className = 'relicario-toast-container';
document.body.appendChild(container);
}
const toast = document.createElement('div');
toast.className = `relicario-toast relicario-toast--${type}`;
toast.textContent = message;
container.appendChild(toast);
requestAnimationFrame(() => {
requestAnimationFrame(() => toast.classList.add('relicario-toast--visible'));
});
setTimeout(() => {
toast.classList.remove('relicario-toast--visible');
toast.addEventListener('transitionend', () => toast.remove(), { once: true });
}, durationMs);
}

View File

@@ -1719,3 +1719,41 @@ textarea {
background: linear-gradient(to top, rgba(17, 22, 30, 0.7), transparent); background: linear-gradient(to top, rgba(17, 22, 30, 0.7), transparent);
pointer-events: none; pointer-events: none;
} }
/* Toast notifications */
.relicario-toast-container {
position: fixed;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
gap: 6px;
pointer-events: none;
z-index: 9999;
}
.vault-shell .relicario-toast-container {
left: auto;
right: 24px;
transform: none;
}
.relicario-toast {
padding: 8px 16px;
border-radius: 6px;
font-size: 12px;
opacity: 0;
transform: translateY(8px);
transition: opacity 0.2s, transform 0.2s;
pointer-events: none;
}
.relicario-toast--visible {
opacity: 1;
transform: translateY(0);
}
.relicario-toast--success { background: #1f4a24; color: #aff0b5; border: 1px solid #238636; }
.relicario-toast--error { background: #4a1f1f; color: #f0afaf; border: 1px solid #ab2b20; }
.relicario-toast--info { background: #1f2d4a; color: #afc8f0; border: 1px solid #1f6feb; }