动画:
删除图标的移入移出颜色渐变
添加标签后,添加的标签伸缩变化(实际是旋转实现)
设置索引来指向添加的标签,为其添加一个类,类使用帧动画(才能一开始就加载动画)
添加标签的实现:
动态切换添加按钮和输入框
输入内容后,光标消失时,将不为空的内容放进数组,重新渲染
参数:
tags: PropTypes.array, 标签数组,格式为[{name:'标签名',type{color:'背景色',borderColor:'边框色',fontColor:'字体颜色'}}]
delete: PropTypes.bool, 标签是否可删除
add:PropTypes.bool, 标签是否可添加
onDelete: PropTypes.func, 删除回调,参数为删除后的数组
onTagClick: PropTypes.func, 点击标签回调,第一个参数为标签索引,第二个参数为标签内容
onAdd:PropTypes.func, 添加回调,参数为添加标签后的数组
width: PropTypes.string, 标签总体容器的宽度,用于换行,默认不设置不换行
height: PropTypes.string, 标签高度
borderRadius: PropTypes.string, 标签圆角
fontSize:PropTypes.string, 标签字体大小
marginRight: PropTypes.string, 标签距离右间距
marginBottom: PropTypes.string, 标签底间距
iconColor: PropTypes.string, 图标背景颜色
hoverIconColor: PropTypes.string, 鼠标hover图标背景颜色
效果图:
代码示例:
使用:
import React,{useState} from 'react'
import Tag from './tag/tag'
// type:{color:'red',borderColor:'orange',fontColor:'green'}
const tags = [{ name: '标签一'},
{ name: '标签二'},
{ name: '标签三'},
{name:'标签四'}
]
function App(){
const onDelete = function (data)
{
console.log(data)
}
const onTagClick = function (index, name)
{
console.log(name);
console.log(index);
}
const onAdd = function (data)
{
console.log(data);
}
return(
<div>
<Tag width='200px' onAdd={onAdd} onDelete={onDelete} onTagClick={onTagClick} tags={tags} delete add></Tag>
</div>
)
}
export default App
tag.jsx:
import React, { useState,useCallback,useEffect,useRef } from 'react'
import PropTypes from 'prop-types'
import './tag.css'
function App(props) {
const [tags, setTags] = useState([]);
const [newTag, setNewTag] = useState(false);
const [addIndex, setAddIndex] = useState(-1);
const tagf = useRef(null);
const inputf = useRef(null);
useEffect(() => {
setTags(props.tags);
_styleHandler('width', props.width);
_styleHandler('height', props.height);
_styleHandler('borderRadius', props.borderRadius);
_styleHandler('fontSize', props.fontSize);
_styleHandler('marginRight', props.marginRight);
_styleHandler('marginBottom', props.marginBottom);
_styleHandler('iconColor', props.iconColor);
_styleHandler('hoverIconColor', props.hoverIconColor);
}, [props.tags])
//统一初始化处理
const _styleHandler = useCallback((key, value) => {
// console.log(props[keys]);
props[key] && tagf.current.style.setProperty('--' + key, value)
},[])
// 删除
const _delete = useCallback((index) => {
tags.splice(index, 1);
setTags([...tags]);
props.onDelete(tags);
},[tags])
// 点击
const _clickhandler = useCallback((index,name) => {
props.onTagClick(index, name);
}, [])
// 添加
const _addTagItem =useCallback(() => {
setNewTag(true);
},[inputf])
//光标消失,统一处理数据
const _onBlur = useCallback(() => {
let value=inputf.current.value
if (value !== '')
{
tags.push({ name: value })
setTags([...tags]);
setAddIndex(tags.length - 1);
props.onAdd(tags);
}
setNewTag(false);
inputf.current.value = '';
}, [tags])
//回车事件
const _onKeyUp = useCallback((e) => {
if (e.keyCode === 13)
{
inputf.current.blur();
setNewTag(false);
}
}, [tags])
return(
<div ref={tagf} className='jf-tag-container'>
<div className='jf-tag-layout'>
{
tags.map((item, index) => {
return (
<div className={`jf-tag-item ${(addIndex==index)&&'jf-tag-item-add'} `} key={item + index}
style={{
backgroundColor:item.type?(item.type.color ? item.type.color:props.color):props.color,
borderColor: item.type?(item.type.borderColor?item.type.borderColor:props.borderColor):props.borderColor,
color:item.type?(item.type.fontColor?item.type.fontColor:props.fontColor):props.fontColor,
}}
>
<span onClick={() => { _clickhandler(index,item.name) }} className='jf-tag-item-name'>{item.name}</span>
{props.delete && <i onClick={() => { _delete(index) }} className='jf-tag-delete iconfont icon-shanchu'></i>}
</div>
)
})
}
{props.add && <input className='jf-tag-input' style={{ display: newTag ? 'block' : 'none' }} ref={inputf} onBlur={_onBlur} onKeyUp={_onKeyUp} autoFocus type="text" />}
{props.add && <div
className='jf-tag-item jf-tag-item-new'
style={{ display: newTag ? 'none' : 'block' }}
onClick={(e) => { _addTagItem(e) }}>
<span className='jf-tag-item-name'>+新增</span>
</div>}
</div>
</div>
)
}
App.propTypes = {
tags: PropTypes.array,
delete: PropTypes.bool,
add:PropTypes.bool,
onDelete: PropTypes.func,
onTagClick: PropTypes.func,
onAdd:PropTypes.func,
width: PropTypes.string,
height: PropTypes.string,
borderRadius: PropTypes.string,
fontSize:PropTypes.string,
marginRight: PropTypes.string,
marginBottom: PropTypes.string,
iconColor: PropTypes.string,
hoverIconColor: PropTypes.string,
}
App.defaultProps = {
tags: [],
onDelete: () => {},
onTagClick: () => { },
onAdd:()=>{},
color: '#ecf5ff',
borderColor: '#d9ecff',
fontColor: '#409eff',
}
export default React.memo(App)
tag.css:
.jf-tag-container {
display: inline-block;
box-sizing: border-box;
--width: 100%;
--height: 38px;
--color: #ecf5ff;
--borderColor: #d9ecff;
--borderRadius: 5px;
--fontColor: #409eff;
--fontSize: 12px;
--marginRight: 20px;
--marginBottom: 5px;
--iconColor: #ddd;
--hoverIconColor: #ccc;
}
.jf-tag-layout {
display: flex;
flex-wrap: wrap;
width: var(--width);
}
.jf-tag-item {
position: relative;
box-sizing: border-box;
height: var(--height);
color: var(--fontColor);
line-height: var(--height);
background-color: var(--color);
border: solid 1px var(--borderColor);
border-radius: var(--borderRadius);
display: flex;
justify-content: space-between;
font-size: var(--fontSize);
margin: 0 var(--marginRight) var(--marginBottom) 0;
}
.jf-tag-item-add {
animation: scale 0.5s;
}
.jf-tag-input {
outline: none;
border: solid 1px #409eff;
height: var(--height);
width: 89px;
border-radius: var(--borderRadius);
}
.jf-tag-item-new {
cursor: pointer;
}
@keyframes scale {
from {
transform: rotateY(90deg);
}
to {
transform: rotateY(0deg);
}
}
.jf-tag-item-name {
display: inline-block;
flex: 1;
text-align: center;
padding: 0 5px;
}
.jf-tag-delete {
margin: 0;
padding-right: 2px;
color: var(--iconColor);
/* position: absolute; */
/* right: 2px; */
}
.jf-tag-delete:hover {
color: var(--hoverIconColor);
cursor: pointer;
}