ui-lib.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import styles from "./ui-lib.module.scss";
  2. import LoadingIcon from "../icons/three-dots.svg";
  3. import CloseIcon from "../icons/close.svg";
  4. import { createRoot } from "react-dom/client";
  5. export function Popover(props: {
  6. children: JSX.Element;
  7. content: JSX.Element;
  8. open?: boolean;
  9. onClose?: () => void;
  10. }) {
  11. return (
  12. <div className={styles.popover}>
  13. {props.children}
  14. {props.open && (
  15. <div className={styles["popover-content"]}>
  16. <div className={styles["popover-mask"]} onClick={props.onClose}></div>
  17. {props.content}
  18. </div>
  19. )}
  20. </div>
  21. );
  22. }
  23. export function Card(props: { children: JSX.Element[]; className?: string }) {
  24. return (
  25. <div className={styles.card + " " + props.className}>{props.children}</div>
  26. );
  27. }
  28. export function ListItem(props: { children: JSX.Element[] }) {
  29. if (props.children.length > 2) {
  30. throw Error("Only Support Two Children");
  31. }
  32. return <div className={styles["list-item"]}>{props.children}</div>;
  33. }
  34. export function List(props: { children: JSX.Element[] }) {
  35. return <div className={styles.list}>{props.children}</div>;
  36. }
  37. export function Loading() {
  38. return (
  39. <div
  40. style={{
  41. height: "100vh",
  42. width: "100vw",
  43. display: "flex",
  44. alignItems: "center",
  45. justifyContent: "center",
  46. }}
  47. >
  48. <LoadingIcon />
  49. </div>
  50. );
  51. }
  52. interface ModalProps {
  53. title: string;
  54. children?: JSX.Element;
  55. actions?: JSX.Element[];
  56. onClose?: () => void;
  57. }
  58. export function Modal(props: ModalProps) {
  59. return (
  60. <div className={styles["modal-container"]}>
  61. <div className={styles["modal-header"]}>
  62. <div className={styles["modal-title"]}>{props.title}</div>
  63. <div className={styles["modal-close-btn"]} onClick={props.onClose}>
  64. <CloseIcon />
  65. </div>
  66. </div>
  67. <div className={styles["modal-content"]}>{props.children}</div>
  68. <div className={styles["modal-footer"]}>
  69. <div className={styles["modal-actions"]}>
  70. {props.actions?.map((action, i) => (
  71. <div key={i} className={styles["modal-action"]}>
  72. {action}
  73. </div>
  74. ))}
  75. </div>
  76. </div>
  77. </div>
  78. );
  79. }
  80. export function showModal(props: ModalProps) {
  81. const div = document.createElement("div");
  82. div.className = "modal-mask";
  83. document.body.appendChild(div);
  84. const root = createRoot(div);
  85. const closeModal = () => {
  86. props.onClose?.();
  87. root.unmount();
  88. div.remove();
  89. };
  90. div.onclick = (e) => {
  91. if (e.target === div) {
  92. closeModal();
  93. }
  94. };
  95. root.render(<Modal {...props} onClose={closeModal}></Modal>);
  96. }
  97. export type ToastProps = { content: string };
  98. export function Toast(props: ToastProps) {
  99. return (
  100. <div className={styles["toast-container"]}>
  101. <div className={styles["toast-content"]}>{props.content}</div>
  102. </div>
  103. );
  104. }
  105. export function showToast(content: string, delay = 3000) {
  106. const div = document.createElement("div");
  107. div.className = styles.show;
  108. document.body.appendChild(div);
  109. const root = createRoot(div);
  110. const close = () => {
  111. div.classList.add(styles.hide);
  112. setTimeout(() => {
  113. root.unmount();
  114. div.remove();
  115. }, 300);
  116. };
  117. setTimeout(() => {
  118. close();
  119. }, delay);
  120. root.render(<Toast content={content} />);
  121. }