Skip to content

Commit c568401

Browse files
authored
fix(sidebar): extend faded labels until row actions appear (#7874)
1 parent dec53e2 commit c568401

12 files changed

Lines changed: 342 additions & 195 deletions

File tree

‎apps/sim/app/o/[organizationId]/components/organization-sidebar/components/chats-section/chats-section.tsx‎

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ChatNavigationLink,
99
CollapsedChatFlyoutItem,
1010
CollapsedSidebarMenu,
11+
SidebarRowActions,
1112
SidebarSection,
1213
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components'
1314
import { SidebarRenameRow } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-rename-row'
@@ -56,30 +57,27 @@ function ChatRow({
5657
href={chat.href}
5758
chatId={chat.id}
5859
isCurrentRoute={isCurrentRoute}
59-
className={chipVariants({ active: isCurrentRoute || isMenuOpen, fullWidth: true })}
60+
className={cn(
61+
chipVariants({ active: isCurrentRoute || isMenuOpen, fullWidth: true }),
62+
'group/sidebar-row'
63+
)}
6064
onContextMenu={(e) => onContextMenu(e, chat.id)}
6165
>
6266
<OverflowText label={chat.name} className='flex-1 text-[var(--text-body)]' />
63-
<div className='relative flex size-[18px] shrink-0 items-center justify-center'>
64-
{showStatusDot && (
65-
<span
66-
aria-hidden='true'
67-
className={cn(
68-
'size-[6px] rounded-full transition-opacity',
69-
isMenuOpen ? 'opacity-0' : 'group-focus-within:opacity-0 group-hover:opacity-0'
70-
)}
71-
style={{ backgroundColor: chat.isActive ? '#EAB308' : 'var(--brand-accent)' }}
72-
/>
73-
)}
74-
{!showStatusDot && chat.isPinned && (
75-
<Pin
76-
aria-hidden='true'
77-
className={cn(
78-
'absolute size-[12px] text-[var(--text-icon)] transition-opacity',
79-
isMenuOpen ? 'opacity-0' : 'group-focus-within:opacity-0 group-hover:opacity-0'
80-
)}
81-
/>
82-
)}
67+
<SidebarRowActions
68+
open={isMenuOpen}
69+
indicator={
70+
showStatusDot ? (
71+
<span
72+
aria-hidden='true'
73+
className='size-[6px] rounded-full'
74+
style={{ backgroundColor: chat.isActive ? '#EAB308' : 'var(--brand-accent)' }}
75+
/>
76+
) : chat.isPinned ? (
77+
<Pin aria-hidden='true' className='size-[12px] text-[var(--text-icon)]' />
78+
) : undefined
79+
}
80+
>
8381
<button
8482
type='button'
8583
aria-label='Chat options'
@@ -89,14 +87,11 @@ function ChatRow({
8987
e.stopPropagation()
9088
onMoreClick(e, chat.id)
9189
}}
92-
className={cn(
93-
'absolute inset-0 flex items-center justify-center rounded-sm opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100',
94-
isMenuOpen && 'opacity-100'
95-
)}
90+
className='flex size-[18px] items-center justify-center rounded-sm'
9691
>
9792
<MoreHorizontal className='size-[14px] text-[var(--text-icon)]' />
9893
</button>
99-
</div>
94+
</SidebarRowActions>
10095
</ChatNavigationLink>
10196
)
10297
}

‎apps/sim/app/o/[organizationId]/components/organization-sidebar/components/workspaces-section/workspace-list.tsx‎

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { WorkspaceContextMenu } from '@/components/workspaces/workspace-context-
1818
import { getWorkspaceInitial } from '@/lib/workspaces/initials'
1919
import { useOrganizationWorkspaces } from '@/app/o/[organizationId]/components/organization-sidebar/hooks/use-organization-workspaces'
2020
import { SidebarRenameRow } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-rename-row'
21+
import { SidebarRowActions } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-row-actions'
2122
import { useFlyoutInlineRename } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-flyout-inline-rename'
2223
import type { useHoverMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-hover-menu'
2324
import { useToggleWorkspacePin, useUpdateWorkspace } from '@/hooks/queries/workspace'
@@ -139,6 +140,7 @@ export function WorkspaceList({ organizationId, pathname, flyout }: WorkspaceLis
139140
key={workspace.id}
140141
asChild
141142
active={isActive || isMenuOpen}
143+
actionOpen={isMenuOpen}
142144
onPointerMove={(event) => {
143145
if (menu.isOpen || rename.editingId) event.preventDefault()
144146
}}
@@ -176,34 +178,36 @@ export function WorkspaceList({ organizationId, pathname, flyout }: WorkspaceLis
176178
<SettingsGuardedLink
177179
key={workspace.id}
178180
href={href}
179-
className={chipVariants({ active: isActive || isMenuOpen, fullWidth: true })}
181+
className={cn(
182+
chipVariants({ active: isActive || isMenuOpen, fullWidth: true }),
183+
'group/sidebar-row'
184+
)}
180185
onContextMenu={(event) => openMenu(event, workspace.id)}
181186
>
182187
{label}
183-
<div className='relative flex size-[18px] shrink-0 items-center justify-center'>
184-
{isPinned && (
185-
<Pin
186-
role='img'
187-
aria-label='Pinned'
188-
className={cn(
189-
'absolute size-[12px] text-[var(--text-icon)] group-focus-within:opacity-0 group-hover:opacity-0',
190-
isMenuOpen && 'opacity-0'
191-
)}
192-
/>
193-
)}
188+
<SidebarRowActions
189+
open={isMenuOpen}
190+
indicator={
191+
isPinned ? (
192+
<Pin
193+
role='img'
194+
aria-label='Pinned'
195+
aria-hidden={false}
196+
className='size-[12px] text-[var(--text-icon)]'
197+
/>
198+
) : undefined
199+
}
200+
>
194201
<button
195202
type='button'
196203
aria-label={`Options for ${workspace.name}`}
197204
onPointerDown={() => menu.preventDismiss()}
198205
onClick={onMoreClick}
199-
className={cn(
200-
'absolute inset-0 flex items-center justify-center rounded-sm opacity-0 group-focus-within:opacity-100 group-hover:opacity-100',
201-
isMenuOpen && 'opacity-100'
202-
)}
206+
className='flex size-[18px] items-center justify-center rounded-sm'
203207
>
204208
<MoreHorizontal className='size-[14px] text-[var(--text-icon)]' />
205209
</button>
206-
</div>
210+
</SidebarRowActions>
207211
</SettingsGuardedLink>
208212
)
209213
})}

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx‎

