Update react-autosuggest signatures, documentation, use generic type

- Convert Autosuggest component to a generic
- Update the renderSuggestionsContainer signature
- Add readme with usage example
- Document component props
- Export function types for easier reuse/passing in as props
This commit is contained in:
Kevin Ross
2017-11-29 09:28:28 -06:00
parent 3289762cca
commit ed9e0f6d14
3 changed files with 218 additions and 69 deletions

View File

@@ -0,0 +1,28 @@
# react-autosuggest usage notes
The definition uses generics for stronger typing. Read the [TypeScript deep dive on JSX Generic components](https://basarat.gitbooks.io/typescript/docs/jsx/tsx.html#react-jsx-tip-generic-components) for details on consuming these type definitions.
## Example
```jsx
import * as Autosuggest from 'react-autosuggest'
interface Language {
name: string
year: number
}
const LanguageAutosuggest = Autosuggest as { new (): Autosuggest<Language> }
<LanguageAutosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested.bind(this)}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={this.renderSuggestion}
onSuggestionSelected={this.onSuggestionsSelected}
alwaysRenderSuggestions={true}
inputProps={inputProps}
theme={theme}
/>
```
Find multiple full examples in `react-autosuggest-tests.tsx`

View File

@@ -1,88 +1,205 @@
// Type definitions for react-autosuggest 9.3
// Type definitions for react-autosuggest 9.3.2
// Project: http://react-autosuggest.js.org/
// Definitions by: Nicolas Schmitt <https://github.com/nicolas-schmitt>
// Philip Ottesen <https://github.com/pjo256>
// Robert Essig <https://github.com/robessog>
// Terry Bayne <https://github.com/tbayne>
// Christopher Deutsch <https://github.com/cdeutsch>
// Kevin Ross <https://github.com/rosskevin>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
import * as React from 'react';
import * as React from 'react'
declare class Autosuggest extends React.Component<Autosuggest.AutosuggestProps> {}
declare class Autosuggest<T> extends React.Component<Autosuggest.AutosuggestProps<T>> {}
export = Autosuggest;
export = Autosuggest
declare namespace Autosuggest {
interface SuggestionsFetchRequest {
value: string;
reason: 'input-changed' | 'input-focused' | 'escape-pressed' | 'suggestions-revealed' | 'suggestion-selected';
}
/**
* Utilies types based on:
* https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458
*/
interface InputValues {
value: string;
valueBeforeUpDown?: string;
}
/** @internal */
type Diff<T extends string, U extends string> = ({ [P in T]: P } &
{ [P in U]: never } & { [x: string]: never })[T]
interface RenderSuggestionParams {
query: string;
isHighlighted: boolean;
}
/** @internal */
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>
interface SuggestionHighlightedParams {
suggestion: any;
}
export interface SuggestionsFetchRequestedParams {
value: string
reason:
| 'input-changed'
| 'input-focused'
| 'escape-pressed'
| 'suggestions-revealed'
| 'suggestion-selected'
}
interface ChangeEvent {
newValue: string;
method: 'down' | 'up' | 'escape' | 'enter' | 'click' | 'type';
}
export interface RenderSuggestionParams {
query: string
isHighlighted: boolean
}
interface BlurEvent {
highlightedSuggestion: any;
}
export interface SuggestionHighlightedParams {
suggestion: any
}
interface InputProps extends React.InputHTMLAttributes<any> {
value: string;
onChange(event: React.FormEvent<any>, params?: ChangeEvent): void;
onBlur?(event: React.FormEvent<any>, params?: BlurEvent): void;
[key: string]: any;
}
export interface ChangeEvent {
newValue: string
method: 'down' | 'up' | 'escape' | 'enter' | 'click' | 'type'
}
interface SuggestionSelectedEventData<TSuggestion> {
suggestion: TSuggestion;
suggestionValue: string;
suggestionIndex: number;
sectionIndex: number | null;
method: 'click' | 'enter';
}
export interface BlurEvent<TSuggestion> {
highlightedSuggestion: TSuggestion
}
type ThemeKey = 'container' | 'containerOpen' | 'input' | 'inputOpen' | 'inputFocused' | 'suggestionsContainer' |
'suggestionsContainerOpen' | 'suggestionsList' | 'suggestion' | 'suggestionFirst' | 'suggestionHighlighted' |
'sectionContainer' | 'sectionContainerFirst' | 'sectionTitle';
export interface InputProps<TSuggestion>
extends Omit<React.InputHTMLAttributes<any>, 'onChange' | 'onBlur'> {
onChange(event: React.FormEvent<any>, params?: ChangeEvent): void
onBlur?(event: React.FormEvent<any>, params?: BlurEvent<TSuggestion>): void
value: string
[key: string]: any
}
type Theme = Record<string, string | React.CSSProperties> | Partial<Record<ThemeKey, string | React.CSSProperties>>;
export interface SuggestionSelectedEventData<TSuggestion> {
suggestion: TSuggestion
suggestionValue: string
suggestionIndex: number
sectionIndex: number | null
method: 'click' | 'enter'
}
interface AutosuggestProps extends React.Props<Autosuggest> {
suggestions: any[];
onSuggestionsFetchRequested(request: SuggestionsFetchRequest): void;
onSuggestionsClearRequested?(): void;
getSuggestionValue(suggestion: any): any;
renderSuggestion(suggestion: any, params: RenderSuggestionParams): JSX.Element;
inputProps: InputProps;
onSuggestionSelected?(event: React.FormEvent<any>, data: SuggestionSelectedEventData<any>): void;
onSuggestionHighlighted?(params: SuggestionHighlightedParams): void;
shouldRenderSuggestions?(value: string): boolean;
alwaysRenderSuggestions?: boolean;
highlightFirstSuggestion?: boolean;
focusInputOnSuggestionClick?: boolean;
multiSection?: boolean;
renderSectionTitle?(section: any): JSX.Element;
getSectionSuggestions?(section: any): any[];
renderInputComponent?(inputProps: InputProps): JSX.Element;
renderSuggestionsContainer?(containerProps: any, children: any, query: string): JSX.Element;
theme?: Theme;
id?: string;
}
export type ThemeKey =
| 'container'
| 'containerOpen'
| 'input'
| 'inputOpen'
| 'inputFocused'
| 'suggestionsContainer'
| 'suggestionsContainerOpen'
| 'suggestionsList'
| 'suggestion'
| 'suggestionFirst'
| 'suggestionHighlighted'
| 'sectionContainer'
| 'sectionContainerFirst'
| 'sectionTitle'
export type Theme =
| Record<string, string | React.CSSProperties>
| Partial<Record<ThemeKey, string | React.CSSProperties>>
export type RenderSuggestionsContainerParams = {
containerProps: {
id: string
key: string
ref: any
style: Object
}
children: React.ReactNode
query: string
}
// export types for functions - allowing reuse externally - e.g. as props and bound in the constructor
export type GetSectionSuggestions<TSuggestion> = (section: any) => TSuggestion[]
export type GetSuggestionValue<TSuggestion> = (suggestion: TSuggestion) => string
export type OnSuggestionHighlighted = (params: SuggestionHighlightedParams) => void
export type SuggestionsFetchRequested = (request: SuggestionsFetchRequestedParams) => void
export type OnSuggestionsClearRequested = () => void
export type OnSuggestionSelected<TSuggestion> = (
event: React.FormEvent<any>,
data: SuggestionSelectedEventData<TSuggestion>,
) => void
export type RenderInputComponent<TSuggestion> = (
inputProps: InputProps<TSuggestion>,
) => JSX.Element
export type RenderSuggestionsContainer = (params: RenderSuggestionsContainerParams) => JSX.Element
export type RenderSectionTitle = (section: any) => JSX.Element
export type RenderSuggestion<TSuggestion> = (
suggestion: TSuggestion,
params: RenderSuggestionParams,
) => JSX.Element
export type ShouldRenderSuggestions = (value: string) => boolean
export interface AutosuggestProps<TSuggestion> {
/**
* Set it to true if you'd like to render suggestions even when the input is not focused.
*/
alwaysRenderSuggestions?: boolean
/**
* Set it to false if you don't want Autosuggest to keep the input focused when suggestions are clicked/tapped.
*/
focusInputOnSuggestionClick?: boolean
/**
* Implement it to teach Autosuggest where to find the suggestions for every section.
*/
getSectionSuggestions?: GetSectionSuggestions<TSuggestion>
/**
* Implement it to teach Autosuggest what should be the input value when suggestion is clicked.
*/
getSuggestionValue: GetSuggestionValue<TSuggestion>
/**
* Set it to true if you'd like Autosuggest to automatically highlight the first suggestion.
*/
highlightFirstSuggestion?: boolean
/**
* Use it only if you have multiple Autosuggest components on a page.
*/
id?: string
/**
* Pass through arbitrary props to the input. It must contain at least value and onChange.
*/
inputProps: InputProps<TSuggestion>
/**
* Set it to true if you'd like to display suggestions in multiple sections (with optional titles).
*/
multiSection?: boolean
/**
* Will be called every time the highlighted suggestion changes.
*/
onSuggestionHighlighted?: OnSuggestionHighlighted
/**
* Will be called every time you need to recalculate suggestions.
*/
onSuggestionsFetchRequested: SuggestionsFetchRequested
/**
* Will be called every time you need to set suggestions to [].
*/
onSuggestionsClearRequested?: OnSuggestionsClearRequested
/**
* Will be called every time suggestion is selected via mouse or keyboard.
*/
onSuggestionSelected?: OnSuggestionSelected<TSuggestion>
/**
* Use it only if you need to customize the rendering of the input.
*/
renderInputComponent?: RenderInputComponent<TSuggestion>
/**
* Use it if you want to customize things inside the suggestions container beyond rendering the suggestions themselves.
*/
renderSuggestionsContainer?: RenderSuggestionsContainer
/**
* Use your imagination to define how section titles are rendered.
*/
renderSectionTitle?: RenderSectionTitle
/**
* Use your imagination to define how suggestions are rendered.
*/
renderSuggestion: RenderSuggestion<TSuggestion>
/**
* When the input is focused, Autosuggest will consult this function when to render suggestions. Use it, for example, if you want to display suggestions when input value is at least 2 characters long.
*/
shouldRenderSuggestions?: ShouldRenderSuggestions
/**
* These are the suggestions that will be displayed. Items can take an arbitrary shape.
*/
suggestions: TSuggestion[]
/**
* Use your imagination to style the Autosuggest.
*/
theme?: Theme
}
}

View File

@@ -15,6 +15,8 @@ function escapeRegexCharacters(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const LanguageAutosuggest = Autosuggest as { new (): Autosuggest<Language> }
export class ReactAutosuggestBasicTest extends React.Component<any, any> {
// region Fields
static languages: Language[] = [
@@ -89,7 +91,7 @@ export class ReactAutosuggestBasicTest extends React.Component<any, any> {
sectionTitle: { color: 'blue' }
};
return <Autosuggest
return <LanguageAutosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this
.onSuggestionsFetchRequested
@@ -240,7 +242,7 @@ export class ReactAutosuggestMultipleTest extends React.Component<any, any> {
.bind(this)
};
return <Autosuggest
return <LanguageAutosuggest
multiSection={true}
suggestions={suggestions}
onSuggestionsFetchRequested={this
@@ -272,7 +274,7 @@ export class ReactAutosuggestMultipleTest extends React.Component<any, any> {
return <strong>{section.title}</strong>;
}
protected renderInputComponent(inputProps: Autosuggest.InputProps): JSX.Element {
protected renderInputComponent(inputProps: Autosuggest.InputProps<Language>): JSX.Element {
return (
<div>
<input {...inputProps} />
@@ -280,7 +282,7 @@ export class ReactAutosuggestMultipleTest extends React.Component<any, any> {
);
}
protected renderSuggestionsContainer(containerProps: any, children: any, query: string): JSX.Element {
protected renderSuggestionsContainer({containerProps, children, query}: Autosuggest.RenderSuggestionsContainerParams): JSX.Element {
return (
<div {...containerProps}>
<span>{children}</span>
@@ -345,6 +347,8 @@ interface Person {
twitter: string;
}
const PersonAutosuggest = Autosuggest as { new (): Autosuggest<Person> }
export class ReactAutosuggestCustomTest extends React.Component<any, any> {
// region Fields
static people: Person[] = [
@@ -386,7 +390,7 @@ export class ReactAutosuggestCustomTest extends React.Component<any, any> {
.bind(this)
};
return<Autosuggest
return<PersonAutosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
getSuggestionValue={this.getSuggestionValue}