feat(web): sleep timer upgrades — undo toast, chain mode, clock stop, wind-down dim (plan §17)
- Undo: a genuine sleep fire now toasts with a Resume action (toast.ts grows an optional action button; acting dismisses). - Chain: 'End of chapter, then 15 min' — leaving the chapter starts a 15-min countdown instead of fading (SleepState.followUpMinutes). - Clock: the popover gains a time input + Arm — armClockTime resolves HH:MM to its next occurrence and reuses the minutes machinery. - Dim: the ticker eases a pointer-events-none overlay toward 35% black across a countdown's final 30s; stop/finish resets it. (Fade-out and countdown labels already existed — §3 built them.)
This commit is contained in:
1 parent
4769e832c4
commit
f8de17723a
6 files changed
+218
-21
No files matched your search
+24
-3
@@ -48,15 +48,23 @@ const getToastConfig = (type: ToastType) => {
|
||||
};
|
||||
|
||||
// Create a toast element
|
||||
const createToastElement = (message: string, type: ToastType): HTMLElement => {
|
||||
const createToastElement = (
|
||||
message: string,
|
||||
type: ToastType,
|
||||
action?: { label: string; onClick: () => void },
|
||||
): HTMLElement => {
|
||||
const toast = document.createElement("div");
|
||||
const config = getToastConfig(type);
|
||||
|
||||
toast.className = `${config.bgClass} text-white p-4 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px] max-w-[500px] text-sm leading-relaxed pointer-events-auto opacity-0 -translate-y-5 transition-all duration-300 border border-white/10`;
|
||||
|
||||
const actionHtml = action
|
||||
? `<button class="toast-action bg-transparent border border-white/40 text-white cursor-pointer text-xs font-semibold px-2.5 py-1 rounded-md whitespace-nowrap flex-shrink-0 hover:bg-white/10 transition-colors">${toastEscapeHtml(action.label)}</button>`
|
||||
: "";
|
||||
toast.innerHTML = `
|
||||
<span class="text-xl flex-shrink-0">${config.icon}</span>
|
||||
<span class="flex-1 break-words">${toastEscapeHtml(message)}</span>
|
||||
${actionHtml}
|
||||
<button class="toast-close bg-transparent border-0 text-white cursor-pointer text-lg p-0 w-5 h-5 flex items-center justify-center opacity-70 hover:opacity-100 flex-shrink-0 transition-opacity">
|
||||
×
|
||||
</button>
|
||||
@@ -68,6 +76,16 @@ const createToastElement = (message: string, type: ToastType): HTMLElement => {
|
||||
closeBtn.onclick = () => removeToast(toast);
|
||||
}
|
||||
|
||||
// Action button: run the handler and dismiss (an action consumes the
|
||||
// toast — leaving it up after acting invites double-fires).
|
||||
const actionBtn = toast.querySelector(".toast-action") as HTMLElement | null;
|
||||
if (actionBtn && action) {
|
||||
actionBtn.onclick = () => {
|
||||
removeToast(toast);
|
||||
action.onClick();
|
||||
};
|
||||
}
|
||||
|
||||
return toast;
|
||||
};
|
||||
|
||||
@@ -90,14 +108,17 @@ const removeToast = (toast: HTMLElement): void => {
|
||||
}, 300);
|
||||
};
|
||||
|
||||
// Show toast notification
|
||||
// Show toast notification. `action` (optional): a labeled button for
|
||||
// one-click follow-ups (e.g. the sleep timer's "Resume"); acting
|
||||
// dismisses the toast.
|
||||
const showToast = (
|
||||
message: string,
|
||||
type: ToastType,
|
||||
duration: number = TOAST_DEFAULT_DURATION,
|
||||
action?: { label: string; onClick: () => void },
|
||||
): void => {
|
||||
const container = createToastContainer();
|
||||
const toast = createToastElement(message, type);
|
||||
const toast = createToastElement(message, type, action);
|
||||
container.appendChild(toast);
|
||||
animateToastIn(toast);
|
||||
|
||||
|
||||
Reference in new issue
Block a user