Skip to content

Commit 4f97492

Browse files
committed
updated workflow naming conventions
1 parent 057f242 commit 4f97492

File tree

4 files changed

+490
-226
lines changed

4 files changed

+490
-226
lines changed

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

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use client'
22

3+
import { useCallback, useEffect, useMemo, useState } from 'react'
4+
import { Check } from 'lucide-react'
35
import {
46
Popover,
57
PopoverAnchor,
@@ -12,6 +14,29 @@ import {
1214
import { cn } from '@/lib/core/utils/cn'
1315
import { WORKFLOW_COLORS } from '@/lib/workflows/colors'
1416

17+
/**
18+
* Validates a hex color string.
19+
* Accepts 3 or 6 character hex codes with or without #.
20+
*/
21+
function isValidHex(hex: string): boolean {
22+
const cleaned = hex.replace('#', '')
23+
return /^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(cleaned)
24+
}
25+
26+
/**
27+
* Normalizes a hex color to lowercase 6-character format with #.
28+
*/
29+
function normalizeHex(hex: string): string {
30+
let cleaned = hex.replace('#', '').toLowerCase()
31+
if (cleaned.length === 3) {
32+
cleaned = cleaned
33+
.split('')
34+
.map((c) => c + c)
35+
.join('')
36+
}
37+
return `#${cleaned}`
38+
}
39+
1540
interface ContextMenuProps {
1641
/**
1742
* Whether the context menu is open
@@ -173,7 +198,51 @@ export function ContextMenu({
173198
disableCreate = false,
174199
disableCreateFolder = false,
175200
}: ContextMenuProps) {
176-
// Section visibility for divider logic
201+
const [hexInput, setHexInput] = useState(currentColor || '#ffffff')
202+
203+
// Sync hexInput when currentColor changes (e.g., opening menu on different workflow)
204+
useEffect(() => {
205+
setHexInput(currentColor || '#ffffff')
206+
}, [currentColor])
207+
208+
const canSubmitHex = useMemo(() => {
209+
if (!isValidHex(hexInput)) return false
210+
const normalized = normalizeHex(hexInput)
211+
if (currentColor && normalized.toLowerCase() === currentColor.toLowerCase()) return false
212+
return true
213+
}, [hexInput, currentColor])
214+
215+
const handleHexSubmit = useCallback(() => {
216+
if (!canSubmitHex || !onColorChange) return
217+
218+
const normalized = normalizeHex(hexInput)
219+
onColorChange(normalized)
220+
setHexInput(normalized)
221+
}, [hexInput, canSubmitHex, onColorChange])
222+
223+
const handleHexKeyDown = useCallback(
224+
(e: React.KeyboardEvent<HTMLInputElement>) => {
225+
if (e.key === 'Enter') {
226+
e.preventDefault()
227+
handleHexSubmit()
228+
}
229+
},
230+
[handleHexSubmit]
231+
)
232+
233+
const handleHexChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
234+
let value = e.target.value.trim()
235+
if (value && !value.startsWith('#')) {
236+
value = `#${value}`
237+
}
238+
value = value.slice(0, 1) + value.slice(1).replace(/[^0-9a-fA-F]/g, '')
239+
setHexInput(value.slice(0, 7))
240+
}, [])
241+
242+
const handleHexFocus = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
243+
e.target.select()
244+
}, [])
245+
177246
const hasNavigationSection = showOpenInNewTab && onOpenInNewTab
178247
const hasEditSection =
179248
(showRename && onRename) ||
@@ -268,23 +337,56 @@ export function ContextMenu({
268337
expandOnHover
269338
className={disableColorChange ? 'pointer-events-none opacity-50' : ''}
270339
>
271-
<div className='grid grid-cols-6 gap-[4px] p-[2px]'>
272-
{WORKFLOW_COLORS.map(({ color, name }) => (
340+
<div className='flex w-[140px] flex-col gap-[8px] p-[2px]'>
341+
{/* Preset colors */}
342+
<div className='grid grid-cols-6 gap-[4px]'>
343+
{WORKFLOW_COLORS.map(({ color, name }) => (
344+
<button
345+
key={color}
346+
type='button'
347+
title={name}
348+
onClick={(e) => {
349+
e.stopPropagation()
350+
onColorChange(color)
351+
}}
352+
className={cn(
353+
'h-[20px] w-[20px] rounded-[4px]',
354+
currentColor?.toLowerCase() === color.toLowerCase() && 'ring-1 ring-white'
355+
)}
356+
style={{ backgroundColor: color }}
357+
/>
358+
))}
359+
</div>
360+
361+
{/* Hex input */}
362+
<div className='flex items-center gap-[4px]'>
363+
<div
364+
className='h-[20px] w-[20px] flex-shrink-0 rounded-[4px]'
365+
style={{
366+
backgroundColor: isValidHex(hexInput) ? normalizeHex(hexInput) : '#ffffff',
367+
}}
368+
/>
369+
<input
370+
type='text'
371+
value={hexInput}
372+
onChange={handleHexChange}
373+
onKeyDown={handleHexKeyDown}
374+
onFocus={handleHexFocus}
375+
onClick={(e) => e.stopPropagation()}
376+
className='h-[20px] min-w-0 flex-1 rounded-[4px] bg-[#363636] px-[6px] text-[11px] text-white uppercase focus:outline-none'
377+
/>
273378
<button
274-
key={color}
275379
type='button'
276-
title={name}
380+
disabled={!canSubmitHex}
277381
onClick={(e) => {
278382
e.stopPropagation()
279-
onColorChange(color)
383+
handleHexSubmit()
280384
}}
281-
className={cn(
282-
'h-[20px] w-[20px] rounded-[4px]',
283-
currentColor === color && 'ring-1 ring-white'
284-
)}
285-
style={{ backgroundColor: color }}
286-
/>
287-
))}
385+
className='flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center rounded-[4px] bg-[var(--brand-tertiary-2)] text-white disabled:opacity-40'
386+
>
387+
<Check className='h-[12px] w-[12px]' />
388+
</button>
389+
</div>
288390
</div>
289391
</PopoverFolder>
290392
)}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,13 @@ export function WorkflowItem({ workflow, active, level, onWorkflowClick }: Workf
160160
* Captures selection state for context menu operations
161161
*/
162162
const captureSelectionState = useCallback(() => {
163-
// Check current selection state
164163
const { selectedWorkflows: currentSelection, selectOnly } = useFolderStore.getState()
165164
const isCurrentlySelected = currentSelection.has(workflow.id)
166165

167-
// If this workflow is not in the current selection, select only this workflow
168166
if (!isCurrentlySelected) {
169167
selectOnly(workflow.id)
170168
}
171169

172-
// Capture the selection state
173170
const finalSelection = useFolderStore.getState().selectedWorkflows
174171
const finalIsSelected = finalSelection.has(workflow.id)
175172

@@ -180,13 +177,11 @@ export function WorkflowItem({ workflow, active, level, onWorkflowClick }: Workf
180177
.map((id) => workflows[id]?.name)
181178
.filter((name): name is string => !!name)
182179

183-
// Store in ref so it persists even if selection changes
184180
capturedSelectionRef.current = {
185181
workflowIds,
186182
workflowNames: workflowNames.length > 1 ? workflowNames : workflowNames[0],
187183
}
188184

189-
// Check if the captured selection can be deleted
190185
setCanDeleteCaptured(canDeleteWorkflows(workflowIds))
191186
}, [workflow.id, workflows, canDeleteWorkflows])
192187

apps/sim/components/emcn/components/popover/popover.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,6 @@ const Popover: React.FC<PopoverProps> = ({
221221
const [searchQuery, setSearchQuery] = React.useState<string>('')
222222
const [lastHoveredItem, setLastHoveredItem] = React.useState<string | null>(null)
223223

224-
// Reset folder state when popover closes
225224
React.useEffect(() => {
226225
if (open === false) {
227226
setCurrentFolder(null)

0 commit comments

Comments
 (0)