Skip to content

Commit

Permalink
Merge pull request #20 from nexys-system/formv2
Browse files Browse the repository at this point in the history
Formv2
  • Loading branch information
johnb8005 authored Jan 15, 2024
2 parents 9d7bd89 + 2427201 commit d845902
Show file tree
Hide file tree
Showing 55 changed files with 790 additions and 1,495 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ jobs:
steps:
- uses: nexys-system/gh-actions-spa-test@v1
- name: Build package
run: yarn buildpackage
run: yarn buildPackage
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 8 additions & 6 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -50,24 +50,26 @@ export default () => (
<Route path={links.layout.link + "/*"} element={<LayoutComponent />} />
<Route path={links.view.link} element={<View />} />
<Route path={links.card.link} element={<Card />} />
<Route path={links.form.link} element={<Form />} />
<Route path={links.form.link + "/*"} element={<Form />} />
<Route path={links.loadDataAsync.link} element={<LoadDataAsync />} />
<Route path={links.notifications.link} element={<Notifications />} />
<Route path={links.code.link} element={<Code />} />
<Route path={links.listAssign.link} element={<ListAssign />} />
<Route path={links.buttons.link} element={<Buttons />} />
<Route path={links.badge.link} element={<Badge />} />
<Route path={links.download.link} element={<Download />} />

<Route path={links.simpleList.link} element={<SimpleList />} />
<Route path={links.toggle.link} element={<Toggle />} />
<Route path={links.tabs.link + "/*"} element={<Tabs />} />
<Route path={links.fileUpload.link} element={<FileUpload />} />
<Route path={links.detail.link} element={<Detail />} />
<Route path={links.statusChange.link} element={<StatusChange />} />
{/*
<Route path={links.download.link} element={<Download />} />
<Route path={links.superadmin.link + "/*"} element={<Superadmin />} />
<Route path={links.crudBrowser.link + "/*"} element={<CrudBrowser />} />
<Route path={links.auth.link + "/*"} element={<Auth />} />
<Route path={links.dateRange.link} element={<DateRange />} />
//<Route path={links.crudBrowser.link + "/*"} element={<CrudBrowser />} />
<Route path={links.auth.link + "/*"} element={<Auth />} /> <Route path={links.dateRange.link} element={<DateRange />} />*/}

<Route path={"/builder/form"} element={<FormBuilder />} />
<Route path={"/builder/table"} element={<TableBuilder />} />
<Route path={"/"} element={<Public />} />
Expand Down
58 changes: 57 additions & 1 deletion src/components/buttons/with-action.tsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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 (
<button
type={type}
disabled={disabled}
className={className}
onClick={onClick}
>
{children}
</button>
);
};

export const SubmitButton = ({ disabled, loading }: SubmitButtonProps) => {
if (loading) {
return <Spinner />;
}

return (
<Button type="submit" disabled={disabled} context="primary">
Submit
</Button>
);
};

