输入类

选择器

用户可以通过 Select 选择器从一个选项集合中去选中一个或多个选项,并呈现最终选择结果

代码演示

如何引入

import { Select } from '@fibrous/semi-ui-solid';

选项写作 <Select.Option> 与 <Select.OptGroup>。

基本使用

每个 Option 标签都必须声明 value 属性。label 是选项的文本,用于搜索与回调;children 是选项的显示内容,可以是 JSX 元素,没有 children 时显示 label





代码
import { Select } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    return (
        <>
            <Select aria-label="业务线" defaultValue="douyin" style={{ width: '120px' }}>
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying" disabled>
                    剪映
                </Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>
            <br />
            <br />
            <Select aria-label="业务线" defaultValue="douyin" disabled style={{ width: '120px' }}>
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
            </Select>
            <br />
            <br />
            <Select aria-label="业务线" placeholder="请选择业务线" style={{ width: '120px' }}>
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying" disabled>
                    剪映
                </Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>
        </>
    );
}

以数组形式传入 Option

可以直接通过optionList传入一个对象数组,每个对象必须包含 value/label 属性(当然其他属性也可以通过此方式传入)

代码
import { Select } from '@fibrous/semi-ui-solid';

const list = [
    { value: 'douyin', label: '抖音', otherKey: 0 },
    { value: 'ulikecam', label: '轻颜相机', disabled: true, otherKey: 1 },
    { value: 'jianying', label: '剪映', otherKey: 2 },
    { value: 'toutiao', label: '今日头条', otherKey: 3 },
];

export default function SelectDemo() {
    return <Select aria-label="业务线" placeholder="请选择业务线" style={{ width: '180px' }} optionList={list} />;
}

多选

自 v2.28后,select 的选择器会自带 maxHeight 270,内容超出后可以通过垂直滚动查看。

配置multiple属性,可以支持多选

配置 maxTagCount 可以限制已选项展示的数量,超出部分将以+N 的方式展示

配置 ellipsisTrigger (>= v2.28.0) 对溢出部分的 tag 做自适应处理,当宽度不足时,最后一个tag内容作截断处理。开启该功能后会有一定性能损耗,不推荐在大表单场景下使用

配置 expandRestTagsOnClick (>= v2.28.0) 可以在设置 maxTagCount 情况下通过点击展示全剩余的tag

使用 showRestTagsPopover (>= v2.22.0) 可以设置在超出 maxTagCount 后,hover +N 是否显示 Popover,默认为 false。并且,还可以在 restTagsPopoverProps 属性中配置 Popover。

配置 max 属性可限制最大可选的数量,超出最大限制数量后无法选中,同时会触发 onExceed 回调







代码
import { Select, Toast } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    return (
        <>
            <Select aria-label="Select 示例" multiple style={{ width: '320px' }} defaultValue={['douyin', 'ulikecam']}>
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying">剪映</Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>
            <br />
            <br />
            <Select aria-label="Select 示例" multiple maxTagCount={2} showRestTagsPopover restTagsPopoverProps={{ position: 'top' }} style={{ width: '320px' }} defaultValue={['douyin', 'ulikecam', 'jianying']}>
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying">剪映</Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>

            <br />
            <br />
            <Select aria-label="Select 示例" multiple style={{ width: '320px' }} defaultValue={['douyin']} max={2} onExceed={() => Toast.warning('最多只允许选择两项')}>
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying">剪映</Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>
            <br />
            <br />
            <Select aria-label="Select 示例" multiple maxTagCount={2} showRestTagsPopover restTagsPopoverProps={{ position: 'top' }} style={{ width: '220px' }} defaultValue={['xigua', 'ulikecam', 'jianying', 'douyin']} ellipsisTrigger expandRestTagsOnClick>
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying">剪映</Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>
        </>
    );
}

分组

用 Select.OptGroup 进行分组(分组功能仅支持通过 children 声明选项使用,不支持 optionList 方式传入)。分组标题用 label 属性,只接受文本

代码
import { Select } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    return (
        <Select aria-label="地区" placeholder="" style={{ width: '180px' }} filter>
            <Select.OptGroup label="Asia">
                <Select.Option value="a-1" label="China" />
                <Select.Option value="a-2" label="Korea" />
            </Select.OptGroup>
            <Select.OptGroup label="Europe">
                <Select.Option value="b-1" label="Germany" />
                <Select.Option value="b-2" label="France" />
            </Select.OptGroup>
            <Select.OptGroup label="South America">
                <Select.Option value="c-1" label="Peru" />
            </Select.OptGroup>
        </Select>
    );
}
代码
import { For } from 'solid-js';
import { Select } from '@fibrous/semi-ui-solid';

const data = [
    {
        label: 'Asia',
        children: [
            { value: 'a-1', label: 'China' },
            { value: 'a-2', label: 'Korea' },
        ],
    },
    {
        label: 'Europe',
        children: [
            { value: 'b-1', label: 'Germany' },
            { value: 'b-2', label: 'France' },
        ],
    },
    {
        label: 'South America',
        children: [{ value: 'c-1', label: 'Peru' }],
    },
];

export default function SelectDemo() {
    return (
        <Select aria-label="地区" placeholder="" style={{ width: '180px' }} filter>
            <For each={data}>
                {(group) => (
                    <Select.OptGroup label={group.label}>
                        <For each={group.children}>{(option) => <Select.Option value={option.value} label={option.label} />}</For>
                    </Select.OptGroup>
                )}
            </For>
        </Select>
    );
}

不同尺寸

通过 Size 控制选择器的大小尺寸: small / default / large





代码
import { Select } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    return (
        <>
            <Select aria-label="small" placeholder="请选择业务线" style={{ width: '180px' }} size="small">
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
            </Select>
            <br />
            <br />
            <Select aria-label="default" placeholder="请选择业务线" style={{ width: '180px' }}>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
            </Select>
            <br />
            <br />
            <Select aria-label="large" placeholder="请选择业务线" style={{ width: '180px' }} size="large">
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
            </Select>
        </>
    );
}

不同校验状态样式

validateStatus: default / warning / error
仅影响背景颜色等样式表现





代码
import { Select } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    return (
        <>
            <Select aria-label="default" style={{ width: '180px' }}>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
            </Select>
            <br />
            <br />
            <Select aria-label="warning" style={{ width: '180px' }} validateStatus="warning">
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
            </Select>
            <br />
            <br />
            <Select aria-label="error" style={{ width: '180px' }} validateStatus="error">
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
            </Select>
        </>
    );
}

配置前缀、后缀、清除按钮

  • 可以通过prefix传入选择框前缀,通过suffix传入选择框后缀,可以为文本或者 JSX 元素
    当 prefix、suffix 传入的内容为文本或者 Icon 时,会自动带上左右间隔,若为自定义元素,则左右间隔为 0
  • 通过showClear控制清除按钮是否展示
  • 通过showArrow控制右侧下拉箭头是否展示


代码
import { IconGift, IconVigoLogo } from '@fibrous/semi-icons-solid';
import { Select } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    return (
        <>
            <Select aria-label="Select 示例" style={{ width: '320px' }} defaultValue="ulikecam" prefix={<IconVigoLogo />} showClear>
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying">剪映</Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>
            <br />
            <br />
            <Select aria-label="Select 示例" style={{ width: '320px' }} defaultValue="ulikecam" prefix={<IconVigoLogo />} suffix={<IconGift />} showArrow={false}>
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying">剪映</Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>
        </>
    );
}

在顶部/底部渲染附加项

我们在弹出层顶部、底部分别预留了插槽,当你需要在弹出层中添加自定义 node 时
可以通过innerBottomSlot或者outerBottomSlot传入,自定义 node 将会被渲染在弹出层底部;可以通过innerTopSlot或者outerTopSlot传入,自定义 node 将会被渲染在弹出层顶部。

  • innerTopSlot 和 innerBottomSlot将会被渲染在 optionList 内部,当滚动到 optionList 顶部/底部时展现
  • outerTopSlot 和 outerBottomSlot将会被渲染为与 optionList 平级,无论 optionList 是否滚动,都会始终展现

outerBottomSlot:

innerBottomSlot:

代码
import { Select } from '@fibrous/semi-ui-solid';

const innerSlotStyle = {
    'background-color': 'var(--color-white)',
    height: '36px',
    display: 'flex',
    'align-items': 'center',
    cursor: 'pointer',
    'padding-left': '32px',
    'border-top': '1px solid var(--semi-color-border)',
    'border-radius': '0 0 6px 6px',
    color: 'var(--semi-color-link)',
};
const outSlotStyle = {
    'background-color': 'var(--semi-color-fill-0)',
    height: '36px',
    display: 'flex',
    'padding-left': '32px',
    color: 'var(--semi-color-link)',
    'align-items': 'center',
    cursor: 'pointer',
    'border-top': '1px solid var(--semi-color-border)',
    'border-radius': '0 0 6px 6px',
};

