feat(ui): add response semantic color (#12450)

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: shambhu-hegde <143840542+shambhu-hegde@users.noreply.github.com>
This commit is contained in:
cornmander
2025-11-02 19:21:26 -05:00
committed by GitHub
parent 9187f6f6d1
commit 462c7d3502
10 changed files with 70 additions and 36 deletions
@@ -19,12 +19,17 @@ const UNDERLINE_TAG_END_LENGTH = 4; // For "</u>"
interface RenderInlineProps {
text: string;
defaultColor?: string;
}
const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
const RenderInlineInternal: React.FC<RenderInlineProps> = ({
text,
defaultColor,
}) => {
const baseColor = defaultColor ?? theme.text.primary;
// Early return for plain text without markdown or URLs
if (!/[*_~`<[https?:]/.test(text)) {
return <Text color={theme.text.primary}>{text}</Text>;
return <Text color={baseColor}>{text}</Text>;
}
const nodes: React.ReactNode[] = [];
@@ -36,7 +41,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
while ((match = inlineRegex.exec(text)) !== null) {
if (match.index > lastIndex) {
nodes.push(
<Text key={`t-${lastIndex}`}>
<Text key={`t-${lastIndex}`} color={baseColor}>
{text.slice(lastIndex, match.index)}
</Text>,
);
@@ -53,7 +58,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
fullMatch.length > BOLD_MARKER_LENGTH * 2
) {
renderedNode = (
<Text key={key} bold>
<Text key={key} bold color={baseColor}>
{fullMatch.slice(BOLD_MARKER_LENGTH, -BOLD_MARKER_LENGTH)}
</Text>
);
@@ -71,7 +76,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
)
) {
renderedNode = (
<Text key={key} italic>
<Text key={key} italic color={baseColor}>
{fullMatch.slice(ITALIC_MARKER_LENGTH, -ITALIC_MARKER_LENGTH)}
</Text>
);
@@ -81,7 +86,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
fullMatch.length > STRIKETHROUGH_MARKER_LENGTH * 2
) {
renderedNode = (
<Text key={key} strikethrough>
<Text key={key} strikethrough color={baseColor}>
{fullMatch.slice(
STRIKETHROUGH_MARKER_LENGTH,
-STRIKETHROUGH_MARKER_LENGTH,
@@ -111,7 +116,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
const linkText = linkMatch[1];
const url = linkMatch[2];
renderedNode = (
<Text key={key}>
<Text key={key} color={baseColor}>
{linkText}
<Text color={theme.text.link}> ({url})</Text>
</Text>
@@ -124,7 +129,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
UNDERLINE_TAG_START_LENGTH + UNDERLINE_TAG_END_LENGTH - 1 // -1 because length is compared to combined length of start and end tags
) {
renderedNode = (
<Text key={key} underline>
<Text key={key} underline color={baseColor}>
{fullMatch.slice(
UNDERLINE_TAG_START_LENGTH,
-UNDERLINE_TAG_END_LENGTH,
@@ -143,12 +148,22 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
renderedNode = null;
}
nodes.push(renderedNode ?? <Text key={key}>{fullMatch}</Text>);
nodes.push(
renderedNode ?? (
<Text key={key} color={baseColor}>
{fullMatch}
</Text>
),
);
lastIndex = inlineRegex.lastIndex;
}
if (lastIndex < text.length) {
nodes.push(<Text key={`t-${lastIndex}`}>{text.slice(lastIndex)}</Text>);
nodes.push(
<Text key={`t-${lastIndex}`} color={baseColor}>
{text.slice(lastIndex)}
</Text>,
);
}
return <>{nodes.filter((node) => node !== null)}</>;
+21 -16
View File
@@ -35,6 +35,7 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
renderMarkdown = true,
}) => {
const settings = useSettings();
const responseColor = theme.text.response ?? theme.text.primary;
if (!text) return <></>;
@@ -138,8 +139,8 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
// Not a table, treat as regular text
addContentBlock(
<Box key={key}>
<Text wrap="wrap">
<RenderInline text={line} />
<Text wrap="wrap" color={responseColor}>
<RenderInline text={line} defaultColor={responseColor} />
</Text>
</Box>,
);
@@ -177,8 +178,8 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
if (line.trim().length > 0) {
addContentBlock(
<Box key={key}>
<Text wrap="wrap">
<RenderInline text={line} />
<Text wrap="wrap" color={responseColor}>
<RenderInline text={line} defaultColor={responseColor} />
</Text>
</Box>,
);
@@ -197,35 +198,38 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
case 1:
headerNode = (
<Text bold color={theme.text.link}>
<RenderInline text={headerText} />
<RenderInline text={headerText} defaultColor={theme.text.link} />
</Text>
);
break;
case 2:
headerNode = (
<Text bold color={theme.text.link}>
<RenderInline text={headerText} />
<RenderInline text={headerText} defaultColor={theme.text.link} />
</Text>
);
break;
case 3:
headerNode = (
<Text bold color={theme.text.primary}>
<RenderInline text={headerText} />
<Text bold color={responseColor}>
<RenderInline text={headerText} defaultColor={responseColor} />
</Text>
);
break;
case 4:
headerNode = (
<Text italic color={theme.text.secondary}>
<RenderInline text={headerText} />
<RenderInline
text={headerText}
defaultColor={theme.text.secondary}
/>
</Text>
);
break;
default:
headerNode = (
<Text color={theme.text.primary}>
<RenderInline text={headerText} />
<Text color={responseColor}>
<RenderInline text={headerText} defaultColor={responseColor} />
</Text>
);
break;
@@ -268,8 +272,8 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
} else {
addContentBlock(
<Box key={key}>
<Text wrap="wrap" color={theme.text.primary}>
<RenderInline text={line} />
<Text wrap="wrap" color={responseColor}>
<RenderInline text={line} defaultColor={responseColor} />
</Text>
</Box>,
);
@@ -401,6 +405,7 @@ const RenderListItemInternal: React.FC<RenderListItemProps> = ({
const prefix = type === 'ol' ? `${marker}. ` : `${marker} `;
const prefixWidth = prefix.length;
const indentation = leadingWhitespace.length;
const listResponseColor = theme.text.response ?? theme.text.primary;
return (
<Box
@@ -408,11 +413,11 @@ const RenderListItemInternal: React.FC<RenderListItemProps> = ({
flexDirection="row"
>
<Box width={prefixWidth}>
<Text color={theme.text.primary}>{prefix}</Text>
<Text color={listResponseColor}>{prefix}</Text>
</Box>
<Box flexGrow={LIST_ITEM_TEXT_FLEX_GROW}>
<Text wrap="wrap" color={theme.text.primary}>
<RenderInline text={itemText} />
<Text wrap="wrap" color={listResponseColor}>
<RenderInline text={itemText} defaultColor={listResponseColor} />
</Text>
</Box>
</Box>