refactor: Reduce Sidebar Rerenders (#7699)

* menubar and hook optimizations

* sidebar optimizations

* [autofix.ci] apply automated fixes

* cleanup

* [autofix.ci] apply automated fixes

* sidebare button optimization

* 🐛 (typescript_test.yml): increase the maximum shard count to 40 to improve test distribution and performance

* Get state on focus instead of on keypress

* revert test shard change

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
Mike Fortman 2025-05-06 11:11:47 -05:00 committed by GitHub
commit 710b6f99b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 208 additions and 186 deletions

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAddFlow from "@/hooks/flows/use-add-flow";
@ -35,7 +35,7 @@ import { cn, getNumberFromString } from "@/utils/utils";
import { useQueryClient } from "@tanstack/react-query";
import { useShallow } from "zustand/react/shallow";
export const MenuBar = ({}: {}): JSX.Element => {
export const MenuBar = memo((): JSX.Element => {
const shortcuts = useShortcutsStore((state) => state.shortcuts);
const addFlow = useAddFlow();
const setErrorData = useAlertStore((state) => state.setErrorData);
@ -81,20 +81,10 @@ export const MenuBar = ({}: {}): JSX.Element => {
const [inputWidth, setInputWidth] = useState<number>(0);
const measureRef = useRef<HTMLSpanElement>(null);
const changesNotSaved = useUnsavedChanges();
const [flowNames, setFlowNames] = useState<string[]>([]);
const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery();
const flows = useFlowsManagerStore((state) => state.flows);
const [nameLists, setNameList] = useState<string[]>([]);
useEffect(() => {
if (flows) {
const tempNameList: string[] = [];
flows.forEach((flow) => {
tempNameList.push(flow.name);
});
setNameList(tempNameList.filter((name) => name !== currentFlowName));
}
}, [flows, currentFlowName]);
useGetRefreshFlowsQuery(
{
@ -162,17 +152,11 @@ export const MenuBar = ({}: {}): JSX.Element => {
const handleEditName = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
let invalid = false;
for (let i = 0; i < nameLists.length; i++) {
if (value === nameLists[i]) {
invalid = true;
break;
}
}
const invalid = flowNames.includes(value);
setIsInvalidName(invalid);
setFlowName(value);
},
[nameLists],
[flowNames],
);
const handleKeyDown = useCallback(
@ -195,7 +179,6 @@ export const MenuBar = ({}: {}): JSX.Element => {
flowName !== currentFlowName &&
!isInvalidName
) {
// Get a one-time snapshot of currentFlow using get()
const currentFlowSnapshot = useFlowStore.getState().currentFlow;
const newFlow = {
@ -327,6 +310,12 @@ export const MenuBar = ({}: {}): JSX.Element => {
onFocus={() => {
setEditingName(true);
setFlowName(currentFlowName);
const flows = useFlowsManagerStore.getState().flows;
setFlowNames(
flows
?.map((flow) => flow.name)
.filter((name) => name !== currentFlowName) ?? [],
);
}}
onBlur={handleNameSubmit}
value={flowName}
@ -578,6 +567,6 @@ export const MenuBar = ({}: {}): JSX.Element => {
) : (
<></>
);
};
});
export default MenuBar;

View file

@ -85,8 +85,8 @@ const SidebarProvider = React.forwardRef<
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return setOpen((open) => !open);
}, [setOpen, open]);
return setOpen((prev) => !prev);
}, [setOpen]);
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
@ -275,6 +275,14 @@ const SidebarTrigger = React.forwardRef<
>(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar();
const handleClick = React.useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event);
toggleSidebar();
},
[onClick, toggleSidebar],
);
return (
<Button
ref={ref}
@ -282,10 +290,7 @@ const SidebarTrigger = React.forwardRef<
variant="ghost"
size="icon"
className={cn("h-7 w-7 text-muted-foreground", className)}
onClick={(event) => {
onClick?.(event);
toggleSidebar();
}}
onClick={handleClick}
{...props}
>
{props.children ? (
@ -444,25 +449,27 @@ const SidebarGroup = React.forwardRef<
});
SidebarGroup.displayName = "SidebarGroup";
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div";
const SidebarGroupLabel = React.memo(
React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div";
return (
<Comp
ref={ref}
data-sidebar="group-label"
className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-semibold text-foreground/70 outline-none ring-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-1 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className,
)}
{...props}
/>
);
});
return (
<Comp
ref={ref}
data-sidebar="group-label"
className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-semibold text-foreground/70 outline-none ring-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-1 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className,
)}
{...props}
/>
);
}),
);
SidebarGroupLabel.displayName = "SidebarGroupLabel";
const SidebarGroupAction = React.forwardRef<

