Skip to content

Commit

Permalink
Merge pull request #1561 from aliraza556/generic-Search-Bar-in-source…
Browse files Browse the repository at this point in the history
…-table

Fixed(Source-Table): Implement Universal Search Bar Without Empty Spaces
  • Loading branch information
Rassl authored May 29, 2024
2 parents 3c00e62 + 5e9d144 commit 5fe9a1b
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 207 deletions.
177 changes: 0 additions & 177 deletions src/components/SourcesTableModal/SourcesView/Sources/Search/index.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { FetchRadarResponse, Sources as TSources } from '~/types'
import { colors } from '~/utils/colors'
import { Heading, StyledPill } from '../common'
import { sourcesMapper } from '../constants'
import Search from './Search'
import Search from '~/components/SourcesTableModal/SourcesView/common/search'
import Table from './Table'

export const Sources = () => {
Expand Down Expand Up @@ -71,6 +71,7 @@ export const Sources = () => {
<Search
activeIcon={<ClearIcon />}
defaultIcon={<SearchIcon />}
loading={loading}
loadingIcon={<ClipLoader color={colors.PRIMARY_BLUE} size={24} />}
onSearch={setSearch}
placeholder="Find Source"
Expand Down
11 changes: 8 additions & 3 deletions src/components/SourcesTableModal/SourcesView/Topics/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Button } from '@mui/material'
import { useEffect, useRef, useState } from 'react'
import { ClipLoader } from 'react-spinners'
import styled from 'styled-components'
import { Flex } from '~/components/common/Flex'
import { Text } from '~/components/common/Text'
import ClearIcon from '~/components/Icons/ClearIcon'
import SearchIcon from '~/components/Icons/SearchIcon'
import Search from '~/components/SourcesTableModal/SourcesView/Topics/Search'
import Search from '~/components/SourcesTableModal/SourcesView/common/search'
import { Flex } from '~/components/common/Flex'
import { Text } from '~/components/common/Text'
import { putNodeData } from '~/network/fetchSourcesData'
import { useModal } from '~/stores/useModalStore'
import { useTopicsStore } from '~/stores/useTopicsStore'
Expand Down Expand Up @@ -123,6 +123,10 @@ export const TopicSources = () => {
}
}

const handleSearch = (query: string) => {
setFilters({ ...filters, search: query })
}

return (
<>
<Wrapper direction="column" justify="flex-end">
Expand All @@ -136,6 +140,7 @@ export const TopicSources = () => {
defaultIcon={<SearchIcon />}
loading={loading}
loadingIcon={<ClipLoader color={colors.PRIMARY_BLUE} size={24} />}
onSearch={handleSearch}
placeholder="Search ..."
/>
</InputWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable padding-line-between-statements */
import '@testing-library/jest-dom'
import { act, fireEvent, render, waitFor } from '@testing-library/react'
import React from 'react'
import Search from '..'
import Search from '../index'

describe('Search component', () => {
it('highlights input field on click', () => {
const { container } = render(<Search onSearch={() => jest.fn()} />)
const { container } = render(<Search loading onSearch={() => jest.fn()} />)

const inputElement = container.querySelector('input') as HTMLInputElement

Expand All @@ -17,7 +19,7 @@ describe('Search component', () => {
jest.useFakeTimers()

const onSearchMock = jest.fn()
const { container } = render(<Search onSearch={onSearchMock} />)
const { container } = render(<Search loading onSearch={onSearchMock} />)
const inputElement = container.querySelector('input') as HTMLInputElement

fireEvent.change(inputElement, { target: { value: 'test query' } })
Expand All @@ -31,7 +33,7 @@ describe('Search component', () => {
})

it('displays clear button after executing search', () => {
const { container, getByRole } = render(<Search onSearch={() => jest.fn()} />)
const { container, getByRole } = render(<Search loading onSearch={() => jest.fn()} />)

const inputElement = container.querySelector('input') as HTMLInputElement

Expand All @@ -43,13 +45,18 @@ describe('Search component', () => {
})

it('clears search input on clicking clear button', () => {
const { container } = render(<Search onSearch={() => jest.fn()} />)
const onSearchMock = jest.fn()
const { container } = render(<Search loading onSearch={onSearchMock} />)

const inputElement = container.querySelector('input') as HTMLInputElement
const clearButton = container.querySelector('button') as HTMLButtonElement

fireEvent.change(inputElement, { target: { value: 'test query' } })
fireEvent.click(container.querySelector('button') as HTMLInputElement)
waitFor(() => {
fireEvent.change(inputElement, { target: { value: 'test query' } })

fireEvent.click(clearButton)

expect(inputElement.value).toEqual('')
expect(inputElement.value).toEqual('')
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,51 @@ import { debounce } from 'lodash'
import React, { ChangeEvent, useCallback, useMemo, useState } from 'react'
import styled from 'styled-components'
import { Flex } from '~/components/common/Flex'
import { useTopicsStore } from '~/stores/useTopicsStore'
import { colors } from '~/utils/colors'

interface SearchProps extends Omit<InputBaseProps, 'onChange'> {
onSearch: (query: string) => void
placeholder?: string
activeIcon?: React.ReactNode
defaultIcon?: React.ReactNode
loadingIcon?: React.ReactNode
loading: boolean
}

const Search: React.FC<SearchProps> = ({ placeholder, activeIcon, loadingIcon, defaultIcon, loading, ...props }) => {
const [filters, setFilters] = useTopicsStore((s) => [s.filters, s.setFilters])
const [searchValue, setSearchValue] = useState('')
const Search: React.FC<SearchProps> = ({
onSearch,
placeholder,
activeIcon,
defaultIcon,
loadingIcon,
loading,
...props
}) => {
const [searchTerm, setSearchTerm] = useState('')

const resetSearch = () => {
setFilters({ search: '' })
setSearchTerm('')
onSearch('')
}

const handleSearch = useCallback(
(value: string) => {
setFilters({ search: value })
onSearch(value)
},
[setFilters],
[onSearch],
)

const debouncedSearch = useMemo(() => debounce(handleSearch, 300), [handleSearch])

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { value } = e.target
const trimmedValue = e.target.value.trim()

setSearchValue(value)
setSearchTerm(trimmedValue)

if (!e.target.value) {
setFilters({ search: '' })
}

if (e.target.value.length > 2) {
debouncedSearch(e.target.value)
if (!trimmedValue) {
resetSearch()
} else {
debouncedSearch(trimmedValue)
}
}

Expand All @@ -58,7 +64,7 @@ const Search: React.FC<SearchProps> = ({ placeholder, activeIcon, loadingIcon, d

return (
<IconWrapper>
{filters.search ? (
{searchTerm ? (
<StyledButton onClick={resetSearch} type="button">
{activeIcon}
</StyledButton>
Expand All @@ -74,10 +80,10 @@ const Search: React.FC<SearchProps> = ({ placeholder, activeIcon, loadingIcon, d
<StyledInput
autoComplete="off"
autoCorrect="off"
inputProps={{ 'aria-label': 'search sources' }}
inputProps={{ 'aria-label': 'search' }}
onChange={handleChange}
placeholder={placeholder}
value={searchValue}
value={searchTerm}
{...props}
/>
{getSearchIcon()}
Expand Down

0 comments on commit 5fe9a1b

Please sign in to comment.