Files
kandev/apps/web/components/page-topbar.tsx
T
José Almeida 68c94dfa3a feat: refine workbench chrome (#761)
* docs: define product and design direction

* style: establish restrained workbench primitives

* refactor: declutter board and task topbars

* style: align dockview panel chrome

* fix: align task topbar vcs control sizing

* refactor: surface integrations navigation

* fix: repair integrations and task creation workflow

* refactor: include linear in integrations navigation

* style: restore familiar workbench tones

* fix: add dockview tab breathing room

* fix: show configured integration shortcuts

* fix: hover open integrations menu

* fix: stabilize integrations hover menu

* fix: align home and dockview topbars

* fix: normalize home topbar controls

* fix: compact home topbar height

* fix: simplify home chrome and split buttons

* fix: move integrations to left chrome

* fix: restore home stats shortcut

* fix: add dockview tab breathing room

* fix: border home stats button

* fix: streamline dockview topbar actions

* fix: align task split button seam

* fix: address review feedback

* fix: restore topbar e2e affordances

* fix: stabilize workflow e2e isolation

* fix: add task topbar action overflow

* fix: address PR review feedback
2026-05-01 21:02:41 +01:00

122 lines
3.7 KiB
TypeScript

import Link from "next/link";
import { forwardRef } from "react";
import type { ReactNode } from "react";
import { IconArrowLeft } from "@tabler/icons-react";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@kandev/ui/breadcrumb";
import { cn } from "@kandev/ui/lib/utils";
type PageTopbarProps = {
/** Page title shown in the breadcrumb */
title: string;
/** Optional subtitle shown to the right of the title */
subtitle?: string;
/** Optional icon rendered before the title */
icon?: ReactNode;
/** Where the back link navigates to (default: "/") */
backHref?: string;
/** Label for the parent breadcrumb (default: "KanDev") */
backLabel?: string;
/** Optional content rendered before the breadcrumb */
leading?: ReactNode;
/** Optional content rendered at the visual center of the topbar */
center?: ReactNode;
/** Optional content rendered alongside the left orientation label */
leftActions?: ReactNode;
/** Optional content rendered on the right side of the topbar */
actions?: ReactNode;
variant?: "breadcrumb" | "root";
className?: string;
centerClassName?: string;
actionsClassName?: string;
};
export const PageTopbar = forwardRef<HTMLElement, PageTopbarProps>(function PageTopbar(
{
title,
subtitle,
icon,
backHref = "/",
backLabel = "KanDev",
leading,
center,
leftActions,
actions,
variant = "breadcrumb",
className,
centerClassName,
actionsClassName,
},
ref,
) {
return (
<header
ref={ref}
className={cn("relative flex h-14 shrink-0 items-center gap-3 border-b px-4", className)}
>
{leading}
{variant === "root" ? (
<div className="relative z-10 flex min-w-0 items-center">
<span className="truncate text-[15px] font-semibold leading-none">{backLabel}</span>
</div>
) : (
<Breadcrumb className="relative z-10 min-w-0">
<BreadcrumbList className="flex-nowrap">
<BreadcrumbItem className="shrink-0">
<BreadcrumbLink asChild className="flex items-center gap-1.5 cursor-pointer">
<Link href={backHref}>
<IconArrowLeft className="h-3.5 w-3.5" />
{backLabel}
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="shrink-0" />
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="flex min-w-0 items-center gap-2">
{icon}
<span className="truncate text-sm font-semibold">{title}</span>
{subtitle && (
<>
<span className="hidden text-muted-foreground/50 sm:inline">·</span>
<span className="hidden truncate text-xs text-muted-foreground sm:inline">
{subtitle}
</span>
</>
)}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
)}
{leftActions && (
<div className="relative z-10 flex shrink-0 items-center gap-1 [&:empty]:hidden">
{leftActions}
</div>
)}
{center && (
<div
className={cn(
"pointer-events-none absolute left-1/2 top-1/2 z-0 -translate-x-1/2 -translate-y-1/2",
centerClassName,
)}
>
<div className="pointer-events-auto">{center}</div>
</div>
)}
{actions && (
<div
className={cn("relative z-10 ml-auto flex shrink-0 items-center gap-2", actionsClassName)}
>
{actions}
</div>
)}
</header>
);
});