fix(desktop-ui): improve macOS sidebar layout (PR #1001)

This commit is contained in:
diegosouzapw
2026-04-07 23:28:50 -03:00
parent ab2d174a0b
commit c1f363fde2
6 changed files with 110 additions and 42 deletions

View File

@@ -13,6 +13,9 @@
/* OpenClaw × ClawHub Color Palette */
:root {
--desktop-safe-top: 0px;
--desktop-safe-bottom: 0px;
/* Primary - Coral Red (OpenClaw) */
--color-primary: #e54d5e;
--color-primary-hover: #c93d4e;
@@ -152,6 +155,11 @@ body {
-moz-osx-font-smoothing: grayscale;
}
body.electron-macos {
--desktop-safe-top: 30px;
--desktop-safe-bottom: 8px;
}
/* Selection — with fallback for browsers that don't support color-mix() */
::selection {
background-color: rgba(229, 77, 94, 0.22);

View File

@@ -18,6 +18,7 @@ import {
OPENAI_COMPATIBLE_PREFIX,
ANTHROPIC_COMPATIBLE_PREFIX,
} from "@/shared/constants/providers";
import { useIsElectron } from "@/shared/hooks/useElectron";
const isE2EMode = process.env.NEXT_PUBLIC_OMNIROUTE_E2E_MODE === "1";
@@ -122,8 +123,11 @@ function usePageInfo(pathname: string | null): {
export default function Header({ onMenuClick, showMenuButton = true }) {
const pathname = usePathname();
const router = useRouter();
const isElectron = useIsElectron();
const t = useTranslations("header");
const { title, description, breadcrumbs } = usePageInfo(pathname);
const isMacElectron =
isElectron && typeof window !== "undefined" && window.electronAPI?.platform === "darwin";
const handleLogout = async () => {
try {
@@ -138,7 +142,14 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
};
return (
<header className="flex items-center justify-between px-8 py-5 border-b border-black/5 dark:border-white/5 bg-bg/80 backdrop-blur-xl z-10 sticky top-0">
<header
className="sticky top-0 z-10 flex items-center justify-between border-b border-black/5 bg-bg/80 px-8 py-5 backdrop-blur-xl dark:border-white/5"
style={{
paddingTop: isMacElectron
? "calc(1.25rem + var(--desktop-safe-top))"
: undefined,
}}
>
{/* Mobile menu button */}
<div className="flex items-center gap-3 lg:hidden">
{showMenuButton && (

View File

@@ -24,10 +24,12 @@ export default function Sidebar({
onClose,
collapsed = false,
onToggleCollapse,
isMacElectron = false,
}: {
onClose?: any;
collapsed?: boolean;
onToggleCollapse?: any;
isMacElectron?: boolean;
}) {
const pathname = usePathname();
const t = useTranslations("sidebar");
@@ -192,9 +194,12 @@ export default function Sidebar({
<>
<aside
className={cn(
"flex flex-col h-full border-r border-black/5 dark:border-white/5 bg-vibrancy backdrop-blur-xl transition-all duration-300 ease-in-out",
collapsed ? "w-16" : "w-72"
"flex h-full min-h-0 flex-col border-r border-black/5 bg-vibrancy backdrop-blur-xl transition-all duration-300 ease-in-out dark:border-white/5",
collapsed ? "w-16" : "w-80"
)}
style={{
paddingTop: isMacElectron ? "var(--desktop-safe-top)" : undefined,
}}
>
<a
href="#main-content"
@@ -202,34 +207,42 @@ export default function Sidebar({
>
Skip to content
</a>
<div
className={cn(
"flex items-center gap-2 pt-5 pb-2",
collapsed ? "px-3 justify-center" : "px-6"
)}
aria-hidden="true"
>
<div className="w-3 h-3 rounded-full bg-[#FF5F56]" />
<div className="w-3 h-3 rounded-full bg-[#FFBD2E]" />
<div className="w-3 h-3 rounded-full bg-[#27C93F]" />
{!collapsed && <div className="flex-1" />}
{onToggleCollapse && (
<button
onClick={onToggleCollapse}
title={collapsed ? "Expand sidebar" : "Collapse sidebar"}
aria-expanded={!collapsed}
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
className={cn(
"p-1 rounded-md text-text-muted/50 hover:text-text-muted hover:bg-black/5 dark:hover:bg-white/5 transition-colors",
collapsed && "mt-2"
)}
>
<span className="material-symbols-outlined text-[16px]" aria-hidden="true">
{collapsed ? "chevron_right" : "chevron_left"}
</span>
</button>
)}
</div>
{(onToggleCollapse || !isMacElectron) && (
<div
className={cn(
"flex items-center gap-2 pb-2",
isMacElectron ? "pt-3" : "pt-5",
collapsed ? "px-3 justify-center" : "px-6"
)}
aria-hidden="true"
>
{!isMacElectron && (
<>
<div className="w-3 h-3 rounded-full bg-[#FF5F56]" />
<div className="w-3 h-3 rounded-full bg-[#FFBD2E]" />
<div className="w-3 h-3 rounded-full bg-[#27C93F]" />
</>
)}
{!collapsed && <div className="flex-1" />}
{onToggleCollapse && (
<button
onClick={onToggleCollapse}
title={collapsed ? "Expand sidebar" : "Collapse sidebar"}
aria-expanded={!collapsed}
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
className={cn(
"rounded-md p-1 text-text-muted/50 transition-colors hover:bg-black/5 hover:text-text-muted dark:hover:bg-white/5",
collapsed && !isMacElectron && "mt-2",
isMacElectron && "ml-auto"
)}
>
<span className="material-symbols-outlined text-[16px]" aria-hidden="true">
{collapsed ? "chevron_right" : "chevron_left"}
</span>
</button>
)}
</div>
)}
<div className={cn("py-4", collapsed ? "px-2" : "px-6")}>
<Link
@@ -261,7 +274,7 @@ export default function Sidebar({
<nav
aria-label="Main navigation"
className={cn(
"flex-1 py-2 space-y-1 overflow-y-auto custom-scrollbar",
"min-h-0 flex-1 space-y-1 overflow-y-auto py-2 custom-scrollbar",
collapsed ? "px-2" : "px-4"
)}
>
@@ -288,9 +301,14 @@ export default function Sidebar({
<div
className={cn(
"border-t border-black/5 dark:border-white/5",
"shrink-0 border-t border-black/5 dark:border-white/5",
collapsed ? "p-2 flex flex-col gap-1" : "p-3 flex gap-2"
)}
style={{
paddingBottom: isMacElectron
? "calc(0.75rem + var(--desktop-safe-bottom))"
: undefined,
}}
>
<button
onClick={() => setShowRestartModal(true)}
@@ -298,7 +316,7 @@ export default function Sidebar({
className={cn(
"flex items-center justify-center gap-2 rounded-lg font-medium transition-all",
"text-amber-500 hover:bg-amber-500/10 border border-amber-500/20 hover:border-amber-500/40",
collapsed ? "p-2" : "flex-1 px-3 py-2 text-sm"
collapsed ? "p-2" : "flex-1 min-w-0 px-3 py-2 text-xs"
)}
>
<span className="material-symbols-outlined text-[18px]">restart_alt</span>
@@ -310,7 +328,7 @@ export default function Sidebar({
className={cn(
"flex items-center justify-center gap-2 rounded-lg font-medium transition-all",
"text-red-500 hover:bg-red-500/10 border border-red-500/20 hover:border-red-500/40",
collapsed ? "p-2" : "flex-1 px-3 py-2 text-sm"
collapsed ? "p-2" : "flex-1 min-w-0 px-3 py-2 text-xs"
)}
>
<span className="material-symbols-outlined text-[18px]">power_settings_new</span>
@@ -367,4 +385,5 @@ Sidebar.propTypes = {
onClose: PropTypes.func,
collapsed: PropTypes.bool,
onToggleCollapse: PropTypes.func,
isMacElectron: PropTypes.bool,
};

View File

@@ -1,17 +1,19 @@
"use client";
import { useState } from "react";
import { useEffect, useState } from "react";
import Sidebar from "../Sidebar";
import Header from "../Header";
import Breadcrumbs from "../Breadcrumbs";
import NotificationToast from "../NotificationToast";
import MaintenanceBanner from "../MaintenanceBanner";
import { useIsElectron } from "@/shared/hooks/useElectron";
const SIDEBAR_COLLAPSED_KEY = "sidebar-collapsed";
const isE2EMode = process.env.NEXT_PUBLIC_OMNIROUTE_E2E_MODE === "1";
export default function DashboardLayout({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const isElectron = useIsElectron();
const [collapsed, setCollapsed] = useState(() => {
if (typeof window === "undefined") return false;
try {
@@ -21,6 +23,19 @@ export default function DashboardLayout({ children }) {
}
});
const isMacElectron =
isElectron && typeof window !== "undefined" && window.electronAPI?.platform === "darwin";
useEffect(() => {
if (typeof document === "undefined") return;
document.body.classList.toggle("electron-macos", isMacElectron);
return () => {
document.body.classList.remove("electron-macos");
};
}, [isMacElectron]);
const handleToggleCollapse = () => {
const next = !collapsed;
setCollapsed(next);
@@ -28,7 +43,7 @@ export default function DashboardLayout({ children }) {
};
return (
<div className="flex h-screen w-full overflow-hidden bg-bg">
<div className="flex h-dvh min-h-0 w-full overflow-hidden bg-bg">
{/* Mobile sidebar overlay */}
{sidebarOpen && (
<div
@@ -38,8 +53,12 @@ export default function DashboardLayout({ children }) {
)}
{/* Sidebar - Desktop */}
<div className="hidden lg:flex">
<Sidebar collapsed={collapsed} onToggleCollapse={handleToggleCollapse} />
<div className="hidden min-h-0 lg:flex">
<Sidebar
collapsed={collapsed}
onToggleCollapse={handleToggleCollapse}
isMacElectron={isMacElectron}
/>
</div>
{/* Sidebar - Mobile: full viewport height with proper scroll containment */}
@@ -48,17 +67,17 @@ export default function DashboardLayout({ children }) {
sidebarOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
<Sidebar onClose={() => setSidebarOpen(false)} />
<Sidebar onClose={() => setSidebarOpen(false)} isMacElectron={isMacElectron} />
</div>
{/* Main content */}
<main
id="main-content"
className="flex flex-col flex-1 h-full min-w-0 relative transition-colors duration-300"
className="relative flex min-h-0 flex-1 min-w-0 flex-col transition-colors duration-300"
>
<Header onMenuClick={() => setSidebarOpen(true)} />
{!isE2EMode && <MaintenanceBanner />}
<div className="flex-1 overflow-y-auto overflow-x-hidden custom-scrollbar p-4 sm:p-6 lg:p-10">
<div className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden custom-scrollbar p-4 sm:p-6 lg:p-10">
<div className="max-w-7xl mx-auto w-full">
<Breadcrumbs />
{children}

2
test-cherry.js Normal file
View File

@@ -0,0 +1,2 @@
const { convertOpenAIContentToParts } = require('./open-sse/translator/helpers/geminiHelper.ts');
// since it's typescript/ESM, we might need a ts-node or vitest

9
test_gemini.mjs Normal file
View File

@@ -0,0 +1,9 @@
import { convertOpenAIContentToParts } from "./open-sse/translator/helpers/geminiHelper.ts";
const cherryMsg = [
{ type: "text", text: "What runs on this?" },
{ type: "file", file: { data: "ABCDEFG", type: "application/pdf" } },
{ type: "file", file_url: { url: "data:application/pdf;base64,ABCDEFG" } },
];
console.log(convertOpenAIContentToParts(cherryMsg));