129 lines
3.2 KiB
JavaScript
129 lines
3.2 KiB
JavaScript
import MAutocomplete from '@mui/material/Autocomplete'
|
|
import classnames from 'classnames'
|
|
import sort from 'match-sorter'
|
|
import * as R from 'ramda'
|
|
import React from 'react'
|
|
import { HoverableTooltip } from 'src/components/Tooltip'
|
|
import { P } from 'src/components/typography'
|
|
|
|
import TextInput from './TextInput'
|
|
|
|
const Autocomplete = ({
|
|
limit,
|
|
options,
|
|
label,
|
|
valueProp,
|
|
multiple,
|
|
onChange,
|
|
labelProp,
|
|
value: outsideValue,
|
|
error,
|
|
fullWidth,
|
|
textAlign,
|
|
size,
|
|
autoFocus,
|
|
...props
|
|
}) => {
|
|
const mapFromValue = options => it => R.find(R.propEq(valueProp, it))(options)
|
|
const mapToValue = R.prop(valueProp)
|
|
|
|
const getValue = () => {
|
|
if (!valueProp) return outsideValue
|
|
|
|
const transform = multiple
|
|
? R.map(mapFromValue(options))
|
|
: mapFromValue(options)
|
|
|
|
return transform(outsideValue)
|
|
}
|
|
|
|
const value = getValue()
|
|
|
|
const innerOnChange = (evt, value) => {
|
|
if (!valueProp) return onChange(evt, value)
|
|
|
|
const rValue = multiple ? R.map(mapToValue)(value) : mapToValue(value)
|
|
onChange(evt, rValue)
|
|
}
|
|
|
|
const valueArray = () => {
|
|
if (R.isNil(value)) return []
|
|
return multiple ? value : [value]
|
|
}
|
|
|
|
const filter = (array, input) => {
|
|
if (!input) return array
|
|
return sort(array, input, { keys: [valueProp, labelProp] })
|
|
}
|
|
|
|
const filterOptions = (array, { inputValue }) =>
|
|
R.union(
|
|
R.isEmpty(inputValue) ? valueArray() : [],
|
|
filter(array, inputValue),
|
|
).slice(
|
|
0,
|
|
R.defaultTo(undefined)(limit) &&
|
|
Math.max(limit, R.isEmpty(inputValue) ? valueArray().length : 0),
|
|
)
|
|
|
|
return (
|
|
<MAutocomplete
|
|
options={options}
|
|
multiple={multiple}
|
|
value={value}
|
|
onChange={innerOnChange}
|
|
getOptionLabel={R.path([labelProp])}
|
|
forcePopupIcon={false}
|
|
filterOptions={filterOptions}
|
|
openOnFocus
|
|
autoHighlight
|
|
disableClearable
|
|
clearOnEscape
|
|
isOptionEqualToValue={R.eqProps(valueProp)}
|
|
{...props}
|
|
renderInput={params => {
|
|
return (
|
|
<TextInput
|
|
{...params}
|
|
autoFocus={autoFocus}
|
|
label={label}
|
|
value={outsideValue}
|
|
error={error}
|
|
size={size}
|
|
fullWidth={fullWidth}
|
|
textAlign={textAlign}
|
|
/>
|
|
)
|
|
}}
|
|
renderOption={(iprops, props) => {
|
|
if (!props.warning && !props.warningMessage)
|
|
return <li {...iprops}>{R.path([labelProp])(props)}</li>
|
|
|
|
const className = {
|
|
'flex w-4 h-4 rounded-md': true,
|
|
'bg-spring4': props.warning === 'clean',
|
|
'bg-orange-yellow': props.warning === 'partial',
|
|
'bg-tomato': props.warning === 'important',
|
|
}
|
|
|
|
const hoverableElement = <div className={classnames(className)} />
|
|
|
|
return (
|
|
<li {...iprops}>
|
|
<div className="flex flex-row justify-between items-center w-full">
|
|
<div className="flex">{R.path([labelProp])(props)}</div>
|
|
<HoverableTooltip parentElements={hoverableElement} width={250}>
|
|
<P>{props.warningMessage}</P>
|
|
</HoverableTooltip>
|
|
</div>
|
|
</li>
|
|
)
|
|
}}
|
|
slotProps={{
|
|
chip: { onDelete: null },
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export default Autocomplete
|