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
28 changes: 19 additions & 9 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { toastManager } from "../ui/toast";
import {
BotIcon,
CheckIcon,
CircleAlertIcon,
ListTodoIcon,
PencilRulerIcon,
Expand Down Expand Up @@ -273,16 +274,25 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop
{runtimeModeOptions.map((mode) => {
const option = runtimeModeConfig[mode];
const OptionIcon = option.icon;
const isSelected = props.runtimeMode === mode;
return (
<SelectItem key={mode} value={mode} className="min-w-64 py-2">
<div className="grid min-w-0 gap-0.5">
<span className="inline-flex items-center gap-1.5 font-medium text-foreground">
<OptionIcon className="size-3.5 shrink-0 text-muted-foreground" />
{option.label}
</span>
<span className="text-muted-foreground text-xs leading-4">
{option.description}
</span>
<SelectItem key={mode} value={mode} hideIndicator className="min-w-64 py-2">
<div className="flex min-w-0 items-center gap-3">
<div className="grid min-w-0 flex-1 gap-0.5">
<span className="inline-flex items-center gap-1.5 font-medium text-foreground">
<OptionIcon className="size-3.5 shrink-0 text-muted-foreground" />
{option.label}
</span>
<span className="text-muted-foreground text-xs leading-4">
{option.description}
</span>
</div>
<CheckIcon
className={cn(
"size-4 text-blue-400",
isSelected ? "opacity-100" : "opacity-0",
)}
/>
</div>
</SelectItem>
);
Expand Down
145 changes: 84 additions & 61 deletions apps/web/src/components/chat/TraitsPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "@t3tools/shared/model";
import { memo, useCallback, useState } from "react";
import type { VariantProps } from "class-variance-authority";
import { ChevronDownIcon } from "lucide-react";
import { CheckIcon, ChevronDownIcon } from "lucide-react";
import { Button, buttonVariants } from "../ui/button";
import {
Menu,
Expand Down Expand Up @@ -297,67 +297,90 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({

return (
<>
{selectDescriptors.map((descriptor, index) => (
<div key={descriptor.id}>
{index > 0 ? <MenuDivider /> : null}
<MenuGroup>
<div className="px-2 pt-1.5 pb-1 font-medium text-muted-foreground text-xs">
{descriptor.label}
</div>
{ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id ? (
<div className="px-2 pb-1.5 text-muted-foreground/80 text-xs">
Your prompt contains &quot;ultrathink&quot; in the text. Remove it to change this
option.
{selectDescriptors.map((descriptor, index) => {
const selectedValue =
ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id
? "ultrathink"
: (getDescriptorStringValue(descriptor) ?? "");

return (
<div key={descriptor.id}>
{index > 0 ? <MenuDivider /> : null}
<MenuGroup>
<div className="px-2 pt-1.5 pb-1 font-medium text-muted-foreground text-xs">
{descriptor.label}
</div>
{ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id ? (
<div className="px-2 pb-1.5 text-muted-foreground/80 text-xs">
Your prompt contains &quot;ultrathink&quot; in the text. Remove it to change this
option.
</div>
) : null}
<MenuRadioGroup
value={selectedValue}
onValueChange={(value) => handleSelectChange(descriptor, value)}
>
{descriptor.options.map((option) => (
<MenuRadioItem
key={option.id}
value={option.id}
hideIndicator
disabled={ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id}
>
<span className="flex w-full min-w-0 items-center justify-between gap-3">
<span className="min-w-0 truncate">
{option.label}
{option.isDefault ? (
<>
{" "}
<DefaultBadge />
</>
) : null}
</span>
{option.id === selectedValue ? (
<CheckIcon className="size-3.5 shrink-0 text-blue-400" />
) : null}
</span>
</MenuRadioItem>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixed checks in compact menu

Low Severity

TraitsMenuContent now uses hideIndicator with right-aligned blue checks, and that same content is embedded in CompactComposerControlsMenu above Mode and Access rows that still use the default left radio indicators. In the compact footer menu, selection marks and item padding no longer match across sections.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5141f95. Configure here.

))}
</MenuRadioGroup>
</MenuGroup>
</div>
);
})}
{booleanDescriptors.map((descriptor, index) => {
const selectedValue = descriptor.currentValue === true ? "on" : "off";

return (
<div key={descriptor.id}>
{index > 0 || selectDescriptors.length > 0 ? <MenuDivider /> : null}
<MenuGroup>
<div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">
{descriptor.label}
</div>
) : null}
<MenuRadioGroup
value={
ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id
? "ultrathink"
: (getDescriptorStringValue(descriptor) ?? "")
}
onValueChange={(value) => handleSelectChange(descriptor, value)}
>
{descriptor.options.map((option) => (
<MenuRadioItem
key={option.id}
value={option.id}
disabled={ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id}
>
{option.label}
{option.isDefault ? (
<>
{" "}
<DefaultBadge />
</>
) : null}
</MenuRadioItem>
))}
</MenuRadioGroup>
</MenuGroup>
</div>
))}
{booleanDescriptors.map((descriptor, index) => (
<div key={descriptor.id}>
{index > 0 || selectDescriptors.length > 0 ? <MenuDivider /> : null}
<MenuGroup>
<div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">
{descriptor.label}
</div>
<MenuRadioGroup
value={descriptor.currentValue === true ? "on" : "off"}
onValueChange={(value) => {
updateDescriptors(
replaceDescriptorCurrentValue(descriptors, descriptor.id, value === "on"),
);
}}
>
<MenuRadioItem value="on">On</MenuRadioItem>
<MenuRadioItem value="off">Off</MenuRadioItem>
</MenuRadioGroup>
</MenuGroup>
</div>
))}
<MenuRadioGroup
value={selectedValue}
onValueChange={(value) => {
updateDescriptors(
replaceDescriptorCurrentValue(descriptors, descriptor.id, value === "on"),
);
}}
>
{(["on", "off"] as const).map((value) => (
<MenuRadioItem key={value} value={value} hideIndicator>
<span className="flex w-full min-w-0 items-center justify-between gap-3">
<span>{value === "on" ? "On" : "Off"}</span>
{value === selectedValue ? (
<CheckIcon className="size-3.5 shrink-0 text-blue-400" />
) : null}
</span>
</MenuRadioItem>
))}
</MenuRadioGroup>
</MenuGroup>
</div>
);
})}
</>
);
});
Expand Down
46 changes: 28 additions & 18 deletions apps/web/src/components/ui/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,32 +147,42 @@ function MenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) {
return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />;
}

function MenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) {
function MenuRadioItem({
className,
children,
hideIndicator = false,
...props
}: MenuPrimitive.RadioItem.Props & {
hideIndicator?: boolean;
}) {
return (
<MenuPrimitive.RadioItem
className={cn(
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
hideIndicator ? "grid-cols-[1fr] ps-3 pe-3" : "grid-cols-[1rem_1fr] ps-2 pe-4",
className,
)}
data-slot="menu-radio-item"
{...props}
>
<MenuPrimitive.RadioItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</MenuPrimitive.RadioItemIndicator>
<span className="col-start-2">{children}</span>
{hideIndicator ? null : (
<MenuPrimitive.RadioItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</MenuPrimitive.RadioItemIndicator>
)}
<span className={hideIndicator ? "col-start-1" : "col-start-2"}>{children}</span>
</MenuPrimitive.RadioItem>
);
}
Expand Down
Loading