Rhythm Loader
BetaLoaders · Feedback
One rhythm strip, swept like a monitor. The quietest way for an interface to say it is still there.
5 states18 props16.2 KB installedsince 0.2.0
npx @oxygenui-design/cli add rhythm-loaderFirst install? Run oxygen init once to say where your @/ alias points. The source is copied into your repository, along with anything it depends on.
Preview
Every state, switchable.
States4
Why this state exists
One PQRST complex on a baseline, swept by a bright head with a fading tail. Nothing scales — which is what makes it usable in a table row and at twenty pixels.
Why it exists
The clinical default, and the loader to reach for when a beating heart would be the wrong thing to put in front of someone.
It carries no symbol, only the artifact a clinician already reads all day: P wave, QRS complex, T wave, in those proportions rather than the decorative zig-zag that generic ECG graphics use. Because it never scales, it is also the only cardiac loader that survives at twenty pixels — so it is what Pulse Loader renders when it is asked to be small, and what belongs beside a button label or in a table row.
Usage
Props are the FHIR resource.
import { RhythmLoader } from "@/components/oxygen/rhythm-loader";
// Inline, beside a control
<RhythmLoader size="sm" label="Loading results" />
// Clinical dashboard panel, slower cadence
<RhythmLoader mode="overlay" bpm={52} label="Loading the worklist" delay={200} />Dependencies
- clsx
- tailwind-merge
18 props
- actions
React.ReactNode
Rendered under the hint — a Retry or Go back control while someone waits.
- announce
LoaderAnnounce
Live-region politeness while indeterminate.
- bpm
number
Beats per minute, 40–100. Clamped to a resting range.
- delay
number
Wait this long before appearing, so a fast response never flashes a loader.
- hint
string
A second line under the label. Never a substitute for it.
- label
string
What is loading. Always rendered — visibly when `showLabel`, and to assistive technology either way, because a loader nobody can hear is a silent wait.
- minDuration
number
Once visible, stay at least this long, so the loader never blinks out.
- mode
LoaderMode
`inline` sits in the flow; `overlay` covers its positioned ancestor; `page` covers the viewport.
- motion
LoaderMotion
`auto` follows the OS; `reduced` forces the still state; `full` opts out of the OS preference.
- onSlow
(() => void)
Fires once, when `slowAfter` elapses.
Page 1 of 2
Source
Exactly what lands in your repository.
Read directly from the published registry, so this can never drift from what the CLI installs.
Show sourceHide source70 lines
"use client";
/**
* RhythmLoader — one PQRST complex on a baseline, swept like a monitor.
*
* The quietest way to say "still here". No symbol, no scaling, nothing that
* grows: a bright head with a dimmer tail travels the trace once per beat over
* a faint static track, and the line itself never disappears.
*
* This is the right default for clinical density and for any workflow where a
* beating heart would jar — resuscitation, cardiology, bereavement. It is also
* the only cardiac loader that survives at 20px, so it is what PulseLoader
* renders when it is asked to be small.
*
* The shape is a rhythm strip, the artifact a clinician actually reads, rather
* than the zig-zag that decorative "ECG" graphics use: P wave, QRS complex,
* T wave, in that order and in those proportions.
*/
import * as React from "react";
import {
LOADER_ART,
LOADER_VIEWBOX,
LoaderFrame,
beatMs,
clamp,
cycleMs,
resolveLoaderSize,
strokePx,
type LoaderCommonProps,
} from "@/lib/oxygen-loader";
export interface RhythmLoaderProps extends LoaderCommonProps {
/** Beats per minute, 40–100. Clamped to a resting range. */
bpm?: number;
}
export function RhythmLoader({ bpm, speed = 1, size, ...props }: RhythmLoaderProps) {
const px = resolveLoaderSize(size, "lg");
const beat = beatMs(bpm, speed);
return (
<LoaderFrame
{...props}
variant="rhythm"
vars={{
"--ox-loader-size": `${px}px`,
"--ox-loader-beat": `${beat}ms`,
"--ox-loader-cycle": `${cycleMs(4000, clamp(speed, 0.5, 2))}ms`,
"--ox-loader-stroke": `${strokePx(px)}px`,
}}
art={
<svg viewBox={LOADER_VIEWBOX.rhythm} focusable="false">
<path className="ox-loader__stroke ox-loader__track" d={LOADER_ART.strip} />
<path
className="ox-loader__stroke ox-loader__tail"
pathLength={100}
d={LOADER_ART.strip}
/>
<path
className="ox-loader__stroke ox-loader__head"
pathLength={100}
d={LOADER_ART.strip}
/>
</svg>
}
/>
);
}