pankod/refine

View on GitHub
packages/inferencer/src/components/shared-code-viewer/index.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React, { SVGProps } from "react";
import Highlight, { defaultProps } from "prism-react-renderer";
import theme from "prism-react-renderer/themes/vsDark";

import { CreateInferencerConfig } from "../../types";
import { prettierFormat } from "../../utilities";

export const SharedCodeViewer: CreateInferencerConfig["codeViewerComponent"] =
  ({ code: rawCode, loading }) => {
    const code = React.useMemo(() => {
      return prettierFormat(rawCode ?? "");
    }, [rawCode]);

    const [settled, setSettled] = React.useState(false);
    const [isModalVisible, setIsModalVisible] = React.useState(false);
    const [isVisible, setIsVisible] = React.useState(false);
    const [isColumn, setIsColumn] = React.useState(false);
    const [isModalButtonHover, setIsModalButtonHover] = React.useState(false);

    // Settled Check
    React.useEffect(() => {
      if (!loading) {
        const timeout = setTimeout(() => {
          setSettled(true);
        }, 300);

        return () => {
          clearTimeout(timeout);
        };
      }

      return () => undefined;
    }, [loading]);

    // Visibility Check
    React.useEffect(() => {
      if (typeof window !== "undefined") {
        const mediaQuery = window.matchMedia("(max-width: 449px)");
        if (mediaQuery.matches) {
          setIsVisible(false);
        } else {
          setIsVisible(true);
        }

        const handleResize = () => {
          if (mediaQuery.matches) {
            setIsVisible(false);
          } else {
            setIsVisible(true);
          }
        };

        window.addEventListener("resize", handleResize);

        return () => {
          window.removeEventListener("resize", handleResize);
        };
      }

      return () => undefined;
    }, []);

    // Flex Direction Check
    React.useEffect(() => {
      if (typeof window !== "undefined") {
        const mediaQuery = window.matchMedia("(max-width: 1280px)");
        if (mediaQuery.matches) {
          setIsColumn(true);
        } else {
          setIsColumn(false);
        }

        const handleResize = () => {
          if (mediaQuery.matches) {
            setIsColumn(true);
          } else {
            setIsColumn(false);
          }
        };

        window.addEventListener("resize", handleResize);

        return () => {
          window.removeEventListener("resize", handleResize);
        };
      }

      return () => undefined;
    }, []);

    if (code && !loading) {
      return (
        <>
          {isVisible && (
            <div
              className="refine-inferencer--code-viewer"
              style={{
                position: "sticky",
                bottom: "24px",
                paddingTop: "24px",
                left: 0,
                right: 0,
                width: "100%",
                zIndex: 10,
                display: "flex",
                justifyContent: "center",
                transition: "all 0.2s ease",
                opacity: settled && !isModalVisible ? 1 : 0,
                transform:
                  settled && !isModalVisible
                    ? "translateY(0)"
                    : "translateY(100px)",
              }}
            >
              <div
                style={{
                  width: "calc(100% - calc(64px * 2))",
                  maxWidth: "1080px",
                  padding: "20px",
                  backgroundColor: "#1A1A1A",
                  boxShadow:
                    "0px 4px 16px -4px rgba(0, 0, 0, 0.5), 0px 8px 32px -8px rgba(0, 0, 0, 0.35)",
                  borderRadius: "16px",
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "space-between",
                  flexDirection: isColumn ? "column" : "row",
                  gap: "12px",
                }}
              >
                <div
                  style={{
                    display: "flex",
                    alignItems: "flex-start",
                    gap: "8px",
                  }}
                >
                  <div>
                    <InfoIcon />
                  </div>
                  <div
                    style={{
                      fontSize: "14px",
                      lineHeight: "20px",
                      color: "#ffffff",
                    }}
                  >
                    <p
                      style={{
                        padding: 0,
                        margin: 0,
                      }}
                    >
                      Most of the page code is auto-generated by the{" "}
                      <span
                        style={{
                          textDecoration: "underline",
                        }}
                      >
                        Inferencer
                      </span>{" "}
                      feature, based on your backend data structure.
                    </p>
                    <p
                      style={{
                        padding: 0,
                        margin: 0,
                      }}
                    >
                      While this is an excellent way to experiment with refine,{" "}
                      <span style={{ fontWeight: 600 }}>
                        it&apos;s not intended to be used on production.
                      </span>
                    </p>
                  </div>
                </div>
                <div>
                  <button
                    onPointerEnter={() => setIsModalButtonHover(true)}
                    onPointerLeave={() => setIsModalButtonHover(false)}
                    onClick={() => setIsModalVisible(true)}
                    style={{
                      appearance: "none",
                      border: "none",
                      padding: "10px 16px",
                      borderRadius: "4px",
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "center",
                      background: "#0080FF",
                      color: "#ffffff",
                      fontSize: "14px",
                      lineHeight: "20px",
                      fontWeight: 600,
                      gap: "8px",
                      cursor: "pointer",
                      transition: "all 0.2s ease",
                      transform: isModalButtonHover
                        ? "scale(1.025)"
                        : undefined,
                      filter: isModalButtonHover
                        ? "brightness(1.1)"
                        : undefined,
                    }}
                  >
                    <OpenIcon
                      style={{
                        flexShrink: 0,
                      }}
                    />
                    <span>Show the auto-generated code</span>
                  </button>
                </div>
              </div>
            </div>
          )}
          <CodeModal
            visible={isModalVisible}
            onClose={() => setIsModalVisible(false)}
            code={code}
          />
        </>
      );
    }

    return null;
  };

