Initial commit
This commit is contained in:
195
components/FormGenerator.tsx
Normal file
195
components/FormGenerator.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
import {
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Heading,
|
||||
HStack,
|
||||
Input,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
Textarea,
|
||||
} from "@chakra-ui/react";
|
||||
import { type Dispatch, type SetStateAction, useState } from "react";
|
||||
|
||||
interface component {
|
||||
id: string;
|
||||
max_length?: number;
|
||||
options?: { default?: boolean; value: string }[];
|
||||
required: boolean;
|
||||
title: string;
|
||||
type: string;
|
||||
value?: number | string | string[];
|
||||
}
|
||||
|
||||
export default function ({
|
||||
components,
|
||||
read_only = true,
|
||||
}: {
|
||||
components: { [k: number]: component[] };
|
||||
read_only: boolean;
|
||||
}) {
|
||||
function isNumberElemInvalid(e: HTMLInputElement): boolean {
|
||||
return !(
|
||||
e.value ||
|
||||
e.valueAsNumber <= Number.MAX_SAFE_INTEGER ||
|
||||
e.valueAsNumber >= Number.MIN_SAFE_INTEGER
|
||||
);
|
||||
}
|
||||
|
||||
function updateState(
|
||||
state: { [k: string]: string | string[] },
|
||||
setState: Dispatch<SetStateAction<{}>>,
|
||||
id: string,
|
||||
value: string
|
||||
) {
|
||||
const newState = { ...state };
|
||||
newState[id] = value;
|
||||
|
||||
setState(newState);
|
||||
}
|
||||
|
||||
function renderCheckboxOptions(
|
||||
c: component,
|
||||
state: { [k: string]: string | string[] },
|
||||
setState: Dispatch<SetStateAction<{}>>
|
||||
) {
|
||||
if (!c.options) throw new Error("Options for checkbox are undefined");
|
||||
|
||||
const boxes = [];
|
||||
const checkedBoxes = [];
|
||||
|
||||
for (const option of c.options) {
|
||||
if (
|
||||
option.default ||
|
||||
(read_only && Array.isArray(c.value) && c.value.includes(option.value))
|
||||
)
|
||||
checkedBoxes.push(option.value);
|
||||
|
||||
boxes.push(
|
||||
<Checkbox
|
||||
isReadOnly={read_only}
|
||||
onChange={(e) => {
|
||||
const newState = { ...state };
|
||||
const groupValues = newState[c.id] ?? [];
|
||||
|
||||
if (!Array.isArray(groupValues))
|
||||
throw new Error("Expected CheckboxGroup values to be an array");
|
||||
|
||||
e.target.checked
|
||||
? groupValues.push(e.target.value)
|
||||
: groupValues.splice(
|
||||
groupValues.findIndex((v) => v === e.target.value),
|
||||
1
|
||||
);
|
||||
|
||||
newState[c.id] = groupValues;
|
||||
setState(newState);
|
||||
}}
|
||||
value={option.value}
|
||||
>
|
||||
{option.value}
|
||||
</Checkbox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CheckboxGroup defaultValue={checkedBoxes}>
|
||||
<HStack spacing={5}>{boxes}</HStack>
|
||||
</CheckboxGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function generateReactComponents(
|
||||
components: component[],
|
||||
state: { [k: string]: string | string[] },
|
||||
setState: Dispatch<SetStateAction<{}>>
|
||||
): JSX.Element[] {
|
||||
const fragmentsList = [];
|
||||
|
||||
for (const component of components) {
|
||||
fragmentsList.push(
|
||||
<Heading size="md">{component.title}</Heading>,
|
||||
<br />
|
||||
);
|
||||
|
||||
switch (component.type) {
|
||||
case "checkbox":
|
||||
fragmentsList.push(renderCheckboxOptions(component, state, setState));
|
||||
break;
|
||||
|
||||
case "input":
|
||||
fragmentsList.push(
|
||||
<FormControl
|
||||
isInvalid={
|
||||
!(document.getElementById(component.id) as HTMLInputElement)
|
||||
.value.length
|
||||
}
|
||||
isReadOnly={read_only}
|
||||
>
|
||||
<Input
|
||||
id={component.id}
|
||||
maxLength={component.max_length}
|
||||
onChange={(e) =>
|
||||
updateState(state, setState, component.id, e.target.value)
|
||||
}
|
||||
placeholder="Your response"
|
||||
value={component.value}
|
||||
/>
|
||||
<FormErrorMessage>Field is required</FormErrorMessage>
|
||||
</FormControl>
|
||||
);
|
||||
break;
|
||||
|
||||
case "number":
|
||||
fragmentsList.push(
|
||||
<NumberInput
|
||||
isInvalid={isNumberElemInvalid(
|
||||
document.getElementById(component.id) as HTMLInputElement
|
||||
)}
|
||||
isReadOnly={read_only}
|
||||
>
|
||||
<NumberInputField
|
||||
id={component.id}
|
||||
onChange={(e) =>
|
||||
updateState(state, setState, component.id, e.target.value)
|
||||
}
|
||||
value={component.value}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
fragmentsList.push(<br />, <br />, <br />);
|
||||
}
|
||||
|
||||
return fragmentsList;
|
||||
}
|
||||
|
||||
const pages = [];
|
||||
const [responses, setResponses] = useState({});
|
||||
|
||||
for (const [page, componentList] of Object.entries(components)) {
|
||||
pages.push(
|
||||
<div
|
||||
id={`form-page-${page}`}
|
||||
style={{ display: page ? "none" : undefined }}
|
||||
>
|
||||
{generateReactComponents(componentList, responses, setResponses)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
}
|
Reference in New Issue
Block a user