View file

@ -7,7 +7,6 @@ import {
} from "@/utils/reactflowUtils";
const useDeleteFlow = () => {
const flows = useFlowsManagerStore((state) => state.flows);
const setFlows = useFlowsManagerStore((state) => state.setFlows);
const { mutate, isPending } = useDeleteDeleteFlows();
@ -17,6 +16,7 @@ const useDeleteFlow = () => {
}: {
id: string | string[];
}): Promise<void> => {
const flows = useFlowsManagerStore.getState().flows;
return new Promise<void>((resolve, reject) => {
if (!Array.isArray(id)) {
id = [id];

View file

@ -6,29 +6,28 @@ import useFlowStore from "@/stores/flowStore";
import { AllNodeType, EdgeType, FlowType } from "@/types/flow";
import { customStringify } from "@/utils/reactflowUtils";
import { ReactFlowJsonObject } from "@xyflow/react";
const useSaveFlow = () => {
const flows = useFlowsManagerStore((state) => state.flows);
const setFlows = useFlowsManagerStore((state) => state.setFlows);
const setErrorData = useAlertStore((state) => state.setErrorData);
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setSaveLoading = useFlowsManagerStore((state) => state.setSaveLoading);
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
const { mutate: getFlow } = useGetFlow();
const { mutate } = usePatchUpdateFlow();
const currentFlow = useFlowStore((state) => state.currentFlow);
const flowData = currentFlow?.data;
const saveFlow = async (flow?: FlowType): Promise<void> => {
const currentFlow = useFlowStore.getState().currentFlow;
const currentSavedFlow = useFlowsManagerStore.getState().currentFlow;
if (
customStringify(flow || currentFlow) !== customStringify(currentSavedFlow)
) {
setSaveLoading(true);
const flowData = currentFlow?.data;
const nodes = useFlowStore.getState().nodes;
const edges = useFlowStore.getState().edges;
const reactFlowInstance = useFlowStore.getState().reactFlowInstance;
return new Promise<void>((resolve, reject) => {
if (currentFlow) {
flow = flow || {
@ -83,6 +82,7 @@ const useSaveFlow = () => {
},
{
onSuccess: (updatedFlow) => {
const flows = useFlowsManagerStore.getState().flows;
setSaveLoading(false);
if (flows) {
// updates flow in state

View file

@ -542,6 +542,13 @@ export default function Page({
const componentsToUpdate = useFlowStore((state) => state.componentsToUpdate);
const MIN_ZOOM = 0.2;
const MAX_ZOOM = 8;
const fitViewOptions = {
minZoom: MIN_ZOOM,
maxZoom: MAX_ZOOM,
};
return (
<div className="h-full w-full bg-canvas" ref={reactFlowWrapper}>
{showCanvas ? (
@ -572,13 +579,10 @@ export default function Page({
onSelectionChange={onSelectionChange}
deleteKeyCode={[]}
fitView={isEmptyFlow.current ? false : true}
fitViewOptions={{
minZoom: 0.2,
maxZoom: 8,
}}
fitViewOptions={fitViewOptions}
className="theme-attribution"
minZoom={0.2}
maxZoom={3}
minZoom={MIN_ZOOM}
maxZoom={MAX_ZOOM}
zoomOnScroll={!view}
zoomOnPinch={!view}
panOnDrag={!view}

View file

@ -5,18 +5,17 @@ import {
DisclosureTrigger,
} from "@/components/ui/disclosure";
import { SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
import { memo } from "react";
import { memo, useCallback } from "react";
import { BundleItemProps } from "../../types";
import SidebarItemsList from "../sidebarItemsList";
export const BundleItem = memo(
({
item,
isOpen,
onOpenChange,
openCategories,
setOpenCategories,
dataFilter,
nodeColors,
uniqueInputsComponents,
onDragStart,
sensitiveSort,
handleKeyDownInput,
@ -28,45 +27,55 @@ export const BundleItem = memo(
return null;
}
const isOpen = openCategories.includes(item.name);
const handleOpenChange = useCallback(
(isOpen: boolean) => {
setOpenCategories((prev: string[]) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
},
[item.name, setOpenCategories],
);
return (
<>
<Disclosure key={item.name} open={isOpen} onOpenChange={onOpenChange}>
<SidebarMenuItem>
<DisclosureTrigger className="group/collapsible">
<SidebarMenuButton asChild>
<div
tabIndex={0}
onKeyDown={(e) => handleKeyDownInput(e, item.name)}
className="flex cursor-pointer items-center gap-2"
data-testid={`disclosure-bundles-${item.display_name.toLowerCase()}`}
>
<ForwardedIconComponent
name={item.icon}
className="h-4 w-4 text-muted-foreground group-aria-expanded/collapsible:text-primary"
/>
<span className="flex-1 group-aria-expanded/collapsible:font-semibold">
{item.display_name}
</span>
<ForwardedIconComponent
name="ChevronRight"
className="-mr-1 h-4 w-4 text-muted-foreground transition-all group-aria-expanded/collapsible:rotate-90"
/>
</div>
</SidebarMenuButton>
</DisclosureTrigger>
<DisclosureContent>
<SidebarItemsList
item={item}
dataFilter={dataFilter}
nodeColors={nodeColors}
uniqueInputsComponents={uniqueInputsComponents}
onDragStart={onDragStart}
sensitiveSort={sensitiveSort}
/>
</DisclosureContent>
</SidebarMenuItem>
</Disclosure>
</>
<Disclosure key={item.name} open={isOpen} onOpenChange={handleOpenChange}>
<SidebarMenuItem>
<DisclosureTrigger className="group/collapsible">
<SidebarMenuButton asChild>
<div
tabIndex={0}
onKeyDown={(e) => handleKeyDownInput(e, item.name)}
className="flex cursor-pointer items-center gap-2"
data-testid={`disclosure-bundles-${item.display_name.toLowerCase()}`}
>
<ForwardedIconComponent
name={item.icon}
className="h-4 w-4 text-muted-foreground group-aria-expanded/collapsible:text-primary"
/>
<span className="flex-1 group-aria-expanded/collapsible:font-semibold">
{item.display_name}
</span>
<ForwardedIconComponent
name="ChevronRight"
className="-mr-1 h-4 w-4 text-muted-foreground transition-all group-aria-expanded/collapsible:rotate-90"
/>
</div>
</SidebarMenuButton>
</DisclosureTrigger>
<DisclosureContent>
<SidebarItemsList
item={item}
dataFilter={dataFilter}
nodeColors={nodeColors}
onDragStart={onDragStart}
sensitiveSort={sensitiveSort}
/>
</DisclosureContent>
</SidebarMenuItem>
</Disclosure>
);
},
);

View file

@ -15,7 +15,6 @@ export const CategoryDisclosure = memo(function CategoryDisclosure({
setOpenCategories,
dataFilter,
nodeColors,
uniqueInputsComponents,
onDragStart,
sensitiveSort,
}: {
@ -24,10 +23,6 @@ export const CategoryDisclosure = memo(function CategoryDisclosure({
setOpenCategories;
dataFilter: any;
nodeColors: any;
uniqueInputsComponents: {
chatInput: boolean;
webhookInput: boolean;
};
onDragStart: (
event: React.DragEvent<any>,
data: { type: string; node?: APIClassType },
@ -48,17 +43,17 @@ export const CategoryDisclosure = memo(function CategoryDisclosure({
[item.name, setOpenCategories],
);
const isOpen = openCategories.includes(item.name);
const handleOpenChange = useCallback(
(isOpen: boolean) => {
setOpenCategories((prev) =>
isOpen ? [...prev, item.name] : prev.filter((cat) => cat !== item.name),
);
},
[item.name, setOpenCategories],
);
return (
<Disclosure
open={openCategories.includes(item.name)}
onOpenChange={(isOpen) => {
setOpenCategories((prev) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
}}
>
<Disclosure open={isOpen} onOpenChange={handleOpenChange}>
<SidebarMenuItem>
<DisclosureTrigger className="group/collapsible">
<SidebarMenuButton asChild>
@ -87,7 +82,6 @@ export const CategoryDisclosure = memo(function CategoryDisclosure({
item={item}
dataFilter={dataFilter}
nodeColors={nodeColors}
uniqueInputsComponents={uniqueInputsComponents}
onDragStart={onDragStart}
sensitiveSort={sensitiveSort}
/>

View file

@ -4,7 +4,7 @@ import {
SidebarMenu,
} from "@/components/ui/sidebar";
import { SIDEBAR_BUNDLES } from "@/utils/styleUtils";
import { memo } from "react";
import { memo, useState } from "react";
import { CategoryGroupProps } from "../../types";
import { CategoryDisclosure } from "../categoryDisclouse";
@ -16,7 +16,6 @@ export const CategoryGroup = memo(function CategoryGroup({
setOpenCategories,
search,
nodeColors,
uniqueInputsComponents,
onDragStart,
sensitiveSort,
}: CategoryGroupProps) {
@ -67,7 +66,6 @@ export const CategoryGroup = memo(function CategoryGroup({
nodeColors={nodeColors}
onDragStart={onDragStart}
sensitiveSort={sensitiveSort}
uniqueInputsComponents={uniqueInputsComponents}
/>
);
})}

View file

@ -4,7 +4,7 @@ import {
SidebarGroupLabel,
SidebarMenu,
} from "@/components/ui/sidebar";
import { memo, useMemo } from "react";
import { memo, useCallback, useMemo, useState } from "react";
import { SidebarGroupProps } from "../../types";
import { BundleItem } from "../bundleItems";
@ -17,10 +17,9 @@ export const MemoizedSidebarGroup = memo(
nodeColors,
onDragStart,
sensitiveSort,
handleKeyDownInput,
openCategories,
setOpenCategories,
handleKeyDownInput,
uniqueInputsComponents,
}: SidebarGroupProps) => {
const sortedBundles = useMemo(() => {
return BUNDLES.toSorted((a, b) => {
@ -41,17 +40,10 @@ export const MemoizedSidebarGroup = memo(
<BundleItem
key={item.name}
item={item}
isOpen={openCategories.includes(item.name)}
onOpenChange={(isOpen) => {
setOpenCategories((prev) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
}}
openCategories={openCategories}
setOpenCategories={setOpenCategories}
dataFilter={dataFilter}
nodeColors={nodeColors}
uniqueInputsComponents={uniqueInputsComponents}
onDragStart={onDragStart}
sensitiveSort={sensitiveSort}
handleKeyDownInput={handleKeyDownInput}

View file

@ -1,14 +1,17 @@
import ShadTooltip from "@/components/common/shadTooltipComponent";
import useFlowStore from "@/stores/flowStore";
import { checkChatInput, checkWebhookInput } from "@/utils/reactflowUtils";
import { removeCountFromString } from "@/utils/utils";
import { useMemo } from "react";
import { disableItem } from "../../helpers/disable-item";
import { getDisabledTooltip } from "../../helpers/get-disabled-tooltip";
import { UniqueInputsComponents } from "../../types";
import SidebarDraggableComponent from "../sidebarDraggableComponent";
const SidebarItemsList = ({
item,
dataFilter,
nodeColors,
uniqueInputsComponents,
onDragStart,
sensitiveSort,
}) => {
@ -36,6 +39,18 @@ const SidebarItemsList = ({
.map((SBItemName, idx) => {
const currentItem = dataFilter[item.name][SBItemName];
if (SBItemName === "ChatInput" || SBItemName === "Webhook") {
return (
<UniqueInputsDraggableComponent
item={item}
currentItem={currentItem}
SBItemName={SBItemName}
idx={idx}
onDragStart={onDragStart}
nodeColors={nodeColors}
/>
);
}
return (
<ShadTooltip
content={currentItem.display_name}
@ -59,11 +74,8 @@ const SidebarItemsList = ({
official={currentItem.official === false ? false : true}
beta={currentItem.beta ?? false}
legacy={currentItem.legacy ?? false}
disabled={disableItem(SBItemName, uniqueInputsComponents)}
disabledTooltip={getDisabledTooltip(
SBItemName,
uniqueInputsComponents,
)}
disabled={false}
disabledTooltip={""}
/>
</ShadTooltip>
);
@ -73,3 +85,51 @@ const SidebarItemsList = ({
};
export default SidebarItemsList;
const UniqueInputsDraggableComponent = ({
item,
currentItem,
SBItemName,
idx,
onDragStart,
nodeColors,
}) => {
const nodes = useFlowStore((state) => state.nodes);
const chatInputAdded = useMemo(() => checkChatInput(nodes), [nodes]);
const webhookInputAdded = useMemo(() => checkWebhookInput(nodes), [nodes]);
const uniqueInputsComponents: UniqueInputsComponents = useMemo(() => {
console.log("uniqueInputsComponents", {
chatInputAdded,
webhookInputAdded,
});
return {
chatInput: chatInputAdded,
webhookInput: webhookInputAdded,
};
}, [chatInputAdded, webhookInputAdded]);
return (
<ShadTooltip content={currentItem.display_name} side="right" key={idx}>
<SidebarDraggableComponent
sectionName={item.name}
apiClass={currentItem}
icon={currentItem.icon ?? item.icon ?? "Unknown"}
onDragStart={(event) =>
onDragStart(event, {
type: removeCountFromString(SBItemName),
node: currentItem,
})
}
color={nodeColors[item.name]}
itemName={SBItemName}
error={!!currentItem.error}
display_name={currentItem.display_name}
official={currentItem.official === false ? false : true}
beta={currentItem.beta ?? false}
legacy={currentItem.legacy ?? false}
disabled={disableItem(SBItemName, uniqueInputsComponents)}
disabledTooltip={getDisabledTooltip(SBItemName, uniqueInputsComponents)}
/>
</ShadTooltip>
);
};

View file

@ -18,6 +18,7 @@ import Fuse from "fuse.js";
import { cloneDeep } from "lodash";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { useShallow } from "zustand/react/shallow";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import { useTypesStore } from "../../../../stores/typesStore";
@ -48,30 +49,17 @@ interface FlowSidebarComponentProps {
}
export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
const { data, templates } = useTypesStore(
useCallback(
(state) => ({
data: state.data,
templates: state.templates,
}),
[],
),
);
const data = useTypesStore((state) => state.data);
const { getFilterEdge, setFilterEdge, filterType, nodes } = useFlowStore(
useCallback(
(state) => ({
getFilterEdge: state.getFilterEdge,
setFilterEdge: state.setFilterEdge,
filterType: state.filterType,
nodes: state.nodes,
}),
[],
),
const { getFilterEdge, setFilterEdge, filterType } = useFlowStore(
useShallow((state) => ({
getFilterEdge: state.getFilterEdge,
setFilterEdge: state.setFilterEdge,
filterType: state.filterType,
})),
);
const hasStore = useStoreStore((state) => state.hasStore);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { setOpen } = useSidebar();
const addComponent = useAddComponent();
@ -87,15 +75,6 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
const searchInputRef = useRef<HTMLInputElement | null>(null);
const chatInputAdded = useMemo(() => checkChatInput(nodes), [nodes]);
const webhookInputAdded = useMemo(() => checkWebhookInput(nodes), [nodes]);
const uniqueInputsComponents: UniqueInputsComponents = useMemo(() => {
return {
chatInput: chatInputAdded,
webhookInput: webhookInputAdded,
};
}, [chatInputAdded, webhookInputAdded]);
const customComponent = useMemo(() => {
return data?.["custom_component"]?.["CustomComponent"] ?? null;
}, [data]);
@ -358,7 +337,6 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
nodeColors={nodeColors}
onDragStart={onDragStart}
sensitiveSort={sensitiveSort}
uniqueInputsComponents={uniqueInputsComponents}
/>
{hasBundleItems && (
@ -373,7 +351,6 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
openCategories={openCategories}
setOpenCategories={setOpenCategories}
handleKeyDownInput={handleKeyDownInput}
uniqueInputsComponents={uniqueInputsComponents}
/>
)}
</>

View file

@ -14,7 +14,7 @@ export interface CategoryGroupProps {
icon: string;
}[];
openCategories: string[];
setOpenCategories: (categories: string[]) => void;
setOpenCategories: Dispatch<SetStateAction<string[]>>;
search: string;
nodeColors: NodeColors;
onDragStart: (
@ -22,10 +22,6 @@ export interface CategoryGroupProps {
data: { type: string; node?: APIClassType },
) => void;
sensitiveSort: (a: string, b: string) => number;
uniqueInputsComponents: {
chatInput: boolean;
webhookInput: boolean;
};
}
export interface SidebarGroupProps {
@ -39,15 +35,12 @@ export interface SidebarGroupProps {
data: { type: string; node?: APIClassType },
) => void;
sensitiveSort: (a: string, b: string) => number;
openCategories: string[];
setOpenCategories: (
categories: string[] | ((prev: string[]) => string[]),
) => void;
handleKeyDownInput: (
event: React.KeyboardEvent<HTMLDivElement>,
name: string,
) => void;
uniqueInputsComponents: UniqueInputsComponents;
openCategories: string[];
setOpenCategories: Dispatch<SetStateAction<string[]>>;
}
export interface BundleItemProps {
@ -56,11 +49,10 @@ export interface BundleItemProps {
display_name: string;
icon: string;
};
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
openCategories: string[];
setOpenCategories: Dispatch<SetStateAction<string[]>>;
dataFilter: APIDataType;
nodeColors: NodeColors;
uniqueInputsComponents: UniqueInputsComponents;
onDragStart: (
event: React.DragEvent<any>,
data: { type: string; node?: APIClassType },