feat(ext): shared toast notification system
This commit is contained in:
@@ -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');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
26
extension/src/shared/toast.ts
Normal file
26
extension/src/shared/toast.ts
Normal 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);
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
|||||||
Reference in New Issue
Block a user