Add form validation for AxSelect

We're not done yet, there's still some weird behaviors and a case we haven't properly checked (if second AX skill has a value but first AX skill doesn't)
This commit is contained in:
Justin Edmund 2022-03-03 15:27:31 -08:00
parent 9b39299a3a
commit e9546293dc
3 changed files with 117 additions and 30 deletions

View file

@ -1,30 +1,48 @@
.AXSet { .AXSelect {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
gap: $unit; gap: $unit;
&.hidden { .AXSet {
display: none; &.hidden {
} display: none;
}
select { .errors {
flex-grow: 1; color: $error;
} display: none;
padding: $unit 0;
.Input { &.visible {
-webkit-font-smoothing: antialiased; display: block;
border: none; }
background-color: $grey-90; }
border-radius: 6px;
box-sizing: border-box;
color: $grey-00;
height: $unit * 6;
display: block;
font-size: $font-regular;
padding: $unit;
text-align: right;
min-width: 100px;
width: 100px;
}
.fields {
display: flex;
flex-direction: row;
gap: $unit;
select {
flex-grow: 1;
margin: 0;
}
.Input {
-webkit-font-smoothing: antialiased;
border: none;
background-color: $grey-90;
border-radius: 6px;
box-sizing: border-box;
color: $grey-00;
height: $unit * 6;
display: block;
font-size: $font-regular;
padding: $unit;
text-align: right;
min-width: 100px;
width: 100px;
}
}
}
} }

View file

@ -5,13 +5,36 @@ import { axData } from '~utils/axData'
import './index.scss' import './index.scss'
interface ErrorMap {
[index: string]: string
axValue1: string
axValue2: string
}
interface Props { interface Props {
axType: number axType: number
currentSkills?: SimpleAxSkill[], currentSkills?: SimpleAxSkill[],
sendValidity: (isValid: boolean) => void
sendValues: (primaryAxModifier: number, primaryAxValue: number, secondaryAxModifier: number, secondaryAxValue: number) => void sendValues: (primaryAxModifier: number, primaryAxValue: number, secondaryAxModifier: number, secondaryAxValue: number) => void
} }
const AXSelect = (props: Props) => { const AXSelect = (props: Props) => {
// Set up form states and error handling
const [errors, setErrors] = useState<ErrorMap>({
axValue1: '',
axValue2: ''
})
const primaryErrorClasses = classNames({
'errors': true,
'visible': errors.axValue1.length > 0
})
const secondaryErrorClasses = classNames({
'errors': true,
'visible': errors.axValue2.length > 0
})
// Refs // Refs
const primaryAxModifierSelect = React.createRef<HTMLSelectElement>() const primaryAxModifierSelect = React.createRef<HTMLSelectElement>()
const primaryAxValueInput = React.createRef<HTMLInputElement>() const primaryAxValueInput = React.createRef<HTMLInputElement>()
@ -112,11 +135,44 @@ const AXSelect = (props: Props) => {
} }
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) { function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
let newErrors = {...errors}
if (primaryAxValueInput.current == event.target) { if (primaryAxValueInput.current == event.target) {
setPrimaryAxValue(parseFloat(event.target.value)) const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
const value = parseFloat(event.target.value)
if (value < primaryAxSkill.minValue) {
newErrors.axValue1 = `${primaryAxSkill.name.en} must be at least ${primaryAxSkill.minValue}${ (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''}`
} else if (value > primaryAxSkill.maxValue) {
newErrors.axValue1 = `${primaryAxSkill.name.en} cannot be greater than ${primaryAxSkill.maxValue}${ (primaryAxSkill.suffix) ? primaryAxSkill.suffix : ''}`
} else {
newErrors.axValue1 = ''
setPrimaryAxValue(parseFloat(event.target.value))
}
} else { } else {
setSecondaryAxValue(parseFloat(event.target.value)) const primaryAxSkill = axData[props.axType - 1][primaryAxModifier]
const value = parseFloat(event.target.value)
if (primaryAxSkill.secondary) {
const secondaryAxSkill = primaryAxSkill.secondary.find(skill => skill.id == secondaryAxModifier)
if (secondaryAxSkill) {
if (value < secondaryAxSkill.minValue) {
newErrors.axValue2 = `${secondaryAxSkill.name.en} must be at least ${secondaryAxSkill.minValue}${ (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''}`
} else if (value > secondaryAxSkill.maxValue) {
newErrors.axValue2 = `${secondaryAxSkill.name.en} cannot be greater than ${secondaryAxSkill.maxValue}${ (secondaryAxSkill.suffix) ? secondaryAxSkill.suffix : ''}`
} else if (!secondaryAxSkill.suffix && value % 1 !== 0) {
newErrors.axValue2 = `${secondaryAxSkill.name.en} must be a whole number`
} else {
newErrors.axValue2 = ''
setSecondaryAxValue(parseFloat(event.target.value))
}
}
}
} }
props.sendValidity(newErrors.axValue1 === '' && newErrors.axValue2 === '')
setErrors(newErrors)
} }
function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) { function setupInput(ax: AxSkill | undefined, element: HTMLInputElement) {
@ -144,13 +200,19 @@ const AXSelect = (props: Props) => {
return ( return (
<div className="AXSelect"> <div className="AXSelect">
<div className="AXSet"> <div className="AXSet">
<select key="ax1" defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].modifier : -1 } onChange={handleSelectChange} ref={primaryAxModifierSelect}>{ generateOptions(0) }</select> <div className="fields">
<input defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={primaryAxValueInput} disabled /> <select key="ax1" defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].modifier : -1 } onChange={handleSelectChange} ref={primaryAxModifierSelect}>{ generateOptions(0) }</select>
<input defaultValue={ (props.currentSkills && props.currentSkills[0]) ? props.currentSkills[0].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={primaryAxValueInput} disabled />
</div>
<p className={primaryErrorClasses}>{errors.axValue1}</p>
</div> </div>
<div className={secondarySetClasses}> <div className={secondarySetClasses}>
<select key="ax2" defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].modifier : -1 } onChange={handleSelectChange} ref={secondaryAxModifierSelect}>{ generateOptions(1) }</select> <div className="fields">
<input defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={secondaryAxValueInput} disabled /> <select key="ax2" defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].modifier : -1 } onChange={handleSelectChange} ref={secondaryAxModifierSelect}>{ generateOptions(1) }</select>
<input defaultValue={ (props.currentSkills && props.currentSkills[1]) ? props.currentSkills[1].strength : 0 } className="Input" type="number" onChange={handleInputChange} ref={secondaryAxValueInput} disabled />
</div>
<p className={secondaryErrorClasses}>{errors.axValue2}</p>
</div> </div>
</div> </div>
) )

