Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions packages/web/src/common/utils/shortcut/data/shortcuts.data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ describe("shortcuts.data", () => {
label: "Focus on calendar",
});
expect(shortcuts.dayAgendaShortcuts[1]).toEqual({
k: "n",
label: "Create event",
});

expect(shortcuts.dayShortcuts).toHaveLength(3);
expect(shortcuts.dayShortcuts[2]).toEqual({
k: "t",
label: "Go to today",
});
Expand All @@ -38,7 +44,7 @@ describe("shortcuts.data", () => {
currentDate: dayjs(),
});

const tShortcut = shortcuts.dayAgendaShortcuts.find((s) => s.k === "t");
const tShortcut = shortcuts.dayShortcuts.find((s) => s.k === "t");
expect(tShortcut).toBeDefined();
expect(tShortcut?.label).toBe("Scroll to now");
});
Expand All @@ -51,7 +57,7 @@ describe("shortcuts.data", () => {
currentDate: yesterday,
});

const tShortcut = shortcuts.dayAgendaShortcuts.find((s) => s.k === "t");
const tShortcut = shortcuts.dayShortcuts.find((s) => s.k === "t");
expect(tShortcut).toBeDefined();
expect(tShortcut?.label).toBe("Go to today");
});
Expand All @@ -64,7 +70,7 @@ describe("shortcuts.data", () => {
currentDate: tomorrow,
});

const tShortcut = shortcuts.dayAgendaShortcuts.find((s) => s.k === "t");
const tShortcut = shortcuts.dayShortcuts.find((s) => s.k === "t");
expect(tShortcut).toBeDefined();
expect(tShortcut?.label).toBe("Go to today");
});
Expand All @@ -75,7 +81,7 @@ describe("shortcuts.data", () => {
currentDate: undefined,
});

const tShortcut = shortcuts.dayAgendaShortcuts.find((s) => s.k === "t");
const tShortcut = shortcuts.dayShortcuts.find((s) => s.k === "t");
expect(tShortcut).toBeDefined();
expect(tShortcut?.label).toBe("Go to today");
});
Expand Down
24 changes: 16 additions & 8 deletions packages/web/src/common/utils/shortcut/data/shortcuts.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const getShortcuts = (config: ShortcutsConfig = {}) => {
];

let homeShortcuts: Shortcut[] = [];
let dayShortcuts: Shortcut[] = [];
let dayTaskShortcuts: Shortcut[] = [];
let dayAgendaShortcuts: Shortcut[] = [];
let nowShortcuts: Shortcut[] = [];
Expand All @@ -35,14 +36,9 @@ export const getShortcuts = (config: ShortcutsConfig = {}) => {
}

if (isToday) {
dayTaskShortcuts = [
{ k: "u", label: "Focus on tasks" },
{ k: "c", label: "Create task" },
{ k: "e", label: "Edit task" },
{ k: "Delete", label: "Delete task" },
];
dayAgendaShortcuts = [
{ k: "i", label: "Focus on calendar" },
dayShortcuts = [
{ k: "j", label: "Previous day" },
{ k: "k", label: "Next day" },
{
k: "t",
label: (() => {
Expand All @@ -54,6 +50,17 @@ export const getShortcuts = (config: ShortcutsConfig = {}) => {
})(),
},
];

dayTaskShortcuts = [
{ k: "u", label: "Focus on tasks" },
{ k: "c", label: "Create task" },
{ k: "e", label: "Edit task" },
{ k: "Delete", label: "Delete task" },
];
dayAgendaShortcuts = [
{ k: "i", label: "Focus on calendar" },
{ k: "n", label: "Create event" },
];
}
if (isNow) {
nowShortcuts = [
Expand All @@ -69,6 +76,7 @@ export const getShortcuts = (config: ShortcutsConfig = {}) => {
return {
globalShortcuts,
homeShortcuts,
dayShortcuts,
dayTaskShortcuts,
dayAgendaShortcuts,
nowShortcuts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useSaveEventForm } from "@web/views/Forms/hooks/useSaveEventForm";
interface DraftProviderV2Props {
draft: Schema_Event | null;
setDraft: Dispatch<React.SetStateAction<Schema_Event | null>>;
openEventForm: () => void;
openEventForm: (create?: boolean) => void;
closeEventForm: () => void;
onDelete: () => void;
onSave: (draft: Schema_Event | null) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useContext } from "react";
import { render, screen } from "@testing-library/react";
import { useCloseEventForm } from "@web/views/Forms/hooks/useCloseEventForm";
import { useOpenEventForm } from "@web/views/Forms/hooks/useOpenEventForm";
import { useSaveEventForm } from "@web/views/Forms/hooks/useSaveEventForm";
import { DraftContextV2, DraftProviderV2 } from "../DraftProviderV2";

jest.mock("@web/views/Forms/hooks/useOpenEventForm");
jest.mock("@web/views/Forms/hooks/useCloseEventForm");
jest.mock("@web/views/Forms/hooks/useSaveEventForm");

const TestComponent = () => {
const context = useContext(DraftContextV2);
if (!context) return <div>No Context</div>;
return (
<div>
<div data-testid="draft">
{context.draft ? "Draft Exists" : "No Draft"}
</div>
<button onClick={() => context.openEventForm()}>Open</button>
<button onClick={context.closeEventForm}>Close</button>
<button onClick={() => context.onSave(null)}>Save</button>
</div>
);
};

describe("DraftProviderV2", () => {
const mockOpenEventForm = jest.fn();
const mockCloseEventForm = jest.fn();
const mockOnSave = jest.fn();

beforeEach(() => {
(useOpenEventForm as jest.Mock).mockReturnValue(mockOpenEventForm);
(useCloseEventForm as jest.Mock).mockReturnValue(mockCloseEventForm);
(useSaveEventForm as jest.Mock).mockReturnValue(mockOnSave);
});

it("should provide draft context values", () => {
render(
<DraftProviderV2>
<TestComponent />
</DraftProviderV2>,
);

expect(screen.getByTestId("draft")).toHaveTextContent("No Draft");

screen.getByText("Open").click();
expect(mockOpenEventForm).toHaveBeenCalled();

screen.getByText("Close").click();
expect(mockCloseEventForm).toHaveBeenCalled();

screen.getByText("Save").click();
expect(mockOnSave).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe.each([

const defaultConfig = {
onAddTask: jest.fn(),
onCreateEvent: jest.fn(),
onEditTask: jest.fn(),
onCompleteTask: jest.fn(),
onDeleteTask: jest.fn(),
Expand All @@ -67,13 +68,24 @@ describe.each([
expect(config.onFocusTasks).toHaveBeenCalled();
});

it("should call onCreateEvent when 'n' is pressed", async () => {
const config = { ...defaultConfig };
await act(() => renderHook(() => useDayViewShortcuts(config)));

pressKey("n");

expect(config.onCreateEvent).toHaveBeenCalled();
expect(config.onAddTask).not.toHaveBeenCalled();
});

it("should call onAddTask when 'c' is pressed", async () => {
const config = { ...defaultConfig };
await act(() => renderHook(() => useDayViewShortcuts(config)));

pressKey("c");

expect(config.onAddTask).toHaveBeenCalled();
expect(config.onCreateEvent).not.toHaveBeenCalled();
});

it("should call onEditTask when 'e' is pressed", async () => {
Expand Down Expand Up @@ -181,6 +193,7 @@ describe.each([
pressKey("c", {}, textarea);

expect(config.onAddTask).not.toHaveBeenCalled();
expect(config.onCreateEvent).not.toHaveBeenCalled();
});

it("should not handle shortcuts when typing in contenteditable elements", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ interface KeyboardShortcutsConfig {
// Agenda navigation
onFocusAgenda?: () => void;

// Event management
onCreateEvent?: () => void;

// Event undo
onRestoreEvent?: () => void;

Expand Down Expand Up @@ -65,6 +68,7 @@ export function useDayViewShortcuts(config: KeyboardShortcutsConfig) {
onPrevDay,
onGoToToday,
onFocusAgenda,
onCreateEvent,
isEditingTask,
hasFocusedTask,
undoToastId,
Expand Down Expand Up @@ -124,6 +128,8 @@ export function useDayViewShortcuts(config: KeyboardShortcutsConfig) {

useKeyUpEvent({ combination: ["e"], handler: onEditTask });

useKeyUpEvent({ combination: ["n"], handler: onCreateEvent });

useKeyUpEvent({ combination: ["Delete"], handler: handleDeleteTask });

useKeyUpEvent({ combination: ["Backspace"], handler: handleDeleteTask });
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/views/Day/view/DayView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("DayView", () => {
// Check that CMD+K shortcut is displayed in the shortcuts overlay
expect(await screen.findByText("Global")).toBeInTheDocument();
expect(screen.getByTestId(getModifierKeyTestId())).toBeInTheDocument();
expect(screen.getByTestId("k-icon")).toBeInTheDocument();
expect(screen.getAllByTestId("k-icon").length).toBeGreaterThan(1);
expect(screen.getByText("Command Palette")).toBeInTheDocument();
});
});
72 changes: 44 additions & 28 deletions packages/web/src/views/Day/view/DayViewContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { selectDayEvents } from "@web/ducks/events/selectors/event.selectors";
import { useAppSelector } from "@web/store/store.hooks";
import { Dedication } from "@web/views/Calendar/components/Dedication";
import { DraftProviderV2 } from "@web/views/Calendar/components/Draft/context/DraftProviderV2";
import { useDraftContextV2 } from "@web/views/Calendar/components/Draft/context/useDraftContextV2";
import { useRefetch } from "@web/views/Calendar/hooks/useRefetch";
import { StyledCalendar } from "@web/views/Calendar/styled";
import { Agenda } from "@web/views/Day/components/Agenda/Agenda";
Expand All @@ -27,7 +28,7 @@ import {
focusOnFirstTask,
} from "@web/views/Day/util/day.shortcut.util";

export const DayViewContent = () => {
const DayViewContentInner = () => {
useRefetch();

const {
Expand Down Expand Up @@ -110,6 +111,12 @@ export const DayViewContent = () => {
}
};

const { openEventForm } = useDraftContextV2();

const onCreateEvent = useCallback(() => {
openEventForm(true);
}, [openEventForm]);

useDayViewShortcuts({
onAddTask: focusOnAddTaskInput,
onEditTask: handleEditTask,
Expand All @@ -118,43 +125,52 @@ export const DayViewContent = () => {
onMigrateTask: migrateTask,
onFocusTasks: focusOnFirstTask,
onFocusAgenda: handleFocusAgenda,
onCreateEvent: onCreateEvent,
onNextDay: navigateToNextDay,
onPrevDay: navigateToPreviousDay,
onGoToToday: handleGoToToday,
hasFocusedTask,
undoToastId,
});

return (
<>
<DayCmdPalette onGoToToday={handleGoToToday} />
<Dedication />

<StyledCalendar>
<Header />

<div
className={`flex max-w-4/7 min-w-4/7 flex-1 justify-center gap-8 self-center overflow-hidden`}
>
<TaskList />

<Agenda onScrollToNowLineReady={handleScrollToNowLineReady} />
</div>
</StyledCalendar>

<StorageInfoModal isOpen={isModalOpen} onClose={closeModal} />

<ShortcutsOverlay
sections={[
{ title: "Day", shortcuts: shortcuts.dayShortcuts },
{ title: "Tasks", shortcuts: shortcuts.dayTaskShortcuts },
{ title: "Calendar", shortcuts: shortcuts.dayAgendaShortcuts },
{ title: "Global", shortcuts: shortcuts.globalShortcuts },
]}
/>

<FloatingEventForm />
</>
);
};

export const DayViewContent = () => {
return (
<MousePositionProvider>
<DraftProviderV2>
<DayCmdPalette onGoToToday={handleGoToToday} />
<Dedication />

<StyledCalendar>
<Header />

<div
className={`flex max-w-4/7 min-w-4/7 flex-1 justify-center gap-8 self-center overflow-hidden`}
>
<TaskList />

<Agenda onScrollToNowLineReady={handleScrollToNowLineReady} />
</div>
</StyledCalendar>

<StorageInfoModal isOpen={isModalOpen} onClose={closeModal} />

<ShortcutsOverlay
sections={[
{ title: "Home", shortcuts: shortcuts.homeShortcuts },
{ title: "Tasks", shortcuts: shortcuts.dayTaskShortcuts },
{ title: "Calendar", shortcuts: shortcuts.dayAgendaShortcuts },
{ title: "Global", shortcuts: shortcuts.globalShortcuts },
]}
/>

<FloatingEventForm />
<DayViewContentInner />
</DraftProviderV2>
</MousePositionProvider>
);
Expand Down
Loading