mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-30 18:33:04 +08:00
refactor(ui): always show icon-only app switcher, drop auto-compact
The toolbar app switcher used a ResizeObserver-based overflow detection (useAutoCompact) to collapse app labels when space ran out. With the number of managed apps growing, the labels are collapsed in practice anyway, so remove the mechanism and render icons only. Buttons now carry title/aria-label so app names remain discoverable via tooltip and accessible to screen readers.
This commit is contained in:
+1
-9
@@ -42,7 +42,6 @@ import { openclawKeys, useOpenClawHealth } from "@/hooks/useOpenClaw";
|
||||
import { hermesKeys, useOpenHermesWebUI } from "@/hooks/useHermes";
|
||||
import { hermesApi } from "@/lib/api/hermes";
|
||||
import { useProxyStatus } from "@/hooks/useProxyStatus";
|
||||
import { useAutoCompact } from "@/hooks/useAutoCompact";
|
||||
import { useUsageCacheBridge } from "@/hooks/useUsageCacheBridge";
|
||||
import { useTauriEvent } from "@/hooks/useTauriEvent";
|
||||
import { useLastValidValue } from "@/hooks/useLastValidValue";
|
||||
@@ -247,9 +246,6 @@ function App() {
|
||||
const effectiveEditingProvider = useLastValidValue(editingProvider);
|
||||
const effectiveUsageProvider = useLastValidValue(usageProvider);
|
||||
|
||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||
const isToolbarCompact = useAutoCompact(toolbarRef);
|
||||
|
||||
useUsageCacheBridge();
|
||||
|
||||
const promptPanelRef = useRef<any>(null);
|
||||
@@ -1277,10 +1273,7 @@ function App() {
|
||||
<ProfileSwitcher activeApp={activeApp} />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
ref={toolbarRef}
|
||||
className="flex flex-1 min-w-0 overflow-x-hidden items-center py-4 pr-2"
|
||||
>
|
||||
<div className="flex flex-1 min-w-0 overflow-x-hidden items-center py-4 pr-2">
|
||||
<div
|
||||
className="flex shrink-0 items-center gap-1.5 ml-auto"
|
||||
style={{ WebkitAppRegion: "no-drag" } as any}
|
||||
@@ -1399,7 +1392,6 @@ function App() {
|
||||
activeApp={activeApp}
|
||||
onSwitch={setActiveApp}
|
||||
visibleApps={visibleApps}
|
||||
compact={isToolbarCompact}
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-1 p-1 bg-muted rounded-xl">
|
||||
|
||||
@@ -15,7 +15,6 @@ interface AppSwitcherProps {
|
||||
activeApp: AppId;
|
||||
onSwitch: (app: AppId) => void;
|
||||
visibleApps?: VisibleApps;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const ALL_APPS: AppId[] = [
|
||||
@@ -34,7 +33,6 @@ export function AppSwitcher({
|
||||
activeApp,
|
||||
onSwitch,
|
||||
visibleApps,
|
||||
compact,
|
||||
}: AppSwitcherProps) {
|
||||
const handleSwitch = (app: AppId) => {
|
||||
if (app === activeApp) return;
|
||||
@@ -80,6 +78,8 @@ export function AppSwitcher({
|
||||
key={app}
|
||||
type="button"
|
||||
onClick={() => handleSwitch(app)}
|
||||
title={appDisplayName[app]}
|
||||
aria-label={appDisplayName[app]}
|
||||
className={cn(
|
||||
"group inline-flex items-center px-3 h-8 rounded-md text-sm font-medium transition-all duration-200",
|
||||
isActive
|
||||
@@ -115,16 +115,6 @@ export function AppSwitcher({
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"transition-all duration-200 whitespace-nowrap overflow-hidden",
|
||||
compact
|
||||
? "max-w-0 opacity-0 ml-0"
|
||||
: "max-w-[120px] opacity-100 ml-2",
|
||||
)}
|
||||
>
|
||||
{appDisplayName[app]}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { useEffect, useRef, useState, type RefObject } from "react";
|
||||
|
||||
/**
|
||||
* Detects whether the container's children overflow the available width
|
||||
* and returns a `compact` flag for the AppSwitcher.
|
||||
*
|
||||
* Uses ResizeObserver on a flex-constrained container. The container
|
||||
* must have `flex-1 min-w-0 overflow-hidden` so its width is determined
|
||||
* by the parent layout, not its own content — avoiding the oscillation
|
||||
* problem when toggling compact mode.
|
||||
*/
|
||||
export function useAutoCompact(
|
||||
containerRef: RefObject<HTMLDivElement | null>,
|
||||
): boolean {
|
||||
const [compact, setCompact] = useState(false);
|
||||
const normalWidthRef = useRef(0);
|
||||
const lockUntilRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const ro = new ResizeObserver(() => {
|
||||
// During expand animation, ignore resize events to prevent flicker
|
||||
if (Date.now() < lockUntilRef.current) return;
|
||||
|
||||
if (!compact) {
|
||||
// Overflow detected → switch to compact
|
||||
if (el.scrollWidth > el.clientWidth + 1) {
|
||||
// Cache only at the overflow edge: when content fits,
|
||||
// scrollWidth === clientWidth (DOM spec), so caching unconditionally
|
||||
// would pollute normalWidthRef with the container width (e.g. after
|
||||
// maximizing), making the expand threshold unreachable.
|
||||
normalWidthRef.current = el.scrollWidth;
|
||||
setCompact(true);
|
||||
}
|
||||
} else if (normalWidthRef.current > 0) {
|
||||
// In compact mode: only recover to normal if
|
||||
// available space >= what normal mode needed
|
||||
if (el.clientWidth >= normalWidthRef.current) {
|
||||
// Lock out resize events during the expand animation (200ms + 50ms margin)
|
||||
lockUntilRef.current = Date.now() + 250;
|
||||
setCompact(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
ro.observe(el);
|
||||
return () => ro.disconnect();
|
||||
}, [compact]);
|
||||
|
||||
return compact;
|
||||
}
|
||||
Reference in New Issue
Block a user