From cf2b1c02e54c4201dd73423f02caaadcabc56087 Mon Sep 17 00:00:00 2001 From: rizky Date: Sat, 20 Apr 2024 22:55:13 -0700 Subject: [PATCH] fix lib --- comps/custom/AutoHeightTextarea.tsx | 6 +-- comps/custom/Breadcrumb.tsx | 1 + comps/form/Form.tsx | 18 ++++++++- comps/form/base/BaseField.tsx | 58 +++++++++++++++++++++++++++ comps/form/base/BaseForm.tsx | 51 ++++++++++++++++++++++++ comps/form/base/BaseLabel.tsx | 49 +++++++++++++++++++++++ comps/form/base/utils/init.ts | 35 ++++++++++++++++ comps/form/base/utils/reload.ts | 7 ++++ comps/form/base/utils/submit.ts | 9 +++++ comps/form/base/utils/type/field.ts | 57 ++++++++++++++++++++++++++ comps/form/base/utils/type/form.ts | 8 ++++ comps/form/base/utils/use-field.ts | 28 +++++++++++++ comps/form/field/type/TypeText.tsx | 62 +++++++++++++++++++---------- comps/form/typings.ts | 6 +-- utils/pathname.ts | 29 ++++++++------ 15 files changed, 384 insertions(+), 40 deletions(-) create mode 100755 comps/form/base/BaseField.tsx create mode 100755 comps/form/base/BaseForm.tsx create mode 100755 comps/form/base/BaseLabel.tsx create mode 100755 comps/form/base/utils/init.ts create mode 100755 comps/form/base/utils/reload.ts create mode 100755 comps/form/base/utils/submit.ts create mode 100755 comps/form/base/utils/type/field.ts create mode 100755 comps/form/base/utils/type/form.ts create mode 100755 comps/form/base/utils/use-field.ts diff --git a/comps/custom/AutoHeightTextarea.tsx b/comps/custom/AutoHeightTextarea.tsx index ee0bead..78f8beb 100755 --- a/comps/custom/AutoHeightTextarea.tsx +++ b/comps/custom/AutoHeightTextarea.tsx @@ -18,7 +18,7 @@ export function AutoHeightTextarea({ paddingBottom, paddingTop, } = window.getComputedStyle(ref.current); - ref.current.style.height = lineHeight; // set height temporarily to a single row to obtain scrollHeight, disregarding empty space after text (otherwise, scrollHeight would be equal to the height of the element) - this solves auto-shrinking of the textarea (it's not needed for auto-growing it) + ref.current.style.minHeight = lineHeight; // set height temporarily to a single row to obtain scrollHeight, disregarding empty space after text (otherwise, scrollHeight would be equal to the height of the element) - this solves auto-shrinking of the textarea (it's not needed for auto-growing it) const { scrollHeight } = ref.current; // scrollHeight = content height + padding top + padding bottom if (boxSizing === "border-box") { @@ -32,12 +32,12 @@ export function AutoHeightTextarea({ scrollHeight + parseFloat(borderTopWidth) + parseFloat(borderBottomWidth); - ref.current.style.height = `${Math.max(minHeight, allTextHeight)}px`; + ref.current.style.minHeight = `${Math.max(minHeight, allTextHeight)}px`; } else if (boxSizing === "content-box") { const minHeight = parseFloat(lineHeight) * minRows; const allTextHeight = scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom); - ref.current.style.height = `${Math.max(minHeight, allTextHeight)}px`; + ref.current.style.minHeight = `${Math.max(minHeight, allTextHeight)}px`; } else { console.error("Unknown box-sizing value."); } diff --git a/comps/custom/Breadcrumb.tsx b/comps/custom/Breadcrumb.tsx index a0b9d63..ca0fc2d 100755 --- a/comps/custom/Breadcrumb.tsx +++ b/comps/custom/Breadcrumb.tsx @@ -34,6 +34,7 @@ export const Breadcrumb: FC = (_arg) => { if (local.status === "init") { let should_load = true; + local.status = "loading"; if (isEditor && item && breadcrumbData[item.id]) { local.list = breadcrumbData[item.id]; diff --git a/comps/form/Form.tsx b/comps/form/Form.tsx index 4f05dc9..b9e0c87 100755 --- a/comps/form/Form.tsx +++ b/comps/form/Form.tsx @@ -1,12 +1,13 @@ import { useLocal } from "@/utils/use-local"; import get from "lodash.get"; -import { FC, useRef } from "react"; +import { FC, useEffect, useRef } from "react"; import { createPortal } from "react-dom"; import { Toaster } from "sonner"; import { FMInternal, FMProps } from "./typings"; import { editorFormData } from "./utils/ed-data"; import { formInit } from "./utils/init"; import { formReload } from "./utils/reload"; +import { getPathname } from "../../.."; const editorFormWidth = {} as Record; @@ -83,6 +84,13 @@ export const Form: FC = (props) => { }), }); + useEffect(() => { + if (fm.status === "ready") { + fm.status = "init"; + fm.render(); + } + }, [getPathname()]); + if (fm.status === "init") { formInit(fm, props); fm.reload(); @@ -95,6 +103,14 @@ export const Form: FC = (props) => { } const toaster_el = document.getElementsByClassName("prasi-toaster")[0]; + if (fm.status === "resizing") { + setTimeout(() => { + fm.status = "ready"; + fm.render(); + }, 100); + return null; + } + return (
{ diff --git a/comps/form/base/BaseField.tsx b/comps/form/base/BaseField.tsx new file mode 100755 index 0000000..8ba7071 --- /dev/null +++ b/comps/form/base/BaseField.tsx @@ -0,0 +1,58 @@ +import { FMLocal } from "../typings"; +import { BaseLabel } from "./BaseLabel"; +import { BaseFieldProps } from "./utils/type/field"; +import { useField } from "./utils/use-field"; + +export const BaseField = >( + arg: BaseFieldProps & { fm: FMLocal } +) => { + const field = useField(arg); + const fm = arg.fm; + const mode = fm.props.label_mode || "vertical"; + const props = arg.props; + const w = field.width; + const errors = fm.error.get(field.name); + + return ( + + ); +}; diff --git a/comps/form/base/BaseForm.tsx b/comps/form/base/BaseForm.tsx new file mode 100755 index 0000000..086a15d --- /dev/null +++ b/comps/form/base/BaseForm.tsx @@ -0,0 +1,51 @@ +import { useLocal } from "@/utils/use-local"; +import { FC } from "react"; +import { BaseField } from "./BaseField"; +import { initSimpleForm as initBaseForm } from "./utils/init"; +import { BaseFieldProps, BaseFormProps } from "./utils/type/field"; + +type Children> = Exclude< + BaseFormProps["children"], + undefined +>; + +export const BaseForm = >( + arg: BaseFormProps +) => { + const fm = useLocal({ status: "init" } as any); + const local = useLocal({ + Field: null as null | FC>, + children: null as unknown as Children, + }); + + if (fm.status === "init") { + local.Field = (props) => { + return ; + }; + if (arg.children) local.children = arg.children; + else { + local.children = ({ Field }) => { + const data = Object.entries(fm.data); + return ( + <> + {data.map(([key, value]) => { + return ; + })} + + ); + }; + } + initBaseForm(fm, arg); + } + + return ( + { + e.preventDefault(); + fm.submit(); + }} + > + {local.Field && local.children({ Field: local.Field })} + + ); +}; diff --git a/comps/form/base/BaseLabel.tsx b/comps/form/base/BaseLabel.tsx new file mode 100755 index 0000000..d31cfa7 --- /dev/null +++ b/comps/form/base/BaseLabel.tsx @@ -0,0 +1,49 @@ +import { FC } from "react"; +import { BaseFieldLocal } from "./utils/type/field"; +import { FMLocal } from "../typings"; + +export const BaseLabel = >({ + field, + fm, +}: { + field: BaseFieldLocal; + fm: FMLocal; +}) => { + const errors = fm.error.get(field.name); + + return ( +
+ 0 && `c-text-red-600`)}> + {field.label} + + {field.required && ( + + + + + + + + )} +
+ ); +}; diff --git a/comps/form/base/utils/init.ts b/comps/form/base/utils/init.ts new file mode 100755 index 0000000..240eac7 --- /dev/null +++ b/comps/form/base/utils/init.ts @@ -0,0 +1,35 @@ +import { FMLocal } from "../../typings"; +import { formError } from "../../utils/error"; +import { reloadBaseForm } from "./reload"; +import { submitBaseForm } from "./submit"; +import { BaseFormProps } from "./type/field"; + +export const initSimpleForm = >( + fm: FMLocal, + arg: BaseFormProps +) => { + fm.data = {}; + fm.error = formError(fm); + fm.events = { + on_change(name, new_value) {}, + }; + fm.field_def = {}; + fm.fields = {}; + fm.internal = { + submit: { done: [], promises: [], timeout: null as any }, + reload: { done: [], promises: [], timeout: null as any }, + }; + fm.props = {} as any; + fm.reload = async () => { + await reloadBaseForm(fm, arg); + }; + fm.size = { + width: 0, + height: 0, + field: "full", + }; + fm.submit = () => { + return submitBaseForm(fm, arg); + }; + fm.status = "ready"; +}; diff --git a/comps/form/base/utils/reload.ts b/comps/form/base/utils/reload.ts new file mode 100755 index 0000000..636d5e0 --- /dev/null +++ b/comps/form/base/utils/reload.ts @@ -0,0 +1,7 @@ +import { FMLocal } from "../../typings"; +import { BaseFormProps } from "./type/field"; + +export const reloadBaseForm = async >( + fm: FMLocal, + arg: BaseFormProps +) => {}; diff --git a/comps/form/base/utils/submit.ts b/comps/form/base/utils/submit.ts new file mode 100755 index 0000000..e6bb2a4 --- /dev/null +++ b/comps/form/base/utils/submit.ts @@ -0,0 +1,9 @@ +import { FMLocal } from "../../typings"; +import { BaseFormProps } from "./type/field"; + +export const submitBaseForm = async >( + fm: FMLocal, + arg: BaseFormProps +) => { + return true; +}; diff --git a/comps/form/base/utils/type/field.ts b/comps/form/base/utils/type/field.ts new file mode 100755 index 0000000..e13b22f --- /dev/null +++ b/comps/form/base/utils/type/field.ts @@ -0,0 +1,57 @@ +import { FC, ReactNode } from "react"; + + +export type BaseFieldWidth = + | "auto" + | "full" + | "¾" + | "½" + | "⅓" + | "¼" + | "1/2" + | "1/3" + | "1/4" + | "3/4"; + +export type BaseFieldType = "text" | "relation"; +export type BaseFieldProps> = { + name: keyof T; + label?: string; + type?: BaseFieldType; + props?: any; + desc?: string; + on_change?: (arg: { value: any }) => void | Promise; + + prefix?: any; + suffix?: any; + + required?: boolean; + required_msg?: (name: string) => string; + disabled?: boolean; + + width?: BaseFieldWidth; +}; + +export type BaseFieldInternal> = { + name: keyof T; + label: ReactNode; + type: BaseFieldType; + desc: string; + on_change: (arg: { value: any }) => void | Promise; + + prefix: any; + suffix: any; + + required: boolean; + required_msg: (name: string) => string; + disabled: boolean; + + width: BaseFieldWidth; + status: "init" | "loading" | "ready"; + + PassProp: any; + child: any; +}; + +export type BaseFieldLocal> = + BaseFieldInternal; diff --git a/comps/form/base/utils/type/form.ts b/comps/form/base/utils/type/form.ts new file mode 100755 index 0000000..8ff5eab --- /dev/null +++ b/comps/form/base/utils/type/form.ts @@ -0,0 +1,8 @@ +import { FC, ReactNode } from "react"; +import { BaseFieldProps } from "./field"; + +export type BaseFormProps> = { + onLoad: () => Promise; + onSubmit: (arg: { data: T | null }) => Promise; + children?: (arg: { Field: FC> }) => ReactNode; +}; diff --git a/comps/form/base/utils/use-field.ts b/comps/form/base/utils/use-field.ts new file mode 100755 index 0000000..e06adf6 --- /dev/null +++ b/comps/form/base/utils/use-field.ts @@ -0,0 +1,28 @@ +import { useLocal } from "@/utils/use-local"; +import { FMLocal } from "../../typings"; +import { + BaseFieldInternal, + BaseFieldLocal, + BaseFieldProps, +} from "./type/field"; +import { formatName } from "@/gen/utils"; + +export const useField = >( + arg: BaseFieldProps & { fm: FMLocal } +) => { + const fm = arg.fm; + const local = useLocal>({ + status: "init", + } as any) as BaseFieldLocal; + + if (local.status === "init") { + local.name = arg.name; + local.label = arg.label || formatName(arg.name as string); + local.type = arg.type || "text"; + local.desc = arg.desc || ""; + local.status = "ready"; + local.width = arg.width || fm.size.field === "full" ? "full" : "1/2"; + } + + return local as BaseFieldLocal; +}; diff --git a/comps/form/field/type/TypeText.tsx b/comps/form/field/type/TypeText.tsx index 7955926..8e23f3a 100755 --- a/comps/form/field/type/TypeText.tsx +++ b/comps/form/field/type/TypeText.tsx @@ -2,9 +2,10 @@ import { FC } from "react"; import { FMLocal, FieldLocal } from "../../typings"; import { useLocal } from "@/utils/use-local"; import parser from "any-date-parser"; +import { AutoHeightTextarea } from "@/comps/custom/AutoHeightTextarea"; export type PropTypeText = { - type: "text" | "password" | "number" | "date" | "datetime"; + type: "text" | "password" | "number" | "date" | "datetime" | "textarea"; }; const parse = parser.exportAsFunctionAny("en-US"); @@ -31,25 +32,46 @@ export const FieldTypeText: FC<{ return ( <> - { - fm.data[field.name] = ev.currentTarget.value; - fm.render(); - }} - value={value || ""} - disabled={field.disabled} - className="c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full" - spellCheck={false} - onFocus={() => { - field.focused = true; - field.render(); - }} - onBlur={() => { - field.focused = false; - field.render(); - }} - /> + {prop.type === "textarea" ? ( + { + fm.data[field.name] = ev.currentTarget.value; + fm.render(); + }} + value={value || ""} + disabled={field.disabled} + className="c-flex-1 c-bg-transparent c-outline-none c-p-2 c-text-sm c-w-full" + spellCheck={false} + onFocus={() => { + field.focused = true; + field.render(); + }} + onBlur={() => { + field.focused = false; + field.render(); + }} + /> + ) : ( + { + fm.data[field.name] = ev.currentTarget.value; + fm.render(); + }} + value={value || ""} + disabled={field.disabled} + className="c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full" + spellCheck={false} + onFocus={() => { + field.focused = true; + field.render(); + }} + onBlur={() => { + field.focused = false; + field.render(); + }} + /> + )} ); }; diff --git a/comps/form/typings.ts b/comps/form/typings.ts index 8f9427f..6975840 100755 --- a/comps/form/typings.ts +++ b/comps/form/typings.ts @@ -73,9 +73,9 @@ export type FMInternal = { error: { readonly object: Record; readonly list: { name: string; error: string[] }[]; - set: (name: string, error: string[]) => void; - get: (name: string) => string[]; - clear: (name?: string) => void; + set: (name: string | number | symbol, error: string[]) => void; + get: (name: string | number | symbol) => string[]; + clear: (name?: string | number | symbol) => void; }; internal: { reload: { diff --git a/utils/pathname.ts b/utils/pathname.ts index b3e7eee..8cddd23 100755 --- a/utils/pathname.ts +++ b/utils/pathname.ts @@ -1,18 +1,21 @@ -export const getPathname = () => { +export const getPathname = (url?: string) => { // if (["localhost", "prasi.avolut.com"].includes(location.hostname)) { - if ( - location.pathname.startsWith("/vi") || - location.pathname.startsWith("/prod") || - location.pathname.startsWith("/deploy") - ) { - const hash = location.hash; - - if (hash !== "") { - return "/" + location.pathname.split("/").slice(3).join("/") + hash; - } else { - return "/" + location.pathname.split("/").slice(3).join("/"); - } + if ( + location.pathname.startsWith("/vi") || + location.pathname.startsWith("/prod") || + location.pathname.startsWith("/deploy") + ) { + const hash = location.hash; + if (url?.startsWith("/prod")) { + return "/" + url.split("/").slice(3).join("/"); } + + if (hash !== "") { + return "/" + location.pathname.split("/").slice(3).join("/") + hash; + } else { + return "/" + location.pathname.split("/").slice(3).join("/"); + } + } // } return location.pathname; };