export const BackButton = ({ onClick }: { onClick: () => void }) => (
<Button onClick={onClick}>Back</Button>
);
2 changes: 1 addition & 1 deletion src/components/form/generator.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
35 changes: 21 additions & 14 deletions src/components/form/inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,17 +22,17 @@ export const getClassName = (
export const InputWrapper = ({
label,
children,
errors,
error,
}: T.InputWrapperProps) => (
<div className="mb-3">
<label className="block text-sm font-medium text-gray-700">{label}</label>
{children}
{errors && (
{error && (
<div
id="validationServer03Feedback"
className="text-red-500 text-xs italic mt-1"
>
{errors[0]}
{error}
</div>
)}
</div>
Expand Down Expand Up @@ -77,13 +77,13 @@ export const Textarea = ({
/>
);

export const Select = <A extends number | string>({
export const Select = <A, Id extends number | string>({
onChange,
options,
value,
errors,
disabled,
}: T.InputProps<A>) => (
}: T.InputProps<A, Id>) => (
<select
className={getClassName(errors, "border p-2 rounded bg-white")}
onChange={(v) => {
Expand All @@ -104,7 +104,7 @@ export const Select = <A extends number | string>({
onChange(value as any as A);
}}
disabled={disabled}
defaultValue={value}
defaultValue={value as any}
>
<option></option>
{options &&
Expand All @@ -118,21 +118,26 @@ export const Select = <A extends number | string>({

export const SelectEnum = Select;

export const SelectObject = <A extends number | string>(
props: Omit<T.InputProps<A>, "value" | "onChange"> &
Pick<T.InputProps<{ id: A; name: string }>, "value" | "onChange">
export const SelectObject = <A, Id extends number | string>(
props: Omit<
T.InputProps<{ id: Id; name: string }, Id>,
"value" | "onChange"
> &
Pick<T.InputProps<{ id: Id; name: string }, Id>, "value" | "onChange">
) => {
const onChange = (id?: A) => {
const onChange = (id?: Id) => {
const option = props.options?.find((x) => x.id === id);

if (option) {
props.onChange(option);
}
};

const value: A | undefined = props.value?.id;
const options = props.options;

const value: Id | undefined = props.value?.id;

return Select<A>({ ...props, onChange, value });
return Select<Id, Id>({ ...props, onChange, value });
};

export const Checkbox = ({ value, onChange }: T.InputProps<boolean>) => (
Expand All @@ -144,7 +149,9 @@ export const Checkbox = ({ value, onChange }: T.InputProps<boolean>) => (
/>
);

export const InputGeneric = (uiType: T.FormUIType) => {
export const InputGeneric = (
uiType: T.FormUIType
): ((props: T.InputProps<any, any>) => JSX.Element) => {
switch (uiType) {
case T.FormUIType.Switch:
return Checkbox;
Expand Down
5 changes: 2 additions & 3 deletions src/components/toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { FormWrapperProps } from "../lib/form/form-wrapper";
import { ViewStructureUnit } from "../lib/view";
import ViewGeneric, { toViewStructure } from "../lib/view";
import ToggleHeadless, { LayoutProps } from "../lib/toggle";
import { FormViewDef } from "../lib/form/type";
import { FormViewDef, FormWrapperOnActionProps } from "../lib/form/type";
import PreForm from "./form/generator";
import { Row } from "./view";

Expand Down Expand Up @@ -34,7 +33,7 @@ const LayoutForm = ({ setIsForm, children }: LayoutProps) => (

const PreToggle = <A, Out>(
structure: ViewStructureUnit<A>[],
Form: (p: FormWrapperProps<A, Out>) => JSX.Element
Form: (p: FormWrapperOnActionProps<A, Out>) => JSX.Element
) => ToggleHeadless(structure, Form, View, LayoutView, LayoutForm);

export const ToggleFromDef = <A, Out>(
Expand Down
144 changes: 144 additions & 0 deletions src/examples/form/free-ui.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React from "react";

import * as T from "../../lib/form/type";

import {
Input,
Checkbox as InputCheckbox,
SelectObject as InputOptions,
Select as InputOptionsScalar,
InputWrapper as Wrapper,
} from "../../components/form/inputs";

import { SubmitButton } from "../../components/buttons/with-action";
import { FormWrapper } from "../../lib/form/form-wrapper";

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 countries: T.OptionUnit<number>[] = [
{ id: 1, name: "Switzerland" },
{ id: 2, name: "Australia" },
];

const langs: T.OptionUnit[] = [
{ id: "en", name: "English" },
{ id: "fr", name: "French" },
];

const uiStyles: T.OptionUnit<UIStyle>[] = Object.keys(UIStyle)
.filter((x) => !isNaN(x as any))
.map((id) => ({
id: id as any as UIStyle,
name: UIStyle[id as any as UIStyle],
}));

const FormUI = ({
onSubmit,
errors,
form,
setForm,
loading,
}: T.FormUIProps<FormData>) => {
console.log(errors);
return (
<form onSubmit={onSubmit}>
<Wrapper label="Name" error={errors["name"]}>
<Input
value={form["name"]}
onChange={(name) => setForm({ ...form, name })}
/>
</Wrapper>
<Wrapper label={"is uuid"} error={errors["isUuid"]}>
<InputCheckbox
value={form["isUuid"]}
onChange={(isUuid) => setForm({ ...form, isUuid })}
/>
</Wrapper>
<Wrapper
label="Country"
info={"Pick a country from the list"}
error={errors["country"]}
>
<InputOptions<number, number>
options={countries}
value={form["country"]}
onChange={(country) => setForm({ ...form, country })}
/>
</Wrapper>
<Wrapper
label="Language"
info={"Pick a language from the list"}
error={errors["lang"]}
>
<InputOptions<string, string>
options={langs}
value={form["lang"]}
onChange={(lang) => setForm({ ...form, lang })}
/>
</Wrapper>

<Wrapper
label="UI Style"
info={"Pick a UI style from the list"}
error={errors["uiStyle"]}
>
<InputOptionsScalar<UIStyle, UIStyle>
options={uiStyles}
value={form["uiStyle"]}
onChange={(uiStyle) => setForm({ ...form, uiStyle })}
/>
</Wrapper>

<SubmitButton loading={loading} />
</form>
);
};

const clientValidationFunction = (
v: Partial<FormData>
): T.FormErrors<FormData> => {
console.log(v);

const e: T.FormErrors<FormData> = {};

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 (
<FormWrapper
FormUI={FormUI}
onSuccess={console.log}
clientValidationFunction={clientValidationFunction}
asyncCall={asyncCall}
/>
);
};
export default FreeUI;
Loading

0 comments on commit d845902

Please sign in to comment.