Corex.FileUpload (Corex v0.2.0)

View Source

File upload for Phoenix LiveView. Behavior follows Zag.js File Upload. Use multipart on the parent form and read %Plug.Upload{} from params in a controller action, as in Phoenix file uploads.

LiveView phx-submit cannot transport raw multipart file bytes over the WebSocket; use a controller route for classic Plug.Upload, or allow_upload/3 for LiveView-native uploads with Corex.FileUploadLive (<.file_upload_live>). Do not combine this Zag component with live_file_input on the same file control.

Anatomy

Minimal

<.file_upload name="document" class="file-upload">
  <:close>
    <.heroicon name="hero-x-mark" />
  </:close>
</.file_upload>

With label

<.file_upload name="document" class="file-upload">
  <:label>Files</:label>
  <:close>
    <.heroicon name="hero-x-mark" />
  </:close>
</.file_upload>

Custom slots

<.file_upload name="document" class="file-upload">
  <:dropzone>
    <span>Custom dropzone</span>
  </:dropzone>
  <:open>
    <span>Custom trigger</span>
  </:open>
  <:close>
    <.heroicon name="hero-x-mark" />
  </:close>
</.file_upload>

Multipart form (controller)

<.form for={@form} action={~p"/file-upload/form"} method="post" multipart>
  <input type="hidden" name="file_upload_changeset[_sent]" value="1" />
  <.file_upload field={@form[:attachment]} class="file-upload">
    <:label>Attachment</:label>
    <:close>
      <.heroicon name="hero-x-mark" />
    </:close>
    <:error :let={msg}>
      <.heroicon name="hero-exclamation-circle" class="icon" />
      {msg}
    </:error>
  </.file_upload>
  <.action type="submit" class="button ui-accent w-full">Submit</.action>
</.form>

Use multipart on the parent form so %Plug.Upload{} is available on the server for classic uploads. Optional hidden _sent supports used_input? when validating empty submits.

API

Requires a stable id on <.file_upload>.

FunctionActionReturns
clear_files/1Clear accepted files (client)%Phoenix.LiveView.JS{}
clear_files/2Clear accepted files (server)socket
clear_rejected_files/1Clear rejected list (client)%Phoenix.LiveView.JS{}
clear_rejected_files/2Clear rejected list (server)socket
open_file_picker/1Open native picker (client)%Phoenix.LiveView.JS{}
open_file_picker/2Open native picker (server)socket

Events

Pick an event name and pass it to on_* on <.file_upload>. Rejected files are not listed in the DOM; use on_file_reject to react to validation failures.

Server events

EventWhenPayload
on_file_change="files_changed"Accepted file list changes%{"id" => id, ...}
on_file_accept="file_accepted"File passes validation%{"id" => id, ...}
on_file_reject="file_rejected"File fails validation%{"id" => id, ...}

Client events

EventWhenevent.detail
on_file_change_client="files-changed"Accepted list changesid, file metadata
on_file_accept_client="file-accepted"File acceptedid, file metadata
on_file_reject_client="file-rejected"File rejectedid, reason

Form

See Multipart form under Anatomy. Use field={f[:attachment]} inside <.form multipart>.

For cross-cutting invalid styling and error presentation, see the Forms guide.

Style

Target parts with data-scope and data-part, or use Corex Design: import tokens and file-upload.css, then set class="file-upload" on <.file_upload>.

[data-scope="file-upload"][data-part="root"] {}
[data-scope="file-upload"][data-part="trigger"] {}
[data-scope="file-upload"][data-part="dropzone"] {}
[data-scope="file-upload"][data-part="item-delete-trigger"] {}
@import "../corex/corex.css";

Stack modifiers on the host (class on <.file_upload>). Combine axes, for example file-upload ui-accent ui-size-lg or file-upload ui-info ui-solid.

Axes: Semantic (ui-accent, ui-brand, ui-alert, ui-info, ui-success), Variant (ui-solid), Size (ui-size-smui-size-xl), Radius (ui-rounded-*). See the modifier guide.

Semantic modifiers set palette variables on the trigger and dropzone chrome. Variant modifiers control trigger surface treatment. Default is subtle; add file-upload ui-solid for a filled trigger.

Semantic

Palette variables for trigger and dropzone ink and fill. Does not change surface treatment by itself.

ModifierClasses
Defaultfile-upload
Accentfile-upload ui-accent
Brandfile-upload ui-brand
Alertfile-upload ui-alert
Infofile-upload ui-info
Successfile-upload ui-success

Variant

Visual treatment of the open/trigger control. Combine with a semantic modifier for palette-driven ink and fill.

ModifierClasses
Subtle (default)file-upload or file-upload ui-accent
Solidfile-upload ui-accent ui-solid

Size

ModifierClasses
SMfile-upload ui-size-sm
MDfile-upload ui-size-md
LGfile-upload ui-size-lg
XLfile-upload ui-size-xl

Summary

API

Drop every accepted file entry from phx-click. Dispatches corex:file-upload:clear-files.

Clear accepted files from handle_event (file_upload_clear_files).

Remove rejected entries from phx-click. Dispatches corex:file-upload:clear-rejected.

Clear rejected files from handle_event (file_upload_clear_rejected).

Open the OS file picker from phx-click. Dispatches corex:file-upload:open.

