Files
node-factorio-recipes/components/home/FactorySelect/FactorySelect.tsx
2022-08-19 19:33:38 +02:00

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)