diff --git a/components/calendar/__tests__/calendar-app.test.tsx b/components/calendar/__tests__/calendar-app.test.tsx index 323b411a3..24ce2f577 100644 --- a/components/calendar/__tests__/calendar-app.test.tsx +++ b/components/calendar/__tests__/calendar-app.test.tsx @@ -352,6 +352,18 @@ describe('CalendarApp event search (CAL-16)', () => { expect(await screen.findByText('search.no_results', {}, { timeout: 3000 })).toBeInTheDocument(); }); + it('offers Clear search when nothing matches, back to the calendar with the cursor in the box', async () => { + renderApp(); + const box = screen.getByRole('searchbox', { name: 'search.title' }); + fireEvent.change(box, { target: { value: 'zzz' } }); + await screen.findByText('search.no_results', {}, { timeout: 3000 }); + const results = screen.getByTestId('calendar-search-results'); + fireEvent.click(within(results).getByRole('button', { name: 'search.clear' })); + expect(box).toHaveValue(''); + expect(document.activeElement).toBe(box); + await waitFor(() => expect(screen.queryByTestId('calendar-search-results')).toBeNull()); + }); + it('is in the command palette', () => { render(<>); const items = screen.getByTestId('palette').textContent ?? ''; diff --git a/components/calendar/calendar-app.tsx b/components/calendar/calendar-app.tsx index 7eca930b6..b9d63985b 100644 --- a/components/calendar/calendar-app.tsx +++ b/components/calendar/calendar-app.tsx @@ -1795,6 +1795,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = currentUserEmails={currentUserEmails} onSelectEvent={handleSelectEvent} onContextMenuEvent={handleContextMenuEvent} + onClearSearch={() => { setSearchQuery(""); searchInputRef.current?.focus(); }} /> ); } diff --git a/components/calendar/calendar-search.tsx b/components/calendar/calendar-search.tsx index 283cb8551..a46b9d5e6 100644 --- a/components/calendar/calendar-search.tsx +++ b/components/calendar/calendar-search.tsx @@ -6,6 +6,7 @@ import { format } from "date-fns"; import { Search, X, MapPin, Repeat } from "@/components/icons"; import { Input } from "@/components/ui/input"; import { IconButton } from "@/components/ui/icon-button"; +import { Button } from "@/components/ui/button"; import { EmptyState } from "@/components/ui/empty-state"; import { Spinner } from "@/components/ui/spinner"; import { cn } from "@/lib/utils"; @@ -88,11 +89,13 @@ interface CalendarSearchResultsProps { currentUserEmails?: string[]; onSelectEvent: (event: CalendarEvent, anchorRect: DOMRect) => void; onContextMenuEvent?: (e: React.MouseEvent, event: CalendarEvent) => void; + /** Empties the search box, back to the calendar (R9). */ + onClearSearch?: () => void; } /** The events a calendar search found, upcoming first, then past ones. */ export function CalendarSearchResults({ - query, results, calendars, timeFormat = "24h", currentUserEmails, onSelectEvent, onContextMenuEvent, + query, results, calendars, timeFormat = "24h", currentUserEmails, onSelectEvent, onContextMenuEvent, onClearSearch, }: CalendarSearchResultsProps) { const t = useTranslations("calendar"); const intlFormatter = useDisplayDateFormatter(); @@ -199,6 +202,11 @@ export function CalendarSearchResults({ icon={Search} title={t("search.no_results", { query: query.trim() })} description={t("search.no_results_hint")} + actions={onClearSearch && ( + + )} /> )} {section("calendar-search-upcoming", t("search.upcoming"), upcoming)}