1
0
Fork 0

feat: v5 general improvements (#2948)

* feat(v5): General fixes and improvements

* add changeset

* fix: bundlesize
This commit is contained in:
Paul Jankowski 2026-08-03 15:14:02 -04:00 committed by GitHub
parent 8c01259d81
commit 5eac1fd6bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 152 additions and 93 deletions

View file

@ -0,0 +1,30 @@
---
"@docsearch/docusaurus-adapter": patch
"@docsearch/react": patch
"@docsearch/css": patch
---
feat(v5): General UI styling updates and fixes
- New `--docsearch-font-family` variable, used by the search button, keyboard
keys, modal, and sidepanel. It replaces the system font stacks that were
duplicated across `sidepanel.css` and `button.css`, so overriding one
variable now themes every DocSearch surface
- The search input and modal heading are `1rem` at every breakpoint
(previously `0.875rem` with a mobile-only override)
- `.DocSearch-Title` uses `line-height: 1.5em` instead of `0.5em` and adds
`overflow-wrap: anywhere`, so long titles wrap instead of overlapping (#2908)
- Hit icon `svg` sizing moved into `.DocSearch-Hit-icon`. The
`.DocSearch-Hit-icon--small` modifier is replaced by
`.DocSearch-Hit-icon--start`, which top-aligns the icon; recent
conversations use it
- The Ask AI button icon centers with `display: inline-flex` instead of
`margin-block-start`/`align-self` overrides
- Removed the 2px offset on the Ask AI sources action text
- `SourcesPanel` accepts `pluralTitleText`. `titleText` is now the singular
label, and the trigger renders `{count} {label}`
- `AskAiScreenTranslations` and `ConversationScreenTranslations` expose
`relatedSourcesTextPlural`
- Docusaurus adapter: `theme.SearchModal.askAiScreen.relatedSourcesText` is
now the singular "Source", and the new
`theme.SearchModal.askAiScreen.relatedSourcesTextPlural` provides "Sources"

View file

@ -39,6 +39,7 @@ const translations: DocSearchTranslations & {
askAiScreen: {
disclaimerText: string;
relatedSourcesText: string;
relatedSourcesTextPlural: string;
thinkingText: string;
copyButtonText: string;
copyButtonCopiedText: string;
@ -237,8 +238,13 @@ const translations: DocSearchTranslations & {
}),
relatedSourcesText: translate({
id: 'theme.SearchModal.askAiScreen.relatedSourcesText',
message: 'Related sources',
description: 'The text for related sources',
message: 'Source',
description: 'Text shown describing a singular related source',
}),
relatedSourcesTextPlural: translate({
id: 'theme.SearchModal.askAiScreen.relatedSourcesTextPlural',
message: 'Sources',
description: 'Text shown describing multiple related sources',
}),
thinkingText: translate({
id: 'theme.SearchModal.askAiScreen.thinkingText',

View file

@ -2,7 +2,7 @@
"files": [
{
"path": "packages/docsearch-css/dist/style.css",
"maxSize": "8 kB"
"maxSize": "8.05 kB"
},
{
"path": "packages/docsearch-react/dist/umd/index.js",

View file

@ -1,12 +1,12 @@
import { test, expect } from './fixtures';
import { test, expect, gatherA11yViolations } from './fixtures';
test.describe('a11y', () => {
test.describe('a11y > Modal', () => {
test.beforeEach(async ({ docSearch }) => {
await docSearch.goto();
await docSearch.waitForLoad();
});
test.fail('Smoke test > Modal', async ({ docSearch, axe }, testInfo) => {
test('Smoke test', async ({ docSearch, axe }, testInfo) => {
await docSearch.openModal();
const scanResults = await axe().include('.DocSearch-Container').analyze();
@ -16,10 +16,42 @@ test.describe('a11y', () => {
contentType: 'application/json',
});
expect(scanResults.violations).toEqual([]);
// 6 is the current number of reported violations
expect(
gatherA11yViolations(scanResults.violations).length
).toBeLessThanOrEqual(6);
});
test.fail('Smoke test > Sidepanel', async ({ sidepanel, axe }, testInfo) => {
test('Search results', async ({ docSearch, axe }, testInfo) => {
await docSearch.openModal();
await docSearch.typeQueryMatching();
await expect(docSearch.hits).toBeVisible();
const scanResults = await axe()
.include('#docsearch-hits_docsearch-list')
.analyze();
await testInfo.attach('a11y-scan-results-modal-search-results', {
body: JSON.stringify(scanResults.violations, null, 2),
contentType: 'application/json',
});
// 24 is the current number of reported violations
expect(
gatherA11yViolations(scanResults.violations).length
).toBeLessThanOrEqual(24);
});
});
test.describe('a11y > Sidepanel', () => {
test.beforeEach(async ({ docSearch, sidepanel }) => {
await docSearch.goto();
await sidepanel.waitForLoad();
});
test('Smoke test', async ({ sidepanel, axe }, testInfo) => {
await sidepanel.openSidepanel();
const scanResults = await axe()
@ -31,6 +63,9 @@ test.describe('a11y', () => {
contentType: 'application/json',
});
expect(scanResults.violations).toEqual([]);
// 4 is the current number of reported violations
expect(
gatherA11yViolations(scanResults.violations).length
).toBeLessThanOrEqual(4);
});
});

View file

@ -102,6 +102,10 @@ export class SidepanelPage {
this.sidepanel = page.locator('.DocSearch-Sidepanel-Container');
}
async waitForLoad(): Promise<void> {
await expect(this.sidepanelButton).toBeVisible({ timeout: 10000 });
}
async openSidepanel(): Promise<void> {
await this.sidepanelButton.click();
await expect(this.sidepanel).toHaveClass(/is-open/, {
@ -137,4 +141,10 @@ export const test = base.extend<{
},
});
export { expect };
type AxeScanResults = Awaited<ReturnType<AxeBuilder['analyze']>>;
function gatherA11yViolations(violations: AxeScanResults['violations']) {
return violations.flatMap((v) => v.nodes);
}
export { expect, gatherA11yViolations };

View file

@ -84,14 +84,13 @@
/* Ask AI Button */
.DocSearch-Hit-AskAIButton {
align-items: center;
height: 40px;
}
.DocSearch-Hit-AskAIButton-icon {
display: inline-flex;
align-items: center;
margin-inline-end: 8px;
margin-block-start: 0;
align-self: unset;
}
.DocSearch-Hit-AskAIButton-title {
@ -1011,20 +1010,23 @@
gap: 1ch;
border-radius: 100vw;
border: 1px solid var(--docsearch-subtle-color);
background: transparent;
background-color: transparent;
padding-inline: 0.75em;
padding-block: 0.375em;
cursor: pointer;
user-select: none;
color: var(--docsearch-text-color);
transition: all 0.2s ease;
transition:
background-color 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
@media (prefers-reduced-motion: reduce) {
transition: none;
}
&:hover {
background: var(--docsearch-soft-primary-color);
background-color: var(--docsearch-soft-primary-color);
color: var(--docsearch-highlight-color);
border-color: var(--docsearch-soft-primary-color);
}
@ -1047,7 +1049,6 @@
}
.DocSearch-AskAiScreen-Sources-Action-text {
margin-block-start: 2px;
font-size: 1em;
line-height: 1.15em;
white-space: nowrap;

View file

@ -1,6 +1,8 @@
/* Variables */
:root {
/* stylelint-disable-next-line value-keyword-case */
--docsearch-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--docsearch-primary-color: rgb(0 61 255);
--docsearch-primary-dark-color: rgb(2 46 185 / 100%);
--docsearch-soft-primary-color: rgb(0 61 255 / 10%);

View file

@ -11,6 +11,7 @@
justify-content: space-between;
padding: 0 8px;
user-select: none;
font-family: var(--docsearch-font-family);
}
.DocSearch-Button-Container {
@ -58,10 +59,7 @@
justify-content: center;
position: relative;
font-size: 14px;
font-family:
system-ui,
-apple-system,
sans-serif;
font-family: var(--docsearch-font-family);
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 100ms;

View file

@ -50,6 +50,7 @@
margin: 60px auto auto;
max-width: var(--docsearch-modal-width);
position: relative;
font-family: var(--docsearch-font-family);
}
.DocSearch-Popover-Positioner {
@ -442,7 +443,7 @@
color: var(--docsearch-text-color);
flex: 1;
font: inherit;
font-size: 0.875rem;
font-size: 1rem;
font-weight: 400;
height: 100%;
outline: none;
@ -656,8 +657,9 @@
color: var(--docsearch-text-color);
vertical-align: middle;
font-size: 1.1em;
line-height: 0.5em;
line-height: 1.5em;
font-weight: 300;
overflow-wrap: anywhere;
}
.DocSearch-Title strong {
@ -777,23 +779,16 @@
height: 24px;
stroke-width: var(--docsearch-icon-stroke-width);
width: 24px;
svg {
height: 20px;
width: 20px;
}
}
.DocSearch-Hit-icon--start {
align-self: flex-start;
margin-block-start: 0.8em;
}
.DocSearch-Hit-icon svg {
height: 20px;
width: 20px;
}
.DocSearch-Hit-icon--small {
height: 1rem;
width: 1rem;
}
.DocSearch-Hit-icon--small svg {
height: 1rem;
width: 1rem;
margin-block-start: 0.5em;
}
.DocSearch-Hit-action,
@ -1114,10 +1109,6 @@ assistive tech users */
--docsearch-footer-height: 48px;
}
.DocSearch-Input {
font-size: 1rem;
}
/* Prevent body scroll on modal-open for mobile */
/* https://stackoverflow.com/a/24727206/6276948 */

View file

@ -35,6 +35,7 @@
user-select: none;
border: 0;
transition: all 150ms ease-in-out;
font-family: var(--docsearch-font-family);
}
.DocSearch-SidepanelButton.floating {
@ -90,18 +91,7 @@
all: unset;
cursor: pointer;
transition: all 150ms ease-in-out;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
font-family: var(--docsearch-font-family);
line-height: normal;
}
@ -122,7 +112,6 @@
.DocSearch-Sidepanel-Container {
position: fixed;
min-width: 0;
will-change: transform, width, opacity;
overflow: hidden;
box-shadow:
0 0 0 1px rgb(35 38 59 / 5%),
@ -132,12 +121,6 @@
width: 100svw;
}
@media screen and (prefers-reduced-motion: reduce) {
.DocSearch-Sidepanel-Container {
transition: none;
}
}
.DocSearch-Sidepanel-Container.inline,
.DocSearch-Sidepanel-Container.floating {
top: 0;
@ -166,7 +149,10 @@
@media screen and (width >= 769px) {
.DocSearch-Sidepanel-Container {
width: var(--sp-width);
transition: all 280ms cubic-bezier(0.22, 1, 0.36, 1);
transition:
transform 280ms cubic-bezier(0.22, 1, 0.36, 1),
opacity 280ms cubic-bezier(0.22, 1, 0.36, 1),
width 280ms cubic-bezier(0.22, 1, 0.36, 1);
}
.DocSearch-Sidepanel-Container.floating {
@ -209,6 +195,12 @@
}
}
@media screen and (prefers-reduced-motion: reduce) {
.DocSearch-Sidepanel-Container {
transition: none;
}
}
.DocSearch-Sidepanel-Container.is-open {
opacity: 1;
}
@ -233,18 +225,7 @@
display: flex;
flex-direction: column;
background-color: var(--docsearch-sidepanel-background);
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
font-family: var(--docsearch-font-family);
overflow: hidden;
box-sizing: border-box;
height: 100%;
@ -442,18 +423,7 @@
min-height: 1.5rem;
height: 1.5rem;
overflow: hidden;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
font-family: var(--docsearch-font-family);
}
.DocSearch-Sidepanel-Prompt--textarea::placeholder {

View file

@ -31,7 +31,10 @@ export type AskAiScreenTranslations = Partial<
Omit<ToolCallTranslations, 'searchingText' | 'toolCallResultText'> & {
// Misc texts
disclaimerText: string;
/** Text shown describing a singular related source. */
relatedSourcesText: string;
/** Text shown describing multiple related sources. */
relatedSourcesTextPlural: string;
thinkingText: string;
copyButtonText: string;
copyButtonCopiedText: string;
@ -187,6 +190,7 @@ function AskAiExchangeCard({
stoppedStreamingText = 'You stopped this response',
errorTitleText = 'Chat error',
relatedSourcesText,
relatedSourcesTextPlural,
suggestedPromptsTitleText = 'Suggested prompts',
} = translations;
@ -355,7 +359,11 @@ function AskAiExchangeCard({
)}
</div>
<div className="DocSearch-AskAiScreen-Answer-Footer">
<SourcesPanel links={urlsToDisplay} titleText={relatedSourcesText} />
<SourcesPanel
links={urlsToDisplay}
titleText={relatedSourcesText}
pluralTitleText={relatedSourcesTextPlural}
/>
<FeedbackActions
id={messageId}
showActions={showActions}

View file

@ -31,8 +31,10 @@ export type ConversationScreenTranslations = Partial<
reasoningText: string;
/** Text show while assistant is thinking. */
thinkingText: string;
/** Text shown describing related sources. */
/** Text shown describing a singular related source. */
relatedSourcesText: string;
/** Text shown describing multiple related sources. */
relatedSourcesTextPlural: string;
/** Message that's shown when user has stopped the streaming of a message. */
stoppedStreamingText: string;
/** Text shown for copy button on code snippets. */
@ -119,6 +121,7 @@ const ConversationExchange = React.forwardRef<
thinkingText = 'Thinking...',
searchingText = 'Searching...',
relatedSourcesText,
relatedSourcesTextPlural,
stoppedStreamingText = 'You stopped this response',
preToolCallText = 'Searching...',
toolCallResultText = 'Searched for',
@ -281,6 +284,7 @@ const ConversationExchange = React.forwardRef<
<SourcesPanel
links={urlsToDisplay}
titleText={relatedSourcesText}
pluralTitleText={relatedSourcesTextPlural}
/>
<FeedbackActions
isSidepanel={true}

View file

@ -8,16 +8,20 @@ import { Popover } from './ui/Popover';
interface SourcesProps {
links: ExtractedLink[];
titleText?: string;
pluralTitleText?: string;
}
export function SourcesPanel({
links,
titleText = 'Sources',
titleText = 'Source',
pluralTitleText = 'Sources',
}: SourcesProps): JSX.Element | null {
const [open, setOpen] = React.useState(false);
const triggerRef = React.useRef<HTMLButtonElement>(null);
const [boundary, setBoundary] = React.useState<HTMLElement | null>(null);
const sourcesTitle = links.length > 1 ? pluralTitleText : titleText;
useEffect(() => {
if (!open) {
return undefined;
@ -73,7 +77,7 @@ export function SourcesPanel({
<ContentIcon />
</span>
<span className="DocSearch-AskAiScreen-Sources-Action-text">
{links.length} sources
{links.length} {sourcesTitle}
</span>
</Popover.Trigger>
<Popover.Popup
@ -83,7 +87,7 @@ export function SourcesPanel({
alignOffset={-16}
collisionBoundary={boundary ?? 'clipping-ancestors'}
>
<Popover.Title>{titleText}</Popover.Title>
<Popover.Title>{sourcesTitle}</Popover.Title>
<ul className="DocSearch-AskAiScreen-Sources">
{links.map((l) => (
<li key={l.url} className="DocSearch-AskAiScreen-Sources-source">

View file

@ -38,7 +38,7 @@ export function RecentConversationsResults({
title={recentConversationsTitle}
collection={recentConversations}
renderIcon={() => (
<div className="DocSearch-Hit-icon DocSearch-Hit-icon--small">
<div className="DocSearch-Hit-icon DocSearch-Hit-icon--start">
<MessageIcon />
</div>
)}