fix(toast): sticky toasts + improve update notification visibility (P5-40)
Add sticky?: boolean to ToastNotif — sticky toasts skip the 4s auto-dismiss timer entirely, staying until the user clicks or manually dismisses. Sticky toasts also use cyan accent/glow (vs orange for messages) and allow the body to wrap rather than truncate, so longer action-oriented copy is fully readable. Update the Tauri update toast to: sticky: true, ⬆ prefix on the title, "Click to install and restart" as explicit call to action. Fixes: auto-dismiss before user noticed it, no visual distinction from a regular message notification. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,13 +32,14 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (toast.sticky) return;
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
dismiss(toast.id);
|
dismiss(toast.id);
|
||||||
}, 4000);
|
}, 4000);
|
||||||
return () => {
|
return () => {
|
||||||
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
||||||
};
|
};
|
||||||
}, [dismiss, toast.id]);
|
}, [dismiss, toast.id, toast.sticky]);
|
||||||
|
|
||||||
const handleCardClick = () => {
|
const handleCardClick = () => {
|
||||||
if (toast.onClick) {
|
if (toast.onClick) {
|
||||||
@@ -58,12 +59,14 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
const cardStyle: CSSProperties = {
|
const cardStyle: CSSProperties = {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
background: 'var(--lt-bg-card)',
|
background: 'var(--lt-bg-card)',
|
||||||
border: '1px solid var(--lt-border-color)',
|
border: toast.sticky
|
||||||
|
? '1px solid var(--lt-accent-cyan-border)'
|
||||||
|
: '1px solid var(--lt-border-color)',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
padding: '12px 14px',
|
padding: '12px 14px',
|
||||||
minWidth: '280px',
|
minWidth: '280px',
|
||||||
maxWidth: '340px',
|
maxWidth: '340px',
|
||||||
boxShadow: 'var(--lt-box-glow-orange)',
|
boxShadow: toast.sticky ? 'var(--lt-box-glow-cyan)' : 'var(--lt-box-glow-orange)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
animation: 'lotusToastIn 0.2s ease-out both',
|
animation: 'lotusToastIn 0.2s ease-out both',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
@@ -100,7 +103,7 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const nameStyle: CSSProperties = {
|
const nameStyle: CSSProperties = {
|
||||||
color: 'var(--lt-accent-orange)',
|
color: toast.sticky ? 'var(--lt-accent-cyan)' : 'var(--lt-accent-orange)',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: '0.85rem',
|
fontSize: '0.85rem',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -127,8 +130,9 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
fontSize: '0.82rem',
|
fontSize: '0.82rem',
|
||||||
margin: '4px 0 2px',
|
margin: '4px 0 2px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
...(toast.sticky
|
||||||
whiteSpace: 'nowrap',
|
? { whiteSpace: 'normal', lineHeight: 1.4 }
|
||||||
|
: { textOverflow: 'ellipsis', whiteSpace: 'nowrap' }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const roomNameStyle: CSSProperties = {
|
const roomNameStyle: CSSProperties = {
|
||||||
|
|||||||
@@ -459,11 +459,12 @@ function TauriUpdateFeature() {
|
|||||||
firedRef.current = status.version;
|
firedRef.current = status.version;
|
||||||
setToast({
|
setToast({
|
||||||
id: `tauri-update-${status.version}`,
|
id: `tauri-update-${status.version}`,
|
||||||
displayName: 'Update Available',
|
displayName: '⬆ Update Available',
|
||||||
body: `Lotus Chat ${status.version} is ready to install.`,
|
body: `Lotus Chat ${status.version} is ready. Click to install and restart.`,
|
||||||
roomName: 'System',
|
roomName: 'System',
|
||||||
roomId: '',
|
roomId: '',
|
||||||
onClick: install,
|
onClick: install,
|
||||||
|
sticky: true,
|
||||||
});
|
});
|
||||||
}, [status, setToast, install]);
|
}, [status, setToast, install]);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export type ToastNotif = {
|
|||||||
roomId: string;
|
roomId: string;
|
||||||
hashPath?: string; // overrides window.location.hash navigation when set
|
hashPath?: string; // overrides window.location.hash navigation when set
|
||||||
onClick?: () => void; // custom click handler; skips hash navigation when set
|
onClick?: () => void; // custom click handler; skips hash navigation when set
|
||||||
|
sticky?: boolean; // when true, does not auto-dismiss — use for action toasts that require a click
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseAtom = atom<ToastNotif[]>([]);
|
const baseAtom = atom<ToastNotif[]>([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user