iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Modern Web

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

【Day08】數據輸入元件 - Rate

元件介紹

Rate 是一個評分元件。一方面可以對於評價的數據展示,另一方面可以讓人進行對評分的操作。

參考設計 & 屬性分析

因為 MUI 目前的版本還沒有 Rate 元件,因此我們這邊先只參考 Antd 的元件。

count

雖然我們平常常見的評分都是五顆星星,但這邊給了這個 count 的參數讓我們不被限制於只能五顆星星,我覺得還蠻不錯的,這邊的預設也是五顆星。

allowHalf

這個參數允許我們選擇半顆星星,而且他很酷的是,他允許我們只 hover 一半的星星,到底是怎麼做到的呢?這邊來看一下他的 html 結構:

另外,我們這邊發現 ant-rate-star-first 這個 div 節點有一個關鍵的 css 屬性,就是 width: 50%; ,如下圖所示:

透過上述的結構,我們可以大概猜出實作 hover 一半星星的邏輯,首先我們要準備 active 星星全顆,還有 inactive 星星全顆。

接著,在初始狀態,我們先把 width: 50%; 的 active 星星完全重疊在 inactive 星星上面(就是圖中的 star-first div),這邊是透過 position: absolute; 屬性來實作重疊的效果,並且讓 active 星星先隱藏,這邊隱藏的方式是把 opacity 變成 0。

當我們 hover 在左半邊的 star-first div 上面時,就讓 active 的星星顯現(opacity: 1;),並且保持 width: 50%;,這樣看起來就是半顆星 active 的效果;當我們 hover 在右半邊的 star-second div 上面時,我們就改變被疊在下面的星星的顏色,從灰色變成黃色,也就是從 inactive 變成 active,這樣看起來就會變成一顆全星的效果。

disabled

設定了 disabled 為 true 時, Rate 就不能夠讓使用者操作,變成已讀的狀態,也就是純展示的功能。

character

character 屬性也是讓我蠻驚艷的屬性之一,就是他做到可以替換 Rate 字符,言外之意就是你不一定要被他限制住是星星圖案,你也可以是愛心,甚至也可以是文字,我覺得這個非常的酷,因為上面我們解析他的半顆 hover 功能是透過改變 color 來實現,感覺傳入的屬性也是需要能夠支援 color 可以被改變,因此這邊文件寫說限定的型別就是 ReactNode,從範例中也可以看出,他支援 icon 以及文字的傳入。

介面設計

屬性 說明 類型 默認值
count star 總數 number 5
allowHalf 是否允許半顆星星 boolean false
disabled 是否能進行交互 boolean false
defaultValue 預設分數 number
themeColor 主題顏色 number
size star 大小 number 32
character 自定義字符 ReactNode, String

元件實作

簡化來看的話,我們 Rate 整體邏輯架構概念回如下,由一個 <RateWrapper /> 的根節點包覆住整個元件,並且也由這個跟節點決定內部元件佈局排版,以 Rate 為例,應該是 row 方向的佈局,因此我們可以用 flex 來實現。

我們決定 star character 總數的 props 是 count,因此由下面程式碼範例,我們傳入的 count 為多少,就能夠產生長度為多少的陣列:

<RateWrapper>
  {
    [...Array(count).keys()].map((itemKey) => (
       <Character key={itemKey} />
    ))
  }
</RateWrapper>

接著我們來實現 Character,我們預設的 Character 是星星 <StarIcon />,由先前元件分析可知,要做到能夠允許選取半顆星星,需要兩個元件 <CharacterFirst /><CharacterSecond /> 一起來搭配才能實現:

<RateWrapper>
  {
    [...Array(count).keys()].map((itemKey) => (
       <CharacterWrapper key={itemKey}>
         <CharacterFirst>{character}</CharacterFirst>
         <CharacterSecond>{character}</CharacterSecond>
       </CharacterWrapper>
    ))
  }
</RateWrapper>

佈局上,為了實現半星選取,<CharacterFirst /> 必須要設為 position: absolute;,如此 <CharacterFirst /><CharacterSecond /> 才能夠重疊,並且 <CharacterFirst /> 的 width 需要設為 50% 來表示半星。

當我們不需要伴星選取的時候,只需要隱藏 <CharacterFirst /> 就能夠做到了。

const CharacterFirst = styled.div`
  position: absolute;
  color: ${(props) => (props.$isActive ? props.$starColor : '#F0F0F0')};
  width: 50%;
  overflow: hidden;
  cursor: pointer;
`;

佈局完成之後,接著我們要做的是 hover 的時候能夠預覽選取樣式,因此 hover 到哪裡就要 active 到哪裡,當滑鼠移開的時候,則回覆到原本選取狀態

因此我們需要一個 state 用來記錄預覽狀態,另一個 state 則是用來記錄實際上的選取狀態:

const [innerValue, setInnerValue] = useState(defaultValue);
const [previewValue, setPreviewValue] = useState(innerValue);

當滑鼠 hover 上去的時候,我們呼叫 onMouseOver 事件,若 hover 在 <CharacterFirst /> 表示半星,所以要 +0.5;若 hover 在 <CharacterSecond /> 表示全顆星,所以要 +1

<CharacterWrapper key={itemKey}>
  <CharacterFirst
    className="rate__character-first"
    $starColor={starColor}
    $isActive={itemKey + 0.5 <= previewValue}
    onMouseOver={() => handleChangePreviewValue(itemKey + 0.5)}
    onMouseLeave={() => handleChangePreviewValue(innerValue)}
    onClick={() => handleOnClick(itemKey + 0.5)}
  >
    {character}
  </CharacterFirst>
  <CharacterSecond
    className="rate__character-second"
    $starColor={starColor}
    $isActive={itemKey + 1 <= previewValue}
    onMouseOver={() => handleChangePreviewValue(itemKey + 1)}
    onMouseLeave={() => handleChangePreviewValue(innerValue)}
    onClick={() => handleOnClick(itemKey + 1)}
  >
    {character}
  </CharacterSecond>
</CharacterWrapper>

當滑鼠移開的時候,則透過 onMouseLeave 事件來改變 previewValue,設定回原本該有的值:

const handleChangePreviewValue = (currentValue) => {
  if (!isDisabled) {
    setPreviewValue(currentValue);
  }
};

onClick 事件則是確定選取的時候呼叫,因此要改變 innerValue ,那如果 onClick 的時候我們發現選取值與原本的值一樣,則表示他想要取消選擇,此時我們將 innerValue 設為 0:

const handleOnClick = (clickedValue) => {
  if (isDisabled) return;
  setInnerValue((previousValue) => (previousValue === clickedValue ? 0 : clickedValue));
};

那到目前為止,關於 Rate 主要的關鍵功能就都完成了!透過以上的方法,我們藉由 props 來改變 character 也不會是難事了:


Rate 元件原始碼:
Source code

Storybook:
Rate


上一篇
【Day07】數據輸入元件 - Slider
下一篇
【Day09】數據輸入元件 - Upload
系列文
30 天擁有一套自己手刻的 React UI 元件庫30

尚未有邦友留言

立即登入留言