Skip to content

Media Resources

MediaResourceRenderer is the unified component for rendering all server-managed media (images and videos). Use it whenever the resource comes from the API.


When to Use

Source Component
API MediaResource object MediaResourceRenderer
Static local asset (/public/) SafeImage (see Images)
Raw CDN path string SafeImage with getServerFileUrl()

Quick Start

import { MediaResourceRenderer } from "@/components/MediaResource";

<MediaResourceRenderer
  resource={product.heroImage}
  context="card"
/>

context Prop

Sets sensible defaults for the five standard placement types. All defaults can be overridden with explicit props.

Context mode default aspectRatio default objectFit default Gallery
thumbnail static 1/1 cover enabled
card static 4/3 cover enabled
preview static auto contain enabled
modal interactive auto contain disabled
inline static auto contain disabled

mode Prop

Mode Behavior
static Display only — image or video renders with no interaction layer
interactive Adds zoom/pan/rotate, a drawing toolbar, and comment pin support

Props Reference

Prop Type Default Description
resource MediaResourceMinimal \| MediaResourcePublic required The media resource to render
context "thumbnail" \| "card" \| "preview" \| "modal" \| "inline" "card" Sets rendering defaults (see above)
mode "static" \| "interactive" from context Display-only or full interactive mode
aspectRatio string from context CSS aspect-ratio (e.g. "16/9", "auto")
forceAspectRatio boolean false Enforce aspect ratio even when resource dimensions are known
objectFit "cover" \| "contain" \| "fill" from context CSS object-fit for the media element
preset ImagePreset from context Override the SafeImage sizing preset
fill boolean true Stretch to fill the container
width number Fixed width (disables fill)
height number Fixed height (disables fill)
priority boolean false Pass priority to next/image for LCP images
thumbnailUrl string Override the displayed URL (useful for custom previews)
actions Action<MediaResourceInput>[] Context-menu actions shown on right-click
slots SlotDefinition[] Hover-only overlay content (see Slots)
persistentSlots SlotDefinition[] Always-visible overlay content (see Slots)
showCaption boolean false Show filename caption below the media
hoverPreview boolean false Show a tooltip preview on hover
className string Container class
mediaClassName string Class applied to the <img> or <video> element
toolbarClassName string Class applied to the interactive toolbar
galleryKey string Group key for the lightbox gallery (see Gallery)
disableGallery boolean false Opt out of gallery entirely
hideHoverInfo boolean false Suppress the metadata hover overlay
annotationContextType string Entity type for comment pins (requires mode="interactive")
annotationContextId string Entity ID for comment pins (requires mode="interactive")
disableDrawing boolean Suppress drawing toolbar in interactive mode
disableCommentPins boolean Suppress comment pins in interactive mode
disableInitialWheelZoom boolean Don't auto-engage wheel-zoom on mount
onToolActiveChange (active: boolean) => void Called when a drawing/annotation tool activates
onClick (resource: MediaResourceInput, e: React.MouseEvent) => void Custom click handler (overrides gallery open)
onLoad (e: SyntheticEvent) => void Fired when media loads successfully
onError (e: SyntheticEvent) => void Fired on load error
onContextMenu (resource: MediaResourceInput, e: React.MouseEvent) => void Called on right-click alongside the action menu (does not replace it)

Action Menu

Default behavior

actions is optional. When omitted, the renderer automatically calls useMediaResourceActions(resource) internally and uses the result as the context-menu action list. To disable the context menu entirely, pass actions={[]}.

Usage patterns

1. Use defaults as-is (omit actions)

// No actions prop needed — the renderer wires the standard set automatically
<MediaResourceRenderer resource={resource} context="card" />

2. Extend defaults via useMediaResourceActions

import { useMediaResourceActions } from "@/hooks/actions/useMediaResourceActions";

const actions = useMediaResourceActions(resource, {
  exclude: ["copyUrl"],           // remove specific actions by ID
  extraActions: [shareAction],    // appended after built-in actions
});

<MediaResourceRenderer resource={resource} actions={actions} />

3. Replace entirely with a custom action list

import type { Action } from "@/types/actionMenu";
import type { MediaResourceInput } from "@/types/mediaResource";

