diff --git a/webapp/chat-app/src/components/MessageList.tsx b/webapp/chat-app/src/components/MessageList.tsx index 235ad884..aee3db26 100644 --- a/webapp/chat-app/src/components/MessageList.tsx +++ b/webapp/chat-app/src/components/MessageList.tsx @@ -60,6 +60,25 @@ const MessageListContainer = styled.div` `; const MessageContent = styled.div` + /* Add theme-specific CSS variables */ + --theme-bg: ${({theme}) => theme.colors.background}; + --theme-text: ${({theme}) => theme.colors.text.primary}; + --theme-surface: ${({theme}) => theme.colors.surface}; + --theme-border: ${({theme}) => theme.colors.border}; + --theme-primary: ${({theme}) => theme.colors.primary}; + --theme-code-font: ${({theme}) => theme.typography.console.fontFamily}; + /* Apply theme variables to content */ + color: var(--theme-text); + background: var(--theme-bg); + /* Style code blocks with theme variables */ + pre[class*="language-"], + code[class*="language-"] { + background: var(--theme-surface); + color: var(--theme-text); + font-family: var(--theme-code-font); + } + + .href-link, .play-button, .regen-button, .cancel-button, .text-submit-button { cursor: pointer; user-select: none; @@ -67,13 +86,16 @@ const MessageContent = styled.div` padding: 2px 8px; margin: 2px; border-radius: 4px; - background-color: ${({theme}) => theme.colors.surface}; - color: ${({theme}) => theme.colors.text.primary}; + background-color: var(--theme-surface); + color: var(--theme-text); + transition: all var(--transition-duration) var(--transition-timing), + transform 0.2s ease-in-out; &:hover { opacity: 0.8; - background-color: ${({theme}) => theme.colors.primary}; - color: ${({theme}) => theme.colors.background}; + background-color: var(--theme-primary); + color: var(--theme-bg); + transform: translateY(-1px); } } @@ -82,6 +104,7 @@ const MessageContent = styled.div` padding: 4px; margin: 4px 0; border-left: 3px solid ${({theme}) => theme.colors.border}; + transition: all 0.3s ease; &.expanded { background-color: ${({theme}) => theme.colors.surface}; @@ -90,11 +113,26 @@ const MessageContent = styled.div` /* Style code blocks according to theme */ pre[class*="language-"] { background: ${({theme}) => theme.colors.surface}; - border: 1px solid ${({theme}) => theme.colors.border}; + margin: 1em 0; + padding: 1em; + border-radius: ${({theme}) => theme.sizing.borderRadius.md}; + transition: all var(--transition-duration) var(--transition-timing); + box-shadow: ${({theme}) => theme.shadows.medium}; } code[class*="language-"] { color: ${({theme}) => theme.colors.text.primary}; text-shadow: none; + transition: all 0.3s ease; + font-family: ${({theme}) => theme.typography.console.fontFamily}; + } + /* Style inline code differently from code blocks */ + :not(pre) > code { + background: ${({theme}) => theme.colors.surface}; + color: ${({theme}) => theme.colors.text.primary}; + padding: 0.2em 0.4em; + border-radius: ${({theme}) => theme.sizing.borderRadius.sm}; + font-size: 0.9em; + transition: all 0.3s ease; } `; diff --git a/webapp/chat-app/src/styles/GlobalStyles.ts b/webapp/chat-app/src/styles/GlobalStyles.ts index 1cc551a0..9682106d 100644 --- a/webapp/chat-app/src/styles/GlobalStyles.ts +++ b/webapp/chat-app/src/styles/GlobalStyles.ts @@ -19,6 +19,15 @@ const logThemeChange = (theme: DefaultTheme) => { }; export const GlobalStyles = createGlobalStyle<{ theme: DefaultTheme }>` + /* Theme CSS variables */ + :root { + /* Add theme variables */ + --theme-background: ${({theme}) => theme.colors.background}; + --theme-text: ${({theme}) => theme.colors.text.primary}; + --theme-surface: ${({theme}) => theme.colors.surface}; + --theme-border: ${({theme}) => theme.colors.border}; + --theme-primary: ${({theme}) => theme.colors.primary}; + } /* Override Prism.js theme colors to match current theme */ .token.comment, .token.prolog, @@ -76,11 +85,25 @@ export const GlobalStyles = createGlobalStyle<{ theme: DefaultTheme }>` line-height: inherit; } - /* Improve transitions */ - .theme-transition { - transition: all var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1); + /* Message content theme transitions */ + .message-content { + color: var(--theme-text); + background: var(--theme-background); + border-color: var(--theme-border); } - + .message-content pre, + .message-content code { + background: var(--theme-surface); + color: var(--theme-text); + } + + /* Universal code block styles using CSS variables */ + pre code { + background: var(--theme-surface); + color: var(--theme-text); + border-color: var(--theme-border); + } + body { font-family: ${({theme}: { theme: DefaultTheme }) => { logStyleChange('body', 'font-family', theme.typography.fontFamily); diff --git a/webapp/chat-app/src/themes/ThemeProvider.tsx b/webapp/chat-app/src/themes/ThemeProvider.tsx index 59d613f4..f140aa7c 100644 --- a/webapp/chat-app/src/themes/ThemeProvider.tsx +++ b/webapp/chat-app/src/themes/ThemeProvider.tsx @@ -35,6 +35,37 @@ export const ThemeProvider: React.FC = ({children}) => { const previousTheme = useRef(currentTheme); useEffect(() => { + // Create a style element for dynamic theme transitions + const styleEl = document.createElement('style'); + document.head.appendChild(styleEl); + // Add theme CSS variables to root + styleEl.textContent = ` + :root { + --theme-background: ${themes[currentTheme].colors.background}; + --theme-text: ${themes[currentTheme].colors.text.primary}; + --theme-surface: ${themes[currentTheme].colors.surface}; + --theme-border: ${themes[currentTheme].colors.border}; + --theme-primary: ${themes[currentTheme].colors.primary}; + --theme-code-font: ${themes[currentTheme].typography.console.fontFamily}; + } + /* Theme-specific message content styles */ + .message-content { + color: var(--theme-text); + background: var(--theme-background); + } + .message-content pre, + .message-content code { + background: var(--theme-surface); + border: 1px solid var(--theme-border); + font-family: var(--theme-code-font); + } + `; + + // Add theme transition class to message content + const contentElements = document.querySelectorAll('.message-content'); + contentElements.forEach(content => { + content.classList.add('theme-transition'); + }); if (isInitialMount.current) { console.info(`${LOG_PREFIX} Initial theme:`, currentTheme); isInitialMount.current = false; @@ -45,19 +76,62 @@ export const ThemeProvider: React.FC = ({children}) => { } document.body.className = `theme-${currentTheme}`; + // Add dynamic CSS rules for message content + styleEl.textContent = ` + .message-content.theme-${currentTheme} { + --theme-background: ${themes[currentTheme].colors.background}; + --theme-text: ${themes[currentTheme].colors.text.primary}; + --theme-surface: ${themes[currentTheme].colors.surface}; + --theme-primary: ${themes[currentTheme].colors.primary}; + } + `; // Add transition class document.body.classList.add('theme-transition'); + // Force re-render of message content + const bodyElements = document.querySelectorAll('.message-body'); + bodyElements.forEach(content => { + content.classList.add('theme-transition'); + }); + + // Load and apply Prism theme loadPrismTheme(currentTheme).then(() => { // Re-highlight all code blocks with new theme requestAnimationFrame(() => { Prism.highlightAll(); + // Apply theme variables to code blocks + document.querySelectorAll('pre code').forEach(block => { + (block as HTMLElement).style.setProperty('--theme-background', themes[currentTheme].colors.background); + (block as HTMLElement).style.setProperty('--theme-text', themes[currentTheme].colors.text.primary); + }); + // Update code block styles + const codeBlocks = document.querySelectorAll('pre code'); + codeBlocks.forEach(block => { + (block as HTMLElement).classList.add('theme-transition'); + }); }); }); const timer = setTimeout(() => { document.body.classList.remove('theme-transition'); + // Remove transition classes + document.querySelectorAll('.theme-transition').forEach(el => { + el.classList.remove('theme-transition'); + // Remove old theme classes but keep current + Array.from(el.classList) + .filter(cls => cls.startsWith('theme-') && cls !== `theme-${currentTheme}`) + .forEach(cls => el.classList.remove(cls)); + }); + // Remove old theme classes from code blocks + document.querySelectorAll('pre code').forEach(block => { + Array.from(block.classList) + .filter(cls => cls.startsWith('theme-') && cls !== `theme-${currentTheme}`) + .forEach(cls => block.classList.remove(cls)); + }); }, 300); - return () => clearTimeout(timer); + return () => { + clearTimeout(timer); + styleEl.remove(); + }; }, [currentTheme]); const theme = themes[currentTheme] || themes.main; diff --git a/webapp/chat-app/src/themes/themes.ts b/webapp/chat-app/src/themes/themes.ts index 72e02184..54d1ce37 100644 --- a/webapp/chat-app/src/themes/themes.ts +++ b/webapp/chat-app/src/themes/themes.ts @@ -240,6 +240,7 @@ const baseTheme = { }, typography: { fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", + monoFontFamily: "'Fira Code', 'Consolas', monospace", fontSize: { xs: '0.75rem', sm: '0.875rem', diff --git a/webapp/chat-app/src/types/styled.d.ts b/webapp/chat-app/src/types/styled.d.ts index d569606f..559bfc74 100644 --- a/webapp/chat-app/src/types/styled.d.ts +++ b/webapp/chat-app/src/types/styled.d.ts @@ -49,6 +49,7 @@ declare module 'styled-components' { }; typography: { fontFamily: string; + monoFontFamily?: string; fontSize: { xs: string; sm: string;