Joint panel

Pairs host-reported joint positions with independent draft targets, supplied units and limits, and an explicit request action.

Joint targets

Reported at: 12:00:00.125

  • Shoulder

    Reported: 12.5 deg

    Limits: -90 to 90 deg

  • Slide

    Reported: 0 m

    Limits: 0 to 0.5 m

Editing a target does not move a joint. The host handles motion and reports its result.

Inspection arm

Recorded positions and a local target draft

Reported at: 12:00:00.125, controller time

  • Shoulder

    Reported: 12.5 deg

    Limits: -90 to 90 deg

  • Slide

    Reported: 0 m

    Limits: 0 to 0.5 m

Editing a target does not move a joint. The host handles motion and reports its result.

Use it for

  • Reported joint positions and a separate draft target map keyed by joint identifier.
  • value/defaultValue/onValueChange for controlled editing or an isolated prototype; drafts never inherit missing values from reported positions.
  • Finite minimum and maximum values in each joint's supplied unit. A complete valid draft is required before requesting targets.
  • onRequestTargets to ask the host to apply a draft; pending and confirmedLabel describe independently supplied request state.
  • Missing, non-finite, duplicate, and unlisted records receive explicit feedback. No motion or limit policy is inferred.

Not for

  • Treating a successful click as confirmed robot movement.
  • Using the component as a motion planner, interlock, unit converter, or source of joint limits.

Three ways in. They share one source, so the pixels match whichever you pick.

1. Import the package

npm install @noorddev/vlak-react

// once, next to your app's root
import "@noorddev/vlak-react/css";

import { JointPanel } from "@noorddev/vlak-react";

Precompiled React and one stylesheet. No compiler to configure. Per-component imports work too: @noorddev/vlak-react/components/joint-panel.

2. Vendor the source

npx @noorddev/vlak-cli add joint-panel

The StyleX leaf lands in components/vlak/ with its dependencies, for your own compiler to own. See StyleX.

3. Through shadcn

npx shadcn add https://vlak.dev/r/joint-panel.json

The same registry item, installed by shadcn's CLI.

CSS only

<link rel="stylesheet" href="node_modules/@noorddev/vlak/css/vlak.css" />

No React. Link vlak.css and use the markup and classes below.

import { JointPanel } from "@noorddev/vlak-react";

<JointPanel label="Arm joints" name="targets" timeLabel="12:00:00.125"
  joints={[{ id: "shoulder", label: "Shoulder", unit: "deg", reported: 12.5, minimum: -90, maximum: 90 }, { id: "slide", label: "Slide", unit: "m", reported: 0, minimum: 0, maximum: 0.5 }]}
  defaultValue={{ shoulder: 0, slide: 0 }}
  onRequestTargets={targets => console.log("Requested targets", targets)} />

JointPanel

Draft joint targets remain separate from host-reported positions and request confirmation.

PropTypeDefaultDescription
labelrequiredstring
jointsrequiredreadonly JointRecord[]
valueJointTargets
defaultValueJointTargets{}
onValueChange(targets: JointTargets) => void
onRequestTargets(targets: JointTargets) => voidRequests the complete draft. Motion and confirmation belong to the host.
pendingbooleanfalse
confirmedLabelReactNode
timeLabelReactNode
descriptionReactNode
readOnlybooleanfalse

Also accepts Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, "defaultValue">.

KeysDoes
TabMoves through target inputs and the request button.
Arrow up, Arrow downUses native number-input stepping within the supplied limits.
Enter, SpaceActivates the focused request button when the complete draft is valid.
<fieldset class="rs-joint-panel"><legend class="rs-joint-panel-legend">Arm joints</legend><p class="rs-joint-panel-copy">Reported at: 12:00:00.125</p><ul class="rs-joint-panel-list"><li class="rs-joint-panel-joint"><div><p class="rs-joint-panel-title">Shoulder</p><p class="rs-joint-panel-reading">Reported: 12.5 deg</p><p class="rs-joint-panel-copy">Limits: -90 to 90 deg</p></div><label class="rs-joint-panel-field">Draft target (deg)<input class="rs-input rs-input-full rs-joint-panel-input" type="number" step="any" min="-90" max="90" value="0" aria-label="Shoulder draft target (deg)" /></label></li></ul><button class="rs-btn-primary rs-joint-panel-button" type="button">Request targets</button></fieldset>
.rs-joint-panel.rs-joint-panel-legend.rs-joint-panel-copy.rs-joint-panel-list.rs-joint-panel-joint.rs-joint-panel-title.rs-joint-panel-reading.rs-joint-panel-field.rs-joint-panel-input.rs-joint-panel-button