MediaBrowser LiveComponent — embeddable media management UI.
Provides a full media browser with folder navigation, file upload, search, and selection tools. Operates in two modes:
- Uncontrolled (default): all navigation state is owned internally. No URL sync.
- Controlled (opt-in): when
on_navigateis set to a truthy value, navigation events (navigate_folder,search,clear_search,set_page) emit{PhoenixKitWeb.Components.MediaBrowser, id, {:navigate, params}}to the parent LiveView process instead of mutating local state. The parent must callpush_patchand feed URL params back viasend_updatewith a:nav_paramskey.
Enabling uploads
The fastest path is the Embed helper — one use line gives you
uploads, the validate-channel stub, and the message delegator:
defmodule MyAppWeb.MediaPage do
use MyAppWeb, :live_view
use PhoenixKitWeb.Components.MediaBrowser.Embed
endManual setup (if you prefer explicit wiring) is three calls:
def mount(_params, _session, socket) do
{:ok, PhoenixKitWeb.Components.MediaBrowser.setup_uploads(socket)}
end
def handle_event("validate", _params, socket), do: {:noreply, socket}
def handle_info({__MODULE__, _, _} = msg, socket) do
PhoenixKitWeb.Components.MediaBrowser.handle_parent_info(msg, socket)
endUsage (uncontrolled)
<.live_component
module={PhoenixKitWeb.Components.MediaBrowser}
id="media-browser"
phoenix_kit_current_user={@phoenix_kit_current_user}
/>Picking into a typed slot
only_file_type restricts the browser to one kind for its whole lifetime —
the listing is filtered to it, the type-filter control is hidden, and an
off-type upload is refused, so the other kinds are simply not reachable:
<.live_component
module={PhoenixKitWeb.Components.MediaBrowser}
id="audio-picker"
select_mode
only_file_type="audio"
phoenix_kit_current_user={@phoenix_kit_current_user}
/>Use it wherever the selection fills a typed field. Offering everything and
validating afterwards works, but it lets someone choose a PNG for an audio
slot and only find out on the rejection — and if a consumer forgets that
re-check, the PNG lands in the field and renders as a dead <audio>.
Values: image, video, document, audio, archive, other.
Usage (controlled — URL-sync driven by parent)
<.live_component
module={PhoenixKitWeb.Components.MediaBrowser}
id="media-browser"
phoenix_kit_current_user={@phoenix_kit_current_user}
on_navigate={true}
/>The parent LiveView must implement:
def handle_info({PhoenixKitWeb.Components.MediaBrowser, "media-browser",
{:navigate, params}}, socket) do
# push_patch to update URL, handle_params will send_update back
endparams carries :file — the uuid of the file open in the modal viewer, or
nil when it is closed. Put it in the URL and hand it back in :nav_params
and a refresh, or the browser's Back button, reopens or closes the viewer.
A host that leaves the key out of :nav_params altogether keeps the viewer
local to the component; only an explicit file: nil closes it.
Required attributes
id— unique DOM id (required by LiveComponent)
Optional attributes
phoenix_kit_current_user— logged-in user struct (for upload attribution)scope_folder_id— constrain the browser to a virtual root folderon_navigate— when truthy, enables controlled mode (URL-sync via parent)admin— clicking a file always opens the in-place modal viewer (image / video / PDF / icon + metadata + Download + prev/next navigation). Whentrue, the viewer sidebar additionally shows an "Open details page" button linking to/admin/media/:uuid, and rotations made in the viewer persist to the file row. Bulk-select is still reachable via the toolbar's Select button — onceselect_modeis on, clicks toggle selection instead of opening the modal.
pointer.featured—nil(default, feature off) or `%{uuid: uuidnil, label: String.t() nil}` naming the host's own featured-image labelis the star badge's tooltip; falls back to gettext "Featured image" when nil/absent. When set, image tiles/rows gain a "Set as featured" / "Unset featured" kebab item (grid, list and stack views), the matching tile carries adata-role="featured-badge"star overlay, and the modal viewer's sidebar (for image files) shows the same toggle. The browser never persists anything itself: choosing or clearing a featured image sends{__MODULE__, id, {:set_featured, uuid | nil}}to the host process — the{MediaBrowser, id, payload}channel{:navigate, _}already uses — and optimistically moves the badge locally. ⚠️ A host that funnels every{MediaBrowser, _, _}message intohandle_parent_info/2must match{:set_featured, _}before it: that function handles only its own two internal messages and ends in a catch-all, so the choice is swallowed silently — no crash, no log, the star flips in the UI and nothing is ever persisted. The host persists the choice and, if the write is rejected or the pointer changes elsewhere, corrects it with a laterfeaturedassign (e.g. viasend_update).
Summary
Functions
Callback implementation for Phoenix.LiveComponent.handle_event/3.
Catch-all handler for parent LiveViews to delegate MediaBrowser messages.
Callback implementation for Phoenix.LiveComponent.render/1.
Handles the upload setup message from the MediaBrowser component.
Callback implementation for Phoenix.LiveComponent.update/2.
Functions
Callback implementation for Phoenix.LiveComponent.handle_event/3.
Catch-all handler for parent LiveViews to delegate MediaBrowser messages.
Callback implementation for Phoenix.LiveComponent.render/1.
Handles the upload setup message from the MediaBrowser component.
Parent LiveViews embedding MediaBrowser should add this to their handle_info:
def handle_info({PhoenixKitWeb.Components.MediaBrowser, _id, :setup_uploads}, socket) do
{:noreply, PhoenixKitWeb.Components.MediaBrowser.setup_uploads(socket)}
endOr use the catch-all delegator:
def handle_info({PhoenixKitWeb.Components.MediaBrowser, _, _} = msg, socket) do
PhoenixKitWeb.Components.MediaBrowser.handle_parent_info(msg, socket)
end
Callback implementation for Phoenix.LiveComponent.update/2.