style: format all files with prettier

This commit is contained in:
Seth Hobson
2026-01-19 17:07:03 -05:00
parent 8d37048deb
commit 56848874a2
355 changed files with 15215 additions and 10241 deletions

View File

@@ -15,7 +15,7 @@ Using OKLCH for perceptually uniform color scales:
--blue-200: oklch(86% 0.08 250);
--blue-300: oklch(75% 0.12 250);
--blue-400: oklch(65% 0.16 250);
--blue-500: oklch(55% 0.20 250); /* Primary */
--blue-500: oklch(55% 0.2 250); /* Primary */
--blue-600: oklch(48% 0.18 250);
--blue-700: oklch(40% 0.16 250);
--blue-800: oklch(32% 0.12 250);
@@ -27,26 +27,29 @@ Using OKLCH for perceptually uniform color scales:
### Programmatic Scale Generation
```tsx
function generateColorScale(hue: number, saturation: number = 100): Record<string, string> {
function generateColorScale(
hue: number,
saturation: number = 100,
): Record<string, string> {
const lightnessStops = [
{ name: '50', l: 97 },
{ name: '100', l: 93 },
{ name: '200', l: 85 },
{ name: '300', l: 75 },
{ name: '400', l: 65 },
{ name: '500', l: 55 },
{ name: '600', l: 45 },
{ name: '700', l: 35 },
{ name: '800', l: 25 },
{ name: '900', l: 18 },
{ name: '950', l: 12 },
{ name: "50", l: 97 },
{ name: "100", l: 93 },
{ name: "200", l: 85 },
{ name: "300", l: 75 },
{ name: "400", l: 65 },
{ name: "500", l: 55 },
{ name: "600", l: 45 },
{ name: "700", l: 35 },
{ name: "800", l: 25 },
{ name: "900", l: 18 },
{ name: "950", l: 12 },
];
return Object.fromEntries(
lightnessStops.map(({ name, l }) => [
name,
`hsl(${hue}, ${saturation}%, ${l}%)`,
])
]),
);
}
@@ -167,49 +170,50 @@ const error = generateColorScale(0); // Red
### React Theme Context
```tsx
import { createContext, useContext, useEffect, useState } from 'react';
import { createContext, useContext, useEffect, useState } from "react";
type Theme = 'light' | 'dark' | 'system';
type Theme = "light" | "dark" | "system";
interface ThemeContextValue {
theme: Theme;
setTheme: (theme: Theme) => void;
resolvedTheme: 'light' | 'dark';
resolvedTheme: "light" | "dark";
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>('system');
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
const [theme, setTheme] = useState<Theme>("system");
const [resolvedTheme, setResolvedTheme] = useState<"light" | "dark">("light");
useEffect(() => {
const root = document.documentElement;
if (theme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
setResolvedTheme(systemTheme);
root.setAttribute('data-theme', systemTheme);
root.setAttribute("data-theme", systemTheme);
} else {
setResolvedTheme(theme);
root.setAttribute('data-theme', theme);
root.setAttribute("data-theme", theme);
}
}, [theme]);
useEffect(() => {
if (theme !== 'system') return;
if (theme !== "system") return;
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handler = (e: MediaQueryListEvent) => {
const newTheme = e.matches ? 'dark' : 'light';
const newTheme = e.matches ? "dark" : "light";
setResolvedTheme(newTheme);
document.documentElement.setAttribute('data-theme', newTheme);
document.documentElement.setAttribute("data-theme", newTheme);
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}, [theme]);
return (
@@ -221,7 +225,7 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be within ThemeProvider');
if (!context) throw new Error("useTheme must be within ThemeProvider");
return context;
}
```
@@ -233,7 +237,7 @@ export function useTheme() {
```tsx
function hexToRgb(hex: string): [number, number, number] {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!result) throw new Error('Invalid hex color');
if (!result) throw new Error("Invalid hex color");
return [
parseInt(result[1], 16),
parseInt(result[2], 16),
@@ -265,8 +269,8 @@ function getContrastRatio(hex1: string, hex2: string): number {
function meetsWCAG(
foreground: string,
background: string,
size: 'normal' | 'large' = 'normal',
level: 'AA' | 'AAA' = 'AA'
size: "normal" | "large" = "normal",
level: "AA" | "AAA" = "AA",
): boolean {
const ratio = getContrastRatio(foreground, background);
@@ -279,8 +283,8 @@ function meetsWCAG(
}
// Usage
meetsWCAG('#ffffff', '#3b82f6'); // true (4.5:1 for AA normal)
meetsWCAG('#ffffff', '#60a5fa'); // false (below 4.5:1)
meetsWCAG("#ffffff", "#3b82f6"); // true (4.5:1 for AA normal)
meetsWCAG("#ffffff", "#60a5fa"); // false (below 4.5:1)
```
### Accessible Color Pairs
@@ -292,14 +296,14 @@ function getAccessibleTextColor(backgroundColor: string): string {
const luminance = getLuminance(r, g, b);
// Use white text on dark backgrounds, black on light
return luminance > 0.179 ? '#111827' : '#ffffff';
return luminance > 0.179 ? "#111827" : "#ffffff";
}
// Find the nearest accessible shade
function findAccessibleShade(
textColor: string,
backgroundScale: string[],
minContrast: number = 4.5
minContrast: number = 4.5,
): string | null {
for (const shade of backgroundScale) {
if (getContrastRatio(textColor, shade) >= minContrast) {
@@ -315,26 +319,22 @@ function findAccessibleShade(
### Harmony Functions
```tsx
type HarmonyType = 'complementary' | 'triadic' | 'analogous' | 'split-complementary';
type HarmonyType =
| "complementary"
| "triadic"
| "analogous"
| "split-complementary";
function generateHarmony(baseHue: number, type: HarmonyType): number[] {
switch (type) {
case 'complementary':
case "complementary":
return [baseHue, (baseHue + 180) % 360];
case 'triadic':
case "triadic":
return [baseHue, (baseHue + 120) % 360, (baseHue + 240) % 360];
case 'analogous':
return [
(baseHue - 30 + 360) % 360,
baseHue,
(baseHue + 30) % 360,
];
case 'split-complementary':
return [
baseHue,
(baseHue + 150) % 360,
(baseHue + 210) % 360,
];
case "analogous":
return [(baseHue - 30 + 360) % 360, baseHue, (baseHue + 30) % 360];
case "split-complementary":
return [baseHue, (baseHue + 150) % 360, (baseHue + 210) % 360];
default:
return [baseHue];
}
@@ -343,13 +343,13 @@ function generateHarmony(baseHue: number, type: HarmonyType): number[] {
// Generate palette from harmony
function generateHarmoniousPalette(
baseHue: number,
type: HarmonyType
type: HarmonyType,
): Record<string, string> {
const hues = generateHarmony(baseHue, type);
const names = ['primary', 'secondary', 'tertiary'];
const names = ["primary", "secondary", "tertiary"];
return Object.fromEntries(
hues.map((hue, i) => [names[i] || `color-${i}`, `hsl(${hue}, 70%, 50%)`])
hues.map((hue, i) => [names[i] || `color-${i}`, `hsl(${hue}, 70%, 50%)`]),
);
}
```
@@ -358,7 +358,7 @@ function generateHarmoniousPalette(
```tsx
// Simulate color blindness
type ColorBlindnessType = 'protanopia' | 'deuteranopia' | 'tritanopia';
type ColorBlindnessType = "protanopia" | "deuteranopia" | "tritanopia";
// Matrix transforms for common types
const colorBlindnessMatrices: Record<ColorBlindnessType, number[][]> = {

View File

@@ -14,28 +14,28 @@ The 8-point grid is the industry standard for consistent spacing.
/* Spacing scale */
--space-0: 0;
--space-px: 1px;
--space-0-5: calc(var(--space-unit) * 0.5); /* 2px */
--space-1: var(--space-unit); /* 4px */
--space-1-5: calc(var(--space-unit) * 1.5); /* 6px */
--space-2: calc(var(--space-unit) * 2); /* 8px */
--space-2-5: calc(var(--space-unit) * 2.5); /* 10px */
--space-3: calc(var(--space-unit) * 3); /* 12px */
--space-3-5: calc(var(--space-unit) * 3.5); /* 14px */
--space-4: calc(var(--space-unit) * 4); /* 16px */
--space-5: calc(var(--space-unit) * 5); /* 20px */
--space-6: calc(var(--space-unit) * 6); /* 24px */
--space-7: calc(var(--space-unit) * 7); /* 28px */
--space-8: calc(var(--space-unit) * 8); /* 32px */
--space-9: calc(var(--space-unit) * 9); /* 36px */
--space-10: calc(var(--space-unit) * 10); /* 40px */
--space-11: calc(var(--space-unit) * 11); /* 44px */
--space-12: calc(var(--space-unit) * 12); /* 48px */
--space-14: calc(var(--space-unit) * 14); /* 56px */
--space-16: calc(var(--space-unit) * 16); /* 64px */
--space-20: calc(var(--space-unit) * 20); /* 80px */
--space-24: calc(var(--space-unit) * 24); /* 96px */
--space-28: calc(var(--space-unit) * 28); /* 112px */
--space-32: calc(var(--space-unit) * 32); /* 128px */
--space-0-5: calc(var(--space-unit) * 0.5); /* 2px */
--space-1: var(--space-unit); /* 4px */
--space-1-5: calc(var(--space-unit) * 1.5); /* 6px */
--space-2: calc(var(--space-unit) * 2); /* 8px */
--space-2-5: calc(var(--space-unit) * 2.5); /* 10px */
--space-3: calc(var(--space-unit) * 3); /* 12px */
--space-3-5: calc(var(--space-unit) * 3.5); /* 14px */
--space-4: calc(var(--space-unit) * 4); /* 16px */
--space-5: calc(var(--space-unit) * 5); /* 20px */
--space-6: calc(var(--space-unit) * 6); /* 24px */
--space-7: calc(var(--space-unit) * 7); /* 28px */
--space-8: calc(var(--space-unit) * 8); /* 32px */
--space-9: calc(var(--space-unit) * 9); /* 36px */
--space-10: calc(var(--space-unit) * 10); /* 40px */
--space-11: calc(var(--space-unit) * 11); /* 44px */
--space-12: calc(var(--space-unit) * 12); /* 48px */
--space-14: calc(var(--space-unit) * 14); /* 56px */
--space-16: calc(var(--space-unit) * 16); /* 64px */
--space-20: calc(var(--space-unit) * 20); /* 80px */
--space-24: calc(var(--space-unit) * 24); /* 96px */
--space-28: calc(var(--space-unit) * 28); /* 112px */
--space-32: calc(var(--space-unit) * 32); /* 128px */
}
```
@@ -44,20 +44,20 @@ The 8-point grid is the industry standard for consistent spacing.
```css
:root {
/* Component-level spacing */
--spacing-xs: var(--space-1); /* 4px - tight spacing */
--spacing-sm: var(--space-2); /* 8px - compact spacing */
--spacing-md: var(--space-4); /* 16px - default spacing */
--spacing-lg: var(--space-6); /* 24px - comfortable spacing */
--spacing-xl: var(--space-8); /* 32px - loose spacing */
--spacing-2xl: var(--space-12); /* 48px - generous spacing */
--spacing-3xl: var(--space-16); /* 64px - section spacing */
--spacing-xs: var(--space-1); /* 4px - tight spacing */
--spacing-sm: var(--space-2); /* 8px - compact spacing */
--spacing-md: var(--space-4); /* 16px - default spacing */
--spacing-lg: var(--space-6); /* 24px - comfortable spacing */
--spacing-xl: var(--space-8); /* 32px - loose spacing */
--spacing-2xl: var(--space-12); /* 48px - generous spacing */
--spacing-3xl: var(--space-16); /* 64px - section spacing */
/* Specific use cases */
--spacing-inline: var(--space-2); /* Between inline elements */
--spacing-stack: var(--space-4); /* Between stacked elements */
--spacing-inset: var(--space-4); /* Padding inside containers */
--spacing-section: var(--space-16); /* Between major sections */
--spacing-page: var(--space-24); /* Page margins */
--spacing-inline: var(--space-2); /* Between inline elements */
--spacing-stack: var(--space-4); /* Between stacked elements */
--spacing-inset: var(--space-4); /* Padding inside containers */
--spacing-section: var(--space-16); /* Between major sections */
--spacing-page: var(--space-24); /* Page margins */
}
```
@@ -67,18 +67,17 @@ The 8-point grid is the industry standard for consistent spacing.
// Tailwind-like spacing scale generator
function createSpacingScale(baseUnit: number = 4): Record<string, string> {
const scale: Record<string, string> = {
'0': '0',
'px': '1px',
"0": "0",
px: "1px",
};
const multipliers = [
0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10,
11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48,
52, 56, 60, 64, 72, 80, 96,
0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24,
28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 96,
];
for (const m of multipliers) {
const key = m % 1 === 0 ? String(m) : String(m).replace('.', '-');
const key = m % 1 === 0 ? String(m) : String(m).replace(".", "-");
scale[key] = `${baseUnit * m}px`;
}
@@ -140,15 +139,15 @@ function createSpacingScale(baseUnit: number = 4): Record<string, string> {
```css
:root {
/* Icon sizes aligned to spacing grid */
--icon-xs: 12px; /* Inline decorators */
--icon-sm: 16px; /* Small UI elements */
--icon-md: 20px; /* Default size */
--icon-lg: 24px; /* Emphasis */
--icon-xl: 32px; /* Large displays */
--icon-2xl: 48px; /* Hero icons */
--icon-xs: 12px; /* Inline decorators */
--icon-sm: 16px; /* Small UI elements */
--icon-md: 20px; /* Default size */
--icon-lg: 24px; /* Emphasis */
--icon-xl: 32px; /* Large displays */
--icon-2xl: 48px; /* Hero icons */
/* Touch target sizes */
--touch-target-min: 44px; /* WCAG minimum */
--touch-target-min: 44px; /* WCAG minimum */
--touch-target-comfortable: 48px;
}
```
@@ -156,11 +155,11 @@ function createSpacingScale(baseUnit: number = 4): Record<string, string> {
### SVG Icon Component
```tsx
import { forwardRef, type SVGProps } from 'react';
import { forwardRef, type SVGProps } from "react";
interface IconProps extends SVGProps<SVGSVGElement> {
name: string;
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
label?: string;
}
@@ -170,11 +169,11 @@ const sizeMap = {
md: 20,
lg: 24,
xl: 32,
'2xl': 48,
"2xl": 48,
};
export const Icon = forwardRef<SVGSVGElement, IconProps>(
({ name, size = 'md', label, className, ...props }, ref) => {
({ name, size = "md", label, className, ...props }, ref) => {
const pixelSize = sizeMap[size];
return (
@@ -185,16 +184,16 @@ export const Icon = forwardRef<SVGSVGElement, IconProps>(
className={`inline-block flex-shrink-0 ${className}`}
aria-hidden={!label}
aria-label={label}
role={label ? 'img' : undefined}
role={label ? "img" : undefined}
{...props}
>
<use href={`/icons.svg#${name}`} />
</svg>
);
}
},
);
Icon.displayName = 'Icon';
Icon.displayName = "Icon";
```
### Icon Button Patterns
@@ -203,27 +202,27 @@ Icon.displayName = 'Icon';
interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
icon: string;
label: string;
size?: 'sm' | 'md' | 'lg';
variant?: 'solid' | 'ghost' | 'outline';
size?: "sm" | "md" | "lg";
variant?: "solid" | "ghost" | "outline";
}
const sizeClasses = {
sm: 'p-1.5', /* 32px total with 16px icon */
md: 'p-2', /* 40px total with 20px icon */
lg: 'p-2.5', /* 48px total with 24px icon */
sm: "p-1.5" /* 32px total with 16px icon */,
md: "p-2" /* 40px total with 20px icon */,
lg: "p-2.5" /* 48px total with 24px icon */,
};
const iconSizes = {
sm: 'sm' as const,
md: 'md' as const,
lg: 'lg' as const,
sm: "sm" as const,
md: "md" as const,
lg: "lg" as const,
};
export function IconButton({
icon,
label,
size = 'md',
variant = 'ghost',
size = "md",
variant = "ghost",
className,
...props
}: IconButtonProps) {
@@ -233,9 +232,9 @@ export function IconButton({
inline-flex items-center justify-center rounded-lg
transition-colors focus-visible:outline-none focus-visible:ring-2
${sizeClasses[size]}
${variant === 'solid' && 'bg-blue-600 text-white hover:bg-blue-700'}
${variant === 'ghost' && 'hover:bg-gray-100'}
${variant === 'outline' && 'border border-gray-300 hover:bg-gray-50'}
${variant === "solid" && "bg-blue-600 text-white hover:bg-blue-700"}
${variant === "ghost" && "hover:bg-gray-100"}
${variant === "outline" && "border border-gray-300 hover:bg-gray-50"}
${className}
`}
aria-label={label}
@@ -251,62 +250,62 @@ export function IconButton({
```tsx
// Build script for SVG sprite
import { readdir, readFile, writeFile } from 'fs/promises';
import { optimize } from 'svgo';
import { readdir, readFile, writeFile } from "fs/promises";
import { optimize } from "svgo";
async function buildIconSprite(iconDir: string, outputPath: string) {
const files = await readdir(iconDir);
const svgFiles = files.filter((f) => f.endsWith('.svg'));
const svgFiles = files.filter((f) => f.endsWith(".svg"));
const symbols = await Promise.all(
svgFiles.map(async (file) => {
const content = await readFile(`${iconDir}/${file}`, 'utf-8');
const name = file.replace('.svg', '');
const content = await readFile(`${iconDir}/${file}`, "utf-8");
const name = file.replace(".svg", "");
// Optimize SVG
const result = optimize(content, {
plugins: [
'removeDoctype',
'removeXMLProcInst',
'removeComments',
'removeMetadata',
'removeTitle',
'removeDesc',
'removeUselessDefs',
'removeEditorsNSData',
'removeEmptyAttrs',
'removeHiddenElems',
'removeEmptyText',
'removeEmptyContainers',
'convertStyleToAttrs',
'convertColors',
'convertPathData',
'convertTransform',
'removeUnknownsAndDefaults',
'removeNonInheritableGroupAttrs',
'removeUselessStrokeAndFill',
'removeUnusedNS',
'cleanupNumericValues',
'cleanupListOfValues',
'moveElemsAttrsToGroup',
'moveGroupAttrsToElems',
'collapseGroups',
'mergePaths',
"removeDoctype",
"removeXMLProcInst",
"removeComments",
"removeMetadata",
"removeTitle",
"removeDesc",
"removeUselessDefs",
"removeEditorsNSData",
"removeEmptyAttrs",
"removeHiddenElems",
"removeEmptyText",
"removeEmptyContainers",
"convertStyleToAttrs",
"convertColors",
"convertPathData",
"convertTransform",
"removeUnknownsAndDefaults",
"removeNonInheritableGroupAttrs",
"removeUselessStrokeAndFill",
"removeUnusedNS",
"cleanupNumericValues",
"cleanupListOfValues",
"moveElemsAttrsToGroup",
"moveGroupAttrsToElems",
"collapseGroups",
"mergePaths",
],
});
// Extract viewBox and content
const viewBoxMatch = result.data.match(/viewBox="([^"]+)"/);
const viewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 24 24';
const viewBox = viewBoxMatch ? viewBoxMatch[1] : "0 0 24 24";
const innerContent = result.data
.replace(/<svg[^>]*>/, '')
.replace(/<\/svg>/, '');
.replace(/<svg[^>]*>/, "")
.replace(/<\/svg>/, "");
return `<symbol id="${name}" viewBox="${viewBox}">${innerContent}</symbol>`;
})
}),
);
const sprite = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${symbols.join('')}</svg>`;
const sprite = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${symbols.join("")}</svg>`;
await writeFile(outputPath, sprite);
console.log(`Generated sprite with ${symbols.length} icons`);
@@ -317,7 +316,7 @@ async function buildIconSprite(iconDir: string, outputPath: string) {
```tsx
// Lucide React
import { Home, Settings, User, Search } from 'lucide-react';
import { Home, Settings, User, Search } from "lucide-react";
function Navigation() {
return (
@@ -331,8 +330,8 @@ function Navigation() {
}
// Heroicons
import { HomeIcon, Cog6ToothIcon } from '@heroicons/react/24/outline';
import { HomeIcon as HomeIconSolid } from '@heroicons/react/24/solid';
import { HomeIcon, Cog6ToothIcon } from "@heroicons/react/24/outline";
import { HomeIcon as HomeIconSolid } from "@heroicons/react/24/solid";
function ToggleIcon({ active }: { active: boolean }) {
const Icon = active ? HomeIconSolid : HomeIcon;
@@ -340,7 +339,7 @@ function ToggleIcon({ active }: { active: boolean }) {
}
// Radix Icons
import { HomeIcon, GearIcon } from '@radix-ui/react-icons';
import { HomeIcon, GearIcon } from "@radix-ui/react-icons";
```
## Sizing Systems
@@ -350,29 +349,29 @@ import { HomeIcon, GearIcon } from '@radix-ui/react-icons';
```css
:root {
/* Fixed sizes */
--size-4: 1rem; /* 16px */
--size-5: 1.25rem; /* 20px */
--size-6: 1.5rem; /* 24px */
--size-8: 2rem; /* 32px */
--size-10: 2.5rem; /* 40px */
--size-12: 3rem; /* 48px */
--size-14: 3.5rem; /* 56px */
--size-16: 4rem; /* 64px */
--size-20: 5rem; /* 80px */
--size-24: 6rem; /* 96px */
--size-32: 8rem; /* 128px */
--size-4: 1rem; /* 16px */
--size-5: 1.25rem; /* 20px */
--size-6: 1.5rem; /* 24px */
--size-8: 2rem; /* 32px */
--size-10: 2.5rem; /* 40px */
--size-12: 3rem; /* 48px */
--size-14: 3.5rem; /* 56px */
--size-16: 4rem; /* 64px */
--size-20: 5rem; /* 80px */
--size-24: 6rem; /* 96px */
--size-32: 8rem; /* 128px */
/* Component heights */
--height-input-sm: var(--size-8); /* 32px */
--height-input-md: var(--size-10); /* 40px */
--height-input-lg: var(--size-12); /* 48px */
--height-input-sm: var(--size-8); /* 32px */
--height-input-md: var(--size-10); /* 40px */
--height-input-lg: var(--size-12); /* 48px */
/* Avatar sizes */
--avatar-xs: var(--size-6); /* 24px */
--avatar-sm: var(--size-8); /* 32px */
--avatar-md: var(--size-10); /* 40px */
--avatar-lg: var(--size-12); /* 48px */
--avatar-xl: var(--size-16); /* 64px */
--avatar-xs: var(--size-6); /* 24px */
--avatar-sm: var(--size-8); /* 32px */
--avatar-md: var(--size-10); /* 40px */
--avatar-lg: var(--size-12); /* 48px */
--avatar-xl: var(--size-16); /* 64px */
--avatar-2xl: var(--size-24); /* 96px */
}
```
@@ -407,13 +406,13 @@ import { HomeIcon, GearIcon } from '@radix-ui/react-icons';
```css
:root {
--radius-none: 0;
--radius-sm: 0.125rem; /* 2px */
--radius-sm: 0.125rem; /* 2px */
--radius-default: 0.25rem; /* 4px */
--radius-md: 0.375rem; /* 6px */
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
--radius-2xl: 1rem; /* 16px */
--radius-3xl: 1.5rem; /* 24px */
--radius-md: 0.375rem; /* 6px */
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
--radius-2xl: 1rem; /* 16px */
--radius-3xl: 1.5rem; /* 24px */
--radius-full: 9999px;
/* Component-specific */

View File

@@ -9,17 +9,21 @@ A modular scale creates harmonious relationships between font sizes using a math
```tsx
// Common ratios
const RATIOS = {
minorSecond: 1.067, // 16:15
majorSecond: 1.125, // 9:8
minorThird: 1.2, // 6:5
majorThird: 1.25, // 5:4
perfectFourth: 1.333, // 4:3
minorSecond: 1.067, // 16:15
majorSecond: 1.125, // 9:8
minorThird: 1.2, // 6:5
majorThird: 1.25, // 5:4
perfectFourth: 1.333, // 4:3
augmentedFourth: 1.414, // √2
perfectFifth: 1.5, // 3:2
goldenRatio: 1.618, // φ
perfectFifth: 1.5, // 3:2
goldenRatio: 1.618, // φ
};
function generateScale(baseSize: number, ratio: number, steps: number): number[] {
function generateScale(
baseSize: number,
ratio: number,
steps: number,
): number[] {
const scale: number[] = [];
for (let i = -2; i <= steps; i++) {
scale.push(Math.round(baseSize * Math.pow(ratio, i) * 100) / 100);
@@ -37,17 +41,17 @@ const typeScale = generateScale(16, RATIOS.perfectFourth, 6);
```css
:root {
/* Base scale using perfect fourth (1.333) */
--font-size-2xs: 0.563rem; /* ~9px */
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-md: 1.125rem; /* 18px */
--font-size-lg: 1.333rem; /* ~21px */
--font-size-xl: 1.5rem; /* 24px */
--font-size-2xl: 1.777rem; /* ~28px */
--font-size-3xl: 2.369rem; /* ~38px */
--font-size-4xl: 3.157rem; /* ~50px */
--font-size-5xl: 4.209rem; /* ~67px */
--font-size-2xs: 0.563rem; /* ~9px */
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-md: 1.125rem; /* 18px */
--font-size-lg: 1.333rem; /* ~21px */
--font-size-xl: 1.5rem; /* 24px */
--font-size-2xl: 1.777rem; /* ~28px */
--font-size-3xl: 2.369rem; /* ~38px */
--font-size-4xl: 3.157rem; /* ~50px */
--font-size-5xl: 4.209rem; /* ~67px */
/* Font weights */
--font-weight-normal: 400;
@@ -79,8 +83,8 @@ const typeScale = generateScale(16, RATIOS.perfectFourth, 6);
```css
/* Use font-display to control loading behavior */
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2-variations');
font-family: "Inter";
src: url("/fonts/Inter-Variable.woff2") format("woff2-variations");
font-weight: 100 900;
font-style: normal;
font-display: swap; /* Show fallback immediately, swap when loaded */
@@ -88,8 +92,8 @@ const typeScale = generateScale(16, RATIOS.perfectFourth, 6);
/* Optional: size-adjust for better fallback matching */
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%; /* Adjust to match Inter metrics */
ascent-override: 90%;
descent-override: 22%;
@@ -97,7 +101,7 @@ const typeScale = generateScale(16, RATIOS.perfectFourth, 6);
}
body {
font-family: 'Inter', 'Inter Fallback', system-ui, sans-serif;
font-family: "Inter", "Inter Fallback", system-ui, sans-serif;
}
```
@@ -121,15 +125,17 @@ body {
```css
/* Variable font with weight and width axes */
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2');
font-family: "Inter";
src: url("/fonts/Inter-Variable.woff2") format("woff2");
font-weight: 100 900;
font-stretch: 75% 125%;
}
/* Use font-variation-settings for fine control */
.custom-weight {
font-variation-settings: 'wght' 450, 'wdth' 95;
font-variation-settings:
"wght" 450,
"wdth" 95;
}
/* Or use standard properties */
@@ -169,9 +175,8 @@ p {
--max-vw: 1200;
line-height: calc(
var(--min-line-height) +
(var(--max-line-height) - var(--min-line-height)) *
((100vw - var(--min-vw) * 1px) / (var(--max-vw) - var(--min-vw)))
var(--min-line-height) + (var(--max-line-height) - var(--min-line-height)) *
((100vw - var(--min-vw) * 1px) / (var(--max-vw) - var(--min-vw)))
);
}
```
@@ -183,15 +188,15 @@ p {
module.exports = {
theme: {
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
'5xl': ['3rem', { lineHeight: '1' }],
xs: ["0.75rem", { lineHeight: "1rem" }],
sm: ["0.875rem", { lineHeight: "1.25rem" }],
base: ["1rem", { lineHeight: "1.5rem" }],
lg: ["1.125rem", { lineHeight: "1.75rem" }],
xl: ["1.25rem", { lineHeight: "1.75rem" }],
"2xl": ["1.5rem", { lineHeight: "2rem" }],
"3xl": ["1.875rem", { lineHeight: "2.25rem" }],
"4xl": ["2.25rem", { lineHeight: "2.5rem" }],
"5xl": ["3rem", { lineHeight: "1" }],
},
},
};
@@ -268,7 +273,9 @@ p {
}
/* Balance headings */
h1, h2, h3 {
h1,
h2,
h3 {
text-wrap: balance;
}
@@ -292,20 +299,20 @@ h1, h2, h3 {
```css
/* Serif heading + Sans body */
:root {
--font-heading: 'Playfair Display', Georgia, serif;
--font-body: 'Source Sans Pro', -apple-system, sans-serif;
--font-heading: "Playfair Display", Georgia, serif;
--font-body: "Source Sans Pro", -apple-system, sans-serif;
}
/* Geometric heading + Humanist body */
:root {
--font-heading: 'Space Grotesk', sans-serif;
--font-body: 'IBM Plex Sans', sans-serif;
--font-heading: "Space Grotesk", sans-serif;
--font-body: "IBM Plex Sans", sans-serif;
}
/* Modern sans heading + Classic serif body */
:root {
--font-heading: 'Inter', system-ui, sans-serif;
--font-body: 'Georgia', Times, serif;
--font-heading: "Inter", system-ui, sans-serif;
--font-body: "Georgia", Times, serif;
}
```
@@ -314,7 +321,7 @@ h1, h2, h3 {
```css
/* Single variable font family for all uses */
:root {
--font-family: 'Inter', system-ui, sans-serif;
--font-family: "Inter", system-ui, sans-serif;
}
h1 {
@@ -405,7 +412,7 @@ p {
font-variant-numeric: tabular-nums lining-nums;
/* Fractions */
font-feature-settings: 'frac' 1;
font-feature-settings: "frac" 1;
}
/* Tabular numbers for aligned columns */