style: sidebar projects design (#1736)

* chore: disclosure menu for sidebar projects

* fix: projects list spacing

* style: new design
This commit is contained in:
Aaryan Khandelwal 2023-08-01 12:24:34 +05:30 committed by GitHub
parent df8504e6f7
commit cb4d294608
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 187 additions and 91 deletions

View File

@ -5,6 +5,8 @@ import { mutate } from "swr";
// react-beautiful-dnd
import { DragDropContext, Draggable, DropResult, Droppable } from "react-beautiful-dnd";
// headless ui
import { Disclosure } from "@headlessui/react";
// hooks
import useToast from "hooks/use-toast";
import useTheme from "hooks/use-theme";
@ -15,6 +17,7 @@ import { DeleteProjectModal, SingleSidebarProject } from "components/project";
// services
import projectService from "services/project.service";
// icons
import { Icon } from "components/ui";
import { PlusIcon } from "@heroicons/react/24/outline";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
@ -30,7 +33,7 @@ export const ProjectSidebarList: FC = () => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug, projectId } = router.query;
const { user } = useUserAuth();
@ -38,15 +41,18 @@ export const ProjectSidebarList: FC = () => {
const { setToastAlert } = useToast();
const { projects: allProjects } = useProjects();
const joinedProjects = allProjects?.filter((p) => p.sort_order);
const favoriteProjects = allProjects?.filter((p) => p.is_favorite);
const otherProjects = allProjects?.filter((p) => p.sort_order === null);
const orderedAllProjects = allProjects
? orderArrayBy(allProjects, "sort_order", "ascending")
: [];
const orderedJoinedProjects: IProject[] | undefined = joinedProjects
? orderArrayBy(joinedProjects, "sort_order", "ascending")
: undefined;
const orderedFavProjects = favoriteProjects
const orderedFavProjects: IProject[] | undefined = favoriteProjects
? orderArrayBy(favoriteProjects, "sort_order", "ascending")
: [];
: undefined;
const handleDeleteProject = (project: IProject) => {
setProjectToDelete(project);
@ -69,22 +75,25 @@ export const ProjectSidebarList: FC = () => {
const { source, destination, draggableId } = result;
if (!destination || !workspaceSlug) return;
if (source.index === destination.index) return;
const projectList =
destination.droppableId === "all-projects" ? orderedAllProjects : orderedFavProjects;
const projectsList =
(destination.droppableId === "joined-projects"
? orderedJoinedProjects
: orderedFavProjects) ?? [];
let updatedSortOrder = projectList[source.index].sort_order;
if (destination.index === 0) {
updatedSortOrder = projectList[0].sort_order - 1000;
} else if (destination.index === projectList.length - 1) {
updatedSortOrder = projectList[projectList.length - 1].sort_order + 1000;
} else {
const destinationSortingOrder = projectList[destination.index].sort_order;
let updatedSortOrder = projectsList[source.index].sort_order;
if (destination.index === 0) updatedSortOrder = (projectsList[0].sort_order as number) - 1000;
else if (destination.index === projectsList.length - 1)
updatedSortOrder = (projectsList[projectsList.length - 1].sort_order as number) + 1000;
else {
const destinationSortingOrder = projectsList[destination.index].sort_order as number;
const relativeDestinationSortingOrder =
source.index < destination.index
? projectList[destination.index + 1].sort_order
: projectList[destination.index - 1].sort_order;
? (projectsList[destination.index + 1].sort_order as number)
: (projectsList[destination.index - 1].sort_order as number);
updatedSortOrder = Math.round(
(destinationSortingOrder + relativeDestinationSortingOrder) / 2
@ -121,76 +130,162 @@ export const ProjectSidebarList: FC = () => {
data={projectToDelete}
user={user}
/>
<div className="h-full overflow-y-auto px-4">
<div className="h-full overflow-y-auto px-5 space-y-3 pt-3 border-t border-custom-sidebar-border-300">
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="favorite-projects">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{orderedFavProjects && orderedFavProjects.length > 0 && (
<div className="flex flex-col space-y-2 mt-5">
{!sidebarCollapse && (
<h5 className="text-sm font-medium text-custom-sidebar-text-200">
Favorites
</h5>
)}
{orderedFavProjects.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={sidebarCollapse}
provided={provided}
snapshot={snapshot}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
shortContextMenu
<Disclosure
as="div"
className="flex flex-col space-y-2"
defaultOpen={
projectId && orderedFavProjects.find((p) => p.id === projectId) ? true : false
}
>
{({ open }) => (
<>
{!sidebarCollapse && (
<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"
>
Favorites
<Icon
iconName={open ? "arrow_drop_down" : "arrow_right"}
className="group-hover:opacity-100 opacity-0 !text-lg"
/>
</div>
</Disclosure.Button>
)}
</Draggable>
))}
{provided.placeholder}
</div>
<Disclosure.Panel as="div" className="space-y-2">
{orderedFavProjects.map((project, index) => (
<Draggable
key={project.id}
draggableId={project.id}
index={index}
isDragDisabled={project.sort_order === null}
>
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps}>
<SingleSidebarProject
key={project.id}
project={project}
sidebarCollapse={sidebarCollapse}
provided={provided}
snapshot={snapshot}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
shortContextMenu
/>
</div>
)}
</Draggable>
))}
</Disclosure.Panel>
{provided.placeholder}
</>
)}
</Disclosure>
)}
</div>
)}
</Droppable>
</DragDropContext>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="all-projects">
<Droppable droppableId="joined-projects">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{orderedAllProjects && orderedAllProjects.length > 0 && (
<div className="flex flex-col space-y-2 mt-5">
{!sidebarCollapse && (
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Projects</h5>
)}
{orderedAllProjects.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={sidebarCollapse}
provided={provided}
snapshot={snapshot}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
{orderedJoinedProjects && orderedJoinedProjects.length > 0 && (
<Disclosure
as="div"
className="flex flex-col space-y-2"
defaultOpen={
projectId && orderedJoinedProjects.find((p) => p.id === projectId)
? true
: false
}
>
{({ open }) => (
<>
{!sidebarCollapse && (
<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"
/>
</div>
</Disclosure.Button>
)}
</Draggable>
))}
{provided.placeholder}
</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={sidebarCollapse}
provided={provided}
snapshot={snapshot}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
/>
</div>
)}
</Draggable>
))}
</Disclosure.Panel>
{provided.placeholder}
</>
)}
</Disclosure>
)}
</div>
)}
</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 }) => (
<>
{!sidebarCollapse && (
<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={sidebarCollapse}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
shortContextMenu
/>
))}
</Disclosure.Panel>
</>
)}
</Disclosure>
)}
{allProjects && allProjects.length === 0 && (
<button
type="button"

View File

@ -36,8 +36,8 @@ import { PROJECTS_LIST } from "constants/fetch-keys";
type Props = {
project: IProject;
sidebarCollapse: boolean;
provided: DraggableProvided;
snapshot: DraggableStateSnapshot;
provided?: DraggableProvided;
snapshot?: DraggableStateSnapshot;
handleDeleteProject: () => void;
handleCopyText: () => void;
shortContextMenu?: boolean;
@ -133,34 +133,36 @@ export const SingleSidebarProject: React.FC<Props> = ({
};
return (
<Disclosure key={project?.id} defaultOpen={projectId === project?.id}>
<Disclosure key={project.id} defaultOpen={projectId === project.id}>
{({ open }) => (
<>
<div
className={`group relative flex items-center gap-x-1 text-custom-sidebar-text-100 ${
snapshot.isDragging ? "opacity-60" : ""
className={`group relative text-custom-sidebar-text-10 px-2 py-1 w-full flex items-center hover:bg-custom-sidebar-background-80 rounded-md ${
snapshot?.isDragging ? "opacity-60" : ""
}`}
>
<button
type="button"
className={`absolute top-2 left-0 hidden rounded p-0.5 ${
sidebarCollapse ? "" : "group-hover:!flex"
}`}
{...provided.dragHandleProps}
>
<EllipsisVerticalIcon className="h-4" />
<EllipsisVerticalIcon className="-ml-5 h-4" />
</button>
{provided && (
<button
type="button"
className={`absolute top-1/2 -translate-y-1/2 -left-4 hidden rounded p-0.5 ${
sidebarCollapse ? "" : "group-hover:!flex"
}`}
{...provided?.dragHandleProps}
>
<EllipsisVerticalIcon className="h-4" />
<EllipsisVerticalIcon className="-ml-5 h-4" />
</button>
)}
<Tooltip
tooltipContent={`${project?.name}`}
tooltipContent={`${project.name}`}
position="right"
className="ml-2"
disabled={!sidebarCollapse}
>
<Disclosure.Button
as="div"
className={`flex w-full cursor-pointer select-none items-center rounded-sm py-1 text-left text-sm font-medium ${
sidebarCollapse ? "justify-center" : "justify-between ml-4"
className={`flex items-center w-full cursor-pointer select-none text-left text-sm font-medium ${
sidebarCollapse ? "justify-center" : `justify-between`
}`}
>
<div className="flex items-center gap-x-2">
@ -184,21 +186,23 @@ export const SingleSidebarProject: React.FC<Props> = ({
open ? "" : "text-custom-sidebar-text-200"
}`}
>
{truncateText(project?.name, 14)}
{truncateText(project.name, 15)}
</p>
)}
</div>
{!sidebarCollapse && (
<ExpandMoreOutlined
fontSize="small"
className={`${open ? "rotate-180" : ""} text-custom-text-200 duration-300`}
className={`${
open ? "rotate-180" : ""
} !hidden group-hover:!block text-custom-sidebar-text-200 duration-300`}
/>
)}
</Disclosure.Button>
</Tooltip>
{!sidebarCollapse && (
<CustomMenu ellipsis>
<CustomMenu className="hidden group-hover:block" ellipsis>
{!shortContextMenu && (
<CustomMenu.MenuItem onClick={handleDeleteProject}>
<span className="flex items-center justify-start gap-2 ">

View File

@ -47,7 +47,7 @@ export const WorkspaceSidebarMenu = () => {
const { collapsed: sidebarCollapse } = useTheme();
return (
<div className="w-full cursor-pointer space-y-2 px-4 mt-5">
<div className="w-full cursor-pointer space-y-2 px-4 mt-5 pb-5">
{workspaceLinks(workspaceSlug as string).map((link, index) => {
const isActive =
link.name === "Settings"

View File

@ -58,8 +58,6 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un
: null
);
console.log(memberRole);
const groupedIssues:
| {
[key: string]: IIssue[];

View File

@ -18,7 +18,6 @@ export default function useUser({ redirectTo = "", redirectIfFound = false, opti
);
const user = error ? undefined : data;
// console.log("useUser", user);
useEffect(() => {
// if no redirect needed, just return (example: already on /dashboard)

View File

@ -45,7 +45,7 @@ export interface IProject {
network: number;
page_view: boolean;
project_lead: IUserLite | string | null;
sort_order: number;
sort_order: number | null;
slug: string;
total_cycles: number;
total_members: number;