Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/sim/app/_styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/
:root {
--sidebar-width: 0px; /* 0 outside workspace; blocking script always sets actual value on workspace pages */
--sidebar-collapsed-width: 51px; /* icon rail on web; desktop overrides to 0 before first paint */
--sidebar-collapsed-width: 48px; /* icon rail on web; desktop overrides to 0 before first paint */
--sidebar-expanded-width: 238px; /* SIDEBAR_WIDTH.DEFAULT; the width to restore to, held even while collapsed */
--desktop-title-bar-height: 0px; /* macOS traffic-light lane; desktop overrides before first paint */
--workspace-content-title-bar-inset: 0px; /* lane the content pane must leave clear; only non-zero when the pane, not the sidebar, sits under it */
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
// The macOS desktop shell overlays native traffic lights on the
// workspace. Mark it before first paint so the sidebar reserves
// its inset title-bar lane without a post-hydration layout shift.
var collapsedSidebarWidth = 51;
var collapsedSidebarWidth = 48;
try {
if (window.simDesktop && /Mac/i.test(navigator.userAgent)) {
document.documentElement.setAttribute('data-sim-desktop-title-bar', 'inset');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ export const RESOURCE_TAB_ICON_CLASS = 'size-[16px] text-[var(--text-icon)]'
/** Shared geometry for the resource header and controls positioned over it. */
export const RESOURCE_HEADER_CLASSES = {
layout:
'[--resource-header-controls-height:43px] [--resource-header-end-inset:16px] [--resource-header-fixed-reserve:54px]',
'[--resource-header-controls-height:43px] [--resource-header-end-inset:16px] [--resource-header-fixed-reserve:54px] [--resource-header-toggle-size:30px]',
bar: 'h-[calc(var(--resource-header-controls-height)_+_1px)]',
controls: 'h-[var(--resource-header-controls-height)]',
contentTop: 'top-[8.5px]',
overlay: 'absolute top-0 flex h-[var(--resource-header-controls-height)] items-center',
startPadding: 'pl-[var(--resource-header-end-inset)]',
endPadding: 'pr-[var(--resource-header-fixed-reserve)]',
endPosition: 'right-[var(--resource-header-end-inset)]',
adjacentEndPosition: 'right-[var(--resource-header-fixed-reserve)]',
/**
* Sits a control 1px clear of the overlaid 30px collapse toggle — the same
* chip-to-chip gap the sidebar header cluster uses (`gap-[1px]`), so the
* credits chip and the toggle read as one cluster across both surfaces.
*/
adjacentEndPosition:
'right-[calc(var(--resource-header-end-inset)_+_var(--resource-header-toggle-size)_+_1px)]',
emptyAddOffset: '-translate-x-1.5',
} as const
65 changes: 32 additions & 33 deletions apps/sim/app/workspace/[workspaceId]/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,21 +283,32 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
setIsResourceCollapsed(true)
}, [clearWidth])

const clearResourceActivity = useCallback((resourceId: string) => {
setResourceActivityIds((current) => {
if (!current.has(resourceId)) return current
const next = new Set(current)
next.delete(resourceId)
return next
})
}, [])

const expandResource = () => {
userOwnsResourceViewRef.current = true
const activeResourceId = activeResourceParamRef.current
if (activeResourceId) clearResourceActivity(activeResourceId)
setIsResourceCollapsed(false)
}

const selectResourceFromUser = useCallback(
(resourceId: string) => {
userOwnsResourceViewRef.current = true
setResourceActivityIds((current) => {
if (!current.has(resourceId)) return current
const next = new Set(current)
next.delete(resourceId)
return next
})
clearResourceActivity(resourceId)
if (effectiveActiveResourceIdRef.current === resourceId) return
effectiveActiveResourceIdRef.current = resourceId
activeResourceParamRef.current = resourceId
setActiveResourceId(resourceId)
},
[setActiveResourceId]
[setActiveResourceId, clearResourceActivity]
)

const addResourceFromUser = useCallback(
Expand Down Expand Up @@ -581,9 +592,17 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
{showEmptyState && (
<div
className={cn(
'absolute z-10',
RESOURCE_HEADER_CLASSES.contentTop,
RESOURCE_HEADER_CLASSES.adjacentEndPosition
'z-10',
RESOURCE_HEADER_CLASSES.overlay,
// Collapsed, the expand toggle overlays this corner, so the chip
// yields the fixed reserve; open, the toggle lives in the panel's
// corner and the chip takes the standard end inset itself.
isResourceCollapsed
? RESOURCE_HEADER_CLASSES.adjacentEndPosition
: RESOURCE_HEADER_CLASSES.endPosition,
skipResourceTransition
? 'transition-none'
: 'transition-[right] duration-200 [transition-timing-function:cubic-bezier(0.25,0.1,0.25,1)]'
)}
>
<CreditsChip />
Expand Down Expand Up @@ -690,34 +709,14 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
</MothershipResourcesProvider>

<div
className={cn(
'absolute top-0 z-30 flex items-center',
RESOURCE_HEADER_CLASSES.controls,
RESOURCE_HEADER_CLASSES.endPosition
)}
className={cn('z-30', RESOURCE_HEADER_CLASSES.overlay, RESOURCE_HEADER_CLASSES.endPosition)}
>
<Button
variant='ghost'
size={null}
type='button'
onClick={
isResourceCollapsed
? () => {
userOwnsResourceViewRef.current = true
const activeResourceId = activeResourceParamRef.current
if (activeResourceId) {
setResourceActivityIds((current) => {
if (!current.has(activeResourceId)) return current
const next = new Set(current)
next.delete(activeResourceId)
return next
})
}
setIsResourceCollapsed(false)
}
: collapseResource
}
className='size-[30px] rounded-[8px] hover-hover:bg-[var(--surface-active)]'
onClick={isResourceCollapsed ? expandResource : collapseResource}
className='size-[var(--resource-header-toggle-size)] rounded-[8px] hover-hover:bg-[var(--surface-active)]'
aria-label={isResourceCollapsed ? 'Expand resource view' : 'Collapse resource view'}
>
<span className='relative'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { File, Folder, MoreHorizontal, Pencil, Plus, SquareArrowUpRight } from '
import Link from 'next/link'
import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace'
import { ConversationListItem } from '@/app/workspace/[workspaceId]/components'
import { SIDEBAR_RAIL_CHIP_CLASS } from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
import type { useHoverMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
import { interleaveSiblings } from '@/app/workspace/[workspaceId]/w/components/sidebar/utils'
import type { WorkspaceFileFolderApi } from '@/hooks/queries/workspace-file-folders'
Expand Down Expand Up @@ -209,7 +210,7 @@ export function CollapsedSidebarMenu({
<button
type='button'
aria-label={ariaLabel}
className={chipVariants({ fullWidth: true })}
className={cn(chipVariants({ fullWidth: true }), SIDEBAR_RAIL_CHIP_CLASS)}
>
{icon}
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
SIDEBAR_DIVIDER_PAD_ABOVE_CLASS,
SIDEBAR_DIVIDER_PAD_BELOW_CLASS,
SIDEBAR_ITEM_GAP_CLASS,
SIDEBAR_RAIL_CHIP_CLASS,
SIDEBAR_SECTION_GAP_CLASS,
} from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
import { SidebarTooltip } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
Expand Down Expand Up @@ -305,7 +306,11 @@ export function SettingsSidebar({
)}
>
<SidebarTooltip label='Back' enabled={showCollapsedTooltips}>
<button type='button' onClick={handleBack} className={chipVariants({ fullWidth: true })}>
<button
type='button'
onClick={handleBack}
className={cn(chipVariants({ fullWidth: true }), SIDEBAR_RAIL_CHIP_CLASS)}
>
{/* The 16px slot every settings row gives its icon, so Back's label starts on their baseline. */}
<span aria-hidden className={cn(chipIconSlotClass, 'text-[var(--text-icon)]')}>
<ChevronLeft className='size-[14px]' />
Expand Down Expand Up @@ -352,7 +357,10 @@ export function SettingsSidebar({
(item.id === 'inbox'
? !inboxEntitled
: !subscriptionAccess.hasUsableMaxAccess)
const itemClassName = chipVariants({ active, fullWidth: true })
const itemClassName = cn(
chipVariants({ active, fullWidth: true }),
SIDEBAR_RAIL_CHIP_CLASS
)
const content = (
<>
<Icon className={chipContentIconClass} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import { getDesktopUpdates } from '@/lib/desktop'
import { getUserColor } from '@/lib/workspaces/colors'
import { useWorkspaceHostContext } from '@/app/workspace/[workspaceId]/providers/workspace-host-provider'
import type { SettingsSection } from '@/app/workspace/[workspaceId]/settings/navigation'
import { SIDEBAR_ITEM_GAP_CLASS } from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
import {
SIDEBAR_ITEM_GAP_CLASS,
SIDEBAR_RAIL_CHIP_CLASS,
} from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
import { SidebarTooltip } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
import { useUserProfile } from '@/hooks/queries/user-profile'
import { useWorkspaceInvitePolicy } from '@/hooks/use-workspace-invite-policy'
Expand Down Expand Up @@ -237,11 +240,11 @@ export function SidebarFooter({
<button
type='button'
data-item-id='profile'
className={
isCollapsed
? cn(chipVariants({ fullWidth: true }), 'min-w-0')
: cn(chipVariants(), 'max-w-full')
}
className={cn(
chipVariants({ fullWidth: isCollapsed }),
isCollapsed ? 'min-w-0' : 'max-w-full',
SIDEBAR_RAIL_CHIP_CLASS
)}
>
{avatar}
{profile ? (
Expand Down Expand Up @@ -293,7 +296,7 @@ export function SidebarFooter({
than the rail, and a shrinking chip would be squeezed onto the avatar.
Holding its size pushes it past the edge, where the aside's clip hides
it until there is room. */
className='flex-shrink-0'
className={cn('flex-shrink-0', SIDEBAR_RAIL_CHIP_CLASS)}
/>
</DropdownMenuTrigger>
</SidebarTooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { DeleteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/
import { CreateWorkspaceModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/create-workspace-modal/create-workspace-modal'
import { ViewInvitationsMenuItem } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/pending-invitations/view-invitations-menu-item'
import { ViewInvitationsModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/pending-invitations/view-invitations-modal'
import { SIDEBAR_RAIL_CHIP_CLASS } from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
import { invitationKeys } from '@/hooks/queries/invitations'
import {
type Workspace,
Expand Down Expand Up @@ -442,7 +443,7 @@ function WorkspaceHeaderImpl({
type='button'
aria-label='Expand sidebar'
onClick={onExpandSidebar}
className={chipVariants({ fullWidth: true })}
className={cn(chipVariants({ fullWidth: true }), SIDEBAR_RAIL_CHIP_CLASS)}
>
<div className='relative flex size-[16px] flex-shrink-0 items-center justify-center'>
{activeWorkspaceFull?.logoUrl ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,34 @@ export const SIDEBAR_ITEM_GAP_CLASS = 'gap-[1px]'
export const SIDEBAR_DIVIDER_PAD_ABOVE_CLASS = 'pb-2'
export const SIDEBAR_DIVIDER_PAD_BELOW_CLASS = 'pt-2'

/**
* Rail-chip geometry for the collapsed sidebar, gated by the `group/rail`
* marker on the sidebar `aside` (the hover-peek card drops `data-collapsed`,
* so peeked rows stay expanded). Collapses a full-width row to the natural
* icon-only chip — 32px, the chip's own `px-2` + 16px glyph, matching the
* header's Search/Collapse pills. Only the WIDTH is overridden: the rail is
* 48px precisely so that the plain 8px item gutter centers the chip
* ((48 − 32) / 2 = 8) and puts the glyph (24px in) on the rail's midline —
* the same 24px column the expanded rows use, so NOTHING moves on toggle.
* At the previous 51px rail those goals were mutually exclusive by 1.5px
* (rail midline 25.5 vs glyph column 24), which produced either a
* left-biased rail or a drift on toggle; keep the rail width and this chip
* width commensurate (rail = chip + 2 × gutter) if either ever changes.
* Collapsing, the width tweens down to 32px on the 175ms curve the rail
* closes on; expanding targets `auto` (not interpolable), so the chip snaps
* to the still-narrow rail's width and stretch-tracks it open. The duration
* is `!important` because the aside zeroes chip transition durations
* (`[&_.group.cursor-pointer]:duration-0`) for instant hover fills — colors
* are excluded from the property list here, so hover fills keep snapping.
*/
export const SIDEBAR_RAIL_CHIP_CLASS = [
'transition-[width]',
'![transition-duration:175ms]',
'[transition-timing-function:cubic-bezier(0.25,0.1,0.25,1)]',
'motion-reduce:!transition-none',
'group-data-[collapsed]/rail:w-[32px]',
].join(' ')

/**
* Nested-selector variants for cmdk-based surfaces (e.g. the search modal).
* Written as complete literal strings so Tailwind's JIT can detect them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
SIDEBAR_DIVIDER_PAD_ABOVE_CLASS,
SIDEBAR_DIVIDER_PAD_BELOW_CLASS,
SIDEBAR_ITEM_GAP_CLASS,
SIDEBAR_RAIL_CHIP_CLASS,
SIDEBAR_SECTION_GAP_CLASS,
} from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
import {
Expand Down Expand Up @@ -355,6 +356,7 @@ const SidebarNavItem = memo(function SidebarNavItem({
leftIcon={item.icon}
active={active}
fullWidth
className={SIDEBAR_RAIL_CHIP_CLASS}
onClick={
item.onClick
? (e) => {
Expand All @@ -374,6 +376,7 @@ const SidebarNavItem = memo(function SidebarNavItem({
leftIcon={item.icon}
active={active}
fullWidth
className={SIDEBAR_RAIL_CHIP_CLASS}
onClick={item.onClick}
>
{item.label}
Expand Down Expand Up @@ -1351,7 +1354,7 @@ export const Sidebar = memo(function Sidebar({
/>
<div className='relative h-full'>
<aside
className='sidebar-container relative h-full overflow-hidden bg-[var(--surface-1)] [&_.group.cursor-pointer]:duration-0'
className='group/rail sidebar-container relative h-full overflow-hidden bg-[var(--surface-1)] [&_.group.cursor-pointer]:duration-0'
data-collapsed={isCollapsed || undefined}
aria-label='Workspace sidebar'
onClick={handleSidebarClick}
Expand Down Expand Up @@ -1404,11 +1407,24 @@ export const Sidebar = memo(function Sidebar({
* between them. `gap-[1px]` rather than `gap-px`: the `px` spacing key
* is remapped to `--border-width`, which thins to 0.5px on hidpi so
* hairline rules stay hairlines.
*
* The expanded width is EXPLICIT (2 icon chips × 32px + the 1px gap;
* 32px when the desktop inset title bar hides the collapse chip), never
* `auto`: `w-0 → auto` cannot interpolate, so on expand the cluster
* snapped to full width while the rail was still 51px wide — and since
* the cluster refuses to flex-shrink (min-width: auto) while the
* workspace chip's wrapper is `min-w-0 flex-1`, the workspace chip
* crushed to zero and the hover-filled Search chip landed exactly under
* the cursor on the workspace icon: a visible flash on every expand.
* With both endpoints explicit, the width tweens in step with the rail
* and the workspace chip keeps its space throughout.
*/}
<div
className={cn(
'flex h-[30px] items-center gap-[1px] overflow-hidden transition-all duration-200',
isCollapsed && 'w-0 opacity-0'
'flex h-[30px] items-center gap-[1px] overflow-hidden transition-all duration-200 [transition-timing-function:cubic-bezier(0.25,0.1,0.25,1)]',
isCollapsed
? 'w-0 opacity-0'
: 'w-[65px] [[data-sim-desktop-title-bar=inset]_&]:w-[32px]'
)}
>
<SidebarTooltip
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/stores/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const SIDEBAR_WIDTH = {
DEFAULT: 238,
MIN: 238,
/** Width when sidebar is collapsed to icon-only mode */
COLLAPSED: 51,
COLLAPSED: 48,
/** Maximum is 30% of viewport, enforced dynamically */
MAX_PERCENTAGE: 0.3,
} as const
Expand Down
Loading