Add the keyboard operation on recent search items (#5987)

Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
ktmouk
2023-10-12 12:37:57 +09:00
committed by GitHub
parent 8bfd17c8d4
commit c5c323690b
3 changed files with 72 additions and 31 deletions

View File

@@ -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")}
>

View File

@@ -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));