Open the OS file picker from handle_event (file_upload_open).

Components

file_upload(assigns)

Attributes

  • id (:string) - Stable id for the file upload root; set automatically when using field. Defaults to nil.
  • field (Phoenix.HTML.FormField) - Form field for id, name, form, invalid, and required wiring. Defaults to nil.
  • name (:string) - The name attribute of the hidden file input. Defaults to nil.
  • form (:string) - The id of the form this control belongs to. Defaults to nil.
  • invalid (:boolean) - Whether the control has validation errors. Defaults to nil.
  • auto_invalid (:boolean) - When true with field, set invalid from visible changeset errors (default false). Defaults to false.
  • disabled (:boolean) - Whether the control is disabled. Defaults to false.
  • read_only (:boolean) - Whether the file upload is read-only. Defaults to false.
  • required (:boolean) - Whether at least one file is required. Defaults to false.
  • dir (:string) - Text direction (ltr or rtl). Defaults to nil. Must be one of nil, "ltr", or "rtl".
  • max_files (:integer) - Maximum number of files the user may select. Defaults to 1.
  • max_file_size (:integer) - Maximum file size in bytes; omit for no limit. Defaults to nil.
  • min_file_size (:integer) - Minimum file size in bytes; omit for no limit. Defaults to nil.
  • allow_drop (:boolean) - Whether drag-and-drop onto the dropzone is enabled. Defaults to true.
  • prevent_document_drop (:boolean) - When true, prevents dropping files on the document outside the dropzone. Defaults to true.
  • accept (:string) - Comma-separated MIME types or extensions (e.g. image/*,.pdf). Defaults to nil.
  • directory (:boolean) - When true, allow selecting a directory instead of individual files. Defaults to false.
  • on_file_change (:string) - Server event when the accepted file list changes. Defaults to nil.
  • on_file_change_client (:string) - Client event name when the accepted file list changes. Defaults to nil.
  • on_file_accept (:string) - Server event when a file passes validation. Defaults to nil.
  • on_file_accept_client (:string) - Client event name when a file passes validation. Defaults to nil.
  • on_file_reject (:string) - Server event when a file fails validation. Defaults to nil.
  • on_file_reject_client (:string) - Client event name when a file fails validation. Defaults to nil.
  • translation (Corex.FileUpload.Translation) - Override translatable strings. Defaults to nil.
  • errors (:list) - List of error messages when not using field=. Defaults to [].
  • Global attributes are accepted.

Slots

  • label - Label above the dropzone. Accepts attributes:
    • class (:string)
  • dropzone - Custom dropzone content; defaults to translation dropzone text.
  • open - Custom open-picker trigger; defaults to translation open text.
  • close (required) - Remove control for each accepted file entry. Accepts attributes:
    • class (:string)
  • error - Error message content; receives the message as slot argument. Accepts attributes:
    • class (:string)

API

clear_files(file_upload_id)

@spec clear_files(String.t()) :: Phoenix.LiveView.JS.t()

Drop every accepted file entry from phx-click. Dispatches corex:file-upload:clear-files.

<.action phx-click={Corex.FileUpload.clear_files("my-fu")}>Clear accepted</.action>
<.file_upload id="my-fu" class="file-upload" accept="image/*">
  <:close><.heroicon name="hero-x-mark" /></:close>
</.file_upload>
document.getElementById("my-fu")?.dispatchEvent(new CustomEvent("corex:file-upload:clear-files", { bubbles: false }));

clear_files(socket, file_upload_id)

Clear accepted files from handle_event (file_upload_clear_files).

def handle_event("clear_files", _, socket) do
  {:noreply, Corex.FileUpload.clear_files(socket, "my-fu")}
end

clear_rejected_files(file_upload_id)

@spec clear_rejected_files(String.t()) :: Phoenix.LiveView.JS.t()

Remove rejected entries from phx-click. Dispatches corex:file-upload:clear-rejected.

<.action phx-click={Corex.FileUpload.clear_rejected_files("my-fu")}>Clear rejected</.action>
<.file_upload id="my-fu" class="file-upload">
  <:close><.heroicon name="hero-x-mark" /></:close>
</.file_upload>

clear_rejected_files(socket, file_upload_id)

@spec clear_rejected_files(Phoenix.LiveView.Socket.t(), String.t()) ::
  Phoenix.LiveView.Socket.t()

Clear rejected files from handle_event (file_upload_clear_rejected).

def handle_event("clear_rejected", _, socket) do
  {:noreply, Corex.FileUpload.clear_rejected_files(socket, "my-fu")}
end

open_file_picker(file_upload_id)

@spec open_file_picker(String.t()) :: Phoenix.LiveView.JS.t()

Open the OS file picker from phx-click. Dispatches corex:file-upload:open.

<.action phx-click={Corex.FileUpload.open_file_picker("my-fu")}>Browse</.action>
<.file_upload id="my-fu" class="file-upload">
  <:close><.heroicon name="hero-x-mark" /></:close>
</.file_upload>

open_file_picker(socket, file_upload_id)

Open the OS file picker from handle_event (file_upload_open).

def handle_event("browse", _, socket) do
  {:noreply, Corex.FileUpload.open_file_picker(socket, "my-fu")}
end