likwid/frontend/src/components/ui/FeedbackHost.astro

264 lines
7.8 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div id="likwid-toasts" class="ui-toasts" aria-live="polite" aria-atomic="true"></div>
<dialog id="likwid-dialog" class="ui-dialog">
<form method="dialog" class="ui-dialog-form">
<div class="ui-dialog-head">
<h3 id="likwid-dialog-title" class="ui-dialog-title"></h3>
<p id="likwid-dialog-message" class="ui-dialog-message"></p>
</div>
<div id="likwid-dialog-input-wrap" class="ui-dialog-input" style="display: none;">
<label class="ui-label" for="likwid-dialog-textarea" id="likwid-dialog-label"></label>
<textarea id="likwid-dialog-textarea" class="ui-textarea" rows="4"></textarea>
</div>
<div class="ui-dialog-actions">
<button id="likwid-dialog-cancel" class="ui-btn ui-btn-secondary" value="cancel" type="submit">Cancel</button>
<button id="likwid-dialog-confirm" class="ui-btn ui-btn-primary" value="confirm" type="submit">Confirm</button>
</div>
</form>
</dialog>
<script is:inline>
(function() {
const toastsEl = document.getElementById('likwid-toasts');
const dialogEl = document.getElementById('likwid-dialog');
const dialogTitleEl = document.getElementById('likwid-dialog-title');
const dialogMessageEl = document.getElementById('likwid-dialog-message');
const dialogInputWrapEl = document.getElementById('likwid-dialog-input-wrap');
const dialogLabelEl = document.getElementById('likwid-dialog-label');
const dialogTextareaEl = document.getElementById('likwid-dialog-textarea');
const dialogConfirmEl = document.getElementById('likwid-dialog-confirm');
const dialogCancelEl = document.getElementById('likwid-dialog-cancel');
function toast(type, message, opts) {
if (!toastsEl) return;
const options = opts || {};
const id = 't_' + Math.random().toString(16).slice(2);
const el = document.createElement('div');
el.className = 'ui-toast ui-toast-' + (type || 'info');
el.setAttribute('role', type === 'error' ? 'alert' : 'status');
el.dataset.toastId = id;
const text = document.createElement('div');
text.className = 'ui-toast-text';
text.textContent = String(message || '');
const close = document.createElement('button');
close.type = 'button';
close.className = 'ui-toast-close';
close.setAttribute('aria-label', 'Dismiss notification');
close.textContent = '×';
close.addEventListener('click', () => {
el.remove();
});
el.appendChild(text);
el.appendChild(close);
toastsEl.appendChild(el);
const timeoutMs = typeof options.timeoutMs === 'number' ? options.timeoutMs : 4500;
if (timeoutMs > 0) {
window.setTimeout(() => {
el.remove();
}, timeoutMs);
}
}
function openDialog(mode, opts) {
const options = opts || {};
if (!dialogEl || !dialogTitleEl || !dialogMessageEl || !dialogConfirmEl || !dialogCancelEl) {
return Promise.resolve(mode === 'prompt' ? null : false);
}
if (typeof dialogEl.showModal !== 'function') {
if (mode === 'prompt') {
return Promise.resolve(window.prompt(options.message || options.title || '', ''));
}
return Promise.resolve(window.confirm(options.message || options.title || 'Confirm?'));
}
dialogTitleEl.textContent = String(options.title || '');
dialogMessageEl.textContent = String(options.message || '');
const isPrompt = mode === 'prompt';
if (dialogInputWrapEl && dialogTextareaEl && dialogLabelEl) {
dialogInputWrapEl.style.display = isPrompt ? 'block' : 'none';
dialogLabelEl.textContent = String(options.label || '');
dialogTextareaEl.value = String(options.defaultValue || '');
dialogTextareaEl.placeholder = String(options.placeholder || '');
}
dialogConfirmEl.textContent = String(options.confirmText || (isPrompt ? 'Submit' : 'Confirm'));
dialogCancelEl.textContent = String(options.cancelText || 'Cancel');
return new Promise((resolve) => {
function cleanup() {
dialogEl.removeEventListener('close', onClose);
}
function onClose() {
const rv = dialogEl.returnValue;
if (mode === 'prompt') {
if (rv !== 'confirm') {
cleanup();
resolve(null);
return;
}
const v = dialogTextareaEl ? dialogTextareaEl.value : '';
cleanup();
resolve(v);
return;
}
cleanup();
resolve(rv === 'confirm');
}
dialogEl.addEventListener('close', onClose);
dialogEl.showModal();
if (mode === 'prompt' && dialogTextareaEl) {
dialogTextareaEl.focus();
} else {
dialogConfirmEl.focus();
}
});
}
window.likwidUi = window.likwidUi || {};
window.likwidUi.toast = toast;
window.likwidUi.confirm = function(opts) { return openDialog('confirm', opts); };
window.likwidUi.prompt = function(opts) { return openDialog('prompt', opts); };
})();
</script>
<style>
.ui-toasts {
position: fixed;
top: 1rem;
right: 1rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
z-index: 9999;
width: min(420px, calc(100vw - 2rem));
pointer-events: none;
}
.ui-toast {
pointer-events: auto;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.75rem;
padding: 0.875rem 1rem;
border-radius: 12px;
border: 1px solid var(--color-border);
background: color-mix(in srgb, var(--color-surface) 92%, transparent);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.22);
}
.ui-toast-text {
color: var(--color-text);
font-size: 0.9375rem;
line-height: 1.35;
white-space: pre-wrap;
word-break: break-word;
}
.ui-toast-close {
appearance: none;
background: transparent;
border: 0;
color: var(--color-text-muted);
font-size: 1.25rem;
line-height: 1;
cursor: pointer;
padding: 0;
margin: -2px 0 0 0;
}
.ui-toast-close:hover {
color: var(--color-text);
}
.ui-toast-success {
border-color: color-mix(in srgb, var(--color-success) 45%, var(--color-border));
background: color-mix(in srgb, var(--color-success-muted) 65%, var(--color-surface));
}
.ui-toast-error {
border-color: color-mix(in srgb, var(--color-error) 45%, var(--color-border));
background: color-mix(in srgb, var(--color-error-muted) 65%, var(--color-surface));
}
.ui-toast-info {
border-color: color-mix(in srgb, var(--color-info) 45%, var(--color-border));
background: color-mix(in srgb, var(--color-info-muted) 65%, var(--color-surface));
}
.ui-dialog {
border: 1px solid var(--color-border);
border-radius: 14px;
padding: 0;
background: var(--color-surface);
color: var(--color-text);
width: min(560px, calc(100vw - 2rem));
}
.ui-dialog::backdrop {
background: var(--color-overlay);
}
.ui-dialog-form {
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.ui-dialog-title {
margin: 0;
font-size: 1.05rem;
}
.ui-dialog-message {
margin: 0.25rem 0 0 0;
color: var(--color-text-muted);
font-size: 0.9375rem;
line-height: 1.4;
}
.ui-dialog-input {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.ui-label {
font-weight: 600;
font-size: 0.9375rem;
}
.ui-textarea {
width: 100%;
border-radius: 12px;
border: 1px solid var(--color-border);
background: var(--color-field-bg);
color: var(--color-text);
padding: 0.75rem;
font-size: 0.9375rem;
resize: vertical;
min-height: 88px;
}
.ui-dialog-actions {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
}
</style>