Web InnoventixFreeCode

Profile Stats

Original · free

profile with stats and badges

byWeb InnoventixReact + Tailwind
profilestatsprofiles
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/profile-stats.json
profile-stats.tsx
"use client";

import { useCallback, useId, useMemo, useRef, useState, type KeyboardEvent } from "react";
import { motion, useReducedMotion } from "motion/react";

type RangeKey = "30d" | "90d" | "12m";

type Stat = {
  key: string;
  label: string;
  hint: string;
  values: Record<RangeKey, number>;
  deltas: Record<RangeKey, number>;
  suffix?: string;
  spark: Record<RangeKey, number[]>;
};

type Badge = {
  id: string;
  name: string;
  earned: string;
  detail: string;
  tone: "indigo" | "emerald" | "amber" | "violet" | "sky";
  icon: "shield" | "spark" | "flame" | "compass" | "bolt";
};

const RANGES: { key: RangeKey; label: string }[] = [
  { key: "30d", label: "30 days" },
  { key: "90d", label: "90 days" },
  { key: "12m", label: "12 months" },
];

const STATS: Stat[] = [
  {
    key: "merged",
    label: "Pull requests merged",
    hint: "Across 11 repositories in the design-systems org",
    values: { "30d": 24, "90d": 71, "12m": 268 },
    deltas: { "30d": 12, "90d": 8, "12m": 31 },
    spark: {
      "30d": [4, 6, 5, 9, 7, 11, 8, 12],
      "90d": [18, 22, 19, 27, 24, 31, 29, 34],
      "12m": [12, 19, 24, 21, 28, 33, 30, 38],
    },
  },
  {
    key: "reviews",
    label: "Code reviews given",
    hint: "Median first response under 3 hours",
    values: { "30d": 96, "90d": 284, "12m": 1142 },
    deltas: { "30d": 21, "90d": 14, "12m": 47 },
    spark: {
      "30d": [9, 14, 11, 17, 15, 21, 19, 24],
      "90d": [31, 44, 38, 52, 47, 61, 55, 68],
      "12m": [62, 78, 71, 94, 88, 112, 101, 126],
    },
  },
  {
    key: "adoption",
    label: "Component adoption",
    hint: "Share of product surfaces on the shared kit",
    values: { "30d": 87, "90d": 82, "12m": 64 },
    deltas: { "30d": 5, "90d": 9, "12m": 23 },
    suffix: "%",
    spark: {
      "30d": [79, 81, 80, 83, 84, 86, 85, 87],
      "90d": [71, 74, 73, 77, 78, 80, 81, 82],
      "12m": [41, 47, 52, 50, 56, 59, 61, 64],
    },
  },
  {
    key: "ship",
    label: "Avg. time to ship",
    hint: "Draft to production, weighted by scope",
    values: { "30d": 3.4, "90d": 4.1, "12m": 5.8 },
    deltas: { "30d": -18, "90d": -11, "12m": -29 },
    suffix: " d",
    spark: {
      "30d": [5.1, 4.7, 4.9, 4.2, 4.0, 3.7, 3.6, 3.4],
      "90d": [6.2, 5.8, 5.9, 5.1, 4.8, 4.5, 4.3, 4.1],
      "12m": [8.4, 7.9, 7.2, 7.5, 6.8, 6.3, 6.0, 5.8],
    },
  },
];

const BADGES: Badge[] = [
  {
    id: "maintainer",
    name: "Core maintainer",
    earned: "Since Mar 2023",
    detail: "Owns release trains for the tokens and primitives packages.",
    tone: "indigo",
    icon: "shield",
  },
  {
    id: "a11y",
    name: "Accessibility lead",
    earned: "Earned Aug 2024",
    detail: "Drove the WCAG 2.2 AA audit across 340 screens; zero blockers left open.",
    tone: "emerald",
    icon: "spark",
  },
  {
    id: "streak",
    name: "180-day streak",
    earned: "Active now",
    detail: "At least one merged change every working day since 19 Jan.",
    tone: "amber",
    icon: "flame",
  },
  {
    id: "mentor",
    name: "Mentor of the year",
    earned: "Earned Dec 2025",
    detail: "Guided 7 engineers through their first design-system contribution.",
    tone: "violet",
    icon: "compass",
  },
  {
    id: "perf",
    name: "Performance guild",
    earned: "Earned May 2025",
    detail: "Cut the component bundle by 41 kB gzipped without dropping a single API.",
    tone: "sky",
    icon: "bolt",
  },
];

