lib
This commit is contained in:
parent
bec966b3fa
commit
13198aea73
|
|
@ -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<any> = ({
|
||||
fm,
|
||||
|
|
@ -26,7 +27,9 @@ export const Field: React.FC<any> = ({
|
|||
suffix,
|
||||
}) => {
|
||||
let result = null;
|
||||
|
||||
const field = useLocal({
|
||||
focus: false,
|
||||
});
|
||||
const suffixRef = useRef<HTMLDivElement | null>(null);
|
||||
const prefixRef = useRef<HTMLDivElement | null>(null);
|
||||
const is_disable = fm.mode === "view" ? true : disabled;
|
||||
|
|
@ -58,7 +61,25 @@ export const Field: React.FC<any> = ({
|
|||
<div
|
||||
className={cx(
|
||||
"flex",
|
||||
style === "inline" ? "flex-row gap-x-1" : "flex-col"
|
||||
style === "inline" ? "flex-row gap-x-1" : "flex-col",
|
||||
css`
|
||||
.field input:focus {
|
||||
outline: 0px !important;
|
||||
border: 0px !important;
|
||||
outline-offset: 0px !important;
|
||||
--tw-ring-color: transparent !important;
|
||||
}
|
||||
.field textarea:focus {
|
||||
outline: 0px !important;
|
||||
border: 0px !important;
|
||||
outline-offset: 0px !important;
|
||||
--tw-ring-color: transparent !important;
|
||||
}
|
||||
.field input {
|
||||
border: 0px !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
`
|
||||
)}
|
||||
>
|
||||
{!hidden_label ? (
|
||||
|
|
@ -78,22 +99,34 @@ export const Field: React.FC<any> = ({
|
|||
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 && (
|
||||
<div
|
||||
ref={prefixRef}
|
||||
// ref={prefixRef}
|
||||
className={cx(
|
||||
"absolute left-[1px] px-1 py-1 bg-gray-200/50 border border-gray-100 items-center flex flex-row flex-grow rounded-l-md",
|
||||
"px-1 py-1 items-center flex flex-row flex-grow rounded-l-md h-full",
|
||||
css`
|
||||
height: 2.13rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
`,
|
||||
is_disable ? "bg-gray-100" : "bg-gray-200/50"
|
||||
is_disable ? "" : ""
|
||||
)}
|
||||
>
|
||||
{before}
|
||||
|
|
@ -151,7 +184,7 @@ export const Field: React.FC<any> = ({
|
|||
onLoad={onLoad}
|
||||
placeholder={placeholder}
|
||||
disabled={is_disable}
|
||||
on_change={onChange}
|
||||
onChange={onChange}
|
||||
className={className}
|
||||
/>
|
||||
</>
|
||||
|
|
@ -163,8 +196,8 @@ export const Field: React.FC<any> = ({
|
|||
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<any> = ({
|
|||
name={name}
|
||||
disabled={is_disable}
|
||||
className={className}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</>
|
||||
) : ["tag"].includes(type) ? (
|
||||
|
|
@ -184,6 +218,7 @@ export const Field: React.FC<any> = ({
|
|||
name={name}
|
||||
disabled={is_disable}
|
||||
className={className}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
|
@ -196,6 +231,10 @@ export const Field: React.FC<any> = ({
|
|||
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<any> = ({
|
|||
)}
|
||||
{after && (
|
||||
<div
|
||||
ref={suffixRef}
|
||||
// ref={suffixRef}
|
||||
className={cx(
|
||||
"absolute right-[1px] px-1 py-1 items-center flex flex-row flex-grow rounded-r-md",
|
||||
css`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import { FC, useEffect } from "react";
|
||||
|
||||
export const InitEditor: FC<any> = ({ local, editor }) => {
|
||||
useEffect(() => {
|
||||
local.editor = editor;
|
||||
local.render();
|
||||
}, []);
|
||||
return <div></div>;
|
||||
};
|
||||
|
|
@ -57,12 +57,16 @@ export const FieldCheckbox: FC<any> = ({
|
|||
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 (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const TypeColor: React.FC<any> = ({
|
|||
value,
|
||||
onChangePicker,
|
||||
onClose,
|
||||
onChange,
|
||||
}) => {
|
||||
const meta = useLocal({
|
||||
originalValue: "",
|
||||
|
|
|
|||
|
|
@ -70,12 +70,7 @@ export const TypeInput: React.FC<any> = ({
|
|||
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<any> = ({
|
|||
onRatingChange={(e) => {
|
||||
fm.data[name] = getNumber(e);
|
||||
fm.render();
|
||||
if (typeof onChange === "function") {
|
||||
onChange(fm.data[name]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -160,6 +158,9 @@ export const TypeInput: React.FC<any> = ({
|
|||
update={(val) => {
|
||||
fm.data[name] = val;
|
||||
fm.render();
|
||||
if (typeof onChange === "function") {
|
||||
onChange(fm.data[name]);
|
||||
}
|
||||
}}
|
||||
onOpen={() => {
|
||||
input.open = true;
|
||||
|
|
|
|||
|
|
@ -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<any> = ({
|
||||
name,
|
||||
|
|
@ -35,10 +31,14 @@ export const TypeRichText: React.FC<any> = ({
|
|||
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<any> = ({
|
|||
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<any> = ({
|
|||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const AfterEditor = () => {
|
||||
const { editor } = useCurrentEditor();
|
||||
if (!editor) return <></>;
|
||||
return <InitEditor editor={editor} local={local} />;
|
||||
};
|
||||
return (
|
||||
<div
|
||||
ref={(e) => {
|
||||
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<any> = ({
|
|||
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={<AfterEditor />}
|
||||
></EditorProvider>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ export const TypeTag: React.FC<any> = ({
|
|||
setTags(updatedTags);
|
||||
setEditingIndex(null); // Keluar dari mode edit
|
||||
setTempValue(""); // Reset nilai sementara
|
||||
if (typeof onChange === "function") {
|
||||
onChange(tags);
|
||||
}
|
||||
};
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!disabled) return;
|
||||
|
|
@ -55,8 +58,14 @@ export const TypeTag: React.FC<any> = ({
|
|||
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<any> = ({
|
|||
const removeTag = (index: number) => {
|
||||
if (!disabled) return;
|
||||
setTags(tags.filter((_, i) => i !== index));
|
||||
if (typeof onChange === "function") {
|
||||
onChange(tags);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center border border-gray-300 rounded-md flex-grow ">
|
||||
<div className="flex flex-wrap items-center rounded-md flex-grow ">
|
||||
{tags.map((tag, index) => (
|
||||
<div
|
||||
key={index}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export const FieldUploadSingle: FC<{
|
|||
<>
|
||||
<div
|
||||
className={cx(
|
||||
"hover:bg-gray-50 border border-gray-300 text-gray-900 text-md rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-1.5 ",
|
||||
"hover:bg-gray-50 text-gray-900 text-md rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-1.5 ",
|
||||
css`
|
||||
input[type="file"],
|
||||
input[type="file"]::-webkit-file-upload-button {
|
||||
|
|
|
|||
|
|
@ -385,7 +385,7 @@ export const Typeahead: FC<{
|
|||
<div
|
||||
className={cx(
|
||||
local.mode === "single" ? "cursor-pointer" : "cursor-text",
|
||||
"text-black flex relative flex-wrap py-0 items-center w-full h-full flex-1 rounded-md border border-gray-300 overflow-hidden ",
|
||||
"text-black flex relative flex-wrap py-0 items-center w-full h-full flex-1 ",
|
||||
className
|
||||
)}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
|||
}, [isParentActive]);
|
||||
const itemStyle = {
|
||||
paddingLeft:
|
||||
!hasChildren && !depth ? "16px" : !mini ? `${depth * 16}px` : "0px",
|
||||
!hasChildren && !depth ? "13px" : !mini ? `${depth * 16}px` : "0px",
|
||||
};
|
||||
return (
|
||||
<React.Fragment key={item.href || item.title || index}>
|
||||
|
|
@ -248,7 +248,7 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ 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;
|
||||
|
|
|
|||
|
|
@ -807,6 +807,100 @@ export const Pagination: React.FC<any> = ({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
export const PaginationPage: React.FC<any> = ({
|
||||
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 (
|
||||
<div className=" tbl-pagination text-sm bottom-0 right-0 w-full grid grid-cols-1 gap-4 justify-center text-sm bg-white pt-2">
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<div className="flex items-center flex-row gap-x-2 sm:mb-0 text-sm">
|
||||
<div
|
||||
onClick={() => {
|
||||
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 "
|
||||
)}
|
||||
>
|
||||
<HiChevronLeft />
|
||||
</div>
|
||||
<div className="flex flex-row justify-center">
|
||||
<div>
|
||||
<nav
|
||||
className="isolate inline-flex -space-x-px flex flex-row items-center gap-x-2"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
{local.pagination.map((e: any, idx: number) => {
|
||||
return (
|
||||
<div
|
||||
key={"page_" + idx}
|
||||
onClick={() => {
|
||||
if (e?.label !== "...") {
|
||||
local.page = getNumber(e?.label);
|
||||
local.render();
|
||||
onChangePage(local.page - 1);
|
||||
setPage(local.page - 1);
|
||||
}
|
||||
}}
|
||||
className={cx(
|
||||
"text-md px-2.5 py-1",
|
||||
e.active
|
||||
? "relative z-10 inline-flex items-center bg-primary font-semibold text-white rounded-full"
|
||||
: e?.label === "..."
|
||||
? "relative z-10 inline-flex items-center font-semibold text-gray-800 rounded-full"
|
||||
: "cursor-pointer relative z-10 inline-flex items-center hover:bg-gray-100 font-semibold text-gray-800 rounded-full"
|
||||
)}
|
||||
>
|
||||
{e?.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
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 "
|
||||
)}
|
||||
>
|
||||
<HiChevronRight className="text-md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getPagination = (currentPage: number, totalPages: number) => {
|
||||
const pagination: { label: string; active: boolean }[] = [];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import React, { createContext, FC, useContext } from "react";
|
||||
|
||||
const EditorContext = createContext(null);
|
||||
|
||||
export const EditorProvider: FC<any> = ({ editor, children }) => {
|
||||
return (
|
||||
<EditorContext.Provider value={editor}>{children}</EditorContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useCurrentEditor = () => {
|
||||
return useContext(EditorContext);
|
||||
};
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue