first commit
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Building2 } from "lucide-react";
|
||||
import { Card, Badge, Button } from "../../";
|
||||
import { HeaderProps } from "./type";
|
||||
|
||||
function Avatar({
|
||||
avatar,
|
||||
initials,
|
||||
icon,
|
||||
}: Pick<HeaderProps, "avatar" | "initials" | "icon">) {
|
||||
if (avatar) return <>{avatar}</>;
|
||||
|
||||
if (initials) {
|
||||
return (
|
||||
<div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-xl bg-accent text-lg font-bold uppercase text-accent-foreground">
|
||||
{initials}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-xl bg-accent">
|
||||
{icon ?? <Building2 className="h-7 w-7" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HeaderDetails({
|
||||
title,
|
||||
subtitle,
|
||||
subtitleIcon,
|
||||
avatar,
|
||||
initials,
|
||||
icon,
|
||||
badges,
|
||||
actions,
|
||||
actionsSlot,
|
||||
meta,
|
||||
accent = true,
|
||||
className = "",
|
||||
}: HeaderProps) {
|
||||
const hasActions = actionsSlot || (actions && actions.length > 0);
|
||||
const hasMeta = meta && meta.length > 0;
|
||||
|
||||
return (
|
||||
<Card className={`relative overflow-hidden p-0 ${className}`}>
|
||||
{accent && (
|
||||
<div className="h-1 w-full bg-accent" />
|
||||
)}
|
||||
|
||||
<div className="p-5 sm:p-6">
|
||||
{/* Top row: identity + actions */}
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<Avatar avatar={avatar} initials={initials} icon={icon} />
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-wrap items-center gap-2.5">
|
||||
<h1 className="text-xl sm:text-2xl font-bold leading-tight">
|
||||
{title}
|
||||
</h1>
|
||||
{/* {badges?.map((badge, i) => (
|
||||
<Badge
|
||||
key={i}
|
||||
variant={badge.variant ?? "default"}
|
||||
className={`rounded-full text-xs font-medium ${badge.className ?? ""}`}
|
||||
>
|
||||
{badge.icon && (
|
||||
<span className="mr-1.5 flex items-center">
|
||||
{badge.icon}
|
||||
</span>
|
||||
)}
|
||||
{badge.label}
|
||||
</Badge>
|
||||
))} */}
|
||||
</div>
|
||||
|
||||
{subtitle && (
|
||||
<p className="flex items-center gap-1.5 text-sm text-muted-foreground">
|
||||
<span className="flex items-center">
|
||||
{subtitleIcon ?? <Building2 className="h-4 w-4" />}
|
||||
</span>
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasActions && (
|
||||
<div className="flex items-center justify-end gap-2 shrink-0">
|
||||
{actionsSlot}
|
||||
{actions?.map((action, i) => (
|
||||
<Button
|
||||
key={i}
|
||||
variant={action.variant ?? "default"}
|
||||
onClick={action.onClick}
|
||||
disabled={action.disabled}
|
||||
>
|
||||
{action.icon && (
|
||||
<span className="flex items-center">{action.icon}</span>
|
||||
)}
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Lower meta section: Owner / Industry / Deal Value / Created, etc. */}
|
||||
{hasMeta && (
|
||||
<div className="mt-5 border-t pt-4">
|
||||
<div className="flex flex-wrap gap-x-12 gap-y-4">
|
||||
{meta!.map((item, i) => (
|
||||
<div key={i} className="flex items-center gap-3">
|
||||
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
||||
{item.icon}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
||||
{item.label}
|
||||
</span>
|
||||
<span className="text-sm font-semibold">{item.value}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Badge, Button } from "../../";
|
||||
|
||||
type BadgeVariant = React.ComponentProps<typeof Badge>["variant"];
|
||||
|
||||
export interface HeaderBadge {
|
||||
/** Text/content shown inside the badge */
|
||||
label: React.ReactNode;
|
||||
/** Optional leading icon */
|
||||
icon?: React.ReactNode;
|
||||
variant?: BadgeVariant;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface HeaderMetaItem {
|
||||
/** Small muted label, e.g. "Owner" / "Sales Manager" */
|
||||
label: string;
|
||||
/** The value shown under the label */
|
||||
value: React.ReactNode;
|
||||
/** Icon rendered inside the boxed container on the left */
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface HeaderAction {
|
||||
label: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
icon?: React.ReactNode;
|
||||
variant?: React.ComponentProps<typeof Button>["variant"];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface HeaderProps {
|
||||
title: React.ReactNode;
|
||||
subtitle?: React.ReactNode;
|
||||
/** Small icon shown before the subtitle (defaults to a building icon) */
|
||||
subtitleIcon?: React.ReactNode;
|
||||
|
||||
/**
|
||||
* Leading visual. Priority: `avatar` node → `initials` gradient box →
|
||||
* `icon` → default building icon.
|
||||
*/
|
||||
avatar?: React.ReactNode;
|
||||
/** Initials rendered inside a gradient avatar box, e.g. "FM" */
|
||||
initials?: string;
|
||||
icon?: React.ReactNode;
|
||||
|
||||
/** Status / tag badges rendered inline next to the title */
|
||||
badges?: HeaderBadge[];
|
||||
|
||||
/**
|
||||
* Right-side actions. Pass an array for convenience buttons, or a custom
|
||||
* node via `actionsSlot` for full control.
|
||||
*/
|
||||
actions?: HeaderAction[];
|
||||
actionsSlot?: React.ReactNode;
|
||||
|
||||
/** Lower meta section, e.g. Owner / Industry / Deal Value / Created */
|
||||
meta?: HeaderMetaItem[];
|
||||
|
||||
/** Show the gradient accent bar at the top (default true) */
|
||||
accent?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user