iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0

元件介紹

Select 是一個下拉選擇器。觸發時能夠彈出一個菜單讓用戶選擇操作。

這個元件我們底層就能夠使用我們上一篇所提到的 Dropdown 來實作。

參考設計 & 屬性分析

選項

options 是我們選單的 list ,選單中每一個選項我們用 { label, value } 來表示,為何需要兩個值來表達一個欄位呢?原因是因為,我們可能會遇到顯示的 label 跟選取所需要的 value 不一樣的狀況,舉例來說,假設今天我們有一個評論管理列表,透過 Select 我們可以篩選不同星等的留言,從一星留言到五星留言,我們的選項當然可以用 1, 2, 3, 4, 5 來表示,但是假設今天設計師與 PM 討論出來的選項是 1, 2, 3, 4, 5, 全部星等,你絕對沒有看錯,所有都是數字類別的選項今天在最後面突然出現一個非數字選項,遇到這樣的狀況,你的畫面應該如何處理?而且你打 API 的時候應該送什麼資料給後端?

除了這個特別的狀況之外,還有一些比較常見的例子,例如說我要透過 Select 篩選商品的類別,有 3C 商品, 彩妝, 運動, 保健, 親子...等等類別,我們要顯示給使用者看的資料,跟實際上儲存在資料庫裡面或拿來做運算的資料,通常不會是我們所看到的這些中文字。另一方面,若又考慮到多國語言的處理,我們勢必又有更強烈的理由將一個選項切分為 label 以及 value。

value

用來指定當前被選中的項目。
特別提一下這個屬性,因為在設計哪個選項被選中的資料表示法,有看過有人設計成在每個選項裡面加一個 isSelected 的 boolean,所以 option 的內容變成 { label, value, isSelected }。但其實我自己是不太偏好這樣的設計,因為這樣會讓每一個 option 都多一個參數,讓 option 複雜化。用 <Select value={value} options={options} /> 其實就充分可以做到同樣的事,即使可能想要表達一次多個選項被選擇,也只要讓 value 可以支援 array 就可以了。

介面設計

屬性 說明 類型 默認值
options 選項內容 { label, value }[]
isLoading 資料是否正在載入中 boolean flase
isDisable 是否禁用下拉選單 boolean flase
value 用來指定當前被選中的項目 string
placeholder 未選擇任何選項時顯示的 title string
onSelect 當選項被選中時會被調用 (value) => void

元件實作

以下就是我最終期待的 Select 使用起來的樣子,我們只需要給定 選項選中的值placeholderonSelect 就可以了:

const options = [
  {
    label: '我全都要',
    value: 'all'
  },
  {
    label: 'AZ 疫苗',
    value: 'AZ'
  },
  {
    label: 'BNT 疫苗',
    value: 'BNT'
  },
  {
    label: '莫德納疫苗',
    value: 'Moderna'
  },
  {
    label: '高端疫苗',
    value: 'Vaccine'
  }
];

const [selectedValue, setSelectedValue] = useState('');

<Select
  value={selectedValue}
  options={options}
  placeholder="請選擇預約疫苗"
  onSelect={(value) => setSelectedValue(value)}
/>

那我們來看一下內部是怎麼做的,按照上一篇所預告的,這個內部我就直接用上一篇做好的 Dropdown 來實現,這時候就能夠來驗證一下我們 Dropdown 到底好不好用啦!

Select 選單

我們先來看一下 Dropdown 的 overlay 這個 props,因為這裡我們要迭代出我們的選單:

<Dropdown
  {...省略其他props}
  overlay={(
    <Menu>
      {
        options.map((option) => (
          <MenuItem
            key={option.value}
            role="presentation"
            $isSelected={option.value === value}
            onClick={() => {
              onSelect(option.value);
              setIsOpen(false);
            }}
          >
            {option.label}
          </MenuItem>
        ))}
     </Menu>
  )}
>
  ...
</Dropdown>

這個選單其實也蠻單純的,就是判斷 isSelected 的時候是用選項 value 跟選中的 value 來做比較:

$isSelected={option.value === value}

然後點擊的時候,我們做兩件事,一件事是呼叫 onSelect 這個 callback,讓呼叫他的人可以拿到 option.value 這個值,以便更新哪個選項是被選中的。

第二件事是選中之後,我們就把選單關閉 setIsOpen(false),當然你不想要關閉也是可以的,就看使用的情境。

Select Box

Select Box 來顯示我們選中的內容以及選中的狀態,下面是我的 Select Box 的長相:

const foundOption = options.find((option) => option.value === value) || {};

<SelectBox $isDisable={isDisable || isLoading}>
  <span>{foundOption.label || placeholder}</span>
  {
    isLoading ? (
    <StyledCircularProgress
      $color="#00000040"
      size={16}
    />) : (
      <ArrowDown $isOpen={isOpen}>
        <KeyboardArrowDown />
      </ArrowDown>
    )
  }
</SelectBox>

主要的顯示選中內容在這裡:

<span>{foundOption.label || placeholder}</span>

如果有選項被選中,我們就顯示他的 label,如果沒有東西被選中,我們就顯示 placeholder。

再來第二個部分就是 Icon,我們有兩種 Icon,一個是一般狀態的 ArrowIcon,用來表示目前選單是展開還是收合,第二個 Icon 是 Loading Icon,用來表示資料現在正在載入中。

比較特別的是我把 ArrowIcon 加上一個旋轉動畫,讓他看起來比較生動一點,主要是使用 css transition 搭配 transform rotate 來實現:

const ArrowDown = styled.div`
  height: 24px;
  width: 24px;
  transform: rotate(${(props) => (props.$isOpen ? 180 : 0)}deg);
  transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
`;

展示一下效果:

如果他是載入中的話,我們就把 ArrowIcon 換成 Loading Icon:

Disable

我們可以看到上面的 Loading 狀態還加上了 disable 狀態的樣式,因為我們不希望資料還沒被載入完全的狀態下就被展開。

Disable 也是很單純,主要處理兩個部分,一個是樣式,一個是事件。
事件的部分,我們就是用 isDisable 這個 boolean 讓觸發事件無效化就可以了:

onClick={() => ((isDisable || isLoading) ? null : setIsOpen(true))}

樣式的部分,我們一樣把 enable 和 disable 兩個樣式分別獨立出來,然後也是用 boolean 來判斷就可以:

const selectBoxEnable = css`
  color: #333;
  &:hover {
    border: 1px solid #222;
  }
`;

const selectBoxDisable = css`
  background: #f5f5f5;
  color: #00000040;
`;

const SelectBox = styled.div`
  // ...(省略其他樣式)
  ${(props) => (props.$isDisable ? selectBoxDisable : selectBoxEnable)}
`;

在 Dropdown 的幫助之下,我們的 Select 元件就順利搞定啦!


Select 元件原始碼:
Source code

Storybook:
Select


上一篇
【Day19】導航元件 - Dropdown
下一篇
【Day21】導航元件 - Drawer
系列文
30 天擁有一套自己手刻的 React UI 元件庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言