Skip to content

Commit

Permalink
💨 Feat: Add Cmd Palette (#102)
Browse files Browse the repository at this point in the history
* feat: add react-cmdk

* feat: customize cmd items

* fix: disable shortcuts when cmd palette is open

* feat: added actions:
  - create timed event
  - create someday week event
  - create someday month event
  - logout
  -  go to today

* feat: open links in new tab

* feat: add cmd icon to sidebar
  • Loading branch information
tyler-dane authored May 3, 2024
1 parent ef547fc commit 8b40163
Show file tree
Hide file tree
Showing 18 changed files with 408 additions and 46 deletions.
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"mini-css-extract-plugin": "^2.3.0",
"normalizr": "^3.6.1",
"react": "^18.1.0",
"react-cmdk": "^1.3.9",
"react-datepicker": "^4.2.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/assets/svg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { default as ArrowLeftIcon } from "./arrowLeft.svg";
export { default as CheckIcon } from "./check.svg";
export { default as MonthCalendarIcon } from "./monthCalendar.svg";
export { default as HamburgerMenuIcon } from "./hamburgerMenu.svg";
export { default as MetaKeyIcon } from "./metaKey.svg";
export { default as SidebarCollapseIcon } from "./sidebarCollapse.svg";
export { default as SidebarOpenIcon } from "./sidebarOpen.svg";
export { default as TrashIcon } from "./trash.svg";
6 changes: 6 additions & 0 deletions packages/web/src/assets/svg/metaKey.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions packages/web/src/components/Icons/MetaKeyIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import { getColor } from "@core/util/color.utils";
import { ColorNames } from "@core/types/color.types";
import { useAppDispatch, useAppSelector } from "@web/store/store.hooks";
import { settingsSlice } from "@web/ducks/settings/slices/settings.slice";
import { selectIsCmdPaletteOpen } from "@web/ducks/settings/selectors/settings.selectors";

import { StyledMetaKeyIcon } from "../Svg";
import { TooltipWrapper } from "../Tooltip/TooltipWrapper";

export const MetaKeyIcon = () => {
const dispatch = useAppDispatch();

const isCmdPaletteOpen = useAppSelector(selectIsCmdPaletteOpen);

return (
<TooltipWrapper
description="Open command palette (Cmd + K)"
onClick={() => {
if (isCmdPaletteOpen) {
dispatch(settingsSlice.actions.closeCmdPalette());
} else {
dispatch(settingsSlice.actions.openCmdPalette());
}
}}
>
<StyledMetaKeyIcon hovercolor={getColor(ColorNames.WHITE_1)} />
</TooltipWrapper>
);
};
5 changes: 5 additions & 0 deletions packages/web/src/components/Svg/Svg.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled, { css } from "styled-components";
import {
HamburgerMenuIcon,
MetaKeyIcon,
MonthCalendarIcon,
TrashIcon,
} from "@web/assets/svg";
Expand All @@ -27,6 +28,10 @@ export const StyledHamburgerMenuIcon = styled(HamburgerMenuIcon)`
${(props: SvgStylesProps) => svgStyles(props)}
`;

export const StyledMetaKeyIcon = styled(MetaKeyIcon)`
${(props: SvgStylesProps) => svgStyles(props)}
`;

export const StyledMonthCalendarIcon = styled(MonthCalendarIcon)`
${(props: SvgStylesProps) => svgStyles(props)}
`;
Expand Down
8 changes: 7 additions & 1 deletion packages/web/src/components/Svg/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import {
StyledHamburgerMenuIcon,
StyledMetaKeyIcon,
StyledMonthCalendarIcon,
StyledTrashIcon,
} from "./Svg";

export { StyledHamburgerMenuIcon, StyledMonthCalendarIcon, StyledTrashIcon };
export {
StyledHamburgerMenuIcon,
StyledMetaKeyIcon,
StyledMonthCalendarIcon,
StyledTrashIcon,
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ import { RootState } from "@web/store";

export const selectDatesInView = (state: RootState) => state.settings.dates;

export const selectIsCmdPaletteOpen = (state: RootState) =>
state.settings.isCmdPaletteOpen;

export const selectIsRightSidebarOpen = (state: RootState) =>
state.settings.isRightSidebarOpen;
11 changes: 11 additions & 0 deletions packages/web/src/ducks/settings/slices/settings.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface State_Settings {
start: string;
end: string;
};
isCmdPaletteOpen: boolean;
isRightSidebarOpen: boolean;
}

Expand All @@ -15,6 +16,7 @@ const initialState: State_Settings = {
start: dayjs().format(),
end: dayjs().endOf("week").format(),
},
isCmdPaletteOpen: false,
isRightSidebarOpen: false,
};

Expand All @@ -29,6 +31,15 @@ export const settingsSlice = createSlice({
name: "settings",
initialState,
reducers: {
closeCmdPalette: (state) => {
state.isCmdPaletteOpen = false;
},
openCmdPalette: (state) => {
state.isCmdPaletteOpen = true;
},
toggleCmdPalette: (state) => {
state.isCmdPaletteOpen = !state.isCmdPaletteOpen;
},
toggleRightSidebar: (state) => {
state.isRightSidebarOpen = !state.isRightSidebarOpen;
},
Expand Down
21 changes: 15 additions & 6 deletions packages/web/src/views/Calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { LeftSidebar } from "./components/LeftSidebar";
import { RightSidebar } from "./components/RightSidebar";
import { Draft } from "./components/Event/Draft";
import { Dedication } from "./components/Dedication";
import { CmdPalette } from "../CmdPalette";

export const CalendarView = () => {
const prefs = usePreferences();
Expand All @@ -31,22 +32,30 @@ export const CalendarView = () => {

const dateCalcs = useDateCalcs(measurements, gridRefs.gridScrollRef);

useShortcuts(
const isCurrentWeek = weekProps.component.isCurrentWeek;
const startOfSelectedWeek = weekProps.component.startOfView;
const util = weekProps.util;
const toggleSidebar = prefs.toggleSidebar;

const shortcutProps = {
today,
dateCalcs,
weekProps.component.isCurrentWeek,
weekProps.component.startOfView,
weekProps.util,
isCurrentWeek,
startOfSelectedWeek,
util,
scrollUtil,
prefs.toggleSidebar
);
toggleSidebar,
};

useShortcuts(shortcutProps);

const rootProps: RootProps = {
component: { today: today },
};

return (
<Styled id="cal">
<CmdPalette {...shortcutProps} />
<Dedication />

<Draft
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,24 @@ import {
ID_GRID_EVENTS_ALLDAY,
ID_GRID_EVENTS_TIMED,
} from "@web/common/constants/web.constants";
import { roundToNext } from "@web/common/utils";
import { getElemById } from "@web/common/utils/grid.util";
import { GRID_TIME_STEP } from "@web/views/Calendar/layout.constants";
import dayjs, { Dayjs } from "dayjs";

export const getDraftTimes = (isCurrentWeek: boolean, startOfWeek: Dayjs) => {
const currentMinute = dayjs().minute();
const nextMinuteInterval = roundToNext(currentMinute, GRID_TIME_STEP);

const fullStart = isCurrentWeek ? dayjs() : startOfWeek.hour(dayjs().hour());
const _start = fullStart.minute(nextMinuteInterval).second(0);

const _end = _start.add(1, "hour");
const startDate = _start.format();
const endDate = _end.format();

return { startDate, endDate };
};

export const getDraftContainer = (isAllDay: boolean) => {
if (isAllDay) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Measurements_Grid } from "@web/views/Calendar/hooks/grid/useGridLayout"

import { Styled, getSidebarToggleIcon, StyledSidebarOverflow } from "./styled";
import { SomedaySection } from "./SomedaySection";
import { SidebarIconRow } from "./SidebarIconRow";

interface Props {
dateCalcs: DateCalcs;
Expand Down Expand Up @@ -50,7 +51,7 @@ export const LeftSidebar: FC<Props & React.HTMLAttributes<HTMLDivElement>> = (
viewEnd={weekEnd}
/>

{/* <SidebarIconRow prefs={prefs} /> */}
<SidebarIconRow />
</Styled>
);
};
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import React, { FC } from "react";
import { MonthCalendarIcon } from "@web/components/Icons/MonthCalendarIcon";
import { Preferences } from "@web/views/Calendar/hooks/usePreferences";
import React from "react";
import { MetaKeyIcon } from "@web/components/Icons/MetaKeyIcon";

import { StyledBottomRow } from "../styled";

interface Props {
prefs: Preferences;
}

export const SidebarIconRow: FC<Props> = ({ prefs }) => {
export const SidebarIconRow = () => {
return (
<StyledBottomRow>
<MonthCalendarIcon prefs={prefs} />
<MetaKeyIcon />
</StyledBottomRow>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./SidebarIconRow";
61 changes: 33 additions & 28 deletions packages/web/src/views/Calendar/hooks/shortcuts/useShortcuts.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Key } from "ts-keycode-enum";
import dayjs, { Dayjs } from "dayjs";
import { Dayjs } from "dayjs";
import { Categories_Event } from "@core/types/event.types";
import {
SOMEDAY_MONTH_LIMIT_MSG,
SOMEDAY_WEEK_LIMIT_MSG,
} from "@core/constants/core.constants";
import { useAppDispatch, useAppSelector } from "@web/store/store.hooks";
import { isDrafting, roundToNext } from "@web/common/utils";
import { isDrafting } from "@web/common/utils";
import { draftSlice } from "@web/ducks/events/slices/draft.slice";
import { GRID_TIME_STEP } from "@web/views/Calendar/layout.constants";
import { ROOT_ROUTES } from "@web/common/constants/routes";
import {
selectIsAtMonthlyLimit,
Expand All @@ -21,31 +20,34 @@ import { settingsSlice } from "@web/ducks/settings/slices/settings.slice";
import { DateCalcs } from "../grid/useDateCalcs";
import { Util_Scroll } from "../grid/useScroll";
import { WeekProps } from "../useWeek";

export const useShortcuts = (
today: Dayjs,
dateCalcs: DateCalcs,
isCurrentWeek: boolean,
startOfSelectedWeek: Dayjs,
util: WeekProps["util"],
scrollUtil: Util_Scroll,
toggleSidebar: (target: "left" | "right") => void
) => {
import { getDraftTimes } from "../../components/Event/Draft/draft.util";

export interface ShortcutProps {
today: Dayjs;
dateCalcs: DateCalcs;
isCurrentWeek: boolean;
startOfSelectedWeek: Dayjs;
util: WeekProps["util"];
scrollUtil: Util_Scroll;
toggleSidebar: (target: "left" | "right") => void;
}

export const useShortcuts = ({
today,
dateCalcs,
isCurrentWeek,
startOfSelectedWeek,
util,
scrollUtil,
toggleSidebar,
}: ShortcutProps) => {
const dispatch = useAppDispatch();
const navigate = useNavigate();

const isAtMonthlyLimit = useAppSelector(selectIsAtMonthlyLimit);
const isAtWeeklyLimit = useAppSelector(selectIsAtWeeklyLimit);

useEffect(() => {
const _getStart = () => {
if (isCurrentWeek) {
return dayjs();
} else {
return startOfSelectedWeek.hour(dayjs().hour());
}
};

const _createSomedayDraft = (type: "week" | "month") => {
if (type === "week" && isAtWeeklyLimit) {
alert(SOMEDAY_WEEK_LIMIT_MSG);
Expand All @@ -69,13 +71,10 @@ export const useShortcuts = (
};

const _createTimedDraft = () => {
const currentMinute = dayjs().minute();
const nextMinuteInterval = roundToNext(currentMinute, GRID_TIME_STEP);

const _start = _getStart().minute(nextMinuteInterval).second(0);
const _end = _start.add(1, "hour");
const startDate = _start.format();
const endDate = _end.format();
const { startDate, endDate } = getDraftTimes(
isCurrentWeek,
startOfSelectedWeek
);

dispatch(
draftSlice.actions.start({
Expand All @@ -98,6 +97,12 @@ export const useShortcuts = (
const keyDownHandler = (e: KeyboardEvent) => {
if (isDrafting()) return;

const isCmdPaletteOpen =
document.getElementById("headlessui-portal-root") !== null;
if (isCmdPaletteOpen) return;

if (e.metaKey) return;

const handlersByKey = {
[Key.OpenBracket]: () => toggleSidebar("left"),
[Key.ClosedBracket]: () =>
Expand Down
Loading

0 comments on commit 8b40163

Please sign in to comment.