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)}