const TONE_CLASS: Record<Badge["tone"], string> = {
  indigo:
    "border-indigo-200 bg-indigo-50 text-indigo-700 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300",
  emerald:
    "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-300",
  amber:
    "border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-300",
  violet:
    "border-violet-200 bg-violet-50 text-violet-700 dark:border-violet-500/30 dark:bg-violet-500/10 dark:text-violet-300",
  sky: "border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-500/30 dark:bg-sky-500/10 dark:text-sky-300",
};

function BadgeIcon({ name }: { name: Badge["icon"] }) {
  const common = {
    viewBox: "0 0 24 24",
    fill: "none",
    stroke: "currentColor",
    strokeWidth: 1.6,
    strokeLinecap: "round" as const,
    strokeLinejoin: "round" as const,
    "aria-hidden": true,
    className: "h-4 w-4",
  };
  if (name === "shield") {
    return (
      <svg {...common}>
        <path d="M12 3 5 6v5.2c0 4.3 2.9 8.2 7 9.8 4.1-1.6 7-5.5 7-9.8V6l-7-3Z" />
        <path d="m9.2 12 2 2 3.6-3.9" />
      </svg>
    );
  }
  if (name === "spark") {
    return (
      <svg {...common}>
        <path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M18.4 5.6l-2.8 2.8M8.4 15.6l-2.8 2.8" />
        <circle cx="12" cy="12" r="2.6" />
      </svg>
    );
  }
  if (name === "flame") {
    return (
      <svg {...common}>
        <path d="M12 3c.4 2.6-1.2 3.6-2.4 5C8.2 9.6 7 11 7 13.4A5 5 0 0 0 17 14c0-3.4-2.3-4.6-2.9-7.4-1 .7-1.6 1.6-1.8 2.7-.9-1-1.4-2-1.4-3.4" />
        <path d="M12 20a2.4 2.4 0 0 1-2.4-2.4c0-1.5 2.4-3.2 2.4-3.2s2.4 1.7 2.4 3.2A2.4 2.4 0 0 1 12 20Z" />
      </svg>
    );
  }
  if (name === "compass") {
    return (
      <svg {...common}>
        <circle cx="12" cy="12" r="9" />
        <path d="m15.2 8.8-1.9 4.5-4.5 1.9 1.9-4.5 4.5-1.9Z" />
      </svg>
    );
  }
  return (
    <svg {...common}>
      <path d="M13.4 2.5 4.8 13.2h5.6l-.8 8.3 8.6-10.7h-5.6l.8-8.3Z" />
    </svg>
  );
}

function Sparkline({ points, positive }: { points: number[]; positive: boolean }) {
  const path = useMemo(() => {
    const min = Math.min(...points);
    const max = Math.max(...points);
    const span = max - min || 1;
    const w = 100;
    const h = 28;
    return points
      .map((p, i) => {
        const x = (i / (points.length - 1)) * w;
        const y = h - ((p - min) / span) * (h - 4) - 2;
        return `${i === 0 ? "M" : "L"}${x.toFixed(2)} ${y.toFixed(2)}`;
      })
      .join(" ");
  }, [points]);

  return (
    <svg
      viewBox="0 0 100 28"
      preserveAspectRatio="none"
      aria-hidden="true"
      className="h-7 w-full overflow-visible"
    >
      <path
        d={path}
        fill="none"
        strokeWidth={2}
        strokeLinecap="round"
        strokeLinejoin="round"
        className={
          positive
            ? "stroke-emerald-500 dark:stroke-emerald-400"
            : "stroke-indigo-500 dark:stroke-indigo-400"
        }
        vectorEffect="non-scaling-stroke"
      />
    </svg>
  );
}

export default function ProfileStats() {
  const reduce = useReducedMotion();
  const [range, setRange] = useState<RangeKey>("30d");
  const [openBadge, setOpenBadge] = useState<string | null>(null);
  const [following, setFollowing] = useState(false);
  const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
  const panelId = useId();
  const tabsId = useId();

  const onTabKeyDown = useCallback(
    (e: KeyboardEvent<HTMLButtonElement>, index: number) => {
      let next = -1;
      if (e.key === "ArrowRight") next = (index + 1) % RANGES.length;
      else if (e.key === "ArrowLeft") next = (index - 1 + RANGES.length) % RANGES.length;
      else if (e.key === "Home") next = 0;
      else if (e.key === "End") next = RANGES.length - 1;
      if (next === -1) return;
      e.preventDefault();
      setRange(RANGES[next].key);
      tabRefs.current[next]?.focus();
    },
    [],
  );

  const fmt = (n: number, suffix?: string) => {
    const base = Number.isInteger(n) ? n.toLocaleString("en-US") : n.toFixed(1);
    return `${base}${suffix ?? ""}`;
  };

  return (
    <section className="relative w-full bg-white px-4 py-20 text-slate-900 sm:px-6 sm:py-24 dark:bg-slate-950 dark:text-slate-100">
      <style>{`
        @keyframes pfst-ring-pulse {
          0%, 100% { transform: scale(1); opacity: 0.55; }
          50% { transform: scale(1.14); opacity: 0; }
        }
        @keyframes pfst-rise {
          from { opacity: 0; transform: translateY(10px); }
          to { opacity: 1; transform: translateY(0); }
        }
        .pfst-ring { animation: pfst-ring-pulse 2.8s ease-out infinite; }
        .pfst-rise { animation: pfst-rise 520ms cubic-bezier(0.22, 1, 0.36, 1) both; }
        @media (prefers-reduced-motion: reduce) {
          .pfst-ring, .pfst-rise { animation: none !important; }
        }
      `}</style>

      <div
        aria-hidden="true"
        className="pointer-events-none absolute inset-x-0 top-0 h-64 bg-[radial-gradient(60rem_24rem_at_50%_-8rem,rgba(99,102,241,0.14),transparent)] dark:bg-[radial-gradient(60rem_24rem_at_50%_-8rem,rgba(129,140,248,0.16),transparent)]"
      />

      <div className="relative mx-auto max-w-5xl">
        <div className="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
          <div className="h-24 w-full bg-[linear-gradient(110deg,#4f46e5_0%,#7c3aed_45%,#0ea5e9_100%)] sm:h-28" />

          <div className="px-5 pb-8 sm:px-8">
            <div className="-mt-12 flex flex-col gap-6 sm:-mt-14 sm:flex-row sm:items-end sm:justify-between">
              <div className="flex items-end gap-4">
                <div className="relative shrink-0">
                  {!reduce && (
                    <span
                      aria-hidden="true"
                      className="pfst-ring absolute inset-0 rounded-2xl bg-indigo-400/40"
                    />
                  )}
                  <div className="relative grid h-24 w-24 place-items-center rounded-2xl border-4 border-white bg-gradient-to-br from-indigo-500 to-violet-600 text-2xl font-semibold tracking-tight text-white shadow-lg sm:h-28 sm:w-28 dark:border-slate-900">
                    RA
                  </div>
                  <span
                    className="absolute -right-1 -bottom-1 grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-emerald-500 dark:border-slate-900"
                    title="Available for reviews"
                  >
                    <span className="sr-only">Available for reviews</span>
                    <svg
                      viewBox="0 0 24 24"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth={3}
                      strokeLinecap="round"
                      strokeLinejoin="round"
                      aria-hidden="true"
                      className="h-3.5 w-3.5 text-white"
                    >
                      <path d="m5 12.5 4.2 4.2L19 7" />
                    </svg>
                  </span>
                </div>

                <div className="pb-1">
                  <h2 className="text-xl font-semibold tracking-tight sm:text-2xl">
                    Rania Abdel-Karim
                  </h2>
                  <p className="mt-0.5 text-sm text-slate-600 dark:text-slate-400">
                    Staff engineer, design systems &middot; Rotterdam (UTC+1)
                  </p>
                </div>
              </div>

              <div className="flex flex-wrap items-center gap-2">
                <button
                  type="button"
                  aria-pressed={following}
                  onClick={() => setFollowing((v) => !v)}
                  className={`inline-flex h-10 items-center gap-2 rounded-full px-4 text-sm font-medium transition focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:outline-none dark:focus-visible:ring-offset-slate-900 ${
                    following
                      ? "border border-slate-300 bg-white text-slate-700 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800"
                      : "bg-indigo-600 text-white hover:bg-indigo-500"
                  }`}
                >
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth={1.8}
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    aria-hidden="true"
                    className="h-4 w-4"
                  >
                    {following ? (
                      <path d="m5 12.5 4.2 4.2L19 7" />
                    ) : (
                      <path d="M12 5v14M5 12h14" />
                    )}
                  </svg>
                  {following ? "Following" : "Follow"}
                </button>
                <a
                  href="https://example.com/rania/handbook"
                  target="_blank"
                  rel="noopener"
                  className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-300 px-4 text-sm font-medium text-slate-700 transition hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:outline-none dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-900"
                >
                  Team handbook
                  <svg
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth={1.8}
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    aria-hidden="true"
                    className="h-3.5 w-3.5"
                  >
                    <path d="M8 16 16 8M9 8h7v7" />
                  </svg>
                </a>
              </div>
            </div>

            <p className="mt-6 max-w-2xl text-sm leading-relaxed text-slate-600 dark:text-slate-400">
              Maintains the primitives and tokens packages that back 340 product screens.
              Currently untangling theming so every surface can flip to dark mode without a
              per-team migration. Reviews are open on Tuesdays and Thursdays.
            </p>

            <div className="mt-7">
              <div className="flex flex-wrap items-center justify-between gap-3">
                <h3 className="text-sm font-semibold tracking-tight text-slate-900 dark:text-slate-100">
                  Contribution stats
                </h3>
                <div
                  role="tablist"
                  aria-label="Stats time range"
                  id={tabsId}
                  className="inline-flex rounded-full border border-slate-200 bg-slate-50 p-1 dark:border-slate-800 dark:bg-slate-950/60"
                >
                  {RANGES.map((r, i) => {
                    const selected = r.key === range;
                    return (
                      <button
                        key={r.key}
                        ref={(el) => {
                          tabRefs.current[i] = el;
                        }}
                        type="button"
                        role="tab"
                        id={`${tabsId}-${r.key}`}
                        aria-selected={selected}
                        aria-controls={panelId}
                        tabIndex={selected ? 0 : -1}
                        onClick={() => setRange(r.key)}
                        onKeyDown={(e) => onTabKeyDown(e, i)}
                        className={`relative rounded-full px-3 py-1.5 text-xs font-medium transition focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:outline-none dark:focus-visible:ring-offset-slate-900 ${
                          selected
                            ? "text-white"
                            : "text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100"
                        }`}
                      >
                        {selected && (
                          <motion.span
                            layoutId="pfst-range-pill"
                            aria-hidden="true"
                            transition={
                              reduce
                                ? { duration: 0 }
                                : { type: "spring", stiffness: 420, damping: 34 }
                            }
                            className="absolute inset-0 rounded-full bg-slate-900 dark:bg-indigo-600"
                          />
                        )}
                        <span className="relative">{r.label}</span>
                      </button>
                    );
                  })}
                </div>
              </div>

              <div
                role="tabpanel"
                id={panelId}
                aria-labelledby={`${tabsId}-${range}`}
                tabIndex={0}
                className="mt-4 grid gap-3 rounded-xl focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:outline-none sm:grid-cols-2 lg:grid-cols-4"
              >
                {STATS.map((s) => {
                  const delta = s.deltas[range];
                  const good = s.key === "ship" ? delta < 0 : delta > 0;
                  return (
                    <div
                      key={`${s.key}-${range}`}
                      className="pfst-rise rounded-xl border border-slate-200 bg-slate-50/70 p-4 dark:border-slate-800 dark:bg-slate-950/40"
                    >
                      <p className="text-xs font-medium text-slate-500 dark:text-slate-400">
                        {s.label}
                      </p>
                      <div className="mt-1.5 flex items-baseline gap-2">
                        <span className="text-2xl font-semibold tracking-tight tabular-nums">
                          {fmt(s.values[range], s.suffix)}
                        </span>
                        <span
                          className={`inline-flex items-center gap-0.5 text-xs font-medium tabular-nums ${
                            good
                              ? "text-emerald-600 dark:text-emerald-400"
                              : "text-rose-600 dark:text-rose-400"
                          }`}
                        >
                          <svg
                            viewBox="0 0 24 24"
                            fill="none"
                            stroke="currentColor"
                            strokeWidth={2.2}
                            strokeLinecap="round"
                            strokeLinejoin="round"
                            aria-hidden="true"
                            className={`h-3 w-3 ${delta < 0 ? "rotate-180" : ""}`}
                          >
                            <path d="M12 19V5M6 11l6-6 6 6" />
                          </svg>
                          {Math.abs(delta)}%
                        </span>
                      </div>
                      <p className="sr-only">
                        {good ? "Improved" : "Declined"} {Math.abs(delta)} percent over the
                        last {RANGES.find((r) => r.key === range)?.label}.
                      </p>
                      <div className="mt-3">
                        <Sparkline points={s.spark[range]} positive={good} />
                      </div>
                      <p className="mt-2 text-[11px] leading-snug text-slate-500 dark:text-slate-500">
                        {s.hint}
                      </p>
                    </div>
                  );
                })}
              </div>
            </div>

            <div className="mt-8">
              <h3 className="text-sm font-semibold tracking-tight text-slate-900 dark:text-slate-100">
                Badges
              </h3>
              <p className="mt-1 text-xs text-slate-500 dark:text-slate-400">
                Select a badge to see how it was earned.
              </p>
              <ul className="mt-3 flex flex-wrap gap-2">
                {BADGES.map((b) => {
                  const open = openBadge === b.id;
                  return (
                    <li key={b.id}>
                      <button
                        type="button"
                        aria-expanded={open}
                        aria-controls={`${panelId}-${b.id}`}
                        onClick={() => setOpenBadge(open ? null : b.id)}
                        className={`inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-xs font-medium transition focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:outline-none dark:focus-visible:ring-offset-slate-900 ${
                          TONE_CLASS[b.tone]
                        } ${open ? "ring-2 ring-slate-900/10 dark:ring-white/20" : ""}`}
                      >
                        <BadgeIcon name={b.icon} />
                        {b.name}
                      </button>
                    </li>
                  );
                })}
              </ul>

              <div aria-live="polite" className="mt-3">
                {BADGES.filter((b) => b.id === openBadge).map((b) => (
                  <motion.div
                    key={b.id}
                    id={`${panelId}-${b.id}`}
                    initial={reduce ? false : { opacity: 0, y: -6 }}
                    animate={{ opacity: 1, y: 0 }}
                    transition={reduce ? { duration: 0 } : { duration: 0.22, ease: "easeOut" }}
                    className="rounded-xl border border-slate-200 bg-slate-50/70 p-4 dark:border-slate-800 dark:bg-slate-950/40"
                  >
                    <p className="text-sm font-medium text-slate-900 dark:text-slate-100">
                      {b.name}
                      <span className="ml-2 text-xs font-normal text-slate-500 dark:text-slate-400">
                        {b.earned}
                      </span>
                    </p>
                    <p className="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
                      {b.detail}
                    </p>
                  </motion.div>
                ))}
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →