fix(studio): mobile navbar layout and chat settings sheet (#4458)
* fix(studio): mobile navbar layout and chat settings sheet * fix(studio): portal select dropdowns inside sheet modal subtree
This commit is contained in:
parent
72b768e0be
commit
6c2bfebb20
5 changed files with 73 additions and 24 deletions
|
|
@ -209,7 +209,7 @@ export function Navbar() {
|
|||
</div>
|
||||
|
||||
{/* Right: mobile */}
|
||||
<div className="flex items-center gap-2 md:hidden">
|
||||
<div className="col-start-3 flex items-center gap-2 justify-self-end md:hidden">
|
||||
{tourId ? (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -245,8 +245,9 @@ export function Navbar() {
|
|||
return (
|
||||
<span
|
||||
key={item.href}
|
||||
className="rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground/40 cursor-not-allowed"
|
||||
className="flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground/40 cursor-not-allowed"
|
||||
>
|
||||
<HugeiconsIcon icon={item.icon} className="size-4" />
|
||||
{item.label}
|
||||
</span>
|
||||
);
|
||||
|
|
@ -257,12 +258,13 @@ export function Navbar() {
|
|||
to={item.href}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={cn(
|
||||
"rounded-md border px-3 py-2 text-sm font-medium",
|
||||
"flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium",
|
||||
active
|
||||
? "border-foreground bg-foreground text-background"
|
||||
: "border-border text-foreground hover:bg-accent",
|
||||
)}
|
||||
>
|
||||
<HugeiconsIcon icon={item.icon} className="size-4" />
|
||||
{item.label}
|
||||
</Link>
|
||||
);
|
||||
|
|
@ -271,23 +273,33 @@ export function Navbar() {
|
|||
href="https://unsloth.ai/docs"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-foreground hover:bg-accent"
|
||||
className="mt-2 flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-foreground hover:bg-accent"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
>
|
||||
<HugeiconsIcon icon={Book03Icon} className="size-4" />
|
||||
Learn more (Docs)
|
||||
</a>
|
||||
{tourId ? (
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md border border-border px-3 py-2 text-left text-sm font-medium text-foreground hover:bg-accent"
|
||||
className="flex items-center gap-2 rounded-md border border-border px-3 py-2 text-left text-sm font-medium text-foreground hover:bg-accent"
|
||||
onClick={() => {
|
||||
openTour();
|
||||
setMobileOpen(false);
|
||||
}}
|
||||
>
|
||||
<HugeiconsIcon icon={CursorInfo02Icon} className="size-4" />
|
||||
Start tour
|
||||
</button>
|
||||
) : null}
|
||||
<div className="mt-2 flex items-center justify-between rounded-md border border-border px-3 py-2">
|
||||
<span className="text-sm font-medium text-foreground">Theme</span>
|
||||
<AnimatedThemeToggler
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground [&_svg]:size-4"
|
||||
title="Toggle theme"
|
||||
aria-label="Toggle theme"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ export {
|
|||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogPortalContainerContext,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
import { Dialog as SheetPrimitive } from "radix-ui";
|
||||
import type * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DialogPortalContainerContext } from "@/components/ui/dialog";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Cancel01Icon } from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
|
|
@ -69,6 +71,7 @@ function SheetContent({
|
|||
overlayClassName?: string;
|
||||
overlayPosition?: "fixed" | "absolute";
|
||||
}) {
|
||||
const [contentEl, setContentEl] = useState<HTMLDivElement | null>(null);
|
||||
return (
|
||||
<SheetPortal container={container ?? undefined}>
|
||||
<SheetOverlay
|
||||
|
|
@ -76,6 +79,7 @@ function SheetContent({
|
|||
position={overlayPosition ?? position}
|
||||
/>
|
||||
<SheetPrimitive.Content
|
||||
ref={setContentEl}
|
||||
data-slot="sheet-content"
|
||||
data-side={side}
|
||||
className={cn(
|
||||
|
|
@ -85,7 +89,9 @@ function SheetContent({
|
|||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPortalContainerContext.Provider value={contentEl}>
|
||||
{children}
|
||||
</DialogPortalContainerContext.Provider>
|
||||
{showCloseButton && (
|
||||
<SheetPrimitive.Close data-slot="sheet-close" asChild>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -768,6 +768,7 @@ export function ChatPage(): ReactElement {
|
|||
|
||||
<ChatSettingsPanel
|
||||
open={settingsOpen}
|
||||
onOpenChange={setSettingsOpen}
|
||||
params={inferenceParams}
|
||||
onParamsChange={setInferenceParams}
|
||||
autoTitle={autoTitle}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,14 @@ import {
|
|||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/components/ui/sheet";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import type { ReactNode } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
|
|
@ -151,6 +159,7 @@ function CollapsibleSection({
|
|||
|
||||
interface ChatSettingsPanelProps {
|
||||
open: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
params: InferenceParams;
|
||||
onParamsChange: (params: InferenceParams) => void;
|
||||
autoTitle: boolean;
|
||||
|
|
@ -160,12 +169,14 @@ interface ChatSettingsPanelProps {
|
|||
|
||||
export function ChatSettingsPanel({
|
||||
open,
|
||||
onOpenChange,
|
||||
params,
|
||||
onParamsChange,
|
||||
autoTitle,
|
||||
onAutoTitleChange,
|
||||
onReloadModel,
|
||||
}: ChatSettingsPanelProps) {
|
||||
const isMobile = useIsMobile();
|
||||
const isGguf = useChatRuntimeStore((s) => s.activeGgufVariant) != null;
|
||||
const ggufContextLength = useChatRuntimeStore((s) => s.ggufContextLength);
|
||||
const kvCacheDtype = useChatRuntimeStore((s) => s.kvCacheDtype);
|
||||
|
|
@ -214,24 +225,21 @@ export function ChatSettingsPanel({
|
|||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`shrink-0 self-start h-[calc(100%-0.875rem)] overflow-hidden bg-muted/70 rounded-2xl corner-squircle transition-[width] duration-200 ease-linear ${open ? "w-[17rem] border-l border-sidebar-border/70" : "w-0"}`}
|
||||
>
|
||||
<div className="flex h-full w-[17rem] flex-col">
|
||||
<div className="flex items-center gap-2 px-4 py-3">
|
||||
<HugeiconsIcon
|
||||
icon={PencilEdit01Icon}
|
||||
className="size-4 text-muted-foreground/70"
|
||||
/>
|
||||
<span className="flex-1 text-base font-semibold tracking-tight">
|
||||
Configuration
|
||||
</span>
|
||||
</div>
|
||||
const settingsContent = (
|
||||
<>
|
||||
<div className="flex items-center gap-2 px-4 py-3">
|
||||
<HugeiconsIcon
|
||||
icon={PencilEdit01Icon}
|
||||
className="size-4 text-muted-foreground/70"
|
||||
/>
|
||||
<span className="flex-1 text-base font-semibold tracking-tight">
|
||||
Configuration
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-1.5">
|
||||
{/* mt-4 matches the Playground sidebar gap (SidebarHeader py-3 + SidebarGroup pt-1) */}
|
||||
<div className="mt-4 px-2 pb-3">
|
||||
<div className="flex-1 overflow-y-auto px-1.5">
|
||||
{/* mt-4 matches the Playground sidebar gap (SidebarHeader py-3 + SidebarGroup pt-1) */}
|
||||
<div className="mt-4 px-2 pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Select value={activePreset} onValueChange={applyPreset}>
|
||||
<SelectTrigger className="h-8 flex-1 corner-squircle text-xs">
|
||||
|
|
@ -434,7 +442,28 @@ export function ChatSettingsPanel({
|
|||
|
||||
<ChatTemplateSection onReloadModel={onReloadModel} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent side="right" className="w-[18rem] p-0">
|
||||
<SheetHeader className="sr-only">
|
||||
<SheetTitle>Configuration</SheetTitle>
|
||||
<SheetDescription>Chat inference settings</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="flex h-full flex-col">{settingsContent}</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`shrink-0 self-start h-[calc(100%-0.875rem)] overflow-hidden bg-muted/70 rounded-2xl corner-squircle transition-[width] duration-200 ease-linear ${open ? "w-[17rem] border-l border-sidebar-border/70" : "w-0"}`}
|
||||
>
|
||||
<div className="flex h-full w-[17rem] flex-col">{settingsContent}</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue