mix phia.add (phia_ui v0.1.5)

Copy Markdown View Source

Ejects a PhiaUI component into the current Phoenix project as an editable source file.

When you run mix phia.add, PhiaUI renders an EEx template and writes the result to lib/{app_web}/components/ui/{component}.ex. From that point on, the file belongs to your project — edit it freely. This is the same "copy-paste ownership" model popularised by shadcn/ui.

Usage

mix phia.add <component>

Available components

button    badge    card    table    dialog

What gets created

Running mix phia.add button in a project called my_app creates:

lib/my_app_web/components/ui/button.ex
lib/my_app_web/class_merger.ex          (shared dependency, skipped if exists)
lib/my_app_web/class_merger/cache.ex    (shared dependency, skipped if exists)
lib/my_app_web/class_merger/groups.ex   (shared dependency, skipped if exists)
assets/js/phia_hooks/{component}.js     (only for components that need a hook)

The web module namespace (e.g., MyAppWeb) is derived automatically from the :app key in mix.exs.

Behaviour

  • Derives the web module name from Mix.Project.config()[:app] (e.g., :my_appMyAppWeb).
  • Renders the EEx template from PhiaUI's priv/templates/components/.
  • Writes the file with Mix.Generator.create_file/3 (conflict-aware).
  • Idempotent: if the file already exists the user is prompted before any overwrite.
  • The ClassMerger utility (required by every component) is ejected automatically and skipped if already present.
  • Prints a confirmation message via Mix.shell().info/1.

Next steps after ejecting

  1. Run mix phia.install once (if you haven't already) to inject PhiaUI theme tokens into assets/css/app.css.
  2. Add ClassMerger.Cache to your supervision tree: children = [MyAppWeb.ClassMerger.Cache, ...]
  3. Import the component in any LiveView or layout: import MyAppWeb.Components.UI.Button

Options

--help    Print this help message

Examples

$ mix phia.add button
* create lib/my_app_web/components/ui/button.ex

$ mix phia.add dialog
* create lib/my_app_web/components/ui/dialog.ex
* create assets/js/phia_hooks/dialog.js

Summary

Functions

Derives the host project's web module name from the current Mix project.

Builds the absolute target file path for a component under root.

Ejects the ClassMerger dependency files into root.

Ejects a component from PhiaUI's templates into root.

Copies the JS hook file for component_name into root/assets/js/phia_hooks/ if a corresponding hook template exists at priv/templates/js/hooks/{component}.js.

Returns a next-steps message to display after ejecting component_name.

Functions

app_web_name()

@spec app_web_name() :: String.t()

Derives the host project's web module name from the current Mix project.

The web module name is the camelized app name suffixed with "Web". For example, a project with app: :my_app returns "MyAppWeb".

Example

iex> Mix.Tasks.Phia.Add.app_web_name()
"PhiaUiWeb"

component_path(root, component_name)

@spec component_path(Path.t(), String.t()) :: Path.t()

Builds the absolute target file path for a component under root.

The path follows the Phoenix convention of placing UI components under lib/{app_web_dir}/components/ui/.

Example

iex> Mix.Tasks.Phia.Add.component_path("/project", "button")
"/project/lib/phia_ui_web/components/ui/button.ex"

eject_class_merger(root)

@spec eject_class_merger(Path.t()) :: :ok

Ejects the ClassMerger dependency files into root.

ClassMerger is a Tailwind CSS class conflict resolver (similar to tailwind-merge) implemented in pure Elixir. Every PhiaUI component imports the cn/1 helper it provides, so it must be present in the host project.

Creates three files relative to lib/{app_web_dir}/:

  • class_merger.ex — main cn/1 function
  • class_merger/cache.ex — ETS-backed GenServer cache
  • class_merger/groups.ex — Tailwind conflict group mappings

Skips any file that already exists, making this function safe to call repeatedly.

eject_component(component_name, root \\ File.cwd!())

@spec eject_component(String.t(), Path.t()) ::
  :ok | :already_exists | {:error, String.t()}

Ejects a component from PhiaUI's templates into root.

This is the main entry point called by run/1. It runs a pipeline of steps: validate → resolve_target → render_content → write_file.

Also automatically ejects the ClassMerger utility (a required dependency shared by all components) unless it is already present.

Return values

  • :ok — component was created successfully.
  • :already_exists — file already existed; user was prompted.
  • {:error, reason} — component name is not in the known components list.

Example

iex> Mix.Tasks.Phia.Add.eject_component("button", "/tmp/my_project")
:ok

eject_js_hooks(component_name, root)

@spec eject_js_hooks(String.t(), Path.t()) :: :ok

Copies the JS hook file for component_name into root/assets/js/phia_hooks/ if a corresponding hook template exists at priv/templates/js/hooks/{component}.js.

Components such as Dialog, DropdownMenu, and Toast require a client-side JavaScript hook for browser interactions (focus trapping, keyboard handling, etc.). This function handles that copy automatically during ejection.

No-op (returns :ok) when no hook template is found for the given component. Idempotent: skips the copy if the target hook file already exists.

next_steps_message(component_name)

@spec next_steps_message(String.t()) :: String.t()

Returns a next-steps message to display after ejecting component_name.

This message reminds the developer to run mix phia.install, add ClassMerger.Cache to their supervision tree, and import the component.