Initial local backup snapshot
This commit is contained in:
commit
acd9e14ba5
367 changed files with 118038 additions and 0 deletions
174
client/src/components/portal/PortalSurface.tsx
Normal file
174
client/src/components/portal/PortalSurface.tsx
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { Link } from "wouter";
|
||||
|
||||
export function PortalDashboardHeader({
|
||||
title,
|
||||
description,
|
||||
actions,
|
||||
backHref = "/dashboard",
|
||||
className,
|
||||
contentClassName,
|
||||
tourId,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
actions?: ReactNode;
|
||||
backHref?: string;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
tourId?: string;
|
||||
}) {
|
||||
return (
|
||||
<header className={cn("sticky top-0 z-10 border-b border-border bg-white", className)}>
|
||||
<div className="container py-3 sm:py-4">
|
||||
<div className="flex items-start gap-3 sm:items-center sm:gap-4">
|
||||
<Link href={backHref}>
|
||||
<Button variant="ghost" size="icon" className="mt-0.5 shrink-0 sm:mt-0">
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
<div data-tour-id={tourId} className={cn("min-w-0 flex-1", contentClassName)}>
|
||||
<PortalPageHeader title={title} description={description} actions={actions} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export function PortalPageHeader({
|
||||
title,
|
||||
description,
|
||||
actions,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
actions?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-3 md:gap-3.5 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="min-w-0 space-y-1.5">
|
||||
<h1 className="text-[1.65rem] font-bold tracking-tight leading-tight text-slate-950 sm:text-[1.8rem]">{title}</h1>
|
||||
{description ? <p className="max-w-3xl text-sm leading-6 text-slate-600 sm:text-[0.95rem]">{description}</p> : null}
|
||||
</div>
|
||||
{actions ? <div className="flex w-full flex-col gap-2.5 pt-0.5 sm:w-auto sm:flex-row sm:flex-wrap sm:justify-end">{actions}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PortalSectionIntro({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
aside,
|
||||
className,
|
||||
eyebrowClassName,
|
||||
titleClassName,
|
||||
descriptionClassName,
|
||||
}: {
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
aside?: ReactNode;
|
||||
className?: string;
|
||||
eyebrowClassName?: string;
|
||||
titleClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between", className)}>
|
||||
<div className="max-w-2xl space-y-1.5">
|
||||
{eyebrow ? (
|
||||
<p className={cn("text-xs font-semibold uppercase tracking-[0.16em] text-primary", eyebrowClassName)}>{eyebrow}</p>
|
||||
) : null}
|
||||
<h2 className={cn("text-lg font-semibold tracking-tight text-slate-950 sm:text-[1.45rem]", titleClassName)}>{title}</h2>
|
||||
{description ? <p className={cn("text-sm leading-6 text-slate-600", descriptionClassName)}>{description}</p> : null}
|
||||
</div>
|
||||
{aside ? (
|
||||
<div className="rounded-xl border border-slate-200 bg-white/92 px-4 py-2.5 text-sm text-slate-600 shadow-sm">
|
||||
{aside}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PortalSurface({
|
||||
title,
|
||||
description,
|
||||
badge,
|
||||
children,
|
||||
className,
|
||||
bodyClassName,
|
||||
}: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
badge?: ReactNode;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
bodyClassName?: string;
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"overflow-hidden rounded-2xl border border-slate-200/90 bg-white/96 shadow-[var(--portal-panel-shadow)] transition-[transform,box-shadow,border-color,background-color] duration-[var(--portal-motion-base)] hover:shadow-[var(--portal-panel-hover-shadow)]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{title || description || badge ? (
|
||||
<div className="border-b border-slate-100 px-4 py-4 sm:px-5">
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="space-y-1">
|
||||
{title ? <h3 className="text-base font-semibold text-slate-950">{title}</h3> : null}
|
||||
{description ? <p className="text-sm leading-6 text-slate-600">{description}</p> : null}
|
||||
</div>
|
||||
{badge ? <div className="shrink-0">{badge}</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={cn("px-4 py-4 sm:px-5", bodyClassName)}>{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function PortalMetricTile({
|
||||
label,
|
||||
value,
|
||||
detail,
|
||||
tone = "default",
|
||||
}: {
|
||||
label: string;
|
||||
value: ReactNode;
|
||||
detail?: ReactNode;
|
||||
tone?: "default" | "success" | "info" | "warning";
|
||||
}) {
|
||||
const toneClassName =
|
||||
tone === "success"
|
||||
? "border-emerald-200 bg-emerald-50"
|
||||
: tone === "info"
|
||||
? "border-blue-200 bg-blue-50"
|
||||
: tone === "warning"
|
||||
? "border-amber-200 bg-amber-50"
|
||||
: "border-slate-200 bg-white";
|
||||
|
||||
return (
|
||||
<div className={cn("rounded-xl border px-4 py-3.5 shadow-[var(--portal-card-shadow)] transition-[transform,box-shadow,border-color,background-color] duration-[var(--portal-motion-base)] hover:-translate-y-0.5 hover:shadow-[var(--portal-card-hover-shadow)]", toneClassName)}>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.12em] text-slate-500">{label}</p>
|
||||
<div className="mt-1.5 text-[1.65rem] font-semibold tracking-tight text-slate-950">{value}</div>
|
||||
{detail ? <div className="mt-1.5 text-xs leading-5 text-slate-500">{detail}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PortalActionGroup({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return <div className={cn("flex flex-col gap-2 sm:flex-row sm:flex-wrap", className)}>{children}</div>;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue