UNPKG

@douyinfe/semi-ui

Version:

A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.

1,369 lines (1,219 loc) 83.5 kB
--- localeCode: en-US order: 46 category: Input title: Select subTitle: Select icon: doc-select width: 60% brief: The user can select one or more options from a set of options through the Select selector and present the final selection result --- ## Demos ### How to import ```jsx import import { Select } from '@douyinfe/semi-ui'; const Option = Select.Option; ``` ### Basic Usage Each Option tag must declare the `value` attribute, and the Option `children` content will be rendered to the drop-down list ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => ( <> <Select defaultValue="douyin" style={{ width: 120 }}> <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> </Select> <br /> <br /> <Select placeholder="Select line of business" style={{ width: 120 }}> <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> </Select> </> ); ``` ### Pass Option as an array You can pass an array of objects directly through `optionList`. Each object must contain the value / label attribute. ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => { const list = [ { value: 'douyin', label: 'Douyin' }, { value: 'capcut', label: 'Capcut' }, { value: 'coze', label: 'Coze' }, { value: 'toutiao', label: 'TooBuzz' }, ]; return <Select placeholder="Business line" style={{ width: 180 }} optionList={list}></Select>; }; ``` ### Multi-choice Since v2.28, the selector will have its own maxHeight 270, and the content can be viewed by scrolling vertically after it exceeds. Configuration `multiple` properties that can support multi-selection Configuration `maxTagCount`. You can limit the number of options displayed, and the excess will be displayed in the form of + N Configure `ellipsisTrigger` (>= v2.28.0) to do adaptive processing on the overflow part of the tag. When the width is insufficient, the last tag content will be truncated. After enabling this function, there will be a certain performance loss, and it is not recommended to use it in large form scenarios Configure `expandRestTagsOnClick` (>= v2.28.0) to display all remaining tags by clicking when `maxTagCount` is set Use `showRestTagsPopover` (>= v2.22.0) to set whether hover +N displays Popover after exceeding `maxTagCount`, the default is `false`. Also, popovers can be configured in the `restTagsPopoverProps` property Configuration `max` Properties can limit the maximum number of options and cannot be selected beyond the maximum limit, while triggering`On Exceed`callback ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => ( <> <Select multiple style={{ width: '320px' }} defaultValue={['douyin', 'coze']}> <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="coze">Coze</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> <br /> <br /> <Select multiple maxTagCount={2} showRestTagsPopover={true} restTagsPopoverProps={{ position: 'top' }} style={{ width: '320px' }} defaultValue={['douyin', 'coze', 'capcut']} > <Select.Option value="douyin">Semi</Select.Option> <Select.Option value="coze">Coze</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> <br /> <br /> <Select multiple style={{ width: '320px' }} defaultValue={['douyin']} max={2} onExceed={() => Toast.warning('Only two options are allowed')} > <Select.Option value="douyin">Semi</Select.Option> <Select.Option value="coze">Coze</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> <br /> <br /> <Select multiple maxTagCount={2} showRestTagsPopover={true} restTagsPopoverProps={{ position: 'top' }} style={{ width: '220px' }} defaultValue={['xigua', 'coze', 'capcut', 'douyin']} ellipsisTrigger expandRestTagsOnClick > <Select.Option value="douyin">Semi</Select.Option> <Select.Option value="coze">Coze</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> </> ); ``` ### With Group Grouping Option with `OptGroup`(Only supports the declaration of children through jsx, and does not support pass in through optionList) ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => ( <Select placeholder="" style={{ width: 180 }} filter> <Select.OptGroup label="Asia"> <Select.Option value="a-1">China</Select.Option> <Select.Option value="a-2">Korea</Select.Option> </Select.OptGroup> <Select.OptGroup label="Europe"> <Select.Option value="b-1">Germany</Select.Option> <Select.Option value="b-2">France</Select.Option> </Select.OptGroup> <Select.OptGroup label="South America"> <Select.Option value="c-1">Peru</Select.Option> </Select.OptGroup> </Select> ); ``` ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => { 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' }], }, ]; return ( <Select placeholder="" style={{ width: 180 }} filter> {data.map((group, index) => ( <Select.OptGroup label={group.label} key={`${index}-${group.label}`}> {group.children.map((option, index2) => ( <Select.Option value={option.value} key={`${index2}-${group.label}`}> {option.label} </Select.Option> ))} </Select.OptGroup> ))} </Select> ); }; ``` ### Different sizes Size: small / default / large ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => ( <> <Select placeholder="Business line" style={{ width: '200px' }} size="small"> <Select.Option value="douyin">Douyin</Select.Option> </Select> <br /> <br /> <Select placeholder="Business line" style={{ width: '200px' }}> <Select.Option value="douyin">Douyin</Select.Option> </Select> <br /> <br /> <Select placeholder="Business line" style={{ width: '200px' }} size="large"> <Select.Option value="douyin">Douyin</Select.Option> </Select> </> ); ``` ### Different validate status validateStatus: default / warning / error ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => ( <> <Select style={{ width: '180px' }} defaultValue='Capcut'> <Select.Option value="Capcut">Capcut</Select.Option> </Select> <br /> <br /> <Select style={{ width: '180px' }} validateStatus="warning" defaultValue='Capcut'> <Select.Option value="Capcut">Capcut</Select.Option> </Select> <br /> <br /> <Select style={{ width: '180px' }} validateStatus="error" defaultValue='Capcut'> <Select.Option value="Capcut">Capcut</Select.Option> </Select> </> ); ``` ### Configure Prefix, Suffix, Clear Button - You can pass the selection box prefix through `prefix`, the selection box suffix through `suffix`, for text or React Node The left and right padding is automatically brought when the content passed in by prefix and reactix is text or Icon. If it is a custom ReactNode, the left and right padding is 0. - Whether to show the clear button is displayed by `showClear` - Whether to show the right drop-down arrow is displayed by `showArrow` ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; import { IconVigoLogo, IconGift } from '@douyinfe/semi-icons'; () => ( <> <Select style={{ width: '320px' }} defaultValue={'douyin'} prefix={<IconVigoLogo />} showClear={true}> <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="coze">Coze</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> <br /> <br /> <Select style={{ width: '320px' }} defaultValue={'capcut'} prefix={<IconVigoLogo />} suffix={<IconGift />} showArrow={false} > <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="coze">Coze</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> </> ); ``` ### Additional items We have reserved two slots at the bottom of the pop-up layer, which you can use when you need to add a custom node to the pop-up layer. Use`innerTopSlot` or `outerTopSlot` to pass the custom node, which will be rendered at the top of the pop-up layer. Use`innerBottomSlot` or `outerBottomSlot` instead at the bottom. - `innerTopSlot` and `innerBottomSlot` will be rendered inside the Option List - `outerTopSlot` and `outerBottomSlot` will be rendered to level with the option List ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; import { IconClock } from '@douyinfe/semi-icons'; () => { let selectStyle = { width: 180, margin: 20 }; let innerSlotStyle = { backgroundColor: '#FFF', height: '40px', color: '#0077FA', display: 'flex', justifyContent: 'center', alignItems: 'center', cursor: 'pointer', }; let innerSlotNode = <div style={innerSlotStyle}>No suitable product?</div>; let outSlotStyle = { backgroundColor: 'whitesmoke', height: '29px', display: 'flex', justifyContent: 'center', alignItems: 'center', cursor: 'pointer', }; let outSlotNode = ( <div style={outSlotStyle}> <IconClock></IconClock> <span style={{ color: 'rgba(28, 31, 35, 0.55)' }}>More recently viewed pages</span> </div> ); return ( <div> <p>outerBottomSlot:</p> <Select style={selectStyle} dropdownStyle={{ width: 180 }} maxHeight={213} defaultOpen autoAdjustOverflow={false} position="bottom" outerBottomSlot={outSlotNode} > <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="coze">Coze</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> <p>innerBottomSlot:</p> <Select style={selectStyle} dropdownStyle={{ width: 180 }} innerBottomSlot={innerSlotNode} > <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="coze">Coze</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> </div> ); }; ``` Using outerTopSlot to insert content ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => { const list = { component: [ { value: 'select', label: 'Select' }, { value: 'tabs', label: 'Tabs' }, { value: 'avatar', label: 'Avatar' }, { value: 'button', label: 'Button' }, ], design: [ { value: 'color', label: 'Color' }, { value: 'dark', label: 'Dark Mode' }, { value: 'icon', label: 'Icon' }, { value: 'font', label: 'Topography' }, ], feedback: [ { value: 'faq', label: 'FAQ' }, { value: 'join', label: 'Join Chat Group' }, { value: 'hornbill', label: 'Hornbill' }, ], }; const [key, setKey] = useState('component'); const [value, setValue] = useState({ value: 'faq', label: 'FAQ' }); const handleTabClick = itemKey => { setKey(itemKey); }; const tabStyle = { cursor: 'pointer', marginRight: 12, paddingBottom: 4, }; const tabActiveStyle = { ...tabStyle, borderBottom: '1px solid var(--semi-color-primary)', fontWeight: 700, }; const tabWrapper = { display: 'flex', paddingTop: 8, paddingLeft: 32, paddingRight: 32, borderBottom: '0.5px solid var(--semi-color-border)', }; const tabOptions = [ { itemKey: 'component', label: 'Compoonent' }, { itemKey: 'design', label: 'Design' }, { itemKey: 'feedback', label: 'Feedback' }, ]; const outerTopSlotNode = ( <div style={tabWrapper}> {tabOptions.map((item, index) => { style = item.itemKey === key ? tabActiveStyle : tabStyle; return ( <div style={style} key={item.itemKey} onClick={() => handleTabClick(item.itemKey)}> {item.label} </div> ); })} </div> ); return ( <Select defaultOpen autoAdjustOverflow={false} value={value} onChangeWithObject onChange={obj => setValue(obj)} style={{ width: 200 }} outerTopSlot={outerTopSlotNode} optionList={list[key]} /> ); }; ``` ### Controlled component When `value` is passed, Select is a controlled component, and the value selected is entirely determined by `value`. ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => { let [value, setValue] = useState('xigua'); return ( <> <Select value={value} style={{ width: '300px' }} onChange={setValue} placeholder="Controlled Component"> <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> </> ); }; ``` ### Linkage Select If it is a complex linkage with a hierarchical relationship, it is recommended to use Cascader components directly ```jsx live=true import React, { useState } from 'react'; import { Select } from '@douyinfe/semi-ui'; () => { const continents = ['Asia', 'Europe']; const maps = { Asia: ['China', 'Korea'], Europe: ['United Kingdom', 'France', 'Germany'], }; const [continent, setContinent] = useState(continents[0]); const countrys = maps[continent]; const [country, setCountry] = useState(countrys[0]); const continentsChange = newContinent => { setContinent(newContinent); setCountry(maps[newContinent][0]); }; const countryChange = newCountry => setCountry(newCountry); return ( <> <Select style={{ width: '150px', margin: '10px' }} onChange={continentsChange} value={continent}> {continents.map(c => ( <Select.Option value={c} key={c}> {c} </Select.Option> ))} </Select> <Select style={{ width: '150px', margin: '10px' }} value={country} onChange={countryChange}> {countrys.map(c => ( <Select.Option value={c} key={c}> {c} </Select.Option> ))} </Select> </> ); }; ``` ### Search You can turn on the search capability by setting `filter` to true. The default search strategy will include comparison of the input value with the label value of option By default, the search keywords will be cleared automatically after multiple selection is selected. If you want to keep it, you can turn off the default behavior by setting `autoClearSearchValue` to false (provided after v2.3) ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => ( <> <Select filter style={{ width: 180 }} placeholder="Searchable Select"> <Select.Option value="app1">Douyin</Select.Option> <Select.Option value="app2">Coze</Select.Option> <Select.Option value="app3">Capcut</Select.Option> <Select.Option value="app4">BuzzVideo</Select.Option> </Select> <br /> <br /> <Select filter multiple style={{ width: 350 }} placeholder="Searchable Multiple Select" autoClearSearchValue={false} > <Select.Option value="semi-0">Semi-0</Select.Option> <Select.Option value="semi-1">Semi-1</Select.Option> <Select.Option value="semi-2">Semi-2</Select.Option> <Select.Option value="semi-3">Semi-3</Select.Option> <Select.Option value="semi-4">Semi-4</Select.Option> </Select> </> ); ``` ### Search position The default search input is displayed on the Select Trigger. You can specify different positions through `searchPosition`, and you can choose `dropdown` or `trigger`. Available after `v2.61.0` If you want to customize the placeholder of the Input search box in the dropdown, you can control it through `searchPlaceholder` If the `searchPosition` is `trigger`, when `showClear=true`, clicking the clear button of the input will clear the selected items and the search text in the input at the same time If the `searchPosition` is `dropdown`, when `showClear=true`, clicking the clear button of the trigger will clear the selected items, clicking the clear button in the dropdown input will clear search text ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => ( <> <Select filter searchPosition='dropdown' style={{ width: 200 }} defaultValue={'ulikecam'} > <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="ulikecam">UlikeCam</Select.Option> <Select.Option value="jianying">Capcut</Select.Option> <Select.Option value="xigua">XiguaVideo</Select.Option> </Select> <br /> <br /> <Select filter searchPosition='dropdown' multiple style={{ width: 300 }} defaultValue={['semi-1']} autoClearSearchValue={false} > <Select.Option value="semi-0">Semi-0</Select.Option> <Select.Option value="semi-1">Semi-1</Select.Option> <Select.Option value="semi-2">Semi-2</Select.Option> <Select.Option value="semi-3">Semi-3</Select.Option> <Select.Option value="semi-4">Semi-4</Select.Option> </Select> </> ); ``` ### Remote search A multi-select example with remote search, request debounce, loading status. - Use `filter` turn on the search capability. - Use `remote` to disabled local filter - Dynamic Update `optionList` after `onSearch` callback - Update `loading` when fetching data / finish - Use controlled value attribute ```jsx live=true import React from 'react'; import { debounce } from 'lodash-es'; import { Select } from '@douyinfe/semi-ui'; () => { const [loading, setLoading] = useState(false); const optionList = [ { value: 'dsm', label: 'Semi DSM', type: 1 }, { value: 'd2c', label: 'Semi DesignToCode', type: 2 }, { value: 'c2d', label: 'Semi CodeToDesign', type: 3 }, { value: 'plugin', label: 'Semi Plugin', type: 4 }, ]; const [list, setList] = useState(optionList); const [value, setValue] = useState(''); const handleMultipleChange = newValue => { setValue(newValue); }; const handleSearch = inputValue => { setLoading(true); let result = []; if (inputValue) { let length = Math.ceil(Math.random() * 100); result = Array.from({ length }, (v, i) => { return { value: inputValue + i, label: `Relative: ${inputValue}${i}`, type: i + 1 }; }); setTimeout(() => { setLoading(false); setList(result); }, 1000); } else { setLoading(false); } }; return ( <Select style={{ width: 300 }} filter remote onChangeWithObject multiple value={value} onSearch={debounce(handleSearch, 1000)} optionList={list} loading={loading} onChange={handleMultipleChange} emptyContent={null} ></Select> ); }; ``` ### Custom search strategy By default, the user's search input will be compared with the option's label value as a string include. You can set `filter` as a custom function to customize your filter strategy. ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => { function search(sugInput, option) { // Search for both label and value let label = option.label.toUpperCase(); let value = option.value.toUpperCase(); let sug = sugInput.toUpperCase(); return label.includes(sug) || value.includes(sug); } return ( <Select filter={search} style={{ width: '180px' }} placeholder="try hello or douyin"> <Select.Option value="hello">Douyin</Select.Option> <Select.Option value="bytedance">UlikeCam</Select.Option> <Select.Option value="semi">BuzzVideo</Select.Option> </Select> ); }; ``` ### Custom selection rendering By default, the content of `option.label` or `option.children` will be backfilled into the selection box when the option is selected. But you can customize the rendering of the selection box through the `renderSelectedItem` function - Select: `renderSelectedItem(optionNode: object) => content: ReactNode` - Multiple Select: `renderSelectedItem(optionNode: object, { index: number, onClose: function }) => { isRenderInTag: boolean, content: ReactNode }` - When `isRenderInTag` is true, content will automatically wrapped in `Tag` rendering (with background color and close button) - When `isRenderInTag` is false, it renders the returned content directly ```jsx live=true import React from 'react'; import { Select, Avatar, Tag } from '@douyinfe/semi-ui'; () => { const list = [ { name: 'Keman Xia', email: 'xiakeman@example.com', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bag.jpeg', }, { name: 'Yue Shen', email: 'shenyue@example.com', avatar: 'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg', }, { name: 'Chenyi Qu', email: 'quchenyi@example.com', avatar: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/Viamaker.png', }, { name: 'Jiamao Wen', email: 'wenjiamao@example.com', avatar: 'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/6fbafc2d-e3e6-4cff-a1e2-17709c680624.png', }, ]; const renderSelectedItem = optionNode => ( <div key={optionNode.email} style={{ display: 'flex', alignItems: 'center' }}> <Avatar src={optionNode.avatar} size="small"> {optionNode.abbr} </Avatar> <span style={{ marginLeft: 8 }}>{optionNode.email}</span> </div> ); // avatarSrc & avatarShape are supported const renderMultipleWithCustomTag = (optionNode, { onClose }) => { const content = ( <Tag avatarSrc={optionNode.avatar} avatarShape="circle" closable={true} onClose={onClose} size="large" key={optionNode.name} > {optionNode.name} </Tag> ); return { isRenderInTag: false, content, }; }; const renderMultipleWithCustomTag2 = (optionNode, { onClose }) => { const content = ( <Tag avatarSrc={optionNode.avatar} avatarShape="square" closable={true} onClose={onClose} size="large" key={optionNode.name} > {optionNode.name} </Tag> ); return { isRenderInTag: false, content, }; }; const renderCustomOption = (item, index) => { const optionStyle = { display: 'flex', paddingLeft: 24, paddingTop: 10, paddingBottom: 10, }; return ( <Select.Option value={item.name} style={optionStyle} showTick={true} {...item} key={item.email}> <Avatar size="small" src={item.avatar} /> <div style={{ marginLeft: 8 }}> <div style={{ fontSize: 14 }}>{item.name}</div> <div style={{ color: 'var(--color-text-2)', fontSize: 12, lineHeight: '16px', fontWeight: 'normal' }} > {item.email} </div> </div> </Select.Option> ); }; return ( <> <Select placeholder="Please select..." style={{ width: 280, height: 40 }} onChange={v => console.log(v)} defaultValue={'Keman Xia'} renderSelectedItem={renderSelectedItem} > {list.map((item, index) => renderCustomOption(item, index))} </Select> <Select placeholder="Please select..." maxTagCount={2} style={{ width: 280, marginTop: 20 }} onChange={v => console.log(v)} defaultValue={['Keman Xia', 'Yue Shen']} multiple renderSelectedItem={renderMultipleWithCustomTag} > {list.map((item, index) => renderCustomOption(item, index))} </Select> <Select placeholder="Please select..." maxTagCount={2} style={{ width: 280, marginTop: 20 }} onChange={v => console.log(v)} defaultValue={['Keman Xia', 'Yue Shen']} multiple renderSelectedItem={renderMultipleWithCustomTag2} > {list.map((item, index) => renderCustomOption(item, index))} </Select> </> ); }; ``` ### Custom pop-up layer style You can control the style of the pop-up layer through `dropdownClassName`, `dropdownStyle` For example, when you customize the width of the pop-up layer, you can pass the width through `drowndownStyle` ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => ( <Select style={{ width: 180 }} dropdownStyle={{ width: 250 }} dropdownClassName="test"> <Select.Option value="douyin">Douyin</Select.Option> <Select.Option value="coze">Coze</Select.Option> <Select.Option value="capcut">Capcut</Select.Option> <Select.Option value="xigua">BuzzVideo</Select.Option> </Select> ); ``` ### Dynamic Modification Options If you need to update Options dynamically, you should use controlled value ```jsx live=true import React from 'react'; import { Select, Button } from '@douyinfe/semi-ui'; () => { let [options, setOptions] = useState([1, 2, 3, 4]); function add() { let length = Math.ceil(Math.random() * 10); let newOptions = Array.from({ length }, (v, i) => i + 1); setOptions(newOptions); } return ( <> <Select style={{ width: '180px' }} value={4}> {options.map(option => ( <Select.Option value={option} key={option}> {option} </Select.Option> ))} </Select> <br /> <br /> <Button onClick={add}>ChangeOptions Dynamic</Button> </> ); }; ``` ### Get all attribute of selected option By default, through `onChange` uou can only get value attribute of selected option. If you need to take other attributes of the selected option, you can use `onChangeWithObject` Properties At this time, the argument of `onChange` will be object, containing various attributes of selected option, eg: `onChange({ value, label, ...rest })` Note that when onChange With Object is set to true,`defaultValue`/`Value`it should also be object and must have `value` key ```jsx live=true import React from 'react'; import { Select, TextArea } from '@douyinfe/semi-ui'; () => { const list = [ { value: 'douyin', label: 'Douyin', type: 1 }, { value: 'capcut', label: 'Capcut', type: 2 }, { value: 'xigua', label: 'BuzzVideo', type: 3 }, ]; const [cbValue, setCbValue] = useState(); const [multipleCbValue, setMultipleCbValue] = useState(); const onChange = value => { setCbValue(value); console.log(value); }; const onMultipleChange = value => { setMultipleCbValue(value); console.log(value); }; return ( <div> <div> <Select style={{ width: 150 }} onChangeWithObject optionList={list} defaultValue={list[0]} onChange={onChange} ></Select> <h4>onChang callback:</h4> <TextArea style={{ width: 320, marginBottom: 48 }} autosize value={JSON.stringify(cbValue)} rows={2} /> </div> <div> <Select style={{ width: 320 }} onChangeWithObject multiple optionList={list} onChange={onMultipleChange} placeholder="Multiple Select" ></Select> <h4>onChange callback:</h4> <TextArea style={{ width: 320 }} autosize value={JSON.stringify(multipleCbValue)} /> </div> </div> ); }; ``` ### Create entries You can create and select entries that do not exist in the options by setting `allowCreate=true` You can customize the content display when creating the label through renderCreateItem (by returning ReactNode, note that you need to customize the style) In addition, can be used with the `defaultActiveFirstOption` property to automatically select the first item. When you enter directly and press Enter, you can immediately create an Option <Notice title='Notice'> When allowCreate is enabled, it will no longer respond to updates to Children or optionList </Notice> ```jsx live=true import React from 'react'; import { Select } from '@douyinfe/semi-ui'; () => { const optionList = [ { value: 'douyin', label: 'Douyin' }, { value: 'capcut', label: 'Capcut' }, { value: 'xigua', label: 'BuzzVideo' }, ]; return ( <> <Select style={{ width: 400 }} optionList={optionList} allowCreate={true} multiple={true} filter={true} onChange={v => console.log(v)} defaultActiveFirstOption ></Select> <br /> <br /> <Select style={{ width: 400 }} optionList={optionList} allowCreate={true} multiple={true} filter={true} renderCreateItem={input => <div style={{ padding: 10 }}>Create Item:{input}</div>} onChange={v => console.log(v)} defaultActiveFirstOption ></Select> </> ); }; ``` ### Virtualize Turn on list virtualization when passing in `virtualize` to optimize performance when there are a large number of Option nodes virtualize is an object containing the following values: - height: Option list height value, default 270 (before v2.20.8 was 300) - width: Option list width value, default 100% - itemSize: The height of each line of Option, must be passed <Notice title='Note'> When virtualize.height is greater than the default value of 270px, to avoid the double scrollbar issue, you need to set the maxHeight property to the same value as virtualize.height. For example: when setting virtualize.height to 400px, you should also set maxHeight={400}. </Notice> ```jsx live=true hideInDSM import React, { useMemo } from 'react'; import { Select } from '@douyinfe/semi-ui'; () => { const optionList = useMemo(() => { return Array.from({ length: 3000 }, (v, i) => ({ label: `option-${i}`, value: i })); }, []); const virtualize = { height: 270, width: '100%', itemSize: 36, // px }; return ( <> <Select placeholder="3000 options" style={{ width: 260 }} filter virtualize={virtualize} optionList={optionList} ></Select> </> ); }; ``` ### Custom Trigger If the default layout style of the selection box does not meet your needs, you can use `triggerRender` to customize the display of the selection box The parameters of triggerRender are as follows ```typescript interface TriggerRenderProps { value: array<object> // All currently selected options inputValue: string; // The input value of the current input box onSearch: (inputValue: string) => void; // The function used to update the value of the input box. You should call this function when the value of the Input component you customize in triggerRender is updated to synchronize the state to the Select internal. props.filter needs to be true, support after v2.32 onClear: () => void; // Function to clear the value onRemove: (option: object) => void; // support after v2.32 disabled: boolean; // Whether to disable Select placeholder: string; // Select placeholder componentProps: //All props passed to Select by users } ``` ```jsx live=true import React, { useState } from 'react'; import { Select, Tag } from '@douyinfe/semi-ui'; import { IconAppCenter, IconChevronDown } from '@douyinfe/semi-icons'; () => { const [valList, setValList] = useState(['douyin', 'ulikecam']); const list = [ { value: 'douyin', label: 'Douyin' }, { value: 'ulikecam', label: 'UlikeCam' }, { value: 'jianying', label: 'Capcut' }, { value: 'doubao', label: 'Cici' }, ]; const triggerRender = ({ value }) => { return ( <div style={{ minWidth: '112', backgroundColor: 'var(--semi-color-primary-light-default)', height: 32, display: 'flex', alignItems: 'center', paddingLeft: 12, borderRadius: 3, color: 'var(--semi-color-primary)', }} > <div style={{ fontWeight: 600, flexShrink: 0, fontSize: 14, }} > AppName </div> <div style={{ margin: 4, whiteSpace: 'nowrap', textOverflow: 'ellipsis', flexGrow: 1, overflow: 'hidden', }} > {value.map(item => item.label).join(' , ')} </div> <IconAppCenter style={{ marginRight: 8, flexShrink: 0 }} /> </div> ); }; const triggerRender2 = ({ value, ...rest }) => { return ( <div style={{ margin: 4, whiteSpace: 'nowrap', textOverflow: 'ellipsis', flexGrow: 1, overflow: 'hidden', display: 'flex', alignItems: 'center', }} > <Tag size='large' color='cyan' shape='circle' suffixIcon={<IconChevronDown />}> {value.map(item => item.label).join(' / ')} </Tag> </div> ); }; return ( <div> <h4>Different Background Trigger</h4> <Select value={valList} triggerRender={triggerRender} optionList={list} onChange={value => setValList(value)} multiple filter searchPosition='dropdown' style={{ width: 240 }} ></Select> <br /> <br /> <h4>Use circle Tag as trigger</h4> <Select value={valList} onChange={value => setValList(value)} triggerRender={triggerRender2} optionList={list} filter multiple searchPosition='dropdown' style={{ width: 240, marginTop: 20, outline: 0 }} ></Select> </div> ); }; ``` The following is a more complex example: Reusing the drag-and-sort capability of TagInput and adding sorting to Select through triggerRender. ```jsx live=true import React, { useState } from 'react'; import { Select, TagInput } from '@douyinfe/semi-ui'; () => { const [valList, setValList] = useState(['douyin', 'ulikecam']); const [inputVal, setInputVal] = useState(''); const list = [ { value: 'douyin', label: 'Douyin' }, { value: 'ulikecam', label: 'UlikeCam' }, { value: 'jianying', label: 'Capcut' }, { value: 'doubao', label: 'Cici' }, ]; const handleSort = (currentLabels) => { const newValue = currentLabels.map(item => list.find((i) => i.label === item).value) setValList(newValue); }; const triggerRender = ({ value, onSearch, onClear }) => { return ( <div onKeyDown={e=>e.stopPropagation()}> <TagInput draggable allowDuplicates={false} value={value.map(item => item.label)} inputValue={inputVal} onInputChange={(word) => { onSearch(word); setInputVal(word); }} onChange={handleSort} onClear={() => onClear()} showClear /> </div> ); }; return ( <> <h4>Select that can reorder selected options by dragging</h4> <Select value={valList} triggerRender={triggerRender} optionList={list} onChange={value => setValList(value)} multiple filter style={{ width: 240 }} ></Select> </> ); }; ``` ### Custom Option Render - Simple customization: Pass the label property of Option or children into ReactNode, you can control the rendering of the candidates, and the content will automatically bring styles such as padding, background color, etc. - Complete customization: By passing in `renderOptionItem`, you can completely take over the rendering of the candidates in the list, and get the relevant state values from the callback input parameters. Achieve a higher degree of freedom of structural rendering Notice: 1. The style passed in by props needs to be consumed on wrapper dom, otherwise it will not be able to be used normally in virtualization scenarios 2. The styles of selected, focused, disabled, etc. state need to be added by yourself, and you can get the relative boolean value from props 3. `onMouseEnter`、`className` needs to be bound on the wrapper dom, otherwise the display will be problematic when the upper and lower keyboards are operated 4. If your custom item is Select.Option, you need to pass renderProps.onClick transparently to the onSelect prop of Option ```jsx live=true import React from 'react'; import { Select, Checkbox } from '@douyinfe/semi-ui'; () => { const renderOptionItem = renderProps => { const { disabled, selected, label, value, focused, className, style, onMouseEnter, onClick, empty, emptyContent, ...rest } = renderProps; const optionCls = classNames({ ['custom-option-render']: true, ['custom-option-render-focused']: focused, ['custom-option-render-disabled']: disabled, ['custom-option-render-selected']: selected, className, }); // Notice: // 1. The style passed in by props needs to be consumed on wrapper dom, otherwise it will not be able to be used normally in virtualization scenarios // 2. The styles of selected (selected), focused (focused), disabled (disabled) and other states need to be added by yourself, you can get the relative boolean value from props // 3.onMouseEnter needs to be bound on the wrapper dom, otherwise the display will be problematic when the upper and lower keyboards are operated return ( <div style={style} className={optionCls} onClick={() => onClick()} onMouseEnter={e => onMouseEnter()}> <Checkbox checked={selected} /> <div className="option-right">{label}</div> </div> ); }; const optionList = [ { value: 'douyin', label: 'Semi', otherKey: 0 }, { value: 'capcut', label: 'Capcut', disabled: true, otherKey: 1 }, { value: 'cam', label: 'UlikeCam', otherKey: 2 }, { value: 'buzz', label: 'Buzz', otherKey: 3 }, ]; return ( <> <Select filter defaultOpen defaultValue="douyin" dropdownClassName="components-select-demo-renderOptionItem" optionList={optionList} style={{ width: 180 }} renderOptionItem={renderOptionItem} /> <br /> <br /> <Select filter placeholder="multiple" multiple dropdownClassName="components-select-demo-renderOptionItem" optionList={optionList} style={{ width: 320, marginTop: 180 }} renderOptionItem={renderOptionItem} /> </> ); }; ``` ```scss .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 reference ### Select Props | Properties | Instructions | Type | Default | version | | --- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | --- | --- | | allowCreate | Whether to allow the user to create new entries. Needs to be used with `filter`. When allowCreate is enabled, it will no longer respond to updates to children or optionList | boolean | false | | arrowIcon | Customize the right drop-down arrow Icon, when the showClear switch is turned on and there is currently a selected value, hover will give priority to the clear icon | ReactNode | | | | autoAdjustOverflow | Whether the pop-up layer automatically adjusts the direction when it is obscured (only vertical direction is supported for the time being, and the inserted parent is body) | boolean | true | | autoClearSearchValue | After selecting the option, whether to automatically clear the search keywords, it will take effect when mutilple and filter are both enabled | boolean | true | 2.3.0| | autoFocus | Whether automatically focus when component mount | boolean | false | | borderless | borderless mode >=2.33.0 | boolean | | | className | The CSS class name of the wrapper element | string | | | clearIcon | Can be used to customize the clear button, valid when showClear is tru