Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { MultiFieldSearch, SetSomeFieldValues } from '../../../types/config
import { CustomTooltip } from '../../../utils/CustomTooltip';
import type { MetadataFilterSchema } from '../../../utils/search.ts';
import DisabledUntilHydrated from '../../DisabledUntilHydrated';
import { Button } from '../../common/Button.tsx';
import MaterialSymbolsHelpOutline from '~icons/material-symbols/help-outline';

export interface MultiFieldSearchFieldProps {
Expand Down Expand Up @@ -33,10 +34,13 @@ export const MultiFieldSearchField = ({
autoComplete='off'
/>
</DisabledUntilHydrated>
<div className='absolute top-1/2 -translate-y-1/3 right-1.5'>
<span data-tooltip-id={tooltipId} className='text-gray-400 hover:text-primary-600 inline-flex'>
<div className='absolute top-1/2 -translate-y-1/3 right-0'>
<Button
Comment thread
tombch marked this conversation as resolved.
data-tooltip-id={tooltipId}
className='text-gray-400 hover:text-primary-600 inline-flex cursor-default px-2'
>
<MaterialSymbolsHelpOutline className='inline-block h-6 w-5' />
</span>
</Button>
</div>
<CustomTooltip id={tooltipId} place='top'>
<p className='mb-1'>Search across the following fields:</p>
Expand Down
23 changes: 15 additions & 8 deletions website/src/utils/CustomTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import type { ComponentProps, FC } from 'react';
import { Tooltip } from 'react-tooltip';

export const CustomTooltip: FC<ComponentProps<typeof Tooltip>> = ({ className, ...props }) => (
export const CustomTooltip: FC<ComponentProps<typeof Tooltip>> = ({ className, ...props }) => {
const isTouchOnly = typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This runs during render and causes an SSR/hydration mismatch in Astro — the server renders with isTouchOnly = false (no window), but a touch client re-renders with true. Between SSR and hydration, hover events briefly fire on touch devices. Prefer useState(false) + useEffect:

Suggested change
const isTouchOnly = typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches;
const [isTouchOnly, setIsTouchOnly] = useState(false);
useEffect(() => {
setIsTouchOnly(window.matchMedia('(pointer: coarse)').matches);
}, []);

And update the import to import { type ComponentProps, type FC, useEffect, useState } from 'react';

Copy link
Copy Markdown
Collaborator Author

@tombch tombch May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't cause a SSR issue as far as I understand, as isTouchOnly doesn't affect the rendered DOM. So I think adding state and an effect here is unnecessary. I also tested this with firefox's mobile browsing simulator and it didn't come up with any hydration errors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think it's because the tooltip isn't rendered until you mouseover


// Set positionStrategy and z-index to make the Tooltip float above the ReviewPage toolbar
<Tooltip
positionStrategy='fixed'
place='right'
className={`z-20 max-w-sm whitespace-pre-wrap break-words ${className ?? ''}`}
{...props}
/>
);
return (
<Tooltip
positionStrategy='fixed'
place='right'
className={`z-20 max-w-sm whitespace-pre-wrap break-words ${className ?? ''}`}
openEvents={{ mouseenter: !isTouchOnly, click: isTouchOnly, focus: false }}
closeEvents={{ mouseleave: !isTouchOnly, click: isTouchOnly, blur: true }}
globalCloseEvents={{ clickOutsideAnchor: true, scroll: true }}
{...props}
/>
);
};
Loading