Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-759] style: added calendar layout responsiveness #3969

Merged
merged 5 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
75 changes: 71 additions & 4 deletions web/components/issues/issue-layouts/calendar/calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { useState } from "react";
import { observer } from "mobx-react-lite";
// hooks
import useSize from "hooks/use-window-size";
// components
// ui
import { Spinner } from "@plane/ui";
import { CalendarHeader, CalendarWeekDays, CalendarWeekHeader } from "components/issues";
import {
CalendarHeader,
CalendarIssueBlock,
CalendarQuickAddIssueForm,
CalendarWeekDays,
CalendarWeekHeader,
} from "components/issues";
// types
import {
IIssueDisplayFilterOptions,
Expand All @@ -15,6 +23,9 @@ import {
TIssueMap,
} from "@plane/types";
import { ICalendarWeek } from "./types";
// helpers
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
import { cn } from "helpers/common.helper";
// constants
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
import { EUserProjectRoles } from "constants/project";
Expand All @@ -24,6 +35,7 @@ import { ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssuesFilter } from "store/issue/project-views";
import { MONTHS_LIST } from "constants/calendar";

type Props = {
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
Expand Down Expand Up @@ -62,6 +74,8 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
updateFilters,
readOnly = false,
} = props;
// states
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
// store hooks
const {
issues: { viewFlags },
Expand All @@ -70,6 +84,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
const {
membership: { currentProjectRole },
} = useUser();
const [windowWidth] = useSize();

const { enableIssueCreation } = viewFlags || {};
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
Expand All @@ -78,25 +93,39 @@ export const CalendarChart: React.FC<Props> = observer((props) => {

const allWeeksOfActiveMonth = issueCalendarView.allWeeksOfActiveMonth;

if (!calendarPayload)
const formattedDatePayload = renderFormattedPayloadDate(selectedDate) ?? undefined;

if (!calendarPayload || !formattedDatePayload)
return (
<div className="grid h-full w-full place-items-center">
<Spinner />
</div>
);

const issueIdList = groupedIssueIds ? groupedIssueIds[formattedDatePayload] : null;

return (
<>
<div className="flex h-full w-full flex-col overflow-hidden">
<CalendarHeader issuesFilterStore={issuesFilterStore} updateFilters={updateFilters} />
<div className="flex h-full w-full vertical-scrollbar scrollbar-lg flex-col">
<CalendarHeader
setSelectedDate={setSelectedDate}
issuesFilterStore={issuesFilterStore}
updateFilters={updateFilters}
/>
<div
className={cn("flex md:h-full w-full flex-col overflow-y-auto", {
"vertical-scrollbar scrollbar-lg": windowWidth > 768,
})}
>
<CalendarWeekHeader isLoading={!issues} showWeekends={showWeekends} />
<div className="h-full w-full">
{layout === "month" && (
<div className="grid h-full w-full grid-cols-1 divide-y-[0.5px] divide-custom-border-200">
{allWeeksOfActiveMonth &&
Object.values(allWeeksOfActiveMonth).map((week: ICalendarWeek, weekIndex) => (
<CalendarWeekDays
selectedDate={selectedDate}
setSelectedDate={setSelectedDate}
issuesFilterStore={issuesFilterStore}
key={weekIndex}
week={week}
Expand All @@ -115,6 +144,8 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
)}
{layout === "week" && (
<CalendarWeekDays
selectedDate={selectedDate}
setSelectedDate={setSelectedDate}
issuesFilterStore={issuesFilterStore}
week={issueCalendarView.allDaysOfActiveWeek}
issues={issues}
Expand All @@ -129,6 +160,42 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
/>
)}
</div>

{/* mobile view */}
<>
<div className="md:hidden">
1akhanBaheti marked this conversation as resolved.
Show resolved Hide resolved
<p className="p-4 text-xl font-semibold">
{`${selectedDate.getDate()} ${
MONTHS_LIST[selectedDate.getMonth() + 1].title
}, ${selectedDate.getFullYear()}`}
</p>
{issueIdList &&
issueIdList?.length > 0 &&
issueIdList?.map((issueId) => {
if (!issues?.[issueId]) return null;
1akhanBaheti marked this conversation as resolved.
Show resolved Hide resolved
const issue = issues?.[issueId];
return (
<div key={issue.id} className="border-b border-custom-border-200 px-4">
<CalendarIssueBlock issue={issue} quickActions={quickActions} />
</div>
);
})}
</div>
{enableIssueCreation && isEditingAllowed && !readOnly && (
<div className="flex-shrink-0 px-2 border-b border-custom-border-200 !h-10 flex items-center md:hidden">
<CalendarQuickAddIssueForm
formKey="target_date"
groupId={formattedDatePayload}
prePopulatedData={{
target_date: renderFormattedPayloadDate(selectedDate) ?? undefined,
}}
quickAddCallback={quickAddCallback}
addIssuesToView={addIssuesToView}
viewId={viewId}
/>
</div>
)}
</>
</div>
</div>
</>
Expand Down
32 changes: 30 additions & 2 deletions web/components/issues/issue-layouts/calendar/day-tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { observer } from "mobx-react-lite";
import { CalendarIssueBlocks, ICalendarDate, CalendarQuickAddIssueForm } from "components/issues";
// helpers
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
import { cn } from "helpers/common.helper";
// constants
import { MONTHS_LIST } from "constants/calendar";
// types
Expand All @@ -31,6 +32,8 @@ type Props = {
addIssuesToView?: (issueIds: string[]) => Promise<any>;
viewId?: string;
readOnly?: boolean;
selectedDate: Date;
setSelectedDate: (date: Date) => void;
};

export const CalendarDayTile: React.FC<Props> = observer((props) => {
Expand All @@ -46,6 +49,8 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
addIssuesToView,
viewId,
readOnly = false,
selectedDate,
setSelectedDate,
} = props;
const [showAllIssues, setShowAllIssues] = useState(false);
const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month";
Expand All @@ -57,13 +62,14 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
const totalIssues = issueIdList?.length ?? 0;

const isToday = date.date.toDateString() === new Date().toDateString();
const isSelectedDate = date.date.toDateString() == selectedDate.toDateString();

return (
<>
<div className="group relative flex h-full w-full flex-col bg-custom-background-90">
{/* header */}
<div
className={`flex items-center justify-end flex-shrink-0 px-2 py-1.5 text-right text-xs ${
className={`hidden md:flex items-center justify-end flex-shrink-0 px-2 py-1.5 text-right text-xs ${
calendarLayout === "month" // if month layout, highlight current month days
? date.is_current_month
? "font-medium"
Expand All @@ -86,7 +92,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
</div>

{/* content */}
<div className="h-full w-full">
<div className="h-full w-full hidden md:block">
<Droppable droppableId={formattedDatePayload} isDropDisabled={readOnly}>
{(provided, snapshot) => (
<div
Expand Down Expand Up @@ -139,6 +145,28 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
)}
</Droppable>
</div>

{/* Mobile view content */}
<div
onClick={() => setSelectedDate(date.date)}
className={cn(
"text-sm py-2.5 h-full w-full font-medium mx-auto flex flex-col justify-start items-center md:hidden cursor-pointer",
{
"bg-custom-background-100": date.date.getDay() !== 0 && date.date.getDay() !== 6,
}
)}
>
<div
className={cn("h-6 w-6 rounded-full flex items-center justify-center ", {
"bg-custom-primary-100 text-white": isSelectedDate,
"bg-custom-primary-100/10 text-custom-primary-100 ": isToday && !isSelectedDate,
})}
>
{date.date.getDate()}
</div>

{totalIssues > 0 && <div className="flex flex-shrink-0 h-1.5 w-1.5 bg-custom-primary-100 rounded mt-1" />}
</div>
</div>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { useRouter } from "next/router";
import { usePopper } from "react-popper";
import { Popover, Transition } from "@headlessui/react";
// hooks
import useSize from "hooks/use-window-size";
// ui
// icons
import { Check, ChevronUp } from "lucide-react";
import { Check, ChevronUp, MoreVerticalIcon } from "lucide-react";
import { ToggleSwitch } from "@plane/ui";
// types
import {
Expand Down Expand Up @@ -41,6 +42,7 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
const { projectId } = router.query;

const issueCalendarView = useCalendarView();
const [windowWidth] = useSize();

const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
Expand All @@ -60,7 +62,7 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
const calendarLayout = issuesFilterStore.issueFilters?.displayFilters?.calendar?.layout ?? "month";
const showWeekends = issuesFilterStore.issueFilters?.displayFilters?.calendar?.show_weekends ?? false;

const handleLayoutChange = (layout: TCalendarLayouts) => {
const handleLayoutChange = (layout: TCalendarLayouts, closePopover: any) => {
if (!projectId || !updateFilters) return;

updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, {
Expand All @@ -75,6 +77,7 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
? issueCalendarView.calendarFilters.activeMonthDate
: issueCalendarView.calendarFilters.activeWeekDate
);
if (windowWidth <= 768) closePopover(); // close the popover on mobile
};

const handleToggleWeekends = () => {
Expand All @@ -92,21 +95,24 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop

return (
<Popover className="relative">
{({ open }) => (
{({ open, close: closePopover }) => (
<>
<Popover.Button as={React.Fragment}>
<button
type="button"
ref={setReferenceElement}
className={`flex items-center gap-1.5 rounded bg-custom-background-80 px-2.5 py-1 text-xs outline-none hover:bg-custom-background-80 ${
open ? "text-custom-text-100" : "text-custom-text-200"
}`}
>
<div className="font-medium">Options</div>
<button type="button" ref={setReferenceElement}>
<div
className={`flex h-3.5 w-3.5 items-center justify-center transition-all ${open ? "" : "rotate-180"}`}
className={`hidden md:flex items-center gap-1.5 rounded bg-custom-background-80 px-2.5 py-1 text-xs outline-none hover:bg-custom-background-80 ${
open ? "text-custom-text-100" : "text-custom-text-200"
}`}
>
<ChevronUp width={12} strokeWidth={2} />
<div className="font-medium">Options</div>
<div
className={`flex h-3.5 w-3.5 items-center justify-center transition-all ${open ? "" : "rotate-180"}`}
>
<ChevronUp width={12} strokeWidth={2} />
</div>
</div>
<div className="md:hidden">
<MoreVerticalIcon className="h-4 text-custom-text-200" strokeWidth={2} />
</div>
</button>
</Popover.Button>
Expand All @@ -132,7 +138,7 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
key={layout}
type="button"
className="flex w-full items-center justify-between gap-2 rounded px-1 py-1.5 text-left text-xs hover:bg-custom-background-80"
onClick={() => handleLayoutChange(layoutDetails.key)}
onClick={() => handleLayoutChange(layoutDetails.key, closePopover)}
>
{layoutDetails.title}
{calendarLayout === layout && <Check size={12} strokeWidth={2} />}
Expand All @@ -144,7 +150,12 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
onClick={handleToggleWeekends}
>
Show weekends
<ToggleSwitch value={showWeekends} onChange={() => {}} />
<ToggleSwitch
value={showWeekends}
onChange={() => {
if (windowWidth <= 768) closePopover(); // close the popover on mobile
}}
/>
</button>
</div>
</div>
Expand Down
4 changes: 3 additions & 1 deletion web/components/issues/issue-layouts/calendar/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ interface ICalendarHeader {
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => Promise<void>;
setSelectedDate: (date: Date) => void;
}

export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
const { issuesFilterStore, updateFilters } = props;
const { issuesFilterStore, updateFilters, setSelectedDate } = props;

const issueCalendarView = useCalendarView();

Expand Down Expand Up @@ -91,6 +92,7 @@ export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
activeMonthDate: firstDayOfCurrentMonth,
activeWeekDate: today,
});
setSelectedDate(today);
};

return (
Expand Down
1 change: 1 addition & 0 deletions web/components/issues/issue-layouts/calendar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from "./types.d";
export * from "./day-tile";
export * from "./header";
export * from "./issue-blocks";
export * from "./issue-block";
export * from "./week-days";
export * from "./week-header";
export * from "./quick-add-issue-form";
Loading
Loading