Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 13 additions & 16 deletions src/components/layout/RootLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
import { ToastContainer } from "react-toastify";

import { SidebarProvider } from "@/components/ui/sidebar";
import { useDocumentTitle } from "@/hooks/useDocumentTitle";
import { BackendProvider } from "@/providers/BackendProvider";
import { ComponentSpecProvider } from "@/providers/ComponentSpecProvider";
Expand All @@ -15,25 +14,23 @@ const RootLayout = () => {

return (
<BackendProvider>
<SidebarProvider>
<ComponentSpecProvider>
<ToastContainer />
<ComponentSpecProvider>
<ToastContainer />

<div className="App flex flex-col min-h-screen w-full">
<AppMenu />
<div className="App flex flex-col min-h-screen w-full">
<AppMenu />

<main className="flex-1 grid">
<Outlet />
</main>
<main className="flex-1 grid">
<Outlet />
</main>

<AppFooter />
<AppFooter />

{import.meta.env.VITE_ENABLE_ROUTER_DEVTOOLS === "true" && (
<TanStackRouterDevtools />
)}
</div>
</ComponentSpecProvider>
</SidebarProvider>
{import.meta.env.VITE_ENABLE_ROUTER_DEVTOOLS === "true" && (
<TanStackRouterDevtools />
)}
</div>
</ComponentSpecProvider>
</BackendProvider>
);
};
Expand Down
86 changes: 25 additions & 61 deletions src/components/shared/ReactFlow/FlowSidebar/FlowSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,39 @@
import { useState } from "react";

