iT邦幫忙

2021 iThome 鐵人賽

DAY 4
0

元件介紹

Checkbox 是一個多選框元件。通常使用情境是在一個群組的選項當中進行多項選擇時使用。

參考設計 & 屬性分析

checked 屬性

checked 是一個 boolean 值,透過這個屬性來決定該選項是否被選取。

狀態屬性

disabled 屬性表示這個 checkbox 是否被禁用,當前狀態如果是 checked 就不能被改變成 unchecked,反之亦然,也就是禁止改變他的當前狀態。當然這邊的改變指的是透過 onChange 來改變,如果直接改變 checked props 的話,還是可以改變當前的狀態。

外觀屬性

MUI 以及 Antd 的一樣是用 size 來控制不同情境需要的大小,其中 MUI 的傳入值可以是 medium, samll,而 Antd 的傳入值可以是 large, middle, small

顏色的部分也跟 Radio Button 雷同,MUI 提供 color 這個 props 讓我們傳入,傳入值可以是 primary, secondary, default,而 Antd 從文件來看沒有特別提供明顯的介面讓我們改變 Checkbox 的顏色。

label 屬性
Antd 的 label 屬性是放在 Checkbox 的 children component 裡面,而 MUI 的 label 及 labelPlacement 一樣是由另外的元件 FormControlLabel 來獨立控制。

我們在點擊 Checkbox 或是 Radio Button 的時候,都希望 click 的監聽元件不只是那個 box而已,因為這樣能夠被點擊的作用區域太小了,很容易讓使用者點不到,造成不好的使用體驗,因此如果我們點擊到他的 label 也希望能夠勾選,甚至如果我們 checkbox 是 List item 的一部份的話,通常也是會希望整個 List item 被點擊的時候可以改變 Checkbox 狀態。

小結
到目前為止我們看過幾個很類似的元件,例如 Toggle Switch, Radio Button, Checkbox,甚至是我們一開始講的 Button ,有很多屬性其實都蠻雷同的而且也是很多元件都有的,例如 checked, disabled, loading, label, size, color......等等。

我自己在設計元件的時候,會希望也會建議讓這些 props 的介面命名、型別、傳入值盡量保持一致,例如下面舉例一些不是很建議的案例:

  • Checkbox 的 checked 叫做 checked,而 Switch 的 checked 卻叫做 open,然後可能 Radio Button 又叫做 selected。
  • label 也是一樣,不建議有些元件叫做 label ,有些元件叫做 text ,可能又有些元件叫做 title。
  • 可能有些人在某些元件讓 size 可以傳入 medium, small,然後另外的元件可傳入值叫做 big, middle, little.....。另外甚至有些 size 是這些固定的 string 傳入,但有些元件卻是讓 size 傳入一個 number。

在同一套系統當中我個人是會希望盡量統一介面,讓使用者好使用,不用特別再去看一下文件或是深入追一下 code 才知道使用方法,避免可能今天寫一寫,下個月又使用到同樣的元件又忘記了。

這樣的狀況可能會發生在同一個人設計不同元件上面(可能上次設計類似的元件是一段時間以前了),也有可能發生在不同人設計類似的元件上面,這些東西一點一滴累積起來,整個專案可能會變得越來越混亂、越來越難維護、越來越難以令人理解,因此建議設計新元件的時候,也需要去參考其他元件的設計方式,比較能夠設計出統一一致的介面。

當然這樣的建議可能也只是理想和原則,實務上當然也可能遇到需要取捨的時候,但也只少需要是團隊有共識的設計,並且把這些不直觀的設計記錄下來,避免之後的人踩雷,或是不知道為什麼當初這樣設計而不小心改壞掉。

介面設計

屬性 說明 類型 默認值
isChecked 開啟或關閉 boolean false
isDisabled 禁用狀態 boolean false
themeColor 設置顏色 primary, secondary, 色票 pirmary
children 內容, label element, string
onClick 點擊事件 function(event: object) => void
onChange 狀態改變的 callback function function(event: object) => void

元件實作

其實從介面上以及外觀來看,Radio 跟 Checkbox 相似度幾乎是 87%,唯一差別就是 Icon 不一樣,若因為 Icon 不一樣就把程式碼一模一樣複製一次,那這樣在程式碼裡面就需要維護兩套一模一樣的邏輯。因此我希望把前一篇提到的 Radio 重構一下,把 Radio 以及 Checkbox 會用到的部分抽出來成另一個元件,這邊暫時想不到更好的名字,姑且用 Option 這個名字。

所以 Radio 和 Checkbox 我們希望把它變成下面這樣,透過 props 把 checkedIcon & unCheckedIcon 傳進去,其他的邏輯以及樣式的部分就寫在共同的 <Option /> 元件裡面:

Radio:

import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUnchecked';
import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked';

const Radio = (props) => (
  <Option
    checkedIcon={<RadioButtonCheckedIcon />}
    unCheckedIcon={<RadioButtonUncheckedIcon />}
    {...props}
  />
);

Checkbox:

import CheckBoxIcon from '@material-ui/icons/CheckBox';
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';

const Checkbox = (props) => (
  <Option
    checkedIcon={<CheckBoxIcon />}
    unCheckedIcon={<CheckBoxOutlineBlankIcon />}
    {...props}
  />
);

這樣 Option 其實就跟我們上一篇的 Radio 差不多,唯一的差別就是這裡我希望傳進來的 checkedIcon & unCheckedIcon 跟之前一樣擁有一個 className 方便我客製化他的樣式,因此用 React.cloneElement 這個方法把 props 塞入其中(實際上是複製一個一模一樣的 element 並給予他我們傳入的 props)。

<StyledOption
  onClick={isDisabled ? null : onClick}
  $isDisabled={isDisabled}
  $btnColor={btnColor}
  {...props}
>
  {
    isChecked
    ? (
      React.cloneElement(checkedIcon, {
      	className: clsx(checkedIcon.props.className, 'option__checked-icon'),
      })
    )
    : (
      React.cloneElement(unCheckedIcon, {
        className: clsx(unCheckedIcon.props.className, 'option__unchecked-icon'),
      })
    )
}
{!!children && <span>{children}</span>}
</StyledOption>

Checkbox 元件原始碼:
Source code

Option 元件原始碼:
Source code

Storybook:
Checkbox


上一篇
【Day03】數據輸入元件 - Radio
下一篇
【Day05】數據輸入元件 - Input Text / Text Field
系列文
30 天擁有一套自己手刻的 React UI 元件庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言