export default function SelectDemo() {
    return (
        <div>
            <p>outerBottomSlot:</p>
            <Select
                aria-label="outerBottomSlot"
                style={{ width: '300px' }}
                dropdownStyle={{ width: '180px' }}
                maxHeight={150}
                outerBottomSlot={
                    <div style={outSlotStyle}>
                        <span style={{ color: 'var(--semi-color-link)' }}>未找到应用?</span>
                    </div>
                }
                placeholder="自定义外侧底部slot,始终显示"
                defaultOpen
                autoAdjustOverflow={false}
                position="bottom"
            >
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying">剪映</Select.Option>
                <Select.Option value="duoshan">多闪</Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>
            <p style={{ 'margin-top': '200px' }}>innerBottomSlot:</p>
            <Select aria-label="innerBottomSlot" style={{ width: '300px' }} dropdownStyle={{ width: '180px' }} maxHeight={150} innerBottomSlot={<div style={innerSlotStyle}>点击加载更多</div>} placeholder="自定义内侧底部slot,滚动至底部显示">
                <Select.Option value="douyin">抖音</Select.Option>
                <Select.Option value="ulikecam">轻颜相机</Select.Option>
                <Select.Option value="jianying">剪映</Select.Option>
                <Select.Option value="duoshan">多闪</Select.Option>
                <Select.Option value="xigua">西瓜视频</Select.Option>
            </Select>
        </div>
    );
}

通过 outerTopSlot 将内容插入顶部插槽

代码
import { createSignal } from 'solid-js';
import { RadioGroup, Select } from '@fibrous/semi-ui-solid';

interface Item {
    value: string;
    label: string;
}

const list: Record<string, Item[]> = {
    component: [
        { value: 'select', label: '选择器' },
        { value: 'tabs', label: '标签' },
        { value: 'avatar', label: '头像' },
        { value: 'button', label: '按钮' },
    ],
    design: [
        { value: 'color', label: '颜色' },
        { value: 'dark', label: '暗色模式' },
        { value: 'icon', label: '图标' },
        { value: 'font', label: '字体' },
    ],
    feedback: [
        { value: 'faq', label: '常见问题' },
        { value: 'join', label: '加入用户群' },
        { value: 'hornbill', label: '犀鸟反馈问题' },
    ],
};

const categoryOptions = [
    { value: 'component', label: '组件' },
    { value: 'design', label: '设计' },
    { value: 'feedback', label: '反馈' },
];

export default function SelectDemo() {
    const [key, setKey] = createSignal('component');
    const [value, setValue] = createSignal<Item | undefined>({ value: 'faq', label: '常见问题' });

    // The radio group switches with arrow keys, so categories need no tabpanel association
    return (
        <Select
            aria-label="选项"
            defaultOpen
            autoAdjustOverflow={false}
            value={value()}
            onChangeWithObject
            onChange={(obj) => setValue(obj)}
            style={{ width: '200px' }}
            outerTopSlot={
                <div style={{ padding: '8px 12px', 'border-bottom': '0.5px solid var(--semi-color-border)' }}>
                    <RadioGroup type="button" buttonSize="small" aria-label="选项分类" options={categoryOptions} value={key()} onChange={(e) => setKey(String(e.target.value))} />
                </div>
            }
            optionList={list[key()]}
        />
    );
}

受控组件

传入 value 时 Select 为受控组件,所选中的值完全由 value 决定。

代码
import { createSignal } from 'solid-js';
import { Select } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    const [value, setValue] = createSignal<string | undefined>('xigua');
    return (
        <Select aria-label="业务线" value={value()} style={{ width: '300px' }} onChange={(v) => setValue(v)} placeholder="受控的Select">
            <Select.Option value="douyin">抖音</Select.Option>
            <Select.Option value="ulikecam">轻颜相机</Select.Option>
            <Select.Option value="jianying">剪映</Select.Option>
            <Select.Option value="xigua">西瓜视频</Select.Option>
        </Select>
    );
}

动态修改 Options

如果需要动态更新 Options,应该使用受控的 value



代码
import { For, createSignal } from 'solid-js';
import { Button, Select } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    const [options, setOptions] = createSignal([1, 2, 3, 4]);
    const add = () => {
        const length = Math.ceil(Math.random() * 10);
        setOptions(Array.from({ length }, (_, i) => i + 1));
    };
    return (
        <>
            <Select aria-label="数字" style={{ width: '180px' }} placeholder="请选择" value={4}>
                <For each={options()}>{(option) => <Select.Option value={option}>{option}</Select.Option>}</For>
            </Select>
            <br />
            <br />
            <Button onClick={add}>changeOptions Dynamic</Button>
        </>
    );
}

联动

使用受控 value,实现不同 Select 之间的联动。如果是带有层级关系的复杂联动建议直接使用Cascader组件

代码
import { For, createSignal } from 'solid-js';
import { Select } from '@fibrous/semi-ui-solid';

const provinces = ['四川', '广东'];
const maps: Record<string, string[]> = {
    四川: ['成都', '都江堰'],
    广东: ['广州', '深圳', '东莞'],
};

export default function SelectDemo() {
    const [province, setProvince] = createSignal(provinces[0]);
    const citys = () => maps[province()];
    const [city, setCity] = createSignal<string | undefined>(citys()[0]);

    const provinceChange = (newProvince?: string) => {
        if (!newProvince) {
            return;
        }
        setProvince(newProvince);
        setCity(maps[newProvince][0]);
    };

    return (
        <>
            <Select aria-label="省份" style={{ width: '150px', margin: '10px' }} onChange={provinceChange} value={province()}>
                <For each={provinces}>{(pro) => <Select.Option value={pro}>{pro}</Select.Option>}</For>
            </Select>
            <Select aria-label="城市" style={{ width: '150px', margin: '10px' }} value={city()} onChange={(v) => setCity(v)}>
                <For each={citys()}>{(c) => <Select.Option value={c}>{c}</Select.Option>}</For>
            </Select>
        </>
    );
}

开启搜索

将 filter 置为 true,开启搜索能力。默认搜索策略将为 input 输入值与 option 的 label 值进行 include 对比
默认情况下,多选选中后会自动清空搜索关键字。若你希望保留,可以通过 autoClearSearchValue 设为 false 关闭默认行为(v2.3 后提供)

搜索框位置

默认搜索框展示于 Select 的 Trigger 触发器上。通过 searchPosition 可以指定不同的位置,可选 dropdown、trigger。 在 v2.61.0后提供若希望定制位于 dropdown 中的 Input 搜索框的 placeholder,可以通过 searchPlaceholder 控制
若 searchPosition 值为 trigger,当showClear=true 时,点击Trigger区域的清空按钮,将同时清空已选项以及搜索框中的文本
若 searchPosition 值为 dropdown,当showClear=true 时,点击Trigger区域清空按钮,仅清空已选项。点击搜索框中的清空按钮,仅清空搜索文本



代码
import { Select } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    return (
        <>
            <Select aria-label="单选" filter searchPosition="dropdown" style={{ width: '200px' }} defaultValue="ulikecam" placeholder="我的搜索框在下拉菜单中" searchPlaceholder="带搜索功能的单选">
                <Select.Option value="douyin" label="抖音" />
                <Select.Option value="ulikecam" label="轻颜相机" />
                <Select.Option value="jianying" label="剪映" />
                <Select.Option value="xigua" label="西瓜视频" />
            </Select>
            <br />
            <br />
            <Select aria-label="多选" filter searchPosition="dropdown" multiple style={{ width: '300px' }} defaultValue={['semi-1']} placeholder="我的搜索框在下拉菜单中" searchPlaceholder="带搜索功能的多选" autoClearSearchValue={false}>
                <Select.Option value="semi-0" label="Semi-0" />
                <Select.Option value="semi-1" label="Semi-1" />
                <Select.Option value="semi-2" label="Semi-2" />
                <Select.Option value="semi-3" label="Semi-3" />
                <Select.Option value="semi-4" label="Semi-4" />
            </Select>
        </>
    );
}

远程搜索

带有远程搜索,防抖请求,加载状态的多选示例
通过filter开启搜索能力
将remote设置为 true 关闭对当前数据的筛选过滤通过动态更新optionList更新下拉菜单中的备选项

使用受控的 value 属性,通过 onSearch 获取搜索内容

自定义搜索逻辑

可以将 filter 置为自定义函数,定制你想要的搜索策略
如下例子,选项 label 值都是大写,默认的检索策略是字符串 include 对比,会区分大小写。
通过传入自定义 filter 函数,检索时输入小写字母也能搜到相应内容。

代码
import { Select, type SelectOptionProps } from '@fibrous/semi-ui-solid';

function searchLabel(sugInput: string, option: SelectOptionProps) {
    return String(option.label).toUpperCase().includes(sugInput.toUpperCase());
}

export default function SelectDemo() {
    return (
        <Select aria-label="业务线" filter={searchLabel} style={{ width: '180px' }} placeholder="try douyin">
            <Select.Option value="douyin" label="douyin" />
            <Select.Option value="ulikecam" label="HOTSOON" />
            <Select.Option value="jianying" label="PIPIXIA" />
            <Select.Option value="xigua" label="XIGUA" />
        </Select>
    );
}

自定义已选项标签渲染

默认情况下,选中选项后会将 Option 的 children 回填到选择框中,没有 children 时回填 label
但你可以通过 renderSelectedItem 自定义选择框中已选项标签的渲染结构

  • 单选时 renderSelectedItem(optionNode:object) => content:JSX.Element
  • 多选时 renderSelectedItem(optionNode:object, { index:number, disabled:boolean, onClose:function }) => { isRenderInTag:bool, content:JSX.Element }
    • isRenderInTag 为 true 时,会自动将 content 包裹在 Tag 中渲染(带有背景色以及关闭按钮)
    • isRenderInTag 为 false 时,将直接渲染返回的 content
  • 写在 Select.Option 上的其他属性(如示例中的 email)会随 optionNode 一起传入
代码
import { For } from 'solid-js';
import { Avatar, Select, Tag, type SelectRenderMultipleSelectedItemFn, type SelectRenderSingleSelectedItemFn } from '@fibrous/semi-ui-solid';

interface ListItem {
    name: string;
    email: string;
    avatar: string;
}

const list: ListItem[] = [
    {
        name: '夏可漫',
        email: 'xiakeman@example.com',
        avatar: '/assets/placeholder/avatar-a.svg',
    },
    {
        name: '申悦',
        email: 'shenyue@example.com',
        avatar: '/assets/root-web-sites/bag.jpeg',
    },
    {
        name: '曲晨一',
        email: 'quchenyi@example.com',
        avatar: '/assets/placeholder/avatar-c.svg',
    },
    {
        name: '文嘉茂',
        email: 'wenjiamao@example.com',
        avatar: '/assets/root-web-sites/6fbafc2d-e3e6-4cff-a1e2-17709c680624.png',
    },
];

const optionStyle = {
    display: 'flex',
    'padding-left': '24px',
    'padding-top': '10px',
    'padding-bottom': '10px',
};

const renderSelectedItem: SelectRenderSingleSelectedItemFn = (optionNode) => (
    <div style={{ display: 'flex', 'align-items': 'center' }}>
        <Avatar src={String(optionNode.avatar)} alt="" size="small" />
        <span style={{ 'margin-left': '8px' }}>{String(optionNode.email)}</span>
    </div>
);

const renderMultipleWithTag =
    (avatarShape: 'circle' | 'square'): SelectRenderMultipleSelectedItemFn =>
    (optionNode, { onClose }) => ({
        isRenderInTag: false,
        content: (
            <Tag avatarSrc={String(optionNode.avatar)} avatarShape={avatarShape} closable onClose={onClose} size="large">
                {String(optionNode.name)}
            </Tag>
        ),
    });

const renderCustomOption = (item: ListItem) => (
    <Select.Option {...item} value={item.name} style={optionStyle} showTick>
        <Avatar size="small" src={item.avatar} alt="" />
        <div style={{ 'margin-left': '8px' }}>
            <div style={{ 'font-size': '14px' }}>{item.name}</div>
            <div style={{ color: 'var(--color-text-2)', 'font-size': '12px', 'line-height': '16px', 'font-weight': 'normal' }}>{item.email}</div>
        </div>
    </Select.Option>
);

export default function SelectDemo() {
    return (
        <>
            <Select placeholder="请选择" style={{ width: '280px', height: '40px' }} onChange={(v) => console.log(v)} defaultValue="申悦" renderSelectedItem={renderSelectedItem}>
                <For each={list}>{renderCustomOption}</For>
            </Select>
            <Select placeholder="请选择" maxTagCount={2} style={{ width: '280px', 'margin-top': '20px' }} onChange={(v) => console.log(v)} defaultValue={['申悦', '曲晨一']} multiple renderSelectedItem={renderMultipleWithTag('circle')}>
                <For each={list}>{renderCustomOption}</For>
            </Select>
            <Select placeholder="请选择" maxTagCount={2} style={{ width: '280px', 'margin-top': '20px' }} onChange={(v) => console.log(v)} defaultValue={['申悦', '曲晨一']} multiple renderSelectedItem={renderMultipleWithTag('square')}>
                <For each={list}>{renderCustomOption}</For>
            </Select>
        </>
    );
}

自定义弹出层样式

你可以通过 dropdownClassName、dropdownStyle 控制弹出层的样式
例如当自定义弹出层的宽度时,可以通过 dropdownStyle 传入 width

代码
import { Select } from '@fibrous/semi-ui-solid';

export default function SelectDemo() {
    return (
        <Select aria-label="业务线" placeholder="自定义弹出层样式的" style={{ width: '180px' }} dropdownStyle={{ width: '250px' }} dropdownClassName="test">
            <Select.Option value="douyin">抖音</Select.Option>
            <Select.Option value="ulikecam">轻颜相机</Select.Option>
            <Select.Option value="jianying">剪映</Select.Option>
            <Select.Option value="xigua">西瓜视频</Select.Option>
        </Select>
    );
}

获取选项的其他属性

默认情况下onChange只能拿到 value,如果需要拿选中节点的其他属性,可以使用onChangeWithObject属性
此时onChange函数的入参将会是 object,包含 option 的各种属性,例如 onChange({ value, label, ...rest })

onChange回调:

onChange回调:

代码
import { createSignal } from 'solid-js';
import { Select, TextArea } from '@fibrous/semi-ui-solid';

interface Item {
    value: string;
    label: string;
    type: number;
}

const list: Item[] = [
    { value: 'douyin', label: '抖音', type: 1 },
    { value: 'ulikecam', label: '轻颜相机', type: 2 },
    { value: 'jianying', label: '剪映', type: 3 },
    { value: 'toutiao', label: '今日头条', type: 4 },
];

export default function SelectDemo() {
    const [cbValue, setCbValue] = createSignal<Item>();
    const [multipleCbValue, setMultipleCbValue] = createSignal<Item[]>();

    const onChange = (value?: Item) => {
        setCbValue(value);
        console.log(value);
    };

    const onMultipleChange = (value: Item[]) => {
        setMultipleCbValue(value);
        console.log(value);
    };

    return (
        <div>
            <div>
                <Select aria-label="单选" style={{ width: '150px' }} onChangeWithObject optionList={list} placeholder="单选" defaultValue={list[0]} onChange={onChange} />
                <h4 id="select-on-change-with-object-single">onChange回调:</h4>
                <TextArea aria-labelledby="select-on-change-with-object-single" style={{ width: '320px', 'margin-bottom': '48px' }} autosize value={JSON.stringify(cbValue())} rows={2} />
            </div>
            <div>
                <Select aria-label="多选" style={{ width: '320px' }} onChangeWithObject multiple optionList={list} onChange={onMultipleChange} placeholder="多选" />
                <h4 id="select-on-change-with-object-multiple">onChange回调:</h4>
                <TextArea aria-labelledby="select-on-change-with-object-multiple" style={{ width: '320px' }} autosize value={JSON.stringify(multipleCbValue())} />
            </div>
        </div>
    );
}

创建条目

设置allowCreate,可以创建并选中选项中不存在的条目

允许通过 renderCreateItem 自定义创建标签时的内容显示(通过返回 JSX 元素,注意你需要自定义样式),不提供时显示语言包中的「创建」加输入内容。函数的参数为 (inputValue, isFocus, style),与虚拟化结合使用时必须把 style 用在自定义元素上

可以配合defaultActiveFirstOption属性使用,自动选中第一项,当输入完内容直接回车时,可立即创建



With renderCreateItem
代码
import { Select } from '@fibrous/semi-ui-solid';

const optionList = [
    { value: 'douyin', label: '抖音' },
    { value: 'ulikecam', label: '轻颜相机' },
    { value: 'jianying', label: '剪映' },
    { value: 'toutiao', label: '今日头条' },
];

export default function SelectDemo() {
    return (
        <>
            <Select aria-label="业务线" style={{ width: '400px' }} optionList={optionList} allowCreate multiple filter onChange={(v) => console.log(v)} defaultActiveFirstOption />
            <br />
            <br />
            <Select
                aria-label="业务线"
                style={{ width: '400px' }}
                optionList={optionList}
                allowCreate
                multiple
                filter
                placeholder="With renderCreateItem"
                renderCreateItem={(input, _isFocus, style) => <div style={{ padding: '10px', ...style }}>Create Item:{input}</div>}
                onChange={(v) => console.log(v)}
                defaultActiveFirstOption
            />
        </>
    );
}

虚拟化

传入virtualize时开启列表虚拟化,用于大量 Option 节点的情况优化性能
virtualize 是一个包含下列值的对象:

  • height: Option 列表高度值,默认 270 (v2.20.8 前为 300)
  • width: Option 列表宽度值,默认 100%
  • itemSize: 每行 Option 的高度,必传
代码
import { Select } from '@fibrous/semi-ui-solid';

const newOptions = Array.from({ length: 3000 }, (_, i) => ({ label: `option-${i}`, value: i }));
const virtualize = {
    height: 270,
    width: '100%',
    itemSize: 36, // px
};

export default function SelectDemo() {
    return <Select aria-label="virtualize" placeholder="拥有3k个Option的Select" style={{ width: '260px' }} filter virtualize={virtualize} optionList={newOptions} />;
}

自定义触发器

如果 Select 默认的触发器样式满足不了你的需求,可以用triggerRender自定义选择框的展示
如果想保留搜索筛选能力,又不希望自己渲染 Input 相关的结构,可以同时通过 searchPosition='dropdown',将默认的搜索框置于下拉列表中

triggerRender 入参如下

interface TriggerRenderProps {
    value: SelectedItemProps[]; // 当前所有已选中的 option
    inputValue: string; // 当前搜索关键字
    onSearch: (inputValue: string) => void; // 更新搜索关键字,用于把自定义 Input 的值同步给 Select 内部。需同时设置 filter
    onRemove: (option: OptionProps) => void; // 移除单个已选项,option 至少需带有 label、value
    onClear: (e?: MouseEvent) => void; // 清空已选值。绑在触发器内的控件上时传入点击事件,避免这次点击重新打开下拉列表
    disabled: boolean; // 是否禁用 Select
    placeholder: JSX.Element; // Select 的 placeholder
    componentProps: Record<string, unknown>; // 所有传给 Select 的 props
}

不同背景色的触发器



使用 circle Tag 作为触发器

代码
import { createSignal } from 'solid-js';
import { IconAppCenter, IconChevronDown } from '@fibrous/semi-icons-solid';
import { Select, Tag, type SelectTriggerRenderProps } from '@fibrous/semi-ui-solid';

const list = [
    { value: 'douyin', label: '抖音' },
    { value: 'ulikecam', label: '轻颜相机' },
    { value: 'jianying', label: '剪映' },
    { value: 'toutiao', label: '今日头条' },
];

const triggerRender = (props: SelectTriggerRenderProps) => (
    <div
        style={{
            'min-width': '112px',
            'background-color': 'var(--semi-color-primary-light-default)',
            height: '32px',
            display: 'flex',
            'align-items': 'center',
            'padding-left': '12px',
            'border-radius': '3px',
            color: 'var(--semi-color-primary)',
        }}
    >
        <div style={{ 'font-weight': 600, 'flex-shrink': 0, 'font-size': '14px' }}>业务线</div>
        <div
            style={{
                margin: '4px',
                'white-space': 'nowrap',
                'text-overflow': 'ellipsis',
                'flex-grow': 1,
                overflow: 'hidden',
            }}
        >
            {props.value.map((item) => item.label).join(' , ')}
        </div>
        <IconAppCenter style={{ 'margin-right': '8px', 'flex-shrink': 0 }} />
    </div>
);

const triggerRender2 = (props: SelectTriggerRenderProps) => (
    <div
        style={{
            margin: '4px',
            'white-space': 'nowrap',
            'text-overflow': 'ellipsis',
            'flex-grow': 1,
            overflow: 'hidden',
            display: 'flex',
            'align-items': 'center',
        }}
    >
        <Tag size="large" color="cyan" shape="circle" suffixIcon={<IconChevronDown />}>
            {props.value.map((item) => item.label).join(' / ')}
        </Tag>
    </div>
);

export default function SelectDemo() {
    const [valList, setValList] = createSignal(['douyin', 'ulikecam']);
    return (
        <div>
            <h4>不同背景色的触发器</h4>
            <Select aria-label="不同背景色的触发器" value={valList()} triggerRender={triggerRender} optionList={list} onChange={(value) => setValList(value)} multiple filter searchPosition="dropdown" style={{ width: '240px' }} />
            <br />
            <br />
            <h4>使用 circle Tag 作为触发器</h4>
            <Select aria-label="circle Tag 触发器" value={valList()} onChange={(value) => setValList(value)} triggerRender={triggerRender2} optionList={list} filter multiple searchPosition="dropdown" style={{ width: '240px', 'margin-top': '20px', outline: 0 }} />
        </div>
    );
}

下例是更复杂的例子:复用了 TagInput 拖拽排序能力,通过自定义触发器为 Select 增加排序

可对已选项拖拽重新排序的 Select

代码
import { createSignal } from 'solid-js';
import { Select, TagInput, type SelectTriggerRenderProps } from '@fibrous/semi-ui-solid';

const list = [
    { value: 'douyin', label: '抖音' },
    { value: 'ulikecam', label: '轻颜相机' },
    { value: 'jianying', label: '剪映' },
    { value: 'toutiao', label: '今日头条' },
];

export default function SelectDemo() {
    const [valList, setValList] = createSignal(['douyin', 'ulikecam']);
    const [inputVal, setInputVal] = createSignal('');

    const handleSort = (currentLabels: string[]) => {
        setValList(currentLabels.flatMap((item) => list.find((i) => i.label === item)?.value ?? []));
    };

    const triggerRender = (props: SelectTriggerRenderProps) => (
        <div role="presentation" onKeyDown={(e) => e.stopPropagation()}>
            <TagInput
                draggable
                allowDuplicates={false}
                value={props.value.map((item) => String(item.label))}
                inputValue={inputVal()}
                onInputChange={(word) => {
                    props.onSearch(word);
                    setInputVal(word);
                }}
                onChange={handleSort}
                showClear
            />
        </div>
    );

    return (
        <>
            <h4>可对已选项拖拽重新排序的 Select </h4>
            <Select aria-label="可拖拽排序的 Select" value={valList()} triggerRender={triggerRender} optionList={list} onChange={(value) => setValList(value)} multiple filter style={{ width: '240px' }} />
        </>
    );
}

自定义候选项渲染

  • 简单的自定义:通过 Option 的 children 传入 JSX 元素,你可以控制候选项的渲染,此时内容会自动带上内边距、背景色等样式;需要搜索时同时传入文本 label
  • 完全自定义:通过传入renderOptionItem,你可以完全接管列表中候选项的渲染,并且从回调入参中,获取到相关的状态值。实现更高自由度的结构渲染
    入参为 { id, value, label, selected, focused, disabled, inputValue, class, style, onClick, onMouseEnter },以及写在 Option 上的其他属性。入参的各项会随状态更新,不要解构它,在 JSX 中通过 props.xxx 读取。注意事项:
    1. 入参的 style 需在 wrapper dom 上进行消费,否则在虚拟化场景下会无法正常使用
    2. 入参的 class、onMouseEnter 需在 wrapper dom 上进行消费,否则上下键盘操作时显示会有问题
    3. 选中(selected)、聚焦(focused)、禁用(disabled)等状态的样式需自行加上,你可以从入参中获取到相对的 boolean 值
    4. 点击选中需要把入参的 onClick 绑定到 wrapper dom 上
    5. 入参的 id 需与 role="option"、aria-selected、aria-disabled 一起写在 wrapper dom 上:Select 的 aria-activedescendant 指向该 id,读屏才能读出方向键移到的候选项;打开下拉或方向键移动时也按该 id 把候选项滚进列表可视区


多选
代码
import { createSignal } from 'solid-js';
import clsx from 'clsx';
import { Checkbox, Highlight, Select, type SelectOptionRenderProps } from '@fibrous/semi-ui-solid';

const optionList = [
    { value: 'douyin', label: '抖音', otherKey: 0 },
    { value: 'ulikecam', label: '轻颜相机', disabled: true, otherKey: 1 },
    { value: 'jianying', label: '剪映', otherKey: 2 },
    { value: 'toutiao', label: '今日头条', otherKey: 3 },
];

export default function SelectDemo() {
    const [inputValue, setInputValue] = createSignal('');

    // Put style on the outer DOM or the virtual list breaks; add selected/focused/disabled styles from props;
    // bind onMouseEnter and class on the outer DOM or arrow keys display wrongly; put id there so screen
    // readers announce the option the arrow keys reach
    const renderOptionItem = (props: SelectOptionRenderProps) => (
        <div
            id={props.id}
            role="option"
            aria-selected={props.selected}
            aria-disabled={props.disabled}
            style={props.style}
            class={clsx(props.class, 'custom-option-render', {
                'custom-option-render-focused': props.focused,
                'custom-option-render-disabled': props.disabled,
                'custom-option-render-selected': props.selected,
            })}
            onClick={(e) => props.onClick(e)}
            onMouseEnter={(e) => props.onMouseEnter(e)}
        >
            <Checkbox checked={props.selected} />
            <div class="option-right">
                <Highlight sourceString={String(props.label)} searchWords={[inputValue()]} />
            </div>
        </div>
    );

    return (
        <>
            <Select aria-label="单选" filter placeholder="单选" onSearch={(v) => setInputValue(v)} dropdownClassName="components-select-demo-renderOptionItem" optionList={optionList} style={{ width: '180px' }} renderOptionItem={renderOptionItem} />
            <br />
            <br />
            <Select aria-label="多选" filter placeholder="多选" multiple onSearch={(v) => setInputValue(v)} dropdownClassName="components-select-demo-renderOptionItem" optionList={optionList} style={{ width: '320px' }} renderOptionItem={renderOptionItem} />
        </>
    );
}
.components-select-demo-renderOptionItem {
    .custom-option-render {
        display: flex;
        font-size: 14px;
        line-height: 20px;
        word-break: break-all;
        padding-left: 12px;
        padding-right: 12px;
        padding-top: 8px;
        padding-bottom: 8px;
        color: var(--semi-color-text-0);
        position: relative;
        display: flex;
        align-items: center;
        cursor: pointer;
        box-sizing: border-box;
        .option-right {
            margin-left: 8px;
            display: inline-flex;
            align-items: center;
        }
        &:active {
            background-color: var(--semi-color-fill-1);
        }
        &-focused {
            background-color: var(--semi-color-fill-0);
        }
        &-selected {
            //font-weight: 700;
        }
        &-disabled {
            color: var(--semi-color-disabled-text);
            cursor: not-allowed;
        }
        &:first-of-type {
            margin-top: 4px;
        }
        &:last-of-type {
            margin-bottom: 4px;
        }
    }
}

API 参考

Select Props

Props

属性说明类型默认值
allowCreate是否允许创建下拉列表中不存在的条目,需配合 filter 使用。创建时触发 onCreate,条目的渲染可用 renderCreateItem 定制。booleanfalse
aria-labelcombobox 的可访问名称:搜索框在选择框内时是搜索框,否则是触发器。优先级低于 aria-labelledby,高于 insetLabel。string-
arrowIcon自定义右侧下拉箭头图标,传 null 不显示图标。showClear 开启且有已选值时,hover 优先显示清除图标。JSX.Element-
autoAdjustOverflow弹出层被遮挡时是否自动调整方向。booleantrue
autoClearSearchValue多选并开启搜索时,选中选项后是否自动清空搜索关键字。booleantrue
autofocus挂载后是否自动聚焦。boolean-
borderless是否使用无边框样式。booleanfalse
children选项,直接子元素必须是 Select.Option 或 Select.OptGroup。optionList 非空时优先于 children;分组只能用 Select.OptGroup 声明。JSX.Element-
clearIcon自定义清除按钮的图标,showClear 为 true 时有效。JSX.Element-
clickToHide下拉列表已展开时,点击选择框是否收起它。boolean-
defaultActiveFirstOption没有选中值时,打开下拉列表是否默认高亮第一个未禁用的选项(按 Enter 可直接选中)。已有选中值时总是高亮第一个已选的选项。booleantrue
defaultOpen是否默认展开下拉列表。booleanfalse
defaultValue初始选中的值,只在挂载时读取;多选时为数组。设置 onChangeWithObject 时为选项对象,须含 value 与 label。TypeSelectValue<T>-
disabled是否禁用。未设置时使用所在 InputGroup 的值。boolean-
dropdownClassName下拉弹出层的类名。string-
dropdownMatchSelectWidth下拉弹出层的最小宽度是否等于选择框。booleantrue
dropdownStyle下拉弹出层的样式,例如设置 width。根元素上的 style 不会传给弹出层。JSX.CSSProperties | string-
ellipsisTrigger多选并设置 maxTagCount 时,宽度不足是否截断最后一个标签的内容。开启后有一定性能损耗,不建议在大表单中使用。booleanfalse
emptyContent没有选项可显示时的内容,传 null 则不显示下拉列表的空状态。JSX.Element-
expandRestTagsOnClick多选并设置 maxTagCount 时,下拉列表展开期间是否显示全部标签。booleanfalse
filter是否可搜索。传入 true 时开启搜索,默认策略为选项 label(含所在分组的标题)包含输入值;传入函数时接收输入值与选项,返回 true 表示命中。Typeboolean | ((inputValue: string, option: OptionProps) => boolean)false
id触发器的 id,选项的 id 由它派生。未传时由组件生成。string-
innerBottomSlot渲染在下拉列表底部、optionList 内部的内容,滚动到底部时展现。JSX.Element-
innerTopSlot渲染在下拉列表顶部、optionList 内部的内容,滚动到顶部时展现。JSX.Element-
inputProps开启搜索时,搜索框 Input 的额外属性,inputRef 接收其原生 input 元素。类型不含 value、onFocus 与 onChange,它们由 Select 自己处理。SelectInputProps-
insetLabel选择框内的内嵌标签,同时作为 combobox 与下拉列表的名称来源。有 prefix 时不渲染,也不作名称。JSX.Element-
insetLabelIdinsetLabel 元素的 id,combobox 的 aria-labelledby 指向它。未传时由组件生成。string-
loading下拉列表是否显示加载动画。boolean-
max最多可选几项,仅多选时生效;超出后无法继续选中并触发 onExceed。number-
maxHeight下拉列表的最大高度,数字按像素处理,字符串原样使用。虚拟列表的 height 大于默认值时,需同时设置为相同的值,避免出现双滚动条。string | number270
maxTagCount多选时最多显示的标签数,超出的部分以 +N 显示。number-
motion弹出层是否使用动画。booleantrue
multiple是否多选。它决定 onChange 的参数类型:多选为数组,单选为单个值或 undefined。Multiplefalse
onBlur焦点离开 Select 时的回调;点击外部关闭下拉列表时参数为该次 mousedown 的原生事件。Type(e: FocusEvent | MouseEvent) => void-
onChange选中值变化时的回调,单选时参数为 T 或 undefined,多选时为 T[]。Type(value: SelectChangeValue<T, Multiple>) => void-
onChangeWithObject是否将选项对象作为值:设为 true 时,onChange 的参数以及 value、defaultValue 使用选项对象 { value, label, ...rest },对象须含 value 与 label。booleanfalse
onClear点击清除按钮时的回调。Type() => void-
onCreateallowCreate 为 true,创建条目时的回调。Type(option: OptionProps) => void-
onDeselect取消选中时的回调,仅多选时有效,value 总是选项的原始 value。Type(value: OptionProps['value'], option: OptionProps) => void-
onDropdownVisibleChange下拉列表展开或收起时的回调。Type(visible: boolean) => void-
onExceed试图选择的数量超出 max 时的回调,仅多选时生效。Type(option: OptionProps) => void-
onFocus焦点从外部进入 Select 时的回调。Type(e: FocusEvent) => void-
onListScroll候选项列表滚动时的回调,虚拟列表的滚动同样触发。Type(e: Event) => void-
onSearch搜索关键字变化时的回调。第二个参数是引起变化的事件;点击外部关闭后清空搜索时是原生 MouseEvent,通过 ref 的 search 调用时按传入的事件,从 triggerRender 调用 onSearch 时没有事件。Type(value: string, event?: InputChangeEvent | KeyboardEvent | MouseEvent) => void-
onSelect选项被选中时的回调,value 总是选项的原始 value(设置 onChangeWithObject 时也是)。Type(value: OptionProps['value'], option: OptionProps) => void-
optionList以数组形式传入选项,每项须带 value 与 label,其余字段随选项一起回传。数组非空时优先于 children;不支持分组。OptionProps[]-
outerBottomSlot渲染在下拉列表底部、与 optionList 平级的内容,始终展现。JSX.Element-
outerTopSlot渲染在下拉列表顶部、与 optionList 平级的内容,始终展现。JSX.Element-
placeholder选择框中没有选中值时显示的占位内容。JSX.Element""
position弹出层展开的位置,可选值同 Tooltip 的 position。Position-
prefix选择框的前缀。文本或图标会自动带上左右间隔,自定义元素的左右间隔为 0。JSX.Element-
preventScroll聚焦时是否阻止浏览器滚动到该元素,作用于组件内部的 focus 调用。boolean-
ref接收触发器元素,元素上带有下方 Methods 中的方法;它的 focus() 聚焦 combobox(搜索框在选择框内时是搜索框),不展开下拉列表。TypeRef<SelectElement>-
remote是否开启远程搜索。开启后输入内容变化时不再在本地筛选,由使用方根据 onSearch 更新 optionList。booleanfalse
renderCreateItemallowCreate 为 true 时自定义创建条目的渲染,参数为输入值、是否聚焦和行样式。与虚拟化结合使用时必须把 style 用在自定义元素上。不设置时显示语言包中的「创建」加输入内容。Type(inputValue: OptionProps['value'], focus: boolean, style?: JSX.CSSProperties) => JSX.Element-
renderOptionItem完全自定义下拉列表中候选项的渲染,参数含 id、label、value、selected、focused、disabled、inputValue、class、style、onClick、onMouseEnter。id 需与 role、aria-selected 一起写在最外层元素上,class、style、onMouseEnter 也需在最外层元素上使用。Type(props: OptionRenderProps) => JSX.Element-
renderSelectedItem自定义选择框中已选项的渲染。单选时返回内容;多选时第二个参数含 index、disabled 与 onClose,返回 { isRenderInTag, content },isRenderInTag 为 true 时内容会包在 Tag 中。TypeByMultiple<Multiple, RenderSingleSelectedItemFn, RenderMultipleSelectedItemFn>-
restTagsPopoverProps显示剩余标签的 Popover 的配置,可设置 position、trigger 等。PopoverProps-
searchPlaceholder搜索框在下拉列表中(searchPosition 为 dropdown)时,搜索框的占位文字。string-
searchPosition开启搜索时搜索框的位置:trigger 在选择框内,dropdown 在下拉列表顶部。trigger 时点击选择框的清除按钮同时清空搜索文本,dropdown 时只清空已选项。'trigger' | 'dropdown'"trigger"
showArrow是否显示右侧下拉箭头。booleantrue
showClear是否显示清除按钮。有已选值或搜索文本且未禁用时,清除按钮出现在选择框内。booleanfalse
showRestTagsPopover超出 maxTagCount 后,hover 到 +N 时是否用 Popover 显示剩余的标签。booleanfalse
size大小,可选 small、default、large。所在 InputGroup 的 size 优先于它。SelectSize-
spacing弹出层与选择框的距离,单位为像素。number | { x: number; y: number }-
stopPropagation是否阻止弹出层上的点击事件冒泡。booleantrue
suffix选择框的后缀。文本或图标会自动带上左右间隔,自定义元素的左右间隔为 0。JSX.Element-
triggerRender自定义触发器的渲染。参数含 value、inputValue、onSearch、onClear、onRemove、disabled、placeholder 与 componentProps。Type(props: TriggerRenderProps) => JSX.Element-
validateStatus校验状态,可选 default、warning、error,只影响背景色等样式。ValidateStatus-
value当前选中的值,多选时为数组。传入后即为受控组件,配合 onChange 使用。设置 onChangeWithObject 时为选项对象,须含 value 与 label。TypeSelectValue<T>-
virtualize开启列表虚拟化,用于选项很多时优化性能。itemSize 为每行高度,必传;height 默认 270,width 默认 100%。VirtualizeProps-

