Skip to content

Commit

Permalink
feat: support custom label render (#995)
Browse files Browse the repository at this point in the history
* feat: support custom label render

* fix: update tests & demo & doc

* Update src/Select.tsx

Co-authored-by: Amumu <yoyo837@hotmail.com>

* fix: demo

* fix: ci lint

Signed-off-by: xliez <xliez@foxmail.com>

* fix: hooks deps & format demo

* test: 测试用例调整

* chore: fix lint

Signed-off-by: xliez <xliez@foxmail.com>

* test: 添加测试用例

---------

Signed-off-by: xliez <xliez@foxmail.com>
Co-authored-by: Amumu <yoyo837@hotmail.com>
  • Loading branch information
xliez and yoyo837 committed Jan 2, 2024
1 parent b6f4829 commit 9a7fc96
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -130,6 +130,7 @@ export default () => (
| virtual | Disable virtual scroll | boolean | true |
| direction | direction of dropdown | 'ltr' \| 'rtl' | 'ltr' |
| optionRender | Custom rendering options | (oriOption: FlattenOptionData\<BaseOptionType\> , info: { index: number }) => React.ReactNode | - |
| labelRender | Custom rendering label | (props: LabelInValueType) => React.ReactNode | - |
| maxCount | The max number of items can be selected | number | - |

### Methods
Expand Down
8 changes: 8 additions & 0 deletions docs/demo/custom-label.md
@@ -0,0 +1,8 @@
---
title: custom-label
nav:
title: Demo
path: /demo
---

<code src="../examples/custom-label.tsx"></code>
66 changes: 66 additions & 0 deletions docs/examples/custom-label.tsx
@@ -0,0 +1,66 @@
/* eslint-disable no-console */
import Select, { Option } from 'rc-select';
import React from 'react';
import '../../assets/index.less';

const children = [];
for (let i = 10; i < 36; i += 1) {
children.push(
<Option key={i.toString(36) + i} test={i}>
{i.toString(36) + i}
</Option>,
);
}

const Test: React.FC = () => {
const [value, setValue] = React.useState<string>('test');

return (
<div>
<h2>custom label render</h2>

<div>
<Select
placeholder="placeholder"
style={{ width: 500 }}
value={value}
onChange={(val: string, option) => {
console.log('change', val, option);
setValue(val);
}}
onSelect={(val, option) => {
console.log('selected', val, option);
}}
onDeselect={(val, option) => {
console.log('deselected', val, option);
}}
tokenSeparators={[',']}
labelRender={(props) => {
const { label, value: _value } = props;
const style: React.CSSProperties = { backgroundColor: 'red' };
if (label) {
return _value;
} else return <span style={style}>no this value in options</span>;
}}
onFocus={() => console.log('focus')}
onBlur={() => console.log('blur')}
>
{children}
</Select>
</div>
<p>
<button
type="button"
onClick={() => {
setValue('test');
}}
>
set value as test
</button>
</p>
</div>
);
};

export default Test;
/* eslint-enable */
6 changes: 4 additions & 2 deletions src/Select.tsx
Expand Up @@ -149,6 +149,7 @@ export interface SelectProps<ValueType = any, OptionType extends BaseOptionType
direction?: 'ltr' | 'rtl';
listHeight?: number;
listItemHeight?: number;
labelRender?: (props: LabelInValueType) => React.ReactNode;

// >>> Icon
menuItemSelectedIcon?: RenderNode;
Expand Down Expand Up @@ -199,6 +200,7 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
direction,
listHeight = 200,
listItemHeight = 20,
labelRender,

// Value
value,
Expand Down Expand Up @@ -343,9 +345,9 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp

return mergedValues.map((item) => ({
...item,
label: item.label ?? item.value,
label: (typeof labelRender === 'function' ? labelRender(item) : item.label) ?? item.value,
}));
}, [mode, mergedValues]);
}, [mode, mergedValues, labelRender]);

/** Convert `displayValues` to raw value type set */
const rawValues = React.useMemo(
Expand Down
65 changes: 65 additions & 0 deletions tests/Select.test.tsx
@@ -1,3 +1,4 @@
import type { LabelInValueType } from '@/Select';
import { fireEvent, render as testingRender } from '@testing-library/react';
import { mount, render } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
Expand Down Expand Up @@ -2115,6 +2116,70 @@ describe('Select.Basic', () => {
);
});

it('labelRender', () => {
const onLabelRender = jest.fn();
const labelRender = (props: LabelInValueType) => {
const { label, value } = props;
onLabelRender();
return `${label}-${value}`;
};
const wrapper = mount(
<Select options={[{ label: 'realLabel', value: 'a' }]} value="a" labelRender={labelRender} />,
);

expect(onLabelRender).toHaveBeenCalled();
expect(findSelection(wrapper).text()).toEqual('realLabel-a');
});

it('labelRender when value is not in options', () => {
const onLabelRender = jest.fn();
const options = [{ label: 'realLabel', value: 'b' }];
const labelRender = (props: LabelInValueType) => {
const { label, value } = props;
// current value is in options
if (options.find((item) => item.value === value)) {
return label;
} else {
// current value is not in options
onLabelRender();
return `${label || 'fakeLabel'}-${value}`;
}
};
const wrapper = mount(<Select value="a" labelRender={labelRender} options={options} />);

expect(onLabelRender).toHaveBeenCalled();
expect(findSelection(wrapper).text()).toEqual('fakeLabel-a');
});

it('labelRender when labelInValue and useCache', () => {
const onLabelRender = jest.fn();
const labelRender = (props: LabelInValueType) => {
const { label, value } = props;
onLabelRender({ label, value });
return `custom label`;
};

const wrapper = mount(
<Select
labelInValue
value={{ key: 1, label: 'One' }}
labelRender={labelRender}
options={[
{
value: 2,
label: 'Two',
},
]}
/>,
);

expect(onLabelRender).toHaveBeenCalledWith({ label: 'One', value: 1 });
expect(findSelection(wrapper).text()).toEqual('custom label');

wrapper.setProps({ options: [] });
expect(findSelection(wrapper).text()).toEqual('custom label');
});

it('multiple items should not disabled', () => {
const { container } = testingRender(
<Select
Expand Down

1 comment on commit 9a7fc96

@vercel
Copy link

@vercel vercel bot commented on 9a7fc96 Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

select – ./

select.vercel.app
select-react-component.vercel.app
select-git-master-react-component.vercel.app

Please sign in to comment.