mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Polish sidebar navigation spacing
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" }],
|
||||
]);
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user