const customActions: Action<MediaResourceInput>[] = [
  {
    type: "action",
    id: "share",
    label: "Share",
    icon: Share2,
    onSelect: ({ data }) => shareResource(data),
  },
];

<MediaResourceRenderer resource={resource} actions={customActions} />

Worked example — adding a share action via extraActions

import { Share2 } from "lucide-react";
import type { StandardAction } from "@/types/actionMenu";
import type { MediaResourceInput } from "@/types/mediaResource";
import { useMediaResourceActions } from "@/hooks/actions/useMediaResourceActions";

const shareAction: StandardAction<MediaResourceInput> = {
  type: "action",
  id: "share",
  label: "Share",
  icon: Share2,
  onSelect: ({ data }) => {
    navigator.clipboard.writeText(data.url);
  },
};

function MyCard({ resource }: { resource: MediaResourceInput }) {
  const actions = useMediaResourceActions(resource, {
    extraActions: [shareAction],
  });

  return <MediaResourceRenderer resource={resource} actions={actions} />;
}

Default action IDs

ID Type Description
openInNewTab StandardAction Opens the raw CDN URL in a new browser tab
download CustomAction (images) / StandardAction (video) Images: format selector split-button (webp/png/jpeg); video: direct download
copyUrl StandardAction Copies the CDN URL to the clipboard
SeparatorAction Visual divider (id: "clipboard-separator")
toggleClipboard StandardAction Adds or removes the resource from the media clipboard
metadata CustomAction Inline metadata panel (always rendered last)

Slots

slots and persistentSlots accept SlotDefinition[] to place overlay content inside the renderer:

type SlotDefinition = {
  id: string;
  content: React.ReactNode;
  position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
  className?: string;
};
  • slots — visible only while the user hovers over the media
  • persistentSlots — always visible regardless of hover state

Hover Metadata Overlay

When hovering, a tooltip displays resource metadata automatically. Fields shown:

  • Type, resolution, aspect ratio, orientation
  • File size, format, duration (video)
  • Color profile, creation date

Disable with hideHoverInfo={true}.

buildMetadataRows(resource, overrides?)

Returns a MetadataRow[] array for building custom metadata displays outside the renderer:

import { buildMetadataRows } from "@/components/MediaResource/utils/buildMetadataRows";

const rows = buildMetadataRows(resource, {
  resolution: "Custom label",
});

Multiple MediaResourceRenderer instances sharing a galleryKey are grouped into a single lightbox gallery. Clicking any instance opens the full-screen viewer with navigation between all items in the group.

// Group all product images in one gallery
{product.images.map((img) => (
  <MediaResourceRenderer
    key={img.id}
    resource={img}
    galleryKey="product-gallery"
  />
))}
  • Single image — omit galleryKey; a per-instance gallery is created automatically
  • Opt out — set disableGallery={true}

Annotations

Enable comment pins and drawing tools in interactive mode:

<MediaResourceRenderer
  resource={revision.outputImage}
  mode="interactive"
  context="modal"
  annotationContextType="shot_revision"
  annotationContextId={revision.id}
/>

Both annotationContextType and annotationContextId must be provided together. Individual tools can be suppressed with disableDrawing or disableCommentPins.


Driver System

The renderer auto-detects the resource type and delegates to the appropriate driver:

Driver Activated when
ImageDriver resource.mediaType is image
VideoDriver resource.mediaType is video

Drivers handle the underlying SafeImage/<video> element, zoom state, and interactive tool wiring. You do not interact with drivers directly.


Source Files

File Contents
src/components/MediaResource/MediaResourceRenderer.tsx Main renderer component
src/components/MediaResource/drivers/ImageDriver.tsx Image rendering + zoom/pan
src/components/MediaResource/drivers/VideoDriver.tsx Video rendering
src/components/MediaResource/interactive/InteractiveToolbar.tsx Drawing + annotation toolbar
src/components/MediaResource/interactive/CommentPinOverlay.tsx Comment pin layer
src/components/MediaResource/utils/buildMetadataRows.ts Metadata row builder
src/components/MediaResource/utils/formatters.ts Size/duration formatters
src/components/MediaResource/MediaResourceOverlay.tsx Hover metadata overlay
src/components/MediaResource/index.ts Public exports
src/hooks/actions/useMediaResourceActions.tsx Standard action set hook