Skip to content

Commit 5fee230

Browse files
committed
fix(sidebar): address PR review — extract components, fix reactive subscription
1 parent 1a42d95 commit 5fee230

File tree

3 files changed

+132
-100
lines changed

3 files changed

+132
-100
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { Folder } from 'lucide-react'
2+
import Link from 'next/link'
3+
import {
4+
DropdownMenu,
5+
DropdownMenuContent,
6+
DropdownMenuItem,
7+
DropdownMenuSub,
8+
DropdownMenuSubContent,
9+
DropdownMenuSubTrigger,
10+
DropdownMenuTrigger,
11+
} from '@/components/emcn'
12+
import { cn } from '@/lib/core/utils/cn'
13+
import type { useHoverMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
14+
import type { FolderTreeNode } from '@/stores/folders/types'
15+
import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
16+
17+
interface CollapsedSidebarMenuProps {
18+
icon: React.ReactNode
19+
hover: ReturnType<typeof useHoverMenu>
20+
onClick?: () => void
21+
children: React.ReactNode
22+
className?: string
23+
}
24+
25+
export function CollapsedSidebarMenu({
26+
icon,
27+
hover,
28+
onClick,
29+
children,
30+
className,
31+
}: CollapsedSidebarMenuProps) {
32+
return (
33+
<div className={cn('flex flex-col px-[8px]', className)}>
34+
<DropdownMenu
35+
open={hover.isOpen}
36+
onOpenChange={(open) => {
37+
if (!open) hover.close()
38+
}}
39+
modal={false}
40+
>
41+
<div {...hover.triggerProps}>
42+
<DropdownMenuTrigger asChild>
43+
<button
44+
type='button'
45+
className='mx-[2px] flex h-[30px] items-center rounded-[8px] px-[8px] hover:bg-[var(--surface-active)]'
46+
onClick={onClick}
47+
>
48+
{icon}
49+
</button>
50+
</DropdownMenuTrigger>
51+
</div>
52+
<DropdownMenuContent side='right' align='start' sideOffset={8} {...hover.contentProps}>
53+
{children}
54+
</DropdownMenuContent>
55+
</DropdownMenu>
56+
</div>
57+
)
58+
}
59+
60+
export function CollapsedFolderItems({
61+
nodes,
62+
workflowsByFolder,
63+
workspaceId,
64+
}: {
65+
nodes: FolderTreeNode[]
66+
workflowsByFolder: Record<string, WorkflowMetadata[]>
67+
workspaceId: string
68+
}) {
69+
return (
70+
<>
71+
{nodes.map((folder) => {
72+
const folderWorkflows = workflowsByFolder[folder.id] || []
73+
const hasChildren = folder.children.length > 0 || folderWorkflows.length > 0
74+
75+
if (!hasChildren) {
76+
return (
77+
<DropdownMenuItem key={folder.id} disabled>
78+
<Folder className='h-[14px] w-[14px]' />
79+
<span className='truncate'>{folder.name}</span>
80+
</DropdownMenuItem>
81+
)
82+
}
83+
84+
return (
85+
<DropdownMenuSub key={folder.id}>
86+
<DropdownMenuSubTrigger>
87+
<Folder className='h-[14px] w-[14px]' />
88+
<span className='truncate'>{folder.name}</span>
89+
</DropdownMenuSubTrigger>
90+
<DropdownMenuSubContent>
91+
<CollapsedFolderItems
92+
nodes={folder.children}
93+
workflowsByFolder={workflowsByFolder}
94+
workspaceId={workspaceId}
95+
/>
96+
{folderWorkflows.map((workflow) => (
97+
<DropdownMenuItem key={workflow.id} asChild>
98+
<Link href={`/workspace/${workspaceId}/w/${workflow.id}`}>
99+
<div
100+
className='h-[14px] w-[14px] flex-shrink-0 rounded-[3px] border-[2px]'
101+
style={{
102+
backgroundColor: workflow.color,
103+
borderColor: `${workflow.color}60`,
104+
backgroundClip: 'padding-box',
105+
}}
106+
/>
107+
<span className='truncate'>{workflow.name}</span>
108+
</Link>
109+
</DropdownMenuItem>
110+
))}
111+
</DropdownMenuSubContent>
112+
</DropdownMenuSub>
113+
)
114+
})}
115+
</>
116+
)
117+
}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
export {
2+
CollapsedFolderItems,
3+
CollapsedSidebarMenu,
4+
} from './collapsed-sidebar-menu/collapsed-sidebar-menu'
15
export { HelpModal } from './help-modal/help-modal'
26
export { NavItemContextMenu } from './nav-item-context-menu'
37
export { SearchModal } from './search-modal/search-modal'

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx

Lines changed: 11 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
5-
import { Folder, MoreHorizontal } from 'lucide-react'
5+
import { MoreHorizontal } from 'lucide-react'
66
import Link from 'next/link'
77
import { useParams, usePathname, useRouter } from 'next/navigation'
88
import {
@@ -12,9 +12,6 @@ import {
1212
DropdownMenu,
1313
DropdownMenuContent,
1414
DropdownMenuItem,
15-
DropdownMenuSub,
16-
DropdownMenuSubContent,
17-
DropdownMenuSubTrigger,
1815
DropdownMenuTrigger,
1916
FolderPlus,
2017
Home,
@@ -41,6 +38,8 @@ import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/provide
4138
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
4239
import { createCommands } from '@/app/workspace/[workspaceId]/utils/commands-utils'
4340
import {
41+
CollapsedFolderItems,
42+
CollapsedSidebarMenu,
4443
HelpModal,
4544
NavItemContextMenu,
4645
SearchModal,
@@ -73,103 +72,11 @@ import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
7372
import { useTaskEvents } from '@/hooks/use-task-events'
7473
import { SIDEBAR_WIDTH } from '@/stores/constants'
7574
import { useFolderStore } from '@/stores/folders/store'
76-
import type { FolderTreeNode } from '@/stores/folders/types'
7775
import { useSearchModalStore } from '@/stores/modals/search/store'
7876
import { useSidebarStore } from '@/stores/sidebar/store'
79-
import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
8077

8178
const logger = createLogger('Sidebar')
8279

83-
interface CollapsedSidebarMenuProps {
84-
icon: React.ReactNode
85-
hover: ReturnType<typeof useHoverMenu>
86-
onClick?: () => void
87-
children: React.ReactNode
88-
className?: string
89-
}
90-
91-
function CollapsedSidebarMenu({
92-
icon,
93-
hover,
94-
onClick,
95-
children,
96-
className,
97-
}: CollapsedSidebarMenuProps) {
98-
return (
99-
<div className={cn('flex flex-col px-[8px]', className)}>
100-
<DropdownMenu
101-
open={hover.isOpen}
102-
onOpenChange={(open) => {
103-
if (!open) hover.close()
104-
}}
105-
modal={false}
106-
>
107-
<div {...hover.triggerProps}>
108-
<DropdownMenuTrigger asChild>
109-
<button
110-
type='button'
111-
className='mx-[2px] flex h-[30px] items-center rounded-[8px] px-[8px] hover:bg-[var(--surface-active)]'
112-
onClick={onClick}
113-
>
114-
{icon}
115-
</button>
116-
</DropdownMenuTrigger>
117-
</div>
118-
<DropdownMenuContent side='right' align='start' sideOffset={8} {...hover.contentProps}>
119-
{children}
120-
</DropdownMenuContent>
121-
</DropdownMenu>
122-
</div>
123-
)
124-
}
125-
126-
function renderCollapsedFolderItems(
127-
nodes: FolderTreeNode[],
128-
grouped: Record<string, WorkflowMetadata[]>,
129-
wsId: string
130-
): React.ReactNode[] {
131-
return nodes.map((folder) => {
132-
const folderWorkflows = grouped[folder.id] || []
133-
const hasChildren = folder.children.length > 0 || folderWorkflows.length > 0
134-
135-
if (!hasChildren) {
136-
return (
137-
<DropdownMenuItem key={folder.id} disabled>
138-
<Folder className='h-[14px] w-[14px]' />
139-
<span className='truncate'>{folder.name}</span>
140-
</DropdownMenuItem>
141-
)
142-
}
143-
144-
return (
145-
<DropdownMenuSub key={folder.id}>
146-
<DropdownMenuSubTrigger>
147-
<Folder className='h-[14px] w-[14px]' />
148-
<span className='truncate'>{folder.name}</span>
149-
</DropdownMenuSubTrigger>
150-
<DropdownMenuSubContent>
151-
{renderCollapsedFolderItems(folder.children, grouped, wsId)}
152-
{folderWorkflows.map((workflow) => (
153-
<DropdownMenuItem key={workflow.id} asChild>
154-
<Link href={`/workspace/${wsId}/w/${workflow.id}`}>
155-
<div
156-
className='h-[14px] w-[14px] flex-shrink-0 rounded-[3px] border-[2px]'
157-
style={{
158-
backgroundColor: workflow.color,
159-
borderColor: `${workflow.color}60`,
160-
backgroundClip: 'padding-box',
161-
}}
162-
/>
163-
<span className='truncate'>{workflow.name}</span>
164-
</Link>
165-
</DropdownMenuItem>
166-
))}
167-
</DropdownMenuSubContent>
168-
</DropdownMenuSub>
169-
)
170-
})
171-
}
172-
17380
function SidebarItemSkeleton() {
17481
return (
17582
<div className='sidebar-collapse-hide mx-[2px] flex h-[30px] items-center px-[8px]'>
@@ -457,11 +364,11 @@ export const Sidebar = memo(function Sidebar() {
457364

458365
useFolders(workspaceId)
459366
const folders = useFolderStore((s) => s.folders)
367+
const getFolderTree = useFolderStore((s) => s.getFolderTree)
460368

461369
const folderTree = useMemo(
462-
() => (isCollapsed && workspaceId ? useFolderStore.getState().getFolderTree(workspaceId) : []),
463-
// eslint-disable-next-line react-hooks/exhaustive-deps
464-
[isCollapsed, workspaceId, folders]
370+
() => (isCollapsed && workspaceId ? getFolderTree(workspaceId) : []),
371+
[isCollapsed, workspaceId, folders, getFolderTree]
465372
)
466373

467374
const workflowsByFolder = useMemo(
@@ -1305,7 +1212,11 @@ export const Sidebar = memo(function Sidebar() {
13051212
<DropdownMenuItem disabled>No workflows yet</DropdownMenuItem>
13061213
) : (
13071214
<>
1308-
{renderCollapsedFolderItems(folderTree, workflowsByFolder, workspaceId)}
1215+
<CollapsedFolderItems
1216+
nodes={folderTree}
1217+
workflowsByFolder={workflowsByFolder}
1218+
workspaceId={workspaceId}
1219+
/>
13091220
{(workflowsByFolder.root || []).map((workflow) => (
13101221
<DropdownMenuItem key={workflow.id} asChild>
13111222
<Link href={`/workspace/${workspaceId}/w/${workflow.id}`}>

0 commit comments

Comments
 (0)