Skip to content

Commit

Permalink
fix: highlight first category item after the search filter is cleared (
Browse files Browse the repository at this point in the history
  • Loading branch information
d-rita authored Feb 25, 2025
1 parent 44b6701 commit c7866d6
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ describe('Command Palette - List View - Browse Apps View', () => {
const clearButton = getAllByRole('button')[1]
await user.click(clearButton)

// first item - back Action highlighted
expect(listItems[0]).toHaveClass('highlighted')
// first app item - highlighted
expect(backActionListItem).not.toHaveClass('highlighted')
expect(listItems[1]).toHaveClass('highlighted')
expect(listItems[1].querySelector('span')).toHaveTextContent(
'Test App 1'
)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,105 @@ describe('Command Palette - List View - Search Results', () => {
expect(listItems[0]).toHaveTextContent('Logout')
expect(listItems[0]).toHaveClass('highlighted')
})

it('handles multiple search results in the HOME View', async () => {
const user = userEvent.setup()
const {
getByPlaceholderText,
queryAllByTestId,
container,
getByTestId,
queryByTestId,
} = render(
<CommandPalette apps={testApps} shortcuts={[]} commands={[]} />
)
// open modal
fireEvent.keyDown(container, { key: 'k', metaKey: true })

// Search field
const searchField = await getByPlaceholderText(
'Search apps, shortcuts, commands'
)
expect(searchField).toHaveValue('')

const searchTerm = 'app'

await user.type(searchField, searchTerm)
expect(queryByTestId('headerbar-top-apps-list')).not.toBeInTheDocument()

const resultsListItems = queryAllByTestId('headerbar-list-item')
expect(resultsListItems.length).toBe(9)

const firstResult = resultsListItems[0]
const fifthResult = resultsListItems[4]

expect(firstResult).toHaveTextContent('Test App 1')
expect(firstResult).toHaveClass('highlighted')

// scroll down to fifth result
await user.keyboard('{ArrowDown}'.repeat(4))

expect(fifthResult).toHaveTextContent('Test App 5')
expect(fifthResult).toHaveClass('highlighted')

// clear search field
await user.keyboard('{Backspace}'.repeat(searchTerm.length))

expect(searchField).toHaveValue('')

const appsGrid = getByTestId('headerbar-top-apps-list')
const firstGridApp = appsGrid.querySelectorAll('a')[0]

expect(firstGridApp).toHaveClass('highlighted')
expect(firstGridApp.querySelector('span')).toHaveTextContent(
'Test App 1'
)
})

it('highlights first action in "no grid home view" when search is cleared', async () => {
const user = userEvent.setup()
const {
getByPlaceholderText,
queryAllByTestId,
container,
queryByTestId,
} = render(
<CommandPalette
apps={[]}
shortcuts={testShortcuts}
commands={testCommands}
/>
)
// open modal
fireEvent.keyDown(container, { key: 'k', metaKey: true })

const appsGrid = queryByTestId('headerbar-top-apps-list')
const browseAppsAction = queryByTestId('headerbar-browse-apps')
const browseCommandsAction = queryByTestId('headerbar-browse-commands')

// since there are no apps
expect(appsGrid).not.toBeInTheDocument()
// since apps < MIN_APPS_NUM(8)
expect(browseAppsAction).not.toBeInTheDocument()
// since commands > 1
expect(browseCommandsAction).toBeInTheDocument()
expect(browseCommandsAction).toHaveClass('highlighted')

// Search field
const searchField = await getByPlaceholderText(
'Search apps, shortcuts, commands'
)
const searchTerm = 'test'
await user.type(searchField, searchTerm)

const resultsListItems = queryAllByTestId('headerbar-list-item')
expect(resultsListItems.length).toBe(2)
expect(resultsListItems[0]).toHaveClass('highlighted')

// clear search field
await user.keyboard('{Backspace}'.repeat(searchTerm.length))

expect(searchField).toHaveValue('')
expect(browseCommandsAction).toHaveClass('highlighted')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,24 @@ export const CommandPaletteContextProvider = ({ children }) => {
const [activeSection, setActiveSection] = useState(null)
const [showGrid, setShowGrid] = useState(null)

const goToDefaultView = useCallback(() => {
const goToDefaultSection = useCallback(() => {
const defaultSection = showGrid ? GRID_SECTION : ACTIONS_SECTION

setFilter('')
setCurrentView(HOME_VIEW)
setActiveSection(defaultSection)
setHighlightedIndex(0)
}, [
showGrid,
setCurrentView,
setFilter,
setActiveSection,
setHighlightedIndex,
])
}, [showGrid, setActiveSection, setHighlightedIndex])

const goToDefaultView = useCallback(() => {
setFilter('')
setCurrentView(HOME_VIEW)
goToDefaultSection()
}, [setCurrentView, setFilter, goToDefaultSection])

const contextValue = useMemo(
() => ({
filter,
setFilter,
goToDefaultSection,
goToDefaultView,
highlightedIndex,
setHighlightedIndex,
Expand All @@ -49,6 +48,7 @@ export const CommandPaletteContextProvider = ({ children }) => {
}),
[
filter,
goToDefaultSection,
goToDefaultView,
highlightedIndex,
currentView,
Expand Down
21 changes: 12 additions & 9 deletions components/header-bar/src/command-palette/hooks/use-navigation.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useCallback, useMemo, useRef } from 'react'
import { useCommandPaletteContext } from '../context/command-palette-context.js'
import {
ACTIONS_SECTION,
GRID_COLUMNS,
GRID_ROWS,
GRID_SECTION,
HOME_VIEW,
} from '../utils/constants.js'
import { handleHomeNavigation } from '../utils/home-navigation.js'
Expand All @@ -25,20 +25,23 @@ export const useNavigation = ({ itemsArray, actionsArray }) => {
} = useCommandPaletteContext()

const activeItems = useMemo(() => {
if (filter) {
return itemsArray
}

if (currentView === HOME_VIEW) {
return activeSection === ACTIONS_SECTION ? actionsArray : itemsArray
if (activeSection === GRID_SECTION) {
return itemsArray
} else {
return actionsArray
}
} else {
return filter ? itemsArray : actionsArray.concat(itemsArray)
return actionsArray.concat(itemsArray)
}
}, [filter, itemsArray, actionsArray, currentView, activeSection])

const { modalOpen, setModalOpen } = useModal(modalRef)

// highlight first item in filtered results
useEffect(() => {
setHighlightedIndex(0)
}, [filter, setHighlightedIndex])

const handleListViewNavigation = useCallback(
({ event, listLength }) => {
const index = handleListNavigation({
Expand Down
11 changes: 9 additions & 2 deletions components/header-bar/src/command-palette/views/home-view.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { spacers } from '@dhis2/ui-constants'
import PropTypes from 'prop-types'
import React from 'react'
import React, { useEffect } from 'react'
import i18n from '../../locales/index.js'
import { useCommandPaletteContext } from '../context/command-palette-context.js'
import AppItem from '../sections/app-item.js'
Expand All @@ -14,9 +14,16 @@ import {
import ListView from './list-view.js'

function HomeView({ apps, filteredItems, actions }) {
const { filter, highlightedIndex, activeSection } =
const { filter, highlightedIndex, activeSection, goToDefaultSection } =
useCommandPaletteContext()
const topApps = apps?.slice(0, MIN_APPS_NUM)

useEffect(() => {
if (!filter) {
goToDefaultSection()
}
}, [filter, goToDefaultSection])

return (
<>
{filter.length > 0 ? (
Expand Down
13 changes: 11 additions & 2 deletions components/header-bar/src/command-palette/views/list-view.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import PropTypes from 'prop-types'
import React from 'react'
import React, { useEffect } from 'react'
import { useCommandPaletteContext } from '../context/command-palette-context.js'
import List from '../sections/list.js'
import EmptySearchResults from '../sections/search-results.js'

function ListView({ filteredItems, actions }) {
const { filter } = useCommandPaletteContext()
const { filter, setHighlightedIndex } = useCommandPaletteContext()
const backAction = actions?.[0]

useEffect(() => {
const firstItemIndexInListView = 1
if (filter) {
setHighlightedIndex(0)
} else {
setHighlightedIndex(firstItemIndexInListView)
}
}, [filter, setHighlightedIndex])

return filteredItems.length > 0 ? (
<List filteredItems={filteredItems} backAction={backAction} />
) : (
Expand Down

0 comments on commit c7866d6

Please sign in to comment.