diff --git a/web/src/components/AppSidebar/AppSidebar.tsx b/web/src/components/AppSidebar/AppSidebar.tsx
index 574c9750..446cd5d9 100644
--- a/web/src/components/AppSidebar/AppSidebar.tsx
+++ b/web/src/components/AppSidebar/AppSidebar.tsx
@@ -22,7 +22,7 @@ import {
Trash2Icon,
UserRoundIcon,
} from "lucide-react";
-import { useEffect, useState } from "react";
+import { type ReactNode, useEffect, useState } from "react";
import toast from "react-hot-toast";
import { Link, matchPath, useLocation, useNavigate, useSearchParams } from "react-router-dom";
import ConfirmDialog from "@/components/ConfirmDialog";
@@ -62,7 +62,7 @@ import { User_Role, UserNotification_Status } from "@/types/proto/api/v1/user_se
import { useTranslate } from "@/utils/i18n";
import MemosLogo from "../MemosLogo";
import { getSidebarRouteKind } from "./routes";
-import SidebarRow, { SIDEBAR_ROW_CLASSES, SIDEBAR_ROW_ICON_CLASSES, sidebarRowStateClasses } from "./SidebarRow";
+import SidebarRow, { SIDEBAR_ROW_CLASSES, SIDEBAR_ROW_FOCUS_CLASSES, SIDEBAR_ROW_ICON_CLASSES, sidebarRowStateClasses } from "./SidebarRow";
import SidebarSection, {
SIDEBAR_SECTION_ACTION_BUTTON_CLASSES,
SIDEBAR_SECTION_ACTION_ICON_CLASSES,
@@ -391,8 +391,41 @@ interface GlobalNavItem {
icon: LucideIcon;
active: boolean;
count?: number;
+ alwaysExpanded?: boolean;
}
+/**
+ * Pills keep a constant px so the icon sits exactly where it does in the collapsed 30px
+ * square; all width change comes from the label column, which animates 0fr -> 1fr. That
+ * keeps the expand/collapse a single smooth motion with no padding jump.
+ */
+const navPillClasses = (active: boolean) =>
+ cn(
+ "relative flex h-[30px] min-w-0 items-center rounded-md px-[7px] transition-colors",
+ SIDEBAR_ROW_FOCUS_CLASSES,
+ sidebarRowStateClasses(active),
+ );
+
+const NavPillLabel = ({ expanded, label, children }: { expanded: boolean; label: ReactNode; children?: ReactNode }) => (
+
+ {/* Content is shrink-0 so the collapsing track clips it in place — a plain
+ left-to-right reveal instead of re-truncating the label on every frame. */}
+
+ {label}
+ {children}
+
+
+);
+
const GlobalNavigation = () => {
const t = useTranslate();
const location = useLocation();
@@ -458,27 +491,11 @@ const GlobalNavigation = () => {
path: ROUTES.EXPLORE,
icon: EarthIcon,
active: routeKind === "explore" || routeKind === "profile" || routeKind === "memo",
+ alwaysExpanded: true,
},
{ id: "about", label: t("common.about"), path: ROUTES.ABOUT, icon: InfoIcon, active: location.pathname === ROUTES.ABOUT },
];
- const scopeTrigger = (
-
- }
- >
-
- {activeScopeItem.label}
-
-
- );
-
const scopeMenuContent = (
{scopeItems.map((item) => {
@@ -505,68 +522,74 @@ const GlobalNavigation = () => {