Skip to content

Commit 9a7fc96

Browse files
xliezyoyo837
andauthored
feat: support custom label render (#995)
* feat: support custom label render * fix: update tests & demo & doc * Update src/Select.tsx Co-authored-by: Amumu <[email protected]> * fix: demo * fix: ci lint Signed-off-by: xliez <[email protected]> * fix: hooks deps & format demo * test: 测试用例调整 * chore: fix lint Signed-off-by: xliez <[email protected]> * test: 添加测试用例 --------- Signed-off-by: xliez <[email protected]> Co-authored-by: Amumu <[email protected]>
1 parent b6f4829 commit 9a7fc96

File tree

5 files changed

+144
-2
lines changed

5 files changed

+144
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export default () => (
130130
| virtual | Disable virtual scroll | boolean | true |
131131
| direction | direction of dropdown | 'ltr' \| 'rtl' | 'ltr' |
132132
| optionRender | Custom rendering options | (oriOption: FlattenOptionData\<BaseOptionType\> , info: { index: number }) => React.ReactNode | - |
133+
| labelRender | Custom rendering label | (props: LabelInValueType) => React.ReactNode | - |
133134
| maxCount | The max number of items can be selected | number | - |
134135

135136
### Methods

docs/demo/custom-label.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: custom-label
3+
nav:
4+
title: Demo
5+
path: /demo
6+
---
7+
8+
<code src="../examples/custom-label.tsx"></code>

docs/examples/custom-label.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/* eslint-disable no-console */
2+
import Select, { Option } from 'rc-select';
3+
import React from 'react';
4+
import '../../assets/index.less';
5+
6+
const children = [];
7+
for (let i = 10; i < 36; i += 1) {
8+
children.push(
9+
<Option key={i.toString(36) + i} test={i}>
10+
{i.toString(36) + i}
11+
</Option>,
12+
);
13+
}
14+
15+
const Test: React.FC = () => {
16+
const [value, setValue] = React.useState<string>('test');
17+
18+
return (
19+
<div>
20+
<h2>custom label render</h2>
21+
22+
<div>
23+
<Select
24+
placeholder="placeholder"
25+
style={{ width: 500 }}
26+
value={value}
27+
onChange={(val: string, option) => {
28+
console.log('change', val, option);
29+
setValue(val);
30+
}}
31+
onSelect={(val, option) => {
32+
console.log('selected', val, option);
33+
}}
34+
onDeselect={(val, option) => {
35+
console.log('deselected', val, option);
36+
}}
37+
tokenSeparators={[',']}
38+
labelRender={(props) => {
39+
const { label, value: _value } = props;
40+
const style: React.CSSProperties = { backgroundColor: 'red' };
41+
if (label) {
42+
return _value;
43+
} else return <span style={style}>no this value in options</span>;
44+
}}
45+
onFocus={() => console.log('focus')}
46+
onBlur={() => console.log('blur')}
47+
>
48+
{children}
49+
</Select>
50+
</div>
51+
<p>
52+
<button
53+
type="button"
54+
onClick={() => {
55+
setValue('test');
56+
}}
57+
>
58+
set value as test
59+
</button>
60+
</p>
61+
</div>
62+
);
63+
};
64+
65+
export default Test;
66+
/* eslint-enable */

src/Select.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export interface SelectProps<ValueType = any, OptionType extends BaseOptionType
149149
direction?: 'ltr' | 'rtl';
150150
listHeight?: number;
151151
listItemHeight?: number;
152+
labelRender?: (props: LabelInValueType) => React.ReactNode;
152153

153154
// >>> Icon
154155
menuItemSelectedIcon?: RenderNode;
@@ -199,6 +200,7 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
199200
direction,
200201
listHeight = 200,
201202
listItemHeight = 20,
203+
labelRender,
202204

203205
// Value
204206
value,
@@ -343,9 +345,9 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
343345

344346
return mergedValues.map((item) => ({
345347
...item,
346-
label: item.label ?? item.value,
348+
label: (typeof labelRender === 'function' ? labelRender(item) : item.label) ?? item.value,
347349
}));
348-
}, [mode, mergedValues]);
350+
}, [mode, mergedValues, labelRender]);
349351

350352
/** Convert `displayValues` to raw value type set */
351353
const rawValues = React.useMemo(

tests/Select.test.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { LabelInValueType } from '@/Select';
12
import { fireEvent, render as testingRender } from '@testing-library/react';
23
import { mount, render } from 'enzyme';
34
import KeyCode from 'rc-util/lib/KeyCode';
@@ -2115,6 +2116,70 @@ describe('Select.Basic', () => {
21152116
);
21162117
});
21172118

2119+
it('labelRender', () => {
2120+
const onLabelRender = jest.fn();
2121+
const labelRender = (props: LabelInValueType) => {
2122+
const { label, value } = props;
2123+
onLabelRender();
2124+
return `${label}-${value}`;
2125+
};
2126+
const wrapper = mount(
2127+
<Select options={[{ label: 'realLabel', value: 'a' }]} value="a" labelRender={labelRender} />,
2128+
);
2129+
2130+
expect(onLabelRender).toHaveBeenCalled();
2131+
expect(findSelection(wrapper).text()).toEqual('realLabel-a');
2132+
});
2133+
2134+
it('labelRender when value is not in options', () => {
2135+
const onLabelRender = jest.fn();
2136+
const options = [{ label: 'realLabel', value: 'b' }];
2137+
const labelRender = (props: LabelInValueType) => {
2138+
const { label, value } = props;
2139+
// current value is in options
2140+
if (options.find((item) => item.value === value)) {
2141+
return label;
2142+
} else {
2143+
// current value is not in options
2144+
onLabelRender();
2145+
return `${label || 'fakeLabel'}-${value}`;
2146+
}
2147+
};
2148+
const wrapper = mount(<Select value="a" labelRender={labelRender} options={options} />);
2149+
2150+
expect(onLabelRender).toHaveBeenCalled();
2151+
expect(findSelection(wrapper).text()).toEqual('fakeLabel-a');
2152+
});
2153+
2154+
it('labelRender when labelInValue and useCache', () => {
2155+
const onLabelRender = jest.fn();
2156+
const labelRender = (props: LabelInValueType) => {
2157+
const { label, value } = props;
2158+
onLabelRender({ label, value });
2159+
return `custom label`;
2160+
};
2161+
2162+
const wrapper = mount(
2163+
<Select
2164+
labelInValue
2165+
value={{ key: 1, label: 'One' }}
2166+
labelRender={labelRender}
2167+
options={[
2168+
{
2169+
value: 2,
2170+
label: 'Two',
2171+
},
2172+
]}
2173+
/>,
2174+
);
2175+
2176+
expect(onLabelRender).toHaveBeenCalledWith({ label: 'One', value: 1 });
2177+
expect(findSelection(wrapper).text()).toEqual('custom label');
2178+
2179+
wrapper.setProps({ options: [] });
2180+
expect(findSelection(wrapper).text()).toEqual('custom label');
2181+
});
2182+
21182183
it('multiple items should not disabled', () => {
21192184
const { container } = testingRender(
21202185
<Select

0 commit comments

Comments
 (0)