Sidebar customizer: drop the Search row, skip More for a lone item

- Search is reached from the top bar, so it is no longer previewed as a fixed
  sidebar nav row; New chat stays.
- More now appears only when it would hold two or more rows. A single unpinned
  row renders inline in its saved order position instead: a flyout wrapping one
  item costs a click and earns nothing. The customizer's More preview follows the
  same threshold.
This commit is contained in:
michaelhan 2026-07-25 01:31:35 -07:00
commit ec9178ab4a
5 changed files with 23 additions and 19 deletions

View file

@ -825,12 +825,16 @@ export function AppSidebar() {
},
},
};
const pinnedNavIds = sidebarNav
.filter((item) => item.pinned)
.map((item) => item.id);
const overflowNavIds = sidebarNav
const unpinnedNavIds = sidebarNav
.filter((item) => !item.pinned)
.map((item) => item.id);
// A flyout wrapping a single row costs a click and earns nothing, so More only
// appears once it would hold two or more; a lone unpinned row renders inline,
// in its saved order position.
const overflowNavIds = unpinnedNavIds.length > 1 ? unpinnedNavIds : [];
const inlineNavIds = sidebarNav
.filter((item) => item.pinned || overflowNavIds.length === 0)
.map((item) => item.id);
const showSidebarBrand = !usesCustomTitlebar;
const showCompactMacBrand = showSidebarBrand && usesNativeMacTitlebar;
@ -1612,7 +1616,7 @@ export function AppSidebar() {
{/* Rows come from the saved pin order (Settings -> Appearance ->
Sidebar navigation). Pinned ids render here in order; the rest
fall into the More flyout below, so nothing is unreachable. */}
{pinnedNavIds.map((id) => {
{inlineNavIds.map((id) => {
const row = navRows[id];
return (
<NavItem

View file

@ -11,7 +11,6 @@ import {
Folder01Icon,
Image03Icon,
MoreHorizontalIcon,
Search01Icon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { Reorder, useDragControls } from "motion/react";
@ -109,9 +108,10 @@ function MovableRow({ item }: { item: SidebarNavItemPref }) {
/**
* Pin and reorder the sidebar navigation rows. A row with its switch off moves
* into the "More" flyout instead of disappearing, so every page stays reachable.
* New Chat and Search render as static rows: they are actions pinned to the top,
* not destinations.
* into the "More" flyout instead of disappearing, so every page stays reachable
* -- unless it is the only unpinned row, which stays inline (a menu holding one
* item earns nothing). New chat renders as a static row: it is an action pinned
* to the top, not a destination.
*/
export function SidebarNavCustomizer() {
const t = useT();
@ -121,7 +121,6 @@ export function SidebarNavCustomizer() {
return (
<div className="flex flex-col rounded-xl border border-border/70 p-1.5">
<FixedRow icon={Edit03Icon} label={t("shell.navigation.newChat")} />
<FixedRow icon={Search01Icon} label={t("shell.navigation.search")} />
<Reorder.Group
axis="y"
values={sidebarNav.map((item) => item.id)}
@ -138,9 +137,10 @@ export function SidebarNavCustomizer() {
<MovableRow key={item.id} item={item} />
))}
</Reorder.Group>
{/* Only meaningful once something is unpinned -- with everything pinned the
sidebar has no More row at all. */}
{unpinnedCount > 0 && (
{/* Only meaningful at two or more: with everything pinned there is no More
row, and a lone unpinned row renders inline rather than hiding behind
a menu built for one item. */}
{unpinnedCount > 1 && (
<>
<div className="mx-2 my-1 border-t border-border/70" />
<FixedRow

View file

@ -84,10 +84,10 @@ export const SIDEBAR_MENU_DEFAULT_VISIBLE: Record<SidebarMenuItemId, boolean> =
/**
* Sidebar NAVIGATION rows the user can pin and reorder (distinct from the
* profile-menu entries above). New Chat and Search stay fixed at the top: they
* are actions, not destinations. Array order is render order; an unpinned row
* moves into the "More" flyout rather than disappearing, so no page becomes
* unreachable.
* profile-menu entries above). New chat stays fixed at the top: it is an action,
* not a destination. Array order is render order; an unpinned row moves into the
* "More" flyout rather than disappearing, so no page becomes unreachable --
* except when it is the only unpinned row, which renders inline instead.
*/
export const SIDEBAR_NAV_ITEM_IDS = [
"projects",

View file

@ -519,7 +519,7 @@ export const en = {
sidebarNav: {
title: "Sidebar navigation",
description:
"Pin and reorder the sidebar tabs. Anything unpinned moves into the More menu instead of being hidden. New chat and Search stay fixed.",
"Pin and reorder the sidebar tabs. Anything unpinned moves into the More menu instead of being hidden, unless it is the only one left. New chat stays fixed.",
dragToReorder: "Drag to reorder",
pinToSidebar: "Pin {name} to the sidebar",
moreHolds: "More ({count})",

View file

@ -373,7 +373,7 @@ export const zhCN = {
sidebarNav: {
title: "侧边栏导航",
description:
"固定并重新排序侧边栏标签。取消固定的项目会移入“更多”菜单而不是被隐藏。新聊天和搜索保持固定。",
"固定并重新排序侧边栏标签。取消固定的项目会移入“更多”菜单而不是被隐藏;若仅剩一项,则直接显示。新聊天保持固定。",
dragToReorder: "拖动以重新排序",
pinToSidebar: "将{name}固定到侧边栏",
moreHolds: "更多({count}",