From fa6ceae3af99281fe3ea19436ad43c390a82bf91 Mon Sep 17 00:00:00 2001 From: rizky Date: Sat, 10 Aug 2024 21:19:04 -0700 Subject: [PATCH] fix --- comps/form/field/type/FilePreview.tsx | 46 ++-- comps/form/field/type/TypeUploadMulti.tsx | 148 ++++++++++--- comps/form/typings.ts | 7 +- comps/ui/tooltip.tsx | 251 ++++++++++++++++++++++ gen/prop/gen_prop_fields.ts | 2 + 5 files changed, 398 insertions(+), 56 deletions(-) create mode 100755 comps/ui/tooltip.tsx diff --git a/comps/form/field/type/FilePreview.tsx b/comps/form/field/type/FilePreview.tsx index aaf333e..fdd19a6 100755 --- a/comps/form/field/type/FilePreview.tsx +++ b/comps/form/field/type/FilePreview.tsx @@ -3,10 +3,10 @@ import { ReactElement } from "react"; export const ThumbPreview = ({ url, - del, + options, }: { url: string; - del: ReactElement; + options: ReactElement; }) => { const file = getFileName(url); if (typeof file === "string") return; @@ -25,16 +25,23 @@ export const ThumbPreview = ({ font-size: 14px; font-weight: black; padding: 3px 7px; - height: 26px; + + width: 60px; + height: 60px; + + &:hover { + border: 1px solid #1c4ed8; + outline: 1px solid #1c4ed8; + } `, - "c-flex c-items-center" + "c-flex c-justify-center c-items-center" )} + onClick={() => { + let _url = siteurl(url || ""); + window.open(_url, "_blank"); + }} > {file.extension} - -
- -
); @@ -44,8 +51,17 @@ export const ThumbPreview = ({ is_image = true; content = ( { + let _url = siteurl(url || ""); + window.open(_url, "_blank"); + }} className={cx( "c-rounded-md", + css` + &:hover { + outline: 2px solid #1c4ed8; + } + `, css` width: 60px; height: 60px; @@ -75,21 +91,11 @@ export const ThumbPreview = ({
{ - let _url = siteurl(url || ""); - window.open(_url, "_blank"); - }} > {content} - {del} + {options}
)} diff --git a/comps/form/field/type/TypeUploadMulti.tsx b/comps/form/field/type/TypeUploadMulti.tsx index 132fc13..7f1ceb6 100755 --- a/comps/form/field/type/TypeUploadMulti.tsx +++ b/comps/form/field/type/TypeUploadMulti.tsx @@ -1,11 +1,12 @@ import { useLocal } from "@/utils/use-local"; +import { Spinner } from "lib/comps/ui/field-loading"; +import { Tooltip } from "lib/comps/ui/tooltip"; import get from "lodash.get"; -import { Trash2, Upload } from "lucide-react"; +import { Check, Trash2, Upload } from "lucide-react"; import { ChangeEvent, FC } from "react"; import { FMLocal, FieldLocal, FieldProp } from "../../typings"; +import { ThumbPreview } from "./FilePreview"; import { PropTypeInput } from "./TypeInput"; -import { FilePreview, ThumbPreview } from "./FilePreview"; -import { Spinner } from "lib/comps/ui/field-loading"; const w = window as unknown as { serverurl: string; }; @@ -30,6 +31,11 @@ export const FieldUploadMulti: FC<{ style: "inline" as "inline" | "full", }); + const cover = { + field: field.prop.upload?.cover_field || "", + text: field.prop.upload?.cover_text, + }; + const parse_list = () => { let list: string[] = []; if (value.startsWith("[")) { @@ -114,46 +120,118 @@ export const FieldUploadMulti: FC<{ ` )} > - {list.map((value, idx) => { - return ( -
{ - e.stopPropagation(); - e.preventDefault(); - }} - > -
- { - e.preventDefault(); - e.stopPropagation(); - if (confirm("Remove this file ?")) { - list.splice(idx, 1); - fm.data[field.name] = JSON.stringify(list); - fm.render(); + {!isEditor && + list.map((value, idx) => { + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + }} + > +
+ {fm.data[cover.field] === value && ( +
- + {cover.text}
- } - /> + )} + +
{ + e.preventDefault(); + e.stopPropagation(); + if (confirm("Remove this file ?")) { + list.splice(idx, 1); + fm.data[field.name] = JSON.stringify(list); + fm.render(); + } + }} + className={cx( + "c-flex c-flex-row c-items-center c-px-1 c-rounded c-bg-white c-cursor-pointer hover:c-bg-red-100 transition-all", + css` + border: 1px solid red; + width: 25px; + height: 25px; + ` + )} + > + +
+ + {cover.field && ( + +
{ + e.preventDefault(); + e.stopPropagation(); + + if (fm.data[cover.field] === value) { + fm.data[cover.field] = ""; + } else { + fm.data[cover.field] = value; + } + fm.render(); + }} + className={cx( + "c-flex c-flex-row c-items-center c-px-1 c-rounded c-bg-white c-cursor-pointer hover:c-bg-blue-100 transition-all", + css` + border: 1px solid black; + width: 25px; + height: 25px; + ` + )} + > + {value === fm.data[cover.field] && ( + <> + + + )} +
+
+ )} +
+ } + /> +
-
- ); - })} + ); + })} {input.uploading.size > 0 && (
void; + delay?: number; + asChild?: boolean; +} + +export function useTooltip({ + initialOpen = false, + placement = "top", + open: controlledOpen, + onOpenChange: setControlledOpen, + delay = 1000, + offset: tooltipOffset, +}: TooltipOptions = {}) { + const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen); + + const arrowRef = React.useRef(null); + const open = controlledOpen ?? uncontrolledOpen; + const setOpen = setControlledOpen ?? setUncontrolledOpen; + + const data = useFloating({ + placement, + open, + onOpenChange: setOpen, + whileElementsMounted: autoUpdate, + middleware: [ + offset(typeof tooltipOffset === "undefined" ? 5 : tooltipOffset), + flip({ + fallbackAxisSideDirection: "start", + padding: 5, + }), + shift({ padding: 5 }), + arrow({ element: arrowRef }), + ], + }); + + const context = data.context; + + const hover = useHover(context, { + move: false, + delay, + enabled: controlledOpen == null, + }); + const focus = useFocus(context, { + enabled: controlledOpen == null, + }); + const dismiss = useDismiss(context); + const role = useRole(context, { role: "tooltip" }); + + const interactions = useInteractions([hover, focus, dismiss, role]); + + return React.useMemo( + () => ({ + open, + setOpen, + arrowRef, + ...interactions, + ...data, + }), + [open, setOpen, arrowRef, interactions, data] + ); +} + +type ContextType = ReturnType | null; + +const TooltipContext = React.createContext(null); + +export const useTooltipContext = () => { + const context = React.useContext(TooltipContext); + + if (context == null) { + throw new Error("Tooltip components must be wrapped in "); + } + + return context; +}; + +export function Tooltip({ + children, + content, + className, + onClick, + onPointerEnter, + onPointerLeave, + asChild, + ...options +}: { + children: React.ReactNode; + content: React.ReactNode; + className?: string; + onClick?: (e: React.MouseEvent) => void; + onPointerEnter?: (e: React.MouseEvent) => void; + onPointerLeave?: (e: React.MouseEvent) => void; +} & TooltipOptions) { + // This can accept any props as options, e.g. `placement`, + // or other positioning options. + const tooltip = useTooltip(options); + + if (!content) + return ( +
+ {children} +
+ ); + + return ( + + + {children} + + + {content} + + + + ); +} + +function TooltipArrow() { + const context = useTooltipContext(); + const { x: arrowX, y: arrowY } = context.middlewareData.arrow || { + x: 0, + y: 0, + }; + const staticSide = mapPlacementSideToCSSProperty(context.placement) as string; + + return ( +
+ ); +} + +function mapPlacementSideToCSSProperty(placement: Placement) { + const staticPosition = placement.split("-")[0]; + + const staticSide = { + top: "bottom", + right: "left", + bottom: "top", + left: "right", + }[staticPosition]; + + return staticSide; +} + +export const TooltipTrigger = React.forwardRef< + HTMLElement, + React.HTMLProps & { asChild?: boolean } +>(function TooltipTrigger({ children, asChild = false, ...props }, propRef) { + const context = useTooltipContext(); + const childrenRef = (children as any).ref; + const ref = useMergeRefs([context.refs.setReference, propRef, childrenRef]); + + // `asChild` allows the user to pass any element as the anchor + if (asChild && React.isValidElement(children)) { + return React.cloneElement( + children, + context.getReferenceProps({ + ref, + ...props, + ...children.props, + "data-state": context.open ? "open" : "closed", + }) + ); + } + + return ( +
+ {children} +
+ ); +}); + +export const TooltipContent = React.forwardRef< + HTMLDivElement, + React.HTMLProps +>(function TooltipContent(props, propRef) { + const context = useTooltipContext(); + const ref = useMergeRefs([context.refs.setFloating, propRef]); + + if (!context.open) return null; + + return ( + +
+ + ); +}); diff --git a/gen/prop/gen_prop_fields.ts b/gen/prop/gen_prop_fields.ts index 2d0bb52..9bc4edd 100755 --- a/gen/prop/gen_prop_fields.ts +++ b/gen/prop/gen_prop_fields.ts @@ -67,6 +67,7 @@ const get_layer = async ( table: string ) => { const { cols, rels } = await loadSingle(id_site, table); + const options = []; if (cols) { for (const [k, v] of Object.entries(cols)) { @@ -125,6 +126,7 @@ const loadSingle = async (id_site: string, table: string) => { const idb_key = `${id_site}-${table}`; let cached_raw = localStorage.getItem(ls_key); let cached_keys: string[] = []; + if (cached_raw) { try { let res = JSON.parse(cached_raw);