[WEB-1920] dev: app sidebar revamp (#5150)
* chore: user activity icon added * dev: sidebar navigation component added * chore: dashboard constant file updated * chore: unread notification indicator position * chore: app sidebar project section * chore: app sidebar User and Workspace section updated * chore: notification to inbox transition * chore: code refactor * chore: code refactor
This commit is contained in:
parent
996192b9bf
commit
a7ecfade98
13 changed files with 187 additions and 167 deletions
|
|
@ -6,7 +6,7 @@ import Link from "next/link";
|
|||
import { useParams } from "next/navigation";
|
||||
import { usePopper } from "react-popper";
|
||||
// icons
|
||||
import { Activity, Check, ChevronDown, LogOut, Mails, PlusSquare, Settings } from "lucide-react";
|
||||
import { Check, ChevronDown, LogOut, Mails, PlusSquare, Settings } from "lucide-react";
|
||||
// ui
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// types
|
||||
|
|
@ -34,19 +34,6 @@ const userLinks = (workspaceSlug: string) => [
|
|||
},
|
||||
];
|
||||
|
||||
const profileLinks = (workspaceSlug: string, userId: string) => [
|
||||
{
|
||||
name: "My activity",
|
||||
icon: Activity,
|
||||
link: `/${workspaceSlug}/profile/${userId}`,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
icon: Settings,
|
||||
link: "/profile",
|
||||
},
|
||||
];
|
||||
|
||||
export const SidebarDropdown = observer(() => {
|
||||
// router params
|
||||
const { workspaceSlug } = useParams();
|
||||
|
|
@ -285,22 +272,14 @@ export const SidebarDropdown = observer(() => {
|
|||
>
|
||||
<div className="flex flex-col gap-2.5 pb-2">
|
||||
<span className="px-2 text-custom-sidebar-text-200">{currentUser?.email}</span>
|
||||
{profileLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => (
|
||||
<Link
|
||||
key={index}
|
||||
href={link.link}
|
||||
onClick={() => {
|
||||
if (index == 0) handleItemClick();
|
||||
}}
|
||||
>
|
||||
<Menu.Item key={index} as="div">
|
||||
<span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
|
||||
<link.icon className="h-4 w-4 stroke-[1.5]" />
|
||||
{link.name}
|
||||
</span>
|
||||
</Menu.Item>
|
||||
</Link>
|
||||
))}
|
||||
<Link href="/profile">
|
||||
<Menu.Item as="div">
|
||||
<span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
|
||||
<Settings className="h-4 w-4 stroke-[1.5]" />
|
||||
<span>Settings</span>
|
||||
</span>
|
||||
</Menu.Item>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={`pt-2 ${isUserInstanceAdmin || false ? "pb-2" : ""}`}>
|
||||
<Menu.Item
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import {
|
|||
// components
|
||||
import { Logo } from "@/components/common";
|
||||
import { LeaveProjectModal, PublishProjectModal } from "@/components/project";
|
||||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
// helpers
|
||||
|
|
@ -95,11 +96,6 @@ const navigation = (workspaceSlug: string, projectId: string) => [
|
|||
href: `/${workspaceSlug}/projects/${projectId}/inbox`,
|
||||
Icon: Intake,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings`,
|
||||
Icon: Settings,
|
||||
},
|
||||
];
|
||||
|
||||
export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -467,7 +463,7 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
leaveFrom="transform scale-100 opacity-100"
|
||||
leaveTo="transform scale-95 opacity-0"
|
||||
>
|
||||
<Disclosure.Panel as="div" className="mt-1 space-y-1">
|
||||
<Disclosure.Panel as="div" className="flex flex-col gap-0.5 mt-1">
|
||||
{navigation(workspaceSlug?.toString(), project?.id).map((item) => {
|
||||
if (
|
||||
(item.name === "Cycles" && !project.cycle_view) ||
|
||||
|
|
@ -479,31 +475,29 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
return;
|
||||
|
||||
return (
|
||||
<Link key={item.name} href={item.href} onClick={handleProjectClick}>
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`${project?.name}: ${item.name}`}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!isSidebarCollapsed}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-md pl-[18px] pr-2 py-1.5 outline-none text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-90 focus:bg-custom-sidebar-background-90",
|
||||
{
|
||||
"text-custom-primary-100 bg-custom-primary-100/10 hover:bg-custom-primary-100/10":
|
||||
pathname.includes(item.href),
|
||||
"p-0 size-7 justify-center mx-auto": isSidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
<Tooltip
|
||||
key={item.name}
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`${project?.name}: ${item.name}`}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!isSidebarCollapsed}
|
||||
>
|
||||
<Link key={item.name} href={item.href} onClick={handleProjectClick}>
|
||||
<SidebarNavItem
|
||||
key={item.name}
|
||||
className={`pl-[18px] ${isSidebarCollapsed ? "p-0 size-7 justify-center mx-auto" : ""}`}
|
||||
isActive={pathname.includes(item.href)}
|
||||
>
|
||||
<item.Icon
|
||||
className={`flex-shrink-0 size-4 ${item.name === "Intake" ? "stroke-1" : "stroke-[1.5]"}`}
|
||||
/>
|
||||
{!isSidebarCollapsed && <span className="text-xs font-medium">{item.name}</span>}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1.5 py-[1px]">
|
||||
<item.Icon
|
||||
className={`flex-shrink-0 size-4 ${item.name === "Intake" ? "stroke-1" : "stroke-[1.5]"}`}
|
||||
/>
|
||||
{!isSidebarCollapsed && <span className="text-xs font-medium">{item.name}</span>}
|
||||
</div>
|
||||
</SidebarNavItem>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Disclosure.Panel>
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
{
|
||||
key: "favorite",
|
||||
type: "FAVORITES",
|
||||
title: "Favorites",
|
||||
title: "FAVORITES",
|
||||
icon: Star,
|
||||
projects: favoriteProjects,
|
||||
isOpen: isFavoriteProjectsListOpen,
|
||||
|
|
@ -161,7 +161,7 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
{
|
||||
key: "all",
|
||||
type: "JOINED",
|
||||
title: "My projects",
|
||||
title: "MY PROJECTS",
|
||||
icon: Briefcase,
|
||||
projects: joinedProjects,
|
||||
isOpen: isAllProjectsListOpen,
|
||||
|
|
@ -217,7 +217,13 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
position="right"
|
||||
disabled={!isCollapsed}
|
||||
>
|
||||
<span>{isCollapsed ? <section.icon className="flex-shrink-0 size-3" /> : section.title}</span>
|
||||
<>
|
||||
{isCollapsed ? (
|
||||
<section.icon className="flex-shrink-0 size-3" />
|
||||
) : (
|
||||
<span className="text-xs font-semibold">{section.title}</span>
|
||||
)}
|
||||
</>
|
||||
</Tooltip>
|
||||
</Disclosure.Button>
|
||||
{!isCollapsed && (
|
||||
|
|
@ -264,7 +270,7 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
{section.isOpen && (
|
||||
<Disclosure.Panel
|
||||
as="div"
|
||||
className={cn("mt-2 ml-1 space-y-1", {
|
||||
className={cn("space-y-1", {
|
||||
"space-y-0 ml-0": isCollapsed,
|
||||
})}
|
||||
static
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import React from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
import { NotificationAppSidebarOption } from "@/components/workspace-notifications";
|
||||
// constants
|
||||
import { SIDEBAR_USER_MENU_ITEMS } from "@/constants/dashboard";
|
||||
|
|
@ -25,6 +25,7 @@ export const SidebarUserMenu = observer(() => {
|
|||
const { isMobile } = usePlatformOS();
|
||||
const {
|
||||
membership: { currentWorkspaceRole },
|
||||
data: currentUser,
|
||||
} = useUser();
|
||||
// router params
|
||||
const { workspaceSlug } = useParams();
|
||||
|
|
@ -42,48 +43,47 @@ export const SidebarUserMenu = observer(() => {
|
|||
});
|
||||
};
|
||||
|
||||
const notificationIndicatorElement = (
|
||||
<NotificationAppSidebarOption
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
isSidebarCollapsed={sidebarCollapsed ?? false}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("w-full space-y-1", {
|
||||
className={cn("flex flex-col gap-0.5", {
|
||||
"space-y-0": sidebarCollapsed,
|
||||
})}
|
||||
>
|
||||
{SIDEBAR_USER_MENU_ITEMS.map(
|
||||
(link) =>
|
||||
workspaceMemberInfo >= link.access && (
|
||||
<Link key={link.key} href={`/${workspaceSlug}${link.href}`} onClick={() => handleLinkClick(link.key)}>
|
||||
<Tooltip
|
||||
tooltipContent={link.label}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!sidebarCollapsed}
|
||||
isMobile={isMobile}
|
||||
<Tooltip
|
||||
key={link.key}
|
||||
tooltipContent={link.label}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!sidebarCollapsed}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
<Link
|
||||
href={`/${workspaceSlug}${link.href}${link.key === "my-work" ? `/${currentUser?.id}` : ""}`}
|
||||
onClick={() => handleLinkClick(link.key)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"relative group w-full flex items-center gap-1.5 rounded-md px-2 py-1.5 outline-none text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-90 focus:bg-custom-sidebar-background-90",
|
||||
{
|
||||
"text-custom-primary-100 bg-custom-primary-100/10 hover:bg-custom-primary-100/10": link.highlight(
|
||||
pathname,
|
||||
`/${workspaceSlug}`
|
||||
),
|
||||
"p-0 size-8 aspect-square justify-center mx-auto": sidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
<SidebarNavItem
|
||||
key={link.key}
|
||||
className={`${sidebarCollapsed ? "p-0 size-8 aspect-square justify-center mx-auto" : ""}`}
|
||||
isActive={link.highlight(pathname, `/${workspaceSlug}`)}
|
||||
>
|
||||
<span className="flex-shrink-0 size-4 grid place-items-center">
|
||||
<link.Icon className="size-4" />
|
||||
</span>
|
||||
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{link.label}</p>}
|
||||
{link.key === "notifications" && (
|
||||
<NotificationAppSidebarOption
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
isSidebarCollapsed={sidebarCollapsed ?? false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1.5 py-[1px]">
|
||||
<link.Icon className="size-4 flex-shrink-0" />
|
||||
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{link.label}</p>}
|
||||
</div>
|
||||
{link.key === "notifications" && notificationIndicatorElement}
|
||||
</SidebarNavItem>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { ChevronRight } from "lucide-react";
|
|||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
// constants
|
||||
import { SIDEBAR_WORKSPACE_MENU_ITEMS } from "@/constants/dashboard";
|
||||
import { SIDEBAR_CLICKED } from "@/constants/event-tracker";
|
||||
|
|
@ -53,15 +55,21 @@ export const SidebarWorkspaceMenu = observer(() => {
|
|||
if (sidebarCollapsed) toggleWorkspaceMenu(true);
|
||||
}, [sidebarCollapsed, toggleWorkspaceMenu]);
|
||||
|
||||
const indicatorElement = (
|
||||
<div className="flex-shrink-0">
|
||||
<UpgradeBadge />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Disclosure as="div" defaultOpen>
|
||||
{!sidebarCollapsed && (
|
||||
<Disclosure.Button
|
||||
as="button"
|
||||
className="group/workspace-button w-full px-2 py-1.5 flex items-center justify-between gap-1 text-custom-sidebar-text-400 hover:bg-custom-sidebar-background-90 rounded text-sm font-semibold"
|
||||
className="group/workspace-button w-full px-2 py-1.5 flex items-center justify-between gap-1 text-custom-sidebar-text-400 hover:bg-custom-sidebar-background-90 rounded text-xs font-semibold"
|
||||
onClick={() => toggleWorkspaceMenu(!isWorkspaceMenuOpen)}
|
||||
>
|
||||
<span>Workspace</span>
|
||||
<span>WORKSPACE</span>
|
||||
<span className="flex-shrink-0 opacity-0 pointer-events-none group-hover/workspace-button:opacity-100 group-hover/workspace-button:pointer-events-auto rounded p-0.5 hover:bg-custom-sidebar-background-80">
|
||||
<ChevronRight
|
||||
className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform", {
|
||||
|
|
@ -83,7 +91,7 @@ export const SidebarWorkspaceMenu = observer(() => {
|
|||
{isWorkspaceMenuOpen && (
|
||||
<Disclosure.Panel
|
||||
as="div"
|
||||
className={cn("mt-2 ml-1 space-y-1", {
|
||||
className={cn("flex flex-col mt-0.5 gap-0.5", {
|
||||
"space-y-0 mt-0 ml-0": sidebarCollapsed,
|
||||
})}
|
||||
static
|
||||
|
|
@ -91,47 +99,32 @@ export const SidebarWorkspaceMenu = observer(() => {
|
|||
{SIDEBAR_WORKSPACE_MENU_ITEMS.map(
|
||||
(link) =>
|
||||
workspaceMemberInfo >= link.access && (
|
||||
<Link
|
||||
<Tooltip
|
||||
key={link.key}
|
||||
href={`/${workspaceSlug}${link.href}`}
|
||||
onClick={() => handleLinkClick(link.key)}
|
||||
className="block"
|
||||
tooltipContent={link.label}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!sidebarCollapsed}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
<Tooltip
|
||||
tooltipContent={link.label}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!sidebarCollapsed}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"group w-full flex items-center gap-1.5 rounded-md px-2 py-1.5 outline-none text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-90 focus:bg-custom-sidebar-background-90",
|
||||
{
|
||||
"text-custom-primary-100 bg-custom-primary-100/10 hover:bg-custom-primary-100/10":
|
||||
link.highlight(pathname, `/${workspaceSlug}`),
|
||||
"p-0 size-8 aspect-square justify-center mx-auto": sidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
<Link href={`/${workspaceSlug}${link.href}`} onClick={() => handleLinkClick(link.key)}>
|
||||
<SidebarNavItem
|
||||
key={link.key}
|
||||
className={`${sidebarCollapsed ? "p-0 size-8 aspect-square justify-center mx-auto" : ""}`}
|
||||
isActive={link.highlight(pathname, `/${workspaceSlug}`)}
|
||||
>
|
||||
<div className={cn("grow flex items-center gap-1.5", { "justify-center": sidebarCollapsed })}>
|
||||
<span className="flex-shrink-0 size-4 grid place-items-center">
|
||||
<link.Icon
|
||||
className={cn("size-4", {
|
||||
"rotate-180": link.key === "active-cycles",
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5 py-[1px]">
|
||||
<link.Icon
|
||||
className={cn("size-4", {
|
||||
"rotate-180": link.key === "active-cycles",
|
||||
})}
|
||||
/>
|
||||
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{link.label}</p>}
|
||||
</div>
|
||||
{!sidebarCollapsed && link.key === "active-cycles" && (
|
||||
<div className="flex-shrink-0">
|
||||
<UpgradeBadge />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
{!sidebarCollapsed && link.key === "active-cycles" && indicatorElement}
|
||||
</SidebarNavItem>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)
|
||||
)}
|
||||
</Disclosure.Panel>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue