import { PlusOutlined } from "@ant-design/icons";
import type { InputRef } from "antd";
import { Flex, Input, Tag, theme, Tooltip } from "antd";
import React, { useEffect, useRef, useState } from "react";
const tagInputStyle: React.CSSProperties = {
width: 64,
height: 22,
marginInlineEnd: 8,
verticalAlign: "top"
};
interface TagsProps {
value?: Array<string>;
onChange?: (value: string[]) => void;
}
const Tags: React.FC<TagsProps> = ({ value = [], onChange }) => {
const { token } = theme.useToken();
const [inputVisible, setInputVisible] = useState(false);
const [inputValue, setInputValue] = useState("");
const [editInputIndex, setEditInputIndex] = useState(-1);
const [editInputValue, setEditInputValue] = useState("");
const inputRef = useRef<InputRef>(null);
const editInputRef = useRef<InputRef>(null);
useEffect(() => {
if (inputVisible) {
inputRef.current?.focus();
}
}, [inputVisible]);
useEffect(() => {
editInputRef.current?.focus();
}, [editInputValue]);
const handleClose = (removedTag: string) => {
const newTags = value.filter(tag => tag !== removedTag);
onChange?.(newTags);
};
const showInput = () => {
setInputVisible(true);
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
const handleInputConfirm = () => {
if (inputValue && !value.includes(inputValue)) {
onChange?.([...value, inputValue]);
}
setInputVisible(false);
setInputValue("");
};
const handleEditInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEditInputValue(e.target.value);
};
const handleEditInputConfirm = () => {
const newTags = [...value];
newTags[editInputIndex] = editInputValue;
onChange?.(newTags);
setEditInputIndex(-1);
setEditInputValue("");
};
const tagPlusStyle: React.CSSProperties = {
height: 22,
background: token.colorBgContainer,
borderStyle: "dashed"
};
return (
<Flex gap="4px 0" wrap="wrap">
{value.map<React.ReactNode>((tag, index) => {
if (editInputIndex === index) {
return (
<Input
ref={editInputRef}
key={tag}
size="small"
style={tagInputStyle}
value={editInputValue}
onChange={handleEditInputChange}
onBlur={handleEditInputConfirm}
onPressEnter={handleEditInputConfirm}
/>
);
}
const isLongTag = tag.length > 20;
const tagElem = (
<Tag key={tag} closeIcon style={{ userSelect: "none" }} onClose={() => handleClose(tag)}>
<span
onDoubleClick={e => {
if (index !== 0) {
setEditInputIndex(index);
setEditInputValue(tag);
e.preventDefault();
}
}}
>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
</Tag>
);
return isLongTag ? (
<Tooltip title={tag} key={tag}>
{tagElem}
</Tooltip>
) : (
tagElem
);
})}
{inputVisible ? (
<Input
ref={inputRef}
type="text"
size="small"
style={tagInputStyle}
value={inputValue}
onChange={handleInputChange}
onBlur={handleInputConfirm}
onPressEnter={handleInputConfirm}
/>
) : (
<Tag style={tagPlusStyle} icon={<PlusOutlined />} onClick={showInput}>
New Tag
</Tag>
)}
</Flex>
);
};
export default Tags;