From 13198aea7301d28450f1210a1f23f6bd084d8d7b Mon Sep 17 00:00:00 2001 From: faisolavolut Date: Wed, 22 Jan 2025 09:50:32 +0700 Subject: [PATCH] lib --- components/form/Field.tsx | 65 ++++++++++++--- components/form/field/AfterEditor.tsx | 9 +++ components/form/field/TypeCheckbox.tsx | 6 +- components/form/field/TypeColor.tsx | 1 + components/form/field/TypeInput.tsx | 13 +-- components/form/field/TypeRichText.tsx | 41 +++++++--- components/form/field/TypeTag.tsx | 14 +++- components/form/field/TypeUploadSingle.tsx | 2 +- components/form/field/Typeahead.tsx | 2 +- components/partials/Sidebar.tsx | 4 +- components/tablelist/TableList.tsx | 94 ++++++++++++++++++++++ components/ui/EditorProvider.tsx | 13 +++ components/ui/button.tsx | 2 +- utils/siteurl.ts | 2 +- 14 files changed, 228 insertions(+), 40 deletions(-) create mode 100644 components/form/field/AfterEditor.tsx create mode 100644 components/ui/EditorProvider.tsx diff --git a/components/form/Field.tsx b/components/form/Field.tsx index 87d706b..f3a4650 100644 --- a/components/form/Field.tsx +++ b/components/form/Field.tsx @@ -8,6 +8,7 @@ import { TypeRichText } from "./field/TypeRichText"; import { TypeTag } from "./field/TypeTag"; import get from "lodash.get"; import { getNumber } from "@/lib/utils/getNumber"; +import { useLocal } from "@/lib/utils/use-local"; export const Field: React.FC = ({ fm, @@ -26,7 +27,9 @@ export const Field: React.FC = ({ suffix, }) => { let result = null; - + const field = useLocal({ + focus: false, + }); const suffixRef = useRef(null); const prefixRef = useRef(null); const is_disable = fm.mode === "view" ? true : disabled; @@ -58,7 +61,25 @@ export const Field: React.FC = ({
{!hidden_label ? ( @@ -78,22 +99,34 @@ export const Field: React.FC = ({ error ? "flex flex-row rounded-md flex-grow border-red-500 border items-center" : "flex flex-row rounded-md flex-grow items-center", - is_disable ? "bg-gray-100" : "", - "relative", - "" + is_disable + ? "border border-gray-100 bg-gray-100" + : "border border-gray-300 ", + "relative field", + !is_disable + ? style === "underline" + ? "focus-within:border-b focus-within:border-b-gray-500" + : "focus-within:border focus-within:border-gray-500" + : "", + + style === "underline" + ? "rounded-none border-transparent border-b-gray-300" + : "", + ["rating", "color", "single-checkbox", "checkbox"].includes(type) && + css` + border: 0px !important; + ` )} > {before && (
{before} @@ -151,7 +184,7 @@ export const Field: React.FC = ({ onLoad={onLoad} placeholder={placeholder} disabled={is_disable} - on_change={onChange} + onChange={onChange} className={className} /> @@ -163,8 +196,8 @@ export const Field: React.FC = ({ onLoad={onLoad} placeholder={placeholder} disabled={is_disable} - on_change={onChange} className={className} + onChange={onChange} mode="single" /> @@ -175,6 +208,7 @@ export const Field: React.FC = ({ name={name} disabled={is_disable} className={className} + onChange={onChange} /> ) : ["tag"].includes(type) ? ( @@ -184,6 +218,7 @@ export const Field: React.FC = ({ name={name} disabled={is_disable} className={className} + onChange={onChange} /> ) : ( @@ -196,6 +231,10 @@ export const Field: React.FC = ({ type={type} disabled={is_disable} onChange={onChange} + onFocus={() => { + field.focus = true; + field.render(); + }} className={cx( before && css` @@ -215,7 +254,7 @@ export const Field: React.FC = ({ )} {after && (
= ({ local, editor }) => { + useEffect(() => { + local.editor = editor; + local.render(); + }, []); + return
; +}; diff --git a/components/form/field/TypeCheckbox.tsx b/components/form/field/TypeCheckbox.tsx index 1736cb7..5ab368c 100644 --- a/components/form/field/TypeCheckbox.tsx +++ b/components/form/field/TypeCheckbox.tsx @@ -57,12 +57,16 @@ export const FieldCheckbox: FC = ({ const val = selected.map((e) => e.value); if (mode === "single") { selected = val?.[0]; - + fm.data[name] = selected; } else { fm.data[name] = val; } fm.render(); + + if (typeof onChange === "function") { + onChange(fm.data[name]); + } }; return ( <> diff --git a/components/form/field/TypeColor.tsx b/components/form/field/TypeColor.tsx index fd65852..f90f301 100644 --- a/components/form/field/TypeColor.tsx +++ b/components/form/field/TypeColor.tsx @@ -10,6 +10,7 @@ export const TypeColor: React.FC = ({ value, onChangePicker, onClose, + onChange, }) => { const meta = useLocal({ originalValue: "", diff --git a/components/form/field/TypeInput.tsx b/components/form/field/TypeInput.tsx index 9e806d6..4de6e4f 100644 --- a/components/form/field/TypeInput.tsx +++ b/components/form/field/TypeInput.tsx @@ -70,12 +70,7 @@ export const TypeInput: React.FC = ({ disabled={disabled} required={required} className={cx( - "text-sm", - error - ? css` - border-color: red !important; - ` - : ``, + "text-sm border-none", css` background-color: ${disabled ? "rgb(243 244 246)" @@ -112,6 +107,9 @@ export const TypeInput: React.FC = ({ onRatingChange={(e) => { fm.data[name] = getNumber(e); fm.render(); + if (typeof onChange === "function") { + onChange(fm.data[name]); + } }} />
@@ -160,6 +158,9 @@ export const TypeInput: React.FC = ({ update={(val) => { fm.data[name] = val; fm.render(); + if (typeof onChange === "function") { + onChange(fm.data[name]); + } }} onOpen={() => { input.open = true; diff --git a/components/form/field/TypeRichText.tsx b/components/form/field/TypeRichText.tsx index 16788f7..0d59640 100644 --- a/components/form/field/TypeRichText.tsx +++ b/components/form/field/TypeRichText.tsx @@ -1,12 +1,7 @@ import { useLocal } from "@/lib/utils/use-local"; import { Input } from "../../ui/input"; -import { useEffect, useState } from "react"; -import { - useEditor, - EditorContent, - useCurrentEditor, - EditorProvider, -} from "@tiptap/react"; +import { useEffect, useRef, useState } from "react"; +import { useCurrentEditor, EditorProvider } from "@tiptap/react"; import Underline from "@tiptap/extension-underline"; import Link from "@tiptap/extension-link"; import StarterKit from "@tiptap/starter-kit"; @@ -23,6 +18,7 @@ import TableCell from "@tiptap/extension-table-cell"; import TableHeader from "@tiptap/extension-table-header"; import TableRow from "@tiptap/extension-table-row"; import { ButtonRichText } from "../../ui/button-rich-text"; +import { InitEditor } from "./AfterEditor"; export const TypeRichText: React.FC = ({ name, @@ -35,10 +31,14 @@ export const TypeRichText: React.FC = ({ onChange, }) => { let value: any = fm.data?.[name] || ""; + const editorRef = useRef(null); + const [content, setContent] = useState(``); + const input = useLocal({ - value: 0 as any, + value: `` as any, ref: null as any, open: false, + editor: null as any, }); const [url, setUrl] = useState(null as any); const local = useLocal({ @@ -47,9 +47,13 @@ export const TypeRichText: React.FC = ({ tab: 0, active: "General", }); - useEffect(() => {}, [fm.data?.[name]]); - useEffect(() => {}, []); + useEffect(() => { + try { + fm.fields[name] = { ...fm.fields?.[name], ...input }; + fm.render(); + } catch (e) {} + }, []); const MenuBar = () => { const { editor } = useCurrentEditor(); if (disabled) return <>; @@ -766,11 +770,18 @@ export const TypeRichText: React.FC = ({ }, }), ]; - + const AfterEditor = () => { + const { editor } = useCurrentEditor(); + if (!editor) return <>; + return ; + }; return (
{ + if (e) input.ref = e; + }} className={cx( - "flex flex-col relative bg-white border border-gray-300 rounded-md w-full richtext-field", + "flex flex-col relative bg-white rounded-md w-full richtext-field", css` .tiptap h1 { font-size: 1.4rem !important; @@ -812,9 +823,13 @@ export const TypeRichText: React.FC = ({ onUpdate={({ editor }) => { fm.data[name] = editor.getHTML(); fm.render(); + if (typeof onChange === "function") { + onChange(fm.data[name]); + } }} - content={fm.data[name]} + content={input.value} editable={!disabled} + slotAfter={} >
); diff --git a/components/form/field/TypeTag.tsx b/components/form/field/TypeTag.tsx index 077652c..bc1b2e9 100644 --- a/components/form/field/TypeTag.tsx +++ b/components/form/field/TypeTag.tsx @@ -48,6 +48,9 @@ export const TypeTag: React.FC = ({ setTags(updatedTags); setEditingIndex(null); // Keluar dari mode edit setTempValue(""); // Reset nilai sementara + if (typeof onChange === "function") { + onChange(tags); + } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (!disabled) return; @@ -55,8 +58,14 @@ export const TypeTag: React.FC = ({ e.preventDefault(); setTags([...tags, inputValue]); setInputValue(""); + if (typeof onChange === "function") { + onChange(tags); + } } else if (e.key === "Backspace" && !inputValue && tags.length > 0) { setTags(tags.slice(0, -1)); + if (typeof onChange === "function") { + onChange(tags); + } } }; const handleFocusTag = (index: number) => { @@ -70,10 +79,13 @@ export const TypeTag: React.FC = ({ const removeTag = (index: number) => { if (!disabled) return; setTags(tags.filter((_, i) => i !== index)); + if (typeof onChange === "function") { + onChange(tags); + } }; return ( -
+
{tags.map((tag, index) => (
{ diff --git a/components/partials/Sidebar.tsx b/components/partials/Sidebar.tsx index 56e9386..1ddb16d 100644 --- a/components/partials/Sidebar.tsx +++ b/components/partials/Sidebar.tsx @@ -69,7 +69,7 @@ const SidebarTree: React.FC = ({ data, minimaze, mini }) => { }, [isParentActive]); const itemStyle = { paddingLeft: - !hasChildren && !depth ? "16px" : !mini ? `${depth * 16}px` : "0px", + !hasChildren && !depth ? "13px" : !mini ? `${depth * 16}px` : "0px", }; return ( @@ -248,7 +248,7 @@ const SidebarTree: React.FC = ({ data, minimaze, mini }) => { ? " bg-layer font-normal vertical-rounded-tab" : " bg-layer text-primary font-bold vertical-rounded-tab" : "text-white", - !depth && !hasChildren ? "px-3" : "", + !depth && !hasChildren ? "px-2" : "", css` & > span { white-space: wrap !important; diff --git a/components/tablelist/TableList.tsx b/components/tablelist/TableList.tsx index 21b0864..26e54c9 100644 --- a/components/tablelist/TableList.tsx +++ b/components/tablelist/TableList.tsx @@ -807,6 +807,100 @@ export const Pagination: React.FC = ({
); }; +export const PaginationPage: React.FC = ({ + onNextPage, + onPrevPage, + disabledNextPage, + disabledPrevPage, + page, + count, + list, + take, + setPage, + onChangePage, +}) => { + const local = useLocal({ + page: 1 as any, + pagination: [] as any, + }); + useEffect(() => { + local.page = page; + local.pagination = getPagination(page, Math.ceil(count / take)); + local.render(); + }, [page, count]); + return ( +
+
+
+
{ + if (!disabledPrevPage) { + onPrevPage(); + } + }} + className={cx( + "flex flex-row items-center gap-x-2 justify-center rounded-full p-2 text-md", + disabledPrevPage + ? "text-gray-200 border-gray-200 border " + : "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border " + )} + > + +
+
+
+ +
+
+
{ + if (!disabledNextPage) { + onNextPage(); + } + }} + className={cx( + "flex flex-row items-center gap-x-2 justify-center rounded-full p-2 ", + disabledNextPage + ? "text-gray-200 border-gray-200 border" + : "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border " + )} + > + +
+
+
+
+ ); +}; const getPagination = (currentPage: number, totalPages: number) => { const pagination: { label: string; active: boolean }[] = []; diff --git a/components/ui/EditorProvider.tsx b/components/ui/EditorProvider.tsx new file mode 100644 index 0000000..2848adb --- /dev/null +++ b/components/ui/EditorProvider.tsx @@ -0,0 +1,13 @@ +import React, { createContext, FC, useContext } from "react"; + +const EditorContext = createContext(null); + +export const EditorProvider: FC = ({ editor, children }) => { + return ( + {children} + ); +}; + +export const useCurrentEditor = () => { + return useContext(EditorContext); +}; diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 7447bee..7348129 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -9,7 +9,7 @@ const btn = cva( " text-white px-4 py-1.5 group active-menu-icon relative flex items-stretch justify-center p-0.5 text-center border border-transparent text-white enabled:hover:bg-cyan-800 rounded-md" ); const buttonVariants = cva( - "cursor-pointer px-4 py-1.5 group relative flex items-stretch justify-center p-0.5 text-center inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + "cursor-pointer px-4 py-1.5 relative flex items-stretch justify-center p-0.5 text-center inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { diff --git a/utils/siteurl.ts b/utils/siteurl.ts index ac310ce..1a7d4a0 100644 --- a/utils/siteurl.ts +++ b/utils/siteurl.ts @@ -1,6 +1,6 @@ import dotenv from "dotenv"; dotenv.config(); export const siteurl = (param: string) => { - if (param.startsWith("http")) return param; + if (param && param.startsWith("http")) return param; return `${process.env.NEXT_PUBLIC_BASE_URL + param}`; };