142 行
4.0 KiB
TypeScript
142 行
4.0 KiB
TypeScript
import { Children, Fragment, isValidElement, type ReactNode } from "react";
|
|
import { cn } from "../../lib/utils";
|
|
import { SectionPanel } from "./SectionPanel";
|
|
|
|
export function BusinessDetailWorkspace({
|
|
children,
|
|
className,
|
|
testId,
|
|
}: {
|
|
children: ReactNode;
|
|
className?: string;
|
|
testId?: string;
|
|
}) {
|
|
return (
|
|
<div className={cn("detail-workspace-grid", className)} data-testid={testId}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function BusinessDetailMain({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: ReactNode;
|
|
className?: string;
|
|
}) {
|
|
return <div className={cn("space-y-5", className)}>{children}</div>;
|
|
}
|
|
|
|
export function BusinessDetailAside({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: ReactNode;
|
|
className?: string;
|
|
}) {
|
|
return <div className={cn("space-y-5", className)}>{children}</div>;
|
|
}
|
|
|
|
export function BusinessDetailFactGrid({
|
|
children,
|
|
className,
|
|
testId,
|
|
}: {
|
|
children: ReactNode;
|
|
className?: string;
|
|
testId?: string;
|
|
}) {
|
|
return (
|
|
<div className={cn("detail-fact-grid", className)} data-testid={testId}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function BusinessDetailFact({
|
|
label,
|
|
children,
|
|
className,
|
|
}: {
|
|
label: ReactNode;
|
|
children: ReactNode;
|
|
className?: string;
|
|
}) {
|
|
return (
|
|
<div className={cn("detail-fact-card", className)}>
|
|
<div className="detail-fact-card__label">{label}</div>
|
|
<div className="detail-fact-card__value">{children}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function hasVisibleNode(node: ReactNode): boolean {
|
|
return Children.toArray(node).some((child) => {
|
|
if (isValidElement(child) && child.type === Fragment) {
|
|
return hasVisibleNode(child.props.children);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
export function BusinessDetailActionPanel({
|
|
title = "操作",
|
|
status,
|
|
beforeActions,
|
|
primaryAction,
|
|
secondaryActions,
|
|
moreActions,
|
|
inlineActions = false,
|
|
summary,
|
|
children,
|
|
className,
|
|
testId,
|
|
}: {
|
|
title?: string;
|
|
status?: ReactNode;
|
|
beforeActions?: ReactNode;
|
|
primaryAction?: ReactNode;
|
|
secondaryActions?: ReactNode;
|
|
moreActions?: ReactNode;
|
|
inlineActions?: boolean;
|
|
summary?: ReactNode;
|
|
children?: ReactNode;
|
|
className?: string;
|
|
testId?: string;
|
|
}) {
|
|
const hasStatus = hasVisibleNode(status);
|
|
const hasBeforeActions = hasVisibleNode(beforeActions);
|
|
const hasPrimaryAction = hasVisibleNode(primaryAction);
|
|
const hasSecondaryActions = hasVisibleNode(secondaryActions);
|
|
const hasMoreActions = hasVisibleNode(moreActions);
|
|
const hasSummary = hasVisibleNode(summary);
|
|
const hasChildren = hasVisibleNode(children);
|
|
const hasActionContent = hasPrimaryAction || hasSecondaryActions || hasMoreActions;
|
|
const hasContentBeforeSummary = hasBeforeActions || hasActionContent || hasChildren;
|
|
|
|
if (!hasStatus && !hasBeforeActions && !hasActionContent && !hasSummary && !hasChildren) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<SectionPanel title={title} actions={status} className={cn("detail-action-panel lg:sticky lg:top-24", className)}>
|
|
<div className="detail-action-stack" data-testid={testId}>
|
|
{hasBeforeActions ? <div>{beforeActions}</div> : null}
|
|
{inlineActions && (hasPrimaryAction || hasMoreActions) ? <div className="flex flex-wrap items-center gap-2" data-testid="detail-inline-actions">{primaryAction}{moreActions}</div> : null}
|
|
{!inlineActions && hasPrimaryAction ? <div className="grid gap-2">{primaryAction}</div> : null}
|
|
{hasSecondaryActions ? <div className="workflow-action-bar">{secondaryActions}</div> : null}
|
|
{!inlineActions && hasMoreActions ? <div className="border-t border-zinc-200 pt-3">{moreActions}</div> : null}
|
|
{hasChildren ? (
|
|
<div className={cn(hasActionContent && "border-t border-zinc-200 pt-3")}>{children}</div>
|
|
) : null}
|
|
{hasSummary ? (
|
|
<div className={cn(hasContentBeforeSummary && "border-t border-zinc-200 pt-3")}>
|
|
<div className="space-y-2 text-sm text-zinc-600">{summary}</div>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</SectionPanel>
|
|
);
|
|
}
|