import React, { ChangeEvent } from 'react' import * as MUI from '@material-ui/core' import styled, { css } from '@monorail/helpers/styled-components' import { matchOn, matchOnI } from '@monorail/sharedHelpers/matchers' import { isEmptyString, isNil } from '@monorail/sharedHelpers/typeGuards' import { IconButton } from '@monorail/v2/core/IconButton/IconButton' import * as Icons from '@monorail/v2/icons/Icons' import { OmitBannedProps, undefinedStyleError, } from '@monorail/v2/shared/helpers' // #region CSS const searchFieldBaseCss = css` border-radius: 1000px; ` const searchFieldClearCss = css` height: unset; width: unset; color: ${({ theme }) => theme.v2.FoundationDollop}; &:focus, &:hover { background-color: unset; box-shadow: unset; color: ${({ theme }) => theme.v2.FoundationPinch}; } &:active { color: ${({ theme }) => theme.v2.FoundationDash}; } ` // #endregion CSS type SearchFieldEndAdornmentProps = { shouldHide: boolean onClick: () => void } const SearchFieldEndAdornment = (props: SearchFieldEndAdornmentProps) => ( ) export const StyledOutlinedInput = styled( MUI.OutlinedInput as typeof SearchField, // as-cast necessary in order to allow for additional Monorail pass-through props )` ${searchFieldBaseCss} ` type ClearableSearchFieldProps = Pick< SearchFieldProps, 'value' | 'inputRef' | 'onChange' > & { /** * Callback when clear button is clicked */ onClear: () => void } export const useClearableSearchField = (props: ClearableSearchFieldProps) => { // Hook into potential existing ref in order to focus on input after clicking 'clear' const _localInputRef = React.useRef(null) const inputRef = props.inputRef ?? _localInputRef // Turn optionally uncontrolled component to be controlled const [searchValue, setSearchValue] = MUI.useControlled({ controlled: props.value, default: '', name: 'SearchField', }) const shouldHideClearAdornment = isEmptyString(searchValue) return { searchFieldProps: { inputRef, value: searchValue, onChange: (e: ChangeEvent) => { setSearchValue(e.currentTarget.value) props.onChange?.(e) }, endAdornment: ( { setSearchValue('') // Only functions for uncontrolled props.onClear?.() if (inputRef.current) { inputRef.current.value = '' inputRef.current.dispatchEvent( new Event('input', { bubbles: true }), ) } inputRef.current?.focus() }} /> ), }, } } type SearchFieldMonorailProps = { value?: string // Clarify `unknown` MUI prop // This actually constricts the ref type to disallow callback-refs. This shouldn't be a problem in practice. // - DS 2020-09-25 inputRef?: React.RefObject // Clarify `any` MUI prop } export type SearchFieldProps = SearchFieldMonorailProps & OmitBannedProps> /** * Basic input styled as a search field */ export function SearchField(props: SearchFieldProps) { return ( } inputProps={{ 'aria-label': 'search', ...props.inputProps }} {...props} /> ) } ;(SearchField as any).muiName = (MUI.OutlinedInput as any).muiName // eslint-disable-line /** * `SearchField` composed with `useClearableSearch` * * TODO: If we don't like separate `SearchField` and `SearchFieldClearable`, we can change it to * `` and create a `SearchFieldBase`. */ export function SearchFieldClearable( props: ClearableSearchFieldProps & SearchFieldProps, ) { const { onClear, onChange, inputRef, value, ...restProps } = props const { searchFieldProps } = useClearableSearchField({ onClear, onChange, inputRef, value, }) return } ;(SearchFieldClearable as any).muiName = (SearchField as any).muiName // eslint-disable-line