const CodeModal = ({
  visible,
  onClose,
  code = "",
}: {
  visible: boolean;
  onClose: () => void;
  code?: string;
}) => {
  const modalRef = React.useRef<HTMLDivElement>(null);

  const [isCopied, setIsCopied] = React.useState(false);
  const [isCopyHover, setIsCopyHover] = React.useState(false);
  const [isCloseHover, setIsCloseHover] = React.useState(false);
  const [isLearnMoreHover, setIsLearnMoreHover] = React.useState(false);

  // On Outside Click
  React.useEffect(() => {
    if (typeof document !== "undefined") {
      const onOutsideClick = (event: PointerEvent) => {
        if (
          modalRef.current &&
          !modalRef.current.contains(event.target as Node)
        ) {
          onClose();
        }
      };

      document.addEventListener("pointerdown", onOutsideClick);

      return () => {
        document.removeEventListener("pointerdown", onOutsideClick);
      };
    }

    return () => undefined;
  }, [onClose]);

  // onCopy Handler
  const onCopy = () => {
    if (typeof navigator !== "undefined") {
      navigator.clipboard.writeText(code);
      setIsCopied(true);
      setTimeout(() => {
        setIsCopied(false);
      }, 1000);
    }
  };

  const title = (
    <div
      style={{
        display: "flex",
        justifyContent: "space-between",
        alignItems: "center",
      }}
    >
      <div
        style={{
          fontWeight: 700,
          fontSize: "20px",
          lineHeight: "32px",
          color: "#0D0D0D",
        }}
      >
        Auto-generated code by Inferencer
      </div>
      <button
        onClick={onClose}
        onPointerEnter={() => setIsCloseHover(true)}
        onPointerLeave={() => setIsCloseHover(false)}
        style={{
          flexShrink: 0,
          appearance: "none",
          border: "none",
          background: "none",
          padding: 0,
          margin: 0,
          outline: "none",
          borderRadius: "50px",
          width: "32px",
          height: "32px",
          cursor: "pointer",
          transition: "all 0.15s ease",
          transform: isCloseHover ? "scale(1.05)" : undefined,
          filter: isCloseHover ? "brightness(0.8)" : undefined,
        }}
      >
        <CloseIcon />
      </button>
    </div>
  );

  const highlight = (
    <div
      style={{
        fontSize: "13px",
        borderRadius: "8px",
        flex: "1",
        overflow: "scroll",
        background: "#1E1E1E",
      }}
    >
      <Highlight {...defaultProps} theme={theme} code={code} language="tsx">
        {({ className, style, tokens, getLineProps, getTokenProps }) => (
          <pre
            className={className}
            style={{
              ...style,
              padding: "14px 14px 14px 14px",
              margin: "0",
              width: "100%",
              boxSizing: "border-box",
            }}
          >
            {tokens.map((line, i) => (
              <div
                key={i}
                {...getLineProps({
                  line,
                  key: i,
                })}
              >
                {line.map((token, key) => (
                  <span
                    key={key}
                    {...getTokenProps({
                      token,
                      key,
                    })}
                  />
                ))}
              </div>
            ))}
          </pre>
        )}
      </Highlight>
    </div>
  );

  const buttons = (
    <div
      style={{
        display: "flex",
        justifyContent: "flex-start",
        alignItems: "flex-end",
        gap: "16px",
      }}
    >
      <button
        onPointerEnter={() => setIsCopyHover(true)}
        onPointerLeave={() => setIsCopyHover(false)}
        onClick={onCopy}
        style={{
          appearance: "none",
          height: "40px",
          border: "none",
          padding: "10px 16px",
          borderRadius: "4px",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          background: "#0080FF",
          color: "#ffffff",
          fontSize: "14px",
          lineHeight: "20px",
          fontWeight: 600,
          gap: "8px",
          cursor: "pointer",
          position: "relative",
          overflow: "hidden",
          transition: "filter 0.2s ease",
          transform: isCopyHover ? "scale(1.025)" : "scale(1)",
          filter: isCopyHover ? "brightness(1.1)" : undefined,
        }}
      >
        <ClipboardIcon
          style={{
            flexShrink: 0,
            marginTop: "-2px",
            marginBottom: "-2px",
          }}
        />
        <span>Copy Generated Code</span>
        <div
          style={{
            position: "absolute",
            width: "100%",
            height: "100%",
            top: 0,
            left: 0,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            background: "#0080FF",
            transition: "all 0.2s ease",
            transform: isCopied ? "translateY(0)" : "translateY(40px)",
          }}
        >
          <ClipboardIcon
            style={{
              flexShrink: 0,
              marginTop: "-2px",
              marginBottom: "-2px",
            }}
          />
          <span>Copied!</span>
        </div>
      </button>
      <a
        onPointerEnter={() => setIsLearnMoreHover(true)}
        onPointerLeave={() => setIsLearnMoreHover(false)}
        href="https://refine.dev/docs/packages/documentation/inferencer"
        target="_blank"
        rel="noreferrer"
        style={{
          appearance: "none",
          textDecoration: "none",
          border: "none",
          padding: "10px 16px",
          borderRadius: "4px",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          background: "rgba(0, 128, 255, 0.1)",
          color: "#0080FF",
          fontSize: "14px",
          lineHeight: "20px",
          fontWeight: 600,
          gap: "8px",
          cursor: "pointer",
          transition: "all 0.2s ease",
          transform: isLearnMoreHover ? "scale(1.025)" : undefined,
          filter: isLearnMoreHover ? "brightness(1.1)" : undefined,
        }}
      >
        <OpenIcon
          style={{
            flexShrink: 0,
          }}
        />
        <span>Learn more about inferencer</span>
      </a>
    </div>
  );

  return (
    <div
      className="refine-inferencer--code-viewer-modal"
      style={{
        position: "fixed",
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        zIndex: 9999,
        backgroundColor: "rgba(0, 0, 0, 0.5)",
        transition: "all 0.2s ease",
        opacity: visible ? 1 : 0,
        pointerEvents: visible ? "all" : "none",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <div
        ref={modalRef}
        style={{
          transform: visible
            ? "scale(1) translateY(0px)"
            : "scale(0) translateY(-200px)",
          transition: "all 0.25s cubic-bezier(.35,1.29,.81,1.08)",
          transitionDelay: "0.25",
          width: "calc(100% - calc(32px * 2))",
          height: "calc(100% - calc(32px * 2))",
          backgroundColor: "#fff",
          maxWidth: "640px",
          maxHeight: "720px",
          borderRadius: "8px",
          padding: "16px",
        }}
      >
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            height: "100%",
            width: "100%",
            gap: "16px",
          }}
        >
          {title}
          {highlight}
          {buttons}
        </div>
      </div>
    </div>
  );
};

const InfoIcon = (props: SVGProps<SVGSVGElement>) => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width={20}
    height={20}
    fill="none"
    {...props}
  >
    <path
      fill="#0080FF"
      fillRule="evenodd"
      d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10Zm0-15a1.25 1.25 0 1 0 0 2.5A1.25 1.25 0 0 0 10 5Zm0 10c.69 0 1.25-.56 1.25-1.25V10a1.25 1.25 0 1 0-2.5 0v3.75c0 .69.56 1.25 1.25 1.25Z"
      clipRule="evenodd"
    />
  </svg>
);

const OpenIcon = (props: SVGProps<SVGSVGElement>) => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width={16}
    height={16}
    fill="none"
    {...props}
  >
    <path
      fill="currentColor"
      d="M5 2a1 1 0 0 1 0 2H4v8h8v-1a1 1 0 1 1 2 0v1a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h1Z"
    />
    <path
      fill="currentColor"
      d="M9 2a1 1 0 0 0 0 2h1.586L6.293 8.293a1 1 0 0 0 1.414 1.414L12 5.414V7a1 1 0 1 0 2 0V3a1 1 0 0 0-1-1H9Z"
    />
  </svg>
);

const CloseIcon = (props: SVGProps<SVGSVGElement>) => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width={32}
    height={32}
    fill="none"
    {...props}
  >
    <path
      fill="#A6A6A6"
      fillRule="evenodd"
      d="M16 32C7.163 32 0 24.837 0 16S7.163 0 16 0s16 7.163 16 16-7.163 16-16 16Zm-2.586-21.414a2 2 0 1 0-2.828 2.828L13.172 16l-2.586 2.586a2 2 0 1 0 2.828 2.828L16 18.828l2.586 2.586a2 2 0 1 0 2.828-2.828L18.828 16l2.586-2.586a2 2 0 1 0-2.828-2.828L16 13.172l-2.586-2.586Z"
      clipRule="evenodd"
    />
  </svg>
);

const ClipboardIcon = (props: SVGProps<SVGSVGElement>) => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width={20}
    height={20}
    viewBox="0 0 24 24"
    fill="none"
    {...props}
  >
    <path
      fill="currentColor"
      fillRule="evenodd"
      d="M8 5a3 3 0 0 0 3 3h2a3 3 0 0 0 3-3h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h1Zm0 7a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2H9a1 1 0 0 1-1-1Zm1 3a1 1 0 1 0 0 2h4a1 1 0 1 0 0-2H9Z"
      clipRule="evenodd"
    />
    <path fill="currentColor" d="M13 4a1 1 0 1 1 0 2h-2a1 1 0 1 1 0-2h2Z" />
  </svg>
);