Methods

方法说明类型
clearInput清空搜索框的值。Type() => void
close关闭下拉列表。Type() => void
deselectAll清空所有已选项。Type() => void
open展开下拉列表。Type() => void
rePosition按触发器当前位置立即重新计算下拉层位置。下拉层已随滚动、窗口尺寸变化、触发器移动以及触发器或列表的尺寸变化自动重新定位,一般无需调用;跟随触发器移动会晚一帧,需要同一帧内对齐时调用。Type() => void
search按输入了该值来搜索,该值会被置给搜索框;不传事件时 onSearch 的第二个参数为 undefined。Type(value: string, event?: InputChangeEvent | KeyboardEvent | MouseEvent) => void
selectAll选中所有可见且未禁用的选项,仅多选时有效。Type() => void

class、style、data-* 与原生属性和事件落在根元素上,style 不会传给下拉弹出层(用 dropdownStyle);设置 triggerRender 后根元素不再带有 Select 自身的样式类。aria-label、aria-labelledby、aria-describedby、aria-errormessage、aria-invalid、aria-required 落在 combobox 上;可搜索时搜索框挂载后它就是 combobox,这些属性随之移过去。ref 得到根元素,见下文 Methods。

Option Props


选项以 value 作为标识,同一个 Select 内 value 不应重复;label 可以重复

Props

属性说明类型默认值
children选项在下拉列表与回显中的内容,设置后代替 label 显示,可以是任意 JSX。它不参与搜索,也不作为回调里的 label;需要搜索时同时传 label。JSX.Element-
class选项行的类名。string-
disabled是否禁用。boolean-
label选项的文本,用于搜索、高亮与回调;没有 children 时作为显示内容。未设置时取 value。string | number-
showTick选中时是否显示 √ 图标,默认显示。boolean-
style选项行的样式。JSX.CSSProperties | string-
value选项的值,用来识别选项,每个选项都应声明。string | number-

Select.Option 在 Select 内部只被读取,它的属性成为选项记录,自身不渲染。class、style、data-* 落在选项行上;其他未声明的属性不会进入 DOM,随选项记录在 onSelect、onDeselect、renderSelectedItem 与 renderOptionItem 中,以及开启 onChangeWithObject 后的 onChange 入参中回传。

OptGroup Props


Props

属性说明类型默认值
children分组内的选项,只能是 Select.Option。JSX.Element-
class分组标题的类名。string-
id分组标题的 id,分组的 aria-labelledby 指向它。未传时由组件生成。string-
label分组标题文本,参与默认搜索。为空时不渲染标题,分组也不包 role="group"。string | number-
style分组标题的样式。JSX.CSSProperties | string-

Select.OptGroup 在 Select 内部只被读取,自身不渲染。class、style、id 与其他未声明的属性落在分组标题元素上。

Methods

Select 的 ref 得到根元素(触发器),方法挂在这个元素上,类型为 SelectElement(即 HTMLDivElement & SelectHandle)。除下表中的方法外,元素的 focus() 聚焦 combobox(搜索框在选择框内时是搜索框),不展开下拉列表。

方法说明
open()手动展开下拉列表
close()手动关闭下拉列表
clearInput()手动清空搜索框的值
selectAll()选中所有可见且未禁用的选项,仅多选时有效
deselectAll()清空所有已选项
search(value: string, event?: Event)按输入了该值来搜索,该值会被置给搜索框;不传事件时 onSearch 的第二个参数为 undefined
rePosition()按触发器当前位置立即重新计算下拉层位置;下拉层已随滚动、窗口尺寸变化、触发器移动以及触发器或列表的尺寸变化自动重新定位,一般无需调用;跟随触发器移动会晚一帧,需要同一帧内对齐时调用
import { Select, type SelectElement } from '@fibrous/semi-ui-solid';

function App() {
    let select: SelectElement | undefined;
    return (
        <>
            <Select ref={select} />
            <button onClick={() => select?.open()}>打开</button>
        </>
    );
}

Accessibility

ARIA

  • 按 ARIA 1.2 的 combobox 写法,任一时刻只有一个元素是 combobox,弹出层的 role 为 listbox,可选项的 role 为 option:
    • 不可搜索,或搜索框在下拉内(searchPosition="dropdown")时,Select trigger 是 select-only combobox,选中的内容是它的值
    • 搜索框在 trigger 内时,搜索框本身是 combobox,带 aria-autocomplete="list",trigger 不再有角色和 Tab 停靠点。单选的搜索框只在搜索时出现,未搜索时 trigger 仍是 combobox;搜索框在输入前为空,已选文本以 aria-describedby 关联为它的描述。多选的搜索框一直存在
    • 下拉内的搜索框保持文本框角色,带 aria-controls、aria-autocomplete、aria-activedescendant,名称与 combobox 相同
  • combobox 的名称按优先级取:aria-labelledby、aria-label、实际渲染出来的 insetLabel(combobox 的 aria-labelledby 指向它,未传 insetLabelId 时组件生成 id;有 prefix 时 insetLabel 不渲染,也不作名称),都没有时为语言包中的「选择」。建议传入与表单标签一致的名称。listbox 与下拉内的搜索框使用同一个名称来源,不指向 combobox 自身。选中的内容是 combobox 的值,不计入名称
  • combobox 具有 aria-haspopup、aria-expanded、aria-controls 属性,表示它与弹出层的关系
  • 多选时,listbox aria-multiselectable 为 true,表示当前可以多选
  • Option 选中时,aria-selected 为 true;当 Option 禁用时,aria-disabled 为 true
  • 带 label 的 OptGroup 中的选项位于 role="group" 内,以分组标题为名称(aria-labelledby),读屏会读出选项所属的分组;虚拟列表不显示分组标题,不包分组
  • 下拉打开时,属性 aria-activedescendant 指向键盘聚焦的 option,保证在朗读旁白时识别到当前的选择的 option;完全自定义候选项渲染时请把参数中的 id 写在 option 元素上(更多用法请参考Managing Focus in Composites Using aria-activedescendant)

键盘和焦点

不带 Filter 功能,或搜索框在下拉内(searchPosition="dropdown")的 Select:

  • Select 聚焦后,键盘用户可以通过 空格、上箭头、下箭头(含 Alt + 下箭头)或 Enter 键打开下拉菜单。已有选中值时焦点落在第一个已选的选项上,直接按 Enter 不改变值;没有选中值时保留当前高亮,初始高亮为第一个未禁用的选项(defaultActiveFirstOption 默认为 true)
  • Home、End 打开下拉菜单,并把焦点移到第一个、最后一个可用选项
  • 输入字符打开下拉菜单,并把焦点移到下一个以该字符开头的选项;快速连续输入时按整串匹配,重复输入同一字符时在以它开头的选项间轮换
  • 当下拉菜单打开时:
    • 使用 Esc 键或 Tab 键可以关闭菜单
    • 使用 上箭头 或 下箭头 可以切换选项,Home、End 移到首尾,输入字符移到以它开头的选项
    • 被聚焦的选项可以通过 Enter 或 空格 选中:单选选中后收起面板,多选切换选中状态并保持打开。快速连续输入字符的过程中,空格 计入匹配的字符串,不选中选项。焦点在下拉内的搜索框时,Home、End、空格 与字符用于编辑搜索内容,不移动选项焦点
  • 多选时焦点在已选标签上,按键只作用于标签:Backspace、Delete 删除标签,Enter、空格 不打开下拉菜单、不改变选中值
  • 当焦点在下拉菜单中,且用户使用的 innerBottomSlot 或 outerBottomSlot 属性的自定义 slot 中含有可交互元素时:
    • 可以使用 Tab 键切换到这些可交互元素上
    • 当焦点在自定义 slot 的首个可交互元素上时,使用 Shift + Tab ,焦点回到 Select 框上

带 Filter 功能的 Select:

  • Select 聚焦后,键盘用户可以通过 上箭头 或 下箭头 或 Enter 键打开下拉菜单。此时焦点仍然处于 Select 框,用户可以输入内容,同时也能使用 上箭头 或 下箭头 切换选项
  • 当下拉菜单打开时:使用 上箭头 或 下箭头 切换选项;Enter 选中被聚焦的选项,单选选中后收起面板,多选切换选中状态并保持打开;Esc 或 Tab 关闭菜单。Home、End、空格 与字符留给 Select 框内的输入框,用于编辑搜索内容,不移动选项焦点
  • 当焦点在 Select 框上,且用户使用的 innerBottomSlot 或 outerBottomSlot 属性的自定义 slot 中含有可交互元素时:
    • 可以使用 Tab 键切换到这些可交互元素上
    • 当焦点在自定义 slot 的首个可交互元素上时,使用 Shift + Tab ,焦点回到 Select 框上

清除按钮:

  • showClear 开启、有已选值或搜索文本且未禁用时,trigger 内渲染清除按钮。它是原生 button,名称为语言包中的「清空已选值」,在 hover、下拉打开或焦点位于 Select 内时显示
  • 焦点在 Select 框上时按 Tab 移到清除按钮,Enter 或 Space 清空,效果与鼠标点击相同;清空后焦点回到 Select 框

