Merge pull request #111 from ctk-hq/feat/super-form

Created super form
pull/109/merge
Artem Golub 3 years ago committed by GitHub
commit 1e85671688
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -25,6 +25,7 @@ import ProtectedRoute from "./partials/ProtectedRoute";
import "./index.css";
import { lightTheme } from "./utils/theme";
import { SuperFormProvider } from "./components/SuperFormProvider";
const queryClient = new QueryClient();
@ -100,6 +101,7 @@ export default function App() {
return (
<CssVarsProvider theme={lightTheme}>
<QueryClientProvider client={queryClient}>
<SuperFormProvider>
<div>
<Toaster />
<SideBar isAuthenticated={isAuthenticated} state={state} />
@ -146,10 +148,13 @@ export default function App() {
<Route path="/signup" element={<Signup dispatch={dispatch} />} />
<Route path="/login" element={<Login dispatch={dispatch} />} />
<Route path="/github/cb" element={<GitHub dispatch={dispatch} />} />
<Route
path="/github/cb"
element={<GitHub dispatch={dispatch} />}
/>
</Routes>
</div>
</SuperFormProvider>
<ReactQueryDevtools initialIsOpen={true} />
</QueryClientProvider>
</CssVarsProvider>

@ -0,0 +1,31 @@
import { styled } from "@mui/joy";
import { FunctionComponent, ReactElement } from "react";
import { useSuperForm } from "../hooks";
import { IFormField } from "../types";
interface IRootProps {
spans: number[];
}
const Root = styled("div", {
shouldForwardProp: (name) => name !== "spans"
})<IRootProps>`
grid-column: span ${({ spans }) => spans[0]};
@media (max-width: 640px) {
grid-column: span ${({ spans }) => spans[1]};
}
`;
export interface IGridColumnProps {
spans: number[];
fields: IFormField[];
}
export const GridColumn: FunctionComponent<IGridColumnProps> = (
props: IGridColumnProps
): ReactElement => {
const { spans, fields } = props;
const { renderField } = useSuperForm();
return <Root spans={spans}>{fields.map(renderField)}</Root>;
};

@ -0,0 +1,30 @@
import { styled } from "@mui/joy";
import { FunctionComponent, ReactElement, ReactNode } from "react";
import { useSuperForm } from "../hooks";
import { IFormField } from "../types";
const Root = styled("div")`
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 0px;
grid-row-gap: 0px;
@media (max-width: 640px) {
grid-template-columns: repeat(1, 1fr);
}
column-gap: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
export interface IGridProps {
fields: IFormField[];
children?: ReactNode;
}
export const GridRow: FunctionComponent<IGridProps> = (
props: IGridProps
): ReactElement => {
const { fields } = props;
const { renderField } = useSuperForm();
return <Root>{fields.map(renderField)}</Root>;
};

@ -0,0 +1,28 @@
import { styled } from "@mui/joy";
import { FunctionComponent, ReactElement } from "react";
import { useSuperForm } from "../hooks";
import { IFormField, TFinalFormField } from "../types";
const Root = styled("div")`
display: flex;
flex-direction: column;
row-gap: ${({ theme }) => theme.spacing(1)};
@media (max-width: 640px) {
row-gap: 0;
}
`;
export interface IRecordFormProps<T extends IFormField> {
fields: T[];
}
export const SuperForm: FunctionComponent<IRecordFormProps<TFinalFormField>> = <
T extends IFormField
>(
props: IRecordFormProps<T>
): ReactElement => {
const { fields } = props;
const { renderField } = useSuperForm();
return <Root>{fields.map(renderField)}</Root>;
};

@ -0,0 +1,43 @@
import { FunctionComponent, ReactElement, ReactNode, useMemo } from "react";
import { SuperFormContext } from "../contexts";
import { IFormField } from "../types";
import TextField from "./global/FormElements/TextField";
import Toggle from "./global/FormElements/Toggle";
import { GridColumn } from "./GridColumn";
import { GridRow } from "./GridRow";
import Records from "./Records";
export interface ISuperFormProviderProps {
children?: ReactNode;
}
const types: Record<string, FunctionComponent<any>> = {
text: TextField,
"grid-row": GridRow,
"grid-column": GridColumn,
toggle: Toggle,
records: Records
};
export const SuperFormProvider: FunctionComponent<ISuperFormProviderProps> = (
props: ISuperFormProviderProps
): ReactElement => {
const { children } = props;
const value = useMemo(
() => ({
types,
renderField: (field: IFormField) => {
const Component = types[field.type];
return <Component key={field.id} {...field} />;
}
}),
[]
);
return (
<SuperFormContext.Provider value={value}>
{children}
</SuperFormContext.Provider>
);
};

@ -1,7 +1,6 @@
import { styled } from "@mui/joy";
import TextField from "../../../global/FormElements/TextField";
import Toggle from "../../../global/FormElements/Toggle";
import Records from "../../../Records";
import { TFinalFormField } from "../../../../types";
import { SuperForm } from "../../../SuperForm";
const Root = styled("div")`
display: flex;
@ -12,77 +11,145 @@ const Root = styled("div")`
}
`;
const Group = styled("div")`
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 0px;
grid-row-gap: 0px;
@media (max-width: 640px) {
grid-template-columns: repeat(1, 1fr);
}
column-gap: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
const SpanTwo = styled("div")`
grid-column: span 2;
@media (max-width: 640px) {
grid-column: span 3;
}
`;
const General = () => {
return (
<Root>
<Group>
<TextField label="Service name" name="serviceName" required={true} />
</Group>
<Group>
<TextField label="Image name" name="imageName" required={false} />
<TextField label="Image tag" name="imageTag" />
</Group>
<Group>
<TextField
label="Container name"
name="containerName"
required={false}
/>
</Group>
<Group>
<SpanTwo>
<TextField label="Command" name="command" required={false} />
</SpanTwo>
</Group>
<Group>
<SpanTwo>
<TextField label="Entrypoint" name="entrypoint" required={false} />
</SpanTwo>
</Group>
<Group>
<TextField label="Env file" name="envFile" required={false} />
</Group>
<Group>
<SpanTwo>
<TextField
label="Working directory"
name="workingDir"
required={false}
/>
</SpanTwo>
</Group>
<Group>
<SpanTwo>
<Toggle
name="restart"
label="Restart policy"
options={[
<SuperForm
fields={[
{
id: "row1",
type: "grid-row",
fields: [
{
id: "serviceName",
type: "text",
name: "serviceName",
label: "Service name",
required: true
}
]
},
{
id: "row2",
type: "grid-row",
fields: [
{
id: "imageName",
type: "text",
name: "imageName",
label: "Image name"
},
{
id: "imageTag",
type: "text",
name: "imageTag",
label: "Image tag"
}
]
},
{
id: "row3",
type: "grid-row",
fields: [
{
id: "containerName",
type: "text",
name: "containerName",
label: "Container name"
}
]
},
{
id: "row4",
type: "grid-row",
fields: [
{
id: "row4-column1",
type: "grid-column",
spans: [2, 3],
fields: [
{
id: "command",
type: "text",
name: "command",
label: "Command"
}
]
}
]
},
{
id: "row5",
type: "grid-row",
fields: [
{
id: "row5-column1",
type: "grid-column",
spans: [2, 3],
fields: [
{
id: "entrypoint",
type: "text",
name: "entrypoint",
label: "Entrypoint"
}
]
}
]
},
{
id: "row6",
type: "grid-row",
fields: [
{
id: "row6-column1",
type: "grid-column",
spans: [2, 3],
fields: [
{
id: "envFile",
type: "text",
name: "envFile",
label: "Env file"
}
]
}
]
},
{
id: "row7",
type: "grid-row",
fields: [
{
id: "row7-column1",
type: "grid-column",
spans: [2, 3],
fields: [
{
id: "workingDir",
type: "text",
name: "workingDir",
label: "Working directory"
}
]
}
]
},
{
id: "row8",
type: "grid-row",
fields: [
{
id: "row8-column1",
type: "grid-column",
spans: [2, 3],
fields: [
{
id: "restart",
type: "toggle",
name: "restart",
label: "Restart Policy",
options: [
{
value: "no",
text: "no"
@ -99,30 +166,37 @@ const General = () => {
value: "unless-stopped",
text: "unless-stopped"
}
]}
/>
</SpanTwo>
</Group>
<Records
name="ports"
title="Ports"
defaultOpen={true}
fields={(index: number) => [
]
}
]
}
]
},
{
id: "ports",
type: "records",
name: "ports",
title: "Ports",
defaultOpen: true,
fields: (index: number): TFinalFormField[] => [
{
id: `ports[${index}].hostPort`,
type: "text",
name: `ports[${index}].hostPort`,
placeholder: "Host port",
required: true,
type: "text"
required: true
},
{
id: `ports[${index}].containerPort`,
type: "text",
name: `ports[${index}].containerPort`,
placeholder: "Container port",
type: "text"
placeholder: "Container port"
},
{
name: `ports[${index}].protocol`,
id: `ports[${index}].protocol`,
type: "toggle",
name: `ports[${index}].protocol`,
label: "Protocol",
options: [
{
value: "tcp",
@ -134,74 +208,85 @@ const General = () => {
}
]
}
]}
newValue={{
],
newValue: {
hostPort: "",
containerPort: "",
protocol: ""
}}
/>
<Records
name="dependsOn"
title="Depends on"
fields={(index: number) => [
}
},
{
id: "dependsOn",
type: "records",
name: "dependsOn",
title: "Depends on",
fields: (index: number): TFinalFormField[] => [
{
id: `dependsOn[${index}]`,
type: "text",
name: `dependsOn[${index}]`,
placeholder: "Service name",
required: false,
type: "text"
required: false
}
]}
newValue={""}
/>
<Records
name="networks"
title="Networks"
fields={(index: number) => [
],
newValue: ""
},
{
id: "networks",
type: "records",
title: "Networks",
name: "networks",
fields: (index: number): TFinalFormField[] => [
{
id: `networks[${index}]`,
type: "text",
name: `networks[${index}]`,
placeholder: "Network name",
required: false,
type: "text"
required: false
}
]}
newValue={""}
/>
<Records
name="labels"
title="Labels"
fields={(index: number) => [
],
newValue: ""
},
{
id: "labels",
type: "records",
title: "Labels",
name: "labels",
fields: (index: number): TFinalFormField[] => [
{
id: `labels[${index}].key`,
type: "text",
name: `labels[${index}].key`,
placeholder: "Key",
required: true,
type: "text"
required: true
},
{
id: `labels[${index}].value`,
type: "text",
name: `labels[${index}].value`,
placeholder: "Value",
required: true,
type: "text"
required: true
}
]}
newValue={{ key: "", value: "" }}
/>
<Records
name="profiles"
title="Profiles"
fields={(index: number) => [
],
newValue: { key: "", value: "" }
},
{
id: "profiles",
type: "records",
title: "Profiles",
name: "profiles",
fields: (index: number): TFinalFormField[] => [
{
id: `profiles[${index}]`,
name: `profiles[${index}]`,
placeholder: "Name",
required: true,
type: "text"
}
],
newValue: ""
}
]}
newValue={""}
/>
</Root>
);

@ -0,0 +1,4 @@
import { createContext } from "react";
import { ISuperFormContext } from "../types";
export const SuperFormContext = createContext<ISuperFormContext | null>(null);

@ -1 +1,2 @@
export * from "./TabContext";
export * from "./SuperFormContext";

@ -1,3 +1,4 @@
export * from "./useTitle";
export * from "./useAccordionState";
export * from "./useTabContext";
export * from "./useSuperForm";

@ -0,0 +1,12 @@
import { useContext } from "react";
import { SuperFormContext } from "../contexts";
import { ISuperFormContext } from "../types";
export const useSuperForm = (): ISuperFormContext => {
const context = useContext(SuperFormContext);
if (!context) {
throw new Error("Cannot find super form context!");
}
return context;
};

@ -1,5 +1,6 @@
import { AnchorId } from "@jsplumb/common";
import { Dictionary } from "lodash";
import { FunctionComponent, ReactNode } from "react";
import { KeyValuePair } from "tailwindcss/types/config";
import { string } from "yup";
import { NodeGroupType } from "./enums";
@ -520,3 +521,85 @@ export interface ITabContext {
value: string;
onChange: (newValue: string) => void;
}
/* -- SuperForm -- */
export interface ISuperFormContext {
types: Record<string, FunctionComponent>;
renderField: (field: IFormField) => ReactNode;
}
export interface IFormField {
id: string;
type:
| "grid-row"
| "grid-column"
| "text"
| "integer"
| "toggle"
| "accordion"
| "records";
}
export interface IGridRowField<T extends IFormField> extends IFormField {
type: "grid-row";
fields: T[];
}
export interface IGridColumnField<T extends IFormField> extends IFormField {
type: "grid-column";
spans: number[];
fields: T[];
}
export interface IValueField extends IFormField {
name: string;
}
export interface ISingleRowField extends IValueField {
help?: string;
}
export interface ITextField extends ISingleRowField {
type: "text";
label?: string;
placeholder?: string;
required?: boolean;
}
export interface IIntegerField extends ISingleRowField {
type: "integer";
label: string;
required: boolean;
}
export interface IToggleField extends ISingleRowField {
type: "toggle";
label: string;
options: {
text: string;
value: string;
}[];
}
export interface IRecordsField<T extends IFormField> extends IValueField {
type: "records";
title: string;
defaultOpen?: boolean;
fields: (index: number) => T[];
newValue: any;
}
export interface IAccordionField extends IFormField {
type: "accordion";
title: string;
}
export type TFinalFormField =
| IGridColumnField<TFinalFormField>
| IGridRowField<TFinalFormField>
| ITextField
| IIntegerField
| IToggleField
| IRecordsField<TFinalFormField>
| IAccordionField;

Loading…
Cancel
Save