From 4eaa9314edb103b4729ccba22fc741fae735f1bc Mon Sep 17 00:00:00 2001 From: Dan Piazza <220388267+DanPiazza-Netwrix@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:31:38 -0400 Subject: [PATCH 1/6] Fix mobile layout for Algolia search modal --- src/css/custom.css | 53 ++++++++++++++++++++++++++++++++++++ src/theme/SearchBar/index.js | 13 +++------ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/css/custom.css b/src/css/custom.css index 83d471e0b6..d283bd8f23 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -382,6 +382,59 @@ html { position: relative; } +/* Transfer the search-area visual chrome (background, bottom border, top radius) + from .DocSearch-Form to .DocSearch-SearchBar now that our custom controls are + siblings of the form rather than children of it. */ +.DocSearch-SearchBar { + background: var(--docsearch-searchbox-focus-background); + border-bottom: 1px solid var(--docsearch-subtle-color); + border-radius: 4px 4px 0 0; + align-items: center; +} + +/* Hide the "close the query" text label — keep only the × icon. + !important is needed because DocSearch styles are dynamically imported + after our CSS, so they would otherwise win the cascade. */ +.DocSearch-Clear { + font-size: 0 !important; +} +.DocSearch-Clear span { + display: none; +} + +/* Strip the duplicate chrome from the form itself. */ +.DocSearch-Form { + flex: 1 1 auto; + width: auto; + background: transparent; + border-block-end: none !important; + border-radius: 0; +} + +/* On mobile, wrap the custom controls onto their own row below the search input. */ +@media (max-width: 768px) { + .DocSearch-SearchBar { + flex-wrap: wrap; + } + + .search-custom-controls { + flex: 0 0 100%; + margin-right: 0 !important; + padding: 6px 16px 8px; + border-bottom: 1px solid var(--docsearch-subtle-color); + } + + /* Fix .DocSearch-Title — the library sets line-height: 0.5em which causes + wrapped lines to overlap, and there is no word-break so long query strings + overflow the viewport horizontally. */ + .DocSearch-Title { + line-height: 1.4 !important; + white-space: normal !important; + word-break: break-word; + overflow-wrap: break-word; + } +} + /* Responsive improvements */ @media (max-width: 768px) { .DocSearch-Button-Keys { diff --git a/src/theme/SearchBar/index.js b/src/theme/SearchBar/index.js index 2ebdd418d0..29b89f36ba 100644 --- a/src/theme/SearchBar/index.js +++ b/src/theme/SearchBar/index.js @@ -686,24 +686,18 @@ export default function SearchBar() { const [modalHeaderEl, setModalHeaderEl] = useState(null); const onModalOpen = useCallback(() => { - // Try to locate the header/searchbar area in the DocSearch modal. - // DocSearch v3 uses these classnames. + // Target .DocSearch-SearchBar so our controls are a sibling of .DocSearch-Form. + // This lets us move them below the search row on mobile via flex-wrap. const el = document.querySelector('.DocSearch-SearchBar') || - document.querySelector('.DocSearch-Form') || document.querySelector('.DocSearch-Modal'); setModalHeaderEl(el || null); }, []); - // When modalHeaderEl exists, insert filters BEFORE the form input if possible. - // We target .DocSearch-Form when present, otherwise the container itself. const portalTarget = useMemo(() => { if (!modalHeaderEl) return null; - return ( - modalHeaderEl.querySelector('.DocSearch-Form') || - modalHeaderEl - ); + return modalHeaderEl; }, [modalHeaderEl]); // Keep contextualSearch stable to prevent query reset @@ -724,6 +718,7 @@ export default function SearchBar() { createPortal( // Wrapper to align with DocSearch input
Date: Wed, 18 Mar 2026 13:45:23 -0400 Subject: [PATCH 2/6] Fix text wrap in search modal at certain screen widths --- src/css/custom.css | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/css/custom.css b/src/css/custom.css index d283bd8f23..dec597b75e 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -392,6 +392,15 @@ html { align-items: center; } +/* Fix .DocSearch-Title — the library sets line-height: 0.5em which causes + wrapped lines to overlap, and no word-break so long queries overflow horizontally. */ +.DocSearch-Title { + line-height: 1.4 !important; + white-space: normal !important; + word-break: break-word; + overflow-wrap: break-word; +} + /* Hide the "close the query" text label — keep only the × icon. !important is needed because DocSearch styles are dynamically imported after our CSS, so they would otherwise win the cascade. */ @@ -424,15 +433,7 @@ html { border-bottom: 1px solid var(--docsearch-subtle-color); } - /* Fix .DocSearch-Title — the library sets line-height: 0.5em which causes - wrapped lines to overlap, and there is no word-break so long query strings - overflow the viewport horizontally. */ - .DocSearch-Title { - line-height: 1.4 !important; - white-space: normal !important; - word-break: break-word; - overflow-wrap: break-word; - } + } /* Responsive improvements */ From db09e199cda0e703aedbde76de77e75b1c960182 Mon Sep 17 00:00:00 2001 From: Dan Piazza <220388267+DanPiazza-Netwrix@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:46:01 -0400 Subject: [PATCH 3/6] Dark Mode: Fix parts of the background on search results page --- src/theme/SearchPage/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/theme/SearchPage/index.js b/src/theme/SearchPage/index.js index cba39c632f..e76e08dc89 100644 --- a/src/theme/SearchPage/index.js +++ b/src/theme/SearchPage/index.js @@ -683,7 +683,7 @@ function SearchPageContent() { position: 'sticky', top: 'var(--ifm-navbar-height)', zIndex: 10, - backgroundColor: 'var(--ifm-navbar-background-color)', + backgroundColor: 'var(--ifm-background-color)', paddingTop: '16px', paddingBottom: '12px', }}> @@ -708,6 +708,8 @@ function SearchPageContent() { fontSize: '16px', borderRadius: '8px', border: '2px solid var(--ifm-color-emphasis-300)', + background: 'var(--ifm-background-color)', + color: 'var(--ifm-font-color-base)', marginBottom: '0', transition: 'border-color 0.2s', }} @@ -852,7 +854,7 @@ function SearchPageContent() { flexDirection: 'column', paddingTop: '16px', paddingBottom: '16px', - backgroundColor: 'var(--ifm-navbar-background-color)', + backgroundColor: 'var(--ifm-background-color)', }}> Date: Wed, 18 Mar 2026 16:55:31 -0400 Subject: [PATCH 4/6] Dark Mode: Improve general experience, contrast, backgrounds, buttons, etc. --- docusaurus.config.js | 2 +- .../CommunityHighlights/styles.module.css | 5 ++ src/pages/index.module.css | 4 ++ src/theme/ColorModeToggle/index.js | 60 +++++++++++++++++++ src/theme/ColorModeToggle/styles.module.css | 36 +++++++++++ src/theme/SearchBar/index.js | 2 +- 6 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/theme/ColorModeToggle/index.js create mode 100644 src/theme/ColorModeToggle/styles.module.css diff --git a/docusaurus.config.js b/docusaurus.config.js index 57175b8ae4..6b316bbc0b 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -218,7 +218,7 @@ const config = { logo: { alt: 'Netwrix Logo', src: 'branding/Netwrix_Logo_Dark.svg', - srcDark: 'branding/Netwrix_Logo_Light.svg', + srcDark: 'branding/logo-light.svg', href: '/', }, items: [ diff --git a/src/components/CommunityHighlights/styles.module.css b/src/components/CommunityHighlights/styles.module.css index fefcb3479b..d0087b6246 100644 --- a/src/components/CommunityHighlights/styles.module.css +++ b/src/components/CommunityHighlights/styles.module.css @@ -105,6 +105,11 @@ border-color: var(--ifm-color-emphasis-200); } +[data-theme='dark'] .featuredLabel { + background: linear-gradient(45deg, #8d1f1f, #835b00); + color: #fff; +} + .featuredCard::before { content: ''; position: absolute; diff --git a/src/pages/index.module.css b/src/pages/index.module.css index 181268cf43..171c653051 100644 --- a/src/pages/index.module.css +++ b/src/pages/index.module.css @@ -234,6 +234,10 @@ background: var(--ifm-background-color); } +[data-theme='dark'] .stats { + background: linear-gradient(135deg, #003c6c 0%, #005a9e 100%); +} + /* Responsive */ @media screen and (max-width: 996px) { .heroBanner { diff --git a/src/theme/ColorModeToggle/index.js b/src/theme/ColorModeToggle/index.js new file mode 100644 index 0000000000..280ae8b676 --- /dev/null +++ b/src/theme/ColorModeToggle/index.js @@ -0,0 +1,60 @@ +import React from 'react'; +import clsx from 'clsx'; +import useIsBrowser from '@docusaurus/useIsBrowser'; +import {translate} from '@docusaurus/Translate'; +import IconDarkMode from '@theme/Icon/DarkMode'; +import IconLightMode from '@theme/Icon/LightMode'; +import styles from './styles.module.css'; + +/** + * Swizzled ColorModeToggle. + * + * Docusaurus default shows the current-mode icon (sun in light, moon in dark). + * This version shows the target-mode icon instead: + * - Light mode → moon ("switch to dark") + * - Dark mode → sun ("switch to light") + */ +export default function ColorModeToggle({className, buttonClassName, value, onChange}) { + const isBrowser = useIsBrowser(); + const title = translate( + { + message: 'Switch between dark and light mode (currently {mode})', + id: 'theme.colorToggle.ariaLabel', + description: 'The ARIA label for the navbar color mode toggle', + }, + { + mode: + value === 'dark' + ? translate({ + message: 'dark mode', + id: 'theme.colorToggle.ariaLabel.mode.dark', + description: 'The name for the dark color mode', + }) + : translate({ + message: 'light mode', + id: 'theme.colorToggle.ariaLabel.mode.light', + description: 'The name for the light color mode', + }), + }, + ); + + return ( +
+ +
+ ); +} diff --git a/src/theme/ColorModeToggle/styles.module.css b/src/theme/ColorModeToggle/styles.module.css new file mode 100644 index 0000000000..e2178235a8 --- /dev/null +++ b/src/theme/ColorModeToggle/styles.module.css @@ -0,0 +1,36 @@ +.toggle { + display: flex; + align-items: center; +} + +.toggleButton { + display: flex; + overflow: hidden; +} + +.toggleButtonDisabled { + cursor: not-allowed; +} + +.icon { + width: 1.4rem; + height: 1.4rem; +} + +/* Moon (lightModeIcon) visible in light mode */ +.lightModeIcon { + display: block; +} + +/* Sun (darkModeIcon) hidden in light mode */ +.darkModeIcon { + display: none; +} + +[data-theme='dark'] .lightModeIcon { + display: none; +} + +[data-theme='dark'] .darkModeIcon { + display: block; +} diff --git a/src/theme/SearchBar/index.js b/src/theme/SearchBar/index.js index 29b89f36ba..4c912a2cd4 100644 --- a/src/theme/SearchBar/index.js +++ b/src/theme/SearchBar/index.js @@ -390,7 +390,7 @@ function MultiSelectDropdown({label, options, selectedValues, onChange, placehol padding: '8px 12px', cursor: 'pointer', fontSize: '14px', - background: isSelected ? 'var(--docsearch-hit-active-color)' : 'transparent', + background: 'transparent', }} onMouseEnter={(e) => { if (!isSelected) { From abc741b49978f151fdddf3a7cab3d5743e5ebc39 Mon Sep 17 00:00:00 2001 From: Dan Piazza <220388267+DanPiazza-Netwrix@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:24:13 -0400 Subject: [PATCH 5/6] Change position of search modal "close" icon so it's always in the upper-right on both mobile and desktop --- src/css/custom.css | 64 +++++++++++++++++---- src/theme/SearchBar/index.js | 105 ++++++++++++++++++----------------- 2 files changed, 107 insertions(+), 62 deletions(-) diff --git a/src/css/custom.css b/src/css/custom.css index dec597b75e..b758549aea 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -407,37 +407,77 @@ html { .DocSearch-Clear { font-size: 0 !important; } -.DocSearch-Clear span { - display: none; -} -/* Strip the duplicate chrome from the form itself. */ +/* Strip the duplicate chrome from the form itself. + width: auto !important overrides DocSearch's dynamically-loaded width: 100%, + which would otherwise force the form to consume the entire first flex row and + push the × button onto its own line on mobile. */ .DocSearch-Form { flex: 1 1 auto; - width: auto; + width: auto !important; background: transparent; border-block-end: none !important; border-radius: 0; } -/* On mobile, wrap the custom controls onto their own row below the search input. */ +/* Hide the DocSearch-owned close button — we render our own in the portal so + we never mutate React-owned DOM nodes (which causes reconciliation crashes). */ +.DocSearch-Close { + display: none !important; +} + +/* Custom controls (Typo Tolerance toggle + product filter) portaled into the search bar. */ +.search-custom-controls { + display: flex; + align-items: center; + gap: 8px; + margin-right: 8px; +} + +/* Our replacement close button rendered inside the portal. */ +.search-modal-close { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + background: none; + border: none; + cursor: pointer; + color: var(--docsearch-muted-color); + padding: 0; + margin: 0 16px 0 8px; +} +.search-modal-close:hover { + color: var(--docsearch-text-color); +} +[data-theme='dark'] .search-modal-close { + color: white; +} + +/* On mobile, wrap the custom controls onto their own row below the search input, + but keep the × button on the first row (upper-right corner). */ @media (max-width: 768px) { .DocSearch-SearchBar { flex-wrap: wrap; } + .DocSearch-Form { + order: 1; + } + + .search-modal-close { + order: 2; + margin: 0 16px 0 8px; + } + .search-custom-controls { + order: 3; flex: 0 0 100%; - margin-right: 0 !important; + margin-right: 0; padding: 6px 16px 8px; border-bottom: 1px solid var(--docsearch-subtle-color); } - -} - -/* Responsive improvements */ -@media (max-width: 768px) { .DocSearch-Button-Keys { display: none; } diff --git a/src/theme/SearchBar/index.js b/src/theme/SearchBar/index.js index 4c912a2cd4..83cb7e1920 100644 --- a/src/theme/SearchBar/index.js +++ b/src/theme/SearchBar/index.js @@ -441,20 +441,20 @@ function DocSearch({externalUrlRegex, onModalOpen, selectedProductsRef, typoTole } }, []); + const closeModal = useCallback(() => { + setIsOpen(false); + searchButtonRef.current?.focus(); + setInitialQuery(undefined); + }, []); + const openModal = useCallback(() => { prepareSearchContainer(); importDocSearchModalIfNeeded().then(() => { setIsOpen(true); // Let React render the modal, then caller can locate DOM nodes - setTimeout(() => onModalOpen?.(), 0); + setTimeout(() => onModalOpen?.(closeModal), 0); }); - }, [prepareSearchContainer, onModalOpen]); - - const closeModal = useCallback(() => { - setIsOpen(false); - searchButtonRef.current?.focus(); - setInitialQuery(undefined); - }, []); + }, [prepareSearchContainer, onModalOpen, closeModal]); const handleInput = useCallback( (event) => { @@ -684,22 +684,20 @@ export default function SearchBar() { // This is where we will portal the filters into the modal DOM. const [modalHeaderEl, setModalHeaderEl] = useState(null); + // Holds the closeModal function passed up from the inner DocSearch component. + const closeModalRef = useRef(null); - const onModalOpen = useCallback(() => { + const onModalOpen = useCallback((closeModal) => { // Target .DocSearch-SearchBar so our controls are a sibling of .DocSearch-Form. // This lets us move them below the search row on mobile via flex-wrap. const el = document.querySelector('.DocSearch-SearchBar') || document.querySelector('.DocSearch-Modal'); + closeModalRef.current = closeModal ?? null; setModalHeaderEl(el || null); }, []); - const portalTarget = useMemo(() => { - if (!modalHeaderEl) return null; - return modalHeaderEl; - }, [modalHeaderEl]); - // Keep contextualSearch stable to prevent query reset // Product filters are handled via transformSearchClient instead const contextualSearch = false; @@ -714,45 +712,52 @@ export default function SearchBar() { onModalOpen={onModalOpen} /> - {portalTarget && + {modalHeaderEl && createPortal( - // Wrapper to align with DocSearch input -
- -
- + {/* Custom controls: Typo Tolerance + product filter */} +
+
+ + + Typo Tolerance + +
+ - - Typo Tolerance -
-
, - portalTarget, + {/* Our own close button — replaces the DocSearch-Close button + (which is hidden via CSS) so we avoid mutating React-owned + DOM nodes, which causes reconciliation errors. */} + + , + modalHeaderEl, )} ); From a1c6b6ce8863c303576e3e6e425cbb0bca91b760 Mon Sep 17 00:00:00 2001 From: Dan Piazza <220388267+DanPiazza-Netwrix@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:53:01 -0400 Subject: [PATCH 6/6] Fix how full search results page scrolls for both light and dark mode --- src/css/custom.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/css/custom.css b/src/css/custom.css index b758549aea..64c9272c6a 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -62,6 +62,10 @@ --ifm-color-success: #34C263; --ifm-color-success-dark: #27914A; --ifm-button-background-color: #5851DB; + /* Infima defaults this to `transparent` (relying on the browser's white + canvas). An explicit opaque value is needed so that sticky elements + like search-page controls can hide content scrolling behind them. */ + --ifm-background-color: #fff; } .table-of-contents {