Skip to content

Commit

Permalink
feat: draggable dashboard menu
Browse files Browse the repository at this point in the history
  • Loading branch information
Loxeris committed Apr 24, 2024
1 parent 67dc88b commit 6c03fee
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 26 deletions.
9 changes: 9 additions & 0 deletions src/app/(dashboard)/jobmonitor/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";

export default function JobMonitorLayout({
children,
}: {
children: React.ReactNode;
}) {
return <section>{children}</section>;
}
77 changes: 51 additions & 26 deletions src/components/ui/DashboardDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { usePathname } from "next/navigation";
import NextLink from "next/link";
import {
Drawer,
Icon,
Expand All @@ -13,19 +12,31 @@ import {
import { Dashboard, FolderCopy } from "@mui/icons-material";
import MonitorIcon from "@mui/icons-material/Monitor";
import MenuBookIcon from "@mui/icons-material/MenuBook";
import { ReactEventHandler } from "react";
import React, { ComponentType, ReactEventHandler } from "react";
import { DragDropContext, Droppable } from "react-beautiful-dnd";
import DrawerItemGroup from "./DrawerItemGroup";
import { DiracLogo } from "./DiracLogo";

// Define the sections that are accessible to users.
// Each section has an associated icon and path.
const userSections: Record<
string,
{ icon: React.ComponentType; path: string }
> = {
Dashboard: { icon: Dashboard, path: "/" },
"Job Monitor": { icon: MonitorIcon, path: "/jobmonitor" },
"File Catalog": { icon: FolderCopy, path: "/filecatalog" },
};
let userSections: {
title: string;
items: { title: string; id: number; icon: ComponentType; path: string }[];
}[] = [
{
title: "Dashboard",
items: [
{ title: "Dashboard", id: 0, icon: Dashboard, path: "/" },
{ title: "Job Monitor", id: 1, icon: MonitorIcon, path: "/jobmonitor" },
],
},
{
title: "Other",
items: [
{ title: "File Catalog", id: 2, icon: FolderCopy, path: "/filecatalog" },
],
},
];

interface DashboardDrawerProps {
variant: "permanent" | "temporary";
Expand All @@ -34,6 +45,27 @@ interface DashboardDrawerProps {
handleDrawerToggle: ReactEventHandler;
}

function onDragEnd(result: any) {
// Reorder the list of items in the group.
if (!result.destination) {
return;
}
const source = result.source;
const destination = result.destination;

const sourceGroup = userSections.find(
(group) => group.title == source.droppableId,
);
const destinationGroup = userSections.find(
(group) => group.title == destination.droppableId,
);

if (sourceGroup && destinationGroup) {
const [removed] = sourceGroup.items.splice(source.index, 1);
destinationGroup.items.splice(destination.index, 0, removed);
}
}

export default function DashboardDrawer(props: DashboardDrawerProps) {
// Get the current URL
const pathname = usePathname();
Expand Down Expand Up @@ -67,22 +99,15 @@ export default function DashboardDrawer(props: DashboardDrawerProps) {
<DiracLogo />
</Toolbar>
{/* Map over user sections and render them as list items in the drawer. */}
<List>
{Object.keys(userSections).map((title: string) => (
<ListItem key={title} disablePadding>
<ListItemButton
component={NextLink}
href={userSections[title]["path"]}
selected={pathname === userSections[title]["path"]}
>
<ListItemIcon>
<Icon component={userSections[title]["icon"]} />
</ListItemIcon>
<ListItemText primary={title} />
</ListItemButton>
</ListItem>
))}
</List>
<DragDropContext onDragEnd={onDragEnd}>
<List>
{userSections.map(({ title, items }, index: number) => (
<ListItem key={title} disablePadding>
<DrawerItemGroup title={title} items={items} />
</ListItem>
))}
</List>
</DragDropContext>
{/* Render a link to documentation, positioned at the bottom of the drawer. */}
<List style={{ position: "absolute", bottom: "0" }}>
<ListItem key={"Documentation"}>
Expand Down
73 changes: 73 additions & 0 deletions src/components/ui/DrawerItemGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
Accordion,
AccordionDetails,
AccordionSummary,
Icon,
ListItemButton,
ListItemIcon,
ListItemText,
} from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { Draggable, Droppable } from "react-beautiful-dnd";
import React from "react";
import Link from "next/link";
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
import { StrictModeDroppable } from "./StrictModeDroppable";

export default function DrawerItemGroup({
title,
items,
}: {
title: string;
items: {
title: string;
id: number;
icon: React.ComponentType;
path: string;
}[];
}) {
return (
<Accordion sx={{ width: "100%" }} disableGutters>
{/* Accordion summary */}
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
{title}
</AccordionSummary>
{/* Accordion details */}
<StrictModeDroppable droppableId={title}>
{(provided, snapshot) => (
<AccordionDetails
{...provided.droppableProps}
ref={provided.innerRef}
>
{items.map(({ title, id, icon, path }, index) => (
<Draggable key={id} draggableId={title} index={index}>
{(provided) => (
<div>
<ListItemButton
disableGutters
ref={provided.innerRef}
key={title}
component={Link}
href={path}
sx={{ pl: 2, borderRadius: 2, pr: 1 }}
{...provided.draggableProps}
>
<ListItemIcon>
<Icon component={icon} />
</ListItemIcon>
<ListItemText primary={title} />
<div {...provided.dragHandleProps}>
<Icon component={DragIndicatorIcon} />
</div>
</ListItemButton>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</AccordionDetails>
)}
</StrictModeDroppable>
</Accordion>
);
}
16 changes: 16 additions & 0 deletions src/components/ui/StrictModeDroppable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect, useState } from "react";
import { Droppable, DroppableProps } from "react-beautiful-dnd";
export const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
const animation = requestAnimationFrame(() => setEnabled(true));
return () => {
cancelAnimationFrame(animation);
setEnabled(false);
};
}, []);
if (!enabled) {
return null;
}
return <Droppable {...props}>{children}</Droppable>;
};

0 comments on commit 6c03fee

Please sign in to comment.