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 { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { color, config, Icon, IconButton, Icons } from 'folds';
|
||||
import { toastQueueAtom, dismissToastAtom, ToastNotif } from '../../state/toast';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
|
||||
// Inject the keyframe animation once
|
||||
const STYLE_ID = 'lotus-toast-keyframes';
|
||||
@@ -29,6 +32,10 @@ type ToastCardProps = {
|
||||
|
||||
function ToastCard({ toast }: ToastCardProps) {
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -56,17 +63,29 @@ function ToastCard({ toast }: ToastCardProps) {
|
||||
dismiss(toast.id);
|
||||
};
|
||||
|
||||
const accent = toast.sticky ? color.Primary.Main : color.Surface.OnContainer;
|
||||
|
||||
const cardStyle: CSSProperties = {
|
||||
position: 'relative',
|
||||
background: 'var(--lt-bg-card)',
|
||||
border: toast.sticky
|
||||
? '1px solid var(--lt-accent-cyan-border)'
|
||||
: '1px solid var(--lt-border-color)',
|
||||
borderRadius: '12px',
|
||||
padding: '12px 14px',
|
||||
background: lotusTerminal ? 'var(--lt-bg-card)' : color.Surface.Container,
|
||||
border: `${config.borderWidth.B300} solid ${
|
||||
lotusTerminal
|
||||
? toast.sticky
|
||||
? 'var(--lt-accent-cyan-border)'
|
||||
: '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',
|
||||
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',
|
||||
animation: 'lotusToastIn 0.2s ease-out both',
|
||||
userSelect: 'none',
|
||||
@@ -75,8 +94,8 @@ function ToastCard({ toast }: ToastCardProps) {
|
||||
const rowStyle: CSSProperties = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
marginRight: '20px',
|
||||
gap: config.space.S200,
|
||||
marginRight: config.space.S500,
|
||||
};
|
||||
|
||||
const avatarStyle: CSSProperties = {
|
||||
@@ -91,19 +110,25 @@ function ToastCard({ toast }: ToastCardProps) {
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
borderRadius: '50%',
|
||||
background: 'var(--lt-accent-orange-dim)',
|
||||
border: '1px solid var(--lt-accent-orange-border)',
|
||||
background: lotusTerminal ? 'var(--lt-accent-orange-dim)' : color.Primary.Container,
|
||||
border: `${config.borderWidth.B300} solid ${
|
||||
lotusTerminal ? 'var(--lt-accent-orange-border)' : color.Primary.ContainerLine
|
||||
}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '10px',
|
||||
fontWeight: 700,
|
||||
color: 'var(--lt-accent-orange)',
|
||||
color: lotusTerminal ? 'var(--lt-accent-orange)' : color.Primary.OnContainer,
|
||||
flexShrink: 0,
|
||||
};
|
||||
|
||||
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,
|
||||
fontSize: '0.85rem',
|
||||
overflow: 'hidden',
|
||||
@@ -111,22 +136,8 @@ function ToastCard({ toast }: ToastCardProps) {
|
||||
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 = {
|
||||
color: 'var(--lt-text-primary)',
|
||||
color: lotusTerminal ? 'var(--lt-text-primary)' : color.Surface.OnContainer,
|
||||
fontSize: '0.82rem',
|
||||
margin: '4px 0 2px',
|
||||
overflow: 'hidden',
|
||||
@@ -136,7 +147,7 @@ function ToastCard({ toast }: ToastCardProps) {
|
||||
};
|
||||
|
||||
const roomNameStyle: CSSProperties = {
|
||||
color: 'var(--lt-text-secondary)',
|
||||
color: lotusTerminal ? 'var(--lt-text-secondary)' : color.SurfaceVariant.OnContainer,
|
||||
fontSize: '0.75rem',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
@@ -161,14 +172,19 @@ function ToastCard({ toast }: ToastCardProps) {
|
||||
}}
|
||||
aria-label={`Notification from ${toast.displayName} in ${toast.roomName}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
style={dismissBtnStyle}
|
||||
onClick={handleDismiss}
|
||||
aria-label="Dismiss notification"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<span style={{ position: 'absolute', top: config.space.S100, right: config.space.S100 }}>
|
||||
<IconButton
|
||||
type="button"
|
||||
size="300"
|
||||
radii="300"
|
||||
variant="Surface"
|
||||
fill="None"
|
||||
onClick={handleDismiss}
|
||||
aria-label="Dismiss notification"
|
||||
>
|
||||
<Icon size="100" src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</span>
|
||||
<div style={rowStyle}>
|
||||
{toast.avatarUrl ? (
|
||||
<img src={toast.avatarUrl} alt="" style={avatarStyle} aria-hidden="true" />
|
||||
@@ -201,7 +217,7 @@ export function LotusToastContainer() {
|
||||
zIndex: 10001,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px',
|
||||
gap: config.space.S200,
|
||||
pointerEvents: 'auto',
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user