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 使用起来的样子,我们只需要给定 选项
、选中的值
、placeholder
、onSelect
就可以了:
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
今天我们要准备开始写专案了,但是先带大家了解一下一个方便的 Library : Lombok Day...
-治理结构 -波特的价值链 职能通过开展将输入转化为有用结果的活动来产生价值。一个组织通常由直线职...
CSV Import - Advanced 昨天介绍了如何使用 CSV Import update ...
经过了一晚的思考,我认为昨天讲的还不够清楚,因此今天我找了三题相关题目,今天来为大家做更深入的讲解,...
本日将完成从Line控制购物车品项,建立订单,产生付款连结,通知付款人 替购物车加上送出订单按钮 传...