112 lines
3.3 KiB
TypeScript
112 lines
3.3 KiB
TypeScript
import { FC, memo, useCallback, useEffect, useMemo } from 'react'
|
|
import Select, { ActionMeta, CSSObjectWithLabel } from 'react-select'
|
|
import { isNonNullable } from '../../../src/utils'
|
|
import styles from './FactorySelect.module.css'
|
|
import { EntitySpan } from '../EntitySpan/EntitySpan'
|
|
import { useFactories } from '../../contexts/FactoryProvider'
|
|
|
|
interface Props {
|
|
id: string
|
|
factories: string[]
|
|
onSetFactories: (factories: string[]) => void
|
|
fixInputs?: boolean
|
|
}
|
|
|
|
interface Option {
|
|
label: string
|
|
value: string
|
|
isFixed: boolean
|
|
}
|
|
|
|
const selectStyles: Record<
|
|
'multiValue' | 'multiValueLabel' | 'multiValueRemove',
|
|
(base: CSSObjectWithLabel, state: { data: Option }) => CSSObjectWithLabel
|
|
> = {
|
|
multiValue: (base, state) => {
|
|
return state.data.isFixed
|
|
? { ...base, backgroundColor: 'var(--fixed-item-bg) !important' }
|
|
: base
|
|
},
|
|
multiValueLabel: (base, state) => {
|
|
return state.data.isFixed ? { ...base, fontWeight: 'bold', paddingRight: '6px' } : base
|
|
},
|
|
multiValueRemove: (base, state) => {
|
|
return state.data.isFixed ? { ...base, display: 'none' } : base
|
|
}
|
|
}
|
|
|
|
const orderOptions = (values: Option[]) => {
|
|
return values.filter(v => v.isFixed).concat(values.filter(v => !v.isFixed))
|
|
}
|
|
|
|
const FactorySelectBase: FC<Props> = ({ id, factories, onSetFactories, fixInputs }) => {
|
|
const { factories: details } = useFactories()
|
|
const options = useMemo<Option[]>(
|
|
() =>
|
|
details.map(detail => ({
|
|
label: detail.name,
|
|
value: detail.href,
|
|
isFixed: !!fixInputs && !detail.recipe
|
|
})),
|
|
[details, fixInputs]
|
|
)
|
|
|
|
const state = useMemo<typeof options>(() => {
|
|
return factories
|
|
.map(factory => options.find(option => option.value === factory))
|
|
.filter(isNonNullable)
|
|
}, [factories, options])
|
|
|
|
useEffect(() => {
|
|
if (!fixInputs) return
|
|
const set = new Set(factories)
|
|
const addOptions = options
|
|
.filter(option => option.isFixed && !set.has(option.value))
|
|
.map(option => option.value)
|
|
if (addOptions.length) {
|
|
onSetFactories([...factories, ...addOptions])
|
|
}
|
|
}, [factories, fixInputs, onSetFactories, options])
|
|
|
|
const onChange = useCallback(
|
|
(values: readonly Option[], { action, removedValue }: ActionMeta<Option>) => {
|
|
if ((action === 'remove-value' || action === 'pop-value') && removedValue.isFixed) {
|
|
return
|
|
} else if (action === 'clear') {
|
|
const clearedValues = orderOptions(options.filter(v => v.isFixed))
|
|
onSetFactories(clearedValues.map(s => s?.value))
|
|
} else {
|
|
onSetFactories(values.map(s => s?.value))
|
|
}
|
|
},
|
|
[onSetFactories, options]
|
|
)
|
|
|
|
return (
|
|
<Select
|
|
id={id}
|
|
instanceId={id}
|
|
value={orderOptions(state)}
|
|
isClearable={state.some(option => !option.isFixed)}
|
|
components={{
|
|
MultiValueLabel: ({ data, innerProps }) => (
|
|
<EntitySpan {...innerProps} value={data.value} simpleStyle={true} />
|
|
),
|
|
Option: ({ innerProps, data }) => (
|
|
<div {...innerProps} className={styles.option}>
|
|
<EntitySpan value={data.value} simpleStyle={true} />
|
|
</div>
|
|
)
|
|
}}
|
|
isMulti
|
|
options={orderOptions(options)}
|
|
onChange={onChange}
|
|
className={styles.select}
|
|
classNamePrefix={'factory-select'}
|
|
styles={selectStyles}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export const FactorySelect = memo(FactorySelectBase)
|