Commit 54ecf795 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Convert `SearchInput` to TS

parent e418e397
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
Spinner, Spinner,
} from '@hypothesis/frontend-shared/lib/next'; } from '@hypothesis/frontend-shared/lib/next';
import classnames from 'classnames'; import classnames from 'classnames';
import type { RefObject } from 'preact';
import { useCallback, useRef, useState } from 'preact/hooks'; import { useCallback, useRef, useState } from 'preact/hooks';
import { useShortcut } from '../../shared/shortcut'; import { useShortcut } from '../../shared/shortcut';
...@@ -20,15 +21,14 @@ import { useSidebarStore } from '../store'; ...@@ -20,15 +21,14 @@ import { useSidebarStore } from '../store';
* (everyone else) * (everyone else)
* - Restore previous focus when the user presses 'Escape' while the search * - Restore previous focus when the user presses 'Escape' while the search
* input is focused. * input is focused.
*
* @param {import('preact').RefObject<HTMLInputElement>} searchInputRef
*/ */
function useSearchKeyboardShortcuts(searchInputRef) { function useSearchKeyboardShortcuts(
const prevFocusRef = searchInputRef: RefObject<HTMLInputElement>
/** @type {import('preact').RefObject<HTMLOrSVGElement>} */ (useRef()); ) {
const prevFocusRef = useRef<HTMLOrSVGElement | null>(null);
const focusSearch = useCallback( const focusSearch = useCallback(
/** @param {KeyboardEvent} event */ event => { (event: KeyboardEvent) => {
// When user is in an input field, respond to CMD-/CTRL-K keypresses, // When user is in an input field, respond to CMD-/CTRL-K keypresses,
// but ignore '/' keypresses // but ignore '/' keypresses
if ( if (
...@@ -39,12 +39,12 @@ function useSearchKeyboardShortcuts(searchInputRef) { ...@@ -39,12 +39,12 @@ function useSearchKeyboardShortcuts(searchInputRef) {
) { ) {
return; return;
} }
prevFocusRef.current = /** @type {HTMLOrSVGElement|null} */ ( prevFocusRef.current = document.activeElement as HTMLOrSVGElement | null;
document.activeElement if (searchInputRef.current) {
);
searchInputRef.current?.focus(); searchInputRef.current?.focus();
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
}
}, },
[searchInputRef] [searchInputRef]
); );
...@@ -66,15 +66,19 @@ function useSearchKeyboardShortcuts(searchInputRef) { ...@@ -66,15 +66,19 @@ function useSearchKeyboardShortcuts(searchInputRef) {
useShortcut('escape', restoreFocus); useShortcut('escape', restoreFocus);
} }
/** export type SearchInputProps = {
* @typedef SearchInputProps /**
* @prop {boolean} [alwaysExpanded] - * When true, the input field is always shown. If false, the input field is
* If true, the input field is always shown. If false, the input field is only shown * only shown if the query is non-empty.
* if the query is non-empty.
* @prop {string|null} query - The currently active filter query
* @prop {(value: string) => void} onSearch -
* Callback to invoke when the current filter query changes
*/ */
alwaysExpanded?: boolean;
/** The currently-active filter query */
query: string | null;
/** Callback for when the current filter query changes */
onSearch: (value: string) => void;
};
/** /**
* An input field in the top bar for entering a query that filters annotations * An input field in the top bar for entering a query that filters annotations
...@@ -84,15 +88,15 @@ function useSearchKeyboardShortcuts(searchInputRef) { ...@@ -84,15 +88,15 @@ function useSearchKeyboardShortcuts(searchInputRef) {
* This component also renders a eloading spinner to indicate when the client * This component also renders a eloading spinner to indicate when the client
* is fetching for data from the API or in a "loading" state for any other * is fetching for data from the API or in a "loading" state for any other
* reason. * reason.
*
* @param {SearchInputProps} props
*/ */
export default function SearchInput({ alwaysExpanded, query, onSearch }) { export default function SearchInput({
alwaysExpanded,
query,
onSearch,
}: SearchInputProps) {
const store = useSidebarStore(); const store = useSidebarStore();
const isLoading = store.isLoading(); const isLoading = store.isLoading();
const input = /** @type {import('preact').RefObject<HTMLInputElement>} */ ( const input = useRef<HTMLInputElement | null>(null);
useRef()
);
useSearchKeyboardShortcuts(input); useSearchKeyboardShortcuts(input);
// The active filter query from the previous render. // The active filter query from the previous render.
...@@ -101,8 +105,7 @@ export default function SearchInput({ alwaysExpanded, query, onSearch }) { ...@@ -101,8 +105,7 @@ export default function SearchInput({ alwaysExpanded, query, onSearch }) {
// The query that the user is currently typing, but may not yet have applied. // The query that the user is currently typing, but may not yet have applied.
const [pendingQuery, setPendingQuery] = useState(query); const [pendingQuery, setPendingQuery] = useState(query);
/** @param {Event} e */ const onSubmit = (e: Event) => {
const onSubmit = e => {
e.preventDefault(); e.preventDefault();
if (input.current?.value || prevQuery) { if (input.current?.value || prevQuery) {
// Don't set an initial empty query, but allow a later empty query to // Don't set an initial empty query, but allow a later empty query to
...@@ -176,8 +179,8 @@ export default function SearchInput({ alwaysExpanded, query, onSearch }) { ...@@ -176,8 +179,8 @@ export default function SearchInput({ alwaysExpanded, query, onSearch }) {
disabled={isLoading} disabled={isLoading}
elementRef={input} elementRef={input}
value={pendingQuery || ''} value={pendingQuery || ''}
onInput={e => onInput={(e: Event) =>
setPendingQuery(/** @type {HTMLInputElement} */ (e.target).value) setPendingQuery((e.target as HTMLInputElement).value)
} }
/> />
{!isLoading && ( {!isLoading && (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment