feat(ui): format multi-line banner warnings with a bold title (#22955)

Co-authored-by: Sehoon Shon <sshon@google.com>
This commit is contained in:
Keith Guerin
2026-03-18 11:39:12 -07:00
committed by GitHub
parent a5a461c234
commit 4dcca1ca10
7 changed files with 104 additions and 22 deletions

View File

@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from '../../test-utils/render.js';
import { renderWithProviders } from '../../test-utils/render.js';
import { Banner } from './Banner.js';
import { describe, it, expect } from 'vitest';
@@ -12,22 +12,23 @@ describe('Banner', () => {
it.each([
['warning mode', true, 'Warning Message'],
['info mode', false, 'Info Message'],
['multi-line warning', true, 'Title Line\\nBody Line 1\\nBody Line 2'],
])('renders in %s', async (_, isWarning, text) => {
const { lastFrame, waitUntilReady, unmount } = render(
const renderResult = renderWithProviders(
<Banner bannerText={text} isWarning={isWarning} width={80} />,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
await renderResult.waitUntilReady();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
});
it('handles newlines in text', async () => {
const text = 'Line 1\\nLine 2';
const { lastFrame, waitUntilReady, unmount } = render(
const renderResult = renderWithProviders(
<Banner bannerText={text} isWarning={false} width={80} />,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
await renderResult.waitUntilReady();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
});
});

View File

@@ -14,20 +14,21 @@ export function getFormattedBannerContent(
isWarning: boolean,
subsequentLineColor: string,
): ReactNode {
if (isWarning) {
return (
<Text color={theme.status.warning}>{rawText.replace(/\\n/g, '\n')}</Text>
);
}
const text = rawText.replace(/\\n/g, '\n');
const lines = text.split('\n');
return lines.map((line, index) => {
if (index === 0) {
if (isWarning) {
return (
<Text key={index} bold color={theme.status.warning}>
{line}
</Text>
);
}
return (
<ThemedGradient key={index}>
<Text>{line}</Text>
<Text bold>{line}</Text>
</ThemedGradient>
);
}

View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="88" viewBox="0 0 920 88">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="88" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">L</text>
<text x="27" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">i</text>
<text x="36" y="19" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">n</text>
<text x="45" y="19" fill="#9974b4" textLength="9" lengthAdjust="spacingAndGlyphs">e</text>
<text x="63" y="19" fill="#c3677f" textLength="9" lengthAdjust="spacingAndGlyphs">1</text>
<text x="711" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="36" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Line 2</text>
<text x="711" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">I</text>
<text x="27" y="19" fill="#5390e0" textLength="9" lengthAdjust="spacingAndGlyphs">n</text>
<text x="36" y="19" fill="#5f8bdb" textLength="9" lengthAdjust="spacingAndGlyphs">f</text>
<text x="45" y="19" fill="#6c85d7" textLength="9" lengthAdjust="spacingAndGlyphs">o</text>
<text x="63" y="19" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">M</text>
<text x="72" y="19" fill="#8f77c1" textLength="9" lengthAdjust="spacingAndGlyphs">e</text>
<text x="81" y="19" fill="#9974b4" textLength="9" lengthAdjust="spacingAndGlyphs">s</text>
<text x="90" y="19" fill="#a471a7" textLength="9" lengthAdjust="spacingAndGlyphs">s</text>
<text x="99" y="19" fill="#ae6d99" textLength="9" lengthAdjust="spacingAndGlyphs">a</text>
<text x="108" y="19" fill="#b96a8c" textLength="9" lengthAdjust="spacingAndGlyphs">g</text>
<text x="117" y="19" fill="#c3677f" textLength="9" lengthAdjust="spacingAndGlyphs">e</text>
<text x="711" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="105" viewBox="0 0 920 105">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="105" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#ffffaf" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Title Line</text>
<text x="711" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="36" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">Body Line 1</text>
<text x="711" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="53" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">Body Line 2</text>
<text x="711" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#ffffaf" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Warning Message</text>
<text x="711" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -4,20 +4,25 @@ exports[`Banner > handles newlines in text 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Line 1 │
│ Line 2 │
╰──────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`Banner > renders in info mode 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Info Message │
╰──────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`Banner > renders in multi-line warning 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Title Line │
│ Body Line 1 │
│ Body Line 2 │
╰──────────────────────────────────────────────────────────────────────────────╯"
`;
exports[`Banner > renders in warning mode 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ Warning Message │
╰──────────────────────────────────────────────────────────────────────────────╯
"
╰──────────────────────────────────────────────────────────────────────────────╯"
`;