refactor: Enhance TabBar component with layout grouping and memoization

- Integrated LayoutGroup from framer-motion to improve tab animations and transitions.
- Added useMemo to ensure safeActiveTabId is derived efficiently, preventing unnecessary re-renders.
- Refactored tab rendering logic for better clarity and performance, maintaining existing functionality.
This commit is contained in:
Jamie Pine
2025-12-24 16:47:28 -08:00
parent 938f8326fa
commit 8d9fe080ca

View File

@@ -1,7 +1,8 @@
import clsx from "clsx";
import { motion } from "framer-motion";
import { motion, LayoutGroup } from "framer-motion";
import { Plus, X } from "@phosphor-icons/react";
import { useTabManager } from "./useTabManager";
import { useMemo } from "react";
export function TabBar() {
const { tabs, activeTabId, switchTab, closeTab, createTab } =
@@ -12,53 +13,66 @@ export function TabBar() {
return null;
}
// Ensure activeTabId exists in tabs array, fallback to first tab
// Memoize to prevent unnecessary rerenders during rapid state updates
const safeActiveTabId = useMemo(() => {
return tabs.find((t) => t.id === activeTabId)?.id ?? tabs[0]?.id;
}, [tabs, activeTabId]);
return (
<div className="flex items-center h-9 px-1 gap-1 mx-2 mb-1.5 bg-app-box/50 rounded-full shrink-0">
<div className="flex items-center flex-1 gap-1 min-w-0">
{tabs.map((tab) => (
<motion.button
key={tab.id}
layout
onClick={() => switchTab(tab.id)}
className={clsx(
"relative flex items-center justify-center py-1.5 rounded-full text-[13px] group flex-1 min-w-0",
tab.id === activeTabId
? "text-ink"
: "text-ink-dull hover:text-ink hover:bg-app-hover/50",
)}
>
{tab.id === activeTabId && (
<motion.div
layoutId="activeTab"
className="absolute inset-0 bg-app-selected rounded-full shadow-sm"
transition={{
type: "easeInOut",
duration: 0.15,
}}
/>
)}
{/* Close button - absolutely positioned left */}
<button
onClick={(e) => {
e.stopPropagation();
closeTab(tab.id);
}}
className={clsx(
"absolute left-1.5 z-10 size-5 flex items-center justify-center rounded-full transition-all",
tab.id === activeTabId
? "opacity-60 hover:opacity-100 hover:bg-app-hover"
: "opacity-0 group-hover:opacity-60 hover:!opacity-100 hover:bg-app-hover",
)}
title="Close tab"
>
<X size={10} weight="bold" />
</button>
<span className="relative z-10 truncate px-6">
{tab.title}
</span>
</motion.button>
))}
</div>
<LayoutGroup id="tab-bar">
<div className="flex items-center flex-1 gap-1 min-w-0">
{tabs.map((tab) => {
const isActive = tab.id === safeActiveTabId;
return (
<button
key={tab.id}
onClick={() => switchTab(tab.id)}
className={clsx(
"relative flex items-center justify-center py-1.5 rounded-full text-[13px] group flex-1 min-w-0",
isActive
? "text-ink"
: "text-ink-dull hover:text-ink hover:bg-app-hover/50",
)}
>
{isActive && (
<motion.div
layoutId="activeTab"
className="absolute inset-0 bg-app-selected rounded-full shadow-sm"
initial={false}
transition={{
type: "spring",
stiffness: 500,
damping: 35,
}}
/>
)}
{/* Close button - absolutely positioned left */}
<span
onClick={(e) => {
e.stopPropagation();
closeTab(tab.id);
}}
className={clsx(
"absolute left-1.5 z-10 size-5 flex items-center justify-center rounded-full transition-all cursor-pointer",
isActive
? "opacity-60 hover:opacity-100 hover:bg-app-hover"
: "opacity-0 group-hover:opacity-60 hover:!opacity-100 hover:bg-app-hover",
)}
title="Close tab"
>
<X size={10} weight="bold" />
</span>
<span className="relative z-10 truncate px-6">
{tab.title}
</span>
</button>
);
})}
</div>
</LayoutGroup>
<button
onClick={() => createTab()}
className="size-7 flex items-center justify-center rounded-full hover:bg-app-hover text-ink-dull hover:text-ink shrink-0 transition-colors"