import * as React from "react" ; import { Universities , University } from "polar-shared/src/util/Universities" ; import { arrayStream } from "polar-shared/src/util/ArrayStreams" ; import { isPresent } from "polar-shared/src/Preconditions" ; import TextField from "@material-ui/core/TextField" ; import Autocomplete , { AutocompleteRenderGroupParams } from "@material-ui/lab/Autocomplete" ; import useTheme from "@material-ui/core/styles/useTheme" ; import useMediaQuery from "@material-ui/core/useMediaQuery" ; import { VariableSizeList , ListChildComponentProps } from "react-window" ; import ListSubheader from "@material-ui/core/ListSubheader" ; import Typography from "@material-ui/core/Typography" ; import { nullToUndefined } from "polar-shared/src/util/Nullable" ; const LIMIT = 250 ; export interface IOption < T > { readonly value : T ; readonly label : string ; } const universities = arrayStream ( Universities . get ( ) ) . sort ( ( ( a , b ) => a . name . localeCompare ( b . name ) ) ) . collect ( ) ; function toOption ( university ) { return { value : university , label : university . name } ; } const options = arrayStream ( universities ) . map ( toOption ) . collect ( ) ; type OptionsCallback < T > = ( options : ReadonlyArray < IOption < T > > ) => void ; function loadOptions ( inputValue , callback ) { callback ( Loader . filter ( inputValue ) ) ; } class Loader { public static defaultOptions ( ) { return arrayStream ( options ) . head ( LIMIT ) . collect ( ) ; } public static filter ( inputString ) { if ( ! inputString || inputString . trim ( ) === '' ) { this . defaultOptions ( ) ; } const predicate = ( option ) => { return isPresent ( option . label ) && option . label . toLowerCase ( ) . indexOf ( inputString . toLowerCase ( ) ) !== - 1 ; } ; return arrayStream ( options ) . filter ( predicate ) . head ( LIMIT ) . collect ( ) ; } } const LISTBOX_PADDING = 8 ; function renderRow ( props ) { const { data , index , style } = props ; return React . cloneElement ( data [ index ] , { style : { ... style , top : ( style . top as number ) + LISTBOX_PADDING , } , } ) ; } const OuterElementContext = React . createContext ( { } ) ; const OuterElementType = React . forwardRef < HTMLDivElement > ( ( props , ref ) => { const outerProps = React . useContext ( OuterElementContext ) ; return < div ref = { ref } { ... props } { ... outerProps } />; } ) ; function useResetCache ( data ) { const ref = React . useRef < VariableSizeList > ( null ) ; React . useEffect ( ( ) => { if ( ref . current != null ) { ref . current . resetAfterIndex ( 0 , true ) ; } } , [ data ] ) ; return ref ; } const ListboxComponent = React . forwardRef < HTMLDivElement > ( function ListboxComponent ( props , ref ) { const { children , ... other } = props ; const itemData = React . Children . toArray ( children ) ; const theme = useTheme ( ) ; const smUp = useMediaQuery ( theme . breakpoints . up ( 'sm' ) , { noSsr : true } ) ; const itemCount = itemData . length ; const itemSize = smUp ? 36 : 48 ; const getChildSize = ( child ) => { if ( React . isValidElement ( child ) && child . type === ListSubheader ) { return 48 ; } return itemSize ; } ; const getHeight = ( ) => { if ( itemCount > 8 ) { return 8 * itemSize ; } return itemData . map ( getChildSize ) . reduce ( ( a , b ) => a + b , 0 ) ; } ; const gridRef = useResetCache ( itemCount ) ; return ( < div ref = { ref } > < OuterElementContext . Provider value = { other } > < VariableSizeList itemData = { itemData } height = { getHeight ( ) + 2 * LISTBOX_PADDING } width = "100%" ref = { gridRef } outerElementType = { OuterElementType } innerElementType = "ul" itemSize = { ( index ) => getChildSize ( itemData [ index ] ) } overscanCount = { 5 } itemCount = { itemCount } > { renderRow } < /VariableSizeList> < /OuterElementContext.Provider> < /div>); } ) ; const renderGroup = ( params ) => params . children ; interface IProps { readonly placeholder ? : string ; readonly onSelect : ( option : IOption < University > | undefined ) => void ; } const groupByFunction = ( option ) => option . value . name [ 0 ] . toUpperCase ( ) ; const optionsComparator = ( a , b ) => { return groupByFunction ( a ) . localeCompare ( groupByFunction ( b ) ) ; } ; export const UniversitySelect = ( props ) => { const sortedOptions = React . useMemo ( ( ) => [ ... options ] . sort ( optionsComparator ) , [ ] ) ; return ( < Autocomplete style = { { flexGrow : 1 } } onChange = { ( event , option ) => props . onSelect ( nullToUndefined ( option ) ) } disableListWrap ListboxComponent = { ListboxComponent as React . ComponentType < React . HTMLAttributes < HTMLElement > > } getOptionLabel = { ( option ) => option . label } renderGroup = { renderGroup } options = { sortedOptions } groupBy = { groupByFunction } renderInput = { ( params ) => < TextField { ... params } variant = "outlined" label = "... and studies at" / > } renderOption = { ( option ) => < Typography noWrap > { option . label } < /Typography>}/ > ) ; } ;