iT邦幫忙

2021 iThome 鐵人賽

DAY 3
0
Modern Web

30 天擁有一套自己手刻的 React UI 元件庫系列 第 3

【Day03】數據輸入元件 - Radio

元件介紹

Radio 是一個單選框元件。讓我們在一組選項當中選擇其中一個選項。當我們的情境是希望用戶可以一次看到所有選項時,可以使用 Radio Button。在 MUI 及 Antd 都有個共同的說明,就是 Radio Button 的選項不宜多,如果你的選項多到需要被折疊,那建議你使用更不佔空間的下拉選單元件

參考設計 & 屬性分析

checked 屬性

checked 屬性是每個 Radio Button 必備的屬性,是一個 boolean 值,透過這個屬性來決定該選項是否被選取。

狀態屬性

disabled 屬性也是每個 Radio Button 的必備屬性,表示這個 Radio 被禁用,禁止改變他的當前狀態。

當然 Radio Button 應該就不會有 loading 屬性了,畢竟 Radio Button 不是在當前改變的時候觸發動作,是需要發送按鈕按下之後才會將結果送出。

外觀屬性

大小的部分 MUI 以及 Antd 的一樣是用 size 來控制,其中 MUI 的傳入值可以是 medium, samll,而 Antd 的傳入值可以是 large, middle, small

顏色的部分,MUI 提供 color 這個 props 讓我們傳入,傳入值可以是 primary, secondary, default,而 Antd 從文件來看沒有特別提供明顯的介面讓我們改變 Radio 的顏色,如果要改顏色可能需要再看要用什麼方法去覆寫。

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

Radio Group
由於 Radio Button 是單選選項,當介面有多個選項時,為了讓同一個 Group 的 option 做到單選的效果,MUI 及 Antd 都提供了 Radio Group 的 wrapper component 來協助我們將這些選項編輯為同一個群組,介面的設計上看起來他們也是蠻有共識的。

// Antd Radio Group
<Radio.Group value={value} onChange={handleChangeAntdRadio}>
    <Radio value={1}>A</Radio>
    <Radio value={2}>B</Radio>
    <Radio value={3}>C</Radio>
    <Radio value={4}>D</Radio>
</Radio.Group>
// MUI Radio Group
<RadioGroup value={value} onChange={handleChangeMuiRadio} aria-label="gender" name="gender1" >
    <FormControlLabel value="female" control={<Radio />} label="Female" />
    <FormControlLabel value="male" control={<Radio />} label="Male" />
    <FormControlLabel value="other" control={<Radio />} label="Other" />
    <FormControlLabel value="disabled" disabled control={<Radio />} label="(Disabled option)" />
</RadioGroup>

為了做群組單選的操做,這邊就不在單一個 Radio 上面監聽 onChange 的事件,而是將 onChange 事件拉到外層的 wrapper 上面。並且也讓我們在外層的 Radio Group 元件上能夠傳入 value 這個 props ,用來告訴群組中的選項哪一個是被選中的,若選項中的 value 跟傳入 Radio Group 的 value 一致,那這個選項就是 checked = true 的狀態。

介面設計

Radio Button

屬性 說明 類型 默認值
value 用來幫助判斷是否被選中 any
isChecked 開啟或關閉 boolean false
isDisabled 禁用狀態 boolean false
themeColor 設置顏色 primary, secondary, 色票 pirmary
children 內容, label element, string
onClick 點擊事件 function(event: object) => void

Radio Group

屬性 說明 類型 默認值
value 用來幫助判斷下面 Radio 是否被選中 any
onChange 狀態改變的 callback function function(event: object) => void
columns 用來決定 children 排版的欄位數 number 1

元件實作

Radio

Radio 的結構是相對於單純的結構,主要分成兩部分,第一部分是 Radio icon,第二部分是 label,我們的 label 是透過 children 來傳入;而透過 isChecked 這個 props 來決定顯示被選取或不被選取的樣式。:

<StyledRadio
  onClick={isDisabled ? null : onClick}
  $isDisabled={isDisabled}
  $btnColor={btnColor}
  {...props}
>
  {
    isChecked
      ? <RadioButtonCheckedIcon className="radio__checked-icon" />
      : <RadioButtonUncheckedIcon className="radio__unchecked-icon" />
  }
  {!!children && <span>{children}</span>}
</StyledRadio>

在設計這個元件的時候,我將 checkdeIcon 以及 unCheckedIcon 各別給他一個 className,並且包覆在 StyledRadio 這個 wrapper 之下,主要的目的是我希望樣式的變化能夠透過 StyledRadio 這個父層的 styled-components 來處理就好,所以也就只需要把 props 傳入 StyledRadio 就能夠透過 className 來改變子層的樣式,而不用每個元件都需要傳入各別傳入同樣的 props。

const StyledRadio = styled.div`
  // {...其他樣式省略}

  .radio__checked-icon {
    color: ${(props) => props.$btnColor};
  }

  .radio__unchecked-icon {
    color: ${(props) => (props.$isDisabled ? DISABLED_COLOR : '#808080')};
  }

  &:hover {
    .radio__unchecked-icon {
      color: ${(props) => (props.$isDisabled ? DISABLED_COLOR : props.$btnColor)};
    }
  }
`;

Radio Group

接著我們來做一個陽春的 Radio Group,主要的目的是希望能夠透過它來統一管理子層單選的 Radio buttons,並且能夠做一些簡單的排版。

我希望使用起來可以像下面這樣,只需要父層傳入 value 及 onChange,子層的 Radio 傳入 value,就能夠做到單選效果:

<RadioGroup
  value={selectedValue}
  onChange={handleOnChange}
  columns={2}
  style={{ maxWidth: 500 }}
  {...args}
>
    <Radio value="male">Male</Radio>
    <Radio value="female">Female</Radio>
    <Radio value="others">Others</Radio>
</RadioGroup>

主要的作法如下,關鍵是會用到 React.Children.mapReact.cloneElement 這兩個方法:

<StyledRadioGroup
  {...props}
>
  {React.Children.map(children, (child) => (
    React.cloneElement(child, {
      onClick: () => handleOnClick(child.props.value),
      isChecked: child.props.value === value,
    })
  ))}
</StyledRadioGroup>

React.Children.map 可以幫助我們將 array of Radio 的 children 做迭代,如同我們用 Array.prototype.map() 在處理一個陣列一樣。

在迭代當中的每一個迴圈,我們可以拿到的 child 就是一個 Radio element,此時再搭配 React.cloneElement 這個方法,藉此產生一個擁有原始 element 的 props 以及我們在這裡新注入 props 的全新 element。

簡單來說,就是我們想要把從 RadioGroup 傳進來的 props 經過運算之後,當作新的 props 注入每一個 child element 裏面,以這裡為例就是我們把 RadioGroup 傳入的 value 跟 child value 做比較,若相符就是被選中的 Radio,把這個 boolean 注入 isChecked,如此就能夠用這樣的方法達到 Radio 的單選功能。


Radio 元件原始碼:
Source code

RadioGroup 元件原始碼:
Source code

Storybook:
Radio


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

尚未有邦友留言

立即登入留言