refactor(webui): unify floating controls
This commit is contained in:
@@ -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