[WEB-1580] chore: drag handler and sidebar improvement (#4780)

* chore: sidebar width reduced

* chore: drag handler added to project sidebar and ui improvement

* chore: label drag handler added
This commit is contained in:
Anmol Singh Bhatia 2024-06-12 18:25:57 +05:30 committed by GitHub
parent 64619bf5eb
commit 7677f021a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 64 additions and 27 deletions

View File

@ -32,10 +32,9 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
<div <div
className={`fixed inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 className={`fixed inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100
duration-300 md:relative duration-300 md:relative
${sidebarCollapsed ? "-ml-[280px]" : ""} ${sidebarCollapsed ? "-ml-[250px]" : ""}
sm:${sidebarCollapsed ? "-ml-[280px]" : ""} sm:${sidebarCollapsed ? "-ml-[250px]" : ""}
md:ml-0 ${sidebarCollapsed ? "w-[80px]" : "w-[280px]"} md:ml-0 ${sidebarCollapsed ? "w-[70px]" : "w-[250px]"}
lg:ml-0 ${sidebarCollapsed ? "w-[80px]" : "w-[280px]"}
`} `}
> >
<div ref={ref} className="flex h-full w-full flex-1 flex-col"> <div ref={ref} className="flex h-full w-full flex-1 flex-col">

View File

@ -95,10 +95,9 @@ export const ProfileLayoutSidebar = observer(() => {
return ( return (
<div <div
className={`fixed inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 duration-300 md:relative className={`fixed inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 duration-300 md:relative
${sidebarCollapsed ? "-ml-[280px]" : ""} ${sidebarCollapsed ? "-ml-[250px]" : ""}
sm:${sidebarCollapsed ? "-ml-[280px]" : ""} sm:${sidebarCollapsed ? "-ml-[250px]" : ""}
md:ml-0 ${sidebarCollapsed ? "w-[80px]" : "w-[280px]"} md:ml-0 ${sidebarCollapsed ? "w-[70px]" : "w-[250px]"}
lg:ml-0 ${sidebarCollapsed ? "w-[80px]" : "w-[280px]"}
`} `}
> >
<div ref={ref} className="flex h-full w-full flex-col gap-y-4"> <div ref={ref} className="flex h-full w-full flex-col gap-y-4">

View File

@ -27,10 +27,11 @@ interface ILabelItemBlock {
customMenuItems: ICustomMenuItem[]; customMenuItems: ICustomMenuItem[];
handleLabelDelete: (label: IIssueLabel) => void; handleLabelDelete: (label: IIssueLabel) => void;
isLabelGroup?: boolean; isLabelGroup?: boolean;
dragHandleRef: MutableRefObject<HTMLButtonElement | null>;
} }
export const LabelItemBlock = (props: ILabelItemBlock) => { export const LabelItemBlock = (props: ILabelItemBlock) => {
const { label, customMenuItems, handleLabelDelete, isLabelGroup } = props; const { label, isDragging, customMenuItems, handleLabelDelete, isLabelGroup, dragHandleRef } = props;
// states // states
const [isMenuActive, setIsMenuActive] = useState(false); const [isMenuActive, setIsMenuActive] = useState(false);
// refs // refs
@ -41,6 +42,12 @@ export const LabelItemBlock = (props: ILabelItemBlock) => {
return ( return (
<div className="group flex items-center"> <div className="group flex items-center">
<div className="flex items-center"> <div className="flex items-center">
<DragHandle
className={cn("opacity-0 group-hover:opacity-100", {
"opacity-100": isDragging,
})}
ref={dragHandleRef}
/>
<LabelName color={label.color} name={label.name} isGroup={isLabelGroup ?? false} /> <LabelName color={label.color} name={label.name} isGroup={isLabelGroup ?? false} />
</div> </div>

View File

@ -10,7 +10,7 @@ export const LabelName = (props: ILabelName) => {
const { name, color, isGroup } = props; const { name, color, isGroup } = props;
return ( return (
<div className="flex items-center gap-3 pl-2"> <div className="flex items-center gap-3">
{isGroup ? ( {isGroup ? (
<Component className="h-3.5 w-3.5" color={color} /> <Component className="h-3.5 w-3.5" color={color} />
) : ( ) : (

View File

@ -55,13 +55,13 @@ export const ProjectSettingLabelGroup: React.FC<Props> = observer((props) => {
return ( return (
<LabelDndHOC label={label} isGroup isChild={false} isLastChild={isLastChild} onDrop={onDrop}> <LabelDndHOC label={label} isGroup isChild={false} isLastChild={isLastChild} onDrop={onDrop}>
{(isDragging, isDroppingInLabel) => ( {(isDragging, isDroppingInLabel, dragHandleRef) => (
<div <div
className={`rounded ${isDroppingInLabel ? "border-[2px] border-custom-primary-100" : "border-[1.5px] border-transparent"}`} className={`rounded ${isDroppingInLabel ? "border-[2px] border-custom-primary-100" : "border-[1.5px] border-transparent"}`}
> >
<Disclosure <Disclosure
as="div" as="div"
className={`rounded text-custom-text-100 cursor-grab ${ className={`rounded text-custom-text-100 ${
!isDroppingInLabel ? "border-[0.5px] border-custom-border-200" : "" !isDroppingInLabel ? "border-[0.5px] border-custom-border-200" : ""
} ${isDragging ? "bg-custom-background-80" : "bg-custom-background-100"}`} } ${isDragging ? "bg-custom-background-80" : "bg-custom-background-100"}`}
defaultOpen defaultOpen
@ -89,6 +89,7 @@ export const ProjectSettingLabelGroup: React.FC<Props> = observer((props) => {
customMenuItems={customMenuItems} customMenuItems={customMenuItems}
handleLabelDelete={handleLabelDelete} handleLabelDelete={handleLabelDelete}
isLabelGroup isLabelGroup
dragHandleRef={dragHandleRef}
/> />
)} )}

View File

@ -64,9 +64,9 @@ export const ProjectSettingLabelItem: React.FC<Props> = (props) => {
return ( return (
<LabelDndHOC label={label} isGroup={false} isChild={isChild} isLastChild={isLastChild} onDrop={onDrop}> <LabelDndHOC label={label} isGroup={false} isChild={isChild} isLastChild={isLastChild} onDrop={onDrop}>
{(isDragging, isDroppingInLabel) => ( {(isDragging, isDroppingInLabel, dragHandleRef) => (
<div <div
className={`rounded cursor-grab ${isDroppingInLabel ? "border-[2px] border-custom-primary-100" : "border-[1.5px] border-transparent"}`} className={`rounded ${isDroppingInLabel ? "border-[2px] border-custom-primary-100" : "border-[1.5px] border-transparent"}`}
> >
<div <div
className={`py-3 px-1 group relative flex items-center justify-between gap-2 space-y-3 rounded ${ className={`py-3 px-1 group relative flex items-center justify-between gap-2 space-y-3 rounded ${
@ -90,6 +90,7 @@ export const ProjectSettingLabelItem: React.FC<Props> = (props) => {
isDragging={isDragging} isDragging={isDragging}
customMenuItems={customMenuItems} customMenuItems={customMenuItems}
handleLabelDelete={handleLabelDelete} handleLabelDelete={handleLabelDelete}
dragHandleRef={dragHandleRef}
/> />
)} )}
</div> </div>

View File

@ -12,6 +12,7 @@ import { useParams, usePathname } from "next/navigation";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
// icons // icons
import { import {
MoreVertical,
PenSquare, PenSquare,
LinkIcon, LinkIcon,
Star, Star,
@ -123,6 +124,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
// refs // refs
const actionSectionRef = useRef<HTMLDivElement | null>(null); const actionSectionRef = useRef<HTMLDivElement | null>(null);
const projectRef = useRef<HTMLDivElement | null>(null); const projectRef = useRef<HTMLDivElement | null>(null);
const dragHandleRef = useRef<HTMLButtonElement | null>(null);
// router params // router params
const { workspaceSlug, projectId: URLProjectId } = useParams(); const { workspaceSlug, projectId: URLProjectId } = useParams();
// pathname // pathname
@ -185,6 +187,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
useEffect(() => { useEffect(() => {
const element = projectRef.current; const element = projectRef.current;
const dragHandleElement = dragHandleRef.current;
if (!element) return; if (!element) return;
@ -192,6 +195,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
draggable({ draggable({
element, element,
canDrag: () => !disableDrag && !isSideBarCollapsed, canDrag: () => !disableDrag && !isSideBarCollapsed,
dragHandle: dragHandleElement ?? undefined,
getInitialData: () => ({ id: projectId, dragInstanceId: "PROJECTS" }), getInitialData: () => ({ id: projectId, dragInstanceId: "PROJECTS" }),
onDragStart: () => { onDragStart: () => {
setIsDragging(true); setIsDragging(true);
@ -207,7 +211,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
const root = createRoot(container); const root = createRoot(container);
root.render( root.render(
<div className="rounded flex items-center bg-custom-background-100 text-sm p-1 pr-2"> <div className="rounded flex items-center bg-custom-background-100 text-sm p-1 pr-2">
<div className="h-6 w-6 grid place-items-center flex-shrink-0"> <div className="size-4 grid place-items-center flex-shrink-0">
{project && <Logo logo={project?.logo_props} />} {project && <Logo logo={project?.logo_props} />}
</div> </div>
<p className="truncate text-custom-sidebar-text-200">{project?.name}</p> <p className="truncate text-custom-sidebar-text-200">{project?.name}</p>
@ -268,7 +272,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
}, },
}) })
); );
}, [projectRef?.current, projectId, isLastChild, projectListType, handleOnProjectDrop]); }, [projectRef?.current, dragHandleRef?.current, projectId, isLastChild, projectListType, handleOnProjectDrop]);
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
useOutsideClickDetector(projectRef, () => projectRef?.current?.classList?.remove(HIGHLIGHT_CLASS)); useOutsideClickDetector(projectRef, () => projectRef?.current?.classList?.remove(HIGHLIGHT_CLASS));
@ -288,12 +292,39 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
<DropIndicator classNames="absolute top-0" isVisible={instruction === "DRAG_OVER"} /> <DropIndicator classNames="absolute top-0" isVisible={instruction === "DRAG_OVER"} />
<div <div
className={cn( className={cn(
"group relative flex w-full items-center rounded-md py-1 text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-80", "h-9 group relative flex w-full px-3 py-2 items-center rounded-md text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-80",
{ {
"bg-custom-sidebar-background-80": isMenuActive, "bg-custom-sidebar-background-80": isMenuActive,
"pr-0": !isSideBarCollapsed,
} }
)} )}
> >
{!disableDrag && (
<Tooltip
isMobile={isMobile}
tooltipContent={project.sort_order === null ? "Join the project to rearrange" : "Drag to rearrange"}
position="top-right"
disabled={isDragging}
>
<button
type="button"
className={cn(
"absolute -left-[18px] flex opacity-0 rounded text-custom-sidebar-text-400 ml-2 cursor-grab",
{
"group-hover:opacity-100": !isSideBarCollapsed,
"cursor-not-allowed opacity-60": project.sort_order === null,
"cursor-grabbing": isDragging,
flex: isMenuActive,
hidden: isSideBarCollapsed,
}
)}
ref={dragHandleRef}
>
<MoreVertical className="-ml-3 h-3.5" />
<MoreVertical className="-ml-5 h-3.5" />
</button>
</Tooltip>
)}
<Tooltip <Tooltip
tooltipContent={`${project.name}`} tooltipContent={`${project.name}`}
position="right" position="right"
@ -303,18 +334,18 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
<Disclosure.Button <Disclosure.Button
as="div" as="div"
className={cn( className={cn(
"flex flex-grow cursor-pointer select-none items-center justify-between truncate text-left text-sm font-medium pl-2", "flex flex-grow cursor-pointer select-none items-center justify-between truncate text-left text-sm font-medium",
{ {
"justify-center": isSideBarCollapsed, "justify-center": isSideBarCollapsed,
} }
)} )}
> >
<div <div
className={cn("flex w-full h-7 flex-grow items-center gap-1 truncate", { className={cn("flex w-full flex-grow items-center gap-2.5 truncate", {
"justify-center": isSideBarCollapsed, "justify-center": isSideBarCollapsed,
})} })}
> >
<div className="h-5 w-5 grid place-items-center flex-shrink-0 mr-1"> <div className="size-5 grid place-items-center flex-shrink-0">
<Logo logo={project.logo_props} /> <Logo logo={project.logo_props} />
</div> </div>
{!isSideBarCollapsed && <p className="truncate text-custom-sidebar-text-200">{project.name}</p>} {!isSideBarCollapsed && <p className="truncate text-custom-sidebar-text-200">{project.name}</p>}
@ -322,7 +353,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
{!isSideBarCollapsed && ( {!isSideBarCollapsed && (
<ChevronDown <ChevronDown
className={cn( className={cn(
"mb-0.5 hidden h-4 w-4 flex-shrink-0 text-custom-sidebar-text-400 duration-300 group-hover:block", "hidden h-4 w-4 flex-shrink-0 text-custom-sidebar-text-400 duration-300 group-hover:block",
{ {
"rotate-180": open, "rotate-180": open,
block: isMenuActive, block: isMenuActive,
@ -338,17 +369,16 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
customButton={ customButton={
<div <div
ref={actionSectionRef} ref={actionSectionRef}
className="my-auto mt-1.5 w-full cursor-pointer px-1 text-custom-sidebar-text-400 duration-300" className="size-5 flex items-center justify-center cursor-pointer px-1 text-custom-sidebar-text-400 duration-300"
onClick={() => setIsMenuActive(!isMenuActive)} onClick={() => setIsMenuActive(!isMenuActive)}
> >
<MoreHorizontal className="h-3.5 w-3.5" /> <MoreHorizontal className="h-3.5 w-3.5" />
</div> </div>
} }
className={cn("hidden flex-shrink-0 group-hover:block", { className={cn("hidden flex-shrink-0 mr-1 group-hover:block", {
"!block": isMenuActive, "!block": isMenuActive,
})} })}
buttonClassName="!text-custom-sidebar-text-400" buttonClassName="!text-custom-sidebar-text-400"
ellipsis
placement="bottom-start" placement="bottom-start"
> >
{!project.is_favorite && ( {!project.is_favorite && (
@ -432,7 +462,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
leaveFrom="transform scale-100 opacity-100" leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0" leaveTo="transform scale-95 opacity-0"
> >
<Disclosure.Panel className={`mt-1 space-y-1 ${isSideBarCollapsed ? "" : "ml-[2.25rem]"}`}> <Disclosure.Panel className={`mt-1 space-y-1 ${isSideBarCollapsed ? "" : "ml-[1.25rem]"}`}>
{navigation(workspaceSlug as string, project?.id).map((item) => { {navigation(workspaceSlug as string, project?.id).map((item) => {
if ( if (
(item.name === "Cycles" && !project.cycle_view) || (item.name === "Cycles" && !project.cycle_view) ||

View File

@ -207,7 +207,7 @@ export const ProjectSidebarList: FC = observer(() => {
leaveTo="transform scale-95 opacity-0" leaveTo="transform scale-95 opacity-0"
> >
{isFavoriteProjectsListOpen && ( {isFavoriteProjectsListOpen && (
<Disclosure.Panel as="div" className={`space-y-2 pl-2`} static> <Disclosure.Panel as="div" static>
{favoriteProjects.map((projectId, index) => ( {favoriteProjects.map((projectId, index) => (
<ProjectSidebarListItem <ProjectSidebarListItem
key={projectId} key={projectId}
@ -269,7 +269,7 @@ export const ProjectSidebarList: FC = observer(() => {
leaveTo="transform scale-95 opacity-0" leaveTo="transform scale-95 opacity-0"
> >
{isAllProjectsListOpen && ( {isAllProjectsListOpen && (
<Disclosure.Panel as="div" className="pl-2" static> <Disclosure.Panel as="div" static>
{joinedProjects.map((projectId, index) => ( {joinedProjects.map((projectId, index) => (
<ProjectSidebarListItem <ProjectSidebarListItem
key={projectId} key={projectId}