Skip to content

Commit b102d4f

Browse files
committed
block icons, sidebar, toolbar
1 parent 1a509c0 commit b102d4f

52 files changed

Lines changed: 490 additions & 377 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/app/layout.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
7373
return;
7474
}
7575
76-
// Sidebar width
77-
var defaultSidebarWidth = '248px';
76+
// Sidebar width. Mirror clampSidebarWidth() in stores/sidebar/store.ts:
77+
// the upper bound can never fall below the 248px minimum, so a narrow
78+
// window yields a width >= MIN instead of a sub-minimum sliver.
79+
var defaultSidebarWidth = 248;
7880
try {
7981
var stored = localStorage.getItem('sidebar-state');
8082
if (stored) {
@@ -87,21 +89,18 @@ export default function RootLayout({ children }: { children: React.ReactNode })
8789
document.documentElement.setAttribute('data-sidebar-collapsed', '');
8890
} else {
8991
var width = state && state.sidebarWidth;
90-
var maxSidebarWidth = window.innerWidth * 0.3;
91-
92-
if (width >= 248 && width <= maxSidebarWidth) {
93-
document.documentElement.style.setProperty('--sidebar-width', width + 'px');
94-
} else if (width > maxSidebarWidth) {
95-
document.documentElement.style.setProperty('--sidebar-width', maxSidebarWidth + 'px');
96-
} else {
97-
document.documentElement.style.setProperty('--sidebar-width', defaultSidebarWidth);
98-
}
92+
var maxSidebarWidth = Math.max(248, window.innerWidth * 0.3);
93+
var finalWidth =
94+
typeof width === 'number' && isFinite(width)
95+
? Math.min(Math.max(width, 248), maxSidebarWidth)
96+
: defaultSidebarWidth;
97+
document.documentElement.style.setProperty('--sidebar-width', finalWidth + 'px');
9998
}
10099
} else {
101-
document.documentElement.style.setProperty('--sidebar-width', defaultSidebarWidth);
100+
document.documentElement.style.setProperty('--sidebar-width', defaultSidebarWidth + 'px');
102101
}
103102
} catch (e) {
104-
document.documentElement.style.setProperty('--sidebar-width', defaultSidebarWidth);
103+
document.documentElement.style.setProperty('--sidebar-width', defaultSidebarWidth + 'px');
105104
}
106105
107106
// Panel width and active tab

apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/workspace-chrome.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { usePathname } from 'next/navigation'
55
import { cn } from '@/lib/core/utils/cn'
66
import { Sidebar } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
77
import { useFullscreenOriginStore } from '@/stores/fullscreen-origin'
8+
import { useSidebarStore } from '@/stores/sidebar/store'
89

910
const FULLSCREEN_SUFFIXES = ['/upgrade'] as const
1011

