Skip to content

Commit

Permalink
Feature Request: Search box above section list octokatherine#277 (oct…
Browse files Browse the repository at this point in the history
…okatherine#278)

* Feature Request: Search box above section list octokatherine#277

Add new SectionFilter component to filter slugs based on section names
Update SectionsColumn to display filtered sections
Add units tests for SectionFilter component

* Update filteredSlugs state on add action

* Trim section query string before filtering with it

* Move sections filter above custom section

* Improve search filter UX

Remove search input after selecting a section
Move search filter logic to SectionsColumn component
Update SectionFilter unit test

* Remove unused filterSections function

* Fix resetSearchFilter function call
  • Loading branch information
ksaswin authored Jul 23, 2023
1 parent 450a6b6 commit ff1e825
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 13 deletions.
14 changes: 14 additions & 0 deletions components/SectionFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const SectionFilter = ({ searchFilter, setSearchFilter }) => {
return (
<input
type="text"
placeholder="Search for a section"
className="mb-3 space-y-3 w-full py-2 pl-3 pr-6 bg-white dark:bg-gray-200 rounded-md shadow cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-400"
data-testid="slugs-filter"
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
/>
)
}

export default SectionFilter
70 changes: 57 additions & 13 deletions components/SectionsColumn.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Image from 'next/image'
import useLocalStorage from '../hooks/useLocalStorage'
import { SortableItem } from './SortableItem'
import CustomSection from './CustomSection'
import SectionFilter from './SectionFilter'

const kebabCaseToTitleCase = (str) => {
return str
Expand Down Expand Up @@ -50,6 +51,8 @@ export const SectionsColumn = ({
const [addAction, setAddAction] = useState(false)
const [currentSlugList, setCurrentSlugList] = useState([])
const [slugsFromPreviousSession, setSlugsFromPreviousSession] = useState([])
const [searchFilter, setSearchFilter] = useState('')
const [filteredSlugs, setFilteredSlugs] = useState([])
const { saveBackup, deleteBackup } = useLocalStorage()

useEffect(() => {
Expand All @@ -76,13 +79,19 @@ export const SectionsColumn = ({
}
}, [])

const updateSlugsOnAdd = (previousState, section) => {
return previousState.filter((slug) => slug !== section)
}

const onAddSection = (e, section) => {
localStorage.setItem('current-focused-slug', section)
setpageRefreshed(false)
setAddAction(true)
setSectionSlugs((prev) => prev.filter((s) => s !== section))
setSectionSlugs((prev) => updateSlugsOnAdd(prev, section))
setFilteredSlugs((prev) => updateSlugsOnAdd(prev, section))
setSelectedSectionSlugs((prev) => [...prev, section])
setFocusedSectionSlug(localStorage.getItem('current-focused-slug'))
resetSearchFilter()
}

useEffect(() => {
Expand Down Expand Up @@ -163,6 +172,27 @@ export const SectionsColumn = ({

let alphabetizedSectionSlugs = sectionSlugs.sort()

const getAutoCompleteResults = (searchQuery) => {
const suggestedSlugs = sectionSlugs.filter((slug) => {
return getTemplate(slug).name.toLowerCase().includes(searchQuery.toLowerCase())
})

return suggestedSlugs.length ? suggestedSlugs : [undefined]
}

const resetSearchFilter = () => setSearchFilter('')

useEffect(() => {
if (!searchFilter) {
setFilteredSlugs([])
return
}

const suggestedSlugs = getAutoCompleteResults(searchFilter.trim())

setFilteredSlugs(suggestedSlugs)
}, [searchFilter])

return (
<div className="sections w-80">
<h3 className="px-1 text-sm font-medium border-b-2 border-transparent text-emerald-500 whitespace-nowrap focus:outline-none">
Expand Down Expand Up @@ -228,6 +258,7 @@ export const SectionsColumn = ({
{t('section-column-click-add')}
</h4>
)}
<SectionFilter searchFilter={searchFilter} setSearchFilter={setSearchFilter} />
<CustomSection
setSelectedSectionSlugs={setSelectedSectionSlugs}
setFocusedSectionSlug={setFocusedSectionSlug}
Expand All @@ -240,24 +271,37 @@ export const SectionsColumn = ({
(pageRefreshed && slugsFromPreviousSession.indexOf('title-and-description') == -1
? sectionSlugs.push('title-and-description')
: ' ',
(alphabetizedSectionSlugs = sectionSlugs.sort()),
(alphabetizedSectionSlugs = !filteredSlugs.length
? sectionSlugs.sort()
: filteredSlugs.sort()),
pageRefreshed || addAction
? (alphabetizedSectionSlugs = [...new Set(alphabetizedSectionSlugs)])
: ' ',
alphabetizedSectionSlugs.map((s) => {
const template = getTemplate(s)
if (template) {
if (s === undefined) {
return (
<li key={s}>
<button
className="flex items-center block w-full h-full py-2 pl-3 pr-6 bg-white dark:bg-gray-200 rounded-md shadow cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-400"
type="button"
onClick={(e) => onAddSection(e, s)}
>
<span>{template.name}</span>
</button>
</li>
<h4
className="mb-3 text-xs leading-6 text-gray-900 dark:text-gray-300"
key="unavailable-section"
>
The section you're looking for is unavailable
</h4>
)
} else {
const template = getTemplate(s)
if (template) {
return (
<li key={s}>
<button
className="flex items-center block w-full h-full py-2 pl-3 pr-6 bg-white dark:bg-gray-200 rounded-md shadow cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-400"
type="button"
onClick={(e) => onAddSection(e, s)}
>
<span>{template.name}</span>
</button>
</li>
)
}
}
}))
}
Expand Down
36 changes: 36 additions & 0 deletions components/__tests__/SectionFilter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

import SectionFilter from '../SectionFilter'

jest.mock('next-i18next', () => ({
useTranslation: () => ({ t: jest.fn() }),
}))

describe('<SectionFilter />', () => {
const props = {
searchFilter: '',
setSearchFilter: jest.fn(),
}

it('should render', () => {
const { container } = render(<SectionFilter {...props} />)
expect(container).toBeInTheDocument()
})

it('should call the callBack function with updated filter query', () => {
render(<SectionFilter {...props} />)

const input = screen.getByTestId('slugs-filter')
expect(input).toBeInTheDocument()
expect(input).toHaveAttribute('type', 'text')
expect(input).toHaveAttribute('placeholder', 'Search for a section')

userEvent.type(input, 'app')

expect(props.setSearchFilter).toHaveBeenCalledTimes(3)
expect(props.setSearchFilter).toHaveBeenNthCalledWith(1, 'a')
expect(props.setSearchFilter).toHaveBeenNthCalledWith(2, 'p')
expect(props.setSearchFilter).toHaveBeenNthCalledWith(3, 'p')
})
})

0 comments on commit ff1e825

Please sign in to comment.