【Day20】导航元件 - Select

元件介绍

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


<<:  [Day25] swift & kotlin 游戏篇!(7) 小鸡BB-游戏制作-API与游戏动画

>>:  day 25 - 第一手消息 telegram API

Day07_Lombok 初探

今天我们要准备开始写专案了,但是先带大家了解一下一个方便的 Library : Lombok Day...

安全功能(security function)

-治理结构 -波特的价值链 职能通过开展将输入转化为有用结果的活动来产生价值。一个组织通常由直线职...

CSV Import - Advanced

CSV Import - Advanced 昨天介绍了如何使用 CSV Import update ...

Day-21 Excel位址精选题目练习

经过了一晚的思考,我认为昨天讲的还不够清楚,因此今天我找了三题相关题目,今天来为大家做更深入的讲解,...

[DAY29] 接上金流系统,串接建立订单功能

本日将完成从Line控制购物车品项,建立订单,产生付款连结,通知付款人 替购物车加上送出订单按钮 传...