[WEB-704] fix: inbox responsiveness (#4275)

* chore: inbox responsiveness

* fix: sidebar in full view

* style: border theme

* condition update
This commit is contained in:
Lakhan Baheti 2024-04-29 00:54:35 +05:30 committed by GitHub
parent ea436c925a
commit 0c880bbbc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 258 additions and 25 deletions

View File

@ -17,6 +17,7 @@ import { Button, ControlLink, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui
import { import {
DeclineIssueModal, DeclineIssueModal,
DeleteInboxIssueModal, DeleteInboxIssueModal,
InboxIssueActionsMobileHeader,
InboxIssueCreateEditModalRoot, InboxIssueCreateEditModalRoot,
InboxIssueSnoozeModal, InboxIssueSnoozeModal,
InboxIssueStatus, InboxIssueStatus,
@ -38,10 +39,12 @@ type TInboxIssueActionsHeader = {
projectId: string; projectId: string;
inboxIssue: IInboxIssueStore | undefined; inboxIssue: IInboxIssueStore | undefined;
isSubmitting: "submitting" | "submitted" | "saved"; isSubmitting: "submitting" | "submitted" | "saved";
toggleMobileSidebar: boolean;
setToggleMobileSidebar: (value: boolean) => void;
}; };
export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((props) => { export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((props) => {
const { workspaceSlug, projectId, inboxIssue, isSubmitting } = props; const { workspaceSlug, projectId, inboxIssue, isSubmitting, toggleMobileSidebar, setToggleMobileSidebar } = props;
// states // states
const [isSnoozeDateModalOpen, setIsSnoozeDateModalOpen] = useState(false); const [isSnoozeDateModalOpen, setIsSnoozeDateModalOpen] = useState(false);
const [selectDuplicateIssue, setSelectDuplicateIssue] = useState(false); const [selectDuplicateIssue, setSelectDuplicateIssue] = useState(false);
@ -207,7 +210,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
/> />
</> </>
<div className="relative flex h-full w-full items-center justify-between gap-2 px-4"> <div className="hidden relative lg:flex h-full w-full items-center justify-between gap-2 px-4">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{issue?.project_id && issue.sequence_id && ( {issue?.project_id && issue.sequence_id && (
<h3 className="text-base font-medium text-custom-text-300 flex-shrink-0"> <h3 className="text-base font-medium text-custom-text-300 flex-shrink-0">
@ -319,6 +322,28 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
</div> </div>
</div> </div>
</div> </div>
<div className="lg:hidden">
<InboxIssueActionsMobileHeader
inboxIssue={inboxIssue}
isSubmitting={isSubmitting}
handleCopyIssueLink={handleCopyIssueLink}
setAcceptIssueModal={setAcceptIssueModal}
setDeclineIssueModal={setDeclineIssueModal}
setIsSnoozeDateModalOpen={setIsSnoozeDateModalOpen}
setSelectDuplicateIssue={setSelectDuplicateIssue}
setDeleteIssueModal={setDeleteIssueModal}
canMarkAsAccepted={canMarkAsAccepted}
canMarkAsDeclined={canMarkAsDeclined}
canMarkAsDuplicate={canMarkAsDuplicate}
canDelete={canDelete}
isAcceptedOrDeclined={isAcceptedOrDeclined}
handleInboxIssueNavigation={handleInboxIssueNavigation}
workspaceSlug={workspaceSlug}
toggleMobileSidebar={toggleMobileSidebar}
setToggleMobileSidebar={setToggleMobileSidebar}
/>
</div>
</> </>
); );
}); });

View File

@ -0,0 +1,170 @@
import React from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import {
CircleCheck,
CircleX,
ChevronDown,
ChevronUp,
Clock,
ExternalLink,
FileStack,
Link,
Trash2,
PanelLeft,
} from "lucide-react";
import { CustomMenu } from "@plane/ui";
// components
import { InboxIssueStatus } from "@/components/inbox";
import { IssueUpdateStatus } from "@/components/issues";
// helpers
import { cn } from "@/helpers/common.helper";
// store types
import type { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
type Props = {
workspaceSlug: string;
inboxIssue: IInboxIssueStore | undefined;
isSubmitting: "submitting" | "submitted" | "saved";
handleInboxIssueNavigation: (direction: "next" | "prev") => void;
canMarkAsAccepted: boolean;
canMarkAsDeclined: boolean;
isAcceptedOrDeclined: boolean | undefined;
canMarkAsDuplicate: boolean;
canDelete: boolean;
setAcceptIssueModal: (value: boolean) => void;
setDeclineIssueModal: (value: boolean) => void;
setDeleteIssueModal: (value: boolean) => void;
setIsSnoozeDateModalOpen: (value: boolean) => void;
setSelectDuplicateIssue: (value: boolean) => void;
handleCopyIssueLink: () => void;
toggleMobileSidebar: boolean;
setToggleMobileSidebar: (value: boolean) => void;
};
export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) => {
const {
inboxIssue,
isSubmitting,
handleInboxIssueNavigation,
canMarkAsAccepted,
canMarkAsDeclined,
canDelete,
canMarkAsDuplicate,
isAcceptedOrDeclined,
workspaceSlug,
setAcceptIssueModal,
setDeclineIssueModal,
setDeleteIssueModal,
setIsSnoozeDateModalOpen,
setSelectDuplicateIssue,
handleCopyIssueLink,
toggleMobileSidebar,
setToggleMobileSidebar,
} = props;
const router = useRouter();
const issue = inboxIssue?.issue;
const currentInboxIssueId = issue?.id;
if (!issue || !inboxIssue) return null;
return (
<div className="h-12 relative flex border-custom-border-200 w-full items-center gap-2 px-4">
<PanelLeft
onClick={() => setToggleMobileSidebar(!toggleMobileSidebar)}
className={cn(
"w-4 h-4 flex-shrink-0 mr-2",
toggleMobileSidebar ? "text-custom-primary-100" : "text-custom-text-200"
)}
/>
<div className="flex items-center gap-2 w-full">
<div className="flex items-center gap-x-2">
<button
type="button"
className="rounded border border-custom-border-200 p-1.5"
onClick={() => handleInboxIssueNavigation("prev")}
>
<ChevronUp size={14} strokeWidth={2} />
</button>
<button
type="button"
className="rounded border border-custom-border-200 p-1.5"
onClick={() => handleInboxIssueNavigation("next")}
>
<ChevronDown size={14} strokeWidth={2} />
</button>
</div>
<div className="flex items-center gap-4">
<InboxIssueStatus inboxIssue={inboxIssue} iconSize={12} />
<div className="flex items-center justify-end w-full">
<IssueUpdateStatus isSubmitting={isSubmitting} />
</div>
</div>
<div className="ml-auto">
<CustomMenu verticalEllipsis placement="bottom-start">
{isAcceptedOrDeclined && (
<CustomMenu.MenuItem onClick={handleCopyIssueLink}>
<div className="flex items-center gap-2">
<Link size={14} strokeWidth={2} />
Copy issue link
</div>
</CustomMenu.MenuItem>
)}
{isAcceptedOrDeclined && (
<CustomMenu.MenuItem
onClick={() =>
router.push(`/${workspaceSlug}/projects/${issue?.project_id}/issues/${currentInboxIssueId}`)
}
>
<div className="flex items-center gap-2">
<ExternalLink size={14} strokeWidth={2} />
Open issue
</div>
</CustomMenu.MenuItem>
)}
{canMarkAsAccepted && !isAcceptedOrDeclined && (
<CustomMenu.MenuItem onClick={() => setIsSnoozeDateModalOpen(true)}>
<div className="flex items-center gap-2">
<Clock size={14} strokeWidth={2} />
Snooze
</div>
</CustomMenu.MenuItem>
)}
{canMarkAsDuplicate && !isAcceptedOrDeclined && (
<CustomMenu.MenuItem onClick={() => setSelectDuplicateIssue(true)}>
<div className="flex items-center gap-2">
<FileStack size={14} strokeWidth={2} />
Mark as duplicate
</div>
</CustomMenu.MenuItem>
)}
{canMarkAsAccepted && (
<CustomMenu.MenuItem onClick={() => setAcceptIssueModal(true)}>
<div className="flex items-center gap-2 text-green-500">
<CircleCheck size={14} strokeWidth={2} />
Accept
</div>
</CustomMenu.MenuItem>
)}
{canMarkAsDeclined && (
<CustomMenu.MenuItem onClick={() => setDeclineIssueModal(true)}>
<div className="flex items-center gap-2 text-red-500">
<CircleX size={14} strokeWidth={2} />
Decline
</div>
</CustomMenu.MenuItem>
)}
{canDelete && !isAcceptedOrDeclined && (
<CustomMenu.MenuItem onClick={() => setDeleteIssueModal(true)}>
<div className="flex items-center gap-2 text-red-500">
<Trash2 size={14} strokeWidth={2} />
Delete
</div>
</CustomMenu.MenuItem>
)}
</CustomMenu>
</div>
</div>
</div>
);
});

View File

@ -1,4 +1,5 @@
export * from "./root"; export * from "./root";
export * from "./inbox-issue-header"; export * from "./inbox-issue-header";
export * from "./inbox-issue-mobile-header";
export * from "./issue-properties"; export * from "./issue-properties";
export * from "./issue-root"; export * from "./issue-root";

View File

@ -9,10 +9,12 @@ type TInboxContentRoot = {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
inboxIssueId: string; inboxIssueId: string;
toggleMobileSidebar: boolean;
setToggleMobileSidebar: (value: boolean) => void;
}; };
export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => { export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
const { workspaceSlug, projectId, inboxIssueId } = props; const { workspaceSlug, projectId, inboxIssueId, toggleMobileSidebar, setToggleMobileSidebar } = props;
// states // states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
// hooks // hooks
@ -43,6 +45,8 @@ export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
<div className="w-full h-full overflow-hidden relative flex flex-col"> <div className="w-full h-full overflow-hidden relative flex flex-col">
<div className="flex-shrink-0 min-h-[50px] border-b border-custom-border-300"> <div className="flex-shrink-0 min-h-[50px] border-b border-custom-border-300">
<InboxIssueActionsHeader <InboxIssueActionsHeader
setToggleMobileSidebar={setToggleMobileSidebar}
toggleMobileSidebar={toggleMobileSidebar}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
inboxIssue={inboxIssue} inboxIssue={inboxIssue}

View File

@ -1,13 +1,15 @@
import { FC } from "react"; import { FC, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import useSWR from "swr"; import useSWR from "swr";
import { Inbox } from "lucide-react"; import { Inbox, PanelLeft } from "lucide-react";
// components // components
import { EmptyState } from "@/components/empty-state"; import { EmptyState } from "@/components/empty-state";
import { InboxSidebar, InboxContentRoot } from "@/components/inbox"; import { InboxSidebar, InboxContentRoot } from "@/components/inbox";
import { InboxLayoutLoader } from "@/components/ui"; import { InboxLayoutLoader } from "@/components/ui";
// constants // constants
import { EmptyStateType } from "@/constants/empty-state"; import { EmptyStateType } from "@/constants/empty-state";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks // hooks
import { useProjectInbox } from "@/hooks/store"; import { useProjectInbox } from "@/hooks/store";
@ -20,6 +22,8 @@ type TInboxIssueRoot = {
export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => { export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => {
const { workspaceSlug, projectId, inboxIssueId, inboxAccessible } = props; const { workspaceSlug, projectId, inboxIssueId, inboxAccessible } = props;
// states
const [toggleMobileSidebar, setToggleMobileSidebar] = useState(false);
// hooks // hooks
const { loader, error, fetchInboxIssues } = useProjectInbox(); const { loader, error, fetchInboxIssues } = useProjectInbox();
@ -52,20 +56,43 @@ export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => {
); );
return ( return (
<div className="relative w-full h-full flex overflow-hidden"> <>
<InboxSidebar workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} /> {!inboxIssueId && (
<div className="flex lg:hidden items-center px-4 w-full h-12 border-b border-custom-border-200">
{inboxIssueId ? ( <PanelLeft
<InboxContentRoot onClick={() => setToggleMobileSidebar(!toggleMobileSidebar)}
workspaceSlug={workspaceSlug.toString()} className={cn("w-4 h-4 ", toggleMobileSidebar ? "text-custom-primary-100" : " text-custom-text-200")}
projectId={projectId.toString()} />
inboxIssueId={inboxIssueId.toString()}
/>
) : (
<div className="w-full h-full relative flex justify-center items-center">
<EmptyState type={EmptyStateType.INBOX_DETAIL_EMPTY_STATE} layout="screen-simple" />
</div> </div>
)} )}
</div> <div className="w-full h-full flex overflow-hidden bg-custom-background-100">
<div
className={cn(
"absolute z-10 top-[50px] lg:!top-0 lg:!relative bg-custom-background-100 flex-shrink-0 w-full lg:w-2/6 bottom-0 transition-all",
toggleMobileSidebar ? "translate-x-0" : "-translate-x-full lg:!translate-x-0",
)}
>
<InboxSidebar
setToggleMobileSidebar={setToggleMobileSidebar}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
</div>
{inboxIssueId ? (
<InboxContentRoot
setToggleMobileSidebar={setToggleMobileSidebar}
toggleMobileSidebar={toggleMobileSidebar}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
inboxIssueId={inboxIssueId.toString()}
/>
) : (
<div className="w-full h-full relative flex justify-center items-center">
<EmptyState type={EmptyStateType.INBOX_DETAIL_EMPTY_STATE} layout="screen-simple" />
</div>
)}
</div>
</>
); );
}); });

View File

@ -19,10 +19,11 @@ type InboxIssueListItemProps = {
projectId: string; projectId: string;
projectIdentifier?: string; projectIdentifier?: string;
inboxIssue: IInboxIssueStore; inboxIssue: IInboxIssueStore;
setToggleMobileSidebar: (value: boolean) => void;
}; };
export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props) => { export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props) => {
const { workspaceSlug, projectId, inboxIssue, projectIdentifier } = props; const { workspaceSlug, projectId, inboxIssue, projectIdentifier,setToggleMobileSidebar } = props;
// router // router
const router = useRouter(); const router = useRouter();
const { inboxIssueId } = router.query; const { inboxIssueId } = router.query;
@ -34,6 +35,7 @@ export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props)
const handleIssueRedirection = (event: MouseEvent, currentIssueId: string | undefined) => { const handleIssueRedirection = (event: MouseEvent, currentIssueId: string | undefined) => {
if (inboxIssueId === currentIssueId) event.preventDefault(); if (inboxIssueId === currentIssueId) event.preventDefault();
setToggleMobileSidebar(false);
}; };
if (!issue) return <></>; if (!issue) return <></>;

