|
| 1 | +import Link from "next/link"; |
| 2 | +import { ArrowLeft, CheckCircle2, AlertTriangle, Info } from "lucide-react"; |
| 3 | + |
| 4 | +export const metadata = { |
| 5 | + title: "Compatibility — UniqueUI", |
| 6 | + description: |
| 7 | + "What we test against in CI vs. what we treat as best-effort. Framework, runtime, and Tailwind support for UniqueUI components and CLI.", |
| 8 | +}; |
| 9 | + |
| 10 | +type Status = "tested" | "best-effort" | "manual" | "unsupported"; |
| 11 | + |
| 12 | +type Row = { |
| 13 | + label: string; |
| 14 | + version: string; |
| 15 | + status: Status; |
| 16 | + notes: string; |
| 17 | +}; |
| 18 | + |
| 19 | +const runtime: Row[] = [ |
| 20 | + { |
| 21 | + label: "Node.js (CLI users)", |
| 22 | + version: "≥ 18", |
| 23 | + status: "tested", |
| 24 | + notes: "Minimum supported by `uniqueui-cli`. Use 20 LTS or 22 LTS for the smoothest experience.", |
| 25 | + }, |
| 26 | + { |
| 27 | + label: "Node.js (this repo contributors)", |
| 28 | + version: "24.x", |
| 29 | + status: "tested", |
| 30 | + notes: "Matches `package.json#engines` for monorepo work; mismatch is fine for end users of the CLI.", |
| 31 | + }, |
| 32 | + { |
| 33 | + label: "pnpm", |
| 34 | + version: "10.33.x", |
| 35 | + status: "tested", |
| 36 | + notes: "Pinned via `packageManager`. Other PMs work for end users (`pnpm`, `npm`, `yarn`, `bun` all detected).", |
| 37 | + }, |
| 38 | +]; |
| 39 | + |
| 40 | +const frameworks: Row[] = [ |
| 41 | + { |
| 42 | + label: "Next.js (App Router)", |
| 43 | + version: "15 – 16", |
| 44 | + status: "tested", |
| 45 | + notes: "The docs site itself runs Next 16. Components use `\"use client\"` where they touch state or motion.", |
| 46 | + }, |
| 47 | + { |
| 48 | + label: "Next.js (Pages Router)", |
| 49 | + version: "13+", |
| 50 | + status: "best-effort", |
| 51 | + notes: "Components are framework-agnostic React. No `next/*` imports in registry code.", |
| 52 | + }, |
| 53 | + { |
| 54 | + label: "Vite + React", |
| 55 | + version: "≥ 5", |
| 56 | + status: "best-effort", |
| 57 | + notes: "After `npx uniqueui init`, edit `components.json` paths if your project uses `src/` or `app/` differently.", |
| 58 | + }, |
| 59 | + { |
| 60 | + label: "Remix / React Router 7", |
| 61 | + version: "≥ 2", |
| 62 | + status: "best-effort", |
| 63 | + notes: "Same caveat as Vite. The CLI does not auto-detect framework yet.", |
| 64 | + }, |
| 65 | + { |
| 66 | + label: "Astro (React island)", |
| 67 | + version: "≥ 4", |
| 68 | + status: "best-effort", |
| 69 | + notes: "Wrap each UniqueUI component in `client:load` or `client:visible`.", |
| 70 | + }, |
| 71 | +]; |
| 72 | + |
| 73 | +const stack: Row[] = [ |
| 74 | + { |
| 75 | + label: "React", |
| 76 | + version: "18 / 19", |
| 77 | + status: "tested", |
| 78 | + notes: "The docs site runs React 19; components are written to also work on 18.", |
| 79 | + }, |
| 80 | + { |
| 81 | + label: "React Server Components", |
| 82 | + version: "—", |
| 83 | + status: "manual", |
| 84 | + notes: |
| 85 | + "UniqueUI components are client components by default (`\"use client\"`). Import them from a Server Component normally.", |
| 86 | + }, |
| 87 | + { |
| 88 | + label: "TypeScript", |
| 89 | + version: "≥ 5", |
| 90 | + status: "tested", |
| 91 | + notes: "Each registry file is strict-mode safe. The CLI itself ships types.", |
| 92 | + }, |
| 93 | + { |
| 94 | + label: "Tailwind CSS v3", |
| 95 | + version: "≥ 3.4", |
| 96 | + status: "tested", |
| 97 | + notes: "The CLI merges component `tailwindConfig.theme.extend` directly into your `tailwind.config.{js,ts}`.", |
| 98 | + }, |
| 99 | + { |
| 100 | + label: "Tailwind CSS v4", |
| 101 | + version: "≥ 4.0", |
| 102 | + status: "manual", |
| 103 | + notes: |
| 104 | + "The docs site uses v4. The CLI's auto-merge still targets a v3-style config file. If your project is v4 CSS-first, copy the component's keyframes / theme tokens into your `@theme` block manually. We are tracking a first-class v4 path in the backlog.", |
| 105 | + }, |
| 106 | + { |
| 107 | + label: "Motion (formerly Framer Motion)", |
| 108 | + version: "≥ 12", |
| 109 | + status: "tested", |
| 110 | + notes: "Components import from `motion/react`. The CLI installs `motion` for you.", |
| 111 | + }, |
| 112 | +]; |
| 113 | + |
| 114 | +const cliFlows: Row[] = [ |
| 115 | + { |
| 116 | + label: "`uniqueui init` — Next.js (App Router)", |
| 117 | + version: "—", |
| 118 | + status: "tested", |
| 119 | + notes: "Smoke-tested locally and in CI.", |
| 120 | + }, |
| 121 | + { |
| 122 | + label: "`uniqueui add <slug>` — every registry component", |
| 123 | + version: "—", |
| 124 | + status: "tested", |
| 125 | + notes: "`scripts/test-all-components.ts` installs each component into a fresh Next app and builds.", |
| 126 | + }, |
| 127 | + { |
| 128 | + label: "shadcn registry — `npx shadcn add .../r/<slug>.json`", |
| 129 | + version: "—", |
| 130 | + status: "tested", |
| 131 | + notes: "`pnpm test:e2e:shadcn` adds every item with the shadcn CLI and runs `next build`.", |
| 132 | + }, |
| 133 | + { |
| 134 | + label: "Offline / airgapped install", |
| 135 | + version: "—", |
| 136 | + status: "manual", |
| 137 | + notes: "Pass `--url file://path/to/built/registry` to point the CLI at a local checkout.", |
| 138 | + }, |
| 139 | +]; |
| 140 | + |
| 141 | +const STATUS_META: Record<Status, { label: string; bg: string; text: string; icon: React.ComponentType<{ className?: string }> }> = { |
| 142 | + tested: { |
| 143 | + label: "Tested in CI", |
| 144 | + bg: "bg-emerald-50 dark:bg-emerald-500/10 border-emerald-200 dark:border-emerald-500/30", |
| 145 | + text: "text-emerald-700 dark:text-emerald-300", |
| 146 | + icon: CheckCircle2, |
| 147 | + }, |
| 148 | + "best-effort": { |
| 149 | + label: "Best effort", |
| 150 | + bg: "bg-amber-50 dark:bg-amber-500/10 border-amber-200 dark:border-amber-500/30", |
| 151 | + text: "text-amber-700 dark:text-amber-300", |
| 152 | + icon: Info, |
| 153 | + }, |
| 154 | + manual: { |
| 155 | + label: "Manual step", |
| 156 | + bg: "bg-blue-50 dark:bg-blue-500/10 border-blue-200 dark:border-blue-500/30", |
| 157 | + text: "text-blue-700 dark:text-blue-300", |
| 158 | + icon: AlertTriangle, |
| 159 | + }, |
| 160 | + unsupported: { |
| 161 | + label: "Not supported", |
| 162 | + bg: "bg-rose-50 dark:bg-rose-500/10 border-rose-200 dark:border-rose-500/30", |
| 163 | + text: "text-rose-700 dark:text-rose-300", |
| 164 | + icon: AlertTriangle, |
| 165 | + }, |
| 166 | +}; |
| 167 | + |
| 168 | +function StatusBadge({ status }: { status: Status }) { |
| 169 | + const meta = STATUS_META[status]; |
| 170 | + const Icon = meta.icon; |
| 171 | + return ( |
| 172 | + <span |
| 173 | + className={`inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-[11px] font-medium ${meta.bg} ${meta.text}`} |
| 174 | + > |
| 175 | + <Icon className="h-3 w-3" aria-hidden /> |
| 176 | + {meta.label} |
| 177 | + </span> |
| 178 | + ); |
| 179 | +} |
| 180 | + |
| 181 | +function Matrix({ title, rows }: { title: string; rows: Row[] }) { |
| 182 | + return ( |
| 183 | + <section className="space-y-4"> |
| 184 | + <h2 className="text-2xl font-bold text-neutral-900 dark:text-white">{title}</h2> |
| 185 | + <div className="overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800"> |
| 186 | + <table className="w-full text-sm"> |
| 187 | + <thead className="bg-neutral-50 dark:bg-neutral-900/40 text-neutral-500 dark:text-neutral-400"> |
| 188 | + <tr> |
| 189 | + <th scope="col" className="px-4 py-2 text-left font-medium">Target</th> |
| 190 | + <th scope="col" className="px-4 py-2 text-left font-medium">Version</th> |
| 191 | + <th scope="col" className="px-4 py-2 text-left font-medium">Status</th> |
| 192 | + <th scope="col" className="px-4 py-2 text-left font-medium">Notes</th> |
| 193 | + </tr> |
| 194 | + </thead> |
| 195 | + <tbody className="divide-y divide-neutral-200 dark:divide-neutral-800"> |
| 196 | + {rows.map((row) => ( |
| 197 | + <tr key={row.label} className="bg-white dark:bg-neutral-950/40 align-top"> |
| 198 | + <td className="px-4 py-3 font-medium text-neutral-900 dark:text-white">{row.label}</td> |
| 199 | + <td className="px-4 py-3 font-mono text-xs text-neutral-700 dark:text-neutral-300">{row.version}</td> |
| 200 | + <td className="px-4 py-3"><StatusBadge status={row.status} /></td> |
| 201 | + <td className="px-4 py-3 text-neutral-600 dark:text-neutral-400">{row.notes}</td> |
| 202 | + </tr> |
| 203 | + ))} |
| 204 | + </tbody> |
| 205 | + </table> |
| 206 | + </div> |
| 207 | + </section> |
| 208 | + ); |
| 209 | +} |
| 210 | + |
| 211 | +export default function CompatibilityPage() { |
| 212 | + return ( |
| 213 | + <div className="space-y-12"> |
| 214 | + <div className="space-y-4"> |
| 215 | + <Link |
| 216 | + href="/docs" |
| 217 | + className="inline-flex items-center gap-1.5 text-sm text-neutral-500 hover:text-neutral-900 dark:hover:text-white transition-colors" |
| 218 | + > |
| 219 | + <ArrowLeft className="h-3.5 w-3.5" aria-hidden /> |
| 220 | + Back to docs |
| 221 | + </Link> |
| 222 | + <h1 className="text-4xl font-bold tracking-tight text-neutral-900 dark:text-white sm:text-5xl"> |
| 223 | + Compatibility |
| 224 | + </h1> |
| 225 | + <p className="max-w-2xl text-lg leading-relaxed text-neutral-600 dark:text-neutral-400"> |
| 226 | + What we verify on every push, versus what we treat as best-effort. |
| 227 | + If your stack lands in a row marked <strong>Manual step</strong>, the docs note tells you exactly what to do. |
| 228 | + </p> |
| 229 | + </div> |
| 230 | + |
| 231 | + <section className="rounded-xl border border-neutral-200 bg-neutral-50 p-5 text-sm leading-relaxed text-neutral-700 dark:border-neutral-800 dark:bg-neutral-900/40 dark:text-neutral-300"> |
| 232 | + <p className="mb-3 font-semibold text-neutral-900 dark:text-white">Legend</p> |
| 233 | + <ul className="space-y-2"> |
| 234 | + <li className="flex flex-wrap items-center gap-2"> |
| 235 | + <StatusBadge status="tested" /> |
| 236 | + <span>Exercised on every CI run (unit, E2E smoke, registry validation, or per-component build).</span> |
| 237 | + </li> |
| 238 | + <li className="flex flex-wrap items-center gap-2"> |
| 239 | + <StatusBadge status="best-effort" /> |
| 240 | + <span>We expect it to work because the code is framework-agnostic, but it isn't in our test matrix yet.</span> |
| 241 | + </li> |
| 242 | + <li className="flex flex-wrap items-center gap-2"> |
| 243 | + <StatusBadge status="manual" /> |
| 244 | + <span>Works, but requires a one-time setup step documented in the notes column.</span> |
| 245 | + </li> |
| 246 | + </ul> |
| 247 | + </section> |
| 248 | + |
| 249 | + <Matrix title="Runtime" rows={runtime} /> |
| 250 | + <Matrix title="React stack" rows={stack} /> |
| 251 | + <Matrix title="Frameworks" rows={frameworks} /> |
| 252 | + <Matrix title="CLI flows" rows={cliFlows} /> |
| 253 | + |
| 254 | + <section className="space-y-3 rounded-xl border border-amber-300/60 bg-amber-50 p-5 text-sm leading-relaxed text-amber-800 dark:border-amber-500/20 dark:bg-amber-500/5 dark:text-amber-200"> |
| 255 | + <p className="font-semibold">Tailwind v4 — what to do today</p> |
| 256 | + <p> |
| 257 | + A v4 project's tokens live in CSS (<code className="font-mono text-xs">@theme</code> in your global stylesheet), |
| 258 | + not in a JS <code className="font-mono text-xs">tailwind.config</code>. When a UniqueUI component declares |
| 259 | + custom keyframes (e.g. <code className="font-mono text-xs">moving-border</code>, <code className="font-mono text-xs">aurora-background</code>), |
| 260 | + the CLI's automatic merge won't find a config object to write into, so you need to copy the |
| 261 | + <code className="font-mono text-xs"> theme.extend.keyframes</code> and |
| 262 | + <code className="font-mono text-xs"> theme.extend.animation</code> entries from the component's registry JSON |
| 263 | + (<code className="font-mono text-xs">https://uniqueui-platform.vercel.app/registry/<slug>.json</code>) into your |
| 264 | + <code className="font-mono text-xs"> @theme</code> block. A first-class v4 emission path is on the roadmap. |
| 265 | + </p> |
| 266 | + </section> |
| 267 | + |
| 268 | + <section className="space-y-2 text-sm text-neutral-500 dark:text-neutral-400"> |
| 269 | + <p> |
| 270 | + Found a gap? Open an issue at{" "} |
| 271 | + <a |
| 272 | + className="text-purple-600 underline underline-offset-2 dark:text-purple-400" |
| 273 | + href="https://github.com/pras75299/uniqueui/issues/new" |
| 274 | + > |
| 275 | + github.com/pras75299/uniqueui/issues |
| 276 | + </a>{" "} |
| 277 | + with your stack versions and the error you hit. |
| 278 | + </p> |
| 279 | + </section> |
| 280 | + </div> |
| 281 | + ); |
| 282 | +} |
0 commit comments