spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import type { HTMLAttributes, ReactNode } from "react";2import { cn } from "./cn";34/** Flat bordered surface. No shadow, 3 px radius. */5export function Card({ className, children, ...rest }: HTMLAttributes<HTMLDivElement>) {6 return (7 <div className={cn("rounded-sm border border-hair bg-surface", className)} {...rest}>8 {children}9 </div>10 );11}1213export function CardHeader({ title, description, aside, className, level = 3 }: { title: ReactNode; description?: ReactNode; aside?: ReactNode; className?: string; level?: 2 | 3 }) {14 const H = level === 2 ? "h2" : "h3";15 return (16 <div className={cn("flex items-start justify-between gap-3 border-b border-hair px-3 py-2.5 sm:px-4", className)}>17 <div className="min-w-0">18 <H className="text-[13.5px] font-semibold leading-tight text-ink">{title}</H>19 {description && <p className="mt-0.5 text-[12px] text-ink-3">{description}</p>}20 </div>21 {aside && <div className="shrink-0">{aside}</div>}22 </div>23 );24}2526export function CardBody({ className, children, padded = true }: { className?: string; children: ReactNode; padded?: boolean }) {27 return <div className={cn(padded && "px-3 py-3 sm:px-4", className)}>{children}</div>;28}2930/** Page-level section: heading row + content, hairline top. */31export function Section({ title, description, aside, children, className, id, level = 2 }: { title: ReactNode; description?: ReactNode; aside?: ReactNode; children: ReactNode; className?: string; id?: string; level?: 2 | 3 }) {32 const H = level === 2 ? "h2" : "h3";33 return (34 <section id={id} className={cn("min-w-0 border-t border-hair pt-4", className)}>35 <div className="mb-3 flex items-end justify-between gap-3">36 <div className="min-w-0">37 <H className={cn("font-semibold leading-tight text-ink", level === 2 ? "text-[15px] sm:text-[16px]" : "text-[14px]")}>{title}</H>38 {description && <p className="mt-0.5 text-[12px] text-ink-3">{description}</p>}39 </div>40 {aside && <div className="shrink-0 text-right">{aside}</div>}41 </div>42 {children}43 </section>44 );45}4647/** Uniform page container. */48export function Page({ children, className, wide }: { children: ReactNode; className?: string; wide?: boolean }) {49 return <div className={cn("mx-auto w-full px-3 sm:px-5 lg:px-8", wide ? "max-w-[1600px]" : "max-w-[1280px]", className)}>{children}</div>;50}5152export function PageHeader({ title, eyebrow, description, aside, className }: { title: ReactNode; eyebrow?: ReactNode; description?: ReactNode; aside?: ReactNode; className?: string }) {53 return (54 <header className={cn("flex flex-col gap-3 py-5 sm:flex-row sm:items-end sm:justify-between sm:py-7", className)}>55 <div className="min-w-0">56 {eyebrow && <div className="label-caps mb-1.5">{eyebrow}</div>}57 <h1 className="text-[22px] font-semibold leading-tight tracking-[-0.02em] text-ink sm:text-[28px]">{title}</h1>58 {description && <p className="mt-1.5 max-w-2xl text-[13.5px] text-ink-2">{description}</p>}59 </div>60 {aside && <div className="shrink-0">{aside}</div>}61 </header>62 );63}64