registry:block
Fine-tune Card
An inspector panel with scrubbable number fields and a segmented control.
Rendered example
Layout
Install
orion-themesegmented-controlshimmer
Open registry JSON registry/blocks/fine-tune-card.tsx
"use client"
import * as React from "react"
import { SegmentedControl } from "@/registry/ui/segmented-control"
import { Shimmer } from "@/registry/ui/shimmer"
/**
* FINE-TUNE CARD — compact interactive inspector.
* Number fields scrub: drag the label to adjust, use up/down arrow
* keys (shift for x10), or type directly.
*/
function ScrubField({
label,
value,
onChange,
min,
max,
step = 1,
suffix = "",
active,
}: {
label: string
value: number
onChange: (v: number) => void
min: number
max: number
step?: number
suffix?: string
active?: boolean
}) {
const drag = React.useRef<{ x: number; v: number } | null>(null)
const clamp = (v: number) => Math.min(max, Math.max(min, Math.round(v)))
return (
<label
className="flex h-6.5 min-w-0 items-center gap-1 rounded-chip py-1 pr-1 pl-0.5 transition-[background-color,box-shadow] duration-200"
style={{
background: active ? "var(--accent-tint)" : "var(--field)",
boxShadow: active ? "0 0 0 1px var(--accent)" : "none",
}}
>
<span
role="slider"
aria-label={label}
aria-valuenow={value}
aria-valuemin={min}
aria-valuemax={max}
tabIndex={0}
onPointerDown={(e) => {
;(e.target as HTMLElement).setPointerCapture(e.pointerId)
drag.current = { x: e.clientX, v: value }
}}
onPointerMove={(e) => {
if (!drag.current) return
onChange(clamp(drag.current.v + ((e.clientX - drag.current.x) / 2) * step))
}}
onPointerUp={() => (drag.current = null)}
onKeyDown={(e) => {
const mult = e.shiftKey ? 10 : 1
if (e.key === "ArrowUp" || e.key === "ArrowRight") {
e.preventDefault()
onChange(clamp(value + step * mult))
} else if (e.key === "ArrowDown" || e.key === "ArrowLeft") {
e.preventDefault()
onChange(clamp(value - step * mult))
}
}}
className="flex h-full shrink-0 cursor-ew-resize touch-none items-center rounded-[4px] px-0.5 text-[12px] text-ink-3 select-none hover:text-ink-2 focus-visible:text-accent-ink focus-visible:outline-none"
>
{label}
</span>
<input
inputMode="numeric"
value={value}
onChange={(e) => {
const n = Number(e.target.value.replace(/[^\d-]/g, ""))
if (!Number.isNaN(n)) onChange(clamp(n))
}}
aria-label={`${label} value`}
className="min-w-0 flex-1 bg-transparent text-[12px] text-ink tabular-nums outline-none"
/>
{suffix && <span className="shrink-0 pr-0.5 text-[11.5px] text-ink-3">{suffix}</span>}
</label>
)
}
const SEGMENTS = ["row", "col", "grid"] as const
function SegmentIcon({ kind }: { kind: string }) {
const dot = "size-1.5 rounded-[2px] border-[1.2px] border-current"
if (kind === "row")
return (
<span className="flex gap-0.5">
{[0, 1, 2].map((i) => (
<span key={i} className={dot} />
))}
</span>
)
if (kind === "col")
return (
<span className="flex flex-col gap-0.5">
{[0, 1].map((i) => (
<span key={i} className={dot} />
))}
</span>
)
return (
<span className="grid grid-cols-2 gap-0.5">
{[0, 1, 2, 3].map((i) => (
<span key={i} className={dot} />
))}
</span>
)
}
export function FineTuneCard() {
const [seg, setSeg] = React.useState(0)
const [width, setWidth] = React.useState(280)
const [height, setHeight] = React.useState(88)
const [radius, setRadius] = React.useState(14)
const [opacity, setOpacity] = React.useState(100)
const [menuOpen, setMenuOpen] = React.useState(false)
const [typeValue, setTypeValue] = React.useState("Select type")
const done =
seg !== 0 || width !== 280 || height !== 88 || radius !== 14 || opacity !== 100 || typeValue !== "Select type"
return (
<div className="relative w-full max-w-60 rounded-card bg-surface shadow-raised">
<div className="primitive-card-bar flex items-center justify-between border-b border-line">
<span className="text-[13px] font-medium text-ink">Card component</span>
{done ? (
<span
className="flex items-center gap-1.5 text-[12px] font-medium text-green"
style={{ animation: "pop-in 250ms cubic-bezier(0.23,1,0.32,1) both" }}
>
<svg
width="10"
height="10"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M20 6L9 17l-5-5" />
</svg>
Edited
</span>
) : (
<span className="flex items-center gap-1.5">
<span className="flex size-4.5 items-center justify-center rounded-[5px] border border-accent/30 bg-accent-tint">
<svg width="9" height="9" viewBox="0 0 24 24" fill="var(--accent)">
<path d="M12 2l2.4 7.2L22 12l-7.6 2.8L12 22l-2.4-7.2L2 12l7.6-2.8z" />
</svg>
</span>
<Shimmer variant="accent" className="text-[12px]">
Adjust
</Shimmer>
</span>
)}
</div>
<div className="primitive-card-pad flex flex-col gap-2 border-b border-line">
<p className="text-[12.5px] font-medium text-ink">Layout</p>
<SegmentedControl
segments={SEGMENTS.map((s) => ({
value: s,
node: <SegmentIcon kind={s} />,
}))}
value={SEGMENTS[seg] ?? SEGMENTS[0]}
onChangeAction={(v) => setSeg(SEGMENTS.indexOf(v))}
/>
<div className="grid min-w-0 grid-cols-2 gap-2">
<ScrubField label="W" value={width} onChange={setWidth} min={40} max={999} active={width !== 280} />
<ScrubField label="H" value={height} onChange={setHeight} min={24} max={999} active={height !== 88} />
</div>
<div className="grid min-w-0 grid-cols-2 gap-2">
<ScrubField label="Radius" value={radius} onChange={setRadius} min={0} max={64} active={radius !== 14} />
<ScrubField
label="Opacity"
value={opacity}
onChange={setOpacity}
min={0}
max={100}
suffix="%"
active={opacity !== 100}
/>
</div>
</div>
<div className="primitive-card-footer flex items-center justify-between">
<span className="text-[12px] text-ink-3">Type</span>
<div className="relative -mr-0.5 w-30">
<button
type="button"
aria-expanded={menuOpen}
onClick={() => setMenuOpen((current) => !current)}
className="flex h-6.5 w-full items-center justify-between rounded-chip bg-inset py-1 pr-1 pl-2 shadow-hairline transition-shadow duration-200 focus-visible:outline-none"
style={{ boxShadow: menuOpen ? "0 0 0 1px var(--accent)" : undefined }}
>
<span className={`text-[12px] ${typeValue !== "Select type" ? "text-ink" : "text-ink-3"}`}>
{typeValue}
</span>
<svg
width="11"
height="11"
viewBox="0 0 24 24"
fill="none"
stroke="var(--ink-3)"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
className="transition-transform duration-200"
style={{ transform: menuOpen ? "rotate(180deg)" : "rotate(0)" }}
>
<path d="M6 9l6 6 6-6" />
</svg>
</button>
{menuOpen && (
<div
className="absolute right-0 bottom-8 z-10 w-30 rounded-[10px] bg-surface p-1 shadow-raised"
style={{ animation: "pop-in 200ms cubic-bezier(0.23,1,0.32,1) both", transformOrigin: "bottom right" }}
>
{["Elevated", "Outlined", "Ghost"].map((item) => (
<button
key={item}
type="button"
onClick={() => {
setTypeValue(item)
setMenuOpen(false)
}}
className="flex h-6.5 w-full items-center rounded-[6px] px-2 text-left text-[12.5px] text-ink transition-colors duration-150 hover:bg-field"
style={{ background: item === typeValue ? "var(--field)" : "transparent" }}
>
{item}
</button>
))}
</div>
)}
</div>
</div>
</div>
)
}