diff --git a/ui/src/components/ui/CollapsibleCard.tsx b/ui/src/components/ui/CollapsibleCard.tsx new file mode 100644 index 0000000..a81d085 --- /dev/null +++ b/ui/src/components/ui/CollapsibleCard.tsx @@ -0,0 +1,40 @@ +import { ChevronDown, ChevronUp } from "lucide-react"; +import { useState } from "react"; + +interface IProps { + title?: string | React.ReactNode; + className?: string; + children: React.ReactNode; +} + +const CollapsibleCard: React.FC = (props) => { + const { title = '', className = '', children } = props; + + const [isOpen, setIsOpen] = useState(false); + + return ( +
+
+
+ { + typeof title === 'string' ?

{title}

: title + } +
+ +
+
+ { + isOpen && children + } +
+
+ ) +} + +export default CollapsibleCard; \ No newline at end of file diff --git a/ui/src/components/ui/Input.tsx b/ui/src/components/ui/Input.tsx new file mode 100644 index 0000000..6f9f0f8 --- /dev/null +++ b/ui/src/components/ui/Input.tsx @@ -0,0 +1,60 @@ +import { useState } from "react"; +import { debounce } from "lodash"; + +interface IProps { + isPassword?: boolean; + value?: string; + setValue?: (value: string) => void; + setIsValueValid?: (value: string) => void; + placeholder?: string; + className?: string; +} + +const Input: React.FC = (props) => { + const { isPassword = false, value = '', setValue = () => {}, setIsValueValid, placeholder = '', className = '' } = props; + const [showPassword, setShowPassword] = useState(!isPassword); + + const handleChange = (e: React.ChangeEvent) => { + setValue(e.target.value); + setIsValueValid?.(e.target.value); + } + + return ( +
+ + { + isPassword && + } +
+ ) +} + +export default Input; \ No newline at end of file diff --git a/ui/src/components/ui/Loading-icon.tsx b/ui/src/components/ui/Loading-icon.tsx new file mode 100644 index 0000000..6a3c951 --- /dev/null +++ b/ui/src/components/ui/Loading-icon.tsx @@ -0,0 +1,12 @@ +const LoadingIcon = () => { + return ( + + + + + + + ); +}; + +export default LoadingIcon; \ No newline at end of file diff --git a/ui/src/components/ui/Modal.tsx b/ui/src/components/ui/Modal.tsx new file mode 100644 index 0000000..e388df2 --- /dev/null +++ b/ui/src/components/ui/Modal.tsx @@ -0,0 +1,56 @@ +import { X } from "lucide-react"; +import React, { useEffect } from "react"; + +interface ModalProps { + open: boolean; + onClose: () => void; + children: React.ReactNode; + className?: string; // 可选:自定义内容区样式 + overlayClassName?: string; // 可选:自定义遮罩样式 + autoClose?: boolean; // 可选:是否自动关闭 +} + +const Modal: React.FC = ({ + open, + onClose, + children, + className = "", + overlayClassName = "", + autoClose = true, +}) => { + useEffect(() => { + let timer: NodeJS.Timeout; + if (open && autoClose) { + timer = setTimeout(() => { + onClose(); + }, 3000); + } + + return () => clearTimeout(timer); + }, [open, autoClose]); + + if (!open) return null; + + return ( +
+
e.stopPropagation()} // 阻止冒泡,点击内容区不关闭 + > + + {children} +
+
+ ); +}; + +export default Modal; \ No newline at end of file