From 5eac1fd6bbbe2efedb4272d8b5487add90c7378e Mon Sep 17 00:00:00 2001 From: Paul Jankowski <8bittitan@gmail.com> Date: Mon, 3 Aug 2026 15:14:02 -0400 Subject: [PATCH] feat: v5 general improvements (#2948) * feat(v5): General fixes and improvements * add changeset * fix: bundlesize --- .changeset/better-cars-smell.md | 30 ++++++++++ .../src/theme/SearchTranslations/index.ts | 10 +++- bundlesize.config.json | 2 +- e2e/a11y.test.ts | 47 +++++++++++++-- e2e/fixtures.ts | 12 +++- packages/docsearch-css/src/_askai.css | 15 ++--- packages/docsearch-css/src/_variables.css | 2 + packages/docsearch-css/src/button.css | 6 +- packages/docsearch-css/src/modal.css | 35 +++++------ packages/docsearch-css/src/sidepanel.css | 58 +++++-------------- packages/docsearch-react/src/AskAiScreen.tsx | 10 +++- .../src/Sidepanel/ConversationScreen.tsx | 6 +- .../src/components/SourcesPanel.tsx | 10 +++- .../ui/RecentConversationsResults.tsx | 2 +- 14 files changed, 152 insertions(+), 93 deletions(-) create mode 100644 .changeset/better-cars-smell.md diff --git a/.changeset/better-cars-smell.md b/.changeset/better-cars-smell.md new file mode 100644 index 00000000..90c6c501 --- /dev/null +++ b/.changeset/better-cars-smell.md @@ -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" diff --git a/adapters/docusaurus-theme-search-algolia/src/theme/SearchTranslations/index.ts b/adapters/docusaurus-theme-search-algolia/src/theme/SearchTranslations/index.ts index f510c7a4..50acbd98 100644 --- a/adapters/docusaurus-theme-search-algolia/src/theme/SearchTranslations/index.ts +++ b/adapters/docusaurus-theme-search-algolia/src/theme/SearchTranslations/index.ts @@ -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', diff --git a/bundlesize.config.json b/bundlesize.config.json index 5daaf707..402f132a 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -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", diff --git a/e2e/a11y.test.ts b/e2e/a11y.test.ts index 7e437f24..c20d5989 100644 --- a/e2e/a11y.test.ts +++ b/e2e/a11y.test.ts @@ -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); }); }); diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts index 4ef7e5b3..bfb83477 100644 --- a/e2e/fixtures.ts +++ b/e2e/fixtures.ts @@ -102,6 +102,10 @@ export class SidepanelPage { this.sidepanel = page.locator('.DocSearch-Sidepanel-Container'); } + async waitForLoad(): Promise { + await expect(this.sidepanelButton).toBeVisible({ timeout: 10000 }); + } + async openSidepanel(): Promise { 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>; + +function gatherA11yViolations(violations: AxeScanResults['violations']) { + return violations.flatMap((v) => v.nodes); +} + +export { expect, gatherA11yViolations }; diff --git a/packages/docsearch-css/src/_askai.css b/packages/docsearch-css/src/_askai.css index 69d1e1ce..3db890ac 100644 --- a/packages/docsearch-css/src/_askai.css +++ b/packages/docsearch-css/src/_askai.css @@ -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; diff --git a/packages/docsearch-css/src/_variables.css b/packages/docsearch-css/src/_variables.css index 8fe577a1..427fdb5b 100644 --- a/packages/docsearch-css/src/_variables.css +++ b/packages/docsearch-css/src/_variables.css @@ -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%); diff --git a/packages/docsearch-css/src/button.css b/packages/docsearch-css/src/button.css index 830cc639..9a909912 100644 --- a/packages/docsearch-css/src/button.css +++ b/packages/docsearch-css/src/button.css @@ -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; diff --git a/packages/docsearch-css/src/modal.css b/packages/docsearch-css/src/modal.css index 6819ba7c..040f38bf 100644 --- a/packages/docsearch-css/src/modal.css +++ b/packages/docsearch-css/src/modal.css @@ -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 */ diff --git a/packages/docsearch-css/src/sidepanel.css b/packages/docsearch-css/src/sidepanel.css index 5a9f56b5..1a03b8f2 100644 --- a/packages/docsearch-css/src/sidepanel.css +++ b/packages/docsearch-css/src/sidepanel.css @@ -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 { diff --git a/packages/docsearch-react/src/AskAiScreen.tsx b/packages/docsearch-react/src/AskAiScreen.tsx index 219fafa9..b2f7e858 100644 --- a/packages/docsearch-react/src/AskAiScreen.tsx +++ b/packages/docsearch-react/src/AskAiScreen.tsx @@ -31,7 +31,10 @@ export type AskAiScreenTranslations = Partial< Omit & { // 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({ )}
- + (null); const [boundary, setBoundary] = React.useState(null); + const sourcesTitle = links.length > 1 ? pluralTitleText : titleText; + useEffect(() => { if (!open) { return undefined; @@ -73,7 +77,7 @@ export function SourcesPanel({ - {links.length} sources + {links.length} {sourcesTitle} - {titleText} + {sourcesTitle}
    {links.map((l) => (
  • diff --git a/packages/docsearch-react/src/components/ui/RecentConversationsResults.tsx b/packages/docsearch-react/src/components/ui/RecentConversationsResults.tsx index d3c0dbb5..a6eb0529 100644 --- a/packages/docsearch-react/src/components/ui/RecentConversationsResults.tsx +++ b/packages/docsearch-react/src/components/ui/RecentConversationsResults.tsx @@ -38,7 +38,7 @@ export function RecentConversationsResults({ title={recentConversationsTitle} collection={recentConversations} renderIcon={() => ( -
    +
    )}