Sidebar settings: hide a lone unpinned tab, match New chat icon, rename Profile menu

- With exactly one tab unpinned, both More and that tab are dropped, so nothing
  is drawn for it (previously it rendered inline). The page stays reachable by URL.
- The customizer's New chat preview uses PencilEdit02Icon, the icon the real row
  renders; Edit03Icon was a different glyph.
- "Sidebar menu" is now "Profile menu", described as the shortcuts behind your
  name at the bottom of the sidebar, so it no longer reads as a second name for
  the navigation section above it.
This commit is contained in:
michaelhan 2026-07-25 01:36:06 -07:00
commit 521aafe993
6 changed files with 22 additions and 22 deletions

View file

@ -735,8 +735,8 @@ SIDEBAR_MENU_ITEM_DEFAULTS = {
}
# Navigable sidebar rows the user can pin/reorder; the boolean is each id's
# default pin state, matching the shipped layout. An unpinned row moves into the
# "More" flyout client-side, so it stays reachable.
# default pin state, matching the shipped layout. Unpinned rows collect in the
# "More" flyout client-side; a single unpinned row is hidden there instead.
SIDEBAR_NAV_ITEM_DEFAULTS = {
"projects": True,
"hub": True,

View file

@ -829,11 +829,11 @@ export function AppSidebar() {
.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.
// appears once it would hold two or more. With exactly one row unpinned, both
// the menu and that row are dropped -- the page stays reachable by URL.
const overflowNavIds = unpinnedNavIds.length > 1 ? unpinnedNavIds : [];
const inlineNavIds = sidebarNav
.filter((item) => item.pinned || overflowNavIds.length === 0)
.filter((item) => item.pinned)
.map((item) => item.id);
const showSidebarBrand = !usesCustomTitlebar;

View file

@ -6,11 +6,11 @@ import {
DashboardCircleIcon,
DownloadSquare01Icon,
DragDropVerticalIcon,
Edit03Icon,
FlimSlateIcon,
Folder01Icon,
Image03Icon,
MoreHorizontalIcon,
PencilEdit02Icon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { Reorder, useDragControls } from "motion/react";
@ -107,11 +107,11 @@ 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
* -- 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.
* Pin and reorder the sidebar navigation rows. Rows with their switch off collect
* in the "More" flyout. A single unpinned row is hidden outright instead: More
* would be a menu of one, so neither it nor the row is drawn (the page stays
* reachable by URL). New chat renders as a static row: it is an action pinned to
* the top, not a destination.
*/
export function SidebarNavCustomizer() {
const t = useT();
@ -120,7 +120,7 @@ export function SidebarNavCustomizer() {
const unpinnedCount = sidebarNav.filter((item) => !item.pinned).length;
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={PencilEdit02Icon} label={t("shell.navigation.newChat")} />
<Reorder.Group
axis="y"
values={sidebarNav.map((item) => item.id)}
@ -138,8 +138,8 @@ export function SidebarNavCustomizer() {
))}
</Reorder.Group>
{/* 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. */}
row, and a single unpinned row is hidden rather than sitting behind a
menu built for one item. */}
{unpinnedCount > 1 && (
<>
<div className="mx-2 my-1 border-t border-border/70" />

View file

@ -86,8 +86,8 @@ 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 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.
* "More" flyout. A single unpinned row is hidden outright rather than getting a
* flyout of one; the page stays reachable by URL.
*/
export const SIDEBAR_NAV_ITEM_IDS = [
"projects",

View file

@ -519,15 +519,15 @@ 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, unless it is the only one left. New chat stays fixed.",
"Pin and reorder the sidebar tabs. Unpinned tabs collect in the More menu; a single unpinned tab is hidden instead of getting a menu of one. New chat stays fixed.",
dragToReorder: "Drag to reorder",
pinToSidebar: "Pin {name} to the sidebar",
moreHolds: "More ({count})",
},
sidebarMenu: {
title: "Sidebar menu",
title: "Profile menu",
description:
"Show, hide, and reorder items in the sidebar profile menu. Settings, Help, Log out, and Shutdown stay fixed.",
"Choose which shortcuts appear when you click your name at the bottom of the sidebar, and in what order. Settings, Help, Log out, and Shutdown always appear.",
darkModeToggle: "Dark mode toggle",
dragToReorder: "Drag to reorder",
},

View file

@ -373,15 +373,15 @@ export const zhCN = {
sidebarNav: {
title: "侧边栏导航",
description:
"固定并重新排序侧边栏标签。取消固定的项目会移入“更多”菜单而不是被隐藏;若仅剩一项,则直接显示。新聊天保持固定。",
"固定并重新排序侧边栏标签。取消固定的标签会收进“更多”菜单;若只有一个取消固定,则直接隐藏,而不是为它单独建一个菜单。新聊天保持固定。",
dragToReorder: "拖动以重新排序",
pinToSidebar: "将{name}固定到侧边栏",
moreHolds: "更多({count}",
},
sidebarMenu: {
title: "侧边栏菜单",
title: "个人菜单",
description:
"显示、隐藏和重新排序侧边栏个人菜单中的项目。设置、帮助、退出登录和关闭保持固定。",
"选择点击侧边栏底部你的名字时显示哪些快捷项及其顺序。设置、帮助、退出登录和关闭始终显示。",
darkModeToggle: "深色模式切换",
dragToReorder: "拖动以重新排序",
},