Calc Editor
ridiculous/calc-editorFreeComponent
Ridiculously typed CSS math editor for calc/clamp/min/max with compile-time dimensional analysis (length±length ✓, length±angle ✗, ÷ by non-number ✗). Ships a full runtime parser/evaluator and a fluid-type clamp() playground.
Install it
npx shadcn@latest add http://ridiculous.turtlesocks.dev/r/calc-editor.jsonSource
1"use client"23import { useEffect, useId, useRef, useState } from "react"4import { Button } from "@/components/ui/button"5import { Input } from "@/components/ui/input"6import {7 Popover,8 PopoverContent,9 PopoverTrigger,10} from "@/components/ui/popover"11import { cn } from "@/lib/utils"12import { computeCalc, evaluateCalc, parseCalc } from "./calc-editor.helpers"13import type {14 CalcFn,15 CalcString,16 CalcStringMap,17 Dimension,18} from "./calc-editor.types"1920// ---------------------------------------------------------------------------21// Shared props22// ---------------------------------------------------------------------------2324export interface CalcEditorPanelProps<25 TFn extends CalcFn | undefined = undefined,26> {27 value: CalcString | (string & {})28 onChange: (29 value: TFn extends CalcFn ? CalcStringMap[TFn] : CalcString,30 ) => void31 /** Lock the outer function; narrows `onChange` to that flavor. */32 fn?: TFn33 /** Viewport width (px) used for the computed-value readout. Default 1280. */34 referenceViewport?: number35 className?: string36 "aria-label"?: string37}3839export interface CalcEditorProps<TFn extends CalcFn | undefined = undefined>40 extends CalcEditorPanelProps<TFn> {}4142const ALL_FNS: readonly CalcFn[] = ["calc", "clamp", "min", "max"] as const4344// Seed expression text when switching to / starting on a function.45function seedFor(fn: CalcFn, inner: string): string {46 const body = inner.trim() || "1rem"47 switch (fn) {48 case "calc":49 return `calc(${body})`50 case "clamp":51 return `clamp(1rem, ${body}, 3rem)`52 case "min":53 return `min(${body}, 2rem)`54 case "max":55 return `max(${body}, 2rem)`56 }57}5859// Pull the "inner" of a calc()/single-arg expression for re-wrapping.60function innerOf(expr: string): string {61 const node = parseCalc(expr)62 if (node && node.kind === "fn" && node.name === "calc") {63 // re-serialize the single calc arg roughly by stripping the wrapper64 const m = expr.trim().match(/^calc\((.*)\)$/s)65 if (m) return m[1].trim()66 }67 return expr.trim()68}6970// ---------------------------------------------------------------------------71// CalcEditor — popover-wrapped72// ---------------------------------------------------------------------------7374export function CalcEditor<TFn extends CalcFn | undefined = undefined>(75 props: CalcEditorProps<TFn>,76) {77 const {78 value,79 className,80 "aria-label": ariaLabel = "Edit a CSS math expression",81 } = props82// … 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 | |
| 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 | |
| Easing Pickereasing-picker | ridiculous | Components | Free | no deps · 17 files |
