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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ChatNavigationLink,
CollapsedChatFlyoutItem,
CollapsedSidebarMenu,
SidebarRowActions,
SidebarSection,
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components'
import { SidebarRenameRow } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-rename-row'
Expand Down Expand Up @@ -56,30 +57,27 @@ function ChatRow({
href={chat.href}
chatId={chat.id}
isCurrentRoute={isCurrentRoute}
className={chipVariants({ active: isCurrentRoute || isMenuOpen, fullWidth: true })}
className={cn(
chipVariants({ active: isCurrentRoute || isMenuOpen, fullWidth: true }),
'group/sidebar-row'
)}
onContextMenu={(e) => onContextMenu(e, chat.id)}
>
<OverflowText label={chat.name} className='flex-1 text-[var(--text-body)]' />
<div className='relative flex size-[18px] shrink-0 items-center justify-center'>
{showStatusDot && (
<span
aria-hidden='true'
className={cn(
'size-[6px] rounded-full transition-opacity',
isMenuOpen ? 'opacity-0' : 'group-focus-within:opacity-0 group-hover:opacity-0'
)}
style={{ backgroundColor: chat.isActive ? '#EAB308' : 'var(--brand-accent)' }}
/>
)}
{!showStatusDot && chat.isPinned && (
<Pin
aria-hidden='true'
className={cn(
'absolute size-[12px] text-[var(--text-icon)] transition-opacity',
isMenuOpen ? 'opacity-0' : 'group-focus-within:opacity-0 group-hover:opacity-0'
)}
/>
)}
<SidebarRowActions
open={isMenuOpen}
indicator={
showStatusDot ? (
<span
aria-hidden='true'
className='size-[6px] rounded-full'
style={{ backgroundColor: chat.isActive ? '#EAB308' : 'var(--brand-accent)' }}
/>
) : chat.isPinned ? (
<Pin aria-hidden='true' className='size-[12px] text-[var(--text-icon)]' />
) : undefined
}
>
<button
type='button'
aria-label='Chat options'
Expand All @@ -89,14 +87,11 @@ function ChatRow({
e.stopPropagation()
onMoreClick(e, chat.id)
}}
className={cn(
'absolute inset-0 flex items-center justify-center rounded-sm opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100',
isMenuOpen && 'opacity-100'
)}
className='flex size-[18px] items-center justify-center rounded-sm'
>
<MoreHorizontal className='size-[14px] text-[var(--text-icon)]' />
</button>
</div>
</SidebarRowActions>
</ChatNavigationLink>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { WorkspaceContextMenu } from '@/components/workspaces/workspace-context-
import { getWorkspaceInitial } from '@/lib/workspaces/initials'
import { useOrganizationWorkspaces } from '@/app/o/[organizationId]/components/organization-sidebar/hooks/use-organization-workspaces'
import { SidebarRenameRow } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-rename-row'
import { SidebarRowActions } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-row-actions'
import { useFlyoutInlineRename } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-flyout-inline-rename'
import type { useHoverMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-hover-menu'
import { useToggleWorkspacePin, useUpdateWorkspace } from '@/hooks/queries/workspace'
Expand Down Expand Up @@ -139,6 +140,7 @@ export function WorkspaceList({ organizationId, pathname, flyout }: WorkspaceLis
key={workspace.id}
asChild
active={isActive || isMenuOpen}
actionOpen={isMenuOpen}
onPointerMove={(event) => {
if (menu.isOpen || rename.editingId) event.preventDefault()
}}
Expand Down Expand Up @@ -176,34 +178,36 @@ export function WorkspaceList({ organizationId, pathname, flyout }: WorkspaceLis
<SettingsGuardedLink
key={workspace.id}
href={href}
className={chipVariants({ active: isActive || isMenuOpen, fullWidth: true })}
className={cn(
chipVariants({ active: isActive || isMenuOpen, fullWidth: true }),
'group/sidebar-row'
)}
onContextMenu={(event) => openMenu(event, workspace.id)}
>
{label}
<div className='relative flex size-[18px] shrink-0 items-center justify-center'>
{isPinned && (
<Pin
role='img'
aria-label='Pinned'
className={cn(
'absolute size-[12px] text-[var(--text-icon)] group-focus-within:opacity-0 group-hover:opacity-0',
isMenuOpen && 'opacity-0'
)}
/>
)}
<SidebarRowActions
open={isMenuOpen}
indicator={
isPinned ? (
<Pin
role='img'
aria-label='Pinned'
aria-hidden={false}
className='size-[12px] text-[var(--text-icon)]'
/>
) : undefined
}
>
<button
type='button'
aria-label={`Options for ${workspace.name}`}
onPointerDown={() => menu.preventDismiss()}
onClick={onMoreClick}
className={cn(
'absolute inset-0 flex items-center justify-center rounded-sm opacity-0 group-focus-within:opacity-100 group-hover:opacity-100',
isMenuOpen && 'opacity-100'
)}
className='flex size-[18px] items-center justify-center rounded-sm'
>
<MoreHorizontal className='size-[14px] text-[var(--text-icon)]' />
</button>
</div>
</SidebarRowActions>
</SettingsGuardedLink>
)
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,13 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuItemAction,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
Duplicate,
Layout,
MoreHorizontal,
Popover,
PopoverContent,
PopoverItem,
PopoverScrollArea,
PopoverSection,
PopoverTrigger,
Trash,
toast,
} from '@sim/emcn'
Expand Down Expand Up @@ -913,63 +909,55 @@ export const Panel = memo(function Panel() {
<Button variant='ghost' className='p-0' onClick={handleCopilotNewChat}>
<Plus className='size-[14px]' />
</Button>
<Popover
<DropdownMenu
open={isCopilotHistoryOpen}
onOpenChange={(open) => {
setIsCopilotHistoryOpen(open)
if (open) loadCopilotChats()
}}
>
<PopoverTrigger asChild>
<Button variant='ghost' className='p-0'>
<DropdownMenuTrigger asChild>
<Button variant='ghost' className='p-0' aria-label='Chat history'>
<BubbleChatDelay className='size-[14px]' />
</Button>
</PopoverTrigger>
<PopoverContent align='end' side='bottom' sideOffset={8} maxHeight={280}>
</DropdownMenuTrigger>
<DropdownMenuContent
align='end'
side='bottom'
sideOffset={8}
className='max-h-[280px] w-[220px]'
>
{copilotChatList.length === 0 ? (
<div className='px-1.5 py-4 text-center text-caption text-muted-foreground'>
No chats yet
</div>
<DropdownMenuItem disabled>No chats yet</DropdownMenuItem>
) : (
<PopoverScrollArea>
<PopoverSection className='pt-0'>Recent</PopoverSection>
<div className='flex flex-col gap-0.5'>
{copilotChatList.map((chat) => (
<div key={chat.id} className='group'>
<PopoverItem
active={copilotChatId === chat.id}
onClick={() => handleCopilotSelectChat(chat)}
<>
<DropdownMenuLabel>Recent</DropdownMenuLabel>
{copilotChatList.map((chat) => (
<DropdownMenuItem
key={chat.id}
active={copilotChatId === chat.id}
actionOpen={copilotChatId === chat.id}
onSelect={() => handleCopilotSelectChat(chat)}
action={
<DropdownMenuItemAction
aria-label='Delete chat'
onClick={() => handleCopilotDeleteChat(chat.id)}
>
<ConversationListItem
title={chat.title || 'New Chat'}
isActive={Boolean(chat.activeStreamId)}
titleClassName='text-small'
actions={
<div
className={`flex shrink-0 items-center gap-1 ${copilotChatId !== chat.id ? 'opacity-0 transition-opacity group-hover:opacity-100' : ''}`}
>
<Button
variant='ghost'
className='size-[16px] p-0'
onClick={(e) => {
e.stopPropagation()
handleCopilotDeleteChat(chat.id)
}}
aria-label='Delete chat'
>
<Trash className='size-[10px]' />
</Button>
</div>
}
/>
</PopoverItem>
</div>
))}
</div>
</PopoverScrollArea>
<Trash />
</DropdownMenuItemAction>
}
>
<ConversationListItem
title={chat.title || 'New Chat'}
isActive={Boolean(chat.activeStreamId)}
titleClassName='text-small'
/>
</DropdownMenuItem>
))}
</>
)}
</PopoverContent>
</Popover>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ export function CollapsedSidebarMenu({
<DropdownMenuContent
side='right'
align='start'
className='w-[220px]'
sideOffset={8}
{...hover.contentProps}
onPointerMoveCapture={isEditing ? holdRowFocus : undefined}
Expand Down Expand Up @@ -327,13 +328,13 @@ export function CollapsedChatFlyoutItem({
<DropdownMenuItem
asChild
active={isCurrentRoute || isMenuOpen}
actionOpen={isMenuOpen}
action={
showActions ? (
<DropdownMenuItemAction
aria-label='Chat options'
onPointerDown={onMorePointerDown}
onClick={(e) => onMoreClick?.(e, chat.id)}
className={cn(isMenuOpen && 'opacity-100')}
>
<MoreHorizontal />
</DropdownMenuItemAction>
Expand Down Expand Up @@ -409,6 +410,7 @@ export function CollapsedWorkflowFlyoutItem({
<DropdownMenuItem
asChild
active={isCurrentRoute || actionsOpen}
actionOpen={actionsOpen}
onPointerMove={actionsOpen ? holdRowFocus : undefined}
onPointerLeave={actionsOpen ? holdRowFocus : undefined}
action={
Expand All @@ -423,7 +425,6 @@ export function CollapsedWorkflowFlyoutItem({
<DropdownMenuItemAction
aria-label='Workflow options'
onClick={() => setActionsOpen((prev) => !prev)}
className={cn(actionsOpen && 'opacity-100')}
>
<MoreHorizontal />
</DropdownMenuItemAction>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { SidebarRowActions } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-row-actions'
export { ChatNavigationLink } from './chat-navigation-link/chat-navigation-link'
export {
CollapsedChatFlyoutItem,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'

interface SidebarRowActionsProps {
children: ReactNode
indicator?: ReactNode
open?: boolean
revealOnHover?: boolean
}

/** Reclaims idle action space while retaining indicators and touch/keyboard access. */
export function SidebarRowActions({
children,
indicator,
open = false,
revealOnHover = true,
}: SidebarRowActionsProps) {
return (
<div
className={cn(
'relative size-[18px] shrink-0 items-center justify-center gap-1.5 [@media(hover:none)]:w-auto',
indicator || open ? 'flex' : 'hidden',
revealOnHover &&
'group-focus-within/sidebar-row:flex group-hover/sidebar-row:flex [@media(hover:none)]:flex'
)}
>
{indicator && (
<span
className={cn(
'pointer-events-none flex size-[18px] shrink-0 items-center justify-center transition-opacity',
open && '[@media(hover:hover)]:opacity-0',
revealOnHover &&
'[@media(hover:hover)]:group-focus-within/sidebar-row:opacity-0 [@media(hover:hover)]:group-hover/sidebar-row:opacity-0'
)}
>
{indicator}
</span>
)}
<div
className={cn(
'pointer-events-none absolute inset-0 flex items-center justify-center opacity-0 transition-opacity [@media(hover:none)]:static',
open && 'pointer-events-auto opacity-100',
revealOnHover &&
'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'
)}
>
{children}
</div>
</div>
)
}
Loading
Loading