@linkrunner/relay-ui
v0.1.0
Published
Shared Linkrunner React interface primitives.
Keywords
Readme
@linkrunner/relay-ui
Shared React primitives extracted from the current Linkrunner dashboard. This first package preserves ordinary dashboard button behavior, dimensions, and visual styles, with an explicit Slot adapter for composed links. It contains Button, IconButton, Input, Label, FormFieldLabel, the Tooltip family, the Modal family, and AlertDialog.
Use in an application
Install matching token and UI package versions, then load both stylesheets once in the application's global entry:
import '@linkrunner/relay-tokens/themes.css';
import '@linkrunner/relay-ui/styles.css';
import { Button, Input, TooltipProvider } from '@linkrunner/relay-ui';
export function CampaignForm() {
return (
<TooltipProvider>
<form>
<Input label="Campaign name" name="campaignName" />
<Button type="submit" variant="primary">Save campaign</Button>
</form>
</TooltipProvider>
);
}Keep a single TooltipProvider at the application root in production. Per-component exports are available, for example @linkrunner/relay-ui/button, @linkrunner/relay-ui/modal, and @linkrunner/relay-ui/tooltip. Every JavaScript entry preserves its use client directive for Next.js App Router consumers; React and React DOM remain peer dependencies and are never bundled.
Use class="dark" or data-relay-theme="dark" on the HTML element to switch the entire application, including portaled dialogs and tooltips. Set light mode with data-relay-theme="light" and remove the dark class. A theme applied only to a nested section cannot automatically reach a portal attached to document.body.
Load the application's Inter font asset. The package ships typography values and the dashboard's font feature settings; it does not download fonts or change the host document's font settings.
Link composition
Button asChild accepts one element that forwards DOM props and its ref, such as an
anchor or application routing Link. Button styles and handlers reach that element;
leftIcon and rightIcon stay package-owned and are composed around its content.
The same contract applies to a hybrid button's main action; its second action remains
a separate button. A Fragment cannot be used as the child.
<Button asChild leftIcon={<DownloadSimple aria-hidden="true" />}>
<a href="/reports">Download report</a>
</Button>While disabled or loading, the child has aria-disabled and tabIndex=-1, activation
events are blocked, and native anchors lose their href. Routing components retain
their required href and must forward the supplied capture handlers. Loading shows
the package spinner while keeping the original child content available as its
accessible name. Ordinary buttons keep the dashboard's existing loading behavior.
Styling boundary
The distributed CSS is already compiled. Consumers need neither Tailwind nor a Tailwind content scan. Internal utility classes, animation names, and Tailwind custom properties have a relay- namespace. Component defaults apply only to package-owned elements, including portaled surfaces. There is no document-wide reset.
Consumer className values retain their spelling and can supply layout such as width or margins. They merge with package utilities using the dashboard's current class-composition rules; the winning package classes stay prefixed, and winning application classes use the host stylesheet. Use the existing variants and sizes for appearance. When composing buttonVariants() on a native element, use mergeClasses from @linkrunner/relay-ui/utils; the returned relay-control marker supplies the same scoped CSS defaults as the component.
Tokens are a required peer dependency. The importing application explicitly chooses its token version and loads its theme stylesheet. No package import mutates browser storage, routing, authentication, or data.
Authoritative source
provenance.json records source and packaged SHA-256 hashes for dashboard commit e3e79de0e0ab7b021b7a648b947da3b1c2fe0945. scripts/extract-dashboard.mjs enumerates the mechanical transforms and can verify a checkout:
node scripts/extract-dashboard.mjs --source /path/to/linkrunner-dashboard --ref e3e79de --checkThe transforms add client boundaries, replace imports, namespace existing utilities and variables, give four arbitrary CSS properties equivalent named utilities, and mark owned elements for scoped CSS defaults. The recorded Button adapter also adds relay-control to exported variants, fixes direct-child Slot composition, and implements disabled/busy link semantics. --ref reads the original files using git show, so verification still works after the dashboard adopts Relay. --only button can regenerate that component and provenance without writing the other component sources; --check always verifies the full set.
Inherited behavior remains visible: tooltips suppress keyboard focus opening; ordinary loading buttons replace their label with the spinner's accessible label; optional input helper text is not automatically associated by aria-describedby. The existing palette and contrast limitations also remain unchanged. The Slot correction is limited to composed controls and is recorded separately from the mechanical extraction.
Validation
npm run build
npm testThe build emits ESM, TypeScript declarations, and one stylesheet; it checks CSS variable resolution against the token package, scoped Tailwind defaults, selector isolation, theme selection, and every public entry's client boundary. Interaction tests cover loading, split actions, native prop/ref forwarding, input clear behavior, hover tooltips, modal dismissal, and consequential-confirmation behavior. Real consumer builds and browser rendering are validated from packed artifacts by the repository's consumer pilot workflow.
