diff --git a/components/calendar/__tests__/calendar-app.test.tsx b/components/calendar/__tests__/calendar-app.test.tsx index c19cf0ba6..57035ffd1 100644 --- a/components/calendar/__tests__/calendar-app.test.tsx +++ b/components/calendar/__tests__/calendar-app.test.tsx @@ -148,3 +148,31 @@ describe('CalendarApp in the command palette', () => { expect(useCalendarStore.getState().viewMode).toBe('day'); }); }); + +describe('CalendarApp in the Tasks view', () => { + beforeEach(() => setUp([makeEvent()], 'tasks')); + + it('has no date navigation', () => { + renderApp(); + // The one left is the mini calendar's month arrow. + expect(screen.queryAllByRole('button', { name: 'nav_prev' })).toHaveLength(1); + expect(screen.queryAllByRole('button', { name: 'nav_next' })).toHaveLength(1); + expect(screen.queryByRole('button', { name: 'views.today' })).toBeNull(); + }); + + it('creates a task with n, not an event', () => { + renderApp(); + fireEvent.keyDown(document.body, { key: 'n', code: 'KeyN' }); + expect(screen.getByRole('heading', { name: 'tasks.create' })).toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: 'events.create' })).toBeNull(); + }); + + it('creates a task with the phone create button', () => { + media.mobile = true; + media.desktop = false; + renderApp(); + // The floating button, after the drawer's. + fireEvent.click(screen.getAllByRole('button', { name: 'events.new_task' }).at(-1)!); + expect(screen.getByRole('dialog', { name: 'tasks.create' })).toBeInTheDocument(); + }); +}); diff --git a/components/calendar/calendar-app.tsx b/components/calendar/calendar-app.tsx index 98a824617..c66d92534 100644 --- a/components/calendar/calendar-app.tsx +++ b/components/calendar/calendar-app.tsx @@ -1071,6 +1071,14 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = const closeQuickCreate = useCallback(() => setQuickDraft(null), []); + // The create button and `n` make what the view shows: a task in Tasks. + const inTasksView = normalizedViewMode === "tasks"; + const openCreateForView = useCallback(() => { + if (inTasksView) openCreateTaskModal(); + else openCreateModal(); + }, [inTasksView, openCreateTaskModal, openCreateModal]); + const createLabel = inTasksView ? t("events.new_task") : t("events.create"); + const handleQuickCreateMoreOptions = useCallback(() => { if (!quickDraft) return; const draft = quickDraft; @@ -1351,12 +1359,12 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = case "day": setViewMode("day"); break; case "agenda": setViewMode("agenda"); break; case "tasks": if (enableCalendarTasks) setViewMode("tasks"); break; - case "new": openCreateModal(); break; + case "new": openCreateForView(); break; } }; window.addEventListener("keydown", handleKey); return () => window.removeEventListener("keydown", handleKey); - }, [navigatePrev, navigateNext, goToToday, setViewMode, openCreateModal, showEventModal, showTaskModal, detailPinned, enableCalendarTasks]); + }, [navigatePrev, navigateNext, goToToday, setViewMode, openCreateForView, showEventModal, showTaskModal, detailPinned, enableCalendarTasks]); // The command palette's Calendar group, while this calendar is the one in // front (in the Pro shell: its focused tab). @@ -1695,7 +1703,8 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = onNext={navigateNext} onToday={goToToday} onViewModeChange={(mode) => { setMobileReturnToMonth(false); setViewMode(mode); }} - onCreateEvent={() => openCreateModal()} + onCreateEvent={openCreateForView} + createLabel={createLabel} onImport={() => setShowImportModal(true)} onSubscribe={() => setShowSubscriptionModal(true)} onRefresh={() => { void refreshCalendar(); }} @@ -1775,7 +1784,8 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = style={isNarrow ? undefined : { width: `${calSidebarWidth}px` }} > { setNarrowSidebarOpen(false); openCreateModal(); }} + onCreateEvent={() => { setNarrowSidebarOpen(false); openCreateForView(); }} + createLabel={createLabel} tourTarget={!isNarrow} />
@@ -1939,9 +1949,9 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = {/* Floating Create Event Button (mobile) */} {isMobile && ( diff --git a/components/calendar/calendar-sidebar-header.tsx b/components/calendar/calendar-sidebar-header.tsx index e9120d91a..629eaf7ed 100644 --- a/components/calendar/calendar-sidebar-header.tsx +++ b/components/calendar/calendar-sidebar-header.tsx @@ -6,6 +6,8 @@ import { Plus } from "@/components/icons"; interface CalendarSidebarHeaderProps { onCreateEvent: () => void; + /** The create button's name: what it makes in the current view. */ + createLabel?: string; /** Mark the button for the guided tour; off while the toolbar's own "+" is the visible one. */ tourTarget?: boolean; } @@ -14,8 +16,9 @@ interface CalendarSidebarHeaderProps { * Title row at the top of the calendar sidebar, the same shape as the * contacts sidebar header: the app name on the left and "+" on the right. */ -export function CalendarSidebarHeader({ onCreateEvent, tourTarget = true }: CalendarSidebarHeaderProps) { +export function CalendarSidebarHeader({ onCreateEvent, createLabel, tourTarget = true }: CalendarSidebarHeaderProps) { const t = useTranslations("calendar"); + const label = createLabel ?? t("events.create"); return ( // Same h-14 box as the mail sidebar's account switcher, so the apps line up; @@ -27,8 +30,8 @@ export function CalendarSidebarHeader({ onCreateEvent, tourTarget = true }: Cale variant="ghost" onClick={onCreateEvent} className="h-8 w-8 flex-shrink-0" - aria-label={t("events.create")} - title={t("events.create")} + aria-label={label} + title={label} data-tour={tourTarget ? "create-event-button" : undefined} > diff --git a/components/calendar/calendar-toolbar.tsx b/components/calendar/calendar-toolbar.tsx index d8251dadf..27a8526a4 100644 --- a/components/calendar/calendar-toolbar.tsx +++ b/components/calendar/calendar-toolbar.tsx @@ -23,6 +23,8 @@ interface CalendarToolbarProps { onToday: () => void; onViewModeChange: (mode: CalendarViewMode) => void; onCreateEvent: () => void; + /** The create button's name: what it makes in the current view. */ + createLabel?: string; onImport?: () => void; onSubscribe?: () => void; /** Reload calendars, events and subscriptions. */ @@ -63,6 +65,7 @@ export function CalendarToolbar({ onToday, onViewModeChange, onCreateEvent, + createLabel, onImport, onSubscribe, onRefresh, @@ -140,6 +143,9 @@ export function CalendarToolbar({ } }; + // Tasks have no dates to step through. + const hasDateNav = viewMode !== "tasks"; + // The ⋮ menu: what the calendar as a whole offers. const { contextMenu: moreMenu, @@ -220,18 +226,24 @@ export function CalendarToolbar({ )} - + {hasDateNav && ( + + )} {getDateLabel()} - - + {hasDateNav && ( + <> + + + + )}
{/* Row 2: View switcher pills + calendar toggle */} @@ -373,40 +385,44 @@ export function CalendarToolbar({ size="icon" onClick={onCreateEvent} className="h-8 w-8" - aria-label={t("events.create")} - title={t("events.create")} + aria-label={createLabel ?? t("events.create")} + title={createLabel ?? t("events.create")} data-tour="create-event-button" > )} - - - + {hasDateNav && ( + <> + + + + + )}

{getDateLabel()}