@axdspub/axiom-ui-forms 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +4 -0
- package/.env +1 -0
- package/.eslintrc.json +37 -0
- package/.gitlab-ci.yml +72 -0
- package/.storybook/main.ts +43 -0
- package/.storybook/preview.ts +16 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +10 -0
- package/Dockerfile +34 -0
- package/README.md +19 -0
- package/craco.config.js +42 -0
- package/docker/nginx/conf.d/default.conf +46 -0
- package/docker-compose.yml +13 -0
- package/package.json +103 -0
- package/public/exampleForm.json +77 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/rollup.config.mjs +108 -0
- package/src/App.tsx +25 -0
- package/src/Form/Components/FieldCreator.tsx +206 -0
- package/src/Form/Components/FieldLabel.tsx +14 -0
- package/src/Form/Components/Inputs/Boolean.tsx +13 -0
- package/src/Form/Components/Inputs/JSONString.tsx +40 -0
- package/src/Form/Components/Inputs/LongString.tsx +22 -0
- package/src/Form/Components/Inputs/Number.tsx +22 -0
- package/src/Form/Components/Inputs/Object.tsx +56 -0
- package/src/Form/Components/Inputs/RadioGroup.tsx +24 -0
- package/src/Form/Components/Inputs/SingleSelect.tsx +24 -0
- package/src/Form/Components/Inputs/String.tsx +18 -0
- package/src/Form/Components/Inputs/index.tsx +6 -0
- package/src/Form/Components/Inputs/inputMap.ts +23 -0
- package/src/Form/Components/index.tsx +2 -0
- package/src/Form/FormCreator.tsx +62 -0
- package/src/Form/FormCreatorTypes.ts +187 -0
- package/src/Form/FormMappingTypes.ts +17 -0
- package/src/Form/Manage/CopyableJSONOutput.tsx +75 -0
- package/src/Form/Manage/FormConfigInput.tsx +61 -0
- package/src/Form/Manage/FormMappedOutput.tsx +131 -0
- package/src/Form/Manage/FormMappingInput.tsx +60 -0
- package/src/Form/Manage/Manage.tsx +132 -0
- package/src/Form/Manage/RawFormOutput.tsx +20 -0
- package/src/Form/MapTester.tsx +107 -0
- package/src/Form/SchemaToForm.tsx +348 -0
- package/src/Form/formDefinition.json +8 -0
- package/src/Form/helpers.ts +85 -0
- package/src/Form/index.ts +1 -0
- package/src/Form/testData/assetData.json +65 -0
- package/src/Form/testData/exampleParticle.json +112 -0
- package/src/Form/testData/fields.json +151 -0
- package/src/Form/testData/nestedForm.json +156 -0
- package/src/Form/testData/testSchema.json +89 -0
- package/src/SetTester.tsx +61 -0
- package/src/helpers.ts +36 -0
- package/src/index.css +39 -0
- package/src/index.tsx +19 -0
- package/src/library.ts +3 -0
- package/src/reportWebVitals.ts +15 -0
- package/src/state/formAtom.ts +21 -0
- package/src/state/formMappingAtom.ts +21 -0
- package/src/state/formValuesAtom.ts +22 -0
- package/src/types/generate-schema.d.ts +8 -0
- package/tailwind.config.js +11 -0
- package/tsconfig.json +32 -0
- package/tsconfig.paths.json +19 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
import resolve from '@rollup/plugin-node-resolve'
|
2
|
+
import commonjs from '@rollup/plugin-commonjs'
|
3
|
+
import typescript from '@rollup/plugin-typescript'
|
4
|
+
import dts from 'rollup-plugin-dts'
|
5
|
+
import json from '@rollup/plugin-json'
|
6
|
+
import alias from '@rollup/plugin-alias'
|
7
|
+
import nodePolyfills from 'rollup-plugin-polyfill-node'
|
8
|
+
// import packageJson from './package.json'
|
9
|
+
|
10
|
+
const globals = {
|
11
|
+
'react/jsx-runtime': 'JSXRuntime',
|
12
|
+
react: 'React',
|
13
|
+
'react-dom': 'ReactDOM',
|
14
|
+
lodash: 'lodash'
|
15
|
+
}
|
16
|
+
|
17
|
+
const external = [
|
18
|
+
'react',
|
19
|
+
'react-dom',
|
20
|
+
'react-scripts',
|
21
|
+
'react/jsx-runtime',
|
22
|
+
'lodash'
|
23
|
+
]
|
24
|
+
|
25
|
+
const config = [
|
26
|
+
{
|
27
|
+
external,
|
28
|
+
input: 'src/library.ts',
|
29
|
+
onwarn: function (warning, warn) {
|
30
|
+
if (warning.code === 'MODULE_LEVEL_DIRECTIVE') {
|
31
|
+
return
|
32
|
+
}
|
33
|
+
warn(warning)
|
34
|
+
},
|
35
|
+
output: [
|
36
|
+
/* {
|
37
|
+
file: 'library/cjs.js',
|
38
|
+
format: 'cjs',
|
39
|
+
sourcemap: true,
|
40
|
+
globals
|
41
|
+
}, */
|
42
|
+
{
|
43
|
+
file: 'library/index.js',
|
44
|
+
format: 'es',
|
45
|
+
sourcemap: true,
|
46
|
+
globals
|
47
|
+
},
|
48
|
+
/* {
|
49
|
+
file: 'library/browser.js',
|
50
|
+
format: 'iife',
|
51
|
+
name: 'AxiomUIforms',
|
52
|
+
sourcemap: true,
|
53
|
+
globals
|
54
|
+
}, */
|
55
|
+
{
|
56
|
+
file: 'library/umd.js',
|
57
|
+
format: 'umd',
|
58
|
+
name: 'AxiomUIforms',
|
59
|
+
sourcemap: true,
|
60
|
+
globals
|
61
|
+
}
|
62
|
+
],
|
63
|
+
plugins: [
|
64
|
+
nodePolyfills(),
|
65
|
+
json(),
|
66
|
+
resolve({ preferBuiltins: false, browser: true }),
|
67
|
+
commonjs(),
|
68
|
+
alias({
|
69
|
+
entries: {
|
70
|
+
'@/*': './src'
|
71
|
+
}
|
72
|
+
}),
|
73
|
+
typescript({ tsconfig: './tsconfig.json' })
|
74
|
+
]
|
75
|
+
},
|
76
|
+
{
|
77
|
+
external,
|
78
|
+
input: 'src/library.ts',
|
79
|
+
output: [{
|
80
|
+
file: 'library/axiom-ui-forms.d.ts',
|
81
|
+
format: 'esm',
|
82
|
+
globals
|
83
|
+
}],
|
84
|
+
plugins: [
|
85
|
+
json(),
|
86
|
+
dts({
|
87
|
+
compilerOptions: {
|
88
|
+
// enabling declaration (.d.ts) emit
|
89
|
+
declaration: true,
|
90
|
+
|
91
|
+
// optional - in general it's a good practice to decouple declaration files from your actual transpiled JavaScript files
|
92
|
+
declarationDir: 'library',
|
93
|
+
|
94
|
+
// optional if you're using babel to transpile TS -> JS
|
95
|
+
emitDeclarationOnly: true,
|
96
|
+
|
97
|
+
paths: {
|
98
|
+
'@/*': [
|
99
|
+
'./src/*'
|
100
|
+
]
|
101
|
+
}
|
102
|
+
}
|
103
|
+
})
|
104
|
+
]
|
105
|
+
}
|
106
|
+
]
|
107
|
+
|
108
|
+
export default config
|
package/src/App.tsx
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
import FormManager from '@/Form/Manage/Manage'
|
2
|
+
import React, { type ReactElement } from 'react'
|
3
|
+
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
4
|
+
import SetTester from '@/SetTester'
|
5
|
+
import MapTester from '@/Form/MapTester'
|
6
|
+
import SchemaToForm from '@/Form/SchemaToForm'
|
7
|
+
|
8
|
+
const App = (): ReactElement => {
|
9
|
+
return (
|
10
|
+
|
11
|
+
<div className='h-screen flex flex-col gap-4'>
|
12
|
+
<BrowserRouter>
|
13
|
+
<Routes>
|
14
|
+
<Route path='/' element={<FormManager />} />
|
15
|
+
<Route path='/schema-to-form' element={<SchemaToForm />} />
|
16
|
+
<Route path="/set-tester" element={<SetTester />} />
|
17
|
+
<Route path="/map-tester" element={<MapTester />} />W
|
18
|
+
</Routes>
|
19
|
+
</BrowserRouter>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
)
|
23
|
+
}
|
24
|
+
|
25
|
+
export default App
|
@@ -0,0 +1,206 @@
|
|
1
|
+
import inputMap from '@/Form/Components/Inputs/inputMap'
|
2
|
+
import { type IFormValues, type IFieldInputProps, type IFormField, type IValueChangeFn, type IValueType, type IForm } from '@/Form/FormCreatorTypes'
|
3
|
+
import { checkCondition, cleanUnusedDependenciesFromFormValues, getFieldValue, getPathFromField } from '@/Form/helpers'
|
4
|
+
import { Button, utils } from '@axdspub/axiom-ui-utilities'
|
5
|
+
import { CheckIcon, CopyIcon, Cross1Icon, PlusIcon, TrashIcon } from '@radix-ui/react-icons'
|
6
|
+
import { set } from 'lodash'
|
7
|
+
import React, { useState, type ReactElement } from 'react'
|
8
|
+
|
9
|
+
interface IFieldCreator {
|
10
|
+
field: IFormField
|
11
|
+
form: IForm
|
12
|
+
onChange?: IValueChangeFn
|
13
|
+
className?: string
|
14
|
+
defaultClassName?: string
|
15
|
+
value?: IValueType | IValueType[]
|
16
|
+
formValueState: [IFormValues, (v: IFormValues) => void]
|
17
|
+
}
|
18
|
+
|
19
|
+
const toolButtonClass = 'border-white hover:border-single hover:border-1 hover:border-slate-400'
|
20
|
+
|
21
|
+
const DeleteMultiple = ({
|
22
|
+
doDelete
|
23
|
+
}: {
|
24
|
+
doDelete: () => void
|
25
|
+
}): ReactElement => {
|
26
|
+
const [confirm, setConfirm] = useState(false)
|
27
|
+
|
28
|
+
return (
|
29
|
+
<>
|
30
|
+
{
|
31
|
+
confirm
|
32
|
+
? <p className='flex flex-row gap-2 text-sm'><span className='text-slate-600'>Deleting: </span> Are you sure?
|
33
|
+
<Button size='xs' type='submit'
|
34
|
+
onClick={() => {
|
35
|
+
doDelete()
|
36
|
+
setConfirm(false)
|
37
|
+
}}>Yes <CheckIcon className='inline ml-2' />
|
38
|
+
</Button>
|
39
|
+
<Button size='xs' type='alert'
|
40
|
+
onClick={() => {
|
41
|
+
setConfirm(false)
|
42
|
+
}}>Cancel <Cross1Icon className='inline ml-2' />
|
43
|
+
</Button>
|
44
|
+
</p>
|
45
|
+
: <Button size='xs' className={toolButtonClass} onClick={() => { setConfirm(true) }}>
|
46
|
+
Delete <TrashIcon className='inline ml-2 fill-white' />
|
47
|
+
</Button>
|
48
|
+
}
|
49
|
+
</>
|
50
|
+
)
|
51
|
+
}
|
52
|
+
|
53
|
+
const OneOfMultiple = ({
|
54
|
+
InputComponent,
|
55
|
+
field,
|
56
|
+
form,
|
57
|
+
value,
|
58
|
+
index,
|
59
|
+
onChange,
|
60
|
+
values,
|
61
|
+
formValueState
|
62
|
+
|
63
|
+
}: {
|
64
|
+
InputComponent: React.FC<IFieldInputProps>
|
65
|
+
field: IFormField
|
66
|
+
form: IForm
|
67
|
+
value: IValueType
|
68
|
+
index: number
|
69
|
+
onChange: (v: IValueType[] | undefined) => void
|
70
|
+
values: IValueType[]
|
71
|
+
formValueState: [IFormValues, (v: IFormValues) => void]
|
72
|
+
|
73
|
+
}): ReactElement => {
|
74
|
+
const addValue = (v: IValueType | null): void => {
|
75
|
+
const newValues = [...values]
|
76
|
+
newValues.splice(index + 1, 0, v)
|
77
|
+
onChange(newValues)
|
78
|
+
}
|
79
|
+
|
80
|
+
return (
|
81
|
+
<div className='flex flex-col gap-2'>
|
82
|
+
<InputComponent
|
83
|
+
formValueState={formValueState}
|
84
|
+
form={form}
|
85
|
+
field={{
|
86
|
+
...field,
|
87
|
+
required: false,
|
88
|
+
label: index > 0 ? null : field.label,
|
89
|
+
id: `${field.id}-${index}`
|
90
|
+
}}
|
91
|
+
value={value}
|
92
|
+
onChange={(v) => {
|
93
|
+
const newValues = [...values]
|
94
|
+
newValues[index] = String(v)
|
95
|
+
onChange(newValues)
|
96
|
+
}}
|
97
|
+
/>
|
98
|
+
|
99
|
+
<div className='flex flex-row justify-between w-full p-2'>
|
100
|
+
{index > 0 && (
|
101
|
+
<DeleteMultiple doDelete={() => {
|
102
|
+
const newValues = [...values]
|
103
|
+
newValues.splice(index, 1)
|
104
|
+
onChange(newValues)
|
105
|
+
}} />
|
106
|
+
)}
|
107
|
+
<div className='ml-auto flex gap-2'>
|
108
|
+
<Button
|
109
|
+
size='xs'
|
110
|
+
className={toolButtonClass}
|
111
|
+
onClick={() => {
|
112
|
+
addValue(null)
|
113
|
+
}}>Add <PlusIcon className='inline ml-2' /></Button>
|
114
|
+
<Button
|
115
|
+
size='xs'
|
116
|
+
className={toolButtonClass}
|
117
|
+
onClick={() => {
|
118
|
+
addValue(structuredClone(value))
|
119
|
+
}}>Duplicate <CopyIcon className='inline ml-2' />
|
120
|
+
</Button>
|
121
|
+
</div>
|
122
|
+
</div>
|
123
|
+
</div>
|
124
|
+
)
|
125
|
+
}
|
126
|
+
|
127
|
+
const MultipleFieldCreator = ({ form, field, onChange, value, formValueState }: IFieldCreator): ReactElement => {
|
128
|
+
const [formValues, setFormValues] = formValueState
|
129
|
+
const defaultOnChange = (v: IValueType[] | undefined): void => {
|
130
|
+
const formValuesCopy = structuredClone(formValues)
|
131
|
+
set(formValuesCopy, getPathFromField(field), v)
|
132
|
+
setFormValues(formValuesCopy)
|
133
|
+
}
|
134
|
+
|
135
|
+
const initialVal = value !== undefined ? value : getFieldValue(field, formValues)
|
136
|
+
const initialValues = (initialVal !== undefined ? (Array.isArray(initialVal) ? initialVal : [initialVal]) : [null])
|
137
|
+
|
138
|
+
/* const initialValues = (
|
139
|
+
formValues[getPathFromField(field)] !== undefined
|
140
|
+
? Array.isArray(formValues[getPathFromField(field)])
|
141
|
+
? formValues[getPathFromField(field)]
|
142
|
+
: [formValues[getPathFromField(field)]]
|
143
|
+
: [null]
|
144
|
+
) as IValueType[] */
|
145
|
+
|
146
|
+
const InputComponent = inputMap[field.type]
|
147
|
+
|
148
|
+
return <div>
|
149
|
+
{
|
150
|
+
initialValues?.map((value, index) => {
|
151
|
+
return <OneOfMultiple
|
152
|
+
formValueState={formValueState}
|
153
|
+
key={`${field.id}-${index}`}
|
154
|
+
InputComponent={InputComponent}
|
155
|
+
form={form}
|
156
|
+
field={field}
|
157
|
+
value={value}
|
158
|
+
index={index}
|
159
|
+
onChange={onChange ?? defaultOnChange}
|
160
|
+
values={initialValues}
|
161
|
+
/>
|
162
|
+
})
|
163
|
+
}
|
164
|
+
</div>
|
165
|
+
}
|
166
|
+
|
167
|
+
const FieldCreator = ({
|
168
|
+
field,
|
169
|
+
form,
|
170
|
+
value,
|
171
|
+
onChange,
|
172
|
+
className,
|
173
|
+
defaultClassName = 'py-2 flex flex-col gap-8',
|
174
|
+
formValueState
|
175
|
+
}: IFieldCreator): ReactElement | null => {
|
176
|
+
const [formValues, setFormValues] = formValueState
|
177
|
+
const InputComponent = inputMap[field.type]
|
178
|
+
|
179
|
+
const updateFormValues = (v: IFormValues): void => {
|
180
|
+
setFormValues(cleanUnusedDependenciesFromFormValues(form, v))
|
181
|
+
}
|
182
|
+
|
183
|
+
if (!checkCondition(field, formValues)) {
|
184
|
+
return null
|
185
|
+
}
|
186
|
+
|
187
|
+
const defaultOnChange = (v: IValueType | IValueType[] | undefined): void => {
|
188
|
+
const formValuesCopy = structuredClone(formValues)
|
189
|
+
set(formValuesCopy, getPathFromField(field), v)
|
190
|
+
updateFormValues(formValuesCopy)
|
191
|
+
}
|
192
|
+
const initialValue = value !== undefined ? value : getFieldValue(field, formValues)
|
193
|
+
return InputComponent !== undefined
|
194
|
+
? <div className={utils.makeClassName({
|
195
|
+
className,
|
196
|
+
defaultClassName
|
197
|
+
})}>{
|
198
|
+
field.multiple === true
|
199
|
+
? <MultipleFieldCreator field={field} form={form} onChange={onChange} value={initialValue} formValueState={formValueState} />
|
200
|
+
: <InputComponent field={field} form={form} onChange={onChange ?? defaultOnChange} value={Array.isArray(initialValue) ? initialValue[0] : initialValue} formValueState={formValueState} />
|
201
|
+
|
202
|
+
}</div>
|
203
|
+
: <p>No component definition for {field.type} ({field.id})</p>
|
204
|
+
}
|
205
|
+
|
206
|
+
export default FieldCreator
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import { type IFormField } from '@/Form/FormCreatorTypes'
|
2
|
+
import React, { type ReactElement } from 'react'
|
3
|
+
|
4
|
+
export const FieldLabelText = (field: IFormField): ReactElement => {
|
5
|
+
return (
|
6
|
+
<strong>{field.label} { field.required === true ? <span className='text-red-500'>*</span> : ''}</strong>
|
7
|
+
)
|
8
|
+
}
|
9
|
+
|
10
|
+
const FieldLabel = (field: IFormField): ReactElement => {
|
11
|
+
return <p className='pb-2'><FieldLabelText {...field} /></p>
|
12
|
+
}
|
13
|
+
|
14
|
+
export default FieldLabel
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { FieldLabelText } from '@/Form/Components/FieldLabel'
|
2
|
+
import { type IFieldInputProps } from '@/Form/FormCreatorTypes'
|
3
|
+
import { Checkbox } from '@axdspub/axiom-ui-utilities'
|
4
|
+
import React, { type ReactElement } from 'react'
|
5
|
+
|
6
|
+
const BooleanInput = ({ field, onChange, value }: IFieldInputProps): ReactElement => {
|
7
|
+
const initialValue = value !== undefined ? value : false
|
8
|
+
return <Checkbox id={field.id} testId={field.id} label={<FieldLabelText {...field} />} value={Boolean(initialValue)} onChange={(e) => {
|
9
|
+
onChange(e)
|
10
|
+
}} />
|
11
|
+
}
|
12
|
+
|
13
|
+
export default BooleanInput
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import FieldLabel from '@/Form/Components/FieldLabel'
|
2
|
+
import { type IFieldInputProps } from '@/Form/FormCreatorTypes'
|
3
|
+
import { TextArea } from '@axdspub/axiom-ui-utilities'
|
4
|
+
import React, { useState, type ReactElement } from 'react'
|
5
|
+
|
6
|
+
const JSONStringInput = ({ field, onChange, value }: IFieldInputProps): ReactElement => {
|
7
|
+
const [error, setError] = useState<string | undefined>(undefined)
|
8
|
+
const initialValue = value !== undefined ? value : ''
|
9
|
+
const getValue = (): string => {
|
10
|
+
return initialValue !== undefined && initialValue !== null
|
11
|
+
? typeof initialValue === 'object'
|
12
|
+
? JSON.stringify(initialValue, null, 2)
|
13
|
+
: String(initialValue)
|
14
|
+
: ''
|
15
|
+
}
|
16
|
+
return <div>
|
17
|
+
<TextArea
|
18
|
+
error={error}
|
19
|
+
className={
|
20
|
+
[
|
21
|
+
'min-h-[500px] bg-slate-50 rounded-lg shadow-inner'
|
22
|
+
// 'p-0 bg-[repeating-linear-gradient(to_bottom,var(--tw-gradient-stops))] from-[#efefef] from-[length:0_25px] to-[#FFF] to-[length:25px_50px]'
|
23
|
+
].join(' ')
|
24
|
+
}
|
25
|
+
id={field.id}
|
26
|
+
testId={field.id}
|
27
|
+
label={<FieldLabel {...field} />}
|
28
|
+
value={getValue()}
|
29
|
+
onChange={(e) => {
|
30
|
+
try {
|
31
|
+
JSON.parse(e ?? '')
|
32
|
+
onChange(JSON.parse(e ?? ''))
|
33
|
+
setError(undefined)
|
34
|
+
} catch (e) {
|
35
|
+
setError('Invalid JSON')
|
36
|
+
}
|
37
|
+
}} /></div>
|
38
|
+
}
|
39
|
+
|
40
|
+
export default JSONStringInput
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import FieldLabel from '@/Form/Components/FieldLabel'
|
2
|
+
import { type IFieldInputProps } from '@/Form/FormCreatorTypes'
|
3
|
+
import { TextArea } from '@axdspub/axiom-ui-utilities'
|
4
|
+
import React, { type ReactElement } from 'react'
|
5
|
+
|
6
|
+
const LongStringInput = ({ field, onChange, value }: IFieldInputProps): ReactElement => {
|
7
|
+
const initialValue = value !== undefined ? value : ''
|
8
|
+
const getValue = (): string => {
|
9
|
+
return initialValue !== undefined && initialValue !== null ? String(initialValue) : ''
|
10
|
+
}
|
11
|
+
return <div>
|
12
|
+
<TextArea
|
13
|
+
id={field.id}
|
14
|
+
testId={field.id}
|
15
|
+
label={<FieldLabel {...field} />}
|
16
|
+
value={getValue()}
|
17
|
+
onChange={(e) => {
|
18
|
+
onChange(e)
|
19
|
+
}} /></div>
|
20
|
+
}
|
21
|
+
|
22
|
+
export default LongStringInput
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import FieldLabel from '@/Form/Components/FieldLabel'
|
2
|
+
import { type IFieldInputProps } from '@/Form/FormCreatorTypes'
|
3
|
+
import { Input } from '@axdspub/axiom-ui-utilities'
|
4
|
+
import React, { type ReactElement } from 'react'
|
5
|
+
|
6
|
+
const NumberInput = ({ field, onChange, value }: IFieldInputProps): ReactElement => {
|
7
|
+
const initialValue = value !== undefined ? value : ''
|
8
|
+
return <div>
|
9
|
+
<Input
|
10
|
+
id={field.id}
|
11
|
+
testId={field.id}
|
12
|
+
value={initialValue !== undefined && initialValue !== null ? String(initialValue) : ''}
|
13
|
+
label={<FieldLabel {...field} />} onChange={(e) => {
|
14
|
+
if (e !== undefined && !isNaN(+e)) {
|
15
|
+
onChange(+e)
|
16
|
+
} else {
|
17
|
+
onChange(undefined)
|
18
|
+
}
|
19
|
+
}} /></div>
|
20
|
+
}
|
21
|
+
|
22
|
+
export default NumberInput
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import FieldCreator from '@/Form/Components/FieldCreator'
|
2
|
+
import FieldLabel from '@/Form/Components/FieldLabel'
|
3
|
+
import { type ICompositeValueType, type IFieldInputProps } from '@/Form/FormCreatorTypes'
|
4
|
+
import { utils } from '@axdspub/axiom-ui-utilities'
|
5
|
+
import React, { type ReactElement } from 'react'
|
6
|
+
|
7
|
+
const ObjectInput = ({ form, field, onChange, value, formValueState }: IFieldInputProps): ReactElement => {
|
8
|
+
const initialValue = (typeof value === 'object' ? value ?? {} : {}) as ICompositeValueType
|
9
|
+
if (field.type === 'object' && field.fields !== undefined) {
|
10
|
+
const cl = `${field.layout === 'horizontal' ? `flex flex-row gap-4 ${field.label !== undefined ? 'px-0' : ''}` : 'flex flex-col gap-4'}`
|
11
|
+
const fc = field.layout === 'horizontal' ? 'flex-1' : ''
|
12
|
+
return (
|
13
|
+
<div>
|
14
|
+
{
|
15
|
+
field.label !== undefined
|
16
|
+
? <FieldLabel {...field} />
|
17
|
+
: null
|
18
|
+
}
|
19
|
+
<div className={`p-4 bg-slate-100 ${cl}`}>
|
20
|
+
{
|
21
|
+
field.fields.map((childField) => {
|
22
|
+
const id = (field.path ?? [field.id]).concat(childField.id).join('.')
|
23
|
+
if (childField.type === 'object') {
|
24
|
+
childField.path = field.path !== undefined ? field.path.concat(id) : [id]
|
25
|
+
childField.level = field.level !== undefined ? field.level + 1 : 1
|
26
|
+
}
|
27
|
+
|
28
|
+
return (
|
29
|
+
<FieldCreator
|
30
|
+
formValueState={formValueState}
|
31
|
+
onChange={field.skip_path === true
|
32
|
+
? undefined
|
33
|
+
: (e) => {
|
34
|
+
initialValue[childField.id] = e
|
35
|
+
onChange({ ...initialValue })
|
36
|
+
}}
|
37
|
+
className={utils.makeClassName({
|
38
|
+
defaultClassName: 'p-0',
|
39
|
+
className: fc
|
40
|
+
})}
|
41
|
+
value={initialValue[childField.id]}
|
42
|
+
field={{ ...childField, id }}
|
43
|
+
form={form}
|
44
|
+
key={id}
|
45
|
+
/>
|
46
|
+
)
|
47
|
+
})
|
48
|
+
}
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
)
|
52
|
+
}
|
53
|
+
return <p>Field config for {field.id} is missing 'fields'</p>
|
54
|
+
}
|
55
|
+
|
56
|
+
export default ObjectInput
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import FieldLabel from '@/Form/Components/FieldLabel'
|
2
|
+
import { type IFieldInputProps } from '@/Form/FormCreatorTypes'
|
3
|
+
import { RadioGroup } from '@axdspub/axiom-ui-utilities'
|
4
|
+
import React, { type ReactElement } from 'react'
|
5
|
+
|
6
|
+
const RadioInput = ({ field, onChange, value }: IFieldInputProps): ReactElement => {
|
7
|
+
const initialValue = value !== undefined ? value : ''
|
8
|
+
|
9
|
+
if (field.type === 'radio' && field.options !== undefined) {
|
10
|
+
return <RadioGroup
|
11
|
+
id={field.id}
|
12
|
+
label={<FieldLabel {...field} />}
|
13
|
+
testId={field.id}
|
14
|
+
options={field.options}
|
15
|
+
value={initialValue !== undefined && initialValue !== null ? String(initialValue) : ''}
|
16
|
+
onChange={(e) => {
|
17
|
+
onChange(e?.value)
|
18
|
+
}}
|
19
|
+
/>
|
20
|
+
}
|
21
|
+
return <p>Field config for {field.id} is missing 'options'</p>
|
22
|
+
}
|
23
|
+
|
24
|
+
export default RadioInput
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import FieldLabel from '@/Form/Components/FieldLabel'
|
2
|
+
import { type IFieldInputProps } from '@/Form/FormCreatorTypes'
|
3
|
+
import { SelectInput } from '@axdspub/axiom-ui-utilities'
|
4
|
+
import React, { type ReactElement } from 'react'
|
5
|
+
|
6
|
+
const SingleSelectInput = ({ field, onChange, value }: IFieldInputProps): ReactElement => {
|
7
|
+
const initialValue = value !== undefined ? value : ''
|
8
|
+
|
9
|
+
if (field.type === 'select' && field.options !== undefined) {
|
10
|
+
return <SelectInput
|
11
|
+
id={field.id}
|
12
|
+
label={<FieldLabel {...field} />}
|
13
|
+
testId={field.id}
|
14
|
+
options={field.options}
|
15
|
+
value={initialValue !== undefined && initialValue !== null ? String(initialValue) : ''}
|
16
|
+
onChange={(e) => {
|
17
|
+
onChange(e?.value)
|
18
|
+
}}
|
19
|
+
/>
|
20
|
+
}
|
21
|
+
return <p>Field config for {field.id} is missing 'options'</p>
|
22
|
+
}
|
23
|
+
|
24
|
+
export default SingleSelectInput
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import FieldLabel from '@/Form/Components/FieldLabel'
|
2
|
+
import { type IFieldInputProps } from '@/Form/FormCreatorTypes'
|
3
|
+
import { Input } from '@axdspub/axiom-ui-utilities'
|
4
|
+
import React, { type ReactElement } from 'react'
|
5
|
+
|
6
|
+
const StringInput = ({ field, onChange, value }: IFieldInputProps): ReactElement => {
|
7
|
+
const initialValue = value !== undefined ? value : ''
|
8
|
+
return <div>
|
9
|
+
<Input
|
10
|
+
id={field.id}
|
11
|
+
testId={field.id}
|
12
|
+
value={initialValue !== undefined && initialValue !== null ? String(initialValue) : ''}
|
13
|
+
label={<FieldLabel {...field} />} onChange={(e) => {
|
14
|
+
onChange(e)
|
15
|
+
}} /></div>
|
16
|
+
}
|
17
|
+
|
18
|
+
export default StringInput
|
@@ -0,0 +1,6 @@
|
|
1
|
+
export { default as BooleanInput } from './Boolean'
|
2
|
+
export { default as JSONStringInput } from './JSONString'
|
3
|
+
export { default as NumberInput } from './Number'
|
4
|
+
export { default as TextInput } from './String'
|
5
|
+
export { default as LongTextInput } from './LongString'
|
6
|
+
export { default as SelectInput } from './SingleSelect'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import BooleanInput from '@/Form/Components/Inputs/Boolean'
|
2
|
+
import LongString from '@/Form/Components/Inputs/LongString'
|
3
|
+
import StringInput from '@/Form/Components/Inputs/String'
|
4
|
+
import ObjectInput from '@/Form/Components/Inputs/Object'
|
5
|
+
import Radio from '@/Form/Components/Inputs/RadioGroup'
|
6
|
+
import SingleSelect from '@/Form/Components/Inputs/SingleSelect'
|
7
|
+
import { type IFieldInputProps } from '@/Form/FormCreatorTypes'
|
8
|
+
import JSONStringInput from '@/Form/Components/Inputs/JSONString'
|
9
|
+
import NumberInput from '@/Form/Components/Inputs/Number'
|
10
|
+
|
11
|
+
const inputMap: Record<string, React.FC<IFieldInputProps>> = {
|
12
|
+
text: StringInput,
|
13
|
+
long_text: LongString,
|
14
|
+
number: NumberInput,
|
15
|
+
json: JSONStringInput,
|
16
|
+
boolean: BooleanInput,
|
17
|
+
select: SingleSelect,
|
18
|
+
radio: Radio,
|
19
|
+
object: ObjectInput
|
20
|
+
|
21
|
+
}
|
22
|
+
|
23
|
+
export default inputMap
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import FieldCreator from '@/Form/Components/FieldCreator'
|
2
|
+
import { type IFormValues, type IForm, type IValueChangeFn } from '@/Form/FormCreatorTypes'
|
3
|
+
import { copyAndAddPathToFields } from '@/Form/helpers'
|
4
|
+
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
|
5
|
+
import React, { useEffect, useState, type ReactElement } from 'react'
|
6
|
+
|
7
|
+
const FormCreator = ({
|
8
|
+
form,
|
9
|
+
formValueState,
|
10
|
+
note,
|
11
|
+
error,
|
12
|
+
onChange
|
13
|
+
}: {
|
14
|
+
form: IForm
|
15
|
+
formValueState: [IFormValues, (v: IFormValues) => void]
|
16
|
+
note?: string
|
17
|
+
error?: string
|
18
|
+
onChange?: IValueChangeFn
|
19
|
+
|
20
|
+
}): ReactElement => {
|
21
|
+
const [activeForm, setActiveForm] = useState<IForm | null>(null)
|
22
|
+
useEffect(() => {
|
23
|
+
const newForm = copyAndAddPathToFields<IForm>(form)
|
24
|
+
setActiveForm(newForm)
|
25
|
+
}, [form])
|
26
|
+
if (activeForm === null) {
|
27
|
+
return <p>Processing</p>
|
28
|
+
}
|
29
|
+
return (
|
30
|
+
<>
|
31
|
+
<div>
|
32
|
+
<h2 className='text-2xl pb-4 font-bold'>{form.label}</h2>
|
33
|
+
{
|
34
|
+
note !== undefined
|
35
|
+
? <p className='pb-4'>{note}</p>
|
36
|
+
: null
|
37
|
+
}
|
38
|
+
{
|
39
|
+
error !== undefined
|
40
|
+
? <p className='pb-4 text-rose-800'><ExclamationTriangleIcon className='inline mr-2' /> {error}</p>
|
41
|
+
: null
|
42
|
+
}
|
43
|
+
{
|
44
|
+
activeForm.description !== undefined
|
45
|
+
? <p className='pb-4'>{form.description}</p>
|
46
|
+
: null
|
47
|
+
}
|
48
|
+
<div className='flex flex-col gap-2'>
|
49
|
+
{
|
50
|
+
activeForm.fields.map((field) => {
|
51
|
+
return (
|
52
|
+
<FieldCreator onChange={onChange} form={form} field={field} key={field.id} formValueState={formValueState} />
|
53
|
+
)
|
54
|
+
})
|
55
|
+
}
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
</>
|
59
|
+
)
|
60
|
+
}
|
61
|
+
|
62
|
+
export default FormCreator
|