Skip to content

Commit

Permalink
feat(Search): add Find button (#5790)
Browse files Browse the repository at this point in the history
* feat(Search): add Find button

* fix(Search): remove absolute position + code style fixes

* fix(Search): fix test get buttons func

* feat(Search): mv appear/disappear logic of controls to CSS

* fix(Search): fix flicking on Safari

* feat(Search): add find button

* chore: update screenshots

---------

Co-authored-by: Inomdzhon Mirdzhamolov <i.mirdzhamolov@vk.team>
  • Loading branch information
BlackySoul and inomdzhon authored Nov 20, 2023
1 parent 35693fd commit 8d0e56b
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 57 deletions.
54 changes: 54 additions & 0 deletions packages/vkui/src/components/Search/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,57 @@ const SearchExample = () => {

<SearchExample />;
```

## Поиск с кнопкой Найти

```jsx { "props": { "adaptivity": true } }
const App = () => {
const platform = usePlatform();

const isVKCOM = platform === Platform.VKCOM;

const [activeModal, setActiveModal] = React.useState(null);

const onFindButtonClick = () => {
setActiveModal('findModal');
};
return (
<SplitLayout
modal={
<ModalRoot activeModal={activeModal}>
<ModalCard
id="findModal"
onClose={() => setActiveModal(null)}
icon={<Icon56MoneyTransferOutline />}
header="Здесь ничего нет"
actions={
<Button size="l" mode="primary" stretched onClick={() => setActiveModal(null)}>
Понятно
</Button>
}
></ModalCard>
</ModalRoot>
}
header={!isVKCOM && <PanelHeader separator={false} />}
>
<SplitCol>
<View activePanel="find">
<Panel id="find">
<PanelHeader>Только для Compact-версии</PanelHeader>
<Group>
<Search
defaultValue="value"
icon={<Icon24Done />}
after={<Icon24User />}
onFindButtonClick={onFindButtonClick}
/>
</Group>
</Panel>
</View>
</SplitCol>
</SplitLayout>
);
};

<App />;
```
5 changes: 5 additions & 0 deletions packages/vkui/src/components/Search/Search.e2e-playground.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import { Icon16Add } from '@vkontakte/icons';
import { noop } from '@vkontakte/vkjs';
import { ComponentPlayground, type ComponentPlaygroundProps } from '@vkui-e2e/playground-helpers';
import { BREAKPOINTS, SizeType } from '../../lib/adaptivity';
import { Platform } from '../../lib/platform';
Expand Down Expand Up @@ -30,6 +31,10 @@ export const SearchPlayground = (props: ComponentPlaygroundProps) => {
{
noPadding: [true],
},
{
onFindButtonClick: [noop],
value: ['value'],
},
]}
>
{(props: SearchProps) => <Search style={{ maxWidth: '320px' }} {...props} />}
Expand Down
68 changes: 46 additions & 22 deletions packages/vkui/src/components/Search/Search.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
* Костыль для PanelHeader. Необходимо для растягивания на всю ширину.
*/
width: 10000px;
overflow: hidden;
}

.Search__field:hover {
Expand All @@ -45,7 +46,17 @@
background-color: var(--vkui--color_search_field_background--active);
}

.Search__control {
.Search__label {
position: absolute;
cursor: text;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0;
}

.Search__input {
padding-left: 12px;
color: var(--vkui--color_icon_medium);
display: flex;
Expand All @@ -54,9 +65,10 @@
width: calc(100% - 1px);
height: 100%;
border-radius: inherit;
position: relative;
}

.Search__input {
.Search__nativeInput {
position: absolute;
left: 0;
top: 0;
Expand All @@ -78,60 +90,66 @@
color: var(--vkui--color_text_primary);
}

.Search__input::-webkit-search-decoration,
.Search__input::-webkit-search-cancel-button,
.Search__input::-webkit-search-results-button,
.Search__input::-webkit-search-results-decoration {
.Search__nativeInput::-webkit-search-decoration,
.Search__nativeInput::-webkit-search-cancel-button,
.Search__nativeInput::-webkit-search-results-button,
.Search__nativeInput::-webkit-search-results-decoration {
display: none;
}

.Search__input:focus {
.Search__nativeInput:focus {
outline: none;
}

.Search--has-after .Search__input {
.Search--has-after .Search__nativeInput {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}

.Search--has-value .Search__input,
.Search--has-icon .Search__input {
margin-right: calc(var(--vkui_internal--search_height) + 4px);
}

.Search--has-icon.Search--has-value .Search__input {
margin-right: calc((var(--vkui_internal--search_height) + 4px) * 2);
}

.Search--disabled {
opacity: var(--vkui--opacity_disable_accessibility);
pointer-events: none;
cursor: default;
}

.Search__input:disabled {
.Search__nativeInput:disabled {
opacity: var(--vkui--opacity_disable_accessibility);
}

.Search__input::placeholder {
.Search__nativeInput::placeholder {
color: var(--vkui--color_text_secondary);
/* Для Firefox */
opacity: 1;
}

.Search__input:disabled::placeholder {
.Search__nativeInput:disabled::placeholder {
color: var(--vkui--color_text_secondary);
}

.Search__icons {
.Search__controls {
position: relative;
display: flex;
align-items: center;
justify-content: center;
color: var(--vkui--color_icon_secondary);
transition: transform 0.3s var(--vkui--animation_easing_platform);
/* Используем translate3d, чтобы поправить дергание при наведении в Safari */
transform: translate3d(100%, 0, 0);

--vkui_internal--search_icon_size: calc(var(--vkui_internal--search_height) + 4px);
}

.Search--has-icon .Search__controls {
/* Исключаем параметр icon из расчёта, чтобы он оставался видимым */
transform: translate3d(calc(100% - var(--vkui_internal--search_icon_size)), 0, 0);
}

.Search--has-value .Search__controls {
transform: translate3d(0, 0, 0);
}

.Search__icon {
width: calc(var(--vkui_internal--search_height) + 4px);
width: var(--vkui_internal--search_icon_size);
display: flex;
align-items: center;
justify-content: center;
Expand Down Expand Up @@ -172,6 +190,12 @@
overflow: hidden;
}

.Search__findButton {
min-width: initial;
max-width: initial;
width: initial;
}

.Group--mode-plain .Search {
padding-top: 4px;
}
11 changes: 10 additions & 1 deletion packages/vkui/src/components/Search/Search.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { baselineComponent } from '../../testing/utils';
import { Search } from './Search';
import styles from './Search.module.css';

const getInput = () => screen.getByRole('searchbox');
const getClearIcon = () => document.querySelector('.vkuiSearch__icon') as Element;
const getClearIcon = () => document.querySelector(`.${styles.Search__icon}`)!;
const getFindButton = () => document.querySelector(`.${styles.Search__findButton}`)!;

describe('Search', () => {
baselineComponent(Search);
Expand Down Expand Up @@ -89,4 +91,11 @@ describe('Search', () => {
userEvent.click(screen.getByTestId('icon'));
expect(cb).toHaveBeenCalled();
});

it('calls onFindButtonClick', () => {
const cb = jest.fn();
render(<Search value="test" onFindButtonClick={cb} />);
userEvent.click(getFindButton());
expect(cb).toHaveBeenCalled();
});
});
59 changes: 45 additions & 14 deletions packages/vkui/src/components/Search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import * as React from 'react';
import { Icon16Clear, Icon16SearchOutline, Icon24Cancel } from '@vkontakte/icons';
import { classNames, noop } from '@vkontakte/vkjs';
import { useAdaptivity } from '../../hooks/useAdaptivity';
import { useAdaptivityConditionalRender } from '../../hooks/useAdaptivityConditionalRender';
import { useBooleanState } from '../../hooks/useBooleanState';
import { useEnsuredControl } from '../../hooks/useEnsuredControl';
import { useExternRef } from '../../hooks/useExternRef';
import { useId } from '../../hooks/useId';
import { usePlatform } from '../../hooks/usePlatform';
import { SizeType } from '../../lib/adaptivity';
import { Platform } from '../../lib/platform';
Expand All @@ -26,20 +28,29 @@ export interface SearchProps
after?: React.ReactNode;
before?: React.ReactNode;
icon?: React.ReactNode;
onIconClick?: (e: VKUITouchEvent) => void;
onIconClick?(e: VKUITouchEvent): void;
defaultValue?: string;
iconAriaLabel?: string;
clearAriaLabel?: string;
/**
* Удаляет отступы у компонента
*/
noPadding?: boolean;
/**
* Текст для кнопки Найти
*/
findButtonText?: string;
/**
* Коллбэк для кнопки Найти
*/
onFindButtonClick?: React.MouseEventHandler<HTMLElement>;
}

/**
* @see https://vkcom.github.io/VKUI/#/Search
*/
export const Search = ({
id: idProp,
before = <Icon16SearchOutline />,
className,
defaultValue = '',
Expand All @@ -56,6 +67,8 @@ export const Search = ({
clearAriaLabel = 'Очистить',
noPadding,
getRootRef,
findButtonText = 'Найти',
onFindButtonClick,
...inputProps
}: SearchProps) => {
const inputRef = useExternRef(getRef);
Expand All @@ -64,13 +77,16 @@ export const Search = ({
setTrue: setFocusedTrue,
setFalse: setFocusedFalse,
} = useBooleanState(false);
const generatedId = useId();
const inputId = idProp ? idProp : `search-${generatedId}`;

const [value, onChange] = useEnsuredControl({
defaultValue,
onChange: onChangeProp,
value: valueProp,
});
const { sizeY = 'none' } = useAdaptivity();
const { sizeY: adaptiveSizeY } = useAdaptivityConditionalRender();
const platform = usePlatform();

const onFocus = (e: React.FocusEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -130,25 +146,29 @@ export const Search = ({
style={style}
>
<div className={styles['Search__field']}>
<label className={styles['Search__control']}>
<label htmlFor={inputId} className={styles['Search__label']}>
{placeholder}
</label>
<div className={styles['Search__input']}>
{before}
<Headline
Component="input"
type="search"
level="1"
weight="3"
{...inputProps}
id={inputId}
placeholder={placeholder}
autoComplete={autoComplete}
getRootRef={inputRef}
className={styles['Search__input']}
className={styles['Search__nativeInput']}
onFocus={onFocus}
onBlur={onBlur}
onChange={onChange}
value={value}
/>
</label>
<div className={styles['Search__icons']}>
</div>
<div className={styles['Search__controls']}>
{icon && (
<IconButton
hoverMode="opacity"
Expand All @@ -161,16 +181,27 @@ export const Search = ({
{icon}
</IconButton>
)}
{!!value && (
<IconButton
hoverMode="opacity"
onStart={onIconCancelClickStart}
onClick={onCancel}
className={styles['Search__icon']}
aria-label={clearAriaLabel}
<IconButton
hoverMode="opacity"
onStart={onIconCancelClickStart}
onClick={onCancel}
className={styles['Search__icon']}
aria-label={clearAriaLabel}
tabIndex={value ? undefined : -1}
>
{platform === Platform.IOS ? <Icon16Clear /> : <Icon24Cancel />}
</IconButton>
{adaptiveSizeY.compact && onFindButtonClick && (
<Button
mode="primary"
size="m"
className={classNames(styles['Search__findButton'], adaptiveSizeY.compact.className)}
focusVisibleMode="inside"
onClick={onFindButtonClick}
tabIndex={value ? undefined : -1}
>
{platform === Platform.IOS ? <Icon16Clear /> : <Icon24Cancel />}
</IconButton>
{findButtonText}
</Button>
)}
</div>
</div>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 8d0e56b

Please sign in to comment.