/
DemoForm.tsx
201 lines (182 loc) · 8.97 KB
/
DemoForm.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import React from 'react';
import styled from 'styled-components';
import ArrayValidator from '../../classes/ArrayValidator';
import DateTimeValidator from '../../classes/DateTimeValidator';
import BooleanValidator from '../../classes/BooleanValidator';
import NumberValidator from '../../classes/NumberValidator';
import ObjectValidator from '../../classes/ObjectValidator';
import StringValidator from '../../classes/StringValidator';
import AutomagicFormiflyField from '../input/AutomagicFormiflyField';
import {useFormiflyContext} from '../meta/FormiflyContext';
import FormiflyForm from '../meta/FormiflyForm';
import {DeepPartial, Dependent, ErrorFunction, MutationFunction, ValueOfValidator} from '../../types';
import DateValidator from '../../classes/DateValidator';
const Button = styled.button`
background-color: transparent;
border-radius: 0.25rem;
cursor: pointer;
padding: 0.25rem;
margin: 0.5rem 0 0.5rem 0;
&:focus-visible {
box-shadow: 0 0 5px black;
}
`;
const FruitContainer = styled.div`
margin: 1rem 0 1rem 0;
display: flex;
flex-direction: column;
& > button {
width: 10rem;
}
`;
const FruitError = styled.p`
color: red;
`;
const DemoFormContent = (props: { shape: typeof validator }) => {
const {shape} = props;
const {values, setFieldValue, errors, validateField} = useFormiflyContext<typeof shape>();
const handleRemoveFruitClick = (index: number) => {
const newFruitValue = [...values.fruit.filter((_: any, fIndex: number) => fIndex !== index)];
setFieldValue('fruit', newFruitValue);
validateField('fruit', newFruitValue);
};
const handleAddAnotherFruitClick = () => {
const newFruitValue = [...values.fruit];
newFruitValue.push(shape.fields.fruit.getDefaultValue()[0]);
setFieldValue('fruit', newFruitValue);
validateField('fruit', newFruitValue);
};
const addFruitDisabled = values.fruit.length >= shape.fields.fruit.maxChildCount;
return <>
<AutomagicFormiflyField name="number" label="Enter a number"/>
<AutomagicFormiflyField name="wholeNumber" help="Only numbers without decimal places are allowed here"
label="Enter a whole number"/>
<AutomagicFormiflyField name="string" label="Enter a string" help="Only lowercase characters are allowed here."/>
<AutomagicFormiflyField label="This input does not have the fancy label effect" labelNoMove={true} name="foo"/>
<AutomagicFormiflyField name="date" help="Only dates in the future are allowed here." label="Select a date/time"/>
<AutomagicFormiflyField label="Select a date" name="onlyDate"/>
<AutomagicFormiflyField name="laterDate" label="Select a later date/time"/>
<AutomagicFormiflyField label="Select something" name="select" options={[
{label: 'Option 1', value: 'option1'},
{label: 'Option 2', value: 'option2'},
]}/>
<AutomagicFormiflyField label="Select something else" name="selectTwo" help="Isn't it great to have choices?" options={[
{label: 'Foo 1', value: 'foo1'},
{label: 'Foo 2', value: 'foo2'},
]}/>
<p id="radiogroup-1-title">Please select one of these fields</p>
<AutomagicFormiflyField additionalDescribedBy="radiogroup-1-title" label="This is a radio option" name="radioGroupOne" type="radio"
value="radio-option-1"/>
<AutomagicFormiflyField additionalDescribedBy="radiogroup-1-title" label="This is another radio option" name="radioGroupOne"
type="radio" value="radio-option-2"/>
<AutomagicFormiflyField label="Please select one of these fields as well"
name="radioGroupTwo"
type="radio-group"
help="This radio group uses the FormiflyRadioGroup component, which creates an accessible field set to hold the options."
options={[
{label: 'First option', value: 'first-option'}, {label: 'Second option', value: 'second-option'},
]}/>
<AutomagicFormiflyField label="Also select one of these horizontal fields please"
name="radioGroupThree"
type="radio-group"
horizontal={true}
options={[
{label: 'Cool option', value: 'cool'}, {label: 'Cooler option', value: 'cooler'},
]}/>
<AutomagicFormiflyField name="multi"
label="Select one or more options"
options={[
{label: 'Select me', value: 'multi1'},
{label: 'Select me too', value: 'multi2'},
{label: 'So many options', value: 'multi3'},
{label: 'Try selecting all of them', value: 'multi4'},
{label: 'Or try selecting all but one', value: 'multi5'},
]}/>
<FruitError aria-live="polite" aria-relevant="additions" role="alert">
{!!errors.fruit && typeof errors.fruit === 'string' && errors.fruit}
</FruitError>
{values.fruit.map((_: unknown, index: number) => {
const disabled = values.fruit.length <= shape.fields.fruit.minChildCount;
return <FruitContainer key={'fruit-input' + index}>
<Button onClick={() => handleRemoveFruitClick(index)} type="button"
title={disabled ? 'There must be at least ' + shape.fields.fruit.minChildCount + ' fruit.' : ''}>
Remove this fruit
</Button>
<AutomagicFormiflyField name={'fruit.' + index + '.name'} label="Name"/>
<AutomagicFormiflyField name={'fruit.' + index + '.tasty'} label="Tasty"/>
<AutomagicFormiflyField name={'fruit.' + index + '.expired'}
label="Expired"
help="Fruit may be still edible long after it's best before date, however once it expires, you should no longer eat it."/>
</FruitContainer>;
})}
<Button onClick={handleAddAnotherFruitClick}
type="button"
disabled={addFruitDisabled}
title={addFruitDisabled ? 'There must be at most ' + shape.fields.fruit.maxChildCount + ' fruit.' : ''}>
Add another fruit
</Button>
<br/>
<br/>
<Button type="submit">Submit Form</Button>
</>;
};
class NotTrueValidator extends BooleanValidator {
constructor(
defaultValue: boolean | undefined,
defaultErrorMsg: string,
mutationFunc?: MutationFunction,
onError?: ErrorFunction,
dependent?: Dependent,
) {
super(defaultValue, defaultErrorMsg, mutationFunc, onError, dependent);
this.validateFuncs.push(value => ({
success: value !== 'true' && value !== true,
errorMsg: defaultErrorMsg,
msgName: 'not_true',
changedValue: Boolean(value),
}));
}
}
const validator = new ObjectValidator({
number: new NumberValidator()
.min(2)
.required()
.decimalPlaces(2),
wholeNumber: new NumberValidator(true).max(5),
string: new StringValidator()
.required()
.regex(/^[a-z]+$/),
foo: new StringValidator(),
date: new DateTimeValidator().minDate(new Date()),
onlyDate: new DateValidator(),
laterDate: new DateTimeValidator().greaterThanSibling('date'),
select: new StringValidator().required(),
selectTwo: new StringValidator(),
radioGroupOne: new StringValidator(),
radioGroupTwo: new StringValidator().required(),
radioGroupThree: new StringValidator(),
multi: new ArrayValidator(new StringValidator()).minLength(1, 'You must select at least one option.'),
fruit: new ArrayValidator(new ObjectValidator({
name: new StringValidator().required(),
tasty: new BooleanValidator(true),
expired: new NotTrueValidator(undefined, 'You cannot add expired food.'),
})).minLength(1, 'You must create at least one fruit.')
.maxLength(5, 'There can not be more than 5 fruit.'),
});
const DemoForm = () => {
const [successText, setSuccessText] = React.useState('');
const onSubmit = (values: DeepPartial<ValueOfValidator<typeof validator>> | undefined) => {
return new Promise<void>((resolve) => {
setSuccessText(JSON.stringify(values));
resolve();
});
};
return <FormiflyForm shape={validator} onSubmit={onSubmit}>
{successText !== '' && <>
<p>Submission successful</p>
<p>Result: {successText}</p>
</>}
<DemoFormContent shape={validator}/>
</FormiflyForm>;
};
export default DemoForm;