import { BlockStack } from "@/components/ui/layout";
import { VerticalResizeHandle } from "@/components/ui/resize-handle";
import {
Sidebar,
SidebarContent,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
import { BOTTOM_FOOTER_HEIGHT, TOP_NAV_HEIGHT } from "@/utils/constants";

import FileActions from "./sections/FileActions";
import GraphComponents from "./sections/GraphComponents";
import RunsAndSubmission from "./sections/RunsAndSubmission";

const MIN_WIDTH = 200;
const MIN_WIDTH = 220;
const MAX_WIDTH = 400;
const DEFAULT_WIDTH = 256;
const COLLAPSED_WIDTH = 48;

const FlowSidebar = () => {
const { open, setOpen } = useSidebar();
const { currentSubgraphPath } = useComponentSpec();
const [sidebarWidth, setSidebarWidth] = useState(DEFAULT_WIDTH);

const isViewingSubgraph = currentSubgraphPath.length > 1;

const triggerLeft = open ? sidebarWidth - 1 : COLLAPSED_WIDTH - 1;

const sidebarTriggerClasses = cn(
"absolute z-1 bg-white mt-8 rounded-r-md shadow-md p-0.5 pr-1",
isViewingSubgraph ? "top-[65px]" : "top-6",
);

return (
<>
<div
className={sidebarTriggerClasses}
style={{ left: `${triggerLeft}px` }}
>
<SidebarTrigger
className="text-gray-600 hover:bg-gray-50 hover:text-gray-900"
onClick={() => setOpen(!open)}
/>
</div>
<div
style={
{ "--sidebar-width": `${sidebarWidth}px` } as React.CSSProperties
}
>
<Sidebar
side="left"
className="mt-14 h-[calc(100vh-56px)]"
collapsible="icon"
>
<SidebarContent className="gap-0! m-0! p-0!">
<FileActions isOpen={open} />
<RunsAndSubmission isOpen={open} />
<GraphComponents isOpen={open} />
</SidebarContent>
{open && (
<VerticalResizeHandle
side="right"
minWidth={MIN_WIDTH}
maxWidth={MAX_WIDTH}
onResize={setSidebarWidth}
/>
)}
</Sidebar>
</div>
</>
<div
className="relative h-full bg-sidebar text-sidebar-foreground overflow-x-hidden overflow-y-auto"
data-testid="flow-sidebar-container"
style={{
width: `${DEFAULT_WIDTH}px`,
minWidth: `${MIN_WIDTH}px`,
maxWidth: `${MAX_WIDTH}px`,
maxHeight: `calc(100vh - ${TOP_NAV_HEIGHT}px - ${BOTTOM_FOOTER_HEIGHT}px)`,
}}
>
<BlockStack fill gap="2">
<FileActions />
<RunsAndSubmission />
<GraphComponents />
</BlockStack>

<VerticalResizeHandle
side="right"
minWidth={MIN_WIDTH}
maxWidth={MAX_WIDTH}
/>
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { useFlagValue } from "@/components/shared/Settings/useFlags";
import { withSuspenseWrapper } from "@/components/shared/SuspenseWrapper";
import { Icon } from "@/components/ui/icon";
import { InlineStack } from "@/components/ui/layout";
import { SidebarMenuItem } from "@/components/ui/sidebar";
import { Skeleton } from "@/components/ui/skeleton";
import { Spinner } from "@/components/ui/spinner";
import { useHydrateComponentReference } from "@/hooks/useHydrateComponentReference";
Expand Down Expand Up @@ -169,7 +168,7 @@ const ComponentMarkup = ({
);

return (
<SidebarMenuItem
<li
className={cn(
"pl-2 py-1.5 w-full",
error
Expand Down Expand Up @@ -236,18 +235,18 @@ const ComponentMarkup = ({
</InlineStack>
)}
</InlineStack>
</SidebarMenuItem>
</li>
);
};

const ComponentItemSkeleton = () => {
return (
<SidebarMenuItem className="pl-2 py-1.5">
<li className="pl-2 py-1.5">
<InlineStack gap="2">
<Spinner size={10} />
<Skeleton size="sm" color="default" />
</InlineStack>
</SidebarMenuItem>
</li>
);
};

Expand Down Expand Up @@ -287,7 +286,7 @@ export const IONodeSidebarItem = ({ nodeType }: IONodeSidebarItemProps) => {
);

return (
<SidebarMenuItem
<li
className={cn(
"pl-2 py-1.5 cursor-grab hover:bg-gray-100 active:bg-gray-200",
)}
Expand All @@ -300,7 +299,7 @@ export const IONodeSidebarItem = ({ nodeType }: IONodeSidebarItemProps) => {
{nodeType === "input" ? "Input Node" : "Output Node"}
</span>
</div>
</SidebarMenuItem>
</li>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { SidebarMenuItem } from "@/components/ui/sidebar";
import { Text } from "@/components/ui/typography";

export const LoadingState = () => (
<SidebarMenuItem>
<li className="px-2 py-1.5">
<div className="animate-pulse flex space-x-2 items-center">
<div className="h-4 w-4 bg-gray-200 rounded-full"></div>
<div className="h-3 bg-gray-200 rounded w-full"></div>
</div>
</SidebarMenuItem>
</li>
);

export const ErrorState = ({ message }: { message: string }) => (
<SidebarMenuItem className="text-red-500">Error: {message}</SidebarMenuItem>
<li className="px-2 py-1.5">
<Text tone="critical">Error: {message}</Text>
</li>
);

export const EmptyState = () => (
<SidebarMenuItem>No components found</SidebarMenuItem>
<li className="px-2 py-1.5">
<Text tone="subdued">No components found</Text>
</li>
);
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
} from "@/components/ui/popover";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radiogroup";
import { Separator } from "@/components/ui/separator";
import { SidebarMenu } from "@/components/ui/sidebar";
import { Skeleton } from "@/components/ui/skeleton";
import { Spinner } from "@/components/ui/spinner";
import {
Expand Down Expand Up @@ -266,7 +265,7 @@ const SearchResults = ({ searchResult }: SearchResultsProps) => {
>{`Search Results (${totalResults})`}</Text>
<Separator />
<div className="h-[calc(100vh-400px)] w-full overflow-y-auto overflow-x-hidden scrollbar-thin">
<SidebarMenu>
<ul>
{totalResults > 0 ? (
searchResult.map((folder) => (
<BlockStack key={folder.name}>
Expand All @@ -290,7 +289,7 @@ const SearchResults = ({ searchResult }: SearchResultsProps) => {
) : (
<Text tone="subdued">No results found</Text>
)}
</SidebarMenu>
</ul>
</div>
</BlockStack>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,50 @@ import { List } from "lucide-react";
import { PipelineRunsList } from "@/components/shared/PipelineRunDisplay/PipelineRunsList";
import { usePipelineRuns } from "@/components/shared/PipelineRunDisplay/usePipelineRuns";
import { withSuspenseWrapper } from "@/components/shared/SuspenseWrapper";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { SidebarMenuButton } from "@/components/ui/sidebar";
import { Spinner } from "@/components/ui/spinner";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useComponentSpec } from "@/providers/ComponentSpecProvider";

export const RecentExecutionsButton = withSuspenseWrapper(
({ tooltipPosition = "top" }: { tooltipPosition?: "top" | "right" }) => {
() => {
const { componentSpec } = useComponentSpec();

const { data: runs } = usePipelineRuns(componentSpec?.name);

return (
<Popover>
<PopoverTrigger data-popover-trigger>
<SidebarMenuButton
asChild
tooltip={`Recent Pipeline Runs (${runs.length})`}
forceTooltip
tooltipPosition={tooltipPosition}
className="text-black/80 rounded-md font-medium transition hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring cursor-pointer py-2 px-3"
>
<List className="w-4 h-4" />
</SidebarMenuButton>
</PopoverTrigger>
<PopoverContent className="w-[500px]">
<PipelineRunsList
pipelineName={componentSpec.name}
overviewConfig={{ showName: false, showDescription: true }}
/>
</PopoverContent>
</Popover>
<TooltipProvider>
<Popover>
<Tooltip>
<TooltipTrigger asChild>
<PopoverTrigger asChild data-popover-trigger>
<Button variant="ghost" size="icon" className="size-7">
<List className="w-4 h-4" />
</Button>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent>
Recent Pipeline Runs ({runs.length})
</TooltipContent>
</Tooltip>
<PopoverContent className="w-[500px]">
<PipelineRunsList
pipelineName={componentSpec.name}
overviewConfig={{ showName: false, showDescription: true }}
/>
</PopoverContent>
</Popover>
</TooltipProvider>
);
},
// skeleton fallback
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { type ReactNode } from "react";

import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";
import { cn } from "@/lib/utils";

interface SidebarSectionProps {
/** Section header title */
title: string;
/** Optional action element displayed on the right side of the header */
headerAction?: ReactNode;
/** Section content */
children: ReactNode;
/** Additional class names for the section container */
className?: string;
}

export const SidebarSection = ({
title,
headerAction,
children,
className,
}: SidebarSectionProps) => {
return (
<BlockStack gap="2" className={cn("p-2", className)}>
<InlineStack align="space-between" className="w-full">
<Text
as="h3"
size="sm"
className="font-medium text-sidebar-foreground/70"
>
{title}
</Text>
{headerAction}
</InlineStack>

{children}
</BlockStack>
);
};
Loading
Loading