Add the keyboard operation on recent search items (#5987)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -47,7 +47,8 @@ type Props = RouteComponentProps<
|
||||
|
||||
@observer
|
||||
class Search extends React.Component<Props> {
|
||||
compositeRef: HTMLDivElement | null | undefined;
|
||||
resultListCompositeRef: HTMLDivElement | null | undefined;
|
||||
recentSearchesCompositeRef: HTMLDivElement | null | undefined;
|
||||
searchInputRef: HTMLInputElement | null | undefined;
|
||||
|
||||
lastQuery = "";
|
||||
@@ -128,12 +129,8 @@ class Search extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.compositeRef) {
|
||||
const linkItems = this.compositeRef.querySelectorAll(
|
||||
"[href]"
|
||||
) as NodeListOf<HTMLAnchorElement>;
|
||||
linkItems[0]?.focus();
|
||||
}
|
||||
const firstItem = this.firstResultItem ?? this.firstRecentSearchItem;
|
||||
firstItem?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -178,6 +175,20 @@ class Search extends React.Component<Props> {
|
||||
this.handleFilterChange({ titleFilter: ev.target.checked });
|
||||
};
|
||||
|
||||
get firstResultItem() {
|
||||
const linkItems = this.resultListCompositeRef?.querySelectorAll(
|
||||
"[href]"
|
||||
) as NodeListOf<HTMLAnchorElement>;
|
||||
return linkItems?.[0];
|
||||
}
|
||||
|
||||
get firstRecentSearchItem() {
|
||||
const linkItems = this.recentSearchesCompositeRef?.querySelectorAll(
|
||||
"li > [href]"
|
||||
) as NodeListOf<HTMLAnchorElement>;
|
||||
return linkItems?.[0];
|
||||
}
|
||||
|
||||
get includeArchived() {
|
||||
return this.params.get("includeArchived") === "true";
|
||||
}
|
||||
@@ -292,8 +303,12 @@ class Search extends React.Component<Props> {
|
||||
});
|
||||
};
|
||||
|
||||
setCompositeRef = (ref: HTMLDivElement | null) => {
|
||||
this.compositeRef = ref;
|
||||
setResultListCompositeRef = (ref: HTMLDivElement | null) => {
|
||||
this.resultListCompositeRef = ref;
|
||||
};
|
||||
|
||||
setRecentSearchesCompositeRef = (ref: HTMLDivElement | null) => {
|
||||
this.recentSearchesCompositeRef = ref;
|
||||
};
|
||||
|
||||
setSearchInputRef = (ref: HTMLInputElement | null) => {
|
||||
@@ -372,7 +387,10 @@ class Search extends React.Component<Props> {
|
||||
/>
|
||||
</Filters>
|
||||
) : (
|
||||
<RecentSearches />
|
||||
<RecentSearches
|
||||
ref={this.setRecentSearchesCompositeRef}
|
||||
onEscape={this.handleEscape}
|
||||
/>
|
||||
)}
|
||||
{showEmpty && (
|
||||
<Fade>
|
||||
@@ -385,7 +403,7 @@ class Search extends React.Component<Props> {
|
||||
)}
|
||||
<ResultList column>
|
||||
<StyledArrowKeyNavigation
|
||||
ref={this.setCompositeRef}
|
||||
ref={this.setResultListCompositeRef}
|
||||
onEscape={this.handleEscape}
|
||||
aria-label={t("Search Results")}
|
||||
>
|
||||
|
||||
@@ -3,8 +3,10 @@ import { CloseIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { CompositeItem } from "reakit/Composite";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import ArrowKeyNavigation from "~/components/ArrowKeyNavigation";
|
||||
import Fade from "~/components/Fade";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
@@ -12,7 +14,15 @@ import useStores from "~/hooks/useStores";
|
||||
import { hover } from "~/styles";
|
||||
import { searchPath } from "~/utils/routeHelpers";
|
||||
|
||||
function RecentSearches() {
|
||||
type Props = {
|
||||
/** Callback when the Escape key is pressed while navigating the list */
|
||||
onEscape?: (ev: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
};
|
||||
|
||||
function RecentSearches(
|
||||
{ onEscape }: Props,
|
||||
ref: React.RefObject<HTMLDivElement>
|
||||
) {
|
||||
const { searches } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const [isPreloaded] = React.useState(searches.recent.length > 0);
|
||||
@@ -25,24 +35,37 @@ function RecentSearches() {
|
||||
<>
|
||||
<Heading>{t("Recent searches")}</Heading>
|
||||
<List>
|
||||
{searches.recent.map((searchQuery) => (
|
||||
<ListItem key={searchQuery.id}>
|
||||
<RecentSearch to={searchPath(searchQuery.query)}>
|
||||
{searchQuery.query}
|
||||
<Tooltip tooltip={t("Remove search")} delay={150}>
|
||||
<RemoveButton
|
||||
aria-label={t("Remove search")}
|
||||
onClick={async (ev) => {
|
||||
ev.preventDefault();
|
||||
await searchQuery.delete();
|
||||
}}
|
||||
<ArrowKeyNavigation
|
||||
ref={ref}
|
||||
onEscape={onEscape}
|
||||
aria-label={t("Search Results")}
|
||||
>
|
||||
{(compositeProps) =>
|
||||
searches.recent.map((searchQuery) => (
|
||||
<ListItem key={searchQuery.id}>
|
||||
<CompositeItem
|
||||
as={RecentSearch}
|
||||
to={searchPath(searchQuery.query)}
|
||||
role="menuitem"
|
||||
{...compositeProps}
|
||||
>
|
||||
<CloseIcon />
|
||||
</RemoveButton>
|
||||
</Tooltip>
|
||||
</RecentSearch>
|
||||
</ListItem>
|
||||
))}
|
||||
{searchQuery.query}
|
||||
<Tooltip tooltip={t("Remove search")} delay={150}>
|
||||
<RemoveButton
|
||||
aria-label={t("Remove search")}
|
||||
onClick={async (ev) => {
|
||||
ev.preventDefault();
|
||||
await searchQuery.delete();
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</RemoveButton>
|
||||
</Tooltip>
|
||||
</CompositeItem>
|
||||
</ListItem>
|
||||
))
|
||||
}
|
||||
</ArrowKeyNavigation>
|
||||
</List>
|
||||
</>
|
||||
) : null;
|
||||
@@ -104,4 +127,4 @@ const RecentSearch = styled(Link)`
|
||||
}
|
||||
`;
|
||||
|
||||
export default observer(RecentSearches);
|
||||
export default observer(React.forwardRef(RecentSearches));
|
||||
|
||||
Reference in New Issue
Block a user