UI components reference
Prop tables for @media-sdk/ui-react@^0.3.0. All components are presentational — your app owns data fetching and state.
pnpm add @media-sdk/ui-react@^0.3.0Wiring diagram
flowchart TB
subgraph app ["Your app"]
state["Query, tab, filter state"]
caps["useMediaCapabilities()"]
search["useMediaSearch / useMediaVideos"]
end
subgraph ui ["@media-sdk/ui-react"]
bar["SearchBar"]
tabs["MediaTabs"]
grid["PhotoGrid / VideoGrid"]
pag["Pagination"]
load["LoadingState / ErrorState"]
prev["PhotoPreview / VideoPreview"]
end
state --> bar
caps --> state
search --> grid
search --> pag
search --> load
grid --> prevSearchBar
Controlled search input with a submit button. Your app owns the query string and submit handler.
| Prop | Type | Required | Description |
|---|---|---|---|
value | string | Yes | Current input value |
onChange | (value: string) => void | Yes | Called when the input value changes |
onSubmit | (event: FormEvent<HTMLFormElement>) => void | Yes | Called on form submit (typically event.preventDefault() then trigger search) |
Example
import { SearchBar } from "@media-sdk/ui-react";
import { useState } from "react";
function AppSearch({
onSearch,
}: {
onSearch: (query: string) => void;
}) {
const [query, setQuery] = useState("");
return (
<SearchBar
value={query}
onChange={setQuery}
onSubmit={(event) => {
event.preventDefault();
onSearch(query);
}}
/>
);
}MediaTabs
Photos / Videos tab switcher. Your app controls which tab is active and responds to changes.
| Prop | Type | Required | Description |
|---|---|---|---|
activeTab | MediaTab | Yes | Current tab: "photos" or "videos" |
onTabChange | (tab: MediaTab) => void | Yes | Called when the user selects a tab |
Types
type MediaTab = "photos" | "videos";Example
import { MediaTabs, type MediaTab } from "@media-sdk/ui-react";
import { useState } from "react";
function TabbedSearch() {
const [activeTab, setActiveTab] = useState<MediaTab>("photos");
return (
<MediaTabs
activeTab={activeTab}
onTabChange={setActiveTab}
/>
);
}Gate the Videos tab in your app with useMediaCapabilities().operations.searchVideos if needed.
PhotoGrid
Renders a grid of PhotoCard items.
| Prop | Type | Required | Description |
|---|---|---|---|
photos | Photo[] | Yes | Photos to display (from useMediaSearch or direct client calls) |
onPhotoSelect | (photo: Photo) => void | Yes | Called when the user clicks a photo card |
Example
import { PhotoGrid } from "@media-sdk/ui-react";
import type { Photo } from "@media-sdk/core";
function Photos({
photos,
onPhotoSelect,
}: {
photos: Photo[];
onPhotoSelect: (photo: Photo) => void;
}) {
return (
<PhotoGrid
photos={photos}
onPhotoSelect={onPhotoSelect}
/>
);
}VideoGrid
Renders a grid of VideoCard items. Same pattern as PhotoGrid.
| Prop | Type | Required | Description |
|---|---|---|---|
videos | Video[] | Yes | Videos to display (from useMediaVideos or direct client calls) |
onVideoSelect | (video: Video) => void | Yes | Called when the user clicks a video card |
Example
import { VideoGrid } from "@media-sdk/ui-react";
import type { Video } from "@media-sdk/core";
function Videos({
videos,
onVideoSelect,
}: {
videos: Video[];
onVideoSelect: (video: Video) => void;
}) {
return (
<VideoGrid
videos={videos}
onVideoSelect={onVideoSelect}
/>
);
}PhotoPreview
Modal overlay for a selected photo. Your app controls visibility — render only when a photo is selected.
| Prop | Type | Required | Description |
|---|---|---|---|
photo | Photo | Yes | Photo to display |
onClose | () => void | Yes | Called when the user closes the preview (overlay click or close button) |
Behavior
- Renders a modal dialog with the large photo and photographer caption
- Clicking the overlay or the close button calls
onClose - Focus management via internal
usePreviewDialoghook
Example
import { PhotoPreview } from "@media-sdk/ui-react";
import type { Photo } from "@media-sdk/core";
function PhotoWithPreview({
selectedPhoto,
onClose,
}: {
selectedPhoto: Photo | null;
onClose: () => void;
}) {
if (!selectedPhoto) {
return null;
}
return (
<PhotoPreview
photo={selectedPhoto}
onClose={onClose}
/>
);
}Pair with client.trackView({ mediaId: photo.id, mediaType: "photo" }) in your app when the preview opens — see Events.
VideoPreview
Modal overlay with an HTML5 video player.
| Prop | Type | Required | Description |
|---|---|---|---|
video | Video | Yes | Video to display |
onClose | () => void | Yes | Called when the user closes the preview |
Behavior
- Selects the best playable MP4 link from
video.video_files - Auto-plays with controls when a source is available
- Shows a fallback message when no playable file exists
- Displays duration caption
Example
import { VideoPreview } from "@media-sdk/ui-react";
import type { Video } from "@media-sdk/core";
function VideoWithPreview({
selectedVideo,
onClose,
}: {
selectedVideo: Video | null;
onClose: () => void;
}) {
if (!selectedVideo) {
return null;
}
return (
<VideoPreview
video={selectedVideo}
onClose={onClose}
/>
);
}Pagination
Previous / next page navigation controls. Pass pagination state from hooks or direct client responses.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
page | number | Yes | — | Current page number |
hasPrevious | boolean | Yes | — | Whether a previous page exists |
hasNext | boolean | Yes | — | Whether a next page exists |
loading | boolean | No | false | When true, disables both navigation buttons |
ariaLabel | string | Yes | — | Accessible label for the navigation landmark |
onPrevious | () => void | Yes | — | Called when the user clicks Previous |
onNext | () => void | Yes | — | Called when the user clicks Next |
Example
import { Pagination } from "@media-sdk/ui-react";
function PhotoPagination({
page,
hasPrevious,
hasNext,
loading,
onPrevious,
onNext,
}: {
page: number;
hasPrevious: boolean;
hasNext: boolean;
loading: boolean;
onPrevious: () => void;
onNext: () => void;
}) {
return (
<Pagination
page={page}
hasPrevious={hasPrevious}
hasNext={hasNext}
loading={loading}
ariaLabel="Photo results"
onPrevious={onPrevious}
onNext={onNext}
/>
);
}Wire to hook pagination:
import { useMediaSearch } from "@media-sdk/react";
import { Pagination } from "@media-sdk/ui-react";
function SearchResults({ query }: { query: string }) {
const { data, loading, nextPage, previousPage } = useMediaSearch({ query });
if (!data) return null;
return (
<Pagination
page={data.pagination.page}
hasPrevious={data.pagination.hasPrevious}
hasNext={data.pagination.hasNext}
loading={loading}
ariaLabel="Photo results"
onPrevious={() => void previousPage()}
onNext={() => void nextPage()}
/>
);
}LoadingState
Accessible loading message with aria-live="polite".
| Prop | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Text shown while loading |
Example
import { LoadingState } from "@media-sdk/ui-react";
function Results({ loading }: { loading: boolean }) {
if (loading) {
return <LoadingState message="Loading photos…" />;
}
return null;
}ErrorState
Accessible error message with role="alert".
| Prop | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Error text to display |
Example
import { ErrorState } from "@media-sdk/ui-react";
function Results({ error }: { error: Error | null }) {
if (error) {
return <ErrorState message={error.message} />;
}
return null;
}For structured error handling, branch on MediaError.code in your app before choosing the message string. See Errors.
PhotoCard
Single photo thumbnail button. Used internally by PhotoGrid; available for custom layouts.
| Prop | Type | Required | Description |
|---|---|---|---|
photo | Photo | Yes | Photo to render |
onSelect | (photo: Photo) => void | Yes | Called when the card is clicked |
VideoCard
Single video thumbnail button with play icon and duration. Used internally by VideoGrid.
| Prop | Type | Required | Description |
|---|---|---|---|
video | Video | Yes | Video to render |
onSelect | (video: Video) => void | Yes | Called when the card is clicked |
End-to-end example
Full wiring of hooks → props → components:
import { useMediaSearch, useMediaVideos } from "@media-sdk/react";
import {
SearchBar,
MediaTabs,
PhotoGrid,
PhotoPreview,
VideoGrid,
VideoPreview,
Pagination,
LoadingState,
ErrorState,
type MediaTab,
} from "@media-sdk/ui-react";
import { useState } from "react";
import type { Photo, Video } from "@media-sdk/core";
function MediaBrowser() {
const [query, setQuery] = useState("");
const [submittedQuery, setSubmittedQuery] = useState("");
const [activeTab, setActiveTab] = useState<MediaTab>("photos");
const [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null);
const [selectedVideo, setSelectedVideo] = useState<Video | null>(null);
const photoSearch = useMediaSearch({
query: submittedQuery,
enabled: activeTab === "photos" && submittedQuery.length > 0,
});
const videoSearch = useMediaVideos({
query: submittedQuery,
enabled: activeTab === "videos" && submittedQuery.length > 0,
});
const active = activeTab === "photos" ? photoSearch : videoSearch;
return (
<>
<SearchBar
value={query}
onChange={setQuery}
onSubmit={(event) => {
event.preventDefault();
setSubmittedQuery(query);
}}
/>
<MediaTabs activeTab={activeTab} onTabChange={setActiveTab} />
{active.loading && <LoadingState message="Loading…" />}
{active.error && <ErrorState message={active.error.message} />}
{activeTab === "photos" && photoSearch.data && (
<PhotoGrid
photos={photoSearch.data.items}
onPhotoSelect={setSelectedPhoto}
/>
)}
{activeTab === "videos" && videoSearch.data && (
<VideoGrid
videos={videoSearch.data.items}
onVideoSelect={setSelectedVideo}
/>
)}
{active.data && (
<Pagination
page={active.data.pagination.page}
hasPrevious={active.data.pagination.hasPrevious}
hasNext={active.data.pagination.hasNext}
loading={active.loading}
ariaLabel={`${activeTab} results`}
onPrevious={() => void active.previousPage()}
onNext={() => void active.nextPage()}
/>
)}
{selectedPhoto && (
<PhotoPreview
photo={selectedPhoto}
onClose={() => setSelectedPhoto(null)}
/>
)}
{selectedVideo && (
<VideoPreview
video={selectedVideo}
onClose={() => setSelectedVideo(null)}
/>
)}
</>
);
}