├── .gitignore
├── Documentation
├── Examples
│ ├── EmployeeFeeback.md
│ ├── README.md
│ └── SudentRegistration.md
└── MultiStep
│ └── README.md
├── README.md
├── babel.config.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.tsx
├── index.tsx
└── llama
│ ├── components
│ ├── Fields
│ │ ├── CheckBoxField.tsx
│ │ ├── DropDownField.tsx
│ │ ├── FileUploadField.tsx
│ │ ├── InputField.tsx
│ │ ├── RadioField.tsx
│ │ └── TextAreaField.tsx
│ ├── Form
│ │ ├── multipleForm.tsx
│ │ ├── renderForm.tsx
│ │ └── singleForm.tsx
│ ├── Loader.tsx
│ └── Progress.tsx
│ └── index.tsx
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /dist
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/Documentation/Examples/EmployeeFeeback.md:
--------------------------------------------------------------------------------
1 |
(https://github.com/orgs/nettantra/teams/llama-forms-developers)",
27 | "license": "MIT",
28 | "peerDependencies": {
29 | "react": ">=16"
30 | },
31 | "dependencies": {
32 | "core-js": "2.6.5"
33 | },
34 | "devDependencies": {
35 | "@types/react": "^18.0.14",
36 | "@types/react-dom": "^18.0.5",
37 | "react": "^18.2.0",
38 | "react-dom": "^18.2.0",
39 | "react-scripts": "5.0.1",
40 | "tsdx": "^0.14.1",
41 | "tslib": "^2.4.0",
42 | "typescript": "^4.7.4"
43 | },
44 | "scripts": {
45 | "package": "rm -rf dist && tsdx build -i src/llama/index.tsx",
46 | "start": "react-scripts start",
47 | "build": "react-scripts build",
48 | "eject": "react-scripts eject"
49 | },
50 | "browserslist": {
51 | "production": [
52 | ">0.2%",
53 | "not dead",
54 | "not op_mini all"
55 | ],
56 | "development": [
57 | "last 1 chrome version",
58 | "last 1 firefox version",
59 | "last 1 safari version"
60 | ]
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nettantra/llama-forms-react/8013e616d20dacfe852d1dfe9cd88b8245eb029a/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nettantra/llama-forms-react/8013e616d20dacfe852d1dfe9cd88b8245eb029a/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nettantra/llama-forms-react/8013e616d20dacfe852d1dfe9cd88b8245eb029a/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LlamaForm from './llama/index'
3 | // import {LlamaForm} from "test-react-form";
4 |
5 |
6 | function App() {
7 |
8 | const login_test = (data:any) => {
9 | console.log("login", data)
10 | }
11 |
12 | return (
13 |
14 |
299 |
300 | );
301 | }
302 |
303 | export default App;
304 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App';
4 |
5 | const root = ReactDOM.createRoot(
6 | document.getElementById('root') as HTMLElement
7 | );
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/src/llama/components/Fields/CheckBoxField.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import React from 'react';
3 | interface Props{
4 | properties:any,
5 | handleData:any,
6 | name:any,
7 | parentState:any,
8 |
9 | }
10 |
11 | export default function CheckBoxField(props:Props) {
12 | const { properties, handleData, name } = props;
13 | const [chechBoxData, setCheckBoxData]:any = useState(props.parentState[name].value);
14 |
15 | const handleChange = (e:any) => {
16 | let checkObj = { ...chechBoxData, [e.target.value]: e.target.checked}
17 | for (let key in checkObj) {
18 | if (!checkObj[key]) {
19 | delete checkObj[key]
20 | }
21 | }
22 | setCheckBoxData(checkObj);
23 | handleData(checkObj);
24 | };
25 |
26 | return (
27 | <>
28 |
29 |
37 | {properties["label"]}
38 |
39 | {properties["values"] &&
40 | properties["values"].map((item:any, index:any) => {
41 | return (
42 |
43 |
54 |
63 | {item}
64 |
65 |
66 | );
67 | })}
68 |
76 | {properties["description"]}
77 |
78 |
79 |
80 |
87 | >
88 | );
89 | }
90 |
--------------------------------------------------------------------------------
/src/llama/components/Fields/DropDownField.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface Props{
4 | properties:any,
5 | handleData:any,
6 | name:any,
7 | parentState:any,
8 | }
9 |
10 | export default function DropDownField(props:Props) {
11 | const { properties, handleData, name } = props;
12 |
13 | const handleChange = (e:any) => {
14 | handleData(e.target.value);
15 | };
16 |
17 |
18 | //loop through the values object
19 | const values = properties.values;
20 | let options = [];
21 | for (let key in values) {
22 | options.push(
23 |
24 | {key}
25 |
26 | );
27 | }
28 | return (
29 | <>
30 |
31 |
39 | {properties["label"]}
40 |
41 |
42 |
43 |
51 | {properties["description"]}
52 |
53 |
64 | {
71 | handleChange(e);
72 | }}
73 | style={{
74 | border: "none",
75 | backgroundColor: "transparent",
76 | fontFamily: "Nunito Sans",
77 | }}
78 | >
79 | {props.parentState[name].value ? null : (
80 | Select
81 | )}
82 | {options}
83 |
84 |
85 |
86 |
87 |
88 |
95 | >
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/src/llama/components/Fields/FileUploadField.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import React from 'react';
3 |
4 | interface Props{
5 | properties:any,
6 | handleData:any,
7 | name:any,
8 | parentState:any,
9 | }
10 |
11 | export default function FileUploadField(props:Props) {
12 | const { properties, handleData, name } = props;
13 | const [error, setError] = useState(false);
14 |
15 | function checkFileType(file:any) {
16 | const fileTypes = properties.accept;
17 | if (!fileTypes) {
18 | return true;
19 | }
20 | if (fileTypes.includes(file.type.split("/")[1])) {
21 | if (properties.maxFileSize && file.size > properties.maxFileSize * 1000) {
22 | return false;
23 | }
24 | if (properties.minFileSize && file.size < properties.minFileSize * 1000) {
25 | return false;
26 | }
27 | return true;
28 | }
29 | return false;
30 | }
31 |
32 | const handleChange = (e:any) => {
33 | if (e.target.files && e.target.files[0]) {
34 | if (checkFileType(e.target.files[0])) {
35 | setError(false);
36 | handleData(e.target.files[0]);
37 | } else {
38 | setError(true);
39 | handleData(e.target.files[0], true);
40 | }
41 | }
42 | };
43 |
44 | return (
45 | <>
46 |
47 |
55 | {properties["label"]}
56 |
57 | {properties["type"] === "range" ? (
58 |
{props.parentState[name].value}
59 | ) : null}
60 |
{
77 | handleChange(e);
78 | }}
79 | />
80 |
97 |
98 |
99 |
100 |
108 | {properties["description"]}
109 |
110 | {error ? (
111 |
120 | {properties["errorMessage"]
121 | ? properties["errorMessage"]
122 | : "Invalid file type or size"}
123 |
124 | ) : null}
125 |
126 |
127 | >
128 | );
129 | }
130 |
--------------------------------------------------------------------------------
/src/llama/components/Fields/InputField.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from "react";
2 |
3 | interface LooseObject {
4 | [key: string]: any
5 | }
6 | interface Props {
7 | properties: LooseObject,
8 | handleData: any,
9 | name: any,
10 | parentState: any,
11 | }
12 |
13 | export default function InputField(props: Props) {
14 | const { properties, handleData, name } = props
15 | const [error, setError] = useState(false)
16 |
17 | let capsWarning: any = useRef();
18 | let inputRef: any = useRef();
19 |
20 | const regexObject: any = {
21 | email: { regex: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, errorMessage: "Please enter a valid email" },
22 | number: { regex: /^[0-9]*$/, errorMessage: "Please enter a valid number" },
23 | }
24 |
25 | function checkValidation(regex: string, value: string) {
26 | const re = new RegExp(regex);
27 | return re.test(value)
28 | }
29 | //*****
30 |
31 | // const prefix = 'prefix-'
32 | const caseChange = (value: any) => {
33 | if (properties["lowercase"]) return value.toLowerCase();
34 | if (properties["uppercase"]) return value.toUpperCase();
35 | return value
36 | }
37 | //it add prefix with input value
38 | const prefixChange = (value: any) => {
39 | if (properties["prefix"]) return value.substring(properties["prefix"].length)
40 | return value
41 | }
42 | //it block invalid character in number
43 | const blockInvalidChar = (e: any) => {
44 | if (properties["type"] === "number") ['e', 'E', '+', '-'].includes(e.key) && e.preventDefault();
45 | return true
46 | }
47 | // this function check the capsLock event in input field
48 | const handleCapsLockchek = (e: any) => {
49 | if (e.getModifierState("CapsLock")) {
50 | capsWarning.current.hidden = false;
51 | capsWarning.current.style.color = 'red';
52 | capsWarning.current.innerHTML = properties?.["capsLockMessage"]?.trim() || "WARNING! Caps lock is ON.";
53 | inputRef.current.value = "";
54 | } else {
55 | capsWarning.current.hidden = true;
56 | }
57 | }
58 |
59 | // const suffixChange = (input) => {
60 | // if(["text", "number"].includes(properties['type'])) return input.replace(/^/g, `${prefix + " "}`)
61 | // // prefix +" "+ input.substring(prefix.length);
62 | // return input
63 |
64 | // }
65 | //*** */
66 | const handleChange = (e: any) => {
67 | let input = e.target.value
68 | // let n = prefix + j
69 | // let i = input.replace(/^/g, `${prefix + " "}`)
70 | // ["text", "number"].includes(properties['type'])? prefix + input.substring(prefix.length) : input
71 | if (input.length > properties["maxLength"]) {
72 | setError(true)
73 | return false
74 | }
75 | let value = caseChange(input)
76 | value = prefixChange(value)
77 | handleData(value, false)
78 |
79 | if (value.length === 0) {
80 | setError(false)
81 | handleData(value, false)
82 | return
83 | }
84 | if (properties.validationRegex) {
85 | setError(!checkValidation(properties['validationRegex'], value))
86 | handleData(value, !checkValidation(properties['validationRegex'], value))
87 | } else if (properties.type in regexObject) {
88 | setError(!checkValidation(regexObject[properties.type]['regex'], value))
89 | handleData(value, !checkValidation(regexObject[properties.type]['regex'], value))
90 | }
91 | }
92 | useEffect(() => {
93 |
94 | if (properties?.["className"]?.trim()) {
95 | inputRef.current.style = ""
96 | inputRef.current.className = properties?.["className"] ?? name
97 | }
98 | if (properties["style"]) {
99 | inputRef.current.style = ""
100 | for (let key in properties["style"]) {
101 | inputRef.current.style.setProperty(key, properties["style"][key]);
102 | }
103 | }
104 | }, []);
105 | return (
106 | <>
107 |
108 |
{properties['label']}
109 | {properties['type'] === 'range' &&
{props.parentState[name].value ? props.parentState[name].value : 0} }
110 |
{ handleChange(e) }}
130 | onKeyDown={blockInvalidChar}
131 | onKeyUp={properties["capsLockWaring"] ? handleCapsLockchek : (e) => { console.log("caps lock warning is disabled") }}
132 | ref={inputRef}
133 | // ref={(target)=>{
134 | // console.log(target.value)
135 | // target.value = prefix
136 | // }}
137 | // ref={(target)=>{
138 | // target.value = prefix
139 | // }}
140 | // onChange={(e)=>{
141 | // const input = e.target.value
142 | // e.target.value = prefix + input
143 | // }}
144 | />
145 |
146 |
{properties['description']}
147 |
148 | {error ?
{properties['errorMessage'] ? properties['errorMessage'] : (properties.type in regexObject) ? regexObject[properties.type]['errorMessage'] : null}
: null}
149 |
150 |
151 | >
152 | )
153 | }
--------------------------------------------------------------------------------
/src/llama/components/Fields/RadioField.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface Props{
4 | properties:any,
5 | handleData:any,
6 | name:any,
7 | parentState:any,
8 | }
9 | export default function RadioField(props:Props) {
10 | const { properties, handleData, name } = props;
11 |
12 | const handleChange = (e:any) => {
13 | handleData(e.target.value);
14 | };
15 |
16 | return (
17 | <>
18 |
19 |
27 | {properties["label"]}
28 |
29 | {properties["values"].map((value:any, index:any) => {
30 | return (
31 |
32 | {
39 | handleChange(e);
40 | }}
41 | disabled={
42 | properties["readOnly"] ? properties["readOnly"] : false
43 | }
44 | />
45 |
54 | {value}
55 |
56 |
57 | );
58 | })}
59 |
{properties["description"]}
66 |
67 |
68 |
120 | >
121 | );
122 | }
123 |
--------------------------------------------------------------------------------
/src/llama/components/Fields/TextAreaField.tsx:
--------------------------------------------------------------------------------
1 | import React,{ useEffect, useRef, useState } from "react";
2 |
3 | interface Props{
4 | properties:any,
5 | handleData:any,
6 | name:any,
7 | parentState:any,
8 | }
9 |
10 | export default function TextAreaField(props:Props) {
11 | const { properties, handleData, name } = props;
12 | let textareaRef : any = useRef()
13 | //********
14 | const caseChange = (value:any) => {
15 | if (properties["lowercase"]) return value.toLowerCase();
16 | if (properties["uppercase"]) return value.toUpperCase();
17 | return value
18 | }
19 | //**** */
20 | const handleChange = (e:any) => {
21 | const value = caseChange(e.target.value)
22 | handleData(value);
23 | };
24 |
25 | useEffect(() => {
26 | if (properties?.["className"]?.trim()) {
27 | textareaRef.current.style = ""
28 | textareaRef.current.className = properties?.["className"] ?? name
29 | }
30 | if (properties["style"]) {
31 | textareaRef.current.style = ""
32 | for (let key in properties["style"]) {
33 | textareaRef.current.style.setProperty(key, properties["style"][key]);
34 | }
35 | }
36 | }, []);
37 |
38 | return (
39 | <>
40 |
41 |
49 | {properties["label"]}
50 |
51 |
80 | >
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/src/llama/components/Form/multipleForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, forwardRef } from "react";
2 | import RenderForm from "./renderForm";
3 | import Loader from "../Loader";
4 |
5 | interface LooseObject {
6 | [key: string]: any;
7 | }
8 | interface Props {
9 | initialStep: number;
10 | fields: any;
11 | parentState: object;
12 | parentSetState: object;
13 | wizardStepSet: object;
14 | onSubmit: any;
15 | buttons: LooseObject;
16 | step: any;
17 | wizardStepOptions: any;
18 | }
19 |
20 | const MultipleForm = forwardRef((props: Props, ref: any) => {
21 | const [step, setStep] = useState(props.initialStep ?? 1);
22 |
23 | const [alert, setAlert] = useState(false);
24 | const [alertMsg, setAlertMsg] = useState("");
25 | const [loading, setLoading] = useState(false);
26 |
27 | const fields = props.fields;
28 | const data: any = props.parentState;
29 | let fieldSet: LooseObject = props.wizardStepSet;
30 |
31 | const alertRender = () => {
32 | return (
33 |
46 | {alertMsg}
47 |
48 | );
49 | };
50 |
51 | const handleNext = async () => {
52 | setLoading(true);
53 | let currentFields = fieldSet[step];
54 | for (let i in currentFields) {
55 | if (
56 | data[currentFields[i]].value === "" &&
57 | fields[currentFields[i]].required
58 | ) {
59 | setAlert(false);
60 | setAlertMsg(`${currentFields[i]} field is required`);
61 | setAlert(true);
62 | setTimeout(() => {
63 | setAlert(false);
64 | setLoading(false);
65 | }, 5000);
66 | return;
67 | }
68 | if (data[currentFields[i]]?.error) {
69 | setAlert(false);
70 | setAlertMsg(`Please look into ${currentFields[i]} field`);
71 | setAlert(true);
72 | setTimeout(() => {
73 | setAlert(false);
74 | setLoading(false);
75 | }, 5000);
76 | return;
77 | }
78 | }
79 | let finalData: any = {};
80 | for (let key in data) {
81 | finalData[key] = data[key].value;
82 | }
83 |
84 | let currentData = {
85 | step: step,
86 | data: finalData,
87 | };
88 |
89 | let wizardStepOptions = props?.wizardStepOptions;
90 |
91 | // if onNext not available for specific steps then call global one
92 | if (wizardStepOptions && props?.wizardStepOptions[step]?.onNext) {
93 | await props?.wizardStepOptions[step]?.onNext(currentData);
94 | } else {
95 | if (wizardStepOptions?.onNext) {
96 | await wizardStepOptions.onNext(currentData);
97 | }
98 | }
99 |
100 | setStep(step + 1);
101 | props.step(step + 1);
102 | setLoading(false);
103 | };
104 |
105 | const handlePrevious = () => {
106 | setLoading(true);
107 | setStep(step - 1);
108 | props.step(step - 1);
109 | setLoading(false);
110 | };
111 |
112 | const handleSubmit = () => {
113 | setLoading(true);
114 | let currentFields = fieldSet[step];
115 | for (let i in currentFields) {
116 | if (
117 | data[currentFields[i]].value === "" &&
118 | fields[currentFields[i]].required
119 | ) {
120 | setAlert(false);
121 | setAlertMsg(`${currentFields[i]} field is required`);
122 | setAlert(true);
123 | setTimeout(() => {
124 | setAlert(false);
125 | setLoading(false);
126 | }, 5000);
127 | return;
128 | }
129 | if (data[currentFields[i]]?.error) {
130 | setAlert(false);
131 | setAlertMsg(`Please look into ${currentFields[i]} field`);
132 | setAlert(true);
133 | setTimeout(() => {
134 | setAlert(false);
135 | setLoading(false);
136 | }, 5000);
137 | return;
138 | }
139 | }
140 | let finalData: any = {};
141 | for (let key in data) {
142 | finalData[key] = data[key].value;
143 | }
144 | props.onSubmit(finalData);
145 | setLoading(false);
146 | };
147 |
148 | return (
149 | <>
150 | {alert && alertRender()}
151 |
157 |
158 |
162 | {[
163 | props?.buttons?.["previous"]?.text ?? "Previous",
164 | props?.buttons?.["previous"]?.loader ? (
165 | loading ? (
166 |
167 | ) : null
168 | ) : null,
169 | ]}
170 |
171 | {step === parseInt(String(Object.keys(fieldSet).pop())) ? (
172 |
173 | {[
174 | props?.buttons?.["submit"]?.text ?? "Submit",
175 | props?.buttons?.["next"]?.loader ? (
176 | loading ? (
177 |
178 | ) : null
179 | ) : null,
180 | ]}
181 |
182 | ) : (
183 |
188 | {[
189 | props?.buttons?.["next"]?.text ?? "Next",
190 | props?.buttons?.["next"]?.loader ? (
191 | loading ? (
192 |
193 | ) : null
194 | ) : null,
195 | ]}
196 |
197 | )}
198 |
199 |
212 | >
213 | );
214 | });
215 | export default MultipleForm;
216 |
--------------------------------------------------------------------------------
/src/llama/components/Form/renderForm.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { Fragment} from "react";
4 | import DropDownField from "../Fields/DropDownField";
5 | import InputField from "../Fields/InputField";
6 | import RadioField from "../Fields/RadioField";
7 | import TextAreaField from "../Fields/TextAreaField";
8 | import CheckBoxField from "../Fields/CheckBoxField";
9 | import FileUploadField from "../Fields/FileUploadField";
10 |
11 |
12 | interface Props{
13 | fields:any,
14 | renderList:any,
15 | parentState:any,
16 | parentSetState:any,
17 |
18 | }
19 | export default function RenderForm(props:Props) {
20 | const fields = props.fields
21 | const fieldList = props.renderList || []
22 |
23 | const renderForm = (type:any, index:any, handleData:any, properties:any, data:any, key:any) => {
24 | // switch case for the different field types
25 | switch (type) {
26 | case 'dropdown': {
27 | return (
28 |
29 |
35 |
36 | )
37 | }
38 | case 'radio': {
39 | return (
40 |
41 |
47 |
48 | )
49 | }
50 | case 'checkbox': {
51 | return (
52 |
53 |
59 |
60 | )
61 | }
62 | case 'file': {
63 | return (
64 |
65 |
71 |
72 | )
73 | }
74 | case 'textarea': {
75 | return (
76 |
77 |
84 |
85 | )
86 | }
87 | default: {
88 | return (
89 |
90 |
96 |
97 | )
98 | }
99 | }
100 | }
101 |
102 |
103 | return (
104 | <>
105 | {fieldList.map((field:any, index:any) => {
106 | const handleData = (
107 | value = props.parentState[field].value,
108 | error = false
109 | ) => {
110 | props.parentSetState({
111 | ...props.parentState,
112 | [field]: { value: value, error: error },
113 | });
114 | };
115 | let properties = fields[field];
116 | if (!properties.depend) {
117 | return renderForm(
118 | fields[field].type,
119 | index,
120 | handleData,
121 | properties,
122 | props.parentState,
123 | field
124 | );
125 | } else {
126 | if (props.parentState[properties.parentField].value === properties?.dependent?.value[0]) {
127 | return renderForm(fields[field].type, index, handleData, properties, props.parentState, field)
128 | }
129 | if (properties?.dependent?.type?.toLowerCase() === "multi" && properties?.dependent?.value?.every((key : string) => Object.keys(props.parentState[properties.parentField].value).includes(key))) {
130 | return renderForm(fields[field].type, index, handleData, properties, props.parentState, field)
131 | } if (properties?.dependent?.type?.toLowerCase() === "single" && Object.keys(props.parentState[properties.parentField].value).some((val) => properties?.dependent?.value?.includes(val))) {
132 | return renderForm(fields[field].type, index, handleData, properties, props.parentState, field)
133 | }
134 | }
135 | })}
136 | >
137 | );
138 | }
--------------------------------------------------------------------------------
/src/llama/components/Form/singleForm.tsx:
--------------------------------------------------------------------------------
1 | import React,{ useState, forwardRef } from "react";
2 | import RenderForm from "./renderForm";
3 |
4 | interface Props{
5 | fields:any,
6 | parentState:any,
7 | parentSetState:any,
8 | renderList:object,
9 | submitButtonText:String,
10 | onSubmit:any
11 | }
12 |
13 | const SingleForm = forwardRef((props:Props, ref:any)=> {
14 | const fields = props.fields;
15 | const data = props.parentState;
16 |
17 | const [alert, setAlert] = useState(false);
18 | const [alertMsg, setAlertMsg] = useState("");
19 |
20 | const alertRender = () => {
21 | return (
22 |
35 | {alertMsg}
36 |
37 | );
38 | };
39 |
40 | const handleSubmit = () => {
41 | setAlert(false);
42 | for (let key in fields) {
43 | if (data[key].value === "" && fields[key].required) {
44 | setAlert(false);
45 | setAlertMsg(`${key} field is required`);
46 | setAlert(true);
47 | setTimeout(() => {
48 | setAlert(false);
49 | }, 5000);
50 | return;
51 | }
52 | if (data[key].error) {
53 | setAlert(false);
54 | setAlertMsg(`Please look into ${key} field`);
55 | setAlert(true);
56 | setTimeout(() => {
57 | setAlert(false);
58 | }, 5000);
59 | return;
60 | }
61 | }
62 | let finalData:any = {};
63 | for (let key in data) {
64 | finalData[key] = data[key].value;
65 | }
66 | props.onSubmit(finalData);
67 | };
68 |
69 | return (
70 | <>
71 | {alert && alertRender()}
72 |
78 |
90 | {props?.submitButtonText ?? "Submit"}
91 |
92 | >
93 | );
94 | })
95 | export default SingleForm;
96 |
--------------------------------------------------------------------------------
/src/llama/components/Loader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | const Loader = () => {
5 | const loader:object = {
6 | height: '12px',
7 | width: '12px',
8 | borderRadius: '50%',
9 | border: "2px solid #f3f3f3",
10 | borderTop: "2px solid #3498db",
11 | borderLeft: "2px solid #3498db",
12 | borderBottom: "2px solid #3498db",
13 | WebkitAnimation: 'spin 1.5s linear infinite', // Safari
14 | animation: 'spin 1.5s linear infinite',
15 | display: 'inline-block',
16 | margin: 'auto',
17 | position: 'relative',
18 | marginLeft:"10px",
19 | }
20 | const animation =`
21 | @-webkit-keyframes spin {
22 | 0% { -webkit-transform: rotate(0deg); }
23 | 100% { -webkit-transform: rotate(360deg); }
24 | }
25 |
26 | @keyframes spin {
27 | 0% { transform: rotate(0deg); }
28 | 100% { transform: rotate(360deg); }
29 | }
30 | `
31 | return (<>
32 |
33 |
34 | >
35 | )
36 | }
37 |
38 | export default Loader
--------------------------------------------------------------------------------
/src/llama/components/Progress.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const Progress = ({
4 | className,
5 | height,
6 | width,
7 | ProgressBar,
8 | subProgressBar,
9 | color,
10 | text,
11 | textAlign,
12 | textColor,
13 | stepLength,
14 | step,
15 | align,
16 | }: any) => {
17 | const Parentdiv = {
18 | height: height ? height : 20,
19 | width: width ? width : "100%",
20 | justifyContent: align,
21 | };
22 |
23 | const Childdiv = {
24 | height: "100%",
25 | width: `${(step / stepLength) * 100}%`,
26 | backgroundColor: color ? color : "#99ccff",
27 | borderRadius: 40,
28 | textAlign: textAlign ? textAlign : "center",
29 | };
30 |
31 | const progresstext = {
32 | color: textColor ? textColor : "black",
33 | };
34 |
35 | const SubStep = {
36 | display: subProgressBar ? "block" : "none",
37 | };
38 |
39 | return (
40 | <>
41 | {ProgressBar ? (
42 |
45 |
46 |
47 | {text}
48 |
49 |
50 |
51 | {subProgressBar ? (
52 |
53 | Step {step} of {stepLength}
54 |
55 | ) : null}
56 |
57 | ) : null}
58 | >
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/src/llama/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from "react";
2 | import MultipleForm from "./components/Form/multipleForm";
3 | import SingleForm from "./components/Form/singleForm";
4 | import { Progress } from "./components/Progress";
5 | interface LooseObject {
6 | [key: string]: any;
7 | }
8 |
9 | export const LlamaForm = (props: any) => {
10 | const { schema = {}, options, onSubmit } = props;
11 | const {
12 | type,
13 | title,
14 | description,
15 | wizard,
16 | wizardOptions = {},
17 | progressBar = {},
18 | buttons = {},
19 | initialStep,
20 | properties = {},
21 | } = schema;
22 |
23 | const [fields, setFields] = useState({});
24 | const [fieldList, setFieldList] = useState([]);
25 | const [data, setData] = useState({});
26 | const [wizardStepSet, setWizardStepSet] = useState({});
27 | const [step, setStep] = useState(initialStep ?? 1);
28 |
29 | let enterButton: any = useRef();
30 |
31 | //loop through the fields and set the data
32 | const customizeData = (fields: LooseObject) => {
33 | let tempData: LooseObject = {};
34 | let list: any = [];
35 | let wizardSet: any = {};
36 | for (let key in fields) {
37 | tempData[key] = { value: fields[key].value || "", error: false };
38 | list.push(key);
39 | try {
40 | wizardSet[fields[key].step].push(key);
41 | } catch {
42 | wizardSet[fields[key].step] = [key];
43 | }
44 | }
45 | setData(tempData);
46 | setFieldList(list);
47 | setWizardStepSet(wizardSet);
48 | };
49 |
50 | const structureData = () => {
51 | if (!properties) return;
52 | let tempFields: any = {};
53 | const fields = options.fields || {};
54 | const value = props.data || {};
55 |
56 | /* Looping through the properties and setting the fields. */
57 | for (let key in properties) {
58 | tempFields[key] = {
59 | ...fields[key],
60 | step: properties[key].step
61 | ? properties[key].step
62 | : properties[key].depend
63 | ? properties[fields[key].parentField].step
64 | : 1,
65 | type: fields[key] ? fields[key].type : "",
66 | values: properties[key].enum || "",
67 | required: properties[key].required || false,
68 | value: value[key] || "",
69 | parentField: fields[key]?.parentField || "",
70 | dependentValue: fields[key]?.dependentValue || "",
71 | depend: properties[key]?.depend || false,
72 | };
73 | }
74 |
75 | setFields(tempFields);
76 | customizeData(tempFields);
77 | };
78 |
79 | useEffect(() => {
80 | structureData();
81 | }, []);
82 |
83 | const formBuilder = () => {
84 | if (!props.schema) return;
85 | if (wizard) {
86 | return (
87 |
99 | );
100 | }
101 |
102 | return (
103 |
112 | );
113 | };
114 |
115 | useEffect(() => {
116 | const listener = (event: any) => {
117 | if (event.key === "Enter" || event.code === "NumpadEnter") {
118 | event.preventDefault();
119 | enterButton.current.click();
120 | }
121 | };
122 | document.addEventListener("keydown", listener);
123 | //need to clear keydown function
124 | return () => {
125 | document.removeEventListener("keydown", listener);
126 | };
127 | }, [step]);
128 |
129 | const {
130 | show: pbShow,
131 | height: pbHeight,
132 | width: pbWidth,
133 | color: pbColor,
134 | text: pbText,
135 | textColor: pbTextColor,
136 | subProgress,
137 | align: pbAlign,
138 | textAlign: pbTextAlign,
139 | className: pbClassName,
140 | } = progressBar;
141 | return (<>
142 |
143 |
{title}
144 |
145 |
{description}
146 |
147 | {progressBar ? (
148 |
162 | ) : null}
163 |
164 |
171 |
172 |
210 | >
211 | );
212 | };
213 |
214 |
215 | export default LlamaForm;
216 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | "lib": [
16 | "dom",
17 | "dom.iterable",
18 | "esnext"
19 | ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
20 | "jsx": "react", /* Specify what JSX code is generated. */
21 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
22 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
23 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
24 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
25 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
26 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
27 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
28 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
29 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
30 |
31 | /* Modules */
32 | "module": "esnext", /* Specify what module code is generated. */
33 | // "rootDir": "./", /* Specify the root folder within your source files. */
34 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
35 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
36 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
37 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
38 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
39 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
40 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
41 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
42 | "resolveJsonModule": true, /* Enable importing .json files. */
43 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
44 |
45 | /* JavaScript Support */
46 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
47 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
48 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
49 |
50 | /* Emit */
51 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
52 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
53 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
54 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
55 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
56 | // "outDir": "./", /* Specify an output folder for all emitted files. */
57 | // "removeComments": true, /* Disable emitting comments. */
58 | "noEmit": true, /* Disable emitting files from a compilation. */
59 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
60 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
61 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
62 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
63 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
64 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
65 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
66 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
67 | // "newLine": "crlf", /* Set the newline character for emitting files. */
68 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
69 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
70 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
71 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
72 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
73 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
74 |
75 | /* Interop Constraints */
76 | "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
77 | "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
78 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
79 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
80 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
81 |
82 | /* Type Checking */
83 | "strict": true, /* Enable all strict type-checking options. */
84 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
85 | "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
86 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
87 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
88 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
89 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
90 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
91 | "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
92 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
93 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
94 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
95 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
96 | "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
97 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
98 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
99 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
100 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
101 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
102 |
103 | /* Completeness */
104 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
105 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
106 | },
107 | "include": [
108 | "src/llama"
109 | ]
110 | }
111 |
--------------------------------------------------------------------------------