diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml index 14c6e3c..adee70a 100644 --- a/.github/workflows/build-package.yml +++ b/.github/workflows/build-package.yml @@ -8,4 +8,4 @@ jobs: steps: - uses: nexys-system/gh-actions-spa-test@v1 - name: Build package - run: yarn buildpackage + run: yarn buildPackage diff --git a/package.json b/package.json index f0d4249..8ab37d1 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "scripts": { "start": "vite", "build": "tsc && vite build", - "buildpackage": "rm -rf ./dist;tsc -p tsconfig.build.json", + "buildPackage": "rm -rf ./dist;tsc -p tsconfig.build.json", "preview": "vite preview", "test": "vitest", "coverage": "vitest run --coverage" diff --git a/src/app.tsx b/src/app.tsx index f1b44ad..12517ee 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -23,7 +23,7 @@ import Detail from "./examples/detail"; import Badge from "./examples/badge"; import Auth from "./examples/auth"; import DateRange from "./examples/date-range"; -import Superadmin from "./examples/superadmin"; + import CrudBrowser from "./examples/crud-browser"; import FormBuilder from "./builder/form"; import TableBuilder from "./builder/table"; @@ -50,24 +50,26 @@ export default () => ( } /> } /> } /> - } /> + } /> } /> } /> } /> } /> } /> } /> - } /> + } /> } /> } /> } /> } /> } /> + {/* + } /> } /> - } /> - } /> - } /> + //} /> + } /> } />*/} + } /> } /> } /> diff --git a/src/components/buttons/with-action.tsx b/src/components/buttons/with-action.tsx index f6b6904..bfd4a04 100644 --- a/src/components/buttons/with-action.tsx +++ b/src/components/buttons/with-action.tsx @@ -1,6 +1,7 @@ import React from "react"; - +import Spinner from "../spinner"; import BtnWithActionGeneric, { BtnProps } from "../../lib/buttons/with-action"; +import { SubmitButtonProps } from "../../lib/form/type"; const Btn = ({ children, disabled, onClick }: BtnProps) => { return ( @@ -19,3 +20,58 @@ const Btn = ({ children, disabled, onClick }: BtnProps) => { const BtnWithAction = BtnWithActionGeneric(Btn); export default BtnWithAction; + +const getButtonClassName = (context?: "primary") => { + const classBase = "m-2 px-4 py-2 border rounded-md text-sm font-medium "; + + if (context === "primary") { + return ( + classBase + + "border-transparent rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:bg-blue-300" + ); + } + return classBase + "hover:bg-gray-100 px-4 py-2"; +}; + +export const Button = ({ + onClick, + context, + disabled, + type, + children, +}: { + type?: "submit"; + disabled?: boolean; + context?: "primary"; + onClick?: () => void; + children: string | JSX.Element; +}) => { + const className = getButtonClassName(context); + + return ( + + ); +}; + +export const SubmitButton = ({ disabled, loading }: SubmitButtonProps) => { + if (loading) { + return ; + } + + return ( + + ); +}; + +export const BackButton = ({ onClick }: { onClick: () => void }) => ( + +); diff --git a/src/components/form/generator.tsx b/src/components/form/generator.tsx index a646805..16d0e30 100644 --- a/src/components/form/generator.tsx +++ b/src/components/form/generator.tsx @@ -1,9 +1,9 @@ import React from "react"; import FormGenerated from "../../lib/form/generator/form"; -import { FormUIGeneratorProps } from "../../lib/form/generator/ui"; import * as UI from "./inputs"; +import { FormUIGeneratorProps } from "../../lib/form/type"; const p: FormUIGeneratorProps = { InputWrapper: UI.InputWrapper, diff --git a/src/components/form/inputs.tsx b/src/components/form/inputs.tsx index ae2b636..e11502a 100644 --- a/src/components/form/inputs.tsx +++ b/src/components/form/inputs.tsx @@ -3,7 +3,7 @@ import React from "react"; import * as T from "../../lib/form/type"; export const getClassName = ( - errors?: string[], + errors?: string, mainClass = "border p-2 rounded" ): string => { const isInvalid: boolean = !!errors; @@ -22,17 +22,17 @@ export const getClassName = ( export const InputWrapper = ({ label, children, - errors, + error, }: T.InputWrapperProps) => (
{children} - {errors && ( + {error && (
- {errors[0]} + {error}
)}
@@ -77,13 +77,13 @@ export const Textarea = ({ /> ); -export const Select = ({ +export const Select = ({ onChange, options, value, errors, disabled, -}: T.InputProps) => ( +}: T.InputProps) => ( setForm({ ...form, name })} + /> + + + setForm({ ...form, isUuid })} + /> + + + + options={countries} + value={form["country"]} + onChange={(country) => setForm({ ...form, country })} + /> + + + + options={langs} + value={form["lang"]} + onChange={(lang) => setForm({ ...form, lang })} + /> + + + + + options={uiStyles} + value={form["uiStyle"]} + onChange={(uiStyle) => setForm({ ...form, uiStyle })} + /> + + + + + ); +}; + +const clientValidationFunction = ( + v: Partial +): T.FormErrors => { + console.log(v); + + const e: T.FormErrors = {}; + + if (!("name" in v) || v["name"] === "") { + e.name = "wth dude"; + } + + if (!("isUuid" in v) || v.isUuid === false) { + e.isUuid = "wth dude"; + } + + return e; +}; + +const asyncCall = () => + new Promise((r, f) => { + setTimeout(() => { + console.log("sdf"); + f({ country: "sdf" }); + }, 2000); + }); + +const FreeUI = () => { + return ( + + ); +}; +export default FreeUI; diff --git a/src/examples/form/generated-form.tsx b/src/examples/form/generated-form.tsx new file mode 100644 index 0000000..6e7aec4 --- /dev/null +++ b/src/examples/form/generated-form.tsx @@ -0,0 +1,83 @@ +import React from "react"; + +import * as T from "../../lib/form/type"; + +import { FormWrapper } from "../../lib/form/form-wrapper"; +import { InputGeneric, InputWrapper } from "../../components/form/inputs"; +import { SubmitButton } from "../../components/buttons/with-action"; +import FormUIGenerator from "../../lib/form/generator/ui"; + +enum UIStyle { + card = 1, + list = 2, +} + +interface FormData { + name: string; + isUuid: boolean; + country: { id: number; name: string }; + lang: { id: string; name: string }; + uiStyle: UIStyle; +} + +const clientValidationFunction = ( + v: Partial +): T.FormErrors => { + console.log(v); + + const e: T.FormErrors = {}; + + if (!("name" in v) || v["name"] === "") { + e.name = "wth dude"; + } + + if (!("isUuid" in v) || v.isUuid === false) { + e.isUuid = "wth dude"; + } + + return e; +}; + +const asyncCall = () => + new Promise((r, f) => { + setTimeout(() => { + console.log("sdf"); + f({ country: "sdf" }); + }, 2000); + }); + +const formDef: T.FormDef[] = [ + { + name: "name", + label: "Name", + uiType: T.FormUIType.Text, + placeholder: "add something here", + }, + { + name: "isUuid", + label: "Is Uuid", + uiType: T.FormUIType.Switch, + placeholder: "add something here", + }, + { + name: "country", + label: "Country", + uiType: T.FormUIType.SelectObject, + placeholder: "lal", + }, +]; + +const GeneratedForm = () => ( + +); + +export default GeneratedForm; diff --git a/src/examples/form/generated.tsx b/src/examples/form/generated.tsx index 0c2a5de..0ca10ca 100644 --- a/src/examples/form/generated.tsx +++ b/src/examples/form/generated.tsx @@ -5,6 +5,30 @@ import PreForm from "../../components/form/generator"; import { apiCall } from "./utils"; +// options +export const continents = [ + { id: 1, name: "Asia" }, + { id: 2, name: "Africa" }, + { id: 3, name: "Europe" }, + { id: 4, name: "North America" }, + { id: 5, name: "South America" }, + { id: 6, name: "Australia/Oceania" }, + { id: 7, name: "Antarctica" }, +]; + +const ages = [ + { id: 1, name: "<20" }, + { id: 2, name: "20-40" }, + { id: 3, name: "40-60" }, + { id: 4, name: "60+" }, +]; + +const methods = [ + { id: "GET", name: "GET" }, + { id: "POST", name: "POST" }, +]; +/// + const def: FormDef[] = [ { name: "firstName", @@ -25,18 +49,21 @@ const def: FormDef[] = [ name: "continent", uiType: FormUIType.SelectObjectNumber, optional: false, + options: continents, }, { label: "Age", name: "age", uiType: FormUIType.SelectNumber, optional: false, + options: ages, }, { label: "Method", name: "method", uiType: FormUIType.Select, optional: false, + options: methods, }, { label: "Birthdate", diff --git a/src/examples/form/index.tsx b/src/examples/form/index.tsx index 6949e7f..9b6655b 100644 --- a/src/examples/form/index.tsx +++ b/src/examples/form/index.tsx @@ -1,40 +1,22 @@ import React from "react"; -import FormWrapper from "../../lib/form/form-wrapper"; +import { FormWrapperLegacy } from "../../lib/form/form-wrapper"; import { FormDataShape, Out } from "./type"; import FormUI from "./ui"; import FormGenerated from "./generated"; import { apiCall, onSuccess, cartoonCharacters, shape } from "./utils"; +import FreeUI from "./free-ui"; +import GeneratedForm from "./generated-form"; +import FormDefs from "./ui-generate"; -export const continents = [ - { id: 1, name: "Asia" }, - { id: 2, name: "Africa" }, - { id: 3, name: "Europe" }, - { id: 4, name: "North America" }, - { id: 5, name: "South America" }, - { id: 6, name: "Australia/Oceania" }, - { id: 7, name: "Antarctica" }, -]; - -const ages = [ - { id: 1, name: "<20" }, - { id: 2, name: "20-40" }, - { id: 3, name: "40-60" }, - { id: 4, name: "60+" }, -]; - -const methods = [ - { id: "GET", name: "GET" }, - { id: "POST", name: "POST" }, -]; - -const Form = FormWrapper(FormUI, shape, apiCall); +const Form = FormWrapperLegacy(FormUI, shape, apiCall); export default () => ( <> -

Form

+

Form

+

Form Simple

Form demo.  @@ -46,21 +28,27 @@ export default () => (

({ errors })} /> -
- -

Form Generated

- - ({ errors })} - /> +
+ +

Form Generated

+ + ({ errors })} /> +
+ +

Form Free UI

+ + + +
+ +

Generated Form

+ + + +
+ +

UI for Form Generation

+ + ); diff --git a/src/examples/form/ui-generate.tsx b/src/examples/form/ui-generate.tsx new file mode 100644 index 0000000..449b802 --- /dev/null +++ b/src/examples/form/ui-generate.tsx @@ -0,0 +1,164 @@ +import React from "react"; + +import * as T from "../../lib/form/type"; + +import { InputGeneric, InputWrapper } from "../../components/form/inputs"; + +import { SubmitButton, BackButton } from "../../components/buttons/with-action"; +import { FormWrapper } from "../../lib/form/form-wrapper"; +import FormGenerator from "../../lib/form/generator/form"; +import FormUIGenerator from "../../lib/form/generator/ui"; + +const formDef: T.FormDef[] = [ + { + name: "name", + label: "Name", + uiType: T.FormUIType.Text, + placeholder: "Add name", + info: "Name of the attribute", + }, + { + name: "label", + label: "Label", + uiType: T.FormUIType.Text, + placeholder: "Add label", + info: "Label for the attribute", + }, + { + name: "placeholder", + label: "Placeholder", + uiType: T.FormUIType.Text, + placeholder: "placeholder", + }, + { + name: "info", + label: "Info", + uiType: T.FormUIType.Text, + placeholder: "info", + }, + { + name: "uxType", + label: "UX Type", + uiType: T.FormUIType.Switch, + }, +]; + +const FormDefs = () => { + const [formDefs, setFormDefs] = React.useState< + T.FormDef<{ [k: string]: string }>[] + >([]); + const [addForm, setAddForm] = React.useState(false); + + const handleAdd = (a: T.FormDef<{ [k: string]: string }>) => { + setFormDefs([...formDefs, a]); + setAddForm(false); + }; + + const handleRemove = (idx: number) => { + const f = formDefs.filter((_x, i) => i !== idx); + setFormDefs(f); + }; + + return ( +
+
+ {addForm && ( + <> + >(formDef)} + onSuccess={handleAdd} + > + setAddForm(false)} /> + + + )} + + {!addForm && ( + <> + + + {formDefs.length === 0 && ( +

Click add to start buikding your form

+ )} + {formDefs.length > 0 && ( + <> + + + + + + + + + + + + {formDefs.map((formDef, idx) => ( + + + + + + + + ))} + +
NameLabelPlaceholderInfo
+ {formDef.name} + + {formDef.label} + + {formDef.placeholder} + + {formDef.info} + + +
+ + )} + + )} +
+
+

Generated Form

{" "} + {formDefs.length > 0 && ( + <> + +
+
{JSON.stringify(formDefs, null, 2)}
+ + + )} +
+
+ ); +}; + +export default FormDefs; diff --git a/src/examples/form/ui.tsx b/src/examples/form/ui.tsx index 8ec472a..60e66ad 100644 --- a/src/examples/form/ui.tsx +++ b/src/examples/form/ui.tsx @@ -1,39 +1,46 @@ -import React from 'react'; -import * as T from '../../lib/form/type'; -import * as Inputs from '../../components/form/inputs'; -import { FormDataShape } from './type'; +import React from "react"; +import * as T from "../../lib/form/type"; +import * as Inputs from "../../components/form/inputs"; +import { FormDataShape } from "./type"; const FormUI = ({ form, setForm, loading, - errors + errors, + onSubmit, }: T.FormUIProps) => { return ( - <> - + + setForm({ ...form, firstName })} + onChange={(firstName) => setForm({ ...form, firstName })} disabled={loading} - placeholder={'First Name'} - errors={errors['firstName']} + placeholder={"First Name"} + errors={errors["firstName"]} /> - + setForm({ ...form, lastName })} + onChange={(lastName) => setForm({ ...form, lastName })} disabled={loading} - placeholder={'Last Name'} - errors={errors['lastName']} + placeholder={"Last Name"} + errors={errors["lastName"]} /> - - + ); }; diff --git a/src/examples/simple-list/list-add/form.tsx b/src/examples/simple-list/list-add/form.tsx index d5252e9..4340673 100644 --- a/src/examples/simple-list/list-add/form.tsx +++ b/src/examples/simple-list/list-add/form.tsx @@ -1,9 +1,10 @@ -import React from 'react'; -import { FormUIProps } from '../../../lib/form/type'; +import React from "react"; +import { FormUIProps } from "../../../lib/form/type"; -import * as Inputs from '../../../components/form/inputs'; -import FormWrapper from '../../../lib/form/form-wrapper'; -import Icon from '../../../components/icon'; +import * as Inputs from "../../../components/form/inputs"; + +import Icon from "../../../components/icon"; +import { FormWrapperLegacy } from "../../../lib/form/form-wrapper"; export interface FormDataShape { name: string; @@ -13,20 +14,21 @@ const FormUI = ({ form, setForm, loading, - errors + onSubmit, + errors, }: FormUIProps) => { return ( - <> +
setForm({ ...form, name })} + onChange={(name) => setForm({ ...form, name })} disabled={loading} - placeholder={'Name'} - errors={errors['name']} + placeholder={"Name"} + errors={errors["name"]} />
- +
); }; @@ -43,8 +45,8 @@ const shape = { name: {} }; const apiCall = async (a: FormDataShape) => Promise.resolve(Math.random() * 100); -const Form = FormWrapper(FormUI, shape, apiCall, { - resetAfterSubmit: true +const Form = FormWrapperLegacy(FormUI, shape, apiCall, { + resetAfterSubmit: true, }); export default Form; diff --git a/src/examples/simple-list/list-form/form.tsx b/src/examples/simple-list/list-form/form.tsx index 4a97a38..e3b77e6 100644 --- a/src/examples/simple-list/list-form/form.tsx +++ b/src/examples/simple-list/list-form/form.tsx @@ -1,9 +1,10 @@ -import React from 'react'; -import { FormUIProps } from '../../../lib/form/type'; +import React from "react"; +import { FormUIProps } from "../../../lib/form/type"; -import * as Inputs from '../../../components/form/inputs'; -import FormWrapper from '../../../lib/form/form-wrapper'; -import Icon from '../../../components/icon'; +import * as Inputs from "../../../components/form/inputs"; + +import Icon from "../../../components/icon"; +import { FormWrapperLegacy } from "../../../lib/form/form-wrapper"; export interface FormDataShape { name: string; @@ -14,27 +15,28 @@ const FormUI = ({ form, setForm, loading, - errors + errors, + onSubmit, }: FormUIProps) => { return ( - <> - +
+ setForm({ ...form, name })} + onChange={(name) => setForm({ ...form, name })} disabled={loading} - placeholder={'Name'} - errors={errors['name']} + placeholder={"Name"} + errors={errors["name"]} /> - + setForm({ ...form, description })} + onChange={(description) => setForm({ ...form, description })} disabled={loading} - placeholder={'Description'} - errors={errors['description']} + placeholder={"Description"} + errors={errors["description"]} /> - + ); }; const shape = { name: {}, description: {} }; const apiCall = async (a: FormDataShape) => { - console.log('calling API'); + console.log("calling API"); return Promise.resolve(Math.random() * 100); }; -const Form = FormWrapper(FormUI, shape, apiCall); +const Form = FormWrapperLegacy(FormUI, shape, apiCall); export default Form; diff --git a/src/examples/simple-list/list-form/index.tsx b/src/examples/simple-list/list-form/index.tsx index ea96029..1633bf4 100644 --- a/src/examples/simple-list/list-form/index.tsx +++ b/src/examples/simple-list/list-form/index.tsx @@ -1,15 +1,15 @@ -import React from 'react'; -import Icon from '../../../components/icon'; +import React from "react"; +import Icon from "../../../components/icon"; -import Form, { FormDataShape } from './form'; -import List from './list'; +import Form, { FormDataShape } from "./form"; +import List from "./list"; export default () => { const [data, setData] = React.useState< { id: number; title: string; subtitle: string }[] >([ - { id: 1, title: 'item #1', subtitle: 'sub#1' }, - { id: 2, title: 'item #2', subtitle: 'sub#2' } + { id: 1, title: "item #1", subtitle: "sub#1" }, + { id: 2, title: "item #2", subtitle: "sub#2" }, ]); const [isInsert, setIsInsert] = React.useState(false); @@ -18,8 +18,8 @@ export default () => { >(undefined); const handleRemove = (id: number) => { - if (confirm('Are you sure you would like to delete that entry?')) { - setData(data.filter(x => x.id !== id)); + if (confirm("Are you sure you would like to delete that entry?")) { + setData(data.filter((x) => x.id !== id)); } }; @@ -46,7 +46,7 @@ export default () => { throw Error("can't be edited"); } - const newData = data.map(x => { + const newData = data.map((x) => { if (x.id === id) { return { title: d.name, subtitle: d.description, id }; } @@ -59,7 +59,7 @@ export default () => { if (edit) { // note: when connecting to an api the update call should be different to the insert call, maybe needs two forms - return
; + return ; } if (isInsert) { @@ -73,7 +73,7 @@ export default () => {
- - ); -}; - -export default Body; diff --git a/src/examples/superadmin/user/authentication/form.tsx b/src/examples/superadmin/user/authentication/form.tsx deleted file mode 100644 index c118037..0000000 --- a/src/examples/superadmin/user/authentication/form.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { FormUIProps } from "../../../../lib/form/type"; - -import * as Inputs from "../../../../components/form/inputs"; -import FormWrapper from "../../../../lib/form/form-wrapper"; -import Icon from "../../../../components/icon"; -import { FormDataShape, shape, UserAuthenticationType } from "./type"; - -const userAuthentiactionOptions: { - id: UserAuthenticationType; - name: string; -}[] = [{ id: UserAuthenticationType.password, name: "password" }]; - -const FormUI = ({ - form, - setForm, - loading, - errors, -}: FormUIProps) => { - return ( - <> - - setForm({ ...form, value })} - disabled={loading} - placeholder={"Value"} - errors={errors["value"]} - /> - - - - setForm({ ...form, type })} - disabled={loading} - placeholder={"Type"} - errors={errors["type"]} - /> - - - - setForm({ ...form, isEnabled })} - disabled={loading} - placeholder={"Is Enabled"} - errors={errors["isEnabled"]} - /> - - - - ); -}; - -const addUserAuthentication = async (a: FormDataShape): Promise => { - console.log("calling API"); - return Promise.resolve(String(Math.random() * 100)); -}; - -const Form = FormWrapper( - FormUI, - shape, - addUserAuthentication -); - -export default Form; diff --git a/src/examples/superadmin/user/authentication/index.tsx b/src/examples/superadmin/user/authentication/index.tsx deleted file mode 100644 index 6c34782..0000000 --- a/src/examples/superadmin/user/authentication/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { UserAuthentication, UserAuthenticationType } from "./type"; -import LoadDataAsync from "../../../../components/load-data-async"; - -import Body from "./body"; - -const getUserAuthentications = (uuid: string) => { - const data: UserAuthentication[] = [ - { - uuid: "u1", - value: "myemail@gmail.com", - type: UserAuthenticationType.password, - isEnabled: true, - }, - { - uuid: "u2", - value: "myghusername", - type: UserAuthenticationType.password, - isEnabled: false, - }, - ]; - - return Promise.resolve(data); -}; - -export default ({ uuid }: { uuid: string }) => ( - getUserAuthentications(uuid)} - Component={Body} - /> -); diff --git a/src/examples/superadmin/user/authentication/list-item.tsx b/src/examples/superadmin/user/authentication/list-item.tsx deleted file mode 100644 index 410555d..0000000 --- a/src/examples/superadmin/user/authentication/list-item.tsx +++ /dev/null @@ -1,21 +0,0 @@ -const ListItem = ({ - title, - children, - right, -}: { - title: string; - children?: JSX.Element; - right?: JSX.Element; -}) => { - return ( -
  • -
    -
    {title}
    - {children} -
    - {right} -
  • - ); -}; - -export default ListItem; diff --git a/src/examples/superadmin/user/authentication/list.tsx b/src/examples/superadmin/user/authentication/list.tsx deleted file mode 100644 index edd5304..0000000 --- a/src/examples/superadmin/user/authentication/list.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import Icon from "../../../../components/icon"; -import { yesOrNo } from "../../../../lib/utils"; -import ListItem from "./list-item"; -import { UserAuthentication, UserAuthenticationType } from "./type"; - -const List = ({ - data, - onRemove, - onEdit, -}: { - data: UserAuthentication[]; - onRemove: (uuid: string) => void; - onEdit: (d: UserAuthentication) => void; -}) => { - if (data.length === 0) { - return ( -

    - Nothing was found -

    - ); - } - - return ( -
      - {data.map((d, i) => ( - - onEdit(d)} - style={{ cursor: "pointer" }} - className="badge bg-warning rounded-pill" - > - - -   - onRemove(d.uuid)} - style={{ cursor: "pointer" }} - className="badge bg-danger rounded-pill" - > - - - - } - > - <> - {UserAuthenticationType[d.type]} {yesOrNo(d.isEnabled)} - - - ))} -
    - ); -}; - -export default List; diff --git a/src/examples/superadmin/user/authentication/type.ts b/src/examples/superadmin/user/authentication/type.ts deleted file mode 100644 index b10a25a..0000000 --- a/src/examples/superadmin/user/authentication/type.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Shape } from "@nexys/validation/dist/type"; - -export enum UserAuthenticationType { - password = 1, -} - -export interface UserAuthentication { - uuid: string; - value: string; - type: UserAuthenticationType; - isEnabled: boolean; -} - -export const shape: Shape = { - value: {}, - type: { type: "number" }, - isEnabled: { type: "boolean" }, -}; - -export type FormDataShape = Omit; diff --git a/src/examples/superadmin/user/detail/access-token.tsx b/src/examples/superadmin/user/detail/access-token.tsx deleted file mode 100644 index f3fbcd8..0000000 --- a/src/examples/superadmin/user/detail/access-token.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import LoadDataAsync from "../../../../components/load-data-async"; -import List from "../../../../components/table"; -import { Definition } from "../../../../lib/types"; -import { AccessToken } from "../type"; - -const getAccessTokens = (user: { uuid: string }) => { - const d: AccessToken[] = [ - { uuid: "u1", token: "t1", date: "2021-23-23" }, - { uuid: "u2", token: "t2", date: "2021-23-23" }, - ]; - return Promise.resolve(d); -}; - -const def: Definition = [ - { name: "token", label: "Token" }, - { name: "date", label: "Date" }, - { - name: "uuid", - label: "", - render: (x) => , - }, -]; - -const TokenList = ({ data }: { data: AccessToken[] }): JSX.Element => ( - -); - -export default ({ user }: { user: { uuid: string } }) => ( - } - getData={() => getAccessTokens(user)} - /> -); diff --git a/src/examples/superadmin/user/detail/detail.tsx b/src/examples/superadmin/user/detail/detail.tsx deleted file mode 100644 index 928cd93..0000000 --- a/src/examples/superadmin/user/detail/detail.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ToggleFromDef } from "../../../../components/toggle"; -import { FormUIType, FormViewDef } from "../../../../lib/form/type"; -import { UserData } from "../../data"; -import { User } from "../type"; - -const def: FormViewDef[] = [ - { - label: "First Name", - name: "firstName", - uiType: FormUIType.Text, - optional: false, - }, - { - label: "Last Name", - name: "lastName", - uiType: FormUIType.Text, - optional: false, - }, -]; - -export default ToggleFromDef(def, UserData.update); diff --git a/src/examples/superadmin/user/detail/index.tsx b/src/examples/superadmin/user/detail/index.tsx deleted file mode 100644 index a85004c..0000000 --- a/src/examples/superadmin/user/detail/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useParams } from "react-router-dom"; - -import Layout from "../../../../components/layout"; -import LoadDataAsync from "../../../../components/load-data-async"; -import links from "../../../../links"; -import { User } from "../type"; - -import Detail from "./detail"; -import StatusChange from "./status"; -import Permissions from "./permissions"; -import AccessToken from "./access-token"; -import Authentication from "../authentication"; -import { UserData } from "../../data"; - -export default ({ instance }: { instance: { uuid: string } }) => { - const { uuid } = useParams<{ uuid?: string }>(); - - if (typeof uuid !== "string") { - throw Error("expected a uuid in the url"); - } - - const baseUrl = links.superadmin.link + `/${instance.uuid}/detail/user`; - - const L = Layout({ - cards: { - User: [ - { Component: ({ data }) => }, - { - Component: ({ data }) => ( - - ), - }, - { Component: ({ data }) => }, - ], - Permissions: [ - { Component: ({ data }) => }, - { Component: ({ data }) => }, - ], - }, - title: "User" + uuid, - backRedirect: baseUrl, - pathPrefix: baseUrl + `/${uuid}/detail`, - }); - - return ( - UserData.getDetail(uuid)} /> - ); -}; diff --git a/src/examples/superadmin/user/detail/permissions.tsx b/src/examples/superadmin/user/detail/permissions.tsx deleted file mode 100644 index 28e9477..0000000 --- a/src/examples/superadmin/user/detail/permissions.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; - -import ListAssign from "../../../../components/list-assign"; -import LoadDataAsync from "../../../../components/load-data-async"; -import { Item } from "../../../../lib/list-assign"; -import { getPermissions, updatePermission } from "../../data"; - -const ListAssign2 = ({ data: dataIn }: { data: Item[] }) => { - const [data, setData] = React.useState(dataIn); - - const onUpdate = async (id: number) => { - await updatePermission(id); - const d = data.map((d) => { - if (d.id === id) { - return { ...d, assigned: !d.assigned }; - } - - return d; - }); - - setData([...d]); - }; - - return data={data} updatePromise={onUpdate} />; -}; - -export default ({ uuid }: { uuid: string }) => ( - getPermissions(uuid)} /> -); diff --git a/src/examples/superadmin/user/detail/status.tsx b/src/examples/superadmin/user/detail/status.tsx deleted file mode 100644 index 2c47b62..0000000 --- a/src/examples/superadmin/user/detail/status.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import StatusChange from "../../../../components/status-change"; - -const status = [ - { id: 1, label: "active" }, - { id: 2, label: "pending" }, - { id: 3, label: "inactive" }, -]; - -const statusChange = (uuid: string, statusId: number) => { - return Promise.resolve(); -}; - -export default ({ uuid, statusId }: { uuid: string; statusId: number }) => { - return ( - <> - statusChange(uuid, s)} - /> - - ); -}; diff --git a/src/examples/superadmin/user/index.tsx b/src/examples/superadmin/user/index.tsx deleted file mode 100644 index 85e6cb8..0000000 --- a/src/examples/superadmin/user/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Route, Routes } from "react-router-dom"; -import List from "./list"; -import Detail from "./detail"; - -export default ({ instance }: { instance: { uuid: string } }) => { - return ( - - } /> - } - /> - - ); -}; diff --git a/src/examples/superadmin/user/list.tsx b/src/examples/superadmin/user/list.tsx deleted file mode 100644 index 4d0a287..0000000 --- a/src/examples/superadmin/user/list.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Link } from "react-router-dom"; -import LoadDataAsync from "../../../components/load-data-async"; -import List from "../../../components/table"; -import { Definition } from "../../../lib/types"; -import links from "../../../links"; -import { UserData } from "../data"; -import { User } from "./type"; - -const def = (instance: { uuid: string }): Definition => [ - { name: "firstName", label: "First Name" }, - { name: "lastName", label: "last Name" }, - { - name: "uuid", - label: "", - render: (x) => ( - - Edit - - ), - }, -]; - -const UserList = ({ - data, - instance, -}: { - data: User[]; - instance: { uuid: string }; -}): JSX.Element => ( - -); - -const Layout = ({ children }: { children: JSX.Element }) => ( - <> -

    Users

    - - {children} - -); - -export default ({ instance }: { instance: { uuid: string } }) => ( - - } - getData={UserData.getList} - /> - -); diff --git a/src/examples/superadmin/user/type.ts b/src/examples/superadmin/user/type.ts deleted file mode 100644 index f475c0f..0000000 --- a/src/examples/superadmin/user/type.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface User { - uuid: string; - firstName: string; - lastName: string; -} - -export interface AccessToken { - uuid: string; - token: string; - date: string; -} diff --git a/src/examples/toggle/index.tsx b/src/examples/toggle/index.tsx index 78f6d5d..224e4e4 100644 --- a/src/examples/toggle/index.tsx +++ b/src/examples/toggle/index.tsx @@ -4,7 +4,7 @@ import { delay } from "../../lib/utils"; import { ToggleFromDef } from "../../components/toggle"; import { FormUIType, FormViewDef } from "../../lib/form/type"; -import { continents } from "../form"; +import { continents } from "../form/generated"; const continent = continents[0]; diff --git a/src/lib/form/form-wrapper.tsx b/src/lib/form/form-wrapper.tsx index 7b55806..0dfc674 100644 --- a/src/lib/form/form-wrapper.tsx +++ b/src/lib/form/form-wrapper.tsx @@ -1,20 +1,69 @@ // generic wrapper for forms, see args below for details -import React from 'react'; +import React from "react"; -import * as T from './type'; -import * as Validation from '@nexys/validation'; -import { isA } from './utils'; +import * as T from "./type"; +import * as Validation from "@nexys/validation"; +import { isNotPartial } from "./utils"; -export interface FormWrapperProps { - data?: { - dataIn: Partial
    ; - options?: { - [k in keyof A]?: { id: number | string; name: string }[]; - }; +export const FormWrapper = ({ + onSuccess, + clientValidationFunction, + FormUI, + errors: externalErrors, + asyncCall, + children, +}: T.FormWrapperProps) => { + const [formData, setFormData] = React.useState>({}); + const [errors, setErrors] = React.useState>( + externalErrors || {} + ); + const [loading, setLoading] = React.useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // client validation + const validation = clientValidationFunction + ? clientValidationFunction(formData) + : {}; + + if (!isNotPartial(formData, validation)) { + // errors found + setErrors(validation); + return; + } + + // reset errors + setErrors({}); + // and pass value up + + if (asyncCall) { + setLoading(true); + asyncCall(formData) + .then((response) => { + onSuccess && onSuccess(formData, response); + }) + .catch((x) => setErrors(x)) + .finally(() => setLoading(false)); + + return; + } + + onSuccess && onSuccess(formData); }; - onSuccess?: (data: A, out?: Out) => void; - onErrors?: (err: any, data: A) => { errors?: T.FormErrorsGeneric }; -} + + return ( + + {children} + + ); +}; /** * @type FormShape: shape of the form @@ -22,63 +71,27 @@ export interface FormWrapperProps { * @param shape : validation shape * @param asyncCall [optional]: async call, typically backend * @param onSuccess [optional]: after call to the backend, action + * note this is the old version of the formwrapper that maps to the new one */ -const FormWrapper = +export const FormWrapperLegacy = ( - FormUI: (props: T.FormUIProps) => JSX.Element, + FormUIInput: (props: T.FormUIProps) => JSX.Element, shape: Validation.Type.Shape, asyncCall?: (data: FormShape) => Promise, - { resetAfterSubmit = true }: { resetAfterSubmit?: boolean } = {} + { resetAfterSubmit = true }: Partial = {} ) => ({ - data = { options: {}, dataIn: {} }, onSuccess, - onErrors - }: FormWrapperProps): JSX.Element => { - type FormErrors = T.FormErrorsGeneric; - const [form, setForm] = React.useState>(data.dataIn); - const [errors, setErrors] = React.useState({}); - const [loading, setLoading] = React.useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - const validation = Validation.Main.checkObject(form, shape) as FormErrors; - - setErrors(validation); - - if (isA(form, validation)) { - // here call the backend - setLoading(true); - try { - const out = asyncCall && (await asyncCall(form)); - setLoading(false); - resetAfterSubmit && setForm({}); // this lines comes before onSuccess, else it creates an error/warning - onSuccess && onSuccess(form, out); - } catch (err) { - if (onErrors) { - const { errors } = onErrors(err, form); + onErrors, + }: T.FormWrapperOnActionProps): JSX.Element => { + const clientValidationFunction = (form: any) => + Validation.Main.checkObject(form, shape) as any; - if (errors) { - setErrors(errors); - setLoading(false); - } - } - } - } - }; - - return ( - - - - ); + return FormWrapper({ + onSuccess, + onErrors, + clientValidationFunction, + FormUI: FormUIInput, + asyncCall, + }); }; - -export default FormWrapper; diff --git a/src/lib/form/generator/form.tsx b/src/lib/form/generator/form.tsx index 97227e5..922bf68 100644 --- a/src/lib/form/generator/form.tsx +++ b/src/lib/form/generator/form.tsx @@ -1,20 +1,20 @@ import * as T from "../type"; import { defToShape } from "../utils"; -import FormUIGenerator, { FormUIGeneratorProps } from "./ui"; -import FormWrapper from "../form-wrapper"; +import { FormWrapperLegacy } from "../form-wrapper"; +import FormUIGenerator from "./ui"; -const FormGenerator = - (p: FormUIGeneratorProps) => +const FormGeneratorLegacy = + (p: T.FormUIGeneratorProps) => ( def: T.FormDef[], asyncCall?: (data: FormShape) => Promise, - options: { resetAfterSubmit?: boolean } = {} + options: Partial = {} ) => { const shape = defToShape(def); const ui = FormUIGenerator(p)(def); - return FormWrapper(ui, shape, asyncCall, options); + return FormWrapperLegacy(ui, shape, asyncCall, options); }; -export default FormGenerator; +export default FormGeneratorLegacy; diff --git a/src/lib/form/generator/ui.tsx b/src/lib/form/generator/ui.tsx index bd4f3be..f7ae7e0 100644 --- a/src/lib/form/generator/ui.tsx +++ b/src/lib/form/generator/ui.tsx @@ -1,45 +1,46 @@ import React from "react"; import * as T from "../type"; -export interface FormUIGeneratorProps { - InputWrapper: (p: T.InputWrapperProps) => JSX.Element; - InputGeneric: (uiType: T.FormUIType) => (p: T.InputProps) => JSX.Element; // here cast to any to avoid types issue, if coded properly this should not cause any issues - Button: () => JSX.Element; -} - const FormUIGenerator = - ({ InputGeneric, InputWrapper, Button }: FormUIGeneratorProps) => + ({ InputGeneric, InputWrapper, Button }: T.FormUIGeneratorProps) => (def: T.FormDef
    []) => ({ form, setForm, loading, errors, - options, + onSubmit, + children, }: T.FormUIProps): JSX.Element => { return ( - <> +
    {def.map((item, i) => { const Input = InputGeneric(item.uiType); const value = form[item.name]; return ( - + setForm({ ...form, [item.name]: val })} disabled={loading} placeholder={item.placeholder} errors={errors[item.name]} - options={options[item.name]} + options={item.options && item.options} /> ); })} - - ); -}; - -export const SubmitButton = ({ disabled, loading }: SubmitButtonProps) => { - if (loading) { - return ; - } - - return ( - - ); -}; - -export const BackButton = ({ onClick }: { onClick: () => void }) => ( - -); diff --git a/src/lib/form/generator2/components/index.tsx b/src/lib/form/generator2/components/index.tsx deleted file mode 100644 index 74529f2..0000000 --- a/src/lib/form/generator2/components/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React from "react"; - -import * as T from "./type"; -import { isNotPartial } from "./utils"; - -interface FormWrapperProps { - onSuccess: (formData: A, asyncCallResponse?: B) => void; - clientValidationFunction?:
    (_a: Partial) => T.FormErrors; - asyncCall?: (formData: A) => Promise; - FormUI: (props: T.FormUIProps) => JSX.Element; - errors?: T.FormErrors; - children?: JSX.Element; -} - -export const FormWrapper = ({ - onSuccess, - clientValidationFunction, - FormUI, - errors: externalErrors, - asyncCall, - children, -}: FormWrapperProps) => { - const [formData, setFormData] = React.useState>({}); - const [errors, setErrors] = React.useState>( - externalErrors || {} - ); - const [loading, setLoading] = React.useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - // client validation - const validation = clientValidationFunction - ? clientValidationFunction(formData) - : {}; - - if (!isNotPartial(formData, validation)) { - // errors found - setErrors(validation); - return; - } - - // reset errors - setErrors({}); - // and pass value up - - if (asyncCall) { - setLoading(true); - asyncCall(formData) - .then((response) => { - onSuccess(formData, response); - }) - .catch((x) => setErrors(x)) - .finally(() => setLoading(false)); - - return; - } - - onSuccess(formData); - }; - - return ( - - {children} - - ); -}; - -// - -export const generateFormUI = - ( - InputGeneric: (uxType: T.UXType) => (a: T.InputProps) => JSX.Element, - Wrapper: (a: T.WrapperProps) => JSX.Element, - SubmitButton: (a: T.SubmitButtonProps) => JSX.Element - ) => - (formDef: T.FormDefUnit[]) => - (props: T.FormUIProps): JSX.Element => - ( -
    - {formDef.map(({ name, uxType, label, placeholder, info }, key) => { - const Input = InputGeneric(uxType); - - return ( - - - props.setFormData({ ...props.formData, [name]: value }) - } - /> - - ); - })} - - - {props.children} - - ); diff --git a/src/lib/form/generator2/components/inputs.tsx b/src/lib/form/generator2/components/inputs.tsx deleted file mode 100644 index 6b98bf2..0000000 --- a/src/lib/form/generator2/components/inputs.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { - InputOptionProps, - InputProps, - OptionUnit, - UXType, - WrapperProps, -} from "../type"; - -export const Wrapper = ({ label, info, error, children }: WrapperProps) => ( -
    -

    {label}

    - {info} - {children} - {error} -
    -); - -export const Input = ({ - value, - onChange, - disabled, - placeholder, -}: InputProps) => ( - onChange(e.target.value)} - className="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 disabled:bg-gray-100 disabled:cursor-not-allowed" - /> -); - -export const InputNumber = ({ - value, - onChange, - disabled, -}: InputProps) => ( - { - const v = e.target.value; - - const n = Number(v); - - if (isNaN(n)) { - return; - } - - onChange(n); - }} - /> -); - -export const InputCheckbox = ({ - value, - onChange, - disabled, -}: InputProps) => ( - onChange(e.target.checked)} - /> -); - -export const InputOptions = ({ - value, - onChange, - options, -}: InputOptionProps, Id>) => { - const handleChange = (e: React.ChangeEvent) => { - const v = e.target.value; - - const optionSelected = options.find( - ({ id }) => (id as unknown as number).toString() === v - ); - if (optionSelected) { - onChange(optionSelected); - } - - console.log(optionSelected); - }; - - return ( - - ); -}; - -export const InputOptionsScalar = ({ - value, - onChange, - options, -}: InputOptionProps) => { - const f = options.find((x) => x.id === value); - return ( - onChange(v.id)} - /> - ); -}; - -export const InputGeneric = ( - uxType: UXType -): ((props: InputProps) => JSX.Element) => { - switch (uxType) { - case UXType.checkbox: - return InputCheckbox; - case UXType.number: - return InputNumber; - default: - return Input; - } -}; diff --git a/src/lib/form/generator2/components/spinner.tsx b/src/lib/form/generator2/components/spinner.tsx deleted file mode 100644 index dbcb99e..0000000 --- a/src/lib/form/generator2/components/spinner.tsx +++ /dev/null @@ -1,13 +0,0 @@ -// src/components/Spinner.tsx - -import React from "react"; - -const Spinner: React.FC = () => { - return ( -
    -
    -
    - ); -}; - -export default Spinner; diff --git a/src/lib/form/generator2/components/type.ts b/src/lib/form/generator2/components/type.ts deleted file mode 100644 index a5962fe..0000000 --- a/src/lib/form/generator2/components/type.ts +++ /dev/null @@ -1,59 +0,0 @@ -export type FormErrors
    = { - [k in keyof A]?: string; -}; - -export interface FormUIProps { - loading: boolean; - onSubmit: (e: React.FormEvent) => void; - errors: FormErrors; - formData: Partial; - setFormData: (x: Partial) => void; - children?: JSX.Element; -} - -export interface WrapperProps { - label?: string; - info?: string; - error?: string; - children: JSX.Element; -} - -export interface OptionUnit { - id: Id; - name: string; -} - -export interface InputProps { - onChange: (s: T) => void; - value?: T; - placeholder?: string; - disabled?: boolean; -} - -export interface InputOptionProps extends InputProps { - options: OptionUnit[]; -} - -export interface SubmitButtonProps { - disabled?: boolean; - loading?: boolean; -} - -export interface BackButtonProps { - onClick: () => void; -} - -export enum UXType { - text = 1, - number = 2, - checkbox = 3, -} - -export interface FormDefUnit { - uxType: UXType; - label: string; - description?: string; - name: keyof A; - placeholder?: string; - info?: string; -} diff --git a/src/lib/form/generator2/components/utils.ts b/src/lib/form/generator2/components/utils.ts deleted file mode 100644 index e3521ef..0000000 --- a/src/lib/form/generator2/components/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FormErrors } from "./type"; - -export const isNotPartial = ( - formData: Partial, - formErrors: FormErrors -): formData is A => Object.keys(formErrors).length === 0; diff --git a/src/lib/form/generator2/index.tsx b/src/lib/form/generator2/index.tsx deleted file mode 100644 index 74529f2..0000000 --- a/src/lib/form/generator2/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React from "react"; - -import * as T from "./type"; -import { isNotPartial } from "./utils"; - -interface FormWrapperProps { - onSuccess: (formData: A, asyncCallResponse?: B) => void; - clientValidationFunction?: (_a: Partial) => T.FormErrors; - asyncCall?: (formData: A) => Promise; - FormUI: (props: T.FormUIProps) => JSX.Element; - errors?: T.FormErrors; - children?: JSX.Element; -} - -export const FormWrapper = ({ - onSuccess, - clientValidationFunction, - FormUI, - errors: externalErrors, - asyncCall, - children, -}: FormWrapperProps) => { - const [formData, setFormData] = React.useState>({}); - const [errors, setErrors] = React.useState>( - externalErrors || {} - ); - const [loading, setLoading] = React.useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - // client validation - const validation = clientValidationFunction - ? clientValidationFunction(formData) - : {}; - - if (!isNotPartial(formData, validation)) { - // errors found - setErrors(validation); - return; - } - - // reset errors - setErrors({}); - // and pass value up - - if (asyncCall) { - setLoading(true); - asyncCall(formData) - .then((response) => { - onSuccess(formData, response); - }) - .catch((x) => setErrors(x)) - .finally(() => setLoading(false)); - - return; - } - - onSuccess(formData); - }; - - return ( - - {children} - - ); -}; - -// - -export const generateFormUI = - ( - InputGeneric: (uxType: T.UXType) => (a: T.InputProps) => JSX.Element, - Wrapper: (a: T.WrapperProps) => JSX.Element, - SubmitButton: (a: T.SubmitButtonProps) => JSX.Element - ) => - (formDef: T.FormDefUnit[]) => - (props: T.FormUIProps): JSX.Element => - ( -
    - {formDef.map(({ name, uxType, label, placeholder, info }, key) => { - const Input = InputGeneric(uxType); - - return ( - - - props.setFormData({ ...props.formData, [name]: value }) - } - /> - - ); - })} - - - {props.children} - - ); diff --git a/src/lib/form/generator2/type.ts b/src/lib/form/generator2/type.ts deleted file mode 100644 index a5962fe..0000000 --- a/src/lib/form/generator2/type.ts +++ /dev/null @@ -1,59 +0,0 @@ -export type FormErrors
    = { - [k in keyof A]?: string; -}; - -export interface FormUIProps { - loading: boolean; - onSubmit: (e: React.FormEvent) => void; - errors: FormErrors; - formData: Partial; - setFormData: (x: Partial) => void; - children?: JSX.Element; -} - -export interface WrapperProps { - label?: string; - info?: string; - error?: string; - children: JSX.Element; -} - -export interface OptionUnit { - id: Id; - name: string; -} - -export interface InputProps { - onChange: (s: T) => void; - value?: T; - placeholder?: string; - disabled?: boolean; -} - -export interface InputOptionProps extends InputProps { - options: OptionUnit[]; -} - -export interface SubmitButtonProps { - disabled?: boolean; - loading?: boolean; -} - -export interface BackButtonProps { - onClick: () => void; -} - -export enum UXType { - text = 1, - number = 2, - checkbox = 3, -} - -export interface FormDefUnit { - uxType: UXType; - label: string; - description?: string; - name: keyof A; - placeholder?: string; - info?: string; -} diff --git a/src/lib/form/generator2/utils.ts b/src/lib/form/generator2/utils.ts deleted file mode 100644 index e3521ef..0000000 --- a/src/lib/form/generator2/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FormErrors } from "./type"; - -export const isNotPartial = ( - formData: Partial, - formErrors: FormErrors -): formData is A => Object.keys(formErrors).length === 0; diff --git a/src/lib/form/type.ts b/src/lib/form/type.ts index 2bb5a90..1a1ee46 100644 --- a/src/lib/form/type.ts +++ b/src/lib/form/type.ts @@ -1,30 +1,77 @@ import { Render } from "../view"; +export interface FormWrapperOnActionProps { + onSuccess?: (data: A, out?: Out) => void; + onErrors?: (err: any, data: A) => { errors?: FormErrorsGeneric }; +} + +export interface FormWrapperProps extends FormWrapperOnActionProps { + clientValidationFunction?: (form: Partial) => FormErrors; + asyncCall?: (formData: A) => Promise; + FormUI: (props: FormUIProps) => JSX.Element; + errors?: FormErrors; + children?: JSX.Element; +} + +export interface FormWrapperOptions { + resetAfterSubmit: boolean; +} + +export interface FormUIGeneratorProps { + InputWrapper: (p: InputWrapperProps) => JSX.Element; + InputGeneric: ( + uiType: FormUIType + ) => (p: InputProps) => JSX.Element; // here cast to any to avoid types issue, if coded properly this should not cause any issues + Button: (a: SubmitButtonProps) => JSX.Element; +} + // compare with https://github.com/nexys-system/react-bootstrap-components/blob/master/src/components/headless/form/type.ts -export type FormErrorsGeneric = { [k in keyof A]?: string[] }; +export type FormErrorsGeneric = { [k in keyof A]?: string }; + +export type FormOptionSets = { + [k in keyof A]?: { id: number | string; name: string }[]; +}; -export interface FormUIProps { +interface FormUIPropsCore { loading: boolean; form: Partial; errors: FormErrorsGeneric; - options: { [k in keyof FormShape]?: { id: number | string; name: string }[] }; + options?: { + [k in keyof FormShape]?: { id: number | string; name: string }[]; + }; setForm: (f: Partial) => void; } -export interface InputProps { +export interface FormUIProps extends FormUIPropsCore { + onSubmit: (e: React.FormEvent) => void; + children?: JSX.Element; +} + +export interface OptionUnit { + id: Id; + name: string; +} + +export interface InputProps { value?: A; onChange: (a?: A) => void; - errors?: string[]; + errors?: string; disabled?: boolean; placeholder?: string; - options?: { id: A; name: string }[]; + options?: OptionUnit[]; +} + +// same as input props but options is mandatory +export interface InputOptionProps + extends InputProps { + options: OptionUnit[]; } export interface InputWrapperProps { label?: string; - children: JSX.Element; - errors?: string[]; + error?: string; info?: string; + children: JSX.Element; } export enum FormUIType { @@ -49,11 +96,28 @@ export interface StructureUnitCore { export interface FormDef extends StructureUnitCore { uiType: FormUIType; - optional: boolean; + optional?: boolean; disabled?: boolean; placeholder?: string; + description?: string; + + info?: string; + options?: OptionUnit[]; } export interface FormViewDef extends FormDef { render?: Render; } + +export type FormErrors = { + [k in keyof A]?: string; +}; + +export interface SubmitButtonProps { + disabled?: boolean; + loading?: boolean; +} + +export interface BackButtonProps { + onClick: () => void; +} diff --git a/src/lib/form/utils.ts b/src/lib/form/utils.ts index 75720c5..f94d740 100644 --- a/src/lib/form/utils.ts +++ b/src/lib/form/utils.ts @@ -1,4 +1,4 @@ -import { FormDef, FormUIType } from "./type"; +import { FormDef, FormErrors, FormUIType } from "./type"; import * as Validation from "@nexys/validation"; export const enumToOptions = (keys: { @@ -8,10 +8,10 @@ export const enumToOptions = (keys: { .filter((x) => !isNaN(Number(x))) .map((x) => ({ id: Number(x) as any as A, name: keys[Number(x)] })); -export const isA = ( - a: Partial, - formErrors: { [k in keyof A]?: string[] } -): a is A => Object.keys(formErrors).length === 0; +export const isNotPartial = ( + formData: Partial, + formErrors: FormErrors +): formData is A => Object.keys(formErrors).length === 0; const getType = (uiType: FormUIType) => { if ([FormUIType.SelectNumber, FormUIType.Number].includes(uiType)) { diff --git a/src/lib/toggle/index.tsx b/src/lib/toggle/index.tsx index a684624..a8faf71 100644 --- a/src/lib/toggle/index.tsx +++ b/src/lib/toggle/index.tsx @@ -1,8 +1,7 @@ import React from "react"; -import { FormWrapperProps } from "../form/form-wrapper"; - import { ViewProps, ViewStructureUnit } from "../view"; +import { FormWrapperOnActionProps } from "../form/type"; export interface LayoutProps { isForm?: boolean; @@ -13,7 +12,7 @@ export interface LayoutProps { const ToggleHeadless = ( structure: ViewStructureUnit[], - Form: (p: FormWrapperProps) => JSX.Element, + Form: (p: FormWrapperOnActionProps) => JSX.Element, View: (p: ViewProps) => JSX.Element, LayoutView: (p: LayoutProps) => JSX.Element, LayoutForm: (p: LayoutProps) => JSX.Element @@ -37,7 +36,6 @@ const ToggleHeadless = return (
    { setData(d); setIsForm(false); diff --git a/src/links.ts b/src/links.ts index 1813a17..16125bf 100644 --- a/src/links.ts +++ b/src/links.ts @@ -11,16 +11,16 @@ export const links: { [menu: string]: { name: string; link: string } } = { buttons: { name: "Buttons", link: "/buttons" }, badge: { name: "Badge", link: "/badge" }, statusChange: { name: "Status Change", link: "/status-change" }, - //download: { name: "Download", link: "/download" }, simpleList: { name: "Simple list", link: "/simple-list" }, toggle: { name: "Toggle", link: "/toggle" }, tabs: { name: "Tabs", link: "/tabs" }, fileUpload: { name: "File Upload", link: "/file-upload" }, detail: { name: "Detail", link: "/detail" }, - //auth: { name: "Auth", link: "/auth" }, + // download: { name: "Download", link: "/download" }, + // auth: { name: "Auth", link: "/auth" }, // superadmin: { name: "Superadmin", link: "/superadmin" }, // crudBrowser: { name: "Crud Browser", link: "/crud-browser" }, - //dateRange: { name: "Date Range", link: "/date-range" }, + // dateRange: { name: "Date Range", link: "/date-range" }, }; export const menus: { name: string; link: string }[] = Object.values(links);