Files
kandev/apps/web/components/task/panel-primitives.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

128 lines
3.7 KiB
TypeScript

import { forwardRef, type ReactNode, type HTMLAttributes } from "react";
import { cn } from "@kandev/ui/lib/utils";
/**
* Reusable dockview panel layout primitives.
*
* PanelRoot - outermost wrapper, fills the dockview content area
* PanelBody - scrollable (or non-scrollable) content region
* PanelToolbar - fixed header strip for panel actions
*/
const PANEL_ROOT_CLASS = "h-full min-h-0 flex flex-col bg-card text-card-foreground";
const PANEL_BAR_CLASS =
"flex items-center gap-1.5 h-[30px] px-2.5 shrink-0 border-border/80 bg-card/95 text-xs text-foreground";
const PANEL_ACTION_CURSOR_CLASS =
"[&_button:not(:disabled)]:cursor-pointer [&_[role=button]:not([aria-disabled=true])]:cursor-pointer";
type PanelRootProps = HTMLAttributes<HTMLDivElement> & {
children: ReactNode;
className?: string;
};
/** Fills the dockview content slot. Use as the outermost element in every panel. */
export const PanelRoot = forwardRef<HTMLDivElement, PanelRootProps>(function PanelRoot(
{ children, className, ...rest },
ref,
) {
return (
<div ref={ref} className={cn(PANEL_ROOT_CLASS, className)} {...rest}>
{children}
</div>
);
});
type PanelBodyProps = Omit<HTMLAttributes<HTMLDivElement>, "className"> & {
children: ReactNode;
className?: string;
/** Add default p-2.5 padding. Default true. */
padding?: boolean;
/** Enable overflow scrolling. Default true. */
scroll?: boolean;
};
/** Flexible content area that grows to fill remaining space. */
export const PanelBody = forwardRef<HTMLDivElement, PanelBodyProps>(function PanelBody(
{ children, className, padding = true, scroll = true, ...rest },
ref,
) {
return (
<div
ref={ref}
className={cn(
"flex-1 min-h-0 bg-card text-card-foreground",
scroll && "overflow-auto",
padding && "p-2.5",
className,
)}
{...rest}
>
{children}
</div>
);
});
type PanelToolbarProps = {
children: ReactNode;
className?: string;
};
type PanelBarProps = {
children?: ReactNode;
className?: string;
borderClassName: "border-b" | "border-t";
};
function PanelBar({ children, className, borderClassName }: PanelBarProps) {
return (
<div className={cn(PANEL_BAR_CLASS, PANEL_ACTION_CURSOR_CLASS, borderClassName, className)}>
{children}
</div>
);
}
/** Fixed header toolbar strip. Doesn't scroll with content. */
export function PanelToolbar({ children, className }: PanelToolbarProps) {
return <PanelHeaderBar className={className}>{children}</PanelHeaderBar>;
}
type PanelHeaderBarProps = {
children?: ReactNode;
className?: string;
};
/** Fixed-height panel header bar. Renders children directly. */
export function PanelHeaderBar({ children, className }: PanelHeaderBarProps) {
return (
<PanelBar borderClassName="border-b" className={className}>
{children}
</PanelBar>
);
}
type PanelHeaderBarSplitProps = {
left?: ReactNode;
right?: ReactNode;
className?: string;
};
/** Panel header bar with left/right slots separated by a spacer. */
export function PanelHeaderBarSplit({ left, right, className }: PanelHeaderBarSplitProps) {
return (
<PanelHeaderBar className={className}>
<div className="flex items-center gap-1.5 min-w-0 overflow-hidden">{left}</div>
<div className="flex-1" />
<div className="flex items-center gap-1.5 min-w-0 overflow-hidden">{right}</div>
</PanelHeaderBar>
);
}
/** Fixed-height panel footer bar with border-t. Mirrors PanelHeaderBar but anchors to the bottom. */
export function PanelFooterBar({ children, className }: PanelHeaderBarProps) {
return (
<PanelBar borderClassName="border-t" className={className}>
{children}
</PanelBar>
);
}