{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "route-line", "title": "Route Line", "description": "A switchback trail that runs, turns back through a half-circle, runs again and ends in a marker. Drawn furniture for hero space.", "dependencies": [ "class-variance-authority" ], "registryDependencies": [ "http://localhost:5173/r/liquid-theme.json" ], "files": [ { "path": "src/components/ui/route-line.tsx", "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * The switchback trail that wanders across the reference hero: a line that\n * runs, turns back on itself through a half-circle, runs again, and ends in a\n * marker.\n *\n * It carries no data. It is a piece of drawn furniture whose job is to give a\n * large empty region something to do — the graphic equivalent of a hairline\n * rule, but one that survives being the only thing on that part of the page.\n * Reach for it in hero space; never put it in a tile.\n *\n * The turns are true semicircles, so the radius is fixed by the row spacing\n * rather than chosen. That is what keeps a switchback reading as one\n * continuous stroke instead of a stack of separate lines with elbows.\n */\nconst svg = cva(\"overflow-visible\", {\n variants: {\n tone: {\n chalk: \"text-white\",\n ink: \"text-ink dark:text-white\",\n current: \"text-current\",\n volt: \"text-volt\",\n flare: \"text-flare\",\n },\n },\n defaultVariants: { tone: \"chalk\" },\n})\n\n/** Fractions of the full width each run covers, cycled. Uneven on purpose. */\nconst RUNS = [1, 0.72, 0.93, 0.55, 0.84]\n\nfunction switchback(\n width: number,\n height: number,\n rows: number,\n inset: number,\n): { d: string; start: [number, number]; end: [number, number] } {\n const n = Math.max(2, rows)\n const gap = (height - inset * 2) / (n - 1)\n const r = gap / 2\n // Every turn eats one radius of horizontal room, so the runs have to stop\n // short of the box or the arcs would push outside it.\n const left = inset\n const right = width - inset - r\n\n let x = left\n let y = inset\n const start: [number, number] = [x, y]\n let d = `M ${x.toFixed(2)} ${y.toFixed(2)}`\n\n for (let i = 0; i < n; i++) {\n const goingRight = i % 2 === 0\n const extent = RUNS[i % RUNS.length]\n const span = (right - left) * extent\n const target = goingRight ? left + span : right - span\n\n d += ` L ${target.toFixed(2)} ${y.toFixed(2)}`\n x = target\n\n if (i < n - 1) {\n // sweep 1 curls clockwise, which is the correct hand travelling right.\n d += ` A ${r.toFixed(2)} ${r.toFixed(2)} 0 0 ${goingRight ? 1 : 0} ${x.toFixed(2)} ${(y + gap).toFixed(2)}`\n y += gap\n }\n }\n\n return { d, start, end: [x, y] }\n}\n\nexport interface RouteLineProps\n extends Omit, \"width\" | \"height\">,\n VariantProps {\n width?: number\n height?: number\n /** How many horizontal runs before the line ends. */\n rows?: number\n thickness?: number\n /** A pennant at the head of the route. */\n flag?: boolean\n /** A filled dot at the tail. */\n terminal?: boolean\n /** Fades the stroke along its length, so the route arrives rather than sits. */\n fade?: boolean\n label?: string\n}\n\nexport function RouteLine({\n width = 240,\n height = 96,\n rows = 3,\n thickness = 2,\n flag = true,\n terminal = true,\n fade = false,\n tone,\n label,\n className,\n ...props\n}: RouteLineProps) {\n const id = React.useId()\n const inset = Math.max(6, thickness * 3)\n const { d, start, end } = switchback(width, height, rows, inset)\n\n return (\n \n {fade ? (\n \n \n \n \n \n \n ) : null}\n\n \n\n {flag ? (\n \n \n \n \n ) : null}\n\n {terminal ? (\n \n ) : null}\n \n )\n}\n", "type": "registry:ui" } ], "type": "registry:ui" }