mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-01 04:02:02 +08:00
feat(toolbar): add smooth transition animation for AppSwitcher compact toggle
Replace conditional rendering with always-rendered span driven by CSS max-width + opacity animation. Add time-lock in useAutoCompact to prevent ResizeObserver flicker during expand animation.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import type { AppId } from "@/lib/api";
|
import type { AppId } from "@/lib/api";
|
||||||
import type { VisibleApps } from "@/types";
|
import type { VisibleApps } from "@/types";
|
||||||
import { ProviderIcon } from "@/components/ProviderIcon";
|
import { ProviderIcon } from "@/components/ProviderIcon";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface AppSwitcherProps {
|
interface AppSwitcherProps {
|
||||||
activeApp: AppId;
|
activeApp: AppId;
|
||||||
@@ -52,22 +53,28 @@ export function AppSwitcher({
|
|||||||
key={app}
|
key={app}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleSwitch(app)}
|
onClick={() => handleSwitch(app)}
|
||||||
className={`group inline-flex items-center gap-2 px-3 h-8 rounded-md text-sm font-medium transition-all duration-200 ${
|
className={cn(
|
||||||
|
"group inline-flex items-center px-3 h-8 rounded-md text-sm font-medium transition-all duration-200",
|
||||||
activeApp === app
|
activeApp === app
|
||||||
? "bg-background text-foreground shadow-sm"
|
? "bg-background text-foreground shadow-sm"
|
||||||
: "text-muted-foreground hover:text-foreground hover:bg-background/50"
|
: "text-muted-foreground hover:text-foreground hover:bg-background/50",
|
||||||
}`}
|
)}
|
||||||
>
|
>
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
icon={appIconName[app]}
|
icon={appIconName[app]}
|
||||||
name={appDisplayName[app]}
|
name={appDisplayName[app]}
|
||||||
size={iconSize}
|
size={iconSize}
|
||||||
/>
|
/>
|
||||||
{!compact && (
|
<span
|
||||||
<span className="transition-all duration-200 whitespace-nowrap">
|
className={cn(
|
||||||
{appDisplayName[app]}
|
"transition-all duration-200 whitespace-nowrap overflow-hidden",
|
||||||
</span>
|
compact
|
||||||
)}
|
? "max-w-0 opacity-0 ml-0"
|
||||||
|
: "max-w-[80px] opacity-100 ml-2",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{appDisplayName[app]}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ export function useAutoCompact(
|
|||||||
): boolean {
|
): boolean {
|
||||||
const [compact, setCompact] = useState(false);
|
const [compact, setCompact] = useState(false);
|
||||||
const normalWidthRef = useRef(0);
|
const normalWidthRef = useRef(0);
|
||||||
|
const lockUntilRef = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = containerRef.current;
|
const el = containerRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
const ro = new ResizeObserver(() => {
|
const ro = new ResizeObserver(() => {
|
||||||
|
// During expand animation, ignore resize events to prevent flicker
|
||||||
|
if (Date.now() < lockUntilRef.current) return;
|
||||||
|
|
||||||
if (!compact) {
|
if (!compact) {
|
||||||
// Cache the total content width in normal mode
|
// Cache the total content width in normal mode
|
||||||
normalWidthRef.current = el.scrollWidth;
|
normalWidthRef.current = el.scrollWidth;
|
||||||
@@ -31,6 +35,8 @@ export function useAutoCompact(
|
|||||||
// In compact mode: only recover to normal if
|
// In compact mode: only recover to normal if
|
||||||
// available space >= what normal mode needed
|
// available space >= what normal mode needed
|
||||||
if (el.clientWidth >= normalWidthRef.current) {
|
if (el.clientWidth >= normalWidthRef.current) {
|
||||||
|
// Lock out resize events during the expand animation (200ms + 50ms margin)
|
||||||
|
lockUntilRef.current = Date.now() + 250;
|
||||||
setCompact(false);
|
setCompact(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user