Codync
무료 오픈소스 1:1 대안, Grok Bot과 Muse 대신
React용 SVG 게이지 컴포넌트, 호환형 아크·눈금·구역·바늘로 속도계·다이얼·미터·시계 제작
Composable SVG gauge primitives for React — build speedometers, dials, meters and clocks from arcs, ticks, zones and needles. Distributed as a shadcn registry, so the source lands in your project and
개발 도구TypeScript · JavaScriptGitHub @thordursk온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Composable SVG gauges for React, distributed as a shadcn registry.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Composable SVG gauge primitives for React, distributed as a shadcn registry. Design a gauge in the studio, copy the code, and own the source in your project. No runtime dependencies beyond React and the shadcn cn helper.
npx shadcn@latest add https://gauge-ui.dev/r/gauge.json
Or register the namespace once in components.json and install by name:
{
"registries": {
"@gauge-ui": "https://gauge-ui.dev/r/{name}.json"
}
}
npx shadcn@latest add @gauge-ui/gauge
The files land in components/gauge/.
import {
Gauge,
GaugeArc,
GaugeHub,
GaugeNeedle,
GaugeTickLabels,
GaugeTicks,
GaugeTrack,
GaugeValue,
GaugeZones,
zoneColor,
} from "@/components/gauge"
const zones = [
{ to: 60, color: "#22c55e" },
{ to: 80, color: "#f59e0b" },
{ to: 100, color: "#ef4444" },
]
export function Speed({ value }: { value: number }) {
return (
<Gauge value={value} min={0} max={100} startAngle={40} endAngle={320}>
<GaugeTrack width={14} />
<GaugeZones zones={zones} width={14} />
<GaugeArc width={14} color={zoneColor(zones, value, "currentColor")} />
<GaugeTicks count={10} />
<GaugeTickLabels count={5} />
<GaugeNeedle style="pointer" />
<GaugeHub />
<GaugeValue decimals={0} />
</Gauge>
)
}
Gauge owns the domain and geometry. Every child reads value, angles and radius from context and positions itself against the reference radius, so parts can be added, removed or reordered freely. Angles are measured clockwise from six o'clock.
| Primitive | Draws |
|---|---|
GaugeTrack | Background arc across the whole sweep |
GaugeArc | Filled arc anchored at from, growing either way to the current value or to; reverse grows it back from the end, wrap carries it past the seam of a closed ring |
GaugeStack | The value arc split into parts that sweep as one, by weight shares or fixed to cutoffs |
GaugeZones | Coloured bands between zone cutoffs |
GaugeTicks | Evenly spaced tick marks |
GaugeTickFill | Ticks as the fill: those between the ends of the value or range are lit, the rest dim, or with mode="nearest" only the tick at each end. Lit and dim colours can be functions of the value |
GaugeMarks | Marks at arbitrary values, or at the count equal cuts of the domain |
GaugeTickLabels | Numeric, compass or clock labels along the arc |
GaugeNeedle | Line, pointer, compass or arrow needle |
GaugeHub | Centre cap for the needle |
GaugeDot | A disc riding the arc at the current value, with an optional halo to stand it off a band |
GaugeTooltip | An HTML tooltip shown on hover, pinned to the value, the pointer or a fixed spot on the dial: the value with a label and unit by default, or any content you give it |
GaugeValue | Formatted current value |
GaugeText | Free text at any position, for units and titles |
GaugeInset | A whole gauge nested inside another, at its own scale |
GaugeControl | Wraps a gauge as an accessible slider: drag round the ring, turn the face with knob, or use the keyboard to set its value |
Gauge and GaugeInset also take wrap, which treats the domain as circular so a heading animates the shortest way round, and rotate, which turns the whole scale under a fixed pointer the way a compass card turns.
LinearGauge lays the scale out straight instead of round, as a horizontal or vertical bar. It publishes the same context as Gauge, so the same parts draw along it. The only exception is GaugeNeedle, which needs a centre to pivot on.
<LinearGauge value={temp} min={-20} max={40} orientation="vertical" padding={{ left: 80, right: 32, top: 24, bottom: 48 }} className="h-80">
<GaugeTrack width={22} />
<GaugeArc width={22} color={zoneColor(zones, temp, "currentColor")} />
<GaugeDot end="from" radius={28} />
<GaugeTicks count={12} offset={22} length={8} />
<GaugeTickLabels count={6} offset={34} anchor="end" format={(v) => `${v}°`} />
</LinearGauge>
length SVG units long (default 400) and centred on the origin. A horizontal bar runs left to right. A vertical bar runs bottom to top.offset moves a part outward, the same as on a dial: up from a horizontal bar, or left of a vertical one. Use a negative offset for labels below or to the right.padding takes one number, or { top, right, bottom, left } for a different amount on each side. The SVG gets a matching aspect-ratio, so a horizontal bar fills its width and a vertical bar fills its height.GaugeControl takes orientation (and length, if it isn't 400) to work as a slider along the bar. LinearGaugeInset puts a bar inside another gauge.wrap domain, a range that runs past the end of the bar continues from its start.Pass a [from, to] pair as value to show a span of the domain instead of a single reading: a forecast's low and high, a target band, a sleep window. Both ends animate with the gauge's transition.
<GaugeControl value={range} onChange={setRange} label="Level">
<Gauge value={range} transition>
<GaugeTrack />
<GaugeArc />
<GaugeDot end="from" />
<GaugeDot />
<GaugeValue />
<GaugeTooltip unit="%" />
</Gauge>
</GaugeControl>
GaugeArc and GaugeStack fill between the two ends rather than from min.GaugeDot and GaugeNeedle follow the far end. Use end="from" to put one on the start.GaugeValue and GaugeTooltip show both ends, joined by separator (default –), with format applied to each. The tooltip points at the middle of the range. GaugeValue takes end to show just one end.GaugeControl gets two thumbs. A press on the ring moves the nearer one, and each thumb can be tabbed to and moved with the keys on its own. Name them with thumbLabels, and pass one valueText per end. With knob, turning the face moves the whole range and keeps its width.GaugeTicks, GaugeMarks and GaugeTickLabels take a function as color, called with each mark's value, so marks inside a range can stand out: color={(v) => (v >= 50 && v <= 70 ? "green" : "gray")}.
GaugeTickFill builds a gauge from ticks alone. It ticks the whole sweep and lights the ticks the fill covers, following the value or range the way GaugeArc does:
<GaugeTickFill count={40} length={36} width={8} color="dodgerblue" />
<GaugeTickFill count={64} litLength={30} color={(v) => (v > 120 ? "red" : "white")} />
<GaugeTickFill count={72} mode="nearest" length={0} litLength={40} dimColor="transparent" />
On an open domain, the ends are sorted and the control's thumbs stop when they meet. On a wrap domain the order matters, because the range runs forward from from: [22, 6] on a 24-hour ring is the night and [6, 22] is the day. Pass wrap to GaugeControl too, so its thumbs can cross the seam.
Helpers zoneColor, zoneCutoffs, compassLabel, clockLabel, clockTime, durationLabel and fadeColor are exported from the same module and are what the studio's generated code uses.
GaugeTooltip is an HTML tooltip that shows while the pointer is over the gauge. By default it reads the value, with an optional label above it and unit after it, styled like the shadcn tooltip.
<GaugeTooltip label="Used" unit="GB" decimals={1} />
position decides what it is pinned to:
position | Pinned to |
|---|---|
value | The arc at the current value, or at at when given. The default |
pointer | The cursor, following it round the gauge |
center | The middle of the dial |
top, right, bottom, left | The middle of that edge of the box round the sweep |
top-left, top-right, bottom-left, bottom-right | That corner of the same box |
side sets which way the bubble stands off that point: top, right, bottom, left or center. The default, auto, points outward along the radius at the value, so the bubble swings round as the value moves. It sits above the pointer, centres on the centre, and points away from the dial at an edge or corner. offset moves the point in or out from the reference radius in SVG units, as it does for arcs and dots, and gap is the room between the point and the bubble in CSS pixels. It also stays up while a press that started on the gauge is held, wherever the pointer goes, so it follows a drag on a GaugeControl and shows on touch screens, and while the gauge or the nearest focusable element round it, such as a GaugeControl, has keyboard focus. Pass open to hold it open, or shut, instead of following hover, presses and focus.
The bubble avoids the edges of the viewport without leaving its point. When it would cross an edge, it flips to the other side of the point if that side has more room. If it still crosses the left or right edge, it slides sideways, but only as far as it keeps overlapping the point. It never slides up or down, so a tooltip whose gauge is below the fold stays with it and scrolls into view alongside it. collisionPadding is the room kept clear of the edge, 8 pixels by default, and avoidCollisions={false} turns this off.
Children replace the default content with any HTML. Pass a function to get the value being shown, animated frame by frame, and className to restyle the bubble:
<GaugeTooltip position="pointer" className="bg-popover text-popover-foreground">
{(v) => (
<div className="flex items-center gap-2">
<span className="size-2 rounded-full" style={{ background: zoneColor(zones, v, "gray") }} />
{v < 60 ? "Normal" : "High"} · {v.toFixed(0)}°
</div>
)}
</GaugeTooltip>
The bubble is portalled to document.body and placed through the SVG's screen transform. The gauge never clips it, and it keeps its CSS size however the gauge, or an inset it sits in, is scaled. Hover is taken over the whole <svg>, so a tooltip inside a GaugeInset shows when the pointer is anywhere over the host gauge. The entrance animation uses the tw-animate-css classes that shadcn projects already include.
GaugeInset is a second gauge drawn inside the first: a fuel dial under a speedometer, a ring inside a ring. It takes its own value and domain and publishes the same context Gauge does, so every primitive works inside it unchanged.
<Gauge value={speed} min={0} max={240} startAngle={40} endAngle={320}>
<GaugeTicks count={12} />
<GaugeNeedle style="pointer" />
<GaugeHub />
<GaugeInset
value={fuel}
y={144}
scale={0.28}
min={0}
max={100}
startAngle={90}
endAngle={270}
>
<GaugeTrack width={26} />
<GaugeArc width={26} />
</GaugeInset>
</Gauge>
It draws in the host gauge's coordinate system rather than a viewport of its own, so it takes no padding or fit. x and y place its centre in the host's SVG units, and scale sizes the whole thing at once: strokes, ticks and text shrink with the dial, so parts meant to be read at a quarter size are worth drawing heavier than they would be at full size. Insets can be stacked for concentric rings, and each one animates its own value.
The Speed & fuel, Nested rings and System templates in the studio are built this way. The studio edits every gauge in a composition: the chips above the value slider pick which one the panels, the slider and the play modes are pointed at, and the same row adds and removes them. An inset's Gauge panel trades padding, fit and the stage colour for where it sits and how big it is drawn. The generated code takes a prop per gauge.
Value changes are instant by default. Pass transition to Gauge and every part that reads the value, including the arc, needle and value text, animates together. No animation library is involved; the gauge steps its own spring or tween per frame and snaps when the viewer prefers reduced motion.
// A gentle spring with the default settings
<Gauge value={value} transition>
// Tune the spring by feel
<Gauge value={value} transition={{ type: "spring", visualDuration: 0.4, bounce: 0.25 }}>
// Or by physics, which takes precedence when given
<Gauge value={value} transition={{ type: "spring", stiffness: 120, damping: 14, mass: 1 }}>
// A timed tween with a named curve, cubic-bezier points, or your own function
<Gauge value={value} transition={{ type: "tween", duration: 0.6, ease: "easeOut" }}>
<Gauge value={value} transition={{ type: "tween", duration: 0.6, ease: [0.22, 1, 0.36, 1] }}>
// Sweep in from the minimum on mount
<Gauge value={value} initialValue={0} transition>
| Option | Applies to | Meaning |
|---|---|---|
visualDuration | spring | Seconds the spring visibly takes to arrive. Default 0.5 |
bounce | spring | 0 settles without overshoot, 1 rings freely. Default 0.1 |
stiffness, damping, mass | spring | Raw physics. Defaults 100, 10 and 1 when any is set |
duration | tween | Seconds. Default 0.5 |
ease | tween | linear, easeIn, easeOut, easeInOut, [x1, y1, x2, y2] or (t) => number. Default easeInOut |
Inside custom primitives, useGauge() exposes both value, the animated figure being drawn this frame, and target, the value the gauge is heading for. On a range, from and targetFrom are the same for its start, and range is true. The studio's Value panel has the same controls, and the code it generates carries your transition.
This repository is also the site: a landing page at / and the studio at /studio. Run it locally with:
npm install
npm run dev
registry.json at the repository root defines the single gauge item. npm run registry:build writes the installable JSON to public/r/, and the production build runs it automatically so the deployed site always serves the current source.