import { ProviderInteractionMode, RuntimeMode } from "@t3tools/contracts";import { memo, type ReactNode } from "react";import { EllipsisIcon } from "lucide-react";import { Menu, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator as MenuDivider, MenuTrigger,} from "../ui/menu";import { ComposerControl, ComposerControlIcon } from "./ComposerControl";import { composerFloatingLayerProps } from "./composerEventScope";import { useComposerMenuState } from "./useComposerMenuState"; export const CompactComposerControlsMenu = memo(function CompactComposerControlsMenu(props: { interactionMode: ProviderInteractionMode; runtimeMode: RuntimeMode; showInteractionModeToggle: boolean; traitsMenuContent?: ReactNode; size?: "sm" | "xs"; /** * The resting strip keeps this menu mounted out of flow while every block * fits inline. Its portaled popup would outlive that transition, so an * open menu closes when its trigger hides. */ hidden?: boolean; onToggleInteractionMode: () => void; onRuntimeModeChange: (mode: RuntimeMode) => void;}) { const size = props.size ?? "sm"; const [open, setOpen] = useComposerMenuState(props.hidden); return ( <Menu open={open} onOpenChange={setOpen}> <MenuTrigger render={ <ComposerControl size={size} variant="ghost" className={size === "xs" ? "shrink-0" : "shrink-0 px-2"} aria-label="More composer controls" /> } > <ComposerControlIcon icon={EllipsisIcon} size={size} /> </MenuTrigger> <MenuPopup align="start" {...composerFloatingLayerProps}> {props.traitsMenuContent ? ( <> {props.traitsMenuContent} <MenuDivider /> </> ) : null} {props.showInteractionModeToggle ? ( <> <div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">Mode</div> <MenuRadioGroup value={props.interactionMode} onValueChange={(value) => { if (!value || value === props.interactionMode) return; props.onToggleInteractionMode(); }} > <MenuRadioItem value="default">Chat</MenuRadioItem> <MenuRadioItem value="plan">Plan</MenuRadioItem> </MenuRadioGroup> <MenuDivider /> </> ) : null} <div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">Access</div> <MenuRadioGroup value={props.runtimeMode} onValueChange={(value) => { if (!value || value === props.runtimeMode) return; props.onRuntimeModeChange(value as RuntimeMode); }} > <MenuRadioItem value="approval-required">Supervised</MenuRadioItem> <MenuRadioItem value="auto-accept-edits">Auto-accept edits</MenuRadioItem> <MenuRadioItem value="auto">Auto</MenuRadioItem> <MenuRadioItem value="full-access">Full access</MenuRadioItem> </MenuRadioGroup> </MenuPopup> </Menu> );});
import { ProviderInteractionMode, RuntimeMode } from "@t3tools/contracts";
import { memo, type ReactNode } from "react";
import { EllipsisIcon } from "lucide-react";
import {
Menu,
MenuPopup,
MenuRadioGroup,
MenuRadioItem,
MenuSeparator as MenuDivider,
MenuTrigger,
} from "../ui/menu";
import { ComposerControl, ComposerControlIcon } from "./ComposerControl";
import { composerFloatingLayerProps } from "./composerEventScope";
import { useComposerMenuState } from "./useComposerMenuState";
export const CompactComposerControlsMenu = memo(function CompactComposerControlsMenu(props: {
interactionMode: ProviderInteractionMode;
runtimeMode: RuntimeMode;
showInteractionModeToggle: boolean;
traitsMenuContent?: ReactNode;
size?: "sm" | "xs";
/**
* The resting strip keeps this menu mounted out of flow while every block
* fits inline. Its portaled popup would outlive that transition, so an
* open menu closes when its trigger hides.
*/
hidden?: boolean;
onToggleInteractionMode: () => void;
onRuntimeModeChange: (mode: RuntimeMode) => void;
}) {
const size = props.size ?? "sm";
const [open, setOpen] = useComposerMenuState(props.hidden);
return (
<Menu open={open} onOpenChange={setOpen}>
<MenuTrigger
render={
<ComposerControl
size={size}
variant="ghost"
className={size === "xs" ? "shrink-0" : "shrink-0 px-2"}
aria-label="More composer controls"
/>
}
>
<ComposerControlIcon icon={EllipsisIcon} size={size} />
</MenuTrigger>
<MenuPopup align="start" {...composerFloatingLayerProps}>
{props.traitsMenuContent ? (
<>
{props.traitsMenuContent}
<MenuDivider />
</>
) : null}
{props.showInteractionModeToggle ? (
<>
<div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">Mode</div>
<MenuRadioGroup
value={props.interactionMode}
onValueChange={(value) => {
if (!value || value === props.interactionMode) return;
props.onToggleInteractionMode();
}}
>
<MenuRadioItem value="default">Chat</MenuRadioItem>
<MenuRadioItem value="plan">Plan</MenuRadioItem>
</MenuRadioGroup>
<MenuDivider />
</>
) : null}
<div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">Access</div>
<MenuRadioGroup
value={props.runtimeMode}
onValueChange={(value) => {
if (!value || value === props.runtimeMode) return;
props.onRuntimeModeChange(value as RuntimeMode);
}}
>
<MenuRadioItem value="approval-required">Supervised</MenuRadioItem>
<MenuRadioItem value="auto-accept-edits">Auto-accept edits</MenuRadioItem>
<MenuRadioItem value="auto">Auto</MenuRadioItem>
<MenuRadioItem value="full-access">Full access</MenuRadioItem>
</MenuRadioGroup>
</MenuPopup>
</Menu>
);
});