126 lines
3.3 KiB
TypeScript
126 lines
3.3 KiB
TypeScript
import { Button, Divider, Flex, Space } from "antd";
|
|
import { createStyles } from "antd-style";
|
|
import ButtonGroup from "antd/es/button/button-group";
|
|
import Search from "antd/es/input/Search";
|
|
import React, { useEffect, useRef, useState } from "react";
|
|
|
|
interface SearchBoxProps {
|
|
children: React.ReactNode;
|
|
placeholder?: string;
|
|
searchKey?: string | undefined;
|
|
onClear?: () => void;
|
|
onCancel?: () => void;
|
|
onConfirm?: () => void;
|
|
onSearch?: (searchKey: string) => void;
|
|
visible?: boolean;
|
|
}
|
|
|
|
const SearchBox: React.FC<SearchBoxProps> = ({
|
|
children,
|
|
placeholder = "请输入搜索内容",
|
|
searchKey,
|
|
onCancel,
|
|
onClear,
|
|
onConfirm,
|
|
onSearch,
|
|
visible,
|
|
}) => {
|
|
const { styles } = useStyles();
|
|
const [operateVisible, setOperateVisible] = useState(false);
|
|
const clickRef = useRef<HTMLDivElement>(null);
|
|
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (clickRef.current && !clickRef.current.contains(event.target as Node)) {
|
|
setOperateVisible(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div className={styles.searchBoxContainer}>
|
|
<Search
|
|
placeholder={placeholder}
|
|
className="search"
|
|
onClick={() => setOperateVisible(true)}
|
|
value={searchKey}
|
|
onChange={(e) => onSearch?.(e.target.value)}
|
|
/>
|
|
<Flex
|
|
vertical
|
|
ref={clickRef}
|
|
className="operate"
|
|
style={{ display: operateVisible ? "flex" : "none" }}
|
|
>
|
|
<div className="operate-content">{children}</div>
|
|
<Flex className="operate-area">
|
|
<Divider className="divider" />
|
|
<ButtonGroup className="button-group">
|
|
<Button onClick={() => {
|
|
onClear?.();
|
|
}}>清除</Button>
|
|
<Space onClick={() => setOperateVisible(false)}>
|
|
<Button onClick={() => onCancel?.()}>取消</Button>
|
|
<Button type="primary" onClick={() => onConfirm?.()}>确定</Button>
|
|
</Space>
|
|
</ButtonGroup>
|
|
</Flex>
|
|
</Flex>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SearchBox;
|
|
|
|
const useStyles = createStyles(({ token }) => {
|
|
return {
|
|
searchBoxContainer: {
|
|
position: "relative",
|
|
width: "300px",
|
|
"& .search": {
|
|
width: "100%",
|
|
},
|
|
"& .operate": {
|
|
position: "absolute",
|
|
left: 0,
|
|
boxSizing: "border-box",
|
|
padding: `${token.padding}px ${token.padding}px 0`,
|
|
zIndex: 999,
|
|
backgroundColor: token.colorBgContainer,
|
|
boxShadow: token.boxShadow,
|
|
borderRadius: token.borderRadius,
|
|
width: "100%",
|
|
marginTop: token.marginXXS,
|
|
height: "300px",
|
|
overflow: "hidden",
|
|
},
|
|
"& .operate-content": {
|
|
flex: 1,
|
|
padding: `${token.paddingContentVertical}px 0`,
|
|
},
|
|
"& .operate-area": {
|
|
height: "50px",
|
|
position: "relative",
|
|
width: "100%",
|
|
"& .divider": {
|
|
margin: 0,
|
|
},
|
|
},
|
|
"& .button-group": {
|
|
position: "absolute",
|
|
transform: "translateY(-50%)",
|
|
top: 'calc(50% + 2px)',
|
|
right: 0,
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
width: '100%'
|
|
},
|
|
},
|
|
};
|
|
});
|