feat: introduce BundleItem component and memoize sidebar groups for performance (#5312)
✨ (index.tsx): introduce MemoizedSidebarGroup component to improve performance by memoizing sorted bundles calculation and rendering 📝 (bundleItems/index.tsx): add BundleItem component to render individual bundle items in the sidebar with collapsible functionality
This commit is contained in:
parent
5aa87ee41e
commit
b3b5290598
3 changed files with 178 additions and 69 deletions
|
|
@ -0,0 +1,83 @@
|
||||||
|
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||||
|
import {
|
||||||
|
Disclosure,
|
||||||
|
DisclosureContent,
|
||||||
|
DisclosureTrigger,
|
||||||
|
} from "@/components/ui/disclosure";
|
||||||
|
import { SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
|
||||||
|
import { memo } from "react";
|
||||||
|
import SidebarItemsList from "../sidebarItemsList";
|
||||||
|
|
||||||
|
export const BundleItem = memo(
|
||||||
|
({
|
||||||
|
item,
|
||||||
|
isOpen,
|
||||||
|
onOpenChange,
|
||||||
|
dataFilter,
|
||||||
|
nodeColors,
|
||||||
|
chatInputAdded,
|
||||||
|
onDragStart,
|
||||||
|
sensitiveSort,
|
||||||
|
handleKeyDownInput,
|
||||||
|
}: {
|
||||||
|
item: any;
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
|
dataFilter: any;
|
||||||
|
nodeColors: any;
|
||||||
|
chatInputAdded: any;
|
||||||
|
onDragStart: any;
|
||||||
|
sensitiveSort: any;
|
||||||
|
handleKeyDownInput: any;
|
||||||
|
}) => {
|
||||||
|
if (
|
||||||
|
!dataFilter[item.name] ||
|
||||||
|
Object.keys(dataFilter[item.name]).length === 0
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
chatInputAdded={chatInputAdded}
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
sensitiveSort={sensitiveSort}
|
||||||
|
/>
|
||||||
|
</DisclosureContent>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</Disclosure>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
BundleItem.displayName = "BundleItem";
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
import {
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupContent,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarMenu,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
|
import { memo, useMemo } from "react";
|
||||||
|
import { BundleItem } from "../bundleItems";
|
||||||
|
|
||||||
|
export const MemoizedSidebarGroup = memo(
|
||||||
|
({
|
||||||
|
BUNDLES,
|
||||||
|
search,
|
||||||
|
sortedCategories,
|
||||||
|
dataFilter,
|
||||||
|
nodeColors,
|
||||||
|
chatInputAdded,
|
||||||
|
onDragStart,
|
||||||
|
sensitiveSort,
|
||||||
|
openCategories,
|
||||||
|
setOpenCategories,
|
||||||
|
handleKeyDownInput,
|
||||||
|
}: {
|
||||||
|
BUNDLES: any;
|
||||||
|
search: any;
|
||||||
|
sortedCategories: any;
|
||||||
|
dataFilter: any;
|
||||||
|
nodeColors: any;
|
||||||
|
chatInputAdded: any;
|
||||||
|
onDragStart: any;
|
||||||
|
sensitiveSort: any;
|
||||||
|
openCategories: any;
|
||||||
|
setOpenCategories: any;
|
||||||
|
handleKeyDownInput: any;
|
||||||
|
}) => {
|
||||||
|
// Memoize the sorted bundles calculation
|
||||||
|
const sortedBundles = useMemo(() => {
|
||||||
|
return BUNDLES.toSorted((a, b) => {
|
||||||
|
const referenceArray = search !== "" ? sortedCategories : BUNDLES;
|
||||||
|
return (
|
||||||
|
referenceArray.findIndex((value) => value === a.name) -
|
||||||
|
referenceArray.findIndex((value) => value === b.name)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [BUNDLES, search, sortedCategories]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarGroup className="p-3">
|
||||||
|
<SidebarGroupLabel>Bundles</SidebarGroupLabel>
|
||||||
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
|
{sortedBundles.map((item) => (
|
||||||
|
<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),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
dataFilter={dataFilter}
|
||||||
|
nodeColors={nodeColors}
|
||||||
|
chatInputAdded={chatInputAdded}
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
sensitiveSort={sensitiveSort}
|
||||||
|
handleKeyDownInput={handleKeyDownInput}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
MemoizedSidebarGroup.displayName = "MemoizedSidebarGroup";
|
||||||
|
|
||||||
|
export default MemoizedSidebarGroup;
|
||||||
|
|
@ -36,6 +36,7 @@ import { APIClassType } from "../../../../types/api";
|
||||||
import sensitiveSort from "../extraSidebarComponent/utils/sensitive-sort";
|
import sensitiveSort from "../extraSidebarComponent/utils/sensitive-sort";
|
||||||
import { CategoryGroup } from "./components/categoryGroup";
|
import { CategoryGroup } from "./components/categoryGroup";
|
||||||
import NoResultsMessage from "./components/emptySearchComponent";
|
import NoResultsMessage from "./components/emptySearchComponent";
|
||||||
|
import MemoizedSidebarGroup from "./components/sidebarBundles";
|
||||||
import SidebarMenuButtons from "./components/sidebarFooterButtons";
|
import SidebarMenuButtons from "./components/sidebarFooterButtons";
|
||||||
import { SidebarHeaderComponent } from "./components/sidebarHeader";
|
import { SidebarHeaderComponent } from "./components/sidebarHeader";
|
||||||
import SidebarItemsList from "./components/sidebarItemsList";
|
import SidebarItemsList from "./components/sidebarItemsList";
|
||||||
|
|
@ -416,75 +417,19 @@ export function FlowSidebarComponent() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasBundleItems && (
|
{hasBundleItems && (
|
||||||
<SidebarGroup className="p-3">
|
<MemoizedSidebarGroup
|
||||||
<SidebarGroupLabel>Bundles</SidebarGroupLabel>
|
BUNDLES={BUNDLES}
|
||||||
<SidebarGroupContent>
|
search={search}
|
||||||
<SidebarMenu>
|
sortedCategories={sortedCategories}
|
||||||
{BUNDLES.toSorted(
|
dataFilter={dataFilter}
|
||||||
(a, b) =>
|
nodeColors={nodeColors}
|
||||||
(search !== "" ? sortedCategories : BUNDLES).findIndex(
|
chatInputAdded={chatInputAdded}
|
||||||
(value) => value === a.name,
|
onDragStart={onDragStart}
|
||||||
) -
|
sensitiveSort={sensitiveSort}
|
||||||
(search !== "" ? sortedCategories : BUNDLES).findIndex(
|
openCategories={openCategories}
|
||||||
(value) => value === b.name,
|
setOpenCategories={setOpenCategories}
|
||||||
),
|
handleKeyDownInput={handleKeyDownInput}
|
||||||
).map(
|
/>
|
||||||
(item) =>
|
|
||||||
dataFilter[item.name] &&
|
|
||||||
Object.keys(dataFilter[item.name]).length > 0 && (
|
|
||||||
<Disclosure
|
|
||||||
key={item.name}
|
|
||||||
open={openCategories.includes(item.name)}
|
|
||||||
onOpenChange={(isOpen) => {
|
|
||||||
setOpenCategories((prev) =>
|
|
||||||
isOpen
|
|
||||||
? [...prev, item.name]
|
|
||||||
: prev.filter((cat) => cat !== item.name),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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.toLocaleLowerCase()}`}
|
|
||||||
>
|
|
||||||
<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}
|
|
||||||
chatInputAdded={chatInputAdded}
|
|
||||||
onDragStart={onDragStart}
|
|
||||||
sensitiveSort={sensitiveSort}
|
|
||||||
/>
|
|
||||||
</DisclosureContent>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</Disclosure>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue