import type {
HTMLAttributes,
InputHTMLAttributes,
ChangeEvent,
ReactNode,
} from 'react'
import type PopperJs from 'popper.js'
import type { BaseProps, SizeType } from '@toptal/picasso-shared'
import type { OutlinedInputProps, Status } from '@toptal/picasso-outlined-input'
/**
* Select props are generalized over possible values in the component and whether
* Select should be a multiselect. If you want `onChange` to take a handler that
* can take array (for multiselect) you should set `M` to `true`. By default it's
* single select.
*
* @param T The type of the value in the `Select`, can be either `number` or `string`
* @param M The `boolean` type of the `multiple` property to indicate whether `onChange` will expect handler to accept plain `T` or array of `T`
* @param V Technical type, don't pass type argument to it directly
*/
export interface SelectProps<
T extends ValueType = ValueType,
M extends boolean = boolean,
V = M extends true ? T[] : T
> extends BaseProps,
Omit<
InputHTMLAttributes,
'onChange' | 'size' | 'color' | 'value'
> {
/** If true, the 'Select' will be disabled */
disabled?: boolean
/** Whether to render select options in portal. Should be disabled in Modals */
disablePortal?: boolean
/** Indicate whether `Select` is in `error`, `warning` or `default` state */
status?: Extract
/** Component ID */
id?: string
/** Width of the component */
width?: 'full' | 'shrink' | 'auto'
/** Width of the menu */
menuWidth?: string
/** Shows the loading icon when options are loading */
loading?: boolean
/** Placeholder option which is selected by default */
placeholder?: string
/** Placeholder for search input */
searchPlaceholder?: string
/** Whether icon should be placed at the beginning or end of the `Input` */
iconPosition?: 'start' | 'end'
/** Specify icon which should be rendered inside Input */
icon?: ReactNode
/** Callback invoked when `Select` changes its state. */
onChange?: (
event: ChangeEvent<{
name?: string | undefined
value: V
}>
) => void
/** Label to show when no options were found */
noOptionsText?: string
/** List of options or option groups to be rendered as `Select` */
options: Option[] | OptionGroups
/** Callback responsible for rendering the option given the option and its index in the list of options */
renderOption?: (option: Option, index?: number) => ReactNode
/** A function that takes a display value from the option item */
getDisplayValue?: (option: Option | null) => string
/** Selected value */
value?: V
/** Allow selecting multiple values */
multiple?: M
/** Whether to render native browser select or not */
native?: boolean
/**
* Component size
* @default medium
*/
size?: SizeType<'small' | 'medium' | 'large'>
/** Whether to render reset icon which clears selected value */
enableReset?: boolean
/** Whether to render reset icon which clears search input value */
enableResetSearch?: boolean
popperContainer?: HTMLElement
/** Defines the minimum options number to show the search
* @default 10
*/
searchThreshold?: number
/** Limits number of options to display on the list
* @default 200
*/
limit?: number
/** Specifies whether the autofill enabled or not, disabled by default */
enableAutofill?: boolean
/** A function that is invoked during search. It takes an array of options and a search value and returns filtered options */
filterOptions?: (
options: Option[],
searchValue: string,
getDisplayValue?: (option: Option | null) => string
) => Option[]
ref?: React.Ref
testIds?: OutlinedInputProps['testIds'] & {
noOptions?: string
loader?: string
limitFooter?: string
searchInput?: string
}
highlight?: 'autofill'
}
export type ValueType = string | number
export type Option = {
key?: number
text: string
description?: string
value: T
disabled?: boolean
[prop: string]: string | number | undefined | boolean
}
export type OptionGroups = {
[group: string]: Option[]
}
export type ItemProps = {
onMouseEnter: () => void
onMouseDown: (event: React.MouseEvent) => void
onClick: (event: React.MouseEvent) => void
}
export type FocusEventType = (event: React.FocusEvent) => void
export type Selection = {
isSelected: () => boolean
isOptionSelected: (option: Option) => boolean
display: (getDisplayValue: (option: Option | null) => string) => string
}
export interface UseSelectStateProps {
getDisplayValue: (option: Option | null) => string
options: Option[]
disabled?: boolean
multiple?: boolean
value?: ValueType | ValueType[]
searchThreshold?: number
limit?: number
}
export type UseSelectStateOutput = {
isOpen: boolean
canOpen: boolean
open: () => void
close: () => void
highlightedIndex: number
closeOnEnter: boolean
setHighlightedIndex: (index: number) => void
setFilterOptionsValue: (value: string) => void
showSearch: boolean
filterOptionsValue: string
displayValue: string
selection: Selection
filteredOptions: Option[] | OptionGroups
emptySelectValue: string | string[]
selectedOptions: Option[]
}
export interface UseSelectProps<
T extends ValueType = ValueType,
M extends boolean = boolean,
V = M extends true ? T[] : T
> {
searchInputRef?: React.Ref
selectRef: React.Ref
popperRef?: React.Ref
selectProps: SelectProps
selectState: UseSelectStateOutput
}
type GetRootProps = () => {
onFocus: FocusEventType
onClick: (event: React.MouseEvent) => void
onBlur: FocusEventType
}
type GetInputProps = () => Partial>
type GetSearchInputProps = () => Partial>
export interface UseSelectOutput {
getItemProps: (item: Option, index: number) => ItemProps
getRootProps: GetRootProps
getInputProps: GetInputProps
getSearchInputProps: GetSearchInputProps
}