fix: hide the date arrows in Tasks and let n and the create button make a task there

This commit is contained in:
Linus Rath
2026-10-28 03:01:55 +01:00
parent 987c730fd2
commit 0cc052f48d
4 changed files with 104 additions and 47 deletions
@@ -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();
});
});
+16 -6
View File
@@ -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` }}
>
<CalendarSidebarHeader
onCreateEvent={() => { setNarrowSidebarOpen(false); openCreateModal(); }}
onCreateEvent={() => { setNarrowSidebarOpen(false); openCreateForView(); }}
createLabel={createLabel}
tourTarget={!isNarrow}
/>
<div className="flex-1 overflow-y-auto overflow-x-hidden py-3">
@@ -1939,9 +1949,9 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps =
{/* Floating Create Event Button (mobile) */}
{isMobile && (
<Button
onClick={() => openCreateModal()}
onClick={openCreateForView}
className="absolute bottom-4 right-4 z-40 h-14 w-14 rounded-full shadow-lg"
aria-label={t("events.create")}
aria-label={createLabel}
>
<Plus className="h-6 w-6" />
</Button>
@@ -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}
>
<Plus className="w-4 h-4" />
+54 -38
View File
@@ -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({
<ArrowLeft className="w-4 h-4" />
</button>
)}
<button onClick={onPrev} className="p-1.5 rounded-md hover:bg-muted transition-colors touch-manipulation" aria-label={t("nav_prev")}>
<ChevronLeft className="w-4 h-4" />
</button>
{hasDateNav && (
<button onClick={onPrev} className="p-1.5 rounded-md hover:bg-muted transition-colors touch-manipulation" aria-label={t("nav_prev")}>
<ChevronLeft className="w-4 h-4" />
</button>
)}
<span className="text-sm font-semibold text-center flex-1 select-none truncate">
{getDateLabel()}
</span>
<button onClick={onNext} className="p-1.5 rounded-md hover:bg-muted transition-colors touch-manipulation" aria-label={t("nav_next")}>
<ChevronRight className="w-4 h-4" />
</button>
<Button variant="ghost" size="sm" onClick={onToday} className="touch-manipulation text-xs h-7 px-2 ms-0.5">
{t("views.today")}
</Button>
{hasDateNav && (
<>
<button onClick={onNext} className="p-1.5 rounded-md hover:bg-muted transition-colors touch-manipulation" aria-label={t("nav_next")}>
<ChevronRight className="w-4 h-4" />
</button>
<Button variant="ghost" size="sm" onClick={onToday} className="touch-manipulation text-xs h-7 px-2 ms-0.5">
{t("views.today")}
</Button>
</>
)}
</div>
{/* 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"
>
<Plus className="w-4 h-4" />
</Button>
)}
<Button
variant="outline"
size="sm"
onClick={onToday}
className="h-8 me-1"
title={formatFullDate(displayNow())}
>
{t("views.today")}
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={onPrev}
aria-label={t("nav_prev")}
>
<ChevronLeft className="w-4 h-4 rtl:rotate-180" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={onNext}
aria-label={t("nav_next")}
>
<ChevronRight className="w-4 h-4 rtl:rotate-180" />
</Button>
{hasDateNav && (
<>
<Button
variant="outline"
size="sm"
onClick={onToday}
className="h-8 me-1"
title={formatFullDate(displayNow())}
>
{t("views.today")}
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={onPrev}
aria-label={t("nav_prev")}
>
<ChevronLeft className="w-4 h-4 rtl:rotate-180" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={onNext}
aria-label={t("nav_next")}
>
<ChevronRight className="w-4 h-4 rtl:rotate-180" />
</Button>
</>
)}
<h1 className="ms-2 text-base font-semibold truncate select-none">
{getDateLabel()}
</h1>