Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cn } from '@/lib/utils';
import {
Popover,
PopoverContent,
PopoverPortal,
PopoverTrigger
} from '@radix-ui/react-popover';
import { every } from 'lodash';
Expand Down Expand Up @@ -191,51 +192,53 @@ export const CommandPopoverButton = ({
</Tooltip>
</TooltipProvider>

<PopoverContent
id="command-popover"
align="start"
sideOffset={12}
data-popover-content
tabIndex={0}
className={cn(
'p-2 rounded-lg border shadow-md bg-background',
'animate-in fade-in-0 zoom-in-95 duration-200',
'focus:outline-none'
)}
onKeyDown={handleKeyDown}
onMouseLeave={handleMouseLeave}
>
<Command className="overflow-hidden bg-transparent">
<CommandListScrollable maxItems={5} className="custom-scrollbar">
<CommandGroup className="p-0">
{nonButtonCommands.map((command, index) => (
<CommandItemAnimated
key={command.id}
index={index}
isSelected={index === selectedIndex}
onMouseMove={() => handleMouseMove(index)}
onSelect={() => handleCommandSelect(command)} // Direct call for mouse clicks
className="space-x-2"
>
<Icon
name={command.icon}
className={cn(
'!size-5 text-muted-foreground transition-transform duration-150',
index === selectedIndex && 'scale-110'
)}
/>
<div className="flex-1">
<div className="font-medium">{command.id}</div>
<div className="text-sm text-muted-foreground">
{command.description}
<PopoverPortal container={window.cl_shadowRootElement}>
<PopoverContent
id="command-popover"
align="start"
sideOffset={12}
data-popover-content
tabIndex={0}
className={cn(
'z-[51] p-2 rounded-lg border shadow-md bg-background',
'animate-in fade-in-0 zoom-in-95 duration-200',
'focus:outline-none'
)}
onKeyDown={handleKeyDown}
onMouseLeave={handleMouseLeave}
>
<Command className="overflow-hidden bg-transparent">
<CommandListScrollable maxItems={5} className="custom-scrollbar">
<CommandGroup className="p-0">
{nonButtonCommands.map((command, index) => (
<CommandItemAnimated
key={command.id}
index={index}
isSelected={index === selectedIndex}
onMouseMove={() => handleMouseMove(index)}
onSelect={() => handleCommandSelect(command)} // Direct call for mouse clicks
className="space-x-2"
>
<Icon
name={command.icon}
className={cn(
'!size-5 text-muted-foreground transition-transform duration-150',
index === selectedIndex && 'scale-110'
)}
/>
<div className="flex-1">
<div className="font-medium">{command.id}</div>
<div className="text-sm text-muted-foreground">
{command.description}
</div>
</div>
</div>
</CommandItemAnimated>
))}
</CommandGroup>
</CommandListScrollable>
</Command>
</PopoverContent>
</CommandItemAnimated>
))}
</CommandGroup>
</CommandListScrollable>
</Command>
</PopoverContent>
</PopoverPortal>
</Popover>
</div>
);
Expand Down
127 changes: 65 additions & 62 deletions frontend/src/components/chat/MessageComposer/FavoriteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cn } from '@/lib/utils';
import {
Popover,
PopoverContent,
PopoverPortal,
PopoverTrigger
} from '@radix-ui/react-popover';
import { Star, Trash } from 'lucide-react';
Expand Down Expand Up @@ -117,69 +118,71 @@ export const FavoriteButton = ({ disabled = false, onSelect }: Props) => {
</Tooltip>
</TooltipProvider>

<PopoverContent
align="start"
sideOffset={12}
className="p-2 w-[300px] rounded-lg border shadow-md bg-background"
>
<Command>
<CommandListScrollable className="max-h-[300px] custom-scrollbar">
{favorites.length === 0 ? (
<CommandEmpty className="py-6 px-4">
<div className="flex flex-col items-center gap-2 text-center">
<p className="text-sm font-medium text-foreground">
{t('chat.favorites.empty.title')}
</p>
<p className="text-xs text-muted-foreground">
{t('chat.favorites.empty.description')}
</p>
</div>
</CommandEmpty>
) : (
<CommandGroup heading={t('chat.favorites.headline')}>
{favorites.map((step) => (
<CommandItem
key={step.id}
value={step.id}
onSelect={() => {
onSelect(step.output);
setOpen(false);
cancelTooltipOpen();
}}
className="cursor-pointer group"
>
<div className="flex items-center justify-between gap-2 w-full overflow-hidden">
<div className="flex flex-col gap-1 overflow-hidden">
<span className="truncate text-sm">
{step.output}
</span>
<span className="text-xs text-muted-foreground">
{new Date(step.createdAt).toLocaleDateString()}
</span>
<PopoverPortal container={window.cl_shadowRootElement}>
<PopoverContent
align="start"
sideOffset={12}
className="z-[51] p-2 w-[300px] rounded-lg border shadow-md bg-background"
>
<Command>
<CommandListScrollable className="max-h-[300px] custom-scrollbar">
{favorites.length === 0 ? (
<CommandEmpty className="py-6 px-4">
<div className="flex flex-col items-center gap-2 text-center">
<p className="text-sm font-medium text-foreground">
{t('chat.favorites.empty.title')}
</p>
<p className="text-xs text-muted-foreground">
{t('chat.favorites.empty.description')}
</p>
</div>
</CommandEmpty>
) : (
<CommandGroup heading={t('chat.favorites.headline')}>
{favorites.map((step) => (
<CommandItem
key={step.id}
value={step.id}
onSelect={() => {
onSelect(step.output);
setOpen(false);
cancelTooltipOpen();
}}
className="cursor-pointer group"
>
<div className="flex items-center justify-between gap-2 w-full overflow-hidden">
<div className="flex flex-col gap-1 overflow-hidden">
<span className="truncate text-sm">
{step.output}
</span>
<span className="text-xs text-muted-foreground">
{new Date(step.createdAt).toLocaleDateString()}
</span>
</div>
<Button
type="button"
variant="ghost"
size="icon"
className="h-7 w-7 text-muted-foreground hover:text-foreground"
aria-label={t('chat.favorites.remove')}
disabled={disabled}
onPointerDown={(event) => event.stopPropagation()}
onClick={(event) => {
event.stopPropagation();
toggleMessageFavorite(step);
}}
>
<Trash className="h-3.5 w-3.5" />
</Button>
</div>
<Button
type="button"
variant="ghost"
size="icon"
className="h-7 w-7 text-muted-foreground hover:text-foreground"
aria-label={t('chat.favorites.remove')}
disabled={disabled}
onPointerDown={(event) => event.stopPropagation()}
onClick={(event) => {
event.stopPropagation();
toggleMessageFavorite(step);
}}
>
<Trash className="h-3.5 w-3.5" />
</Button>
</div>
</CommandItem>
))}
</CommandGroup>
)}
</CommandListScrollable>
</Command>
</PopoverContent>
</CommandItem>
))}
</CommandGroup>
)}
</CommandListScrollable>
</Command>
</PopoverContent>
</PopoverPortal>
</Popover>
</div>
);
Expand Down
103 changes: 53 additions & 50 deletions frontend/src/components/chat/MessageComposer/ModePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cn } from '@/lib/utils';
import {
Popover,
PopoverContent,
PopoverPortal,
PopoverTrigger
} from '@radix-ui/react-popover';
import { ChevronDown, ChevronUp } from 'lucide-react';
Expand Down Expand Up @@ -155,57 +156,59 @@ export const ModePicker = ({
</Button>
</PopoverTrigger>

<PopoverContent
id={`mode-picker-popover-${mode.id}`}
align="start"
side="top"
sideOffset={4}
className={cn(
'p-1 rounded-md border shadow-lg bg-popover',
'animate-in fade-in-0 zoom-in-95 duration-150',
'w-[280px]'
)}
onKeyDown={handleKeyDown}
onMouseLeave={handleMouseLeave}
>
<Command className="overflow-hidden bg-transparent">
<CommandListScrollable maxItems={6} className="custom-scrollbar">
<CommandGroup className="p-0">
{options.map((option, index) => (
<CommandItemAnimated
key={option.id}
index={index}
isSelected={index === selectedIndex}
onMouseMove={() => handleMouseMove(index)}
onSelect={() => handleOptionSelect(option)}
className={cn(
'flex items-start gap-2 px-2 py-2 cursor-pointer',
selectedOptionId === option.id && 'bg-accent'
)}
>
{renderIcon(
option.icon,
cn(
'!size-5 mt-0.5 text-muted-foreground flex-shrink-0',
index === selectedIndex && 'text-foreground'
)
)}
<div className="flex-1 min-w-0">
<div className="font-medium text-sm leading-tight">
{option.name}
</div>
{option.description && (
<div className="text-xs text-muted-foreground mt-0.5 leading-tight">
{option.description}
</div>
<PopoverPortal container={window.cl_shadowRootElement}>
<PopoverContent
id={`mode-picker-popover-${mode.id}`}
align="start"
side="top"
sideOffset={4}
className={cn(
'z-[51] p-1 rounded-md border shadow-lg bg-popover',
'animate-in fade-in-0 zoom-in-95 duration-150',
'w-[280px]'
)}
onKeyDown={handleKeyDown}
onMouseLeave={handleMouseLeave}
>
<Command className="overflow-hidden bg-transparent">
<CommandListScrollable maxItems={6} className="custom-scrollbar">
<CommandGroup className="p-0">
{options.map((option, index) => (
<CommandItemAnimated
key={option.id}
index={index}
isSelected={index === selectedIndex}
onMouseMove={() => handleMouseMove(index)}
onSelect={() => handleOptionSelect(option)}
className={cn(
'flex items-start gap-2 px-2 py-2 cursor-pointer',
selectedOptionId === option.id && 'bg-accent'
)}
>
{renderIcon(
option.icon,
cn(
'!size-5 mt-0.5 text-muted-foreground flex-shrink-0',
index === selectedIndex && 'text-foreground'
)
)}
</div>
</CommandItemAnimated>
))}
</CommandGroup>
</CommandListScrollable>
</Command>
</PopoverContent>
<div className="flex-1 min-w-0">
<div className="font-medium text-sm leading-tight">
{option.name}
</div>
{option.description && (
<div className="text-xs text-muted-foreground mt-0.5 leading-tight">
{option.description}
</div>
)}
</div>
</CommandItemAnimated>
))}
</CommandGroup>
</CommandListScrollable>
</Command>
</PopoverContent>
</PopoverPortal>
</Popover>
</div>
);
Expand Down
Loading
Loading