Studio: polish update banner layout and sidebar icons (#6266)

Polishes the in-app update notification banners and the sidebar account menu.

Update banners (web and llama.cpp):
- Add a download icon next to the title, aligned with the title text.
- Even out the action row so the buttons share balanced padding and equal spacing.
- Let the row wrap on narrow viewports so Copy command is never clipped under overflow-hidden.

Sidebar:
- Use the setting-07 cog for the profile button and the Settings menu item.
- Use the sun-03 icon for the Light Mode menu item.
- Slightly enlarge the profile cog.

Frontend only. Build and typecheck pass.
This commit is contained in:
Michael Han 2026-06-12 10:32:11 -07:00 committed by GitHub
commit e017248616
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 76 additions and 54 deletions

View file

@ -259,7 +259,7 @@ function TauriWrapper({ children }: { children: ReactNode }) {
<>
{children}
<DownloadManagerPanel />
<div className="pointer-events-none fixed bottom-4 right-4 z-[9998] flex w-[calc(100vw-2rem)] max-w-[340px] flex-col items-stretch gap-2">
<div className="pointer-events-none fixed bottom-4 right-4 z-[9998] flex w-[calc(100vw-2rem)] max-w-[400px] flex-col items-stretch gap-2">
<WebUpdateBanner
positioned={false}
enabled={!WEB_UPDATE_HIDDEN_ROUTES.has(pathname)}

View file

@ -66,7 +66,8 @@ import {
PowerIcon,
PencilEdit02Icon,
LayoutAlignLeftIcon,
Settings02Icon,
Setting07Icon,
Sun03Icon,
TestTube01Icon,
ZapIcon,
} from "@hugeicons/core-free-icons";
@ -82,7 +83,7 @@ import {
} from "@/components/ui/tooltip";
import { Tooltip as TooltipPrimitive } from "radix-ui";
import { HugeiconsIcon } from "@hugeicons/react";
import { ChevronDown, ChevronsUpDown, MoreHorizontalIcon, Moon, Sun } from "lucide-react";
import { ChevronDown, MoreHorizontalIcon, Moon } from "lucide-react";
import { Link, useNavigate, useRouterState } from "@tanstack/react-router";
import {
archiveChatItem,
@ -1209,7 +1210,12 @@ export function AppSidebar() {
<span className="truncate font-heading text-[13.5px] tracking-[0.025em] dark:tracking-[0.04em] font-semibold text-nav-fg">{displayTitle}</span>
<span className="truncate text-[11.5px] tracking-nav text-muted-foreground">Unsloth</span>
</div>
<ChevronsUpDown strokeWidth={1.25} className="ml-auto size-4 text-muted-foreground group-data-[collapsible=icon]:hidden" />
{/* settings cog (replaces the up/down chevron) */}
<HugeiconsIcon
icon={Setting07Icon}
strokeWidth={1.5}
className="ml-auto !size-[18px] text-muted-foreground group-data-[collapsible=icon]:hidden"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
@ -1222,7 +1228,7 @@ export function AppSidebar() {
<DropdownMenuItem
onSelect={() => useSettingsDialogStore.getState().openDialog()}
>
<HugeiconsIcon icon={Settings02Icon} strokeWidth={1.75} className="size-icon" />
<HugeiconsIcon icon={Setting07Icon} strokeWidth={1.75} className="size-icon" />
<span>{t("shell.navigation.settings")}</span>
<DropdownMenuShortcut>,</DropdownMenuShortcut>
</DropdownMenuItem>
@ -1239,7 +1245,7 @@ export function AppSidebar() {
ref={anchorRef as React.Ref<HTMLDivElement>}
onSelect={(e) => { e.preventDefault(); toggleTheme(); }}
>
{isDark ? <Sun strokeWidth={1.75} className="size-icon" /> : <Moon strokeWidth={1.75} className="size-icon" />}
{isDark ? <HugeiconsIcon icon={Sun03Icon} strokeWidth={1.75} className="size-icon" /> : <Moon strokeWidth={1.75} className="size-icon" />}
<span>
{isDark
? t("shell.navigation.lightMode")

View file

@ -6,6 +6,7 @@ import { useLlamaUpdateCheck } from "@/hooks/use-llama-update-check";
import { useShowLlamaUpdateBanner } from "@/hooks/use-llama-update-pref";
import { toast } from "@/lib/toast";
import { cn } from "@/lib/utils";
import { Download } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { type ReactElement, useEffect, useRef, useState } from "react";
@ -132,12 +133,12 @@ export function LlamaUpdateBanner({
transition={{ duration: 0.35, ease: EASE_OUT_QUART }}
className={cn(
positioned
? "fixed bottom-4 right-4 z-[9998] w-[calc(100vw-2rem)] max-w-[340px]"
? "fixed bottom-4 right-4 z-[9998] w-[calc(100vw-2rem)] max-w-[400px]"
: "pointer-events-auto w-full",
)}
data-testid="llama-update-banner"
>
<div className="relative overflow-hidden rounded-[24px] bg-white px-4 pb-[22px] pl-6 pt-5 shadow-[0_2px_8px_-2px_rgba(0,0,0,0.16)] dark:bg-card dark:shadow-[0_8px_28px_-6px_rgba(0,0,0,0.28)]">
<div className="relative overflow-hidden rounded-[24px] bg-white px-5 pb-4 pt-5 shadow-[0_2px_8px_-2px_rgba(0,0,0,0.16)] dark:bg-card dark:shadow-[0_8px_28px_-6px_rgba(0,0,0,0.28)]">
{applying ? null : (
<button
type="button"
@ -163,16 +164,23 @@ export function LlamaUpdateBanner({
</button>
)}
<div className="min-w-0 pr-6">
<p className="font-heading text-base font-medium text-foreground">
{applying ? "Updating llama.cpp..." : "New llama.cpp version"}
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{status?.installed_tag ?? "unknown"} &rarr;{" "}
<span className="font-medium text-foreground">
{status?.latest_tag ?? ""}
</span>
</p>
<div className="flex min-w-0 items-start gap-4 pr-6">
<Download
aria-hidden="true"
className="mt-1 size-5 shrink-0 text-foreground"
strokeWidth={1.75}
/>
<div className="min-w-0">
<p className="font-heading text-base font-medium text-foreground">
{applying ? "Updating llama.cpp..." : "New llama.cpp version"}
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{status?.installed_tag ?? "unknown"} &rarr;{" "}
<span className="font-medium text-foreground">
{status?.latest_tag ?? ""}
</span>
</p>
</div>
</div>
{applying ? (
@ -195,24 +203,25 @@ export function LlamaUpdateBanner({
/>
</div>
) : (
<div className="mt-3 flex items-center gap-2">
<Button
size="sm"
className="h-auto rounded-full px-3.5 py-2 text-[13px]"
onClick={handleUpdate}
data-testid="llama-update-button"
>
Update
</Button>
<div className="mt-4 flex flex-wrap items-center justify-between gap-y-2">
<Button
size="sm"
variant="ghost"
className="h-auto rounded-full px-2.5 py-2 text-[13px] text-muted-foreground hover:text-foreground"
className="-ml-2 h-auto rounded-full px-2.5 py-2 text-[13px] font-medium text-foreground"
onClick={snooze}
data-testid="llama-update-snooze-button"
>
Remind me later
</Button>
<Button
size="sm"
// ml offsets the pill's filled edge so visual gaps stay equal
className="ml-2.5 h-auto rounded-full px-3.5 py-2 text-[13px]"
onClick={handleUpdate}
data-testid="llama-update-button"
>
Update
</Button>
</div>
)}
</div>

View file

@ -7,6 +7,7 @@ import { useWebUpdateCheck } from "@/hooks/use-web-update-check";
import { isTauri } from "@/lib/api-base";
import { copyToClipboard } from "@/lib/copy-to-clipboard";
import { cn } from "@/lib/utils";
import { Download } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { type ReactElement, useEffect, useRef, useState } from "react";
@ -78,12 +79,12 @@ export function WebUpdateBanner({
transition={{ duration: 0.35, ease: EASE_OUT_QUART }}
className={cn(
positioned
? "fixed bottom-4 right-4 z-[9999] w-[calc(100vw-2rem)] max-w-[340px]"
? "fixed bottom-4 right-4 z-[9999] w-[calc(100vw-2rem)] max-w-[400px]"
: "pointer-events-auto w-full",
)}
data-testid="web-update-banner"
>
<div className="relative overflow-hidden rounded-[24px] bg-white px-4 pb-[22px] pl-6 pt-5 shadow-[0_2px_8px_-2px_rgba(0,0,0,0.16)] dark:bg-card dark:shadow-[0_8px_28px_-6px_rgba(0,0,0,0.28)]">
<div className="relative overflow-hidden rounded-[24px] bg-white px-5 pb-4 pt-5 shadow-[0_2px_8px_-2px_rgba(0,0,0,0.16)] dark:bg-card dark:shadow-[0_8px_28px_-6px_rgba(0,0,0,0.28)]">
<button
type="button"
onClick={dismiss}
@ -107,47 +108,53 @@ export function WebUpdateBanner({
</svg>
</button>
<div className="min-w-0 pr-6">
<p className="font-heading text-base font-medium text-foreground">
New Unsloth version
</p>
<div className="mt-0.5 flex items-center justify-between gap-3">
<p className="text-xs text-muted-foreground">
<div className="flex min-w-0 items-start gap-4 pr-6">
<Download
aria-hidden="true"
className="mt-1 size-5 shrink-0 text-foreground"
strokeWidth={1.75}
/>
<div className="min-w-0">
<p className="font-heading text-base font-medium text-foreground">
New Unsloth version
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{status.currentVersion} &rarr;{" "}
<span className="font-medium text-foreground">
{status.latestVersion}
</span>
</p>
<a
href={RELEASE_NOTES_URL}
target="_blank"
rel="noopener noreferrer"
className="shrink-0 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
data-testid="web-update-release-notes-link"
>
Release notes
</a>
</div>
</div>
<div className="mt-3 flex items-center gap-2">
<Button
size="sm"
className="h-auto rounded-full px-3.5 py-2 text-[13px]"
onClick={handleCopyCommand}
data-testid="web-update-copy-button"
<div className="mt-4 flex flex-wrap items-center justify-between gap-y-2">
<a
href={RELEASE_NOTES_URL}
target="_blank"
rel="noopener noreferrer"
className="-ml-2 whitespace-nowrap rounded-full px-2.5 py-2 text-[13px] font-medium text-foreground transition-colors hover:bg-muted"
data-testid="web-update-release-notes-link"
>
{copied ? "Copied" : "Copy command"}
</Button>
Release notes
</a>
<Button
size="sm"
variant="ghost"
className="h-auto rounded-full px-2.5 py-2 text-[13px] text-muted-foreground hover:text-foreground"
className="h-auto rounded-full px-2.5 py-2 text-[13px] font-medium text-foreground"
onClick={snooze}
data-testid="web-update-snooze-button"
>
Remind me later
</Button>
<Button
size="sm"
// ml offsets the pill's filled edge so visual gaps stay equal
className="ml-2.5 h-auto rounded-full px-3.5 py-2 text-[13px]"
onClick={handleCopyCommand}
data-testid="web-update-copy-button"
>
{copied ? "Copied" : "Copy command"}
</Button>
</div>
</div>
</motion.div>