@@ -46,12 +47,44 @@ export function WorkspaceChrome({ children }: WorkspaceChromeProps) {
4647

4748
const setOrigin = useFullscreenOriginStore((s) => s.setOrigin)
4849

50+
const hasHydrated = useSidebarStore((s) => s._hasHydrated)
51+
const syncSidebarWidth = useSidebarStore((s) => s.syncWidth)
52+
4953
// Remember the last non-fullscreen page so a fullscreen route's Back control
5054
// can return there, deterministically and for any trigger.
5155
useEffect(() => {
5256
if (pathname && !isFullscreen) setOrigin(pathname)
5357
}, [pathname, isFullscreen, setOrigin])
5458

59+
// Re-apply the sidebar width whenever this persistent shell sees a navigation.
60+
// The blocking script in the document head only runs on full page loads and
61+
// store rehydration only fires once, so a soft navigation can leave
62+
// `--sidebar-width` stuck at its `0px` default — collapsing the sidebar to
63+
// nothing with no reachable control to bring it back. Re-syncing here recovers
64+
// that state. Gated on hydration so it never clobbers the persisted value with
65+
// store defaults during the pre-hydration window.
66+
useEffect(() => {
67+
if (hasHydrated) syncSidebarWidth()
68+
}, [pathname, hasHydrated, syncSidebarWidth])
69+
70+
// Re-clamp the width when the window shrinks below what the persisted width
71+
// allows, so the sidebar can never grow wider than the viewport permits.
72+
useEffect(() => {
73+
let rafId: number | null = null
74+
const onResize = () => {
75+
if (rafId !== null) return
76+
rafId = requestAnimationFrame(() => {
77+
rafId = null
78+
syncSidebarWidth()
79+
})
80+
}
81+
window.addEventListener('resize', onResize)
82+
return () => {
83+
if (rafId !== null) cancelAnimationFrame(rafId)
84+
window.removeEventListener('resize', onResize)
85+
}
86+
}, [syncSidebarWidth])
87+
5588
return (
5689
<div className='flex min-h-0 flex-1'>
5790
<div
Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback } from 'react'
1+
import { useCallback, useEffect, useRef } from 'react'
22
import { SIDEBAR_WIDTH } from '@/stores/constants'
33
import { useSidebarStore } from '@/stores/sidebar/store'
44

@@ -7,60 +7,87 @@ import { useSidebarStore } from '@/stores/sidebar/store'
77
*
88
* Architecture (confirmed industry best-practice for resize handles):
99
*
10-
* mousedown → add `is-resizing` class directly to DOM (no React round-trip,
11-
* so the CSS width transition is suppressed from the very first frame)
12-
* mousemove → write to --sidebar-width CSS custom property inside a
13-
* requestAnimationFrame callback (aligns work with the browser
14-
* paint cycle; mousemove fires faster than 60fps on modern hardware
15-
* so without RAF we'd do redundant writes between paints)
16-
* mouseup → cancel any pending RAF, persist final width to Zustand once
17-
* (one React re-render to save to localStorage and sync
18-
* components that read sidebarWidth from state)
10+
* pointerdown → capture the pointer on the handle (so move/up keep arriving
11+
* even when the cursor leaves the window or crosses an iframe),
12+
* add `is-resizing` class directly to the DOM (no React
13+
* round-trip, so the CSS width transition is suppressed from the
14+
* very first frame)
15+
* pointermove → write to --sidebar-width inside a requestAnimationFrame
16+
* callback (aligns work with the browser paint cycle)
17+
* pointerup → cancel any pending RAF, tear down, persist final width to
18+
* Zustand once (one React re-render to save to localStorage)
19+
*
20+
* The drag is torn down by `pointerup`, `pointercancel`, or window `blur`, so an
21+
* interrupted gesture (release outside the window, alt-tab, context menu, the OS
22+
* stealing focus) can never leave the `is-resizing` / `sidebar-resizing` classes
23+
* stuck — which would otherwise freeze the sidebar at a tiny width with the
24+
* collapse transition permanently disabled. A single-flight guard prevents
25+
* stacking listeners across rapid presses, and an unmount cleanup tears down a
26+
* drag still in flight when the sidebar unmounts (e.g. route change).
1927
*/
2028
export function useSidebarResize() {
2129
const setSidebarWidth = useSidebarStore((s) => s.setSidebarWidth)
30+
const cleanupRef = useRef<(() => void) | null>(null)
31+
32+
const handlePointerDown = useCallback(
33+
(e: React.PointerEvent<HTMLElement>) => {
34+
if (cleanupRef.current) return
2235

23-
const handleMouseDown = useCallback(() => {
24-
const sidebar = document.querySelector<HTMLElement>('.sidebar-container')
25-
sidebar?.classList.add('is-resizing')
26-
document.documentElement.classList.add('sidebar-resizing')
27-
document.body.style.cursor = 'ew-resize'
28-
document.body.style.userSelect = 'none'
36+
const handle = e.currentTarget
37+
const pointerId = e.pointerId
38+
const sidebar = document.querySelector<HTMLElement>('.sidebar-container')
39+
sidebar?.classList.add('is-resizing')
40+
document.documentElement.classList.add('sidebar-resizing')
41+
document.body.style.cursor = 'ew-resize'
42+
document.body.style.userSelect = 'none'
43+
handle.setPointerCapture?.(pointerId)
2944

30-
let rafId: number | null = null
45+
let rafId: number | null = null
3146

32-
const onMouseMove = (e: MouseEvent) => {
33-
if (rafId !== null) cancelAnimationFrame(rafId)
34-
rafId = requestAnimationFrame(() => {
35-
const clamped = Math.min(
36-
Math.max(e.clientX, SIDEBAR_WIDTH.MIN),
37-
window.innerWidth * SIDEBAR_WIDTH.MAX_PERCENTAGE
38-
)
39-
document.documentElement.style.setProperty('--sidebar-width', `${clamped}px`)
40-
rafId = null
41-
})
42-
}
47+
const onPointerMove = (ev: PointerEvent) => {
48+
if (rafId !== null) cancelAnimationFrame(rafId)
49+
rafId = requestAnimationFrame(() => {
50+
const max = Math.max(SIDEBAR_WIDTH.MIN, window.innerWidth * SIDEBAR_WIDTH.MAX_PERCENTAGE)
51+
const clamped = Math.min(Math.max(ev.clientX, SIDEBAR_WIDTH.MIN), max)
52+
document.documentElement.style.setProperty('--sidebar-width', `${clamped}px`)
53+
rafId = null
54+
})
55+
}
56+
57+
const cleanup = () => {
58+
if (rafId !== null) {
59+
cancelAnimationFrame(rafId)
60+
rafId = null
61+
}
62+
sidebar?.classList.remove('is-resizing')
63+
document.documentElement.classList.remove('sidebar-resizing')
64+
document.body.style.cursor = ''
65+
document.body.style.userSelect = ''
66+
if (handle.hasPointerCapture?.(pointerId)) handle.releasePointerCapture(pointerId)
67+
document.removeEventListener('pointermove', onPointerMove)
68+
document.removeEventListener('pointerup', endDrag)
69+
document.removeEventListener('pointercancel', endDrag)
70+
window.removeEventListener('blur', endDrag)
71+
cleanupRef.current = null
72+
}
4373

44-
const onMouseUp = () => {
45-
if (rafId !== null) {
46-
cancelAnimationFrame(rafId)
47-
rafId = null
74+
function endDrag() {
75+
cleanup()
76+
const raw = document.documentElement.style.getPropertyValue('--sidebar-width')
77+
const finalWidth = Number.parseFloat(raw)
78+
if (!Number.isNaN(finalWidth)) setSidebarWidth(finalWidth)
4879
}
49-
sidebar?.classList.remove('is-resizing')
50-
document.documentElement.classList.remove('sidebar-resizing')
51-
document.body.style.cursor = ''
52-
document.body.style.userSelect = ''
53-
document.removeEventListener('mousemove', onMouseMove)
54-
document.removeEventListener('mouseup', onMouseUp)
5580

56-
const raw = document.documentElement.style.getPropertyValue('--sidebar-width')
57-
const finalWidth = Number.parseFloat(raw)
58-
if (!Number.isNaN(finalWidth)) setSidebarWidth(finalWidth)
59-
}
81+
cleanupRef.current = cleanup
82+
document.addEventListener('pointermove', onPointerMove)
83+
document.addEventListener('pointerup', endDrag)
84+
document.addEventListener('pointercancel', endDrag)
85+
window.addEventListener('blur', endDrag)
86+
},
87+
[setSidebarWidth]
88+
)
6089

61-
document.addEventListener('mousemove', onMouseMove)
62-
document.addEventListener('mouseup', onMouseUp)
63-
}, [setSidebarWidth])
90+
useEffect(() => () => cleanupRef.current?.(), [])
6491

65-
return { handleMouseDown }
92+
return { handlePointerDown }
6693
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ export const Sidebar = memo(function Sidebar() {
492492
},
493493
})
494494

495-
const { handleMouseDown } = useSidebarResize()
495+
const { handlePointerDown } = useSidebarResize()
496496

497497
const {
498498
regularWorkflows,
@@ -1772,7 +1772,7 @@ export const Sidebar = memo(function Sidebar() {
17721772
'absolute top-0 right-0 bottom-0 z-20 w-[8px] translate-x-1/2',
17731773
isCollapsed ? 'cursor-e-resize' : 'cursor-ew-resize'
17741774
)}
1775-
onMouseDown={isCollapsed ? undefined : handleMouseDown}
1775+
onPointerDown={isCollapsed ? undefined : handlePointerDown}
17761776
onClick={isCollapsed ? toggleCollapsed : undefined}
17771777
onKeyDown={handleEdgeKeyDown}
17781778
role={isCollapsed ? 'button' : 'separator'}

apps/sim/blocks/blocks/airweave.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const AirweaveBlock: BlockConfig<AirweaveSearchResponse> = {
1414
category: 'tools',
1515
integrationType: IntegrationType.Search,
1616
bgColor: '#6366F1',
17+
iconColor: '#6366F1',
1718
icon: AirweaveIcon,
1819
subBlocks: [
1920
{

apps/sim/blocks/blocks/algolia.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const AlgoliaBlock: BlockConfig = {
1212
category: 'tools',
1313
integrationType: IntegrationType.Search,
1414
bgColor: '#003DFF',
15+
iconColor: '#003DFF',
1516
icon: AlgoliaIcon,
1617
authMode: AuthMode.ApiKey,
1718

apps/sim/blocks/blocks/amplitude.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const AmplitudeBlock: BlockConfig = {
1111
category: 'tools',
1212
integrationType: IntegrationType.Analytics,
1313
bgColor: '#1B1F3B',
14+
iconColor: '#1F77E0',
1415
icon: AmplitudeIcon,
1516
authMode: AuthMode.ApiKey,
1617

apps/sim/blocks/blocks/ashby.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const AshbyBlock: BlockConfig = {
1212
category: 'tools',
1313
integrationType: IntegrationType.HR,
1414
bgColor: '#5D4ED6',
15+
iconColor: '#5D4ED6',
1516
icon: AshbyIcon,
1617
authMode: AuthMode.ApiKey,
1718

apps/sim/blocks/blocks/athena.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const AthenaBlock: BlockConfig<
3131
category: 'tools',
3232
integrationType: IntegrationType.Analytics,
3333
bgColor: 'linear-gradient(45deg, #4D27A8 0%, #A166FF 100%)',
34+
iconColor: '#A166FF',
3435
icon: AthenaIcon,
3536
subBlocks: [
3637
{

apps/sim/blocks/blocks/cloudformation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const CloudFormationBlock: BlockConfig<
2929
integrationType: IntegrationType.DevOps,
3030
docsLink: 'https://docs.sim.ai/tools/cloudformation',
3131
bgColor: 'linear-gradient(45deg, #B0084D 0%, #FF4F8B 100%)',
32+
iconColor: '#FF4F8B',
3233
icon: CloudFormationIcon,
3334
subBlocks: [
3435
{

0 commit comments

Comments
 (0)