Font Editor
ridiculous/font-editorFreeComponent
Ridiculously typed CSS font-shorthand editor with a compile-time strict-order parse — an order-free style/variant/weight/stretch prefix, a mandatory <font-size>, optional / <line-height>, and a mandatory <font-family> list, or a system-font keyword. Ships the cssFont helper, parseFont/formatFont, and a live text preview rendered with the built font.
Install it
npx shadcn@latest add http://ridiculous.turtlesocks.dev/r/font-editor.jsonSource
1"use client"23import type { ReactNode } from "react"4import { useEffect, useId, useRef, useState } from "react"5import { Button } from "@/components/ui/button"6import { Input } from "@/components/ui/input"7import {8 Popover,9 PopoverContent,10 PopoverTrigger,11} from "@/components/ui/popover"12import { cn } from "@/lib/utils"13import {14 ABSOLUTE_SIZES,15 defaultParts,16 FONT_STRETCHES,17 FONT_STYLES,18 FONT_VARIANTS,19 FONT_WEIGHT_KEYWORDS,20 formatFont,21 GENERIC_FAMILIES,22 parseFont,23 SYSTEM_FONTS,24 WEB_SAFE_FAMILIES,25} from "./font-editor.helpers"26import type { FontParts, FontString } from "./font-editor.types"2728const SIZE_UNITS = ["px", "rem", "em", "%", "vw", "vh", "pt"] as const2930const SAMPLE_TEXT_DEFAULT = "The quick brown fox jumps over the lazy dog"3132// Monotonic source of stable per-row ids for the family list (see FamilyEditor).33let nextFamilyId = 03435// ---------------------------------------------------------------------------36// Shared props37// ---------------------------------------------------------------------------3839export interface FontEditorPanelProps {40 value: FontString | (string & {})41 onChange: (value: FontString) => void42 className?: string43 "aria-label"?: string44}4546export interface FontEditorProps extends FontEditorPanelProps {}4748// ---------------------------------------------------------------------------49// shorthand-parts helpers (UI-local)50// ---------------------------------------------------------------------------5152interface Shorthand {53 kind: "shorthand"54 style?: string55 variant?: string56 weight?: string57 stretch?: string58 size: string59 lineHeight?: string60 family: string[]61}6263function asShorthand(parts: FontParts): Shorthand {64 if (parts.kind === "shorthand") return parts65 // Converting from a system keyword → seed a shorthand. `defaultParts()` is66 // typed as the wider `FontParts`, so narrow on the discriminant instead of67 // casting; the fallback keeps a real shorthand without an unchecked `as`.68 const seed = defaultParts()69 if (seed.kind === "shorthand") return seed70 return { kind: "shorthand", size: "16px", family: ["sans-serif"] }71}7273// A number for the dual control: mandatory mantissa (≥1 digit, so a lone `-`74// or a bare unit never matches) with an optional exponent, then an optional75// unit. Empty / sign-only / unit-only inputs fall through to the opaque path.76const SIZE_NUM_UNIT_RE = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)([a-z%]*)$/i7778// … truncated
What it pulls in
Other registry items
Files it writes
More from ridiculous
All 22 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| All ridiculous componentsall | ridiculous | Components | Free | no deps | |
| Anchor Position Editoranchor-position-editor | ridiculous | Components | Free | no deps · 9 files | |
| Background Editorbackground-editor | ridiculous | Components | Free | no deps · 9 files | |
| Box Shadow Editorbox-shadow-editor | ridiculous | Components | Free | no deps · 7 files | |
| Calc Editorcalc-editor | ridiculous | Components | Free | no deps · 4 files | |
| Clip Path Editorclip-path-editor | ridiculous | Components | Free | no deps · 20 files | |
| Color Functioncolor-function | ridiculous | Components | Free | no deps · 10 files | |
| Color Pickercolor-picker | ridiculous | Components | Free | no deps · 11 files |
