Skip to content

Commit 0067fde

Browse files
authored
refactor(tarko-web-ui): centralize markdown theme architecture (#1325)
1 parent 282e306 commit 0067fde

5 files changed

Lines changed: 229 additions & 60 deletions

File tree

multimodal/tarko/agent-web-ui/src/sdk/markdown-renderer/MarkdownRenderer.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useMarkdownComponents } from './hooks/useMarkdownComponents';
88
import { ImageModal } from './components/ImageModal';
99
import { resetFirstH1Flag } from './components/Headings';
1010
import { scrollToElement } from './utils';
11-
import { useDarkMode } from '@/common/hooks/useDarkMode';
11+
import { MarkdownThemeProvider, useMarkdownStyles } from './context/MarkdownThemeContext';
1212
import 'remark-github-blockquote-alert/alert.css';
1313
import './syntax-highlight.css';
1414
import './markdown.css';
@@ -25,7 +25,19 @@ interface MarkdownRendererProps {
2525
* MarkdownRenderer component
2626
* Renders markdown content with custom styling and enhanced functionality
2727
*/
28-
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
28+
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = (props) => {
29+
return (
30+
<MarkdownThemeProvider>
31+
<MarkdownRendererContent {...props} />
32+
</MarkdownThemeProvider>
33+
);
34+
};
35+
36+
/**
37+
* Internal component that uses the theme context
38+
* Separated to ensure theme provider is available
39+
*/
40+
const MarkdownRendererContent: React.FC<MarkdownRendererProps> = ({
2941
content,
3042
publishDate,
3143
author,
@@ -34,7 +46,7 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
3446
}) => {
3547
const [openImage, setOpenImage] = useState<string | null>(null);
3648
const [renderError, setRenderError] = useState<Error | null>(null);
37-
const isDarkMode = useDarkMode();
49+
const { themeClass, colors } = useMarkdownStyles();
3850

3951
/**
4052
* Handle image click for modal preview
@@ -93,8 +105,8 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
93105
/**
94106
* Determine theme class and merge with markdown content styles
95107
*/
96-
const themeClass = forceDarkTheme || isDarkMode ? 'dark' : 'light';
97-
const markdownContentClass = `${themeClass} markdown-content font-inter leading-relaxed text-gray-800 dark:text-gray-200 ${className}`;
108+
const finalThemeClass = forceDarkTheme ? 'dark' : themeClass;
109+
const markdownContentClass = `${finalThemeClass} markdown-content font-inter leading-relaxed ${colors.text.primary} ${className}`;
98110

99111
try {
100112
return (

multimodal/tarko/agent-web-ui/src/sdk/markdown-renderer/components/Headings.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useRef } from 'react';
22
import { generateId } from '../utils';
3+
import { useHeadingStyles } from '../context/MarkdownThemeContext';
34

45
interface HeadingProps {
56
children: React.ReactNode;
@@ -23,29 +24,27 @@ export const resetFirstH1Flag = (): void => {
2324
*/
2425
export const Heading: React.FC<HeadingProps> = ({ children, level }) => {
2526
const id = generateId(children?.toString());
26-
27-
const getHeadingStyles = () => {
28-
const baseStyles =
29-
'group scroll-mt-20 flex items-center font-semibold leading-tight text-gray-900 dark:text-gray-100';
30-
27+
const headingStyles = useHeadingStyles();
28+
29+
const getHeadingClassName = () => {
3130
switch (level) {
3231
case 1:
33-
return `${baseStyles} text-3xl font-bold mt-6 mb-2 pb-2 border-b border-gray-200 dark:border-gray-700`;
32+
return headingStyles.h1;
3433
case 2:
35-
return `${baseStyles} text-2xl font-bold mt-6 mb-2 pb-2`;
34+
return headingStyles.h2;
3635
case 3:
37-
return `${baseStyles} text-xl font-semibold mt-8 mb-3 text-gray-800 dark:text-gray-200`;
36+
return headingStyles.h3;
3837
case 4:
39-
return `${baseStyles} text-md font-semibold mt-6 mb-2 text-gray-800 dark:text-gray-200`;
38+
return headingStyles.h4;
4039
default:
41-
return `${baseStyles} text-sm font-medium mt-4 mb-2 text-gray-700 dark:text-gray-300`;
40+
return headingStyles.h5h6;
4241
}
4342
};
4443

4544
const HeadingTag = `h${level}` as keyof JSX.IntrinsicElements;
4645

4746
return (
48-
<HeadingTag id={id} className={getHeadingStyles()}>
47+
<HeadingTag id={id} className={getHeadingClassName()}>
4948
{children}
5049
</HeadingTag>
5150
);
Lines changed: 27 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,37 @@
11
import React from 'react';
2+
import { useTextStyles } from '../context/MarkdownThemeContext';
23

34
/**
4-
* Text element styles - Updated with merged tailwind classes
5+
* Optimized text components using shared theme context
6+
* All components access the same pre-computed styles for better performance
57
*/
6-
const TEXT_STYLES = {
7-
paragraph: 'my-2 text-gray-800 dark:text-gray-200 leading-relaxed text-base',
8-
unorderedList: 'my-2 list-disc pl-6 text-gray-800 dark:text-gray-200 text-base',
9-
orderedList: 'my-2 list-decimal pl-6 text-gray-800 dark:text-gray-200 text-base',
10-
listItem: 'my-1 text-base',
11-
blockquote:
12-
'border-l-4 border-gray-200 dark:border-slate-600 pl-4 my-5 italic text-slate-600 dark:text-slate-400',
13-
horizontalRule: 'my-8 border-t border-gray-200 dark:border-gray-700',
14-
};
158

16-
/**
17-
* Paragraph component
18-
*/
19-
export const Paragraph: React.FC<{ children: React.ReactNode }> = ({ children }) => (
20-
<p className={TEXT_STYLES.paragraph}>{children}</p>
21-
);
9+
export const Paragraph: React.FC<{ children: React.ReactNode }> = ({ children }) => {
10+
const styles = useTextStyles();
11+
return <p className={styles.paragraph}>{children}</p>;
12+
};
2213

23-
/**
24-
* Unordered list component
25-
*/
26-
export const UnorderedList: React.FC<{ children: React.ReactNode }> = ({ children }) => (
27-
<ul className={TEXT_STYLES.unorderedList}>{children}</ul>
28-
);
14+
export const UnorderedList: React.FC<{ children: React.ReactNode }> = ({ children }) => {
15+
const styles = useTextStyles();
16+
return <ul className={styles.unorderedList}>{children}</ul>;
17+
};
2918

30-
/**
31-
* Ordered list component
32-
*/
33-
export const OrderedList: React.FC<{ children: React.ReactNode }> = ({ children }) => (
34-
<ol className={TEXT_STYLES.orderedList}>{children}</ol>
35-
);
19+
export const OrderedList: React.FC<{ children: React.ReactNode }> = ({ children }) => {
20+
const styles = useTextStyles();
21+
return <ol className={styles.orderedList}>{children}</ol>;
22+
};
3623

37-
/**
38-
* List item component
39-
*/
40-
export const ListItem: React.FC<{ children: React.ReactNode }> = ({ children }) => (
41-
<li className={TEXT_STYLES.listItem}>{children}</li>
42-
);
24+
export const ListItem: React.FC<{ children: React.ReactNode }> = ({ children }) => {
25+
const styles = useTextStyles();
26+
return <li className={styles.listItem}>{children}</li>;
27+
};
4328

44-
/**
45-
* Blockquote component
46-
*/
47-
export const Blockquote: React.FC<{ children: React.ReactNode }> = ({ children }) => (
48-
<blockquote className={TEXT_STYLES.blockquote}>{children}</blockquote>
49-
);
29+
export const Blockquote: React.FC<{ children: React.ReactNode }> = ({ children }) => {
30+
const styles = useTextStyles();
31+
return <blockquote className={styles.blockquote}>{children}</blockquote>;
32+
};
5033

51-
/**
52-
* Horizontal rule component
53-
*/
54-
export const HorizontalRule: React.FC = () => <hr className={TEXT_STYLES.horizontalRule} />;
34+
export const HorizontalRule: React.FC = () => {
35+
const styles = useTextStyles();
36+
return <hr className={styles.horizontalRule} />;
37+
};
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import React, { createContext, useContext, useMemo, type ReactNode } from 'react';
2+
import { useMarkdownTheme, type ThemeColors } from '../hooks/useMarkdownTheme';
3+
4+
/**
5+
* Computed styles for all markdown elements
6+
* Centralized to avoid duplicate calculations across components
7+
*/
8+
interface MarkdownStyles {
9+
text: {
10+
paragraph: string;
11+
unorderedList: string;
12+
orderedList: string;
13+
listItem: string;
14+
blockquote: string;
15+
horizontalRule: string;
16+
};
17+
headings: {
18+
h1: string;
19+
h2: string;
20+
h3: string;
21+
h4: string;
22+
h5h6: string;
23+
};
24+
colors: ThemeColors;
25+
themeClass: string;
26+
}
27+
28+
/**
29+
* Generate all markdown styles in one place
30+
* Prevents style recalculation across multiple components
31+
*/
32+
const createMarkdownStyles = (colors: ThemeColors, themeClass: string): MarkdownStyles => {
33+
const baseHeadingStyles = 'group scroll-mt-20 flex items-center font-semibold leading-tight';
34+
35+
return {
36+
text: {
37+
paragraph: `my-2 ${colors.text.primary} leading-relaxed text-base`,
38+
unorderedList: `my-2 list-disc pl-6 ${colors.text.primary} text-base`,
39+
orderedList: `my-2 list-decimal pl-6 ${colors.text.primary} text-base`,
40+
listItem: 'my-1 text-base',
41+
blockquote: `border-l-4 ${colors.border.quote} pl-4 my-5 italic ${colors.text.muted}`,
42+
horizontalRule: `my-8 border-t ${colors.border.default}`,
43+
},
44+
headings: {
45+
h1: `${baseHeadingStyles} ${colors.text.heading} text-3xl font-bold mt-6 mb-2 pb-2 border-b ${colors.border.default}`,
46+
h2: `${baseHeadingStyles} ${colors.text.heading} text-2xl font-bold mt-6 mb-2 pb-2`,
47+
h3: `${baseHeadingStyles} ${colors.text.primary} text-xl font-semibold mt-8 mb-3`,
48+
h4: `${baseHeadingStyles} ${colors.text.primary} text-md font-semibold mt-6 mb-2`,
49+
h5h6: `${baseHeadingStyles} ${colors.text.secondary} text-sm font-medium mt-4 mb-2`,
50+
},
51+
colors,
52+
themeClass,
53+
};
54+
};
55+
56+
/**
57+
* Context for sharing markdown theme styles across components
58+
* Prevents duplicate hook calls and style calculations
59+
*/
60+
const MarkdownThemeContext = createContext<MarkdownStyles | null>(null);
61+
62+
/**
63+
* Provider component that calculates styles once and shares them
64+
* Optimized for performance with memoization
65+
*/
66+
export const MarkdownThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
67+
const { colors, themeClass } = useMarkdownTheme();
68+
69+
// Memoize the entire style object to prevent recalculation
70+
const styles = useMemo(() => {
71+
return createMarkdownStyles(colors, themeClass);
72+
}, [colors, themeClass]);
73+
74+
return (
75+
<MarkdownThemeContext.Provider value={styles}>
76+
{children}
77+
</MarkdownThemeContext.Provider>
78+
);
79+
};
80+
81+
/**
82+
* Optimized hook to access markdown theme styles
83+
* No additional calculations, just context access
84+
*/
85+
export const useMarkdownStyles = (): MarkdownStyles => {
86+
const context = useContext(MarkdownThemeContext);
87+
88+
if (!context) {
89+
throw new Error('useMarkdownStyles must be used within MarkdownThemeProvider');
90+
}
91+
92+
return context;
93+
};
94+
95+
/**
96+
* Convenience hooks for specific style categories
97+
*/
98+
export const useTextStyles = () => useMarkdownStyles().text;
99+
export const useHeadingStyles = () => useMarkdownStyles().headings;
100+
export const useThemeColors = () => useMarkdownStyles().colors;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useMemo } from 'react';
2+
import { useDarkMode } from '@/common/hooks/useDarkMode';
3+
4+
/**
5+
* Theme color definitions - cached and optimized
6+
* Separated from hook for better performance and reusability
7+
*/
8+
const THEME_COLORS = {
9+
light: {
10+
text: {
11+
primary: 'text-gray-800',
12+
secondary: 'text-gray-700',
13+
heading: 'text-gray-900',
14+
muted: 'text-gray-600',
15+
},
16+
background: {
17+
code: 'bg-gray-50',
18+
quote: 'bg-gray-50',
19+
},
20+
border: {
21+
default: 'border-gray-200',
22+
quote: 'border-gray-200',
23+
}
24+
},
25+
dark: {
26+
text: {
27+
primary: 'text-gray-200',
28+
secondary: 'text-gray-300',
29+
heading: 'text-gray-100',
30+
muted: 'text-gray-400',
31+
},
32+
background: {
33+
code: 'bg-gray-800/50',
34+
quote: 'bg-gray-800/30',
35+
},
36+
border: {
37+
default: 'border-gray-700',
38+
quote: 'border-slate-600',
39+
}
40+
}
41+
} as const;
42+
43+
/**
44+
* Optimized theme management hook with memoization
45+
* Provides consistent color schemes and theme detection across all markdown elements
46+
*
47+
* Performance optimizations:
48+
* - Memoized color object to prevent unnecessary re-renders
49+
* - Static theme definitions for better bundling
50+
* - Minimal object creation on theme changes
51+
*/
52+
export const useMarkdownTheme = () => {
53+
const isDarkMode = useDarkMode();
54+
55+
// Memoize the color object to prevent unnecessary re-renders
56+
const colors = useMemo(() => {
57+
return THEME_COLORS[isDarkMode ? 'dark' : 'light'];
58+
}, [isDarkMode]);
59+
60+
const themeClass = isDarkMode ? 'dark' : 'light';
61+
62+
return {
63+
isDarkMode,
64+
themeClass,
65+
colors,
66+
};
67+
};
68+
69+
/**
70+
* Type definitions for better TypeScript support
71+
*/
72+
export type ThemeColors = typeof THEME_COLORS.light;
73+
export type TextColors = ThemeColors['text'];
74+
export type BackgroundColors = ThemeColors['background'];
75+
export type BorderColors = ThemeColors['border'];

0 commit comments

Comments
 (0)