View file

@ -49,6 +49,7 @@ const WeaponModal = (props: Props) => {
// State // State
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [formValid, setFormValid] = useState(false)
const [element, setElement] = useState(-1) const [element, setElement] = useState(-1)
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1) const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
@ -64,6 +65,10 @@ const WeaponModal = (props: Props) => {
setSecondaryAxValue(secondaryAxValue) setSecondaryAxValue(secondaryAxValue)
} }
function receiveAxValidity(isValid: boolean) {
setFormValid(isValid)
}
function receiveElementValue(element: string) { function receiveElementValue(element: string) {
setElement(parseInt(element)) setElement(parseInt(element))
} }
@ -164,6 +169,7 @@ const WeaponModal = (props: Props) => {
<AXSelect <AXSelect
axType={props.gridWeapon.object.ax} axType={props.gridWeapon.object.ax}
currentSkills={props.gridWeapon.ax} currentSkills={props.gridWeapon.ax}
sendValidity={receiveAxValidity}
sendValues={receiveAxValues} sendValues={receiveAxValues}
/> />
</section> </section>
@ -171,6 +177,7 @@ const WeaponModal = (props: Props) => {
} }
function openChange(open: boolean) { function openChange(open: boolean) {
setFormValid(false)
setOpen(open) setOpen(open)
} }
@ -197,7 +204,7 @@ const WeaponModal = (props: Props) => {
{ (props.gridWeapon.object.element == 0) ? elementSelect() : '' } { (props.gridWeapon.object.element == 0) ? elementSelect() : '' }
{ ([2, 3, 17, 24].includes(props.gridWeapon.object.series)) ? keySelect() : '' } { ([2, 3, 17, 24].includes(props.gridWeapon.object.series)) ? keySelect() : '' }
{ (props.gridWeapon.object.ax > 0) ? axSelect() : '' } { (props.gridWeapon.object.ax > 0) ? axSelect() : '' }
<Button click={updateWeapon}>Save Weapon</Button> <Button click={updateWeapon} disabled={!formValid}>Save Weapon</Button>
</div> </div>
</Dialog.Content> </Dialog.Content>
<Dialog.Overlay className="Overlay" /> <Dialog.Overlay className="Overlay" />