Polish sidebar navigation spacing

This commit is contained in:
klopez4212
2026-06-23 19:25:50 +01:00
parent 40070a5855
commit bb517843fc
16 changed files with 62 additions and 53 deletions
@@ -202,7 +202,7 @@ export function AgentSessionThreadPanel({
"flex cursor-default select-none items-center",
isSinglePanelView
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-[4.75rem] min-h-[4.75rem] shrink-0 gap-2.5 bg-background/80 pb-1 pl-4 pr-2 pt-[2.625rem] backdrop-blur-md supports-[backdrop-filter]:bg-background/70 sm:pl-6 sm:pr-3 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55`
: "relative z-50 min-h-11 shrink-0 gap-3 bg-background/80 px-3 py-1.5 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55",
: "relative z-50 min-h-14 shrink-0 gap-3 bg-background/80 px-5 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55",
)}
data-tauri-drag-region
>
@@ -92,7 +92,6 @@ export function ChannelScreenHeader({
<ChatHeader
belowSystemChrome
chromeWrapperRef={chromeWrapperRef}
density="compact"
actions={actions}
channelType={activeChannel?.channelType}
description={getChannelDescription(activeChannel)}
@@ -704,11 +704,9 @@ function SearchResultSectionTitle({
children: React.ReactNode;
}) {
return (
<div className="sticky top-0 z-10 mr-3 flex min-h-9 items-center gap-2 bg-background/95 px-4 pb-1.5 pt-3 text-xs font-medium uppercase tracking-wide text-muted-foreground/75 backdrop-blur supports-[backdrop-filter]:bg-background/80">
<div className="sticky top-0 z-10 mr-3 flex min-h-9 items-center gap-2 bg-background/95 px-4 pb-1.5 pt-3 text-xs font-medium text-muted-foreground/75 backdrop-blur supports-[backdrop-filter]:bg-background/80">
<span>{children}</span>
{action ? (
<span className="normal-case tracking-normal">{action}</span>
) : null}
{action ? <span>{action}</span> : null}
</div>
);
}
+2 -9
View File
@@ -25,7 +25,6 @@ type ChatHeaderProps = {
belowSystemChrome?: boolean;
/** Ref to the outer chrome wrapper when `belowSystemChrome` is true. */
chromeWrapperRef?: React.Ref<HTMLDivElement>;
density?: "default" | "compact";
title: string;
description?: string;
channelType?: ChannelType;
@@ -87,7 +86,6 @@ export function ChatHeader({
actions,
belowSystemChrome = false,
chromeWrapperRef,
density = "default",
title,
description,
channelType,
@@ -117,13 +115,8 @@ export function ChatHeader({
const header = (
<header
className={cn(
"pointer-events-auto relative z-30 flex min-w-0 shrink-0 cursor-default select-none items-center gap-2.5 bg-transparent px-5 transition-[margin,padding] duration-200 ease-linear",
density === "compact"
? belowSystemChrome
? "min-h-8 py-1.5"
: "min-h-8 py-0"
: "min-h-11 py-1.5",
overlaysContent && !belowSystemChrome && "-mb-11",
"pointer-events-auto relative z-30 flex min-h-14 min-w-0 shrink-0 cursor-default select-none items-center gap-2.5 bg-transparent px-5 py-2 transition-[margin,padding] duration-200 ease-linear",
overlaysContent && !belowSystemChrome && "-mb-14",
clearCollapsedTopChromeControls && "pl-[176px]",
)}
data-testid="chat-header"
@@ -5,8 +5,8 @@ export function HomeLoadingState() {
<div className="min-h-0 flex-1 overflow-hidden">
<div className="grid h-full min-h-0 w-full lg:grid-cols-[320px_minmax(0,1fr)]">
<div className="relative overflow-hidden bg-background/60 after:absolute after:bottom-0 after:right-0 after:top-10 after:w-px after:bg-border/70 after:content-['']">
<div className="px-5 py-1">
<div className="flex min-w-0 items-center justify-between gap-3">
<div className="px-5 py-2">
<div className="flex min-h-10 min-w-0 items-center justify-between gap-3">
<div className="flex min-w-0 items-center gap-[6px]">
<Skeleton className="h-4 w-4 shrink-0 rounded-md" />
<Skeleton className="h-4 w-14" />
@@ -51,8 +51,8 @@ export function HomeLoadingState() {
</div>
<div className="relative flex min-h-0 flex-col overflow-hidden bg-background/60">
<div className="px-5 py-1 pr-3">
<div className="flex min-w-0 items-center justify-between gap-3">
<div className="px-5 py-2">
<div className="flex min-h-10 min-w-0 items-center justify-between gap-3">
<div className="flex min-w-0 items-center gap-[4px]">
<Skeleton className="h-4 w-4 shrink-0 rounded-md" />
<Skeleton className="h-4 w-40" />
@@ -246,8 +246,8 @@ export function InboxDetailPane({
>
<div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">
<TopChromeInsetHeader flush>
<div className="px-5 py-1 pr-3">
<div className="flex min-w-0 items-center justify-between gap-3">
<div className="px-5 py-2">
<div className="flex min-h-10 min-w-0 items-center justify-between gap-3">
<div
className={cn(
"flex min-w-0 items-center",
@@ -325,13 +325,13 @@ export function InboxListPane({
)}
>
<TopChromeInsetHeader flush>
<div className="px-3 py-1">
<div className="flex w-full min-w-0 items-center justify-between gap-3">
<div className="px-5 py-2">
<div className="flex min-h-10 w-full min-w-0 items-center justify-between gap-3">
<Popover>
<PopoverTrigger asChild>
<button
aria-label="Inbox options"
className={INBOX_HEADER_ICON_BUTTON_CLASS}
className={cn(INBOX_HEADER_ICON_BUTTON_CLASS, "-ml-4")}
data-testid="inbox-options-trigger"
type="button"
>
@@ -381,7 +381,10 @@ export function InboxListPane({
<DropdownMenuTrigger asChild>
<button
aria-label={`Filter inbox: ${activeFilter?.label ?? "All"}`}
className={cn(INBOX_HEADER_ICON_BUTTON_CLASS, "relative")}
className={cn(
INBOX_HEADER_ICON_BUTTON_CLASS,
"relative -mr-4",
)}
data-testid="inbox-filter-trigger"
type="button"
>
@@ -322,7 +322,7 @@ export function MessageThreadPanelSkeleton({
"flex cursor-default select-none items-center",
isSinglePanelView
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-[4.75rem] min-h-[4.75rem] shrink-0 gap-2.5 bg-background/80 pb-[0.1875rem] pl-4 pr-2 pt-[2.6875rem] backdrop-blur-md supports-[backdrop-filter]:bg-background/70 sm:pr-3 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55`
: "relative z-50 min-h-11 shrink-0 gap-3 bg-background/80 px-3 py-1.5 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55",
: "relative z-50 min-h-14 shrink-0 gap-3 bg-background/80 px-5 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55",
)}
data-tauri-drag-region
>
@@ -990,7 +990,7 @@ export function MessageThreadPanel({
"flex cursor-default select-none items-center",
isSinglePanelView
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-[4.75rem] min-h-[4.75rem] shrink-0 gap-2.5 bg-background/80 pb-[0.1875rem] pl-4 pr-2 pt-[2.6875rem] backdrop-blur-md supports-[backdrop-filter]:bg-background/70 sm:pr-3 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55`
: "relative z-50 min-h-11 shrink-0 gap-3 bg-background/80 px-3 py-1.5 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55",
: "relative z-50 min-h-14 shrink-0 gap-3 bg-background/80 px-5 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55",
)}
data-tauri-drag-region
>
@@ -469,7 +469,7 @@ export function UserProfilePanel({
isSinglePanelView
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-[4.75rem] min-h-[4.75rem] shrink-0 gap-2.5 bg-transparent pb-1 pl-4 pr-2 pt-[2.625rem] sm:pl-6 sm:pr-3`
: isOverlay
? "relative z-50 min-h-11 shrink-0 gap-3 bg-background/80 px-3 py-1.5 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55"
? "relative z-50 min-h-14 shrink-0 gap-3 bg-background/80 px-5 py-2 backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55"
: "absolute inset-x-0 top-[2.625rem] z-50 min-h-8 gap-3 bg-transparent px-3 py-1 after:absolute after:bottom-0 after:-left-px after:top-0 after:w-px after:bg-border/45 after:transition-colors peer-hover/profile-resize:after:bg-border/80 peer-focus-visible/profile-resize:after:bg-border/80",
)}
data-tauri-drag-region
@@ -619,7 +619,7 @@ export function AppSidebar({
</div>
<SidebarContent
className="buzz-sidebar-scrollbar overscroll-none"
className="buzz-sidebar-scrollbar overscroll-none pt-4"
ref={scrollRef}
>
{isLoading ? (
@@ -716,9 +716,6 @@ export function AppSidebar({
browseAriaLabel="Browse channels"
createAriaLabel="Create a channel"
draggable
groupClassName={
channelSections.length > 0 ? undefined : "pt-1"
}
hasUnread={unreadChannelIds.size > 0}
isCollapsed={collapsedGroups.channels}
isActiveChannel={selectedView === "channel"}
@@ -802,7 +799,7 @@ export function AppSidebar({
presenceByChannelId={dmPresenceByChannelId}
selectedChannelId={selectedChannelId}
testId="dm-list"
title="Direct Messages"
title="Direct messages"
unreadChannelCounts={unreadChannelCounts}
unreadChannelIds={unreadChannelIds}
mutedChannelIds={mutedChannelIds}
@@ -11,7 +11,6 @@ import {
GripVertical,
Pencil,
Plus,
Search,
Star,
StarOff,
Trash2,
@@ -48,6 +47,7 @@ import {
import type { ChannelSection } from "@/features/sidebar/lib/useChannelSections";
import type { Channel } from "@/shared/api/types";
import { cn } from "@/shared/lib/cn";
import { HashSearch } from "@/shared/ui/icons";
// ---------------------------------------------------------------------------
// Shared styles
@@ -56,7 +56,7 @@ import { cn } from "@/shared/lib/cn";
const SECTION_LABEL_BUTTON_CLASS =
"group/section-label flex w-fit max-w-[calc(100%-3rem)] cursor-pointer appearance-none items-center gap-1 text-left transition-colors hover:text-sidebar-foreground focus-visible:text-sidebar-foreground";
const SECTION_LABEL_CHEVRON_CLASS =
"relative size-2.5 shrink-0 opacity-0 text-sidebar-foreground/45 transition-[color,opacity] group-hover/sidebar-section:opacity-100 group-hover/sidebar-section:text-sidebar-foreground group-hover/section-label:opacity-100 group-hover/section-label:text-sidebar-foreground group-focus-within/sidebar-section:opacity-100 group-focus-within/sidebar-section:text-sidebar-foreground group-focus-visible/section-label:opacity-100 group-focus-visible/section-label:text-sidebar-foreground";
"relative size-2.5 shrink-0 text-current opacity-0 transition-[color,opacity] group-hover/sidebar-section:opacity-100 group-hover/section-label:opacity-100 group-focus-within/sidebar-section:opacity-100 group-focus-visible/section-label:opacity-100";
const SECTION_LABEL_CHEVRON_ICON_CLASS =
"absolute left-1/2 top-1/2 size-2.5 -translate-x-1/2 -translate-y-1/2";
@@ -261,18 +261,24 @@ function SectionHeaderActions({
{onBrowseClick ? (
<button
aria-label={browseAriaLabel}
className={SECTION_ICON_BUTTON_CLASS}
className={cn(
SECTION_ICON_BUTTON_CLASS,
SECTION_ACTION_VISIBILITY_CLASS,
)}
onClick={onBrowseClick}
title={browseAriaLabel}
type="button"
>
<Search className="h-4 w-4" />
<HashSearch className="h-4 w-4" />
</button>
) : null}
{onCreateClick ? (
<button
aria-label={createAriaLabel}
className={SECTION_ICON_BUTTON_CLASS}
className={cn(
SECTION_ICON_BUTTON_CLASS,
SECTION_ACTION_VISIBILITY_CLASS,
)}
onClick={onCreateClick}
type="button"
>
@@ -416,8 +422,8 @@ export function ChannelGroupSection({
) : null;
const sectionContent = (
<SidebarGroup className={groupClassName}>
<div className="group/sidebar-section relative">
<SidebarGroup className={cn("group/sidebar-section", groupClassName)}>
<div className="relative">
<SidebarGroupLabel asChild>
<button
aria-controls={contentId}
@@ -535,13 +541,12 @@ export function CustomChannelSection({
<SortableSectionShell sectionId={section.id}>
{({ dragHandleProps, isDragging }) => (
<DroppableSectionBody sectionId={section.id}>
<SidebarGroup className={cn(isDragging && "opacity-30")}>
<SidebarGroup
className={cn("group/sidebar-section", isDragging && "opacity-30")}
>
<ContextMenu>
<ContextMenuTrigger asChild>
<div
className="group/sidebar-section relative"
{...dragHandleProps}
>
<div className="relative" {...dragHandleProps}>
<SidebarGroupLabel asChild>
<button
aria-controls={contentId}
@@ -35,7 +35,7 @@ import { PresenceDot } from "@/features/presence/ui/PresenceBadge";
const SECTION_LABEL_BUTTON_CLASS =
"group/section-label flex w-fit max-w-[calc(100%-3rem)] cursor-pointer appearance-none items-center gap-1 text-left transition-colors hover:text-sidebar-foreground focus-visible:text-sidebar-foreground";
const SECTION_LABEL_CHEVRON_CLASS =
"relative size-2.5 shrink-0 opacity-0 text-sidebar-foreground/45 transition-[color,opacity] group-hover/sidebar-section:opacity-100 group-hover/sidebar-section:text-sidebar-foreground group-hover/section-label:opacity-100 group-hover/section-label:text-sidebar-foreground group-focus-within/sidebar-section:opacity-100 group-focus-within/sidebar-section:text-sidebar-foreground group-focus-visible/section-label:opacity-100 group-focus-visible/section-label:text-sidebar-foreground";
"relative size-2.5 shrink-0 text-current opacity-0 transition-[color,opacity] group-hover/sidebar-section:opacity-100 group-hover/section-label:opacity-100 group-focus-within/sidebar-section:opacity-100 group-focus-visible/section-label:opacity-100";
const SECTION_LABEL_CHEVRON_ICON_CLASS =
"absolute left-1/2 top-1/2 size-2.5 -translate-x-1/2 -translate-y-1/2";
const SIDEBAR_ROW_ACTION_VISIBILITY_CLASS =
@@ -300,8 +300,8 @@ export function SidebarSection({
const canToggle = Boolean(onToggleCollapsed);
return (
<SidebarGroup>
<div className="group/sidebar-section relative">
<SidebarGroup className="group/sidebar-section">
<div className="relative">
<SidebarGroupLabel asChild={canToggle}>
{canToggle ? (
<button
@@ -23,7 +23,7 @@ export function AuxiliaryPanelHeader({
{...props}
>
<div
className="pointer-events-auto relative z-30 flex min-h-8 shrink-0 cursor-default select-none items-center gap-2.5 px-4 py-1.5 sm:pr-3"
className="pointer-events-auto relative z-30 flex min-h-14 shrink-0 cursor-default select-none items-center gap-2.5 px-5 py-2"
data-tauri-drag-region
>
{children}
@@ -21,7 +21,7 @@ type ViewLoadingFallbackProps = {
function LoadingHeaderSkeleton() {
return (
<TopChromeInsetHeader data-tauri-drag-region flush>
<header className="flex min-h-8 min-w-0 cursor-default select-none items-center gap-2.5 px-5 py-1.5">
<header className="flex min-h-14 min-w-0 cursor-default select-none items-center gap-2.5 px-5 py-2">
<div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-1 overflow-hidden">
<Skeleton className="h-4 w-4 shrink-0 rounded-sm" />
+10
View File
@@ -0,0 +1,10 @@
import { createLucideIcon } from "lucide-react";
export const HashSearch = createLucideIcon("hash-search", [
["line", { x1: "4", x2: "20", y1: "9", y2: "9", key: "pulu6f" }],
["line", { x1: "4", x2: "11", y1: "15", y2: "15", key: "th0qa4" }],
["line", { x1: "10", x2: "8", y1: "3", y2: "21", key: "1ggp8o" }],
["line", { x1: "16", x2: "15", y1: "3", y2: "12", key: "noe5so" }],
["path", { d: "m21 21-1.9-1.9", key: "1g2n9r" }],
["circle", { cx: "17", cy: "17", r: "3", key: "18b49y" }],
]);
+7 -3
View File
@@ -802,9 +802,13 @@ test("narrow thread view collapses channel header actions into a menu", async ({
if (!menuBox || !threadPanelBox) {
throw new Error("Expected header action menu and thread panel bounds");
}
const menuGapPx = threadPanelBox.x - (menuBox.x + menuBox.width);
expect(menuGapPx).toBeGreaterThanOrEqual(18);
expect(menuGapPx).toBeLessThanOrEqual(22);
const menuGap = threadPanelBox.x - (menuBox.x + menuBox.width);
const headerPaddingInlineEnd = await page
.getByTestId("chat-header")
.evaluate((header) =>
Number.parseFloat(window.getComputedStyle(header).paddingRight),
);
expect(Math.round(menuGap)).toBe(Math.round(headerPaddingInlineEnd));
await menuTrigger.click();