Skip to content

Commit 057f242

Browse files
committed
feat(popover): add expandOnHover, added the ability to change the color of a workflow icon
1 parent e21cc11 commit 057f242

File tree

15 files changed

+559
-219
lines changed

15 files changed

+559
-219
lines changed

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/access-control/access-control.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ function AddMembersModal({
163163
className='flex items-center gap-[10px] rounded-[4px] px-[8px] py-[6px] hover:bg-[var(--surface-2)]'
164164
>
165165
<Checkbox checked={isSelected} />
166-
<Avatar size='xs'>
166+
<Avatar size='sm'>
167167
{member.user?.image && (
168168
<AvatarImage src={member.user.image} alt={name} />
169169
)}
@@ -663,7 +663,7 @@ export function AccessControl() {
663663
return (
664664
<div key={member.id} className='flex items-center justify-between'>
665665
<div className='flex flex-1 items-center gap-[12px]'>
666-
<Avatar size='sm'>
666+
<Avatar size='md'>
667667
{member.userImage && <AvatarImage src={member.userImage} alt={name} />}
668668
<AvatarFallback
669669
style={{

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credential-sets/credential-sets.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,12 +434,10 @@ export function CredentialSets() {
434434
filteredOwnedSets.length === 0 &&
435435
!hasNoContent
436436

437-
// Early returns AFTER all hooks
438437
if (membershipsLoading || invitationsLoading) {
439438
return <CredentialSetsSkeleton />
440439
}
441440

442-
// Detail view for a polling group
443441
if (viewingSet) {
444442
const activeMembers = members.filter((m) => m.status === 'active')
445443
const totalCount = activeMembers.length + pendingInvitations.length
@@ -529,7 +527,7 @@ export function CredentialSets() {
529527
return (
530528
<div key={member.id} className='flex items-center justify-between'>
531529
<div className='flex flex-1 items-center gap-[12px]'>
532-
<Avatar size='sm'>
530+
<Avatar size='md'>
533531
{member.userImage && (
534532
<AvatarImage src={member.userImage} alt={name} />
535533
)}
@@ -583,7 +581,7 @@ export function CredentialSets() {
583581
return (
584582
<div key={invitation.id} className='flex items-center justify-between'>
585583
<div className='flex flex-1 items-center gap-[12px]'>
586-
<Avatar size='sm'>
584+
<Avatar size='md'>
587585
<AvatarFallback
588586
style={{ background: getUserColor(email) }}
589587
className='border-0 text-white'

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
import {
44
Popover,
55
PopoverAnchor,
6+
PopoverBackButton,
67
PopoverContent,
78
PopoverDivider,
9+
PopoverFolder,
810
PopoverItem,
911
} from '@/components/emcn'
12+
import { cn } from '@/lib/core/utils/cn'
13+
import { WORKFLOW_COLORS } from '@/lib/workflows/colors'
1014

1115
interface ContextMenuProps {
1216
/**
@@ -53,6 +57,14 @@ interface ContextMenuProps {
5357
* Callback when delete is clicked
5458
*/
5559
onDelete: () => void
60+
/**
61+
* Callback when color is changed
62+
*/
63+
onColorChange?: (color: string) => void
64+
/**
65+
* Current workflow color (for showing selected state)
66+
*/
67+
currentColor?: string
5668
/**
5769
* Whether to show the open in new tab option (default: false)
5870
* Set to true for items that can be opened in a new tab
@@ -83,11 +95,21 @@ interface ContextMenuProps {
8395
* Set to true for items that can be exported (like workspaces)
8496
*/
8597
showExport?: boolean
98+
/**
99+
* Whether to show the change color option (default: false)
100+
* Set to true for workflows to allow color customization
101+
*/
102+
showColorChange?: boolean
86103
/**
87104
* Whether the export option is disabled (default: false)
88105
* Set to true when user lacks permissions
89106
*/
90107
disableExport?: boolean
108+
/**
109+
* Whether the change color option is disabled (default: false)
110+
* Set to true when user lacks permissions
111+
*/
112+
disableColorChange?: boolean
91113
/**
92114
* Whether the rename option is disabled (default: false)
93115
* Set to true when user lacks permissions
@@ -134,13 +156,17 @@ export function ContextMenu({
134156
onDuplicate,
135157
onExport,
136158
onDelete,
159+
onColorChange,
160+
currentColor,
137161
showOpenInNewTab = false,
138162
showRename = true,
139163
showCreate = false,
140164
showCreateFolder = false,
141165
showDuplicate = true,
142166
showExport = false,
167+
showColorChange = false,
143168
disableExport = false,
169+
disableColorChange = false,
144170
disableRename = false,
145171
disableDuplicate = false,
146172
disableDelete = false,
@@ -150,7 +176,10 @@ export function ContextMenu({
150176
// Section visibility for divider logic
151177
const hasNavigationSection = showOpenInNewTab && onOpenInNewTab
152178
const hasEditSection =
153-
(showRename && onRename) || (showCreate && onCreate) || (showCreateFolder && onCreateFolder)
179+
(showRename && onRename) ||
180+
(showCreate && onCreate) ||
181+
(showCreateFolder && onCreateFolder) ||
182+
(showColorChange && onColorChange)
154183
const hasCopySection = (showDuplicate && onDuplicate) || (showExport && onExport)
155184

156185
return (
@@ -170,10 +199,21 @@ export function ContextMenu({
170199
height: '1px',
171200
}}
172201
/>
173-
<PopoverContent ref={menuRef} align='start' side='bottom' sideOffset={4}>
202+
<PopoverContent
203+
ref={menuRef}
204+
align='start'
205+
side='bottom'
206+
sideOffset={4}
207+
onPointerDownOutside={(e) => e.preventDefault()}
208+
onInteractOutside={(e) => e.preventDefault()}
209+
>
210+
{/* Back button - shown only when in a folder */}
211+
<PopoverBackButton />
212+
174213
{/* Navigation actions */}
175214
{showOpenInNewTab && onOpenInNewTab && (
176215
<PopoverItem
216+
rootOnly
177217
onClick={() => {
178218
onOpenInNewTab()
179219
onClose()
@@ -182,11 +222,12 @@ export function ContextMenu({
182222
Open in new tab
183223
</PopoverItem>
184224
)}
185-
{hasNavigationSection && (hasEditSection || hasCopySection) && <PopoverDivider />}
225+
{hasNavigationSection && (hasEditSection || hasCopySection) && <PopoverDivider rootOnly />}
186226

187227
{/* Edit and create actions */}
188228
{showRename && onRename && (
189229
<PopoverItem
230+
rootOnly
190231
disabled={disableRename}
191232
onClick={() => {
192233
onRename()
@@ -198,6 +239,7 @@ export function ContextMenu({
198239
)}
199240
{showCreate && onCreate && (
200241
<PopoverItem
242+
rootOnly
201243
disabled={disableCreate}
202244
onClick={() => {
203245
onCreate()
@@ -209,6 +251,7 @@ export function ContextMenu({
209251
)}
210252
{showCreateFolder && onCreateFolder && (
211253
<PopoverItem
254+
rootOnly
212255
disabled={disableCreateFolder}
213256
onClick={() => {
214257
onCreateFolder()
@@ -218,11 +261,39 @@ export function ContextMenu({
218261
Create folder
219262
</PopoverItem>
220263
)}
264+
{showColorChange && onColorChange && (
265+
<PopoverFolder
266+
id='color-picker'
267+
title='Change color'
268+
expandOnHover
269+
className={disableColorChange ? 'pointer-events-none opacity-50' : ''}
270+
>
271+
<div className='grid grid-cols-6 gap-[4px] p-[2px]'>
272+
{WORKFLOW_COLORS.map(({ color, name }) => (
273+
<button
274+
key={color}
275+
type='button'
276+
title={name}
277+
onClick={(e) => {
278+
e.stopPropagation()
279+
onColorChange(color)
280+
}}
281+
className={cn(
282+
'h-[20px] w-[20px] rounded-[4px]',
283+
currentColor === color && 'ring-1 ring-white'
284+
)}
285+
style={{ backgroundColor: color }}
286+
/>
287+
))}
288+
</div>
289+
</PopoverFolder>
290+
)}
221291

222292
{/* Copy and export actions */}
223-
{hasEditSection && hasCopySection && <PopoverDivider />}
293+
{hasEditSection && hasCopySection && <PopoverDivider rootOnly />}
224294
{showDuplicate && onDuplicate && (
225295
<PopoverItem
296+
rootOnly
226297
disabled={disableDuplicate}
227298
onClick={() => {
228299
onDuplicate()
@@ -234,6 +305,7 @@ export function ContextMenu({
234305
)}
235306
{showExport && onExport && (
236307
<PopoverItem
308+
rootOnly
237309
disabled={disableExport}
238310
onClick={() => {
239311
onExport()
@@ -245,8 +317,9 @@ export function ContextMenu({
245317
)}
246318

247319
{/* Destructive action */}
248-
{(hasNavigationSection || hasEditSection || hasCopySection) && <PopoverDivider />}
320+
{(hasNavigationSection || hasEditSection || hasCopySection) && <PopoverDivider rootOnly />}
249321
<PopoverItem
322+
rootOnly
250323
disabled={disableDelete}
251324
onClick={() => {
252325
onDelete()

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import { useCallback, useMemo, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import clsx from 'clsx'
6-
import { ChevronRight, Folder, FolderOpen } from 'lucide-react'
6+
import { ChevronRight, Folder, FolderOpen, MoreHorizontal } from 'lucide-react'
77
import { useParams, useRouter } from 'next/navigation'
8+
import { getNextWorkflowColor } from '@/lib/workflows/colors'
89
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
910
import { ContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu'
1011
import { DeleteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/delete-modal/delete-modal'
@@ -23,10 +24,7 @@ import {
2324
import { useCreateFolder, useUpdateFolder } from '@/hooks/queries/folders'
2425
import { useCreateWorkflow } from '@/hooks/queries/workflows'
2526
import type { FolderTreeNode } from '@/stores/folders/types'
26-
import {
27-
generateCreativeWorkflowName,
28-
getNextWorkflowColor,
29-
} from '@/stores/workflows/registry/utils'
27+
import { generateCreativeWorkflowName } from '@/stores/workflows/registry/utils'
3028

3129
const logger = createLogger('FolderItem')
3230

@@ -173,6 +171,7 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
173171
menuRef,
174172
handleContextMenu,
175173
closeMenu,
174+
preventDismiss,
176175
} = useContextMenu()
177176

178177
// Rename hook
@@ -242,6 +241,40 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
242241
[isEditing, handleRenameKeyDown, handleExpandKeyDown]
243242
)
244243

244+
/**
245+
* Handle more button pointerdown - prevents click-outside dismissal when toggling
246+
*/
247+
const handleMorePointerDown = useCallback(() => {
248+
if (isContextMenuOpen) {
249+
preventDismiss()
250+
}
251+
}, [isContextMenuOpen, preventDismiss])
252+
253+
/**
254+
* Handle more button click - toggles context menu at button position
255+
*/
256+
const handleMoreClick = useCallback(
257+
(e: React.MouseEvent<HTMLButtonElement>) => {
258+
e.preventDefault()
259+
e.stopPropagation()
260+
261+
// Toggle: close if open, open if closed
262+
if (isContextMenuOpen) {
263+
closeMenu()
264+
return
265+
}
266+
267+
const rect = e.currentTarget.getBoundingClientRect()
268+
handleContextMenu({
269+
preventDefault: () => {},
270+
stopPropagation: () => {},
271+
clientX: rect.right,
272+
clientY: rect.top,
273+
} as React.MouseEvent)
274+
},
275+
[isContextMenuOpen, closeMenu, handleContextMenu]
276+
)
277+
245278
return (
246279
<>
247280
<div
@@ -303,12 +336,22 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
303336
spellCheck='false'
304337
/>
305338
) : (
306-
<span
307-
className='truncate font-medium text-[var(--text-tertiary)] group-hover:text-[var(--text-primary)]'
308-
onDoubleClick={handleDoubleClick}
309-
>
310-
{folder.name}
311-
</span>
339+
<>
340+
<span
341+
className='min-w-0 flex-1 truncate font-medium text-[var(--text-tertiary)] group-hover:text-[var(--text-primary)]'
342+
onDoubleClick={handleDoubleClick}
343+
>
344+
{folder.name}
345+
</span>
346+
<button
347+
type='button'
348+
onPointerDown={handleMorePointerDown}
349+
onClick={handleMoreClick}
350+
className='flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-[4px] opacity-0 transition-opacity hover:bg-[var(--surface-7)] group-hover:opacity-100'
351+
>
352+
<MoreHorizontal className='h-[14px] w-[14px] text-[var(--text-tertiary)]' />
353+
</button>
354+
</>
312355
)}
313356
</div>
314357

0 commit comments

Comments
 (0)