Skip to content

Commit

Permalink
chore: renderPopup for Select (#1312)
Browse files Browse the repository at this point in the history
  • Loading branch information
GermanVor committed Feb 7, 2024
1 parent 6d6b369 commit 25e0f24
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 17 deletions.
87 changes: 87 additions & 0 deletions src/components/Select/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,92 @@ const MyComponent = () => {

<!--/GITHUB_BLOCK-->

### Render custom popup

To render custom popup use the `renderPopup` property.

<!--LANDING_BLOCK
<ExampleBlock
code={`
<Select
filterable
renderSelectedOption={({renderList, renderFilter}) => {
return (
<React.Fragment>
{renderFilter()}
<div className="CustomElement" />
{renderList()}
</React.Fragment>
);
}}
>
<Select.Option value="val_1" data={{color: '#8FE1A1'}}>Value 1</Select.Option>
<Select.Option value="val_2" data={{color: '#38C0A8'}}>Value 2</Select.Option>
<Select.Option value="val_3" data={{color: '#3A7AC3'}}>Value 3</Select.Option>
<Select.Option value="val_4" data={{color: '#534581'}}>Value 4</Select.Option>
</Select>
`}
>
<UIKit.Select
filterable
placeholder="Custom selected options"
renderSelectedOption={({renderList, renderFilter}) => {
return (
<React.Fragment>
{renderFilter()}
<div style={{width: "100%", height: "20px", backgroundColor: "tomato"}} />
{renderList()}
</React.Fragment>
);
}}
>
<UIKit.Select.Option value="val_1" data={{color: '#8FE1A1'}}>Value 1</UIKit.Select.Option>
<UIKit.Select.Option value="val_2" data={{color: '#38C0A8'}}>Value 2</UIKit.Select.Option>
<UIKit.Select.Option value="val_3" data={{color: '#3A7AC3'}}>Value 3</UIKit.Select.Option>
<UIKit.Select.Option value="val_4" data={{color: '#534581'}}>Value 4</UIKit.Select.Option>
</UIKit.Select>
</ExampleBlock>
LANDING_BLOCK-->

<!--GITHUB_BLOCK-->

```tsx
import type {SelectProps} from '@gravity-ui/uikit';

const renderPopup: SelectProps['renderPopup'] = ({renderList, renderFilter}) => {
return (
<React.Fragment>
{renderFilter()}
<div className="CustomElement" />
{renderList()}
</React.Fragment>
);
};

const MyComponent = () => {
return (
<Select filterable renderPopup={renderPopup}>
<Select.Option value="val_1" data={{color: '#8FE1A1'}}>
Value 1
</Select.Option>
<Select.Option value="val_2" data={{color: '#38C0A8'}}>
Value 2
</Select.Option>
<Select.Option value="val_3" data={{color: '#3A7AC3'}}>
Value 3
</Select.Option>
<Select.Option value="val_4" data={{color: '#534581'}}>
Value 4
</Select.Option>
</Select>
);
};
```

<!--/GITHUB_BLOCK-->

### Error

The state of the `Select` in which you want to indicate incorrect user input. To change `Select` appearance, use the `validationState` property with the `"invalid"` value. An optional message text can be added via the `errorMessage` property. By default, message text is rendered outside the component.
Expand Down Expand Up @@ -1030,6 +1116,7 @@ LANDING_BLOCK-->
| [renderOption](#render-custom-options) | Used to render user options | `function` | |
| renderOptionGroup | Used to render user option groups | `function` | |
| [renderSelectedOption](#render-custom-selected-options) | Used to render user selected options | `function` | |
| [renderPopup](#render-custom-popup) | Used to render user popup content | `function` | |
| [size](#size) | Control / options size | `string` | `'m'` |
| value | Values that represent selected options | `string[]` | |
| view | Control view | `string` | `'normal'` |
Expand Down
23 changes: 17 additions & 6 deletions src/components/Select/__tests__/Select.filter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {cleanup} from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import {TextInput} from '../../controls';
import type {SelectOption, SelectProps} from '../types';
import type {SelectOption, SelectProps, SelectRenderPopup} from '../types';

import {TEST_QA, generateOptions, generateOptionsGroups, setup} from './utils';

Expand All @@ -17,7 +17,7 @@ const onFilterChange = jest.fn();
const FILTER_PLACEHOLDER = 'Filter placeholder';
const EMPTY_OPTIONS_QA = 'empty-options';

const renderCustomFilter: SelectProps['renderFilter'] = (props) => {
const RENDER_CUSTOM_FILTER: SelectProps['renderFilter'] = (props) => {
const {value, ref, onChange, onKeyDown} = props;

return (
Expand All @@ -31,17 +31,28 @@ const renderCustomFilter: SelectProps['renderFilter'] = (props) => {
);
};

const RENDER_POPUP: SelectRenderPopup = ({renderList, renderFilter}) => {
return (
<React.Fragment>
{renderFilter()}
{renderList()}
</React.Fragment>
);
};

describe('Select filter', () => {
test.each<[string, Partial<SelectProps>]>([
['default', {renderFilter: undefined}],
['custom', {renderFilter: renderCustomFilter}],
])('base functional with %s filter section', async (_, {renderFilter}) => {
test.each([
['default', undefined, undefined],
['custom', RENDER_CUSTOM_FILTER, RENDER_POPUP],
['custom', RENDER_CUSTOM_FILTER, undefined],
])('base functional with %s filter section', async (_, renderFilter, renderPopup) => {
const {getByTestId, getByPlaceholderText, getAllByRole, queryAllByRole} = setup({
options: generateOptions(40),
filterPlaceholder: FILTER_PLACEHOLDER,
filterable: true,
onFilterChange,
renderFilter,
renderPopup,
});
const user = userEvent.setup();
const selectControl = getByTestId(TEST_QA);
Expand Down
57 changes: 57 additions & 0 deletions src/components/Select/__tests__/Select.renderPopup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';

import userEvent from '@testing-library/user-event';

import {SelectQa} from '../constants';

import {DEFAULT_OPTIONS, TEST_QA, setup} from './utils';

const QA = 'SELECT_RENDER_POPUP_TEST_QA';

describe('Select renderPopup', () => {
test('default case', async () => {
const {getByTestId} = setup({
options: DEFAULT_OPTIONS,
filterable: true,
renderPopup: ({renderFilter, renderList}) => {
return (
<React.Fragment>
{renderFilter()}
<div data-qa={QA} />
{renderList()}
</React.Fragment>
);
},
});

const user = userEvent.setup();
const selectControl = getByTestId(TEST_QA);
// open select popup
await user.click(selectControl);

const filterInput = getByTestId(SelectQa.FILTER_INPUT);
expect(filterInput).toBeVisible();

const list = getByTestId(SelectQa.LIST);
expect(list).toBeVisible();

const customPopupDiv = getByTestId(QA);
expect(customPopupDiv).toBeVisible();
});

test('empty options', async () => {
const {getByTestId} = setup({
options: [],
renderEmptyOptions: () => <div data-qa={QA} />,
renderPopup: ({renderList}) => renderList(),
});

const user = userEvent.setup();
const selectControl = getByTestId(TEST_QA);
// open select popup
await user.click(selectControl);

const emptyContent = getByTestId(QA);
expect(emptyContent).toBeVisible();
});
});
29 changes: 19 additions & 10 deletions src/components/Select/__tests__/Select.single.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {cleanup} from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import type {SelectRenderPopup} from '../types';

import {
DEFAULT_OPTIONS,
GROUPED_OPTIONS,
Expand All @@ -18,15 +20,18 @@ afterEach(() => {
const onUpdate = jest.fn();
const onOpenChange = jest.fn();
const SELECTED_OPTION = DEFAULT_OPTIONS[0];
const RENDER_POPUP: SelectRenderPopup = ({renderList}) => renderList();

describe('Select single mode actions', () => {
describe('select option by click in', () => {
test.each([
[OptionsListType.FLAT, SELECTED_OPTION],
[OptionsListType.GROUPED, SELECTED_OPTION],
])('%s list', async (type, selectedOption) => {
[OptionsListType.FLAT, SELECTED_OPTION, RENDER_POPUP],
[OptionsListType.GROUPED, SELECTED_OPTION, RENDER_POPUP],
[OptionsListType.FLAT, SELECTED_OPTION, undefined],
[OptionsListType.GROUPED, SELECTED_OPTION, undefined],
])('%s list', async (type, selectedOption, renderPopup) => {
const options = type === 'grouped' ? GROUPED_OPTIONS : DEFAULT_OPTIONS;
const {getByTestId, getByText} = setup({options, onUpdate, onOpenChange});
const {getByTestId, getByText} = setup({options, onUpdate, onOpenChange, renderPopup});
const user = userEvent.setup();
const selectControl = getByTestId(TEST_QA);
// open select popup
Expand All @@ -42,13 +47,17 @@ describe('Select single mode actions', () => {

describe('select option by', () => {
test.each([
['Enter', OptionsListType.FLAT, SELECTED_OPTION],
['Enter', OptionsListType.GROUPED, SELECTED_OPTION],
['Space', OptionsListType.FLAT, SELECTED_OPTION],
['Space', OptionsListType.GROUPED, SELECTED_OPTION],
])('%s in %s list', async (key, type, selectedOption) => {
['Enter', OptionsListType.FLAT, SELECTED_OPTION, RENDER_POPUP],
['Enter', OptionsListType.GROUPED, SELECTED_OPTION, RENDER_POPUP],
['Space', OptionsListType.FLAT, SELECTED_OPTION, RENDER_POPUP],
['Space', OptionsListType.GROUPED, SELECTED_OPTION, RENDER_POPUP],
['Enter', OptionsListType.FLAT, SELECTED_OPTION, undefined],
['Enter', OptionsListType.GROUPED, SELECTED_OPTION, undefined],
['Space', OptionsListType.FLAT, SELECTED_OPTION, undefined],
['Space', OptionsListType.GROUPED, SELECTED_OPTION, undefined],
])('%s in %s list', async (key, type, selectedOption, renderPopup) => {
const options = type === 'grouped' ? GROUPED_OPTIONS : DEFAULT_OPTIONS;
const {getByTestId} = setup({options, onUpdate, onOpenChange});
const {getByTestId} = setup({options, onUpdate, onOpenChange, renderPopup});
const user = userEvent.setup();
const selectControl = getByTestId(TEST_QA);
await user.keyboard('[Tab]');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';

import {TextInput} from '../../../controls';
import {blockNew as block} from '../../../utils/cn';
import {SelectQa} from '../../constants';
import type {SelectProps} from '../../types';
import type {SelectFilterRef} from '../../types-misc';

Expand Down Expand Up @@ -48,7 +49,7 @@ export const SelectFilter = React.forwardRef<SelectFilterRef, SelectFilterProps>
placeholder={placeholder}
onUpdate={onChange}
onKeyDown={onKeyDown}
qa={SELECT_FILTER_QA}
qa={SelectQa.FILTER_INPUT}
/>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/Select/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ export const SelectQa = {
POPUP: 'select-popup',
SHEET: 'select-sheet',
CLEAR: 'select-clear',
FILTER_INPUT: 'select-filter-input',
};

0 comments on commit 25e0f24

Please sign in to comment.