fix(ui): toast cards render on stock themes; gate TDS glow (native-cinny audit 4/N)
LotusToastContainer was styled entirely with --lt-* CSS vars but rendered unconditionally (not gated on lotusTerminal). Those vars only exist inside the Lotus Terminal theme's scoped block with no global fallback, so in-app toast notifications rendered with undefined background/border/colors on every stock Cinny theme. Now the card uses folds tokens (color.Surface.*/Primary.*, config.radii/space/borderWidth, color.Other.Shadow) by default, keeping the TDS --lt-* glow/accents only when lotusTerminal is active. The raw <button> dismiss control is now a folds IconButton. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
import React, { useEffect, useRef, CSSProperties } from 'react';
|
import React, { useEffect, useRef, CSSProperties } from 'react';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
|
import { color, config, Icon, IconButton, Icons } from 'folds';
|
||||||
import { toastQueueAtom, dismissToastAtom, ToastNotif } from '../../state/toast';
|
import { toastQueueAtom, dismissToastAtom, ToastNotif } from '../../state/toast';
|
||||||
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
|
import { settingsAtom } from '../../state/settings';
|
||||||
|
|
||||||
// Inject the keyframe animation once
|
// Inject the keyframe animation once
|
||||||
const STYLE_ID = 'lotus-toast-keyframes';
|
const STYLE_ID = 'lotus-toast-keyframes';
|
||||||
@@ -29,6 +32,10 @@ type ToastCardProps = {
|
|||||||
|
|
||||||
function ToastCard({ toast }: ToastCardProps) {
|
function ToastCard({ toast }: ToastCardProps) {
|
||||||
const dismiss = useSetAtom(dismissToastAtom);
|
const dismiss = useSetAtom(dismissToastAtom);
|
||||||
|
// Lotus Terminal (TDS) gets its bespoke glow/accents; every other theme uses
|
||||||
|
// folds tokens so toasts render correctly on stock Cinny themes (the --lt-*
|
||||||
|
// vars only exist while Terminal mode is active).
|
||||||
|
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
|
||||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -56,17 +63,29 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
dismiss(toast.id);
|
dismiss(toast.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const accent = toast.sticky ? color.Primary.Main : color.Surface.OnContainer;
|
||||||
|
|
||||||
const cardStyle: CSSProperties = {
|
const cardStyle: CSSProperties = {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
background: 'var(--lt-bg-card)',
|
background: lotusTerminal ? 'var(--lt-bg-card)' : color.Surface.Container,
|
||||||
border: toast.sticky
|
border: `${config.borderWidth.B300} solid ${
|
||||||
? '1px solid var(--lt-accent-cyan-border)'
|
lotusTerminal
|
||||||
: '1px solid var(--lt-border-color)',
|
? toast.sticky
|
||||||
borderRadius: '12px',
|
? 'var(--lt-accent-cyan-border)'
|
||||||
padding: '12px 14px',
|
: 'var(--lt-border-color)'
|
||||||
|
: toast.sticky
|
||||||
|
? color.Primary.Main
|
||||||
|
: color.Surface.ContainerLine
|
||||||
|
}`,
|
||||||
|
borderRadius: config.radii.R400,
|
||||||
|
padding: `${config.space.S300} ${config.space.S400}`,
|
||||||
minWidth: '280px',
|
minWidth: '280px',
|
||||||
maxWidth: '340px',
|
maxWidth: '340px',
|
||||||
boxShadow: toast.sticky ? 'var(--lt-box-glow-cyan)' : 'var(--lt-box-glow-orange)',
|
boxShadow: lotusTerminal
|
||||||
|
? toast.sticky
|
||||||
|
? 'var(--lt-box-glow-cyan)'
|
||||||
|
: 'var(--lt-box-glow-orange)'
|
||||||
|
: `0 8px 24px ${color.Other.Shadow}`,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
animation: 'lotusToastIn 0.2s ease-out both',
|
animation: 'lotusToastIn 0.2s ease-out both',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
@@ -75,8 +94,8 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
const rowStyle: CSSProperties = {
|
const rowStyle: CSSProperties = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '8px',
|
gap: config.space.S200,
|
||||||
marginRight: '20px',
|
marginRight: config.space.S500,
|
||||||
};
|
};
|
||||||
|
|
||||||
const avatarStyle: CSSProperties = {
|
const avatarStyle: CSSProperties = {
|
||||||
@@ -91,19 +110,25 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
width: '24px',
|
width: '24px',
|
||||||
height: '24px',
|
height: '24px',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
background: 'var(--lt-accent-orange-dim)',
|
background: lotusTerminal ? 'var(--lt-accent-orange-dim)' : color.Primary.Container,
|
||||||
border: '1px solid var(--lt-accent-orange-border)',
|
border: `${config.borderWidth.B300} solid ${
|
||||||
|
lotusTerminal ? 'var(--lt-accent-orange-border)' : color.Primary.ContainerLine
|
||||||
|
}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: 'var(--lt-accent-orange)',
|
color: lotusTerminal ? 'var(--lt-accent-orange)' : color.Primary.OnContainer,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nameStyle: CSSProperties = {
|
const nameStyle: CSSProperties = {
|
||||||
color: toast.sticky ? 'var(--lt-accent-cyan)' : 'var(--lt-accent-orange)',
|
color: lotusTerminal
|
||||||
|
? toast.sticky
|
||||||
|
? 'var(--lt-accent-cyan)'
|
||||||
|
: 'var(--lt-accent-orange)'
|
||||||
|
: accent,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: '0.85rem',
|
fontSize: '0.85rem',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -111,22 +136,8 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
};
|
};
|
||||||
|
|
||||||
const dismissBtnStyle: CSSProperties = {
|
|
||||||
position: 'absolute',
|
|
||||||
top: '8px',
|
|
||||||
right: '10px',
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
color: 'var(--lt-text-secondary)',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '14px',
|
|
||||||
lineHeight: 1,
|
|
||||||
padding: '2px 4px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const bodyStyle: CSSProperties = {
|
const bodyStyle: CSSProperties = {
|
||||||
color: 'var(--lt-text-primary)',
|
color: lotusTerminal ? 'var(--lt-text-primary)' : color.Surface.OnContainer,
|
||||||
fontSize: '0.82rem',
|
fontSize: '0.82rem',
|
||||||
margin: '4px 0 2px',
|
margin: '4px 0 2px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -136,7 +147,7 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const roomNameStyle: CSSProperties = {
|
const roomNameStyle: CSSProperties = {
|
||||||
color: 'var(--lt-text-secondary)',
|
color: lotusTerminal ? 'var(--lt-text-secondary)' : color.SurfaceVariant.OnContainer,
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
@@ -161,14 +172,19 @@ function ToastCard({ toast }: ToastCardProps) {
|
|||||||
}}
|
}}
|
||||||
aria-label={`Notification from ${toast.displayName} in ${toast.roomName}`}
|
aria-label={`Notification from ${toast.displayName} in ${toast.roomName}`}
|
||||||
>
|
>
|
||||||
<button
|
<span style={{ position: 'absolute', top: config.space.S100, right: config.space.S100 }}>
|
||||||
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
style={dismissBtnStyle}
|
size="300"
|
||||||
|
radii="300"
|
||||||
|
variant="Surface"
|
||||||
|
fill="None"
|
||||||
onClick={handleDismiss}
|
onClick={handleDismiss}
|
||||||
aria-label="Dismiss notification"
|
aria-label="Dismiss notification"
|
||||||
>
|
>
|
||||||
×
|
<Icon size="100" src={Icons.Cross} />
|
||||||
</button>
|
</IconButton>
|
||||||
|
</span>
|
||||||
<div style={rowStyle}>
|
<div style={rowStyle}>
|
||||||
{toast.avatarUrl ? (
|
{toast.avatarUrl ? (
|
||||||
<img src={toast.avatarUrl} alt="" style={avatarStyle} aria-hidden="true" />
|
<img src={toast.avatarUrl} alt="" style={avatarStyle} aria-hidden="true" />
|
||||||
@@ -201,7 +217,7 @@ export function LotusToastContainer() {
|
|||||||
zIndex: 10001,
|
zIndex: 10001,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '8px',
|
gap: config.space.S200,
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user