mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-27 21:49:46 -05:00
Redesign: Fix search dropdown opening on navigation-triggered autofocus (#40656)
This commit is contained in:
@@ -99,15 +99,20 @@ export const FocusTargetProvider: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
export function useFocusOnNavigation(targetName?: NamedFocusTarget) {
|
||||
export function useFocusAfterNavigation(
|
||||
targetName?: NamedFocusTarget,
|
||||
onAfterFocus?: (hasSetFocus: boolean) => void,
|
||||
) {
|
||||
const focusTargetRef = useContext(FocusTargetContext);
|
||||
const hasFocusedOnceRef = useRef(false);
|
||||
|
||||
return useCallback(
|
||||
(element: HTMLElement | null) => {
|
||||
const focusTarget = focusTargetRef?.current;
|
||||
|
||||
// Bail out if focusTarget was set to `false`
|
||||
if (!element || !focusTarget) {
|
||||
if (!element || !focusTarget || hasFocusedOnceRef.current) {
|
||||
onAfterFocus?.(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -118,10 +123,15 @@ export function useFocusOnNavigation(targetName?: NamedFocusTarget) {
|
||||
if (shouldSetFocus) {
|
||||
setTimeout(() => {
|
||||
element.focus({ preventScroll: true });
|
||||
onAfterFocus?.(true);
|
||||
// Prevent this focus handler from setting focus again
|
||||
hasFocusedOnceRef.current = true;
|
||||
}, 0);
|
||||
} else {
|
||||
onAfterFocus?.(false);
|
||||
}
|
||||
},
|
||||
[focusTargetRef, targetName],
|
||||
[focusTargetRef, onAfterFocus, targetName],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -136,10 +146,10 @@ export const NavigationFocusTarget = <As extends React.ElementType = 'h1'>({
|
||||
...otherProps
|
||||
}: PolymorphicProps<FocusTargetElementProps, As>) => {
|
||||
const Component = asComp ?? 'h1';
|
||||
const focusOnNavigation = useFocusOnNavigation(focusTargetName);
|
||||
const focusAfterNavigation = useFocusAfterNavigation(focusTargetName);
|
||||
|
||||
return (
|
||||
<Component ref={focusOnNavigation} tabIndex={-1} {...otherProps}>
|
||||
<Component ref={focusAfterNavigation} tabIndex={-1} {...otherProps}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@ import { isFulfilled } from '@reduxjs/toolkit';
|
||||
|
||||
import {
|
||||
FOCUS_TARGET,
|
||||
useFocusOnNavigation,
|
||||
useFocusAfterNavigation,
|
||||
} from '@/mastodon/components/navigation_focus_target';
|
||||
import { getCollectionPath } from '@/mastodon/features/collections/utils';
|
||||
import { useMergedRefs } from '@/mastodon/hooks/useMergedRefs';
|
||||
@@ -111,7 +111,13 @@ export const Search: React.FC<{
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState(-1);
|
||||
const [quickActions, setQuickActions] = useState<SearchOption[]>([]);
|
||||
const focusOnNavigation = useFocusOnNavigation(FOCUS_TARGET.SEARCH);
|
||||
const [shouldOpenOnFocus, setShouldOpenOnFocus] = useState(false);
|
||||
const focusAfterNavigation = useFocusAfterNavigation(
|
||||
FOCUS_TARGET.SEARCH,
|
||||
() => {
|
||||
setShouldOpenOnFocus(true);
|
||||
},
|
||||
);
|
||||
|
||||
const unfocus = useCallback(() => {
|
||||
document.querySelector('.ui')?.parentElement?.focus();
|
||||
@@ -318,6 +324,8 @@ export const Search: React.FC<{
|
||||
const handleChange = useCallback(
|
||||
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(value);
|
||||
setExpanded(true);
|
||||
setSelectedOption(-1);
|
||||
|
||||
const trimmedValue = value.trim();
|
||||
const newQuickActions = [];
|
||||
@@ -504,8 +512,10 @@ export const Search: React.FC<{
|
||||
);
|
||||
|
||||
const handleInputFocus = useCallback(() => {
|
||||
setExpanded(true);
|
||||
setSelectedOption(-1);
|
||||
if (shouldOpenOnFocus) {
|
||||
setExpanded(true);
|
||||
setSelectedOption(-1);
|
||||
}
|
||||
|
||||
if (searchInputRef.current && !singleColumn) {
|
||||
const { left, right } = searchInputRef.current.getBoundingClientRect();
|
||||
@@ -517,7 +527,7 @@ export const Search: React.FC<{
|
||||
searchInputRef.current.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}, [setExpanded, setSelectedOption, singleColumn]);
|
||||
}, [shouldOpenOnFocus, singleColumn]);
|
||||
|
||||
const handleInputBlur = useCallback(() => {
|
||||
setSelectedOption(-1);
|
||||
@@ -567,7 +577,7 @@ export const Search: React.FC<{
|
||||
<input
|
||||
ref={useMergedRefs(
|
||||
searchInputRef,
|
||||
isRedesignEnabled() ? focusOnNavigation : null,
|
||||
isRedesignEnabled() ? focusAfterNavigation : null,
|
||||
)}
|
||||
className='search__input'
|
||||
type='text'
|
||||
|
||||
@@ -2,6 +2,8 @@ import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { Column } from '@/mastodon/components/column';
|
||||
@@ -26,6 +28,7 @@ import type { Hashtag as HashtagType } from 'mastodon/models/tags';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
import exploreRedesignClasses from '../explore/redesign.module.scss';
|
||||
|
||||
import { SearchSection } from './components/search_section';
|
||||
|
||||
@@ -240,7 +243,12 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
|
||||
|
||||
const extraStickyHeaderContent = (
|
||||
<>
|
||||
<div className='explore__search-header'>
|
||||
<div
|
||||
className={classNames(
|
||||
'explore__search-header',
|
||||
isRedesignEnabled() && exploreRedesignClasses.searchHeader,
|
||||
)}
|
||||
>
|
||||
<Search singleColumn initialValue={trimmedValue} key={trimmedValue} />
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user