Skip to content

Commit

Permalink
Fix autocomplete lint issues
Browse files Browse the repository at this point in the history
  • Loading branch information
matheusps committed Jun 3, 2020
1 parent b362dd5 commit b9b1eb8
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 115 deletions.
2 changes: 1 addition & 1 deletion react/components/AutocompleteInput/Option.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const autocompleteOptionShape = PropTypes.oneOfType([
])

export const getTermFromOption = (option: AutocompleteOption): string =>
typeof option === 'string' ? option : option.label
typeof option === 'string' ? option : option?.label ?? ''

const propTypes = {
/** Determine if the option should have a rounded bottom */
Expand Down
65 changes: 36 additions & 29 deletions react/components/AutocompleteInput/SearchInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,26 @@ import React, { useState } from 'react'
import IconSearch from '../../icon/Search'
import ClearInputIcon from '../../icon/Clear'

const propTypes = {
/** Determine if the input's bottom corners should be rounded or not */
roundedBottom: PropTypes.bool,

/* Input props */
/** Input value */
value: PropTypes.string,
/** Clear event handler */
onClear: PropTypes.func,
/** Change event handler */
onChange: PropTypes.func,
/** Search event handler. Called on enter or when clicking the search button */
onSearch: PropTypes.func,
/** Focus event handler */
onFocus: PropTypes.func,
/** Blur event handler */
onBlur: PropTypes.func,
/** Determine if the input and the button should be disabled */
disabled: PropTypes.bool,
}
type NativeInput = React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>

const defaultProps = {
roundedBottom: true,
export interface SearchInputProps extends Omit<NativeInput, 'onChange'> {
value?: string
roundedBottom?: boolean
onClear?: () => void
onSearch?: (value: string) => void
onChange?: (value: string) => void
disabled?: boolean
}

const SearchInput: React.FC<PropTypes.InferProps<typeof propTypes> &
Omit<React.HTMLProps<HTMLInputElement>, 'onChange' | 'value'>> = props => {
const SearchInput: React.FC<SearchInputProps> = props => {
const {
onClear,
onSearch,
roundedBottom,
value,
roundedBottom = true,
value = '',
onChange,
onFocus,
onBlur,
Expand Down Expand Up @@ -95,6 +83,9 @@ const SearchInput: React.FC<PropTypes.InferProps<typeof propTypes> &
<span
className="absolute c-muted-3 fw5 flex items-center ph3 t-body top-0 right-0 h-100 pointer"
onClick={handleClear}
role="button"
tabIndex={0}
onKeyDown={() => null}
>
<ClearInputIcon />
</span>
Expand All @@ -103,15 +94,31 @@ const SearchInput: React.FC<PropTypes.InferProps<typeof propTypes> &
<button
className={buttonClasses}
disabled={disabled}
onClick={() => onSearch(value)}
onClick={() => onSearch?.(value)}
>
<IconSearch size={16} />
</button>
</div>
)
}

SearchInput.propTypes = propTypes
SearchInput.defaultProps = defaultProps
SearchInput.propTypes = {
/** Determine if the input's bottom corners should be rounded or not */
roundedBottom: PropTypes.bool,
/** Input value */
value: PropTypes.string,
/** Clear event handler */
onClear: PropTypes.func,
/** Change event handler */
onChange: PropTypes.func,
/** Search event handler. Called on enter or when clicking the search button */
onSearch: PropTypes.any,
/** Focus event handler */
onFocus: PropTypes.func,
/** Blur event handler */
onBlur: PropTypes.func,
/** Determine if the input and the button should be disabled */
disabled: PropTypes.bool,
}

export default SearchInput
177 changes: 92 additions & 85 deletions react/components/AutocompleteInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import PropTypes from 'prop-types'
import React, { useState, useRef } from 'react'
import React, { useState, useRef, FC, ReactNode } from 'react'

import Spinner from '../Spinner'
import { useClickOutside, useArrowNavigation } from './hooks'
Expand All @@ -8,77 +9,21 @@ import Option, {
autocompleteOptionShape,
getTermFromOption,
} from './Option'
import SearchInput from './SearchInput'
import SearchInput, { SearchInputProps } from './SearchInput'

const propTypes = {
/** Input props. All HTMLInput props can be added too */
input: PropTypes.shape({
/** Clear input handler */
onClear: PropTypes.func.isRequired,
/** Search by term handler (fired on enter or when clicking the search button) */
onSearch: PropTypes.func.isRequired,
/** Change term handler */
onChange: PropTypes.func.isRequired,
/** Term to be searched */
value: PropTypes.string,
/** Determine if the input and button should be disabled */
disabled: PropTypes.bool,
}).isRequired,
/** Options props. More details in the examples */
options: PropTypes.shape({
/**
* Determine if a spinner will be shown below the given options
* to show that more options will be added
*/
loading: PropTypes.bool,
/**
* Function that makes possible to the dev to customly render option.
* Called with all props needed: `(props: { key: string, selected: boolean, value: OptionValue, searchTerm: string, roundedBottom: boolean, icon: ReactElement, onClick: () => void }, index: number)` and should return a React Node
*/
renderOption: PropTypes.func,
/**
* List of options.
* An option could be a string (denoting a search by term) or an object
* with `{value: any, label: string}` (denoting the search is related to an entity).
*/
value: PropTypes.arrayOf(autocompleteOptionShape).isRequired,
/**
* Icon representing the entity.
* Shown when a value is an object to show the difference
*/
icon: PropTypes.element,
/**
* Callback called when an option is selected
* (clicked or via arrow keys + enter)
*/
onSelect: PropTypes.func.isRequired,
/**
* Last searched terms. Can be used to enhance the Autocomplete experience.
* Defined with: `{
* value: OptionValue[],
* onChange: (term: string | OptionValue) => any,
* label: string
* }`
*/
lastSearched: PropTypes.shape({
/** List of last searched options */
value: PropTypes.arrayOf(autocompleteOptionShape).isRequired,
/**
* Last searched change handler.
* Called when a term is searched or an option is selected.
*/
onChange: PropTypes.func,
/** Last Searched options's title */
label: PropTypes.node.isRequired,
}),
}).isRequired,
export type AutocompleteInputProps = {
input: SearchInputProps
options: {
onSelect: (option: AutocompleteOption) => void
value: any
renderOption?: (renderer: any, index: number) => any
loading?: boolean
lastSearched?: any
icon?: ReactNode
}
}

export type AutocompleteInputProps = PropTypes.InferProps<typeof propTypes>

const AutocompleteInput: React.FunctionComponent<PropTypes.InferProps<
typeof propTypes
>> = ({
const AutocompleteInput: FC<AutocompleteInputProps> = ({
input: { value, onClear, onSearch, onChange, ...inputProps },
options: {
onSelect,
Expand Down Expand Up @@ -123,19 +68,19 @@ const AutocompleteInput: React.FunctionComponent<PropTypes.InferProps<
}

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
const selectedOption =
selectedOptionIndex !== -1 ? showedOptions[selectedOptionIndex] : term
addToLastSearched(selectedOption)
setTerm(getTermFromOption(selectedOption))
if (selectedOptionIndex !== -1) {
onSelect(selectedOption)
} else {
onSearch(getTermFromOption(selectedOption))
}
setSelectedOptionIndex(-1)
setShowPopover(false)
if (e.key !== 'Enter') return

const selectedOption =
selectedOptionIndex !== -1 ? showedOptions[selectedOptionIndex] : term
addToLastSearched(selectedOption)
setTerm(getTermFromOption(selectedOption))
if (selectedOptionIndex !== -1) {
onSelect(selectedOption)
} else {
onSearch?.(getTermFromOption(selectedOption))
}
setSelectedOptionIndex(-1)
setShowPopover(false)
}

const handleTermChange = (newTerm = '') => {
Expand All @@ -152,7 +97,7 @@ const AutocompleteInput: React.FunctionComponent<PropTypes.InferProps<
setShowPopover(false)

setTerm('')
onClear()
onClear?.()
}

const handleOptionClick = (option: AutocompleteOption) => {
Expand All @@ -161,7 +106,7 @@ const AutocompleteInput: React.FunctionComponent<PropTypes.InferProps<
setShowPopover(false)
}

const getOptionProps = (option, index) => ({
const getOptionProps = (option: any, index: number) => ({
key: `${getTermFromOption(option)}-${index}`,
selected: index === selectedOptionIndex,
value: option,
Expand Down Expand Up @@ -205,7 +150,7 @@ const AutocompleteInput: React.FunctionComponent<PropTypes.InferProps<
roundedBottom={!popoverOpened}
onKeyDown={handleKeyDown}
onFocus={() => setShowPopover(true)}
onSearch={() => onSearch(term)}
onSearch={() => onSearch?.(term)}
onClear={handleClear}
onChange={handleTermChange}
/>
Expand All @@ -225,6 +170,68 @@ const AutocompleteInput: React.FunctionComponent<PropTypes.InferProps<
)
}

AutocompleteInput.propTypes = propTypes
AutocompleteInput.propTypes = {
/** Input props. All HTMLInput props can be added too */
input: PropTypes.shape({
/** Clear input handler */
onClear: PropTypes.func.isRequired,
/** Search by term handler (fired on enter or when clicking the search button) */
onSearch: PropTypes.func.isRequired,
/** Change term handler */
onChange: PropTypes.func.isRequired,
/** Term to be searched */
value: PropTypes.string,
/** Determine if the input and button should be disabled */
disabled: PropTypes.bool,
}).isRequired,
/** Options props. More details in the examples */
options: PropTypes.shape({
/**
* Determine if a spinner will be shown below the given options
* to show that more options will be added
*/
loading: PropTypes.bool,
/**
* Function that makes possible to the dev to customly render option.
* Called with all props needed: `(props: { key: string, selected: boolean, value: OptionValue, searchTerm: string, roundedBottom: boolean, icon: ReactElement, onClick: () => void }, index: number)` and should return a React Node
*/
renderOption: PropTypes.func,
/**
* List of options.
* An option could be a string (denoting a search by term) or an object
* with `{value: any, label: string}` (denoting the search is related to an entity).
*/
value: PropTypes.arrayOf(autocompleteOptionShape).isRequired,
/**
* Icon representing the entity.
* Shown when a value is an object to show the difference
*/
icon: PropTypes.element,
/**
* Callback called when an option is selected
* (clicked or via arrow keys + enter)
*/
onSelect: PropTypes.func.isRequired,
/**
* Last searched terms. Can be used to enhance the Autocomplete experience.
* Defined with: `{
* value: OptionValue[],
* onChange: (term: string | OptionValue) => any,
* label: string
* }`
*/
lastSearched: PropTypes.shape({
/** List of last searched options */
value: PropTypes.arrayOf(autocompleteOptionShape).isRequired,
/**
* Last searched change handler.
* Called when a term is searched or an option is selected.
*/
onChange: PropTypes.func,
/** Last Searched options's title */
label: PropTypes.node.isRequired,
}),
}).isRequired,
}

export default AutocompleteInput

0 comments on commit b9b1eb8

Please sign in to comment.