【Day10】数据展示元件 - Chip / Tag

元件介绍

Chip 元件用於标记事物的属性、标签或用於分类、筛选。

在 MUI 当中,这样的元件叫做 Chip,而在 Antd 中,这样的元件叫做 Tag,但其实是指一样的元件。

我自己以前会喜欢把这样的元件叫做 Tag,因为最符合直觉,像我们常见的 Hashtag 大概也是长这样。但是开发经验累积一段时间之後,会发现怎麽到处都有东西叫做 Tag?真的很容易撞名,而且这样的元件他也不一定会用在 Tag 上面,可能有些资料叫做 categories 或是 filters ,但他也需要用同样的元件来展示,所以如果叫做 Tag 我自己是觉得容易撞名,有时候也容易混淆,跟别人沟通的时候可能也会不小心产生误会,特别是你的专案里面同时有资料叫做 tags 和 categories 要在同个地方展示,而且你同时又有一个元件叫做 <Tag />,在跟别人沟通的时候真的非常怕他会听错或是自己讲错,因此我觉得 MUI 把它叫做 Chip 真的是很不错,我自己也蛮喜欢这个名字,因此这边文章统一起见,我先暂时叫这个元件为 Chip。

参考设计 & 属性分析

Chip 元件也是在 MUI 及 Antd 还蛮一致的元件,可以变化的样式和功能都很相似。

外观样式
variant 跟 button 很相似,有 default 跟 outlined 两种选项,default 是整个元件填满的样式。

颜色
颜色的部分 MUI 只提供我们使用 default, primary, secondary,而 Antd 的 color 支援填入色票之外,也支援一些保留字,例如 success, proccessing, error, warning, default。

icon
icon 可以让我们在 Chip 开头的地方放上一些图像,例如头像或是其他 icon 方便我们识别。

closeIcon
在结尾的地方放上的图像,MUI 叫做 deleteIcon ,Andtd 叫做 closeIcon,功能上看起来是大同小异。并且这个 icon 是支援点击事件的,透果 onDelete/onClose 可以 handle 对 icon 的点击动作。

我觉得 MUI 及 Antd 这边有个小细节还不错,就是 Antd 的 closeIcon 对应的事件是 onClose,而 MUI deleteIcon 对应到的事件是 onDelete,不会说 close 和 delete 混着用,如果我们没有注意到的话,我们平常专案内的元件很有可能就会设计出命名不一致的元件。

元件内容
在 MUI 里面叫做 label ,是一个可传入 ReactNode 的 props;Antd 则是让内容可以透过 children element 传进去。

// MUI
 <Chip
  ...otherprops
  label="标签内容"
/>

// Antd
<Tag
  ...otherprops
>
  标签内容
</Tag>

之前我们有遇到类似的状况是在 button 的地方,由於 button 是 html 原生的元件,有大家既定认知的使用方式,所以我会比较希望是用 children element 的方式来实作;但 Chip 这边好像两种方式都有人喜欢,毕竟 MUI Chip 也可以让我们在 label 传入 element。

状态属性
由於 Chip 是可点击的元件,有 onDelete/onClose 事件,因此若若有需要它不可被点击的状态,这边也提供 disabled 的 boolean 属性让我们使用。

介面设计

属性 说明 类型 默认值
variant 变化模式 contained, outlined contained
themeColor 主题颜色 primary, secondary, 色票 primary
isDisabled 是否能进行交互 boolean false
label 内容 ReactNode, String
icon 图示 ReactNode
deleteIcon 删除图示 ReactNode
onDelete 删除事件 func

元件实作

一个 Chip 的 children 有可能会出现 icon, label, deleteIcon,如下图:

因此我们在规划 html 结构的时候也以这样为主,其实跟我们在设计 Button 的时候蛮像的,其中 ChipWrapper 来决定其 children 的布局,而 icon & deleteIcon 会再根据各别的条件来决定是否显示:

<ChipWrapper>
  <Icon />
  <Label>
  <DeleteIcon>
</ChipWrapper>

变化模式 variant

我们会根据 variant 来决定他是 contained 或是 outlined 的样式,跟先前的 Button 一样,我们用一个 variantMap 的 object 来取得对应的样式,若没有对应到,则预设为 contained:

const containedStyle = css`
  background: ${(props) => props.$color};
  color: #FFF;
`;

const outlinedStyle = css`
  background: #FFF;
  color: ${(props) => props.$color};
`;

const variantMap = {
  contained: containedStyle,
  outlined: outlinedStyle,
};

客制化颜色

颜色的部分我们一样有 primary, secondray 以及随意传入的色票号码,这边的作法跟 Button 是一样的,附上连结,就不再详细说明。

我们已经有一个 makeColor({ themeColor }) 的 function,把 themeColor 传入,就能够将 primary, secondary 转换成对应的色票号码

https://github.com/TimingJL/13th-ithelp_custom-react-ui-components/blob/main/src/hooks/useColor.jsx

Icon & DeleteIcon

Label 左侧的 icon 是随着有没有传入 icon 这个 props 来决定是否显示,其中我们透过 React.cloneElement 加上一个 className 为 chip__start-icon 来调整他的样式:

<StyledChip
  className={className}
  $variant={variant}
  $color={color}
>
  {icon && React.cloneElement(icon, {
    className: clsx(icon.props.className, 'chip__start-icon'),
  })}
  <Label>{label}</Label>
</StyledChip>

Label 右侧的 icon 多数为用来触发 onDelete function,因命名上也使用 deleteIcon 这个名称。

deleteIcon 这个 props 是当我们想要客制化 endIcon 时能够使用,否则,若只有给定 onDelete function 而没有给定 deleteIcon 时,则显示预设的 deleteIcon:

const endIcon = deleteIcon || <CancelIcon />;

<StyledChip
  className={className}
  $variant={variant}
  $color={color}
>
  {icon && React.cloneElement(icon, {
    className: clsx(icon.props.className, 'chip__start-icon'),
  })}
  <Label>{label}</Label>
  {(deleteIcon || onDelete) && React.cloneElement(endIcon, {
    className: clsx(endIcon.props.className, 'chip__end-icon'),
    onClick: onDelete,
  })}
</StyledChip>

到目前为止,我们就已经完成一个相当接近 MUI 的 Chip 了,实作逻辑上其实并没有特别复杂,跟前面提到几个 数据输入元件 做法都蛮类似的,但主要是样式上会随着不同专案的需要有些调整,若把客制化样式的部分做得好用,我觉得就会是很不错的元件。


Chip 元件原始码:
Source code

Storybook:
Chip


<<:  D17 下载功能改进

>>:  每个人都该学的30个Python技巧|技巧 23:方便的运算函式—pow()、divmod()、round()(字幕、衬乐、练习)

Day 18:将你的 Angular 更新到最新版!

今天要来谈谈如何查看 Angular 应用程序的版本及更新。 首先,我们要先知道目前本机端的 Ang...

第廿七天:旅游快结束的周一

虽然还有一天...有点怀念ESO...收拾一下东西,等会要往车站移动了。 收拾完行李check ou...

【Side Project】 赛後检讨

经历了一个月的洗礼,又再一次完成了铁人赛。 当然不免俗的,最後来一篇赛後检讨。 这篇分成三个大部分来...

Day27 用python写UI-聊聊Treeview(一)

终於结束Text的部分啦~~~ 今天要来讲Treeview,这个就是树状的意思,像树一样有层次感,可...

新手要如何开始做B2C电商? 如何在开店平台架设品牌官网?

我认为想要做电商的新手,必须要掌握以下几点: 1. 确定产品和货源 成立电商第一步就是要确认自己所要...