SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
4.8 KB · 109 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { Check, Copy } from "lucide-react";4import { cn } from "@/lib/utils";5import { Button } from "./button";67export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {8  return <div className={cn("shimmer rounded-md", className)} {...props} />;9}1011export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {12  return <div className={cn("rounded-xl border border-border bg-bg-elevated", className)} {...props} />;13}1415export function CardHeader({ title, description, action, className }: { title: React.ReactNode; description?: React.ReactNode; action?: React.ReactNode; className?: string }) {16  return (17    <div className={cn("flex items-start justify-between gap-4 px-5 pt-5 pb-3", className)}>18      <div className="min-w-0">19        <h3 className="text-[15px] font-semibold leading-6 tracking-tight">{title}</h3>20        {description ? <p className="mt-0.5 text-[13px] text-fg-muted">{description}</p> : null}21      </div>22      {action}23    </div>24  );25}2627export function CardBody({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {28  return <div className={cn("px-5 pb-5", className)} {...props} />;29}3031export function Divider({ className }: { className?: string }) {32  return <hr className={cn("border-border", className)} />;33}3435export function EmptyState({ icon, title, description, action, className }: { icon?: React.ReactNode; title: string; description?: string; action?: React.ReactNode; className?: string }) {36  return (37    <div className={cn("flex flex-col items-center justify-center rounded-xl border border-dashed border-border px-6 py-12 text-center", className)}>38      {icon ? <div className="mb-3 flex size-10 items-center justify-center rounded-lg bg-bg-muted text-fg-muted [&_svg]:size-5">{icon}</div> : null}39      <p className="text-sm font-medium">{title}</p>40      {description ? <p className="mt-1 max-w-sm text-[13px] text-fg-muted">{description}</p> : null}41      {action ? <div className="mt-4">{action}</div> : null}42    </div>43  );44}4546export function CopyButton({ value, className, size = "icon-sm", label = "Copy" }: { value: string; className?: string; size?: "icon-sm" | "icon" | "xs" | "sm"; label?: string }) {47  const [copied, setCopied] = React.useState(false);48  return (49    <Button50      variant="ghost"51      size={size}52      className={className}53      aria-label={label}54      onClick={async () => {55        try {56          await navigator.clipboard.writeText(value);57          setCopied(true);58          setTimeout(() => setCopied(false), 1400);59        } catch {60          /* ignore */61        }62      }}63    >64      {copied ? <Check className="text-success" /> : <Copy />}65      {size === "xs" || size === "sm" ? <span>{copied ? "Copied" : label}</span> : null}66    </Button>67  );68}6970export function PageHeader({ title, description, actions, className }: { title: React.ReactNode; description?: React.ReactNode; actions?: React.ReactNode; className?: string }) {71  return (72    <div className={cn("flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between", className)}>73      <div className="min-w-0">74        <h1 className="text-xl font-semibold tracking-tight sm:text-2xl">{title}</h1>75        {description ? <p className="mt-1 text-sm text-fg-muted">{description}</p> : null}76      </div>77      {actions ? <div className="flex shrink-0 items-center gap-2">{actions}</div> : null}78    </div>79  );80}8182export function Stat({ label, value, hint, className }: { label: string; value: React.ReactNode; hint?: React.ReactNode; className?: string }) {83  return (84    <div className={cn("rounded-xl border border-border bg-bg-elevated px-4 py-3.5", className)}>85      <p className="text-[12px] font-medium text-fg-muted">{label}</p>86      <p className="mt-1 text-xl font-semibold tabular-nums tracking-tight">{value}</p>87      {hint ? <p className="mt-0.5 text-xs text-fg-subtle">{hint}</p> : null}88    </div>89  );90}9192export function Kbd({ children, className }: { children: React.ReactNode; className?: string }) {93  return <kbd className={cn("kbd", className)}>{children}</kbd>;94}9596export function Spinner({ className }: { className?: string }) {97  return (98    <svg className={cn("size-4 animate-spin text-fg-muted", className)} viewBox="0 0 24 24" fill="none" aria-hidden>99      <circle cx="12" cy="12" r="9" stroke="currentColor" strokeOpacity="0.25" strokeWidth="3" />100      <path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />101    </svg>102  );103}104105/** Marks a control whose feature is not implemented yet. Never ship a silent placeholder button. */106export function ComingSoon({ className }: { className?: string }) {107  return <span className={cn("inline-flex items-center rounded-full border border-dashed border-border-strong px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-fg-subtle", className)}>Coming soon</span>;108}109