Skip to content

Commit

Permalink
refactor: Выделить из Header отдельную feature - SearchProduct (#175)
Browse files Browse the repository at this point in the history
* refactor: Выделить из Header отдельную feature - SearchProduct #171

* hotfix: fix CamelCase issue with SCSS module files

* refactor: Заменил querySelector на useRef

* documentation: add stories for SearchProduct, SearchResult, SearchItem
  • Loading branch information
Segodnya authored Jan 11, 2024
1 parent 8c67446 commit 877b44b
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 161 deletions.
57 changes: 0 additions & 57 deletions src/components/SearchItem/search-item.module.scss

This file was deleted.

4 changes: 2 additions & 2 deletions src/components/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useMemo } from 'react'
import classNames from 'classnames'
import Logo from '../logo/Logo'
import Search from '../search/search'
import ArrowIcon from '@/assets/icons/arrow.svg'
import LightningIcon from '@/assets/images/header/lightning.svg'
import ContextMenuElement from '../ContextMenuElement/ContextMenuElement'
Expand All @@ -15,6 +14,7 @@ import { CatalogLinksId } from '@/shared/config/catalogLinks/catalogLinks'
import Link from '@/shared/ui/Link/Link'
import IconCategories from '@/assets/icons/IconCategories.svg'
import styles from './header.module.scss'
import SearchProduct from '@/features/SearchProduct'

function Header() {
const aboutUsNode = useMemo(
Expand Down Expand Up @@ -152,7 +152,7 @@ function Header() {

<div className={styles['header__row-two']}>
<Logo width="138px" height="46px" />
<Search />
<SearchProduct />
<HeaderAccount {...headerAccountData} />
</div>

Expand Down
55 changes: 0 additions & 55 deletions src/components/search/search.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use '../../shared/styles/utils/variables' as var;
@use '@/shared/styles/utils/variables' as var;

.link {
padding: 20px 30px;
Expand All @@ -17,23 +17,6 @@
flex: 0 0 auto;
}

.number {
margin: 0;
font-size: 14px;
line-height: 12px;
font-weight: 400;
color: var.$body-color-light-grey;
}

.price {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 400;
margin: 0;
color: var.$body-color;
}

.wrapper {
display: flex;
flex-direction: column;
Expand All @@ -48,12 +31,29 @@
margin: 0 0 5px;
}

.number {
margin: 0;
font-size: 14px;
line-height: 12px;
font-weight: 400;
color: var.$body-color-light-grey;
}

.price-wrapper {
display: flex;
flex: 0 0 auto;
column-gap: 20px;
}

.price {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 400;
margin: 0;
color: var.$body-color;
}

.image-link {
visibility: visible;
}
22 changes: 22 additions & 0 deletions src/entities/SearchItem/SearchItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Meta, Story } from '@storybook/react'
import { searchResponseData } from '@/mockData/searchData'
import SearchItem from './SearchItem'
import { TProduct } from '@/shared/model/types/common'
import { TLinkProps } from '@/shared/ui/Link/Link'

export default {
title: 'Entities/SearchItem',
component: SearchItem,
parameters: {
layout: 'centered'
}
} as Meta

type StoryProps = TProduct & TLinkProps

const Template: Story<StoryProps> = args => <SearchItem {...args} />

export const Default: Story<StoryProps> = Template.bind({})
Default.args = {
...(searchResponseData.data[2] as StoryProps)
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import { FC, useMemo, useState } from 'react'
import { type FC, useMemo, useState } from 'react'
import ArrowRightIcon from '@/assets/images/searchItem/arrow-right.svg'
import { TProduct } from '@/shared/model/types/common'
import Link, { TLinkProps } from '@/shared/ui/Link/Link'
import styles from './searchItem.module.scss'
import styles from './SearchItem.module.scss'

/**
* @param {string} image - фото товара
* @param {string} name - название товара
* @param {number} number - артикул товара
* @param {string} price - цена товара с валютой
* Компонент элемента из поисковой выдачи для тултипа-подсказки
*/
const SearchItem: FC<TProduct & TLinkProps> = props => {
const { image, name, number, price, ...otherProps } = props
const [isVisible, setVisability] = useState(false)
const [isVisible, setVisibility] = useState(false)

const handleMouseEnter = () => {
setVisability(true)
setVisibility(true)
}

const handleMouseLeave = () => {
setVisability(false)
setVisibility(false)
}

const arrowNode = useMemo(() => <ArrowRightIcon className={styles['image-link']} />, [])

return (
<Link className={styles.link} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} {...otherProps}>
<img src={image} alt="product" className={styles.image}></img>
<Link
className={styles.link}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
{...otherProps}>
<img src={image} alt="product" className={styles.image} />
<div className={styles.wrapper}>
<p className={styles.paragraph}>{name}</p>
<span className={styles.number}>{number}</span>
Expand Down
2 changes: 2 additions & 0 deletions src/features/SearchProduct/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import SearchProduct from './ui/SearchProduct'
export default SearchProduct
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use '../../shared/styles/utils/variables' as var;
@use '@/shared/styles/utils/variables' as var;

.form {
width: 100%;
Expand All @@ -14,14 +14,6 @@
.button {
width: 90px;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
background-color: var.$theme-primary-color;
border: none;
box-sizing: border-box;
color: var.$white;
border-radius: 5px;
margin-right: 5px;
}

Expand Down
12 changes: 12 additions & 0 deletions src/features/SearchProduct/ui/SearchProduct.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Meta, Story } from '@storybook/react'
import SearchProduct from './SearchProduct'

export default {
title: 'Features/SearchProduct',
component: SearchProduct
} as Meta

const Template: Story = () => <SearchProduct />

export const Default = Template.bind({})
Default.args = {}
79 changes: 79 additions & 0 deletions src/features/SearchProduct/ui/SearchProduct.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useRef, useEffect, useState } from 'react'
import { searchResponseData } from '@/mockData/searchData'
import { TResultData } from '@/shared/model/types/common'
import SearchResult from '@/widgets/SearchResult/SearchResult'
import { Input, InputSize, InputTheme } from '@/shared/ui/Input/Input'
import { Button, ButtonDesign, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button'
import styles from './SearchProduct.module.scss'

// @TODO: Перевести форму на Formik + Yup
// https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/92

/**
* Компонент формы для поиска катгорий/товаров по базе данных магазина
*/
const SearchProduct = () => {
const [visible, setVisibility] = useState(false)
const [resultData, setResultData] = useState<TResultData>({ data: [], success: false })
const [query, setQuery] = useState('')
const searchResultRef = useRef(null)

// @TODO: Добавить интеграцию с бэком - подсказки в поиске при вводе текста
// https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/172
const inputEventHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target
setResultData(searchResponseData)
setQuery(value)
}

const closeContextMenuHandler = (e: Event) => {
const searchResultNode = searchResultRef.current

if (searchResultNode) {
const withinBoundaries = e.composedPath().includes(searchResultNode)

if (!withinBoundaries && visible) {
setVisibility(false)
}
}
}

useEffect(() => {
if (resultData.success && query.length > 0) {
setVisibility(true)
} else {
setVisibility(false)
}
}, [resultData, query])

useEffect(() => {
document.addEventListener('click', closeContextMenuHandler)
return () => {
document.removeEventListener('click', closeContextMenuHandler)
}
}, [])

return (
<form className={styles.form}>
<Input
name="search"
placeholder="Поиск по товарам и категориям"
customSize={InputSize.M}
theme={InputTheme.ACCENT}
onChange={inputEventHandler}
/>
{/* @TODO: Добавить onClick-интеграцию с бэком для отправки поискового запроса
https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/173 */}
<Button
theme={ButtonTheme.PRIMARY}
design={ButtonDesign.SQUARE}
size={ButtonSize.XS}
className={styles.button}>
Найти
</Button>
{visible && <SearchResult results={resultData.data} ref={searchResultRef} />}
</form>
)
}

export default SearchProduct
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
@use '@/shared/styles/utils/variables' as var;

.result {
// @TODO: Определить ширину в соответствии с шириной формы поиска
// https://github.com/Studio-Yandex-Practicum/maxboom_frontend/issues/174
width: 800px;
background-color: var.$white;
position: absolute;
Expand Down
Loading

0 comments on commit 877b44b

Please sign in to comment.