From 2bf55c91338525f886f7c4a7b1fe820155d6bace Mon Sep 17 00:00:00 2001 From: shadcn Date: Sat, 31 Jan 2026 14:52:43 +0400 Subject: [PATCH] feat: add geist fonts (#9502) --- .changeset/tired-ways-enjoy.md | 5 ++ .../app/(create)/components/font-picker.tsx | 2 +- .../app/(create)/components/theme-picker.tsx | 2 +- apps/v4/app/(create)/lib/fonts.ts | 40 ++++++------ apps/v4/public/r/config.json | 8 +-- .../r/styles/base-lyra/font-geist-mono.json | 15 +++++ .../public/r/styles/base-lyra/font-geist.json | 15 +++++ .../public/r/styles/base-lyra/registry.json | 18 +++++- .../r/styles/base-maia/font-geist-mono.json | 15 +++++ .../public/r/styles/base-maia/font-geist.json | 15 +++++ .../public/r/styles/base-maia/registry.json | 18 +++++- .../r/styles/base-mira/font-geist-mono.json | 15 +++++ .../public/r/styles/base-mira/font-geist.json | 15 +++++ .../public/r/styles/base-mira/registry.json | 18 +++++- .../r/styles/base-nova/font-geist-mono.json | 15 +++++ .../public/r/styles/base-nova/font-geist.json | 15 +++++ .../public/r/styles/base-nova/registry.json | 18 +++++- .../r/styles/base-vega/font-geist-mono.json | 15 +++++ .../public/r/styles/base-vega/font-geist.json | 15 +++++ .../public/r/styles/base-vega/registry.json | 18 +++++- .../r/styles/radix-lyra/font-geist-mono.json | 15 +++++ .../r/styles/radix-lyra/font-geist.json | 15 +++++ .../public/r/styles/radix-lyra/registry.json | 18 +++++- .../r/styles/radix-maia/font-geist-mono.json | 15 +++++ .../r/styles/radix-maia/font-geist.json | 15 +++++ .../public/r/styles/radix-maia/registry.json | 18 +++++- .../r/styles/radix-mira/font-geist-mono.json | 15 +++++ .../r/styles/radix-mira/font-geist.json | 15 +++++ .../public/r/styles/radix-mira/registry.json | 18 +++++- .../r/styles/radix-nova/font-geist-mono.json | 15 +++++ .../r/styles/radix-nova/font-geist.json | 15 +++++ .../public/r/styles/radix-nova/registry.json | 18 +++++- .../r/styles/radix-vega/font-geist-mono.json | 15 +++++ .../r/styles/radix-vega/font-geist.json | 15 +++++ .../public/r/styles/radix-vega/registry.json | 18 +++++- apps/v4/registry/config.ts | 8 +-- apps/v4/registry/fonts.ts | 28 ++++----- packages/shadcn/src/commands/create.ts | 3 +- .../src/utils/updaters/update-fonts.test.ts | 63 ++++++++++++++++++- .../shadcn/src/utils/updaters/update-fonts.ts | 20 +++--- 40 files changed, 584 insertions(+), 75 deletions(-) create mode 100644 .changeset/tired-ways-enjoy.md create mode 100644 apps/v4/public/r/styles/base-lyra/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/base-lyra/font-geist.json create mode 100644 apps/v4/public/r/styles/base-maia/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/base-maia/font-geist.json create mode 100644 apps/v4/public/r/styles/base-mira/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/base-mira/font-geist.json create mode 100644 apps/v4/public/r/styles/base-nova/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/base-nova/font-geist.json create mode 100644 apps/v4/public/r/styles/base-vega/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/base-vega/font-geist.json create mode 100644 apps/v4/public/r/styles/radix-lyra/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/radix-lyra/font-geist.json create mode 100644 apps/v4/public/r/styles/radix-maia/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/radix-maia/font-geist.json create mode 100644 apps/v4/public/r/styles/radix-mira/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/radix-mira/font-geist.json create mode 100644 apps/v4/public/r/styles/radix-nova/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/radix-nova/font-geist.json create mode 100644 apps/v4/public/r/styles/radix-vega/font-geist-mono.json create mode 100644 apps/v4/public/r/styles/radix-vega/font-geist.json diff --git a/.changeset/tired-ways-enjoy.md b/.changeset/tired-ways-enjoy.md new file mode 100644 index 0000000000..788b2e05c4 --- /dev/null +++ b/.changeset/tired-ways-enjoy.md @@ -0,0 +1,5 @@ +--- +"shadcn": patch +--- + +properly detect already installed fonts diff --git a/apps/v4/app/(create)/components/font-picker.tsx b/apps/v4/app/(create)/components/font-picker.tsx index 6b6ec6aeb5..5276e8a61a 100644 --- a/apps/v4/app/(create)/components/font-picker.tsx +++ b/apps/v4/app/(create)/components/font-picker.tsx @@ -59,7 +59,7 @@ export function FontPicker({ anchor={isMobile ? anchorRef : undefined} side={isMobile ? "top" : "right"} align={isMobile ? "center" : "start"} - className="max-h-80 md:w-72" + className="max-h-96 md:w-72" > { + it("should skip font entirely if already imported", async () => { const input = ` import { Inter } from "next/font/google" @@ -420,6 +420,7 @@ export default function RootLayout({ const result = await transformLayoutFonts(input, fonts, mockConfig) + // Font is already imported, so the layout should remain unchanged. expect(result).toMatchInlineSnapshot(` " import { Inter } from "next/font/google" @@ -432,7 +433,7 @@ export default function RootLayout({ children: React.ReactNode }) { return ( - + {children} ) @@ -441,6 +442,64 @@ export default function RootLayout({ `) }) + it("should skip Geist font if already imported (create-next-app scenario)", async () => { + // This simulates a fresh create-next-app project with Geist already set up. + const input = ` +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} +` + const fonts = [ + { + name: "font-geist", + type: "registry:font" as const, + font: { + family: "'Geist Variable', sans-serif", + provider: "google" as const, + import: "Geist", + variable: "--font-sans", + subsets: ["latin"], + }, + }, + ] + + const result = await transformLayoutFonts(input, fonts, mockConfig) + + // Geist is already imported, so the layout should remain unchanged. + expect(result).toBe(input) + }) + it("should add to existing next/font/google import", async () => { const input = ` import { Roboto } from "next/font/google" diff --git a/packages/shadcn/src/utils/updaters/update-fonts.ts b/packages/shadcn/src/utils/updaters/update-fonts.ts index 0f5439214c..5634d00103 100644 --- a/packages/shadcn/src/utils/updaters/update-fonts.ts +++ b/packages/shadcn/src/utils/updaters/update-fonts.ts @@ -185,26 +185,24 @@ export async function transformLayoutFonts( continue } - // Generate a variable name from the import (e.g., "Inter" -> "inter", "Geist_Mono" -> "geistMono"). - const varName = toCamelCase(importName) - // Check if import already exists. const existingImport = sourceFile.getImportDeclaration((decl) => { const moduleSpecifier = decl.getModuleSpecifierValue() return moduleSpecifier === "next/font/google" }) - // Build font options. - const fontOptions = buildFontOptions(font) - if (existingImport) { // Check if this specific font is already imported. const namedImports = existingImport.getNamedImports() const hasImport = namedImports.some((imp) => imp.getName() === importName) - if (!hasImport) { - existingImport.addNamedImport(importName) + if (hasImport) { + // Font is already imported - skip this font entirely. + // Assume the user already has it set up correctly. + continue } + + existingImport.addNamedImport(importName) } else { // Add new import. sourceFile.addImportDeclaration({ @@ -213,6 +211,12 @@ export async function transformLayoutFonts( }) } + // Generate a variable name from the import (e.g., "Inter" -> "inter", "Geist_Mono" -> "geistMono"). + const varName = toCamelCase(importName) + + // Build font options. + const fontOptions = buildFontOptions(font) + // Check if variable declaration already exists with same variable CSS property. const existingVarDecl = findFontVariableDeclaration( sourceFile,