React components for @arraypress/waveform-bar — a Spotify-style persistent bottom-bar audio player. Two components:
<WaveformBar> |
Singleton mount. Render once in your root layout. Takes a typed config prop. |
<WaveformBarTrigger> |
Polymorphic click trigger. Render anywhere you want a play / queue button — product cards, modals, track rows. |
The core libraries stay zero-dependency vanilla JS, so they keep working anywhere a <script> tag does. This package adds the framework-native ergonomics — typed props, proper attribute serialisation, JSON encoding for arrays — for React developers.
import { WaveformBar, WaveformBarTrigger } from '@arraypress/waveform-bar-react';
function App() {
return (
<>
<WaveformBarTrigger url="/audio/track.mp3" title="My Track" />
<WaveformBar config={{ persist: true, continuous: true }} />
</>
);
}npm install @arraypress/waveform-bar-react \
@arraypress/waveform-bar \
@arraypress/waveform-player \
reactThree peer dependencies — you bring them so you control versions. The bar has a strict runtime dependency on the player; both must be present.
Load both core libraries' JS + CSS once at your app entry. Order matters — the player must load before the bar:
// main.tsx (Vite) / app/layout.tsx (Next.js) / root.tsx (Remix)
import '@arraypress/waveform-player/dist/waveform-player.css';
import '@arraypress/waveform-bar/dist/waveform-bar.css';
import '@arraypress/waveform-player'; // installs window.WaveformPlayer
import '@arraypress/waveform-bar'; // installs window.WaveformBarThen mount <WaveformBar> exactly once in your layout:
function Layout({ children }) {
return (
<>
{children}
<WaveformBar config={{ persist: true, continuous: true }} />
</>
);
}Triggers can render anywhere — they find the bar via document-level click delegation, no React context needed.
<WaveformBarTrigger
url="/audio/track.mp3"
title="My Track"
artist="Producer"
/>Renders a <button> with the play / pause SVG pair the library knows how to toggle.
<WaveformBarTrigger
url="/audio/beat.mp3"
id="product-42"
title="Trap Beat"
artist="Producer"
artwork="/img/cover.jpg"
album="Beat Tape Vol. 3"
bpm={140}
musicalKey="Cm"
duration="3:45"
link="/products/trap-beat"
meta={['Stems included', '24-bit']}
/><WaveformBarTrigger
mode="queue"
url="/audio/track.mp3"
title="My Track"
>
+ Queue
</WaveformBarTrigger><WaveformBarTrigger
url="/audio/track.mp3"
waveform="/peaks/track.json"
/>Saves the Web Audio decode at play time. Generate the JSON at build time with @arraypress/waveform-gen.
Title / artist / artwork update as playback crosses each marker boundary.
<WaveformBarTrigger
url="/audio/guest-mix.mp3"
title="Friday Night Mix"
artist="DJ One"
markers={[
{ time: 0, label: 'Intro', title: 'Opening Track' },
{ time: 180, label: 'Drop', title: 'Big Tune', bpm: 174, key: 'Am' },
{ time: 360, label: 'Outro', title: 'Cooldown' },
]}
>
Play mix
</WaveformBarTrigger>Set as="div" and pass noDefaultIcons so the SVG pair doesn't leak into the card layout:
<WaveformBarTrigger
as="div"
url="/audio/track.mp3"
title="Card Track"
noDefaultIcons
className="product-card"
>
<img src={cover} alt="" />
<h3>{title}</h3>
</WaveformBarTrigger>The bar dispatches every state change as a bubbling CustomEvent — listen from any React effect:
useEffect(() => {
const onPlay = (e: Event) => {
const detail = (e as CustomEvent).detail;
console.log('playing:', detail.track.title);
};
document.addEventListener('waveformbar:play', onPlay);
return () => document.removeEventListener('waveformbar:play', onPlay);
}, []);Events available: waveformbar:play, waveformbar:pause, waveformbar:trackchange, waveformbar:favorite, waveformbar:cart, waveformbar:volumechange, waveformbar:markerchange, waveformbar:queuechange.
The bar's singleton instance is at window.WaveformBar. Cast it where you need it:
function NextButton() {
return (
<button onClick={() => window.WaveformBar?.next()}>
Next track
</button>
);
}Methods: play(track), pause(), togglePlay(), next(), previous(), setVolume(0-1), toggleMute(), setRepeat('off'|'all'|'one'), cycleRepeat(), addToQueue(track), clearQueue(), seekToMarker(index), getCurrentTrack(), getQueue(), more — see the core library README for the full surface.
<WaveformBar> only re-runs init() when the structure of the config prop changes (compared via JSON.stringify). Reference changes alone don't re-init — so passing a fresh object every render is safe as long as the shape is stable.
<WaveformBarTrigger> is a pure render — it just stamps out a <button> (or whatever as resolves to) with the right data-wb-* attributes. The bar's runtime handles clicks via document-level delegation, so trigger re-renders are virtually free.
Every field optional — the library has sensible defaults. See src/types.ts for the full JSDoc.
{
// Persistence + behaviour
persist?: boolean; // default true
autoResume?: boolean; // default true
continuous?: boolean; // default true
repeat?: 'off' | 'all' | 'one';
// UI toggles
showQueue, showPrevNext, showRepeat, showVolume, showMute,
showTime, showTrackLink, showMeta: boolean;
maxMeta: number;
// Theming
theme: 'dark' | 'light' | null;
defaultArtwork: string;
// Waveform visualisation
waveformStyle: 'bars' | 'mirror' | 'line' | 'blocks' | 'dots' | 'seekbar';
waveformHeight, barWidth, barSpacing: number;
waveformColor, progressColor, markerColor: string;
// Volume + persistence keys
volume: number;
storageKey: string;
// Server-side actions
actions: {
favorite?: { endpoint, method?, headers? };
cart?: { endpoint, method?, headers? };
};
}| Prop | Type | Becomes |
|---|---|---|
url required |
string |
data-wb-url |
id |
string |
data-wb-id (falls back to url) |
title |
string |
data-wb-title |
artist |
string |
data-wb-artist |
album |
string |
data-wb-album |
artwork |
string |
data-wb-artwork |
link |
string |
data-wb-link |
duration |
string | number |
data-wb-duration |
bpm |
string | number |
data-wb-bpm |
musicalKey |
string |
data-wb-key |
meta |
string[] |
data-wb-meta (JSON) |
waveform |
number[] | string |
data-wb-waveform (JSON if array) |
markers |
WaveformBarMarker[] |
data-wb-markers (JSON) |
favorited |
boolean |
data-wb-favorited |
inCart |
boolean |
data-wb-in-cart |
mode |
'play' | 'queue' (default 'play') |
Picks the trigger attribute |
as |
'button' | 'a' | 'div' | 'span' (default 'button') |
Element to render |
href |
string |
When as="a" |
aria-label |
string |
Falls back to Play {title} / Add {title} to queue |
className |
string |
Appended to wb-icon-swap |
style |
CSSProperties |
Inline style |
noDefaultIcons |
boolean |
Suppress the default play/pause SVGs |
children |
ReactNode |
Replaces the default icon content |
import type {
WaveformBarProps,
WaveformBarConfig,
WaveformBarTriggerProps,
WaveformBarTrackData,
WaveformBarMarker,
WaveformBarActions,
WaveformBarAction,
WaveformBarTheme,
RepeatMode,
TriggerMode,
WaveformStyle,
} from '@arraypress/waveform-bar-react';.d.ts ships for both ESM and CJS consumers.
npm test # one-shot
npm run test:watch
npm run typecheck
npm run build # emit dist/index.js, dist/index.cjs, dist/index.d.ts46 tests cover <WaveformBarTrigger> rendering + attribute mapping (no module mocking needed) and <WaveformBar> lifecycle (window global mocked since jsdom has no Web Audio).
MIT © ArrayPress