refactor(webui): unify floating controls
This commit is contained in:
@@ -10,10 +10,10 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { useSessionAutomationJobs } from "@/hooks/useSessionAutomationJobs";
|
||||
import { currentLocale } from "@/i18n";
|
||||
import { fmtDateTime } from "@/lib/format";
|
||||
@@ -63,8 +63,8 @@ export function SessionInfoPopover({ sessionKey, token, title }: SessionInfoPopo
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu modal={false} open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -76,11 +76,11 @@ export function SessionInfoPopover({ sessionKey, token, title }: SessionInfoPopo
|
||||
>
|
||||
<ListTodo className="h-4 w-4 stroke-[1.75]" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
sideOffset={8}
|
||||
className="w-[min(23rem,calc(100vw-1.5rem))] rounded-[24px] p-0"
|
||||
className="w-[min(23rem,calc(100vw-1.5rem))] p-0"
|
||||
>
|
||||
<div className="space-y-3 px-4 py-3.5">
|
||||
<div className="min-w-0">
|
||||
@@ -108,8 +108,8 @@ export function SessionInfoPopover({ sessionKey, token, title }: SessionInfoPopo
|
||||
|
||||
{automationContent}
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
floatingItemClassName,
|
||||
floatingSurfaceVisualClassName,
|
||||
} from "@/components/ui/floating-surface";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -2688,10 +2692,9 @@ function CliAppMentionPalette({
|
||||
aria-label={t("thread.composer.mentions.ariaLabel")}
|
||||
style={{ maxHeight: layout.maxHeight }}
|
||||
className={cn(
|
||||
"absolute left-1/2 z-30 w-[calc(100%-0.5rem)] -translate-x-1/2 overflow-hidden rounded-[22px] border",
|
||||
floatingSurfaceVisualClassName,
|
||||
"absolute left-1/2 z-30 w-[calc(100%-0.5rem)] -translate-x-1/2 overflow-hidden",
|
||||
layout.placement === "above" ? "bottom-full mb-2" : "top-full mt-2",
|
||||
"border-border/70 bg-popover p-2 text-popover-foreground shadow-[0_20px_60px_rgba(15,23,42,0.12)]",
|
||||
"dark:border-white/10 dark:shadow-[0_24px_60px_rgba(0,0,0,0.42)]",
|
||||
isHero ? "max-w-[58rem]" : "max-w-[49.5rem]",
|
||||
)}
|
||||
>
|
||||
@@ -2728,7 +2731,8 @@ function CliAppMentionPalette({
|
||||
onChoose(candidate);
|
||||
}}
|
||||
className={cn(
|
||||
"flex min-h-10 w-full items-center gap-2.5 rounded-[13px] px-2.5 py-1.5 text-left transition-colors",
|
||||
floatingItemClassName,
|
||||
"flex min-h-10 w-full items-center gap-2.5 px-2.5 py-1.5 text-left transition-colors",
|
||||
selected
|
||||
? "bg-foreground/[0.055] text-foreground"
|
||||
: "text-foreground/90 hover:bg-foreground/[0.04]",
|
||||
@@ -2836,10 +2840,9 @@ function SlashCommandPalette({
|
||||
aria-label={t("thread.composer.slash.ariaLabel")}
|
||||
style={{ maxHeight: layout.maxHeight }}
|
||||
className={cn(
|
||||
"absolute left-1/2 z-30 w-[calc(100%-0.5rem)] -translate-x-1/2 overflow-hidden rounded-[18px] border",
|
||||
floatingSurfaceVisualClassName,
|
||||
"absolute left-1/2 z-30 w-[calc(100%-0.5rem)] -translate-x-1/2 overflow-hidden",
|
||||
layout.placement === "above" ? "bottom-full mb-2" : "top-full mt-2",
|
||||
"border-border/65 bg-popover p-1.5 text-popover-foreground shadow-[0_18px_55px_rgba(15,23,42,0.16)]",
|
||||
"dark:border-white/10 dark:shadow-[0_22px_55px_rgba(0,0,0,0.45)]",
|
||||
isHero ? "max-w-[58rem]" : "max-w-[49.5rem]",
|
||||
)}
|
||||
>
|
||||
@@ -2868,7 +2871,8 @@ function SlashCommandPalette({
|
||||
onChoose(command);
|
||||
}}
|
||||
className={cn(
|
||||
"flex min-h-[44px] w-full items-center gap-3 rounded-[13px] px-3 py-2 text-left transition-colors",
|
||||
floatingItemClassName,
|
||||
"flex min-h-[44px] w-full items-center gap-3 px-3 py-2 text-left transition-colors",
|
||||
selected
|
||||
? "bg-foreground/[0.065] text-foreground dark:bg-white/[0.09]"
|
||||
: "text-foreground/86 hover:bg-foreground/[0.045] dark:hover:bg-white/[0.065]",
|
||||
|
||||
@@ -6,10 +6,20 @@ import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
floatingItemClassName,
|
||||
floatingItemFocusClassName,
|
||||
} from "@/components/ui/floating-surface";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import type {
|
||||
WorkspaceAccessMode,
|
||||
WorkspaceScopePayload,
|
||||
@@ -134,8 +144,8 @@ export function WorkspaceProjectPicker({
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 items-center rounded-b-[28px] bg-muted/45 px-3 py-1.5 dark:bg-white/[0.045] sm:px-4">
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
@@ -151,16 +161,21 @@ export function WorkspaceProjectPicker({
|
||||
<span className="truncate">{projectLabel}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
side="bottom"
|
||||
sideOffset={8}
|
||||
className="w-[min(25rem,calc(100vw-2rem))] rounded-[22px]"
|
||||
className="w-[min(25rem,calc(100vw-2rem))]"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => applyProjectPath(defaultScope.project_path, defaultScope.project_name)}
|
||||
className="flex min-h-[48px] cursor-default gap-3 rounded-[16px] px-3 py-2.5 focus:bg-muted/55"
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => applyProjectPath(defaultScope.project_path, defaultScope.project_name)}
|
||||
className={cn(
|
||||
floatingItemClassName,
|
||||
floatingItemFocusClassName,
|
||||
"flex min-h-[48px] w-full cursor-default gap-3 px-3 py-2.5 focus:bg-muted/55",
|
||||
)}
|
||||
>
|
||||
<span className="grid h-8 w-8 shrink-0 place-items-center rounded-[12px] bg-muted text-foreground/80">
|
||||
<Folder className="h-4 w-4" />
|
||||
@@ -174,14 +189,9 @@ export function WorkspaceProjectPicker({
|
||||
</span>
|
||||
</span>
|
||||
{!currentProjectScope ? <Check className="h-4 w-4 text-foreground/80" /> : null}
|
||||
</DropdownMenuItem>
|
||||
</button>
|
||||
<div className="my-1 h-px bg-border/45" />
|
||||
<div
|
||||
className="space-y-1.5 px-1.5 py-1.5"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Escape") event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className="space-y-1.5 px-1.5 py-1.5">
|
||||
<form
|
||||
className="flex items-center gap-2"
|
||||
onSubmit={(event) => {
|
||||
@@ -217,8 +227,8 @@ export function WorkspaceProjectPicker({
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -284,46 +294,51 @@ export function WorkspaceAccessMenu({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-56">
|
||||
<AccessMenuItem
|
||||
icon={<Hand className="h-4 w-4" />}
|
||||
label={t("thread.composer.workspace.default")}
|
||||
selected={mode === "restricted"}
|
||||
onSelect={() => setMode("restricted")}
|
||||
/>
|
||||
<AccessMenuItem
|
||||
icon={<AlertTriangle className="h-4 w-4" />}
|
||||
label={t("thread.composer.workspace.full")}
|
||||
selected={mode === "full"}
|
||||
disabled={!canUseFullAccess}
|
||||
warning
|
||||
onSelect={() => setMode("full")}
|
||||
/>
|
||||
<DropdownMenuRadioGroup
|
||||
value={mode}
|
||||
onValueChange={(value) => {
|
||||
if (value === "restricted" || value === "full") setMode(value);
|
||||
}}
|
||||
>
|
||||
<AccessMenuItem
|
||||
value="restricted"
|
||||
icon={<Hand className="h-4 w-4" />}
|
||||
label={t("thread.composer.workspace.default")}
|
||||
/>
|
||||
<AccessMenuItem
|
||||
value="full"
|
||||
icon={<AlertTriangle className="h-4 w-4" />}
|
||||
label={t("thread.composer.workspace.full")}
|
||||
disabled={!canUseFullAccess}
|
||||
warning
|
||||
/>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
function AccessMenuItem({
|
||||
value,
|
||||
icon,
|
||||
label,
|
||||
selected,
|
||||
disabled,
|
||||
warning,
|
||||
onSelect,
|
||||
}: {
|
||||
value: WorkspaceAccessMode;
|
||||
icon: ReactNode;
|
||||
label: string;
|
||||
selected: boolean;
|
||||
disabled?: boolean;
|
||||
warning?: boolean;
|
||||
onSelect: () => void;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
<DropdownMenuRadioItem
|
||||
value={value}
|
||||
indicator="check"
|
||||
indicatorPosition="end"
|
||||
disabled={disabled}
|
||||
onSelect={onSelect}
|
||||
className={cn(
|
||||
"flex h-10 items-center gap-3 rounded-xl px-3 text-[13.5px] font-semibold",
|
||||
"flex h-10 items-center gap-3 rounded-xl pl-3 text-[13.5px] font-semibold",
|
||||
warning && "text-orange-600 focus:text-orange-600 dark:text-orange-300 dark:focus:text-orange-300",
|
||||
)}
|
||||
>
|
||||
@@ -331,7 +346,6 @@ function AccessMenuItem({
|
||||
{icon}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">{label}</span>
|
||||
{selected ? <Check className="h-4 w-4 shrink-0" aria-hidden /> : null}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user