@media-sdk/ui-react
v1.0.0
Published
Presentational React components for Media SDK applications
Readme
@media-sdk/ui-react
Reusable presentational React components for Media SDK applications.
This package does not fetch data. It renders UI from props you pass in. Wire data fetching with @media-sdk/react hooks (or direct @media-sdk/core calls) in your application layer, then pass results into these components.
Installation
pnpm add @media-sdk/ui-reactPeer Dependencies
- React 18 or React 19
@media-sdk/core is installed automatically as a dependency because component prop types reference Photo and Video.
Components
SearchBarMediaTabsPhotoCardPhotoGridPhotoPreviewVideoCardVideoGridVideoPreviewPaginationLoadingStateErrorState
Types: MediaTab ("photos" | "videos")
SearchBar
Controlled search input. Your app owns the query state and submit handler:
import { SearchBar } from "@media-sdk/ui-react";
function AppSearch({
query,
onQueryChange,
onSearch,
}: {
query: string;
onQueryChange: (value: string) => void;
onSearch: (query: string) => void;
}) {
return (
<SearchBar
value={query}
onChange={onQueryChange}
onSubmit={(event) => {
event.preventDefault();
onSearch(query);
}}
/>
);
}PhotoGrid
Renders a grid of PhotoCard items. Pass photos from your data layer:
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
Same pattern for videos:
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 when it is shown:
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}
/>
);
}Pagination
Page navigation controls. Pass pagination state from your hooks or client:
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}
/>
);
}Loading and error states
import {
PhotoGrid,
LoadingState,
ErrorState,
} from "@media-sdk/ui-react";
import type { Photo } from "@media-sdk/core";
function Photos({
photos,
loading,
error,
onPhotoSelect,
}: {
photos: Photo[];
loading: boolean;
error: Error | null;
onPhotoSelect: (photo: Photo) => void;
}) {
if (loading) {
return <LoadingState message="Loading photos..." />;
}
if (error) {
return <ErrorState message="Failed to load photos." />;
}
return (
<PhotoGrid
photos={photos}
onPhotoSelect={onPhotoSelect}
/>
);
}Wiring hooks and UI
@media-sdk/ui-react never calls the Pexels API. Fetch in your app, render with UI components:
import { useMediaSearch } from "@media-sdk/react";
import {
SearchBar,
PhotoGrid,
PhotoPreview,
Pagination,
LoadingState,
ErrorState,
} from "@media-sdk/ui-react";
import { useState } from "react";
import type { Photo } from "@media-sdk/core";
function PhotoBrowser() {
const [query, setQuery] = useState("");
const [submittedQuery, setSubmittedQuery] = useState("");
const [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null);
const { data, loading, error, nextPage, previousPage } = useMediaSearch({
query: submittedQuery,
enabled: submittedQuery.length > 0,
});
return (
<>
<SearchBar
value={query}
onChange={setQuery}
onSubmit={(event) => {
event.preventDefault();
setSubmittedQuery(query);
}}
/>
{loading && <LoadingState message="Loading photos..." />}
{error && <ErrorState message={error.message} />}
{data && (
<>
<PhotoGrid
photos={data.items}
onPhotoSelect={setSelectedPhoto}
/>
<Pagination
page={data.pagination.page}
hasPrevious={data.pagination.hasPrevious}
hasNext={data.pagination.hasNext}
loading={loading}
ariaLabel="Photo results"
onPrevious={() => void previousPage()}
onNext={() => void nextPage()}
/>
</>
)}
{selectedPhoto && (
<PhotoPreview
photo={selectedPhoto}
onClose={() => setSelectedPhoto(null)}
/>
)}
</>
);
}Architecture
@media-sdk/ui-react contains presentational components only. It does not perform API requests, manage search state, or own pagination logic.
apps/web (or your app) ← data fetching + state
│
├── @media-sdk/ui-react ← UI rendering (props in, JSX out)
│
└── @media-sdk/react ← hooks, provider
│
▼
@media-sdk/core ← API client, types, cachePublic API
Import from the package entry only:
import {
SearchBar,
MediaTabs,
PhotoCard,
PhotoGrid,
PhotoPreview,
VideoCard,
VideoGrid,
VideoPreview,
Pagination,
LoadingState,
ErrorState,
type MediaTab,
} from "@media-sdk/ui-react";Internal utilities such as getPhotoSrc and getPlayableVideoLink are not part of the public API.
