From 8d0e56b111eb38c3a018924ff627363c7599df32 Mon Sep 17 00:00:00 2001 From: Victoria Zhizhonkova Date: Mon, 20 Nov 2023 19:35:55 +0700 Subject: [PATCH] feat(Search): add Find button (#5790) * 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 --- packages/vkui/src/components/Search/Readme.md | 54 +++++++++++++++ .../Search/Search.e2e-playground.tsx | 5 ++ .../src/components/Search/Search.module.css | 68 +++++++++++++------ .../src/components/Search/Search.test.tsx | 11 ++- .../vkui/src/components/Search/Search.tsx | 59 ++++++++++++---- .../search-android-chromium-dark-1-snap.png | 4 +- .../search-android-chromium-light-1-snap.png | 4 +- .../search-ios-webkit-dark-1-snap.png | 4 +- .../search-ios-webkit-light-1-snap.png | 4 +- .../search-vkcom-chromium-dark-1-snap.png | 4 +- .../search-vkcom-chromium-light-1-snap.png | 4 +- .../search-vkcom-firefox-dark-1-snap.png | 4 +- .../search-vkcom-firefox-light-1-snap.png | 4 +- .../search-vkcom-webkit-dark-1-snap.png | 4 +- .../search-vkcom-webkit-light-1-snap.png | 4 +- 15 files changed, 180 insertions(+), 57 deletions(-) diff --git a/packages/vkui/src/components/Search/Readme.md b/packages/vkui/src/components/Search/Readme.md index 9259106acc..00f7534cff 100644 --- a/packages/vkui/src/components/Search/Readme.md +++ b/packages/vkui/src/components/Search/Readme.md @@ -185,3 +185,57 @@ const 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 ( + + setActiveModal(null)} + icon={} + header="Здесь ничего нет" + actions={ + + } + > + + } + header={!isVKCOM && } + > + + + + Только для Compact-версии + + } + after={} + onFindButtonClick={onFindButtonClick} + /> + + + + + + ); +}; + +; +``` diff --git a/packages/vkui/src/components/Search/Search.e2e-playground.tsx b/packages/vkui/src/components/Search/Search.e2e-playground.tsx index 32502ee77a..b45cdf82ce 100644 --- a/packages/vkui/src/components/Search/Search.e2e-playground.tsx +++ b/packages/vkui/src/components/Search/Search.e2e-playground.tsx @@ -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'; @@ -30,6 +31,10 @@ export const SearchPlayground = (props: ComponentPlaygroundProps) => { { noPadding: [true], }, + { + onFindButtonClick: [noop], + value: ['value'], + }, ]} > {(props: SearchProps) => } diff --git a/packages/vkui/src/components/Search/Search.module.css b/packages/vkui/src/components/Search/Search.module.css index 60cbb56144..9e79e66d5e 100644 --- a/packages/vkui/src/components/Search/Search.module.css +++ b/packages/vkui/src/components/Search/Search.module.css @@ -35,6 +35,7 @@ * Костыль для PanelHeader. Необходимо для растягивания на всю ширину. */ width: 10000px; + overflow: hidden; } .Search__field:hover { @@ -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; @@ -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; @@ -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; @@ -172,6 +190,12 @@ overflow: hidden; } +.Search__findButton { + min-width: initial; + max-width: initial; + width: initial; +} + .Group--mode-plain .Search { padding-top: 4px; } diff --git a/packages/vkui/src/components/Search/Search.test.tsx b/packages/vkui/src/components/Search/Search.test.tsx index defbb2aaaa..7c54d4dbf1 100644 --- a/packages/vkui/src/components/Search/Search.test.tsx +++ b/packages/vkui/src/components/Search/Search.test.tsx @@ -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); @@ -89,4 +91,11 @@ describe('Search', () => { userEvent.click(screen.getByTestId('icon')); expect(cb).toHaveBeenCalled(); }); + + it('calls onFindButtonClick', () => { + const cb = jest.fn(); + render(); + userEvent.click(getFindButton()); + expect(cb).toHaveBeenCalled(); + }); }); diff --git a/packages/vkui/src/components/Search/Search.tsx b/packages/vkui/src/components/Search/Search.tsx index 8611365a27..489a4e6264 100644 --- a/packages/vkui/src/components/Search/Search.tsx +++ b/packages/vkui/src/components/Search/Search.tsx @@ -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'; @@ -26,7 +28,7 @@ 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; @@ -34,12 +36,21 @@ export interface SearchProps * Удаляет отступы у компонента */ noPadding?: boolean; + /** + * Текст для кнопки Найти + */ + findButtonText?: string; + /** + * Коллбэк для кнопки Найти + */ + onFindButtonClick?: React.MouseEventHandler; } /** * @see https://vkcom.github.io/VKUI/#/Search */ export const Search = ({ + id: idProp, before = , className, defaultValue = '', @@ -56,6 +67,8 @@ export const Search = ({ clearAriaLabel = 'Очистить', noPadding, getRootRef, + findButtonText = 'Найти', + onFindButtonClick, ...inputProps }: SearchProps) => { const inputRef = useExternRef(getRef); @@ -64,6 +77,8 @@ export const Search = ({ setTrue: setFocusedTrue, setFalse: setFocusedFalse, } = useBooleanState(false); + const generatedId = useId(); + const inputId = idProp ? idProp : `search-${generatedId}`; const [value, onChange] = useEnsuredControl({ defaultValue, @@ -71,6 +86,7 @@ export const Search = ({ value: valueProp, }); const { sizeY = 'none' } = useAdaptivity(); + const { sizeY: adaptiveSizeY } = useAdaptivityConditionalRender(); const platform = usePlatform(); const onFocus = (e: React.FocusEvent) => { @@ -130,7 +146,10 @@ export const Search = ({ style={style} >
-