mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
style: sidebar ui improvement (#1816)
* style: sidebar ui improvement * style: sidebar menu spacing * style: sidebar consistent spacing * style: notification improvement * style: remove from favorite filled icon added * chore: settings option added in sidebar context menu * chore: update delete project option visibility for sidebar * style: sidebar project list display border top on scroll
This commit is contained in:
parent
5c964d144a
commit
dbb53a663e
@ -84,7 +84,7 @@ export const NotificationPopover = () => {
|
||||
disabled={!store?.theme?.sidebarCollapsed}
|
||||
>
|
||||
<Popover.Button
|
||||
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||
className={`relative group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||
isActive
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
|
||||
@ -93,9 +93,13 @@ export const NotificationPopover = () => {
|
||||
<NotificationsOutlined fontSize="small" />
|
||||
{store?.theme?.sidebarCollapsed ? null : <span>Notifications</span>}
|
||||
{totalNotificationCount && totalNotificationCount > 0 ? (
|
||||
<span className="ml-auto bg-custom-primary-300 rounded-full text-xs text-white px-1.5">
|
||||
{getNumberCount(totalNotificationCount)}
|
||||
</span>
|
||||
store?.theme?.sidebarCollapsed ? (
|
||||
<span className="absolute right-3.5 top-2 h-2 w-2 bg-custom-primary-300 rounded-full" />
|
||||
) : (
|
||||
<span className="ml-auto bg-custom-primary-300 rounded-full text-xs text-white px-1.5">
|
||||
{getNumberCount(totalNotificationCount)}
|
||||
</span>
|
||||
)
|
||||
) : null}
|
||||
</Popover.Button>
|
||||
</Tooltip>
|
||||
@ -108,7 +112,7 @@ export const NotificationPopover = () => {
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Popover.Panel className="absolute bg-custom-background-100 flex flex-col left-0 md:left-full ml-8 z-10 top-0 md:w-[36rem] w-[20rem] h-[50vh] border border-custom-border-300 shadow-lg rounded-xl">
|
||||
<Popover.Panel className="absolute bg-custom-background-100 flex flex-col left-0 md:left-full ml-8 z-10 -top-44 md:w-[36rem] w-[20rem] h-[75vh] border border-custom-border-300 shadow-lg rounded-xl">
|
||||
<NotificationHeader
|
||||
notificationCount={notificationCount}
|
||||
notificationMutate={notificationMutate}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, FC } from "react";
|
||||
import React, { useState, FC, useRef, useEffect } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { mutate } from "swr";
|
||||
@ -6,7 +6,7 @@ import { mutate } from "swr";
|
||||
// react-beautiful-dnd
|
||||
import { DragDropContext, Draggable, DropResult, Droppable } from "react-beautiful-dnd";
|
||||
// headless ui
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useTheme from "hooks/use-theme";
|
||||
@ -36,6 +36,10 @@ export const ProjectSidebarList: FC = () => {
|
||||
const [projectToDelete, setProjectToDelete] = useState<IProject | null>(null);
|
||||
|
||||
// router
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
@ -126,6 +130,27 @@ export const ProjectSidebarList: FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
if (containerRef.current) {
|
||||
const scrollTop = containerRef.current.scrollTop;
|
||||
setIsScrolled(scrollTop > 0);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const currentContainerRef = containerRef.current;
|
||||
|
||||
if (currentContainerRef) {
|
||||
currentContainerRef.addEventListener("scroll", handleScroll);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (currentContainerRef) {
|
||||
currentContainerRef.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteProjectModal
|
||||
@ -134,7 +159,12 @@ export const ProjectSidebarList: FC = () => {
|
||||
data={projectToDelete}
|
||||
user={user}
|
||||
/>
|
||||
<div className="h-full overflow-y-auto px-4 space-y-3 pt-3 border-t border-custom-sidebar-border-300">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`h-full overflow-y-auto px-4 space-y-3 pt-3 ${
|
||||
isScrolled ? "border-t border-custom-sidebar-border-300" : ""
|
||||
}`}
|
||||
>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="favorite-projects">
|
||||
{(provided) => (
|
||||
@ -147,7 +177,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
<Disclosure.Button
|
||||
as="button"
|
||||
type="button"
|
||||
className="group flex items-center gap-1 px-1.5 text-xs font-semibold text-custom-sidebar-text-200 text-left hover:bg-custom-sidebar-background-80 rounded w-min whitespace-nowrap"
|
||||
className="group flex items-center gap-1 px-1.5 text-xs font-semibold text-custom-sidebar-text-400 text-left hover:bg-custom-sidebar-background-80 rounded w-full whitespace-nowrap"
|
||||
>
|
||||
Favorites
|
||||
<Icon
|
||||
@ -199,37 +229,57 @@ export const ProjectSidebarList: FC = () => {
|
||||
{({ open }) => (
|
||||
<>
|
||||
{!store?.theme?.sidebarCollapsed && (
|
||||
<Disclosure.Button
|
||||
as="button"
|
||||
type="button"
|
||||
className="group flex items-center gap-1 px-1.5 text-xs font-semibold text-custom-sidebar-text-200 text-left hover:bg-custom-sidebar-background-80 rounded w-min whitespace-nowrap"
|
||||
>
|
||||
Projects
|
||||
<Icon
|
||||
iconName={open ? "arrow_drop_down" : "arrow_right"}
|
||||
className="group-hover:opacity-100 opacity-0 !text-lg"
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
<div className="group flex justify-between items-center text-xs px-1.5 rounded text-custom-sidebar-text-400 hover:bg-custom-sidebar-background-80 w-full">
|
||||
<Disclosure.Button
|
||||
as="button"
|
||||
type="button"
|
||||
className="flex items-center gap-1 font-semibold text-left whitespace-nowrap"
|
||||
>
|
||||
Projects
|
||||
<Icon
|
||||
iconName={open ? "arrow_drop_down" : "arrow_right"}
|
||||
className="group-hover:opacity-100 opacity-0 !text-lg"
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
<button
|
||||
className="group-hover:opacity-100 opacity-0"
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "p" });
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="add" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<Disclosure.Panel as="div" className="space-y-2">
|
||||
{orderedJoinedProjects.map((project, index) => (
|
||||
<Draggable key={project.id} draggableId={project.id} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div ref={provided.innerRef} {...provided.draggableProps}>
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={store?.theme?.sidebarCollapsed}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
handleCopyText={() => handleCopyText(project.id)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
</Disclosure.Panel>
|
||||
<Transition
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom="transform scale-95 opacity-0"
|
||||
enterTo="transform scale-100 opacity-100"
|
||||
leave="transition duration-75 ease-out"
|
||||
leaveFrom="transform scale-100 opacity-100"
|
||||
leaveTo="transform scale-95 opacity-0"
|
||||
>
|
||||
<Disclosure.Panel as="div" className="space-y-2">
|
||||
{orderedJoinedProjects.map((project, index) => (
|
||||
<Draggable key={project.id} draggableId={project.id} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div ref={provided.innerRef} {...provided.draggableProps}>
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={store?.theme?.sidebarCollapsed}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
handleCopyText={() => handleCopyText(project.id)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
{provided.placeholder}
|
||||
</>
|
||||
)}
|
||||
@ -239,43 +289,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
{otherProjects && otherProjects.length > 0 && (
|
||||
<Disclosure
|
||||
as="div"
|
||||
className="flex flex-col space-y-2"
|
||||
defaultOpen={projectId && otherProjects.find((p) => p.id === projectId) ? true : false}
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
{!store?.theme?.sidebarCollapsed && (
|
||||
<Disclosure.Button
|
||||
as="button"
|
||||
type="button"
|
||||
className="group flex items-center gap-1 px-1.5 text-xs font-semibold text-custom-sidebar-text-200 text-left hover:bg-custom-sidebar-background-80 rounded w-min whitespace-nowrap"
|
||||
>
|
||||
Other Projects
|
||||
<Icon
|
||||
iconName={open ? "arrow_drop_down" : "arrow_right"}
|
||||
className="group-hover:opacity-100 opacity-0 !text-lg"
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
)}
|
||||
<Disclosure.Panel as="div" className="space-y-2">
|
||||
{otherProjects?.map((project, index) => (
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={store?.theme?.sidebarCollapsed}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
handleCopyText={() => handleCopyText(project.id)}
|
||||
shortContextMenu
|
||||
/>
|
||||
))}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)}
|
||||
|
||||
{allProjects && allProjects.length === 0 && (
|
||||
<button
|
||||
type="button"
|
||||
|
@ -210,7 +210,7 @@ export const SingleProjectCard: React.FC<ProjectCardProps> = ({
|
||||
{project.is_favorite ? (
|
||||
<CustomMenu.MenuItem onClick={handleRemoveFromFavorites}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<StarIcon className="h-4 w-4" />
|
||||
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
||||
<span>Remove from favorites</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
|
@ -12,7 +12,7 @@ import projectService from "services/project.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { CustomMenu, Tooltip } from "components/ui";
|
||||
import { CustomMenu, Icon, Tooltip } from "components/ui";
|
||||
// icons
|
||||
import { EllipsisVerticalIcon, LinkIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
@ -90,6 +90,8 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const isAdmin = project.member_role === 20;
|
||||
|
||||
const handleAddToFavorites = () => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
@ -152,7 +154,7 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`absolute top-1/2 -translate-y-1/2 -left-4 hidden rounded p-0.5 ${
|
||||
className={`absolute top-1/2 -translate-y-1/2 -left-4 hidden rounded p-0.5 text-custom-sidebar-text-400 ${
|
||||
sidebarCollapse ? "" : "group-hover:!flex"
|
||||
} ${project.sort_order === null ? "opacity-60 cursor-not-allowed" : ""}`}
|
||||
{...provided?.dragHandleProps}
|
||||
@ -204,15 +206,19 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
fontSize="small"
|
||||
className={`flex-shrink-0 ${
|
||||
open ? "rotate-180" : ""
|
||||
} !hidden group-hover:!block text-custom-sidebar-text-200 duration-300`}
|
||||
} !hidden group-hover:!block text-custom-sidebar-text-400 duration-300`}
|
||||
/>
|
||||
)}
|
||||
</Disclosure.Button>
|
||||
</Tooltip>
|
||||
|
||||
{!sidebarCollapse && (
|
||||
<CustomMenu className="hidden group-hover:block flex-shrink-0" ellipsis>
|
||||
{!shortContextMenu && (
|
||||
<CustomMenu
|
||||
className="hidden group-hover:block flex-shrink-0"
|
||||
buttonClassName="!text-custom-sidebar-text-400 hover:text-custom-sidebar-text-400"
|
||||
ellipsis
|
||||
>
|
||||
{!shortContextMenu && isAdmin && (
|
||||
<CustomMenu.MenuItem onClick={handleDeleteProject}>
|
||||
<span className="flex items-center justify-start gap-2 ">
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
@ -231,7 +237,7 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
{project.is_favorite && (
|
||||
<CustomMenu.MenuItem onClick={handleRemoveFromFavorites}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<StarIcon className="h-4 w-4" />
|
||||
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
||||
<span>Remove from favorites</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
@ -254,6 +260,14 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
<CustomMenu.MenuItem
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${project?.id}/settings`)}
|
||||
>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<Icon iconName="settings" className="!text-base !leading-4" />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
)}
|
||||
</div>
|
||||
|
@ -9,3 +9,4 @@ export * from "./issues-stats";
|
||||
export * from "./settings-header";
|
||||
export * from "./sidebar-dropdown";
|
||||
export * from "./sidebar-menu";
|
||||
export * from "./sidebar-quick-action";
|
||||
|
@ -51,7 +51,7 @@ export const WorkspaceSidebarMenu = () => {
|
||||
const { collapsed: sidebarCollapse } = useTheme();
|
||||
|
||||
return (
|
||||
<div className="w-full cursor-pointer space-y-2 px-4 mt-5 pb-5">
|
||||
<div className="w-full cursor-pointer space-y-1 p-4">
|
||||
{workspaceLinks(workspaceSlug as string).map((link, index) => {
|
||||
const isActive =
|
||||
link.name === "Settings"
|
||||
|
47
apps/app/components/workspace/sidebar-quick-action.tsx
Normal file
47
apps/app/components/workspace/sidebar-quick-action.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
|
||||
// ui
|
||||
import { Icon } from "components/ui";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const WorkspaceSidebarQuickAction = () => {
|
||||
const store: any = useMobxStore();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-between w-full cursor-pointer px-4 mt-4 ${
|
||||
store?.theme?.sidebarCollapsed ? "flex-col gap-1" : "gap-2"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className={`flex items-center gap-2 flex-grow rounded flex-shrink-0 py-2 ${
|
||||
store?.theme?.sidebarCollapsed
|
||||
? "px-2 hover:bg-custom-sidebar-background-80"
|
||||
: "px-3 shadow border-[0.5px] border-custom-border-300"
|
||||
}`}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "c" });
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="edit_square" className="!text-xl !leading-5 text-custom-sidebar-text-300" />
|
||||
{!store?.theme?.sidebarCollapsed && <span className="text-sm font-medium">New Issue</span>}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`flex items-center justify-center rounded flex-shrink-0 p-2 ${
|
||||
store?.theme?.sidebarCollapsed
|
||||
? "hover:bg-custom-sidebar-background-80"
|
||||
: "shadow border-[0.5px] border-custom-border-300"
|
||||
}`}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "k", ctrlKey: true, metaKey: true });
|
||||
document.dispatchEvent(e);
|
||||
}}
|
||||
>
|
||||
<Icon iconName="search" className="!text-xl !leading-5 text-custom-sidebar-text-300" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -5,6 +5,7 @@ import {
|
||||
WorkspaceHelpSection,
|
||||
WorkspaceSidebarDropdown,
|
||||
WorkspaceSidebarMenu,
|
||||
WorkspaceSidebarQuickAction,
|
||||
} from "components/workspace";
|
||||
import { ProjectSidebarList } from "components/project";
|
||||
// mobx react lite
|
||||
@ -30,6 +31,7 @@ const Sidebar: React.FC<SidebarProps> = observer(({ toggleSidebar, setToggleSide
|
||||
>
|
||||
<div className="flex h-full w-full flex-1 flex-col">
|
||||
<WorkspaceSidebarDropdown />
|
||||
<WorkspaceSidebarQuickAction />
|
||||
<WorkspaceSidebarMenu />
|
||||
<ProjectSidebarList />
|
||||
<WorkspaceHelpSection setSidebarActive={setToggleSidebar} />
|
||||
|
Loading…
Reference in New Issue
Block a user