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