refactor: replace Google Fonts with FontSource packages and migrate to cn utility

- Replace Google Fonts CDN with self-hosted FontSource packages
- Add @fontsource-variable/inter for variable Inter font
- Add @fontsource/ibm-plex-mono with weight 400 for monospace font
- Create src/lib/utils.ts with shadcn's cn utility function
- Install tailwind-merge for intelligent Tailwind class merging
- Replace all clsx usages with cn utility across components
  - LookupInput.tsx: 7 replacements
  - ErrorCard.tsx: 1 replacement
  - Property.tsx: 2 replacements
- Remove Google Fonts URL import from globals.css
This commit is contained in:
2025-10-22 02:22:39 -05:00
parent 771a27da29
commit 2c1f882cd9
8 changed files with 48 additions and 15 deletions

View File

@@ -15,6 +15,8 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@fontsource-variable/inter": "^5.2.8",
"@fontsource/ibm-plex-mono": "^5.2.7",
"@headlessui/react": "^2.2.9",
"@heroicons/react": "^2.0.16",
"@swc/helpers": "^0.5.11",
@@ -27,6 +29,7 @@
"react-hook-form": "^7.42.1",
"react-timeago": "^8.3.0",
"sass": "^1.57.1",
"tailwind-merge": "^3.3.1",
"true-myth": "^9.2.0",
"usehooks-ts": "^3.1.1",
"zod": "^4.1.12"

24
pnpm-lock.yaml generated
View File

@@ -8,6 +8,12 @@ importers:
.:
dependencies:
'@fontsource-variable/inter':
specifier: ^5.2.8
version: 5.2.8
'@fontsource/ibm-plex-mono':
specifier: ^5.2.7
version: 5.2.7
'@headlessui/react':
specifier: ^2.2.9
version: 2.2.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -44,6 +50,9 @@ importers:
sass:
specifier: ^1.57.1
version: 1.93.2
tailwind-merge:
specifier: ^3.3.1
version: 3.3.1
true-myth:
specifier: ^9.2.0
version: 9.2.0
@@ -357,6 +366,12 @@ packages:
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
'@fontsource-variable/inter@5.2.8':
resolution: {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==}
'@fontsource/ibm-plex-mono@5.2.7':
resolution: {integrity: sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==}
'@headlessui/react@2.2.9':
resolution: {integrity: sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==}
engines: {node: '>=10'}
@@ -2508,6 +2523,9 @@ packages:
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
engines: {node: '>=20'}
tailwind-merge@3.3.1:
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
tailwindcss@4.1.15:
resolution: {integrity: sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ==}
@@ -2912,6 +2930,10 @@ snapshots:
'@floating-ui/utils@0.2.10': {}
'@fontsource-variable/inter@5.2.8': {}
'@fontsource/ibm-plex-mono@5.2.7': {}
'@headlessui/react@2.2.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@floating-ui/react': 0.26.28(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -5139,6 +5161,8 @@ snapshots:
tagged-tag@1.0.0: {}
tailwind-merge@3.3.1: {}
tailwindcss@4.1.15: {}
tapable@2.3.0: {}

View File

@@ -1,6 +1,6 @@
import type { FunctionComponent, ReactNode } from "react";
import { XCircleIcon } from "@heroicons/react/20/solid";
import clsx from "clsx";
import { cn } from "@/lib/utils";
export type ErrorCardProps = {
title: ReactNode;
@@ -17,7 +17,7 @@ const ErrorCard: FunctionComponent<ErrorCardProps> = ({
}) => {
return (
<div
className={clsx(
className={cn(
className,
"rounded-md border border-red-700/30 bg-zinc-800 px-3 pb-1 pt-3"
)}

View File

@@ -1,6 +1,6 @@
import type { FunctionComponent, ReactNode } from "react";
import React from "react";
import clsx from "clsx";
import { cn } from "@/lib/utils";
type PropertyProps = {
title: string | ReactNode;
@@ -17,8 +17,8 @@ const Property: FunctionComponent<PropertyProps> = ({
}) => {
return (
<>
<dt className={clsx("font-medium", titleClass)}>{title}:</dt>
<dd className={clsx("mb-2 ml-6 mt-2", valueClass)}>{children}</dd>
<dt className={cn("font-medium", titleClass)}>{title}:</dt>
<dd className={cn("mb-2 ml-6 mt-2", valueClass)}>{children}</dd>
</>
);
};

View File

@@ -17,7 +17,7 @@ import {
ListboxOption,
Transition,
} from "@headlessui/react";
import clsx from "clsx";
import { cn } from "@/lib/utils";
import type { Maybe } from "true-myth";
import { placeholders } from "@/constants";
@@ -121,7 +121,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
<>
<button
type="submit"
className={clsx({
className={cn({
"absolute inset-y-0 left-0 flex items-center pl-3": true,
"pointer-events-none": isLoading,
})}
@@ -143,7 +143,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
const searchInput = (
<input
className={clsx(
className={cn(
"lg:py-4.5 block w-full rounded-l-md border border-transparent",
"bg-zinc-700 py-2 pl-10 pr-1.5 text-sm placeholder-zinc-400 placeholder:translate-y-2 focus:text-zinc-200",
" focus:outline-hidden sm:text-sm md:py-3 md:text-base lg:text-lg"
@@ -182,7 +182,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
>
<div className="relative">
<ListboxButton
className={clsx(
className={cn(
"relative h-full w-full cursor-default whitespace-nowrap rounded-r-lg bg-zinc-700 py-2 pl-1 pr-10 text-right",
"text-xs focus:outline-hidden focus-visible:border-indigo-500 sm:text-sm md:text-base lg:text-lg",
"focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 "
@@ -227,7 +227,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
leaveTo="opacity-0"
>
<ListboxOptions
className={clsx(
className={cn(
"scrollbar-thin absolute right-0 mt-1 max-h-60 min-w-full overflow-auto rounded-md bg-zinc-700 py-1",
"text-zinc-200 shadow-lg ring-1 ring-black/5 focus:outline-hidden sm:text-sm"
)}
@@ -236,7 +236,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
<ListboxOption
key={key}
className={({ focus }) =>
clsx(
cn(
"relative cursor-default select-none py-2 pl-10 pr-4",
focus ? "bg-zinc-800 text-zinc-300" : null
)
@@ -246,7 +246,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
{({ selected }) => (
<>
<span
className={clsx(
className={cn(
"block whitespace-nowrap text-right text-xs md:text-sm lg:text-base",
selected ? "font-medium" : null
)}

6
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -1,5 +1,8 @@
import { type AppType } from "next/dist/shared/lib/utils";
import "@fontsource-variable/inter";
import "@fontsource/ibm-plex-mono/400.css";
import "../styles/globals.css";
const MyApp: AppType = ({ Component, pageProps }) => {

View File

@@ -1,5 +1,4 @@
@import "tailwindcss";
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=Inter:wght@100..900&display=swap");
@theme {
--font-sans: "Inter var", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
@@ -7,8 +6,6 @@
--color-zinc-850: #1D1D20;
}
@source "../**/*.{js,ts,jsx,tsx}";
dd {
margin: 0.5em 0 1em 2em;
}