View File

@ -10,16 +10,18 @@ export type InboxIssueListProps = {
projectId: string; projectId: string;
projectIdentifier?: string; projectIdentifier?: string;
inboxIssues: IInboxIssueStore[]; inboxIssues: IInboxIssueStore[];
setToggleMobileSidebar: (value: boolean) => void;
}; };
export const InboxIssueList: FC<InboxIssueListProps> = observer((props) => { export const InboxIssueList: FC<InboxIssueListProps> = observer((props) => {
const { workspaceSlug, projectId, projectIdentifier, inboxIssues } = props; const { workspaceSlug, projectId, projectIdentifier, inboxIssues, setToggleMobileSidebar } = props;
return ( return (
<> <>
{inboxIssues.map((inboxIssue) => ( {inboxIssues.map((inboxIssue) => (
<Fragment key={inboxIssue.id}> <Fragment key={inboxIssue.id}>
<InboxIssueListItem <InboxIssueListItem
setToggleMobileSidebar={setToggleMobileSidebar}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
projectIdentifier={projectIdentifier} projectIdentifier={projectIdentifier}

View File

@ -19,6 +19,7 @@ import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
type IInboxSidebarProps = { type IInboxSidebarProps = {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
setToggleMobileSidebar: (value: boolean) => void;
}; };
const tabNavigationOptions: { key: TInboxIssueCurrentTab; label: string }[] = [ const tabNavigationOptions: { key: TInboxIssueCurrentTab; label: string }[] = [
@ -33,7 +34,7 @@ const tabNavigationOptions: { key: TInboxIssueCurrentTab; label: string }[] = [
]; ];
export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => { export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
const { workspaceSlug, projectId } = props; const { workspaceSlug, projectId, setToggleMobileSidebar } = props;
// ref // ref
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const elementRef = useRef<HTMLDivElement>(null); const elementRef = useRef<HTMLDivElement>(null);
@ -64,7 +65,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
}); });
return ( return (
<div className="flex-shrink-0 w-2/6 h-full border-r border-custom-border-300"> <div className="bg-custom-background-100 flex-shrink-0 w-full h-full border-r border-custom-border-300">
<div className="relative w-full h-full flex flex-col overflow-hidden"> <div className="relative w-full h-full flex flex-col overflow-hidden">
<div className="border-b border-custom-border-300 flex-shrink-0 w-full h-[50px] relative flex items-center gap-2 pr-3 whitespace-nowrap"> <div className="border-b border-custom-border-300 flex-shrink-0 w-full h-[50px] relative flex items-center gap-2 pr-3 whitespace-nowrap">
{tabNavigationOptions.map((option) => ( {tabNavigationOptions.map((option) => (
@ -109,6 +110,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
> >
{inboxIssuesArray.length > 0 ? ( {inboxIssuesArray.length > 0 ? (
<InboxIssueList <InboxIssueList
setToggleMobileSidebar={setToggleMobileSidebar}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
projectIdentifier={currentProjectDetails?.identifier} projectIdentifier={currentProjectDetails?.identifier}
@ -121,8 +123,8 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
getAppliedFiltersCount > 0 getAppliedFiltersCount > 0
? EmptyStateType.INBOX_SIDEBAR_FILTER_EMPTY_STATE ? EmptyStateType.INBOX_SIDEBAR_FILTER_EMPTY_STATE
: currentTab === EInboxIssueCurrentTab.OPEN : currentTab === EInboxIssueCurrentTab.OPEN
? EmptyStateType.INBOX_SIDEBAR_OPEN_TAB ? EmptyStateType.INBOX_SIDEBAR_OPEN_TAB
: EmptyStateType.INBOX_SIDEBAR_CLOSED_TAB : EmptyStateType.INBOX_SIDEBAR_CLOSED_TAB
} }
layout="screen-simple" layout="screen-simple"
/> />

View File

@ -73,7 +73,7 @@ export const FiltersDropdown: React.FC<Props> = (props) => {
style={styles.popper} style={styles.popper}
{...attributes.popper} {...attributes.popper}
> >
<div className="flex max-h-[37.5rem] w-[18.75rem] flex-col overflow-hidden">{children}</div> <div className="flex max-h-[30rem] lg:max-h-[37.5rem] w-[18.75rem] flex-col overflow-hidden">{children}</div>
</div> </div>
</Popover.Panel> </Popover.Panel>
</Transition> </Transition>