@mad-core/react
v0.3.0
Published
React components for serving responsive, format-optimized images, videos, Lottie animations, and Rive animations from a MAD server
Downloads
234
Maintainers
Readme
@mad-core/react
React components for serving responsive, format-optimized images, videos, Lottie, and Rive animations from a MAD (Media Assets Distributor) server.
Features
<MadImage>— renders a<picture>element with automatic<source>generation for modern formats (avif, webp)<MadVideo>— renders a<video>element with<source>elements for format negotiation (webm, mp4) and optional poster screenshots<MadLottie>— renders a Lottie animation (.json/.lottie) via canvas, with autoplay/loop control<MadRive>— renders a Rive animation (.riv) via canvas, with artboard/state-machine/fit control<MadGallery>— fetches a published gallery and renders its items with a render-propuseMadImage/useMadVideo/useMadAnimation/useMadGallery— hooks for building memoized asset URLs or fetching a gallerybuildMadUrl/buildMadVideoUrl/buildMadAnimationUrl/buildMadGalleryUrl— low-level utilities for constructing MAD URLs- Art direction — breakpoint-aware responsive sources via the
sourcesprop - Path-based URLs — DAM-compatible URL format (
/image/w/800/f/webp/my-slug-a7f3b2,/video/f/webm/w/1280/my-slug-a7f3b2,/animation/my-slug-a7f3b2) - Tree-shakeable — ESM-only,
sideEffects: false - TypeScript-first — full type definitions with JSDoc documentation
- Zero required runtime dependencies — only
reactas a required peer;@lottiefiles/dotlottie-weband@rive-app/canvasare optional peers (needed only if you use<MadLottie>/<MadRive>respectively)
Installation
# npm
npm install @mad-core/react
# pnpm
pnpm add @mad-core/react
# yarn
yarn add @mad-core/reactreact 18 or 19 is required as a peer dependency.
Quick Start
import { MadImage } from "@mad-core/react";
function Hero() {
return (
<MadImage
slug="hero-banner-a7f3b2"
baseUrl="https://cdn.example.com"
widths={[400, 800, 1200, 1600]}
sizes="(max-width: 768px) 100vw, 50vw"
alt="Hero banner"
loading="lazy"
/>
);
}URL Format
MAD uses path-based transform parameters (DAM-compatible):
https://cdn.example.com/image/w/800/h/600/f/webp/q/80/c/cover/hero-banner-a7f3b2
https://cdn.example.com/video/w/1280/h/720/f/webm/demo-video-a7f3b2
──────────────────────────────── ─────────────────
key/value pairs (any order) slug (always last)API Reference
<MadImage>
Renders a responsive <picture> element with optimized format sources.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| slug | string | — | Required. MAD asset slug |
| baseUrl | string | — | Required. MAD server base URL |
| params | MadTransformParams | {} | Base transform params applied to all URLs |
| widths | number[] | — | Responsive srcset widths |
| sizes | string | — | HTML sizes attribute |
| formats | Array<"avif" \| "webp" \| "jpg" \| "png"> | ["avif", "webp"] | Format priority list |
| sources | MadSource[] | — | Art direction sources |
| ...rest | ImgHTMLAttributes | — | Passed to the fallback <img> (alt, className, loading, etc.) |
Art direction example
<MadImage
slug="hero-banner-a7f3b2"
baseUrl="https://cdn.example.com"
widths={[400, 800]}
sizes="100vw"
sources={[
{
media: "(min-width: 1024px)",
params: { w: 1200, p: "center" },
widths: [1200, 1800, 2400],
sizes: "80vw",
},
]}
alt="Responsive hero"
/>useMadImage(params: MadUrlParams): UseMadImageResult
Hook that returns a memoized image URL and a srcSet builder function. Use this when you need full control over the rendered markup.
import { useMadImage } from "@mad-core/react";
function Avatar({ slug }: { slug: string }) {
const { url, srcSet } = useMadImage({
slug,
baseUrl: "https://cdn.example.com",
w: 128,
f: "webp",
});
return (
<img
src={url}
srcSet={srcSet([128, 256, 384])}
sizes="128px"
alt="User avatar"
/>
);
}| Return field | Type | Description |
|-------------|------|-------------|
| url | string | Fully-qualified MAD image URL |
| srcSet | (widths: number[]) => string | Generates a srcset string for the given widths |
buildMadUrl(params: MadUrlParams): string
Low-level utility to build a MAD image URL with path-based transform parameters.
import { buildMadUrl } from "@mad-core/react";
const url = buildMadUrl({
slug: "hero-banner-a7f3b2",
baseUrl: "https://cdn.example.com",
w: 800,
f: "webp",
q: 80,
});
// => "https://cdn.example.com/image/w/800/f/webp/q/80/hero-banner-a7f3b2"<MadVideo>
Renders a <video> element with <source> elements for format negotiation, serving optimized video from a MAD server.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| slug | string | — | Required. MAD asset slug |
| baseUrl | string | — | Required. MAD server base URL |
| params | MadVideoTransformParams | {} | Base transform params applied to all URLs |
| formats | Array<"webm" \| "mp4" \| "ogg"> | ["webm", "mp4"] | Format priority list |
| poster | number \| false | — | Time in seconds for a poster screenshot (omit or false to disable) |
| ...rest | VideoHTMLAttributes | — | Passed to <video> (autoPlay, muted, loop, controls, className, etc.) |
<MadVideo
slug="demo-video-a7f3b2"
baseUrl="https://cdn.example.com"
params={{ w: 1280, h: 720 }}
poster={2}
autoPlay muted loop
/>useMadVideo(params: MadVideoUrlParams): UseMadVideoResult
Hook that returns a memoized video URL and a posterUrl builder function. Use this when you need full control over the rendered markup.
import { useMadVideo } from "@mad-core/react";
function Player({ slug }: { slug: string }) {
const { url, posterUrl } = useMadVideo({
slug,
baseUrl: "https://cdn.example.com",
w: 1280,
f: "webm",
});
return <video src={url} poster={posterUrl(2)} controls />;
}| Return field | Type | Description |
|-------------|------|-------------|
| url | string | Fully-qualified MAD video URL |
| posterUrl | (time: number) => string | Generates a poster screenshot URL at the given seek time |
buildMadVideoUrl(params: MadVideoUrlParams): string
Low-level utility to build a MAD video URL with path-based transform parameters.
import { buildMadVideoUrl } from "@mad-core/react";
const url = buildMadVideoUrl({
slug: "demo-video-a7f3b2",
baseUrl: "https://cdn.example.com",
w: 1280,
f: "webm",
dur: 30,
});
// => "https://cdn.example.com/video/w/1280/f/webm/dur/30/demo-video-a7f3b2"<MadLottie>
Renders a Lottie animation (.json Bodymovin or .lottie dotLottie) served by MAD. The animation is drawn onto a <canvas> element by @lottiefiles/dotlottie-web, which is dynamically imported on mount.
Requires the optional peer dependency:
npm install @lottiefiles/dotlottie-web| Prop | Type | Default | Description |
|------|------|---------|-------------|
| slug | string | — | Required. MAD asset slug |
| baseUrl | string | — | Required. MAD server base URL |
| autoplay | boolean | false | Play immediately after load |
| loop | boolean | false | Loop playback |
| className | string | — | Class applied to the canvas |
| style | CSSProperties | — | Inline style for the canvas |
import { MadLottie } from "@mad-core/react";
<MadLottie
slug="hero-animation-a7f3b2"
baseUrl="https://cdn.example.com"
autoplay
loop
className="h-64 w-64"
/>useMadAnimation(params: MadAnimationUrlParams): UseMadAnimationResult
Hook that returns a memoized animation URL. Use this to wire up your own Lottie player.
import { useMadAnimation } from "@mad-core/react";
function MyPlayer({ slug }: { slug: string }) {
const { url } = useMadAnimation({
slug,
baseUrl: "https://cdn.example.com",
});
// feed `url` into lottie-web, dotlottie-react, etc.
}| Return field | Type | Description |
|-------------|------|-------------|
| url | string | Fully-qualified MAD animation URL |
buildMadAnimationUrl(params: MadAnimationUrlParams): string
Low-level utility to build a MAD animation URL. Works for both Lottie and Rive assets — the URL shape is the same; the asset's type field (from the management API) tells you which player to use.
import { buildMadAnimationUrl } from "@mad-core/react";
const url = buildMadAnimationUrl({
slug: "hero-animation-a7f3b2",
baseUrl: "https://cdn.example.com",
});
// => "https://cdn.example.com/animation/hero-animation-a7f3b2"<MadRive>
Renders a Rive animation (.riv) served by MAD. The animation is drawn onto a <canvas> element by @rive-app/canvas, which is dynamically imported on mount.
Requires the optional peer dependency:
npm install @rive-app/canvas| Prop | Type | Default | Description |
|------|------|---------|-------------|
| slug | string | — | Required. MAD asset slug |
| baseUrl | string | — | Required. MAD server base URL |
| artboard | string | first artboard | Artboard to activate |
| stateMachine | string | — | State machine to activate. Omit to play the default timeline |
| fit | "cover" \| "contain" \| "fill" \| "fitWidth" \| "fitHeight" \| "none" | "contain" | Layout fit mode |
| alignment | "center" \| "topLeft" \| "topCenter" \| … | "center" | Alignment within the fitted bounds |
| autoplay | boolean | false | Play immediately after load |
| className | string | — | Class applied to the canvas |
| style | CSSProperties | — | Inline style for the canvas |
Rive's loop behavior is configured inside the
.rivfile at export time, so there is noloopprop — the file's authored setting is authoritative.
import { MadRive } from "@mad-core/react";
<MadRive
slug="hero-animation-a7f3b2"
baseUrl="https://cdn.example.com"
stateMachine="main"
fit="cover"
autoplay
className="h-64 w-full"
/>Types
MadTransformParams
| Field | Type | Description |
|-------|------|-------------|
| w | number | Target width in pixels |
| h | number | Target height in pixels |
| f | MadImageFormat | Output format |
| q | number | Output quality (1–100) |
| c | MadFit | Resize fit mode |
| p | MadPosition | Crop position anchor |
| r | [x, y, w, h] | Region crop (extract) |
| t | string | Transform: "grayscale", "blur", "blur(5)" |
| ro | 90 \| 180 \| 270 | Rotate degrees |
| sh | true \| number | Sharpen (true = default, number = sigma) |
| fl | MadFlip | Flip: "v" (vertical) or "h" (horizontal) |
| bg | string | Background hex color (3, 6, or 8 chars, no #) |
| wm | "br" \| "bl" \| "tr" \| "tl" \| "center" | Watermark position (requires watermark uploaded via admin) |
| wmo | number | Watermark opacity, 0–100 (default 50) |
| wms | number | Watermark scale as % of image width, 1–100 (default 20) |
MadUrlParams
Extends MadTransformParams with:
| Field | Type | Description |
|-------|------|-------------|
| slug | string | MAD asset slug |
| baseUrl | string | MAD server base URL |
MadSource
| Field | Type | Description |
|-------|------|-------------|
| media | string | CSS media query, e.g. "(min-width: 1024px)" |
| params | MadTransformParams | Transform overrides for this breakpoint |
| widths | number[] | srcset widths (overrides global) |
| sizes | string | HTML sizes attribute for this source |
MadPosition
"center" | "top" | "bottom" | "left" | "right"
MadFit
"cover" | "contain" | "fill" | "inside" | "outside"
MadImageFormat
"webp" | "avif" | "jpg" | "png" | "gif" | "auto"
"auto"enables content negotiation: the server inspects the client'sAcceptheader and picks the best format (avif > webp > jpg).
MadFlip
"v" | "h"
MadVideoTransformParams
| Field | Type | Description |
|-------|------|-------------|
| w | number | Target width in pixels |
| h | number | Target height in pixels |
| f | MadVideoFormat | Output format |
| time | number | Seek position in seconds (alone = JPEG screenshot) |
| dur | number | Duration in seconds (trim video) |
MadVideoUrlParams
Extends MadVideoTransformParams with:
| Field | Type | Description |
|-------|------|-------------|
| slug | string | MAD asset slug |
| baseUrl | string | MAD server base URL |
MadVideoFormat
"mp4" | "webm" | "ogg"
<MadGallery>
Fetches a published gallery from /gallery/:slug and renders its items with a render-prop. Uses plain fetch under the hood (no extra runtime deps); you can inject a custom fetcher to integrate with react-query, SWR, or SSR frameworks.
import { MadGallery } from "@mad-core/react";
function GalleryPage({ slug }: { slug: string }) {
return (
<MadGallery
slug={slug}
baseUrl="https://cdn.example.com"
className="grid grid-cols-3 gap-4"
renderLoading={() => <Spinner />}
renderError={(err) => <p>{err.message}</p>}
renderEmpty={() => <p>No items yet</p>}
renderItem={(item) =>
item.type === "image" ? (
<img src={item.thumbnailUrl ?? item.url} alt={item.altText ?? ""} />
) : item.type === "video" ? (
<video src={item.url} poster={item.posterUrl} controls />
) : null
}
/>
);
}| Prop | Type | Default | Description |
|------|------|---------|-------------|
| slug | string | — | Required. Gallery slug |
| baseUrl | string | — | Required. MAD server base URL |
| renderItem | (item, index) => ReactNode | — | Required. Maps each item to JSX |
| renderLoading | () => ReactNode | — | Shown while fetching |
| renderError | (err) => ReactNode | — | Shown on fetch error |
| renderEmpty | (gallery) => ReactNode | — | Shown when items is empty |
| fetcher | MadGalleryFetcher | fetch | Custom fetcher — e.g. react-query |
| className | string | — | Class applied to the wrapping <div> |
useMadGallery(params: UseMadGalleryParams): UseMadGalleryResult
Lower-level hook that fetches on mount and exposes { data, isLoading, error, refetch }. Unlike the other SDK hooks (which are pure URL builders), this one performs a network request because gallery payloads only exist server-side.
import { useMadGallery } from "@mad-core/react";
function List({ slug }: { slug: string }) {
const { data, isLoading, error, refetch } = useMadGallery({
slug,
baseUrl: "https://cdn.example.com",
});
// …render or invoke refetch()…
}| Return field | Type | Description |
|--------------|------|-------------|
| data | MadGalleryResponse \| null | Parsed payload (null while loading or on error) |
| isLoading | boolean | True while the fetch is in flight |
| error | Error \| null | Non-null when the fetch failed |
| refetch | () => void | Re-runs the fetch (aborts any in-flight request) |
buildMadGalleryUrl(params: MadGalleryUrlParams): string
Pure URL builder for SSR or query libs that want to own the fetch.
import { buildMadGalleryUrl } from "@mad-core/react";
const url = buildMadGalleryUrl({
slug: "summer-trip-a7f3b2",
baseUrl: "https://cdn.example.com",
});
// => "https://cdn.example.com/gallery/summer-trip-a7f3b2"MadGalleryResponse
| Field | Type | Description |
|-------|------|-------------|
| slug | string | Gallery slug |
| name | string | Display name |
| description | string \| null | — |
| namespace | string \| null | Namespace or null for global |
| coverUrl | string \| null | Thumbnail of the cover image (explicit or first image-type item) |
| publishedAt | string \| null | ISO 8601 timestamp; null = draft |
| items | MadGalleryItem[] | Ordered items |
MadGalleryItem
| Field | Type | Description |
|-------|------|-------------|
| type | "image" \| "video" \| "lottie" \| "rive" | Asset type |
| slug | string | MAD asset slug |
| url | string | Full delivery URL (filter/adjustments baked-in for images) |
| thumbnailUrl? | string | w/400/f/auto thumbnail (images only) |
| posterUrl? | string | Video poster JPEG at time/0 |
| duration? | number \| null | Video duration in seconds (if known) |
| altText | string \| null | Alt text for images / a11y |
| width | number \| null | Intrinsic width in px |
| height | number \| null | Intrinsic height in px |
| tags | string[] | Tags attached to the asset |
How It Works
<MadImage> renders a standard HTML <picture> element:
- For each art direction source (
sourcesprop), it generates<source>elements for every format in theformatslist, each with the appropriatemedia,type,srcSet, andsizesattributes. - For global responsive widths (
widthsprop), it generates<source>elements for each format (nomediaattribute). - A fallback
<img>element is rendered last with the base URL (no forced format), letting the browser negotiate the best option.
The browser evaluates sources top-to-bottom and picks the first match, giving you automatic format negotiation (avif > webp > fallback) with art-direction breakpoints.
<MadVideo> renders a standard HTML <video> element:
- For each format in the
formatslist, it generates a<source>element with the correspondingsrcandtypeattributes. - When
posteris set, it generates a poster URL using the MAD video screenshot endpoint (/video/time/{seconds}/slug), which returns a JPEG frame at the given seek position. - All remaining props (
autoPlay,muted,loop,controls, etc.) are passed through to the<video>element.
License
MIT
