diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/NavigationSearch.tsx b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/NavigationSearch.tsx new file mode 100644 index 000000000..854fd260d --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/NavigationSearch.tsx @@ -0,0 +1,140 @@ +import { KeyboardShortcutsFooter } from '../KeyboardShortcutsFooter' +import { useSearchTerm, useSearchActions } from '../Search/search.store' +import { useIsSearchCooldownActive } from '../Search/useSearchCooldown' +import { useSearchQuery } from '../Search/useSearchQuery' +import { SearchDropdownHeader } from './SearchDropdownHeader' +import { SearchInput } from './SearchInput' +import { SearchResultsList } from './SearchResultsList' +import { useGlobalKeyboardShortcut } from './useGlobalKeyboardShortcut' +import { useNavigationSearchKeyboardNavigation } from './useNavigationSearchKeyboardNavigation' +import { EuiInputPopover, EuiHorizontalRule } from '@elastic/eui' +import { css } from '@emotion/react' +import { useState, useRef } from 'react' + +export const NavigationSearch = () => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false) + const popoverContentRef = useRef(null) + const searchTerm = useSearchTerm() + const { setSearchTerm } = useSearchActions() + const isSearchCooldownActive = useIsSearchCooldownActive() + const { isLoading, isFetching, data } = useSearchQuery() + + const results = data?.results ?? [] + const hasContent = !!searchTerm.trim() + const isSearching = isLoading || isFetching + + const { + inputRef, + itemRefs, + isKeyboardNavigating, + handleInputKeyDown, + handleMouseMove, + } = useNavigationSearchKeyboardNavigation({ + resultsCount: results.length, + isLoading: isSearching, + onClose: () => setIsPopoverOpen(false), + }) + + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value + setSearchTerm(value) + setIsPopoverOpen(!!value.trim()) + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault() + setSearchTerm('') + setIsPopoverOpen(false) + return + } + handleInputKeyDown(e) + } + + const handleBlur = (e: React.FocusEvent) => { + // Check if focus is moving to something inside the popover + const relatedTarget = e.relatedTarget as Node | null + if ( + relatedTarget && + popoverContentRef.current?.contains(relatedTarget) + ) { + // Focus is moving inside the popover, don't close + return + } + setIsPopoverOpen(false) + } + + useGlobalKeyboardShortcut('k', () => inputRef.current?.focus()) + + return ( + setIsPopoverOpen(false)} + ownFocus={false} + disableFocusTrap={true} + panelMinWidth={640} + panelPaddingSize="none" + offset={12} + panelProps={{ + css: css` + max-width: 640px; + `, + onMouseDown: (e: React.MouseEvent) => { + // Prevent input blur when clicking anywhere inside the popover panel + e.preventDefault() + }, + }} + input={ + hasContent && setIsPopoverOpen(true)} + onBlur={handleBlur} + onKeyDown={handleKeyDown} + disabled={isSearchCooldownActive} + isLoading={isSearching} + /> + } + > + {hasContent && ( +
+ +
+ )} +
+ ) +} + +const KEYBOARD_SHORTCUTS = [ + { keys: ['returnKey'], label: 'Jump to' }, + { keys: ['sortUp', 'sortDown'], label: 'Navigate' }, + { keys: ['Esc'], label: 'Close' }, +] + +interface SearchDropdownContentProps { + itemRefs: React.MutableRefObject<(HTMLAnchorElement | null)[]> + isKeyboardNavigating: React.MutableRefObject + onMouseMove: () => void +} + +const SearchDropdownContent = ({ + itemRefs, + isKeyboardNavigating, + onMouseMove, +}: SearchDropdownContentProps) => ( + <> + + + + + +) diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SanitizedHtmlContent.tsx b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SanitizedHtmlContent.tsx new file mode 100644 index 000000000..09dd6acbe --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SanitizedHtmlContent.tsx @@ -0,0 +1,42 @@ +import DOMPurify from 'dompurify' +import { memo, useMemo } from 'react' + +interface SanitizedHtmlContentProps { + htmlContent: string + ellipsis?: boolean +} + +export const SanitizedHtmlContent = memo( + ({ htmlContent, ellipsis }: SanitizedHtmlContentProps) => { + const processed = useMemo(() => { + if (!htmlContent) return '' + + const sanitized = DOMPurify.sanitize(htmlContent, { + ALLOWED_TAGS: ['mark'], + ALLOWED_ATTR: [], + KEEP_CONTENT: true, + }) + + if (!ellipsis) { + return sanitized + } + + const temp = document.createElement('div') + temp.innerHTML = sanitized + + const text = temp.textContent || '' + const firstChar = text.trim()[0] + + // Add ellipsis when text starts mid-sentence to indicate continuation + if (firstChar && /[a-z]/.test(firstChar)) { + return '… ' + sanitized + } + + return sanitized + }, [htmlContent, ellipsis]) + + return + } +) + +SanitizedHtmlContent.displayName = 'SanitizedHtmlContent' diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SearchDropdownHeader.tsx b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SearchDropdownHeader.tsx new file mode 100644 index 000000000..a384bc98a --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SearchDropdownHeader.tsx @@ -0,0 +1,62 @@ +import { + EuiBetaBadge, + EuiLink, + useEuiTheme, + useEuiFontSize, +} from '@elastic/eui' +import { css } from '@emotion/react' + +const FEEDBACK_URL = + 'https://github.com/elastic/docs-eng-team/issues/new?template=search-or-ask-ai-feedback.yml' + +export const SearchDropdownHeader = () => { + const { euiTheme } = useEuiTheme() + const { fontSize: xsFontsize } = useEuiFontSize('xs') + + return ( +
+
+ + Pages + + · + +
+ + Give feedback + +
+ ) +} diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SearchInput.tsx b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SearchInput.tsx new file mode 100644 index 000000000..d23b65e6e --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SearchInput.tsx @@ -0,0 +1,107 @@ +import { EuiIcon, EuiLoadingSpinner, useEuiTheme } from '@elastic/eui' +import { css } from '@emotion/react' + +export interface SearchInputProps { + inputRef: React.RefObject + value: string + onChange: (e: React.ChangeEvent) => void + onFocus: () => void + onBlur: (e: React.FocusEvent) => void + onKeyDown: (e: React.KeyboardEvent) => void + disabled: boolean + isLoading: boolean +} + +export const SearchInput = ({ + inputRef, + value, + onChange, + onFocus, + onBlur, + onKeyDown, + disabled, + isLoading, +}: SearchInputProps) => { + const { euiTheme } = useEuiTheme() + + return ( +
+ + {isLoading ? ( + + ) : ( + + )} + + + + + + ⌘K + +
+ ) +} diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SearchResultsList.tsx b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SearchResultsList.tsx new file mode 100644 index 000000000..ead8bb7f4 --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/SearchResultsList.tsx @@ -0,0 +1,282 @@ +import { useSelectedIndex, useSearchActions } from '../Search/search.store' +import { useSearchQuery, SearchResultItem } from '../Search/useSearchQuery' +import { SanitizedHtmlContent } from './SanitizedHtmlContent' +import { EuiBadge, EuiIcon, EuiLoadingSpinner, useEuiTheme } from '@elastic/eui' +import { css } from '@emotion/react' +import { forwardRef, useMemo, MutableRefObject } from 'react' + +const RESULTS_MAX_HEIGHT = 400 +const BREADCRUMB_SEPARATOR = ' / ' + +export interface SearchResultsListProps { + itemRefs: MutableRefObject<(HTMLAnchorElement | null)[]> + isKeyboardNavigating: MutableRefObject + onMouseMove: () => void +} + +export const SearchResultsList = ({ + itemRefs, + isKeyboardNavigating, + onMouseMove, +}: SearchResultsListProps) => { + const { euiTheme } = useEuiTheme() + const selectedIndex = useSelectedIndex() + const { setSelectedIndex } = useSearchActions() + const { isLoading, data } = useSearchQuery() + + const results = data?.results ?? [] + const isInitialLoading = isLoading && !data + + const containerStyles = css` + max-height: ${RESULTS_MAX_HEIGHT}px; + overflow-y: auto; + ` + + const emptyStateStyles = css` + padding: ${euiTheme.size.xl}; + text-align: center; + color: ${euiTheme.colors.textSubdued}; + ` + + if (isInitialLoading) { + return ( +
+
+ +
+
+ ) + } + + if (results.length === 0) { + return ( +
+
No results found
+
+ ) + } + + const handleMouseEnter = (index: number) => { + if (!isKeyboardNavigating.current) { + setSelectedIndex(index) + } + } + + const handleItemMouseMove = (index: number) => { + if (isKeyboardNavigating.current) { + onMouseMove() + setSelectedIndex(index) + } + } + + return ( +
+ {results.map((result, index) => ( + handleMouseEnter(index)} + onMouseMove={() => handleItemMouseMove(index)} + ref={(el) => { + itemRefs.current[index] = el + }} + /> + ))} +
+ ) +} + +interface SearchResultRowProps { + result: SearchResultItem + isSelected: boolean + onMouseEnter: () => void + onMouseMove: () => void +} + +const SearchResultRow = forwardRef( + ({ result, isSelected, onMouseEnter, onMouseMove }, ref) => { + const { euiTheme } = useEuiTheme() + + const breadcrumbItems = useMemo(() => { + const typePrefix = result.type === 'api' ? 'API' : 'Docs' + return [typePrefix, ...result.parents.slice(1).map((p) => p.title)] + }, [result.type, result.parents]) + + return ( + +
+ + + {result.description && ( + <Description text={result.description} /> + )} + </div> + {isSelected && <JumpToIndicator />} + </a> + ) + } +) + +SearchResultRow.displayName = 'SearchResultRow' + +/** + * Breadcrumb with 3-part layout: + * - First: always visible + * - Middle: truncates with ellipsis + * - Last: always visible + */ +const Breadcrumb = ({ items }: { items: string[] }) => { + const { euiTheme } = useEuiTheme() + + if (items.length === 0) return null + + const baseStyles = css` + font-size: ${euiTheme.size.m}; + color: ${euiTheme.colors.textSubdued}; + margin-bottom: ${euiTheme.size.xs}; + ` + + if (items.length === 1) { + return <div css={baseStyles}>{items[0]}</div> + } + + const first = items[0] + const middle = items.slice(1, -1) + const last = items[items.length - 1] + + return ( + <div + css={css` + ${baseStyles} + display: flex; + white-space: nowrap; + overflow: hidden; + `} + > + <span + css={css` + flex-shrink: 0; + `} + > + {first} + </span> + + {middle.length > 0 && ( + <span + css={css` + flex-shrink: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + margin-left: 0.5ch; + `} + > + {BREADCRUMB_SEPARATOR} + {middle.join(BREADCRUMB_SEPARATOR)} + </span> + )} + + <span + css={css` + flex-shrink: 0; + margin-left: 0.5ch; + `} + > + {BREADCRUMB_SEPARATOR} + {last} + </span> + </div> + ) +} + +const Title = ({ text }: { text: string }) => { + const { euiTheme } = useEuiTheme() + + return ( + <div + css={css` + font-weight: ${euiTheme.font.weight.semiBold}; + color: ${euiTheme.colors.textParagraph}; + margin-bottom: ${euiTheme.size.xs}; + + mark { + background-color: ${euiTheme.colors.backgroundBasePrimary}; + font-weight: ${euiTheme.font.weight.bold}; + } + `} + > + <SanitizedHtmlContent htmlContent={text} /> + </div> + ) +} + +const Description = ({ text }: { text: string }) => { + const { euiTheme } = useEuiTheme() + + return ( + <div + css={css` + font-size: 13px; + color: ${euiTheme.colors.textSubdued}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + mark { + background-color: ${euiTheme.colors.backgroundBasePrimary}; + font-weight: ${euiTheme.font.weight.bold}; + } + `} + > + <SanitizedHtmlContent htmlContent={text} ellipsis /> + </div> + ) +} + +const JumpToIndicator = () => { + const { euiTheme } = useEuiTheme() + + return ( + <div + css={css` + flex-shrink: 0; + color: ${euiTheme.colors.textSubdued}; + margin-left: ${euiTheme.size.xxxxl}; + `} + > + <EuiBadge color="hollow"> + Jump to <EuiIcon type="returnKey" size="s" /> + </EuiBadge> + </div> + ) +} diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/index.ts b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/index.ts new file mode 100644 index 000000000..c0fab95fc --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/index.ts @@ -0,0 +1 @@ +export { NavigationSearch } from './NavigationSearch' diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/useGlobalKeyboardShortcut.ts b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/useGlobalKeyboardShortcut.ts new file mode 100644 index 000000000..1a7fce933 --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/useGlobalKeyboardShortcut.ts @@ -0,0 +1,20 @@ +import { useEffect } from 'react' + +/** + * Hook to listen for global keyboard shortcuts with Cmd/Ctrl modifier + */ +export const useGlobalKeyboardShortcut = ( + key: string, + callback: () => void +) => { + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === key) { + e.preventDefault() + callback() + } + } + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [key, callback]) +} diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/useNavigationSearchKeyboardNavigation.ts b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/useNavigationSearchKeyboardNavigation.ts new file mode 100644 index 000000000..a08631d3f --- /dev/null +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/NavigationSearch/useNavigationSearchKeyboardNavigation.ts @@ -0,0 +1,90 @@ +import { useSelectedIndex, useSearchActions } from '../Search/search.store' +import { useRef, useCallback, MutableRefObject } from 'react' + +interface Options { + resultsCount: number + isLoading: boolean + onClose: () => void +} + +interface Result { + inputRef: React.RefObject<HTMLInputElement> + itemRefs: MutableRefObject<(HTMLAnchorElement | null)[]> + isKeyboardNavigating: MutableRefObject<boolean> + handleInputKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void + handleMouseMove: () => void +} + +export const useNavigationSearchKeyboardNavigation = ({ + resultsCount, + isLoading, + onClose, +}: Options): Result => { + const inputRef = useRef<HTMLInputElement>(null) + const itemRefs = useRef<(HTMLAnchorElement | null)[]>([]) + const isKeyboardNavigating = useRef(false) + const selectedIndex = useSelectedIndex() + const { setSelectedIndex } = useSearchActions() + + const handleMouseMove = useCallback(() => { + isKeyboardNavigating.current = false + }, []) + + const scrollToItem = (index: number) => { + const element = itemRefs.current[index] + element?.scrollIntoView?.({ block: 'nearest' }) + } + + const navigateToResult = (index: number) => { + itemRefs.current[index]?.click() + } + + const handleInputKeyDown = useCallback( + (e: React.KeyboardEvent<HTMLInputElement>) => { + switch (e.key) { + case 'Escape': + e.preventDefault() + onClose() + break + + case 'Enter': + e.preventDefault() + if (isLoading || resultsCount === 0) return + navigateToResult(selectedIndex >= 0 ? selectedIndex : 0) + break + + case 'ArrowDown': + e.preventDefault() + isKeyboardNavigating.current = true + if (resultsCount > 0) { + const nextIndex = + selectedIndex < 0 + ? 0 + : Math.min(selectedIndex + 1, resultsCount - 1) + setSelectedIndex(nextIndex) + scrollToItem(nextIndex) + } + break + + case 'ArrowUp': + e.preventDefault() + isKeyboardNavigating.current = true + if (resultsCount > 0 && selectedIndex > 0) { + const prevIndex = selectedIndex - 1 + setSelectedIndex(prevIndex) + scrollToItem(prevIndex) + } + break + } + }, + [resultsCount, isLoading, selectedIndex, setSelectedIndex, onClose] + ) + + return { + inputRef, + itemRefs, + isKeyboardNavigating, + handleInputKeyDown, + handleMouseMove, + } +} diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/Search/search.store.ts b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/Search/search.store.ts index 58ba78c03..35722a0f9 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/Search/search.store.ts +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/Search/search.store.ts @@ -22,7 +22,7 @@ interface SearchState { export const searchStore = create<SearchState>((set) => ({ searchTerm: '', - page: 1, + page: 0, typeFilter: 'all', selectedIndex: NO_SELECTION, actions: { diff --git a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/SearchOrAskAiButton.tsx b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/SearchOrAskAiButton.tsx index 0f3049a0f..2e75eb30a 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/SearchOrAskAiButton.tsx +++ b/src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/SearchOrAskAiButton.tsx @@ -1,7 +1,5 @@ import '../../eui-icons-cache' -import { ElasticAiAssistantButton } from './ElasticAiAssitant' -import { useSearchTerm } from './Search/search.store' -import AiIcon from './ai-icon.svg' +import { NavigationSearch } from './NavigationSearch' import { ModalMode, useModalActions, @@ -9,16 +7,12 @@ import { useModalMode, } from './modal.store' import { - EuiButton, EuiPortal, EuiOverlayMask, EuiFocusTrap, EuiPanel, - EuiTextTruncate, - EuiText, EuiLoadingSpinner, useEuiTheme, - EuiToolTip, } from '@elastic/eui' import { css } from '@emotion/react' import { useQuery } from '@tanstack/react-query' @@ -33,7 +27,6 @@ const SearchOrAskAiModal = lazy(() => export const SearchOrAskAiButton = () => { const { euiTheme } = useEuiTheme() - const searchTerm = useSearchTerm() const isModalOpen = useModalIsOpen() const modalMode = useModalMode() const { openModal, closeModal, setModalMode } = useModalActions() @@ -73,7 +66,7 @@ export const SearchOrAskAiButton = () => { } const openAskAiModal = () => openAndSetModalMode('askAi') - const openSearchModal = () => openAndSetModalMode('search') + // const openSearchModal = () => openAndSetModalMode('search') // Prevent layout jump when hiding the scrollbar by compensating its width @@ -82,11 +75,11 @@ export const SearchOrAskAiButton = () => { if (event.key === 'Escape') { closeModal() } - if ((event.metaKey || event.ctrlKey) && event.key === 'k') { - event.preventDefault() - openSearchModal() - // Input focuses itself via its own Cmd+K listener - } + // Cmd+K is now handled by NavigationSearch to focus the input + // if ((event.metaKey || event.ctrlKey) && event.key === 'k') { + // event.preventDefault() + // openSearchModal() + // } if ( (event.metaKey || event.ctrlKey) && @@ -141,43 +134,17 @@ export const SearchOrAskAiButton = () => { gap: ${euiTheme.size.base}; `} > - <EuiToolTip content="Keyboard shortcut: ⌘;"> - <ElasticAiAssistantButton - size="s" - iconType={AiIcon} - onClick={openAskAiModal} - > - Ask AI Assistant - </ElasticAiAssistantButton> - </EuiToolTip> - - <EuiButton - size="s" - color="text" - onClick={openSearchModal} - iconType="search" - > - <EuiText - color="subdued" - size="s" - style={{ width: 200 }} - textAlign="left" - > - <span> - {searchTerm ? ( - <EuiTextTruncate - text={searchTerm} - truncation="end" - /> - ) : ( - 'Search in Docs' - )} - </span> - </EuiText> - <EuiText color="subdued" size="xs"> - <kbd className="font-body bg-grey-20 border-none!">⌘K</kbd> - </EuiText> - </EuiButton> + {/*<EuiToolTip content="Keyboard shortcut: ⌘;">*/} + {/* <ElasticAiAssistantButton*/} + {/* size="s"*/} + {/* iconType={AiIcon}*/} + {/* onClick={openAskAiModal}*/} + {/* >*/} + {/* Ask AI Assistant*/} + {/* </ElasticAiAssistantButton>*/} + {/*</EuiToolTip>*/} + + <NavigationSearch /> {isModalOpen && ( <EuiPortal> <EuiOverlayMask> diff --git a/src/Elastic.Documentation.Site/Layout/_SecondaryNav.cshtml b/src/Elastic.Documentation.Site/Layout/_SecondaryNav.cshtml index fca499cca..a8df279be 100644 --- a/src/Elastic.Documentation.Site/Layout/_SecondaryNav.cshtml +++ b/src/Elastic.Documentation.Site/Layout/_SecondaryNav.cshtml @@ -13,10 +13,6 @@ } <a href="@Model.Link("/")" class="text-lg leading-[1em] hover:text-blue-elastic active:text-blue-elastic-100">Docs</a> </div> - @if (Model.Features.SearchOrAskAiEnabled) - { - <search-or-ask-ai></search-or-ask-ai> - } <ul class="flex gap-6"> <li class="text-nowrap hover:text-blue-elastic active:text-blue-elastic-100"> <a diff --git a/src/Elastic.Documentation.Site/Navigation/_TocTree.cshtml b/src/Elastic.Documentation.Site/Navigation/_TocTree.cshtml index 42b593a21..7ec59c672 100644 --- a/src/Elastic.Documentation.Site/Navigation/_TocTree.cshtml +++ b/src/Elastic.Documentation.Site/Navigation/_TocTree.cshtml @@ -5,6 +5,11 @@ @{ var currentTopLevelItem = Model.TopLevelItems.FirstOrDefault(i => i.Id == Model.Tree.Id) ?? Model.Tree; } + + <div class="mr-4 mt-6 h-10.5"> + <search-or-ask-ai></search-or-ask-ai> + </div> + @if (Model.IsUsingNavigationDropdown) { <div class="sticky top-0 py-6 bg-white z-10 border-b-1 border-grey-20 pr-4">