dashboard: theme all remaining dialogs (replace native confirm/alert/prompt)
Follow-up to the themed-modal component: route every remaining native browser dialog through modal.js so nothing falls back to the OS chrome. - modal.js: add themedPrompt (input dialog) + themedToast (non-blocking transient notification, info/error/ok) alongside openDialog/themedConfirm. - Migrate call sites: bindAsyncForms confirm/prompt/alerts (common.js), the answer-validation alert (call.js), the M0V3 reparent confirm + action toasts (tabs.js), and the schedule form/cancel/fire confirms + validation and error alerts (schedules.js). - UX: blocking modal for confirms/prompts; non-blocking toast for transient errors + validation. Destructive confirms keep the danger styling. - CSS for the toast stack + prompt input. common.js <-> modal.js is a safe deferred import cycle (usage is call-time only); esbuild bundles it clean.
This commit is contained in:
parent
ff3317eb20
commit
7c2de690ec
6 changed files with 141 additions and 32 deletions
|
|
@ -123,3 +123,60 @@ export function themedConfirm(opts = {}) {
|
|||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
// themedPrompt({ title, message, label, placeholder, value, confirmLabel, cancelLabel })
|
||||
// → Promise<string | null>. Themed replacement for window.prompt(): an input
|
||||
// dialog that resolves to the entered string on confirm, or null on cancel.
|
||||
export function themedPrompt(opts = {}) {
|
||||
const {
|
||||
title = '', message = '', label = '', placeholder = '', value = '',
|
||||
confirmLabel = 'ok', cancelLabel = 'cancel',
|
||||
} = opts;
|
||||
const input = el('input', { type: 'text', class: 'tc-input', placeholder });
|
||||
if (value) input.value = value;
|
||||
const content = el('div', { class: 'tc-promptfield' },
|
||||
label ? el('label', { class: 'tc-promptlabel' }, label) : null,
|
||||
input);
|
||||
const result = openDialog({
|
||||
title,
|
||||
message,
|
||||
content,
|
||||
buttons: [
|
||||
{ label: cancelLabel, value: '__cancel__', class: 'tc-cancel' },
|
||||
{ label: confirmLabel, value: '__ok__', class: 'tc-confirm', autofocus: true },
|
||||
],
|
||||
}).then((v) => (v === '__ok__' ? input.value : null));
|
||||
// Prefer focusing the field over the OK button once the dialog has mounted.
|
||||
setTimeout(() => input.focus(), 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
// themedToast(message, { type, duration }) — non-blocking transient
|
||||
// notification; a lighter alternative to a modal for feedback that needs no
|
||||
// decision (errors, validation, status). Stacks in a fixed top-right
|
||||
// container, auto-dismisses after `duration` ms (errors linger longer), and
|
||||
// can be clicked to dismiss early. `type` ∈ {'info','error','ok'}.
|
||||
export function themedToast(message, opts = {}) {
|
||||
const { type = 'info', duration } = opts;
|
||||
const ms = duration != null ? duration : (type === 'error' ? 8000 : 4000);
|
||||
let container = document.getElementById('tc-toasts');
|
||||
if (!container) {
|
||||
container = el('div', { id: 'tc-toasts', class: 'tc-toasts' });
|
||||
document.body.append(container);
|
||||
}
|
||||
const toast = el('div', {
|
||||
class: 'tc-toast tc-toast-' + type,
|
||||
role: type === 'error' ? 'alert' : 'status',
|
||||
}, message);
|
||||
let removed = false;
|
||||
const remove = () => {
|
||||
if (removed) return;
|
||||
removed = true;
|
||||
toast.classList.add('tc-toast-out');
|
||||
setTimeout(() => toast.remove(), 200);
|
||||
};
|
||||
toast.addEventListener('click', remove);
|
||||
container.append(toast);
|
||||
if (ms > 0) setTimeout(remove, ms);
|
||||
return toast;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue