Skip to content

Commit

Permalink
Merge pull request #394 from contentful/improv/autocomplete-types
Browse files Browse the repository at this point in the history
refactor(autocomplete): more concrete type definitions
  • Loading branch information
arpl committed Jan 20, 2020
2 parents 4d95b23 + abd7913 commit 08b2f88
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const AutocompleteDefaultStory = ({ items }: { items: Item[] }) => {
}, []);

return (
<Autocomplete
<Autocomplete<Item>
maxHeight={number('maxHeight', 100)}
items={filteredItems}
onQueryChange={handleQueryChange}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react';
import '@testing-library/jest-dom/extend-expect';
import {
Expand All @@ -10,7 +9,7 @@ import {
} from '@testing-library/react';

import { KEY_CODE } from './utils';
import Autocomplete from '../Autocomplete';
import Autocomplete, { AutocompleteProps } from '../Autocomplete';

interface Item {
value: number;
Expand All @@ -27,17 +26,20 @@ const items: Item[] = [
configure({ testIdAttribute: 'data-test-id' });

describe('Autocomplete', () => {
let onChangeFn: any;
let onQueryChangeFn: any;
let onChangeFn: (value: Item) => void;
let onQueryChangeFn: (query: string) => void;

afterEach(cleanup);

const build = ({ placeholder = '', width = 'large' }: any) => {
const build = ({
placeholder = '',
width = 'large',
}: Partial<AutocompleteProps<Item>>) => {
onChangeFn = jest.fn();
onQueryChangeFn = jest.fn();

return render(
<Autocomplete
<Autocomplete<Item>
items={items}
onChange={onChangeFn}
onQueryChange={onQueryChangeFn}
Expand Down Expand Up @@ -85,7 +87,9 @@ describe('Autocomplete', () => {
});

describe('dropdown', () => {
let input: any, dropdown, options: any;
let input: HTMLElement;
let dropdown: HTMLElement;
let options: HTMLElement[];

beforeEach(() => {
const { getByTestId } = build({});
Expand Down Expand Up @@ -118,9 +122,9 @@ describe('Autocomplete', () => {

it('dismisses the dropdown when selecting with the enter key', () => {
fireEvent.keyDown(input, { keyCode: KEY_CODE.ENTER });
const dropdown = within(document as any).queryByTestId(
'autocomplete.dropdown-list',
);
const dropdown = within(
(document as unknown) as HTMLElement,
).queryByTestId('autocomplete.dropdown-list');
expect(dropdown).toBeNull();
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useReducer, useMemo, useRef, FunctionComponent } from 'react';
import React, { useReducer, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';

import TextInput from '../TextInput';
Expand All @@ -19,10 +18,10 @@ const NAVIGATED_ITEMS = 'NAVIGATED_ITEMS';
const QUERY_CHANGED = 'QUERY_CHANGED';
const ITEM_SELECTED = 'ITEM_SELECTED';

export interface AutocompleteProps {
children: (items: any[]) => any[];
items: any[];
onChange: (item: {}) => void;
export interface AutocompleteProps<T extends {}> {
children: (items: T[]) => React.ReactNode[];
items: T[];
onChange: (item: T) => void;
onQueryChange: (query: string) => void;
disabled?: boolean;
placeholder?: string;
Expand Down Expand Up @@ -88,7 +87,7 @@ const reducer = (state: State, action: Action): State => {
}
};

export const Autocomplete: FunctionComponent<AutocompleteProps> = ({
export const Autocomplete = <T extends {}>({
children,
items = [],
disabled,
Expand All @@ -104,9 +103,11 @@ export const Autocomplete: FunctionComponent<AutocompleteProps> = ({
noMatchesMessage = 'No matches',
willClearQueryOnClose,
dropdownProps,
}) => {
const listRef: any = useRef();
const inputRef: any = useRef();
}: AutocompleteProps<T>) => {
const listRef: React.MutableRefObject<HTMLDivElement | undefined> = useRef();
const inputRef: React.MutableRefObject<
HTMLInputElement | undefined
> = useRef();

const [{ isOpen, query, highlightedItemIndex }, dispatch] = useReducer(
reducer,
Expand All @@ -117,7 +118,7 @@ export const Autocomplete: FunctionComponent<AutocompleteProps> = ({
dispatch({ type: TOGGLED_LIST, payload: isOpen });
};

const selectItem = (item: {}) => {
const selectItem = (item: T) => {
dispatch({ type: ITEM_SELECTED });
onQueryChange('');
onChange(item);
Expand All @@ -128,7 +129,7 @@ export const Autocomplete: FunctionComponent<AutocompleteProps> = ({
onQueryChange(value);
};

const handleKeyDown = (event: any) => {
const handleKeyDown = (event: React.KeyboardEvent) => {
const isEnter = event.keyCode === KEY_CODE.ENTER;
const isTab =
event.keyCode === KEY_CODE.TAB ||
Expand All @@ -144,7 +145,9 @@ export const Autocomplete: FunctionComponent<AutocompleteProps> = ({
direction,
lastIndex,
);
scrollToItem(listRef.current, newIndex);
if (listRef.current) {
scrollToItem(listRef.current, newIndex);
}
dispatch({ type: NAVIGATED_ITEMS, payload: newIndex });
} else if (isEnter && hasUserSelection) {
const selected = items[highlightedItemIndex as number];
Expand All @@ -156,7 +159,9 @@ export const Autocomplete: FunctionComponent<AutocompleteProps> = ({

const handleInputButtonClick = () => {
query ? updateQuery('') : toggleList();
inputRef.current.focus();
if (inputRef.current) {
inputRef.current.focus();
}
};

const options = useMemo(
Expand Down Expand Up @@ -188,7 +193,7 @@ export const Autocomplete: FunctionComponent<AutocompleteProps> = ({
disabled={disabled}
placeholder={placeholder}
width={width}
inputRef={inputRef}
inputRef={inputRef as React.RefObject<HTMLInputElement>}
testId="autocomplete.input"
type="search"
autoComplete="off"
Expand All @@ -207,7 +212,7 @@ export const Autocomplete: FunctionComponent<AutocompleteProps> = ({
{...dropdownProps}
>
<DropdownList testId="autocomplete.dropdown-list" maxHeight={maxHeight}>
<div ref={listRef}>
<div ref={listRef as React.RefObject<HTMLDivElement>}>
{!options.length && !isLoading && (
<DropdownListItem
isDisabled
Expand Down Expand Up @@ -280,7 +285,7 @@ function OptionSkeleton() {
);
}

function getNavigationDirection(event: KeyboardEvent): Direction | null {
function getNavigationDirection(event: React.KeyboardEvent): Direction | null {
if (event.keyCode === KEY_CODE.ARROW_DOWN) {
return Direction.DOWN;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './Autocomplete';
export { default } from './Autocomplete';

0 comments on commit 08b2f88

Please sign in to comment.