hensei-web/components/AwakeningSelect/index.tsx
2023-01-03 18:06:27 -08:00

200 lines
5.2 KiB
TypeScript

import React, { ForwardedRef, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import Input from '~components/LabelledInput'
import Select from '~components/Select'
import SelectItem from '~components/SelectItem'
import classNames from 'classnames'
import { weaponAwakening, characterAwakening } from '~utils/awakening'
import type { Awakening } from '~utils/awakening'
import './index.scss'
interface Props {
object: 'character' | 'weapon'
awakeningType?: number
awakeningLevel?: number
onOpenChange: (open: boolean) => void
sendValidity: (isValid: boolean) => void
sendValues: (type: number, level: number) => void
}
const AwakeningSelect = (props: Props) => {
const router = useRouter()
const locale =
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
const { t } = useTranslation('common')
const [open, setOpen] = useState(false)
// Refs
const awakeningLevelInput = React.createRef<HTMLInputElement>()
// States
const [awakeningType, setAwakeningType] = useState(-1)
const [awakeningLevel, setAwakeningLevel] = useState(1)
const [maxValue, setMaxValue] = useState(1)
const [error, setError] = useState('')
// Classes
const inputClasses = classNames({
Bound: true,
Hidden: awakeningType === -1,
})
const errorClasses = classNames({
errors: true,
visible: error !== '',
})
// Set max value based on object type
useEffect(() => {
if (props.object === 'character') setMaxValue(9)
else if (props.object === 'weapon') setMaxValue(15)
}, [props.object])
// Set default awakening and level based on object type
useEffect(() => {
let defaultAwakening = 0
if (props.object === 'weapon') defaultAwakening = -1
setAwakeningType(
props.awakeningType != undefined ? props.awakeningType : defaultAwakening
)
setAwakeningLevel(props.awakeningLevel ? props.awakeningLevel : 1)
}, [props.object, props.awakeningType, props.awakeningLevel])
// Send awakening type and level when changed
useEffect(() => {
props.sendValues(awakeningType, awakeningLevel)
}, [props.sendValues, awakeningType, awakeningLevel])
// Send validity of form when awakening level changes
useEffect(() => {
props.sendValidity(awakeningLevel > 0 && error === '')
}, [props.sendValidity, awakeningLevel, error])
// Classes
function changeOpen() {
setOpen(!open)
props.onOpenChange(!open)
}
function onClose() {
props.onOpenChange(false)
}
function generateOptions(object: 'character' | 'weapon') {
let options: Awakening[] = []
if (object === 'character') options = characterAwakening
else if (object === 'weapon') options = weaponAwakening
else return
let optionElements: React.ReactNode[] = options.map((awakening, i) => {
return (
<SelectItem key={i} value={awakening.id}>
{awakening.name[locale]}
</SelectItem>
)
})
if (object === 'weapon') {
optionElements?.unshift(
<SelectItem key={-1} value={-1}>
{t('awakening.no_type')}
</SelectItem>
)
}
return optionElements
}
function handleSelectChange(rawValue: string) {
const value = parseInt(rawValue)
setAwakeningType(value)
}
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
const value = parseFloat(event.target.value)
if (handleLevelError(value)) setAwakeningLevel(value)
}
function handleLevelError(value: number) {
let error = ''
if (value < 1) {
error = t('awakening.errors.value_too_low', {
minValue: 1,
})
} else if (value > maxValue) {
error = t('awakening.errors.value_too_high', {
maxValue: maxValue,
})
} else if (value % 1 != 0) {
error = t('awakening.errors.value_not_whole')
} else if (!value || value <= 0) {
error = t('awakening.errors.value_empty')
} else {
error = ''
}
setError(error)
return error.length === 0
}
const rangeString = (object: 'character' | 'weapon') => {
let minValue = 1
let maxValue = 1
if (object === 'weapon') {
minValue = 1
maxValue = 15
} else if (object === 'character') {
minValue = 1
maxValue = 9
} else return
return `${minValue}~${maxValue}`
}
return (
<div className="AwakeningSelect">
<div className="AwakeningSet">
<div className="fields">
<Select
key="awakening_type"
value={`${awakeningType}`}
open={open}
onValueChange={handleSelectChange}
onOpenChange={() => changeOpen()}
onClose={onClose}
triggerClass="modal"
>
{generateOptions(props.object)}
</Select>
<Input
value={awakeningLevel}
className={inputClasses}
type="number"
placeholder={rangeString(props.object)}
min={1}
max={maxValue}
step="1"
onChange={handleInputChange}
visible={awakeningType !== -1 ? true : false}
ref={awakeningLevelInput}
/>
</div>
<p className={errorClasses}>{error}</p>
</div>
</div>
)
}
export default AwakeningSelect