Lines changed: 39 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,13 @@ import {
1010
DropdownMenu,
1111
DropdownMenuContent,
1212
DropdownMenuItem,
13+
DropdownMenuItemAction,
14+
DropdownMenuLabel,
1315
DropdownMenuSeparator,
1416
DropdownMenuTrigger,
1517
Duplicate,
1618
Layout,
1719
MoreHorizontal,
18-
Popover,
19-
PopoverContent,
20-
PopoverItem,
21-
PopoverScrollArea,
22-
PopoverSection,
23-
PopoverTrigger,
2420
Trash,
2521
toast,
2622
} from '@sim/emcn'
@@ -913,63 +909,55 @@ export const Panel = memo(function Panel() {
913909
<Button variant='ghost' className='p-0' onClick={handleCopilotNewChat}>
914910
<Plus className='size-[14px]' />
915911
</Button>
916-
<Popover
912+
<DropdownMenu
917913
open={isCopilotHistoryOpen}
918914
onOpenChange={(open) => {
919915
setIsCopilotHistoryOpen(open)
920916
if (open) loadCopilotChats()
921917
}}
922918
>
923-
<PopoverTrigger asChild>
924-
<Button variant='ghost' className='p-0'>
919+
<DropdownMenuTrigger asChild>
920+
<Button variant='ghost' className='p-0' aria-label='Chat history'>
925921
<BubbleChatDelay className='size-[14px]' />
926922
</Button>
927-
</PopoverTrigger>
928-
<PopoverContent align='end' side='bottom' sideOffset={8} maxHeight={280}>
923+
</DropdownMenuTrigger>
924+
<DropdownMenuContent
925+
align='end'
926+
side='bottom'
927+
sideOffset={8}
928+
className='max-h-[280px] w-[220px]'
929+
>
929930
{copilotChatList.length === 0 ? (
930-
<div className='px-1.5 py-4 text-center text-caption text-muted-foreground'>
931-
No chats yet
932-
</div>
931+
<DropdownMenuItem disabled>No chats yet</DropdownMenuItem>
933932
) : (
934-
<PopoverScrollArea>
935-
<PopoverSection className='pt-0'>Recent</PopoverSection>
936-
<div className='flex flex-col gap-0.5'>
937-
{copilotChatList.map((chat) => (
938-
<div key={chat.id} className='group'>
939-
<PopoverItem
940-
active={copilotChatId === chat.id}
941-
onClick={() => handleCopilotSelectChat(chat)}
933+
<>
934+
<DropdownMenuLabel>Recent</DropdownMenuLabel>
935+
{copilotChatList.map((chat) => (
936+
<DropdownMenuItem
937+
key={chat.id}
938+
active={copilotChatId === chat.id}
939+
actionOpen={copilotChatId === chat.id}
940+
onSelect={() => handleCopilotSelectChat(chat)}
941+
action={
942+
<DropdownMenuItemAction
943+
aria-label='Delete chat'
944+
onClick={() => handleCopilotDeleteChat(chat.id)}
942945
>
943-
<ConversationListItem
944-
title={chat.title || 'New Chat'}
945-
isActive={Boolean(chat.activeStreamId)}
946-
titleClassName='text-small'
947-
actions={
948-
<div
949-
className={`flex shrink-0 items-center gap-1 ${copilotChatId !== chat.id ? 'opacity-0 transition-opacity group-hover:opacity-100' : ''}`}
950-
>
951-
<Button
952-
variant='ghost'
953-
className='size-[16px] p-0'
954-
onClick={(e) => {
955-
e.stopPropagation()
956-
handleCopilotDeleteChat(chat.id)
957-
}}
958-
aria-label='Delete chat'
959-
>
960-
<Trash className='size-[10px]' />
961-
</Button>
962-
</div>
963-
}
964-
/>
965-
</PopoverItem>
966-
</div>
967-
))}
968-
</div>
969-
</PopoverScrollArea>
946+
<Trash />
947+
</DropdownMenuItemAction>
948+
}
949+
>
950+
<ConversationListItem
951+
title={chat.title || 'New Chat'}
952+
isActive={Boolean(chat.activeStreamId)}
953+
titleClassName='text-small'
954+
/>
955+
</DropdownMenuItem>
956+
))}
957+
</>
970958
)}
971-
</PopoverContent>
972-
</Popover>
959+
</DropdownMenuContent>
960+
</DropdownMenu>
973961
</div>
974962
</div>
975963

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/collapsed-sidebar-menu/collapsed-sidebar-menu.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ export function CollapsedSidebarMenu({
257257
<DropdownMenuContent
258258
side='right'
259259
align='start'
260+
className='w-[220px]'
260261
sideOffset={8}
261262
{...hover.contentProps}
262263
onPointerMoveCapture={isEditing ? holdRowFocus : undefined}
@@ -327,13 +328,13 @@ export function CollapsedChatFlyoutItem({
327328
<DropdownMenuItem
328329
asChild
329330
active={isCurrentRoute || isMenuOpen}
331+
actionOpen={isMenuOpen}
330332
action={
331333
showActions ? (
332334
<DropdownMenuItemAction
333335
aria-label='Chat options'
334336
onPointerDown={onMorePointerDown}
335337
onClick={(e) => onMoreClick?.(e, chat.id)}
336-
className={cn(isMenuOpen && 'opacity-100')}
337338
>
338339
<MoreHorizontal />
339340
</DropdownMenuItemAction>
@@ -409,6 +410,7 @@ export function CollapsedWorkflowFlyoutItem({
409410
<DropdownMenuItem
410411
asChild
411412
active={isCurrentRoute || actionsOpen}
413+
actionOpen={actionsOpen}
412414
onPointerMove={actionsOpen ? holdRowFocus : undefined}
413415
onPointerLeave={actionsOpen ? holdRowFocus : undefined}
414416
action={
@@ -423,7 +425,6 @@ export function CollapsedWorkflowFlyoutItem({
423425
<DropdownMenuItemAction
424426
aria-label='Workflow options'
425427
onClick={() => setActionsOpen((prev) => !prev)}
426-
className={cn(actionsOpen && 'opacity-100')}
427428
>
428429
<MoreHorizontal />
429430
</DropdownMenuItemAction>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { SidebarRowActions } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-row-actions'
12
export { ChatNavigationLink } from './chat-navigation-link/chat-navigation-link'
23
export {
34
CollapsedChatFlyoutItem,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { ReactNode } from 'react'
2+
import { cn } from '@sim/emcn'
3+
4+
interface SidebarRowActionsProps {
5+
children: ReactNode
6+
indicator?: ReactNode
7+
open?: boolean
8+
revealOnHover?: boolean
9+
}
10+
11+
/** Reclaims idle action space while retaining indicators and touch/keyboard access. */
12+
export function SidebarRowActions({
13+
children,
14+
indicator,
15+
open = false,
16+
revealOnHover = true,
17+
}: SidebarRowActionsProps) {
18+
return (
19+
<div
20+
className={cn(
21+
'relative size-[18px] shrink-0 items-center justify-center gap-1.5 [@media(hover:none)]:w-auto',
22+
indicator || open ? 'flex' : 'hidden',
23+
revealOnHover &&
24+
'group-focus-within/sidebar-row:flex group-hover/sidebar-row:flex [@media(hover:none)]:flex'
25+
)}
26+
>
27+
{indicator && (
28+
<span
29+
className={cn(
30+
'pointer-events-none flex size-[18px] shrink-0 items-center justify-center transition-opacity',
31+
open && '[@media(hover:hover)]:opacity-0',
32+
revealOnHover &&
33+
'[@media(hover:hover)]:group-focus-within/sidebar-row:opacity-0 [@media(hover:hover)]:group-hover/sidebar-row:opacity-0'
34+
)}
35+
>
36+
{indicator}
37+
</span>
38+
)}
39+
<div
40+
className={cn(
41+
'pointer-events-none absolute inset-0 flex items-center justify-center opacity-0 transition-opacity [@media(hover:none)]:static',
42+
open && 'pointer-events-auto opacity-100',
43+
revealOnHover &&
44+
'group-focus-within/sidebar-row:pointer-events-auto group-focus-within/sidebar-row:opacity-100 group-hover/sidebar-row:pointer-events-auto group-hover/sidebar-row:opacity-100 [@media(hover:none)]:pointer-events-auto [@media(hover:none)]:opacity-100'
45+
)}
46+
>
47+
{children}
48+
</div>
49+
</div>
50+
)
51+
}

0 commit comments

Comments
 (0)