Skip to content

Commit

Permalink
feat(Select): add support mobile view (#579)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vladimir Chernitsyn authored Mar 27, 2023
1 parent 8ee1301 commit 0c0df4a
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 30 deletions.
4 changes: 4 additions & 0 deletions src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {DEFAULT_VIRTUALIZATION_THRESHOLD, selectBlock} from './constants';
import {useOnFocusOutside} from '../utils/useOnFocusOutside';

import type {CnMods} from '../utils/cn';
import {useMobile} from '../mobile';

import './Select.scss';

Expand Down Expand Up @@ -72,6 +73,7 @@ export const Select = React.forwardRef<HTMLButtonElement, SelectProps>(function
disablePortal,
onClose,
} = props;
const [mobile] = useMobile();
const [{filter}, dispatch] = React.useReducer(reducer, initialState);
// to avoid problem with incorrect popper offset calculation
// for example: https://github.com/radix-ui/primitives/issues/1567
Expand Down Expand Up @@ -235,6 +237,7 @@ export const Select = React.forwardRef<HTMLButtonElement, SelectProps>(function
handleClose={handleClose}
disablePortal={disablePortal}
virtualized={virtualized}
mobile={mobile}
>
{filterable && (
<SelectFilter
Expand All @@ -252,6 +255,7 @@ export const Select = React.forwardRef<HTMLButtonElement, SelectProps>(function
ref={listRef}
size={size}
value={value}
mobile={mobile}
flattenOptions={filteredFlattenOptions}
multiple={multiple}
virtualized={virtualized}
Expand Down
35 changes: 35 additions & 0 deletions src/components/Select/__tests__/Select.popup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {DEFAULT_OPTIONS, setup, TEST_QA} from './utils';
import userEvent from '@testing-library/user-event';
import {SelectQa} from '../constants';

const onUpdate = jest.fn();
describe('Select popup', () => {
test.each([
{
attribute: SelectQa.SHEET,
mode: 'mobile',
},
{
attribute: SelectQa.POPUP,
mode: 'desktop',
},
])('should render $attribute, when mode is $mode', async ({mode, attribute}) => {
const mobile = mode === 'mobile';
const {getByTestId, queryByTestId} = setup(
{
options: DEFAULT_OPTIONS,
onUpdate,
},
mobile,
);

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

expect(popup).not.toBeNull();
});
});
9 changes: 7 additions & 2 deletions src/components/Select/__tests__/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {act, render} from '@testing-library/react';
import {range} from 'lodash';
import {Select, SelectProps, SelectOption, SelectOptionGroup} from '..';
import {selectControlBlock, selectListBlock} from '../constants';
import {MobileProvider} from '../../mobile';

export const OptionsListType = {
FLAT: 'flat',
Expand Down Expand Up @@ -46,8 +47,12 @@ export const ControlledSelect = (props: Partial<SelectProps>) => {
);
};

export function setup(props: Partial<SelectProps> = {}) {
const utils = render(<ControlledSelect {...props} />);
export function setup(props: Partial<SelectProps> = {}, mobile?: boolean) {
const utils = render(
<MobileProvider mobile={Boolean(mobile)}>
<ControlledSelect {...props} />
</MobileProvider>,
);
return utils;
}

Expand Down
24 changes: 24 additions & 0 deletions src/components/Select/components/SelectList/SelectList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ $xl-right-padding: '12px';
border-radius: 0;
}

&_mobile {
max-height: calc(90vh - 20px);
}

&__group-label {
box-sizing: border-box;
position: relative;
Expand All @@ -52,6 +56,12 @@ $xl-right-padding: '12px';
font-size: 15px;
}

#{$block}_mobile & {
height: #{$l-height};
padding: 12px #{$l-right-padding} 8px #{$l-left-padding};
font-size: 15px;
}

#{$block}__item:not(:first-child) & {
margin-top: 5px;

Expand Down Expand Up @@ -135,6 +145,20 @@ $xl-right-padding: '12px';
}
}

#{$block}_mobile & {
padding: 0 #{$l-right-padding} 0 #{$l-left-padding};

& #{$block}__option-default-label {
height: #{$l-height};
line-height: #{$l-height};
font-size: 15px;
}

& #{$block}__tick-icon {
padding-left: #{$l-right-padding};
}
}

&_colored {
background-color: var(--yc-color-base-selection);
}
Expand Down
7 changes: 5 additions & 2 deletions src/components/Select/components/SelectList/SelectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {OptionWrap} from './OptionWrap';
import './SelectList.scss';

type SelectListProps = {
mobile: boolean;
onOptionClick: (option: FlattenOption) => void;
renderOption?: SelectProps['renderOption'];
getOptionHeight?: SelectProps['getOptionHeight'];
Expand All @@ -29,16 +30,18 @@ export const SelectList = React.forwardRef<List<FlattenOption>, SelectListProps>
value,
multiple,
virtualized,
mobile,
} = props;
const optionsHeight = getOptionsHeight({
options: flattenOptions,
getOptionHeight,
size,
mobile,
});

const getItemHeight = React.useCallback(
(option: FlattenOption, index: number) => {
return getPopupItemHeight({getOptionHeight, size, option, index});
return getPopupItemHeight({getOptionHeight, size, option, index, mobile});
},
[getOptionHeight, size],
);
Expand All @@ -64,7 +67,7 @@ export const SelectList = React.forwardRef<List<FlattenOption>, SelectListProps>
return (
<List
ref={ref}
className={selectListBlock({size, virtualized})}
className={selectListBlock({size, virtualized, mobile})}
qa={SelectQa.LIST}
itemClassName={selectListBlock('item')}
itemHeight={getItemHeight}
Expand Down
57 changes: 39 additions & 18 deletions src/components/Select/components/SelectPopup/SelectPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,51 @@ import type {SelectPopupProps} from './types';
import {getModifiers} from './modifiers';

import './SelectPopup.scss';
import {Sheet} from '../../../Sheet';

const b = blockNew('select-popup');

export const SelectPopup = React.forwardRef<HTMLDivElement, SelectPopupProps>(
(
{handleClose, width, open, controlRef, children, className, disablePortal, virtualized},
{
handleClose,
width,
open,
controlRef,
children,
className,
disablePortal,
virtualized,
mobile,
},
ref,
) => (
<Popup
className={b(null, className)}
qa={SelectQa.POPUP}
anchorRef={ref as React.RefObject<HTMLDivElement>}
placement={['bottom-start', 'bottom-end', 'top-start', 'top-end']}
offset={[BORDER_WIDTH, BORDER_WIDTH]}
open={open}
onClose={handleClose}
disablePortal={disablePortal}
restoreFocus
restoreFocusRef={controlRef}
modifiers={getModifiers({width, disablePortal, virtualized})}
>
{children}
</Popup>
),
) =>
mobile ? (
<Sheet
qa={SelectQa.SHEET}
className={className}
visible={Boolean(open)}
onClose={handleClose}
>
{children}
</Sheet>
) : (
<Popup
className={b(null, className)}
qa={SelectQa.POPUP}
anchorRef={ref as React.RefObject<HTMLDivElement>}
placement={['bottom-start', 'bottom-end', 'top-start', 'top-end']}
offset={[BORDER_WIDTH, BORDER_WIDTH]}
open={open}
onClose={handleClose}
disablePortal={disablePortal}
restoreFocus
restoreFocusRef={controlRef}
modifiers={getModifiers({width, disablePortal, virtualized})}
>
{children}
</Popup>
),
);

SelectPopup.displayName = 'SelectPopup';
1 change: 1 addition & 0 deletions src/components/Select/components/SelectPopup/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type React from 'react';

export type SelectPopupProps = {
mobile: boolean;
handleClose: () => void;
width?: number;
open?: boolean;
Expand Down
3 changes: 3 additions & 0 deletions src/components/Select/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const SIZE_TO_ITEM_HEIGHT: Record<NonNullable<SelectProps['size']>, numbe
xl: 36,
};

export const MOBILE_ITEM_HEIGHT = 32;

export const GROUP_ITEM_MARGIN_TOP = 5;

export const BORDER_WIDTH = 1;
Expand All @@ -27,4 +29,5 @@ export const DEFAULT_VIRTUALIZATION_THRESHOLD = 50;
export const SelectQa = {
LIST: 'select-list',
POPUP: 'select-popup',
SHEET: 'select-sheet',
};
17 changes: 11 additions & 6 deletions src/components/Select/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {List, ListItemData} from '../List';
import {KeyCode} from '../constants';
import type {SelectProps, SelectOption, SelectOptionGroup} from './types';
import {Option, OptionGroup} from './tech-components';
import {GROUP_ITEM_MARGIN_TOP, SIZE_TO_ITEM_HEIGHT} from './constants';
import {GROUP_ITEM_MARGIN_TOP, MOBILE_ITEM_HEIGHT, SIZE_TO_ITEM_HEIGHT} from './constants';

// "disable" property needs to deactivate group title item in List
type GroupTitleItem = {label: string; disabled: true};
Expand All @@ -31,25 +31,30 @@ export const getPopupItemHeight = (args: {
size: NonNullable<SelectProps['size']>;
option: FlattenOption;
index: number;
mobile: boolean;
}) => {
const {getOptionHeight, size, option, index} = args;
const {getOptionHeight, size, option, index, mobile} = args;

const itemHeight = mobile ? MOBILE_ITEM_HEIGHT : SIZE_TO_ITEM_HEIGHT[size];

if ('label' in option) {
const marginTop = index === 0 ? 0 : GROUP_ITEM_MARGIN_TOP;
return SIZE_TO_ITEM_HEIGHT[size] + marginTop;

return itemHeight + marginTop;
}

return getOptionHeight ? getOptionHeight(option) : SIZE_TO_ITEM_HEIGHT[size];
return getOptionHeight ? getOptionHeight(option) : itemHeight;
};

export const getOptionsHeight = (args: {
getOptionHeight?: SelectProps['getOptionHeight'];
size: NonNullable<SelectProps['size']>;
options: FlattenOption[];
mobile: boolean;
}) => {
const {getOptionHeight, size, options} = args;
const {getOptionHeight, size, options, mobile} = args;
return options.reduce((height, option, index) => {
return height + getPopupItemHeight({getOptionHeight, size, option, index});
return height + getPopupItemHeight({getOptionHeight, size, option, index, mobile});
}, 0);
};

Expand Down
6 changes: 4 additions & 2 deletions src/components/Sheet/Sheet.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {QAProps} from '../types';
import {SheetContentContainer} from './SheetContent';
import {sheetBlock} from './constants';

import './Sheet.scss';

export interface SheetProps {
export interface SheetProps extends QAProps {
children?: React.ReactNode;
onClose?: () => void;
/** Show/hide sheet */
Expand Down Expand Up @@ -107,10 +108,11 @@ export class Sheet extends React.Component<SheetProps, SheetState> {
visible,
allowHideOnContentScroll,
hideTopBar,
qa,
} = this.props;

return (
<div className={sheetBlock(null, className)}>
<div data-qa={qa} className={sheetBlock(null, className)}>
<SheetContentContainer
id={id}
content={children}
Expand Down

0 comments on commit 0c0df4a

Please sign in to comment.