Skip to content
Permalink
84e7df6c30
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
264 lines (231 sloc) 6.47 KB
import {
Checkbox,
CheckboxGroup,
FormControl,
FormErrorMessage,
Heading,
HStack,
Input,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Radio,
RadioGroup,
Select,
Stack,
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 ||
isNaN(e.valueAsNumber) ||
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 renderRadioElements(
c: component,
state: { [k: string]: string | string[] },
setState: Dispatch<SetStateAction<{}>>,
) {
if (!c.options) throw new Error("Options for radio buttons are undefined!");
const buttons = [];
for (const option of c.options) {
buttons.push(
<Radio checked={option.default} value={option.value}>
{option.value}
</Radio>,
);
}
return (
<RadioGroup
id={c.id}
onChange={(e) => {
const newState = { ...state };
newState[c.id] = e;
setState(newState);
}}
>
<Stack direction="row">{buttons}</Stack>
</RadioGroup>
);
}
function renderSelectElements(
c: component,
state: { [k: string]: string | string[] },
setState: Dispatch<SetStateAction<{}>>,
) {
if (!c.options) throw new Error("Options for select are undefined!");
const selectOptions = [];
for (const option of c.options) {
selectOptions.push(<option value={option.value}>{option.value}</option>);
}
return (
<Select
onChange={(e) => {
const newState = { ...state };
newState[c.id] = e.target.value;
setState(newState);
}}
placeholder="Select option"
>
{selectOptions}
</Select>
);
}
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;
case "radio":
fragmentsList.push(renderRadioElements(component, state, setState));
break;
case "select":
fragmentsList.push(renderSelectElements(component, state, setState));
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 <></>;
}