文案规范

  • 选择器标签
    • 用 1-3 个词描述需要用户所做的输入
    • 使用语句书写规范(首字母大写,其余小写)
    • 避免使用标点符号和介词(“the”, “an”, “a”)
    • 标签需是独立语句。不要让标签是前半句语句,选项是后半句语句。
    • 使用描述性语句,而不是指示性语句。如果选项需要更多解释,可以在选择框下使用帮助文本。
  • 选择器选项
    • 如果没有默认选项,就使用“Select”做占位文案
    • 选项要按首字母顺序或者其他有逻辑的排列顺序,使用户更好地找到选项
    • 使用语句书写规范(首字母大写,其余小写),避免在句尾使用逗号和分号
    • 清晰表达出选项所表示的选择目的

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$transition_duration-select-bgvar(--semi-transition_duration-none)选择器-背景色-动画持续时间
$transition_function-select-bgvar(--semi-transition_function-easeIn)选择器-背景色-过渡曲线
$transition_delay-select-bgvar(--semi-transition_delay-none)选择器-背景色-延迟时间
$transition_duration-select-bordervar(--semi-transition_duration-none)选择器-边框-动画持续时间
$transition_function-select-bordervar(--semi-transition_function-easeIn)选择器-边框-过渡曲线
$transition_delay-select-bordervar(--semi-transition_delay-none)选择器-边框-延迟时间
$transition_duration-select_option-bgvar(--semi-transition_duration-none)选择器-选项-动画持续时间
$transition_function-select_option-bgvar(--semi-transition_function-easeIn)选择器-选项-过渡曲线
$transition_delay-select_option-bgvar(--semi-transition_delay-none)选择器-选项-延迟时间
$transform_scale-selectvar(--semi-transform_scale-none)选择框-变大
$transform_rotate-select-arrowvar(--semi-transform_rotate-none)选择框-箭头-旋转
$color-select-bg-defaultvar(--semi-color-fill-0)选择器输入框背景色 - 默认态
$color-select-bg-hovervar(--semi-color-fill-1)选择器输入框背景色 - 悬停态
$color-select-bg-activevar(--semi-color-fill-2)选择器输入框背景色 - 按下态
$color-select-bg-focusvar(--semi-color-fill-0)选择器输入框背景色 - 聚焦态
$color-select-border-defaulttransparent选择器输入框描边颜色
$color-select-border-hover$color-select-border-default选择器输入框描边颜色 - 悬浮
$color-select-border-activevar(--semi-color-focus-border)选择器输入框描边颜色 - 按下态
$color-select-border-focus$color-select-border-active选择器输入框描边颜色 - 选中态
$color-select_dropdown_input-border$color-select-border-default下拉搜索框底部描边颜色
$color-select_warning-bg-defaultvar(--semi-color-warning-light-default)警示选择器输入框背景色 - 默认态
$color-select_warning-border-defaultvar(--semi-color-warning-light-default)警示选择器输入框描边颜色 - 默认态
$color-select_warning-bg-hovervar(--semi-color-warning-light-hover)警示选择器输入框背景色 - 悬停态
$color-select_warning-border-hovervar(--semi-color-warning-light-hover)警示选择器输入框描边颜色 - 悬停态
$color-select_warning-bg-focusvar(--semi-color-warning-light-default)警示选择器输入框背景色 - 选中态
$color-select_warning-border-focusvar(--semi-color-warning)警示选择器输入框描边颜色 - 选中态
$color-select_warning-bg-activevar(--semi-color-warning-light-active)警示选择器输入框背景色 - 按下态
$color-select_warning-border-activevar(--semi-color-warning-light-active)警示选择器输入框描边颜色 - 按下态
$color-select_danger-bg-defaultvar(--semi-color-danger-light-default)报错选择器输入框背景色 - 默认态
$color-select_danger-border-defaultvar(--semi-color-danger-light-default)报错选择器输入框描边颜色 - 默认态
$color-select_danger-bg-hovervar(--semi-color-danger-light-hover)报错选择器输入框背景色 - 悬停态
$color-select_danger-border-hovervar(--semi-color-danger-light-hover)报错选择器输入框描边颜色 - 悬停态
$color-select_danger-bg-focusvar(--semi-color-danger-light-default)报错选择器输入框背景色 - 选中态
$color-select_danger-border-focusvar(--semi-color-danger)报错选择器输入框描边颜色 - 选中态
$color-select_danger-bg-activevar(--semi-color-danger-light-active)报错选择器输入框背景色 - 按下态
$color-select_danger-border-activevar(--semi-color-danger-light-active)报错选择器输入框描边颜色 - 按下态
$color-select_default-border-focusvar(--semi-color-focus-border)默认选择器输入框描边颜色 - 选中态
$color-select_main-text-defaultvar(--semi-color-text-0)选择器输入框回填内容文本颜色
$color-select-icon-defaultvar(--semi-color-text-2)选择器输入框图标颜色
$color-select_clearBtn-text-defaultvar(--semi-color-text-2)选择器输入框清空按钮颜色 - 默认态
$color-select_clearBtn-text-hovervar(--semi-color-primary)选择器输入框清空按钮颜色 - 悬停态
$color-select_input_disabled-bgvar(--semi-color-disabled-fill)禁用选择器输入框背景色
$color-select_input_disabled-border-focustransparent禁用选择器输入框描边颜色 - 聚焦态
$color-select_input_disabled-textvar(--semi-color-disabled-text)禁用选择器输入框回填内容文字颜色
$color-select_input_disabled-bg-hovervar(--semi-color-disabled-fill)禁用选择器输入框背景色 - 悬停态
$color-select_input_disabled-bg-focusvar(--semi-color-disabled-fill)禁用选择器输入框背景色 - 聚焦态
$color-select_input_placeholder-textvar(--semi-color-text-2)选择器输入框占位文本文字颜色
$color-select_option_main-textvar(--semi-color-text-0)选择器下拉菜单选项文本颜色
$color-select_option_keyword-textvar(--semi-color-primary)选择器下拉菜单选项匹配搜索结果文本颜色
$color-select_option-icon-defaulttransparent选择器下拉菜单选项图标颜色 - 默认态
$color-select_option-bg-hovervar(--semi-color-fill-0)选择器下拉菜单选项背景颜色 - 悬停态
$color-select_option-bg-activevar(--semi-color-fill-1)选择器下拉菜单选项背景颜色 - 按下态
$color-select_option_disabled-textvar(--semi-color-disabled-text)禁用选择器下拉菜单选项文字颜色
$color-select_option-icon-activevar(--semi-color-text-2)选择器下拉菜单选项图标颜色 - 选中态
$color-select_option-border-defaultvar(--semi-color-border)选择器下拉菜单分组标题分割线颜色
$color-select-option-bg-selectedtransparent选择器下拉菜单选项背景颜色 - 选中态
$color-select_create_tips-textvar(--semi-color-text-2)选择器下拉菜单新建选项提示文本颜色
$color-select_group-textvar(--semi-color-text-2)选择器下拉菜单分组标题文本颜色
$color-select_prefix_suffix_text-defaultvar(--semi-color-text-2)选择器输入框前后缀文本颜色
$height-select_large$height-control-large选择器输入框高度 - 大尺寸
$height-select_small$height-control-small选择器输入框高度 - 小尺寸
$height-select_default$height-control-default选择器输入框高度 - 默认尺寸
$width-select_option_tick$width-icon-small选择器下拉菜单项选中对勾图标大小
$width-select-border1px选择器输入框描边宽度
$width-select_arrow32px选择器输入框下拉箭头宽度
$width-select_arrow_empty12px选择器输入框下拉箭头为空时(有suffix icon)宽度
$width-select_clear-icon32px选择器输入框清空按钮宽度
$width-select_group_top-border$border-thickness-control选择器下拉菜单分组标题描边宽度
$height-select_multiple_input_small20px小尺寸多项选择器输入框Input框高度
$height-select_multiple_input_default24px默认多项选择器输入框Input框高度
$height-select_multiple_input_large24px大尺寸多项选择器输入框Input框高度
$width-select-border-hover$width-select-border选择器输入框描边宽度 - 悬浮
$width-select-border-focus$width-select-border-hover选择器输入框描边宽度 - 按下
$width-select-border-active$width-select-border-focus选择器输入框描边宽度 - 选中
$spacing-select_option_tick-marginRight$spacing-tight选择器下拉菜单选中对勾右侧外边距
$spacing-select_prefix_suffix_text-marginX$spacing-base-tight选择器输入框前后缀文本水平内边距
$spacing-select_prefix_suffix_text-marginY0px选择器输入框前后缀文本垂直内边距
$spacing-select_prefix_suffix_icon-marginX$spacing-tight选择器输入框前后缀图标水平内边距
$spacing-select_prefix_suffix_icon-marginY0px选择器输入框前后缀图标垂直内边距
$spacing-select_create_tips-marginRight$spacing-extra-tight创建新选项右侧外边距
$spacing-select_group-marginTop$spacing-extra-tight选择器下拉菜单分组标题顶部外边距
$spacing-select_group-paddingTop$spacing-base-tight选择器下拉菜单分组标题顶部内边距
$spacing-select_group-paddingBottom$spacing-extra-tight选择器下拉菜单分组标题底部内边距
$spacing-select_group-paddingLeft$spacing-base-tight + $width-select_option_tick + $spacing-select_option_tick-marginRight选择器下拉菜单分组标题左侧内边距
$spacing-select_group-paddingRight$spacing-base选择器下拉菜单分组标题右侧内边距
$spacing-select_loading_wrapper-paddingLeft$spacing-base选择器加载左侧内边距
$spacing-select_loading_wrapper-paddingRight$spacing-base选择器加载右侧内边距
$spacing-select_loading_wrapper-paddingTop$spacing-tight选择器加载顶部内边距
$spacing-select_loading_wrapper-paddingBottom$spacing-tight选择器加载底部内边距
$spacing-select_multiple_content_wrapper_empty-marginLeft$spacing-tight多项选择器空回填内容为空时左侧内边距
$spacing-select_multiple_selection-marginLeft$spacing-extra-tight多项选择器回填内容左侧内边距
$spacing-select_option-paddingLeft$spacing-base-tight选择器下拉菜单项左侧内边距
$spacing-select_option-paddingRight$spacing-base-tight选择器下拉菜单项右侧内边距
$spacing-select_option-paddingTop$spacing-tight选择器下拉菜单项顶部内边距
$spacing-select_option-paddingBottom$spacing-tight选择器下拉菜单项底部内边距
$spacing-select_tag-marginTop$spacing-super-tight - 1px多项选择器标签顶部外边距
$spacing-select_tag-marginRight$spacing-extra-tight多项选择器标签右侧外边距
$spacing-select_tag-marginBottom$spacing-super-tight - 1px多项选择器标签底部外边距
$spacing-select_selection-marginLeft$spacing-base-tight选择器内容区左侧外边距
$spacing-select_option_list-paddingTop$spacing-extra-tight选择器内容区顶部内边距
$spacing-select_option_list-paddingRight0px选择器内容区右侧内边距
$spacing-select_option_list-paddingBottom$spacing-extra-tight选择器内容区底部内边距
$spacing-select_option_list-paddingLeft0px选择器内容区左侧内边距
$spacing-select_dropdown_input_paddingTop8px下拉搜索框顶部内边距
$spacing-select_dropdown_input_paddingBottom8px下拉搜索框底部内边距
$spacing-select_dropdown_input_paddingRight12px下拉搜索框右侧内边距
$spacing-select_dropdown_input_paddingLeft12px下拉搜索框左侧内边距
$radius-selectvar(--semi-border-radius-small)选择器输入框圆角
$radius-select_option0px选择器待选项圆角
$font-select-fontWeight$font-weight-regular选择器文本字重
$font-select_keyword-fontWeight600选择器搜索结果命关键词中文本字重
$font-select_prefix_suffix-fontWeight$font-weight-bold选择器输入框前后缀文本字重
$opacity-select_selection_text_inactive0.4

TypeScript 泛型支持

Select 有两个泛型参数:T 是选项 value 的类型(设置 onChangeWithObject 时是 option 对象的类型),Multiple 是 multiple 属性的类型。单选时 onChange 的入参为 T | undefined,多选时为 T[];multiple 是 boolean 变量时为 T | T[] | undefined。onSelect、onDeselect 的入参是选项的原始 value(string | number),不随 T 变化。两个参数通常由 value 与 multiple 推断;多选时显式写 T 需要同时写 Multiple。

import { Select } from '@fibrous/semi-ui-solid';

// 由 value 推断 T 为 string,value 为 string | undefined
<Select value={selectedValue()} onChange={(value) => setSelectedValue(value)}>
    <Select.Option value="option1">选项 1</Select.Option>
    <Select.Option value="option2">选项 2</Select.Option>
</Select>

// 多选时 value 为 string[]
<Select multiple value={selectedValues()} onChange={(value) => setSelectedValues(value)}>
    <Select.Option value="option1">选项 1</Select.Option>
    <Select.Option value="option2">选项 2</Select.Option>
</Select>

// 多选时显式写泛型参数
<Select<string, true> multiple onChange={(value) => setSelectedValues(value)}>
    <Select.Option value="option1">选项 1</Select.Option>
    <Select.Option value="option2">选项 2</Select.Option>
</Select>

FAQ

  • 可搜索的 Select,使用远程数据动态更新optionList,为什么在异步请求完成之前有时候会出现暂无数据?
    请检查是否开启了 remote,不开启 remote 的情况下,默认会将 input 框输入值与当前的 optionList 进行一次对比筛选,如果无匹配时,就会显示暂无数据。
    可以通过开启 remote 关闭对本地当前数据的匹配筛选。

  • Select 会自动限制下拉菜单的宽度吗?

    • 会给 minWidth,但不会写死 width。如果有需要的话,可以自己通过 dropdownStyle 来添加。
  • 为什么单选选择选项后没有触发 blur 事件?

    • 在 V2.17.0 前,Select 单选选择后,会触发 Select 的 blur 事件。
    • 在 V2.17.0 后,Select 增加了 A11y 支持,不会触发 Select 的 blur 事件。
      • 单选选择中,Select 浮层关闭,依然保持焦点在 trigger(此时可以通过 Enter 回车键再次打开 Select 浮层)
      • 无论单选或多选,按下 Esc,仅 Select 浮层关闭,trigger 保持焦点(此时可以通过 Enter 回车键再次打开 Select 浮层)