Petal Components

repository·main·Indexed 21 days ago

https://github.com/petalframework/petal_components

A collection of over 30 shadcn-style HEEx components for Phoenix LiveView apps, built with Tailwind v4. It features a dedicated MCP server for AI coding assistant integration and a specialized Chat component family designed for streaming AI interactions, including support for markdown rendering and generative UI via tool calls.

Tokens
20.8K
Snippets
105
Records
121
Agent score
73%

What's inside petal_components

  1. How to use petal_components in HEEx

    main

    Once use PetalComponents is added to your web module, components are available as plain HEEx tags with a leading dot.

    Naming Conventions

    • HEEx Tags: Use <.component_name> (e.g., <.button>, <.modal>, <.table>). Do not use a pc_ prefix in the tag name.
    • Modules: The underlying modules are namespaced under PetalComponents.* (e.g., PetalComponents.Button).
    • CSS Overrides: Use the pc- prefix for manual styling overrides (e.g., pc-button, pc-modal). This is the only place the pc- prefix should be used.

    Form Input Patterns

    There are two ways to handle inputs:

    1. Bundled Field: Use <.field type="..." /> when you want the label, input, error message, and help text bundled together (ideal for standard forms).
    2. Standalone Primitives: Use <.text_input>, <.select>, <.checkbox>, etc., when you need to compose a custom layout.

    Component Discovery

    If you are unsure of the attributes or slots available, use the petal_components MCP server if available:

    • list_components: Returns all components with summaries.
    • get_component <name>: Returns the full schema (attrs, slots, defaults) and a usage example.
  2. How the Chat component architecture works

    main

    The Chat component family uses a composition-first model designed for streaming AI interactions. The core concept is that the streaming bubble owns its own DOM via phx-update="ignore". This allows you to push token deltas directly to the component via JS hooks without LiveView clobbering the partial text.

    Core Components

    • Chat.conversation/1: The main scrollable thread container. It includes a :footer slot for the composer.
    • Chat.chat_message/1: Represents a user or assistant message bubble. Supports an optional :avatar slot.
    • Chat.streaming_text/1: The specialized bubble for in-progress assistant replies. It listens for pc-chat-token events via the PetalChatStream hook.
    • Chat.prompt_input/1: The composer component. Supports Enter to send, Shift+Enter for newlines, and provides loading and on_stop callbacks.
    <Chat.conversation id="chat">
      <Chat.chat_message :for={msg <- @messages} role={msg.role}>
        <span class="pc-chat__text">{msg.text}</span>
      </Chat.chat_message>
    
      <Chat.chat_message :if={@streaming?} role="assistant">
        <Chat.streaming_text id={@stream_id} />
      </Chat.chat_message>
    
      <:footer>
        <Chat.prompt_input
          phx-submit="send"
          phx-change="draft"
          value={@draft}
          loading={@streaming?}
          on_stop="stop"
        />
      </:footer>
    </Chat.conversation>
  3. Update Alpine.js bind syntax for LiveView compatibility

    main

    The latest versions of LiveView do not support Alpine.js bind shortcuts (e.g., :class). You must use the full x-bind: prefix for all attributes using this syntax.

    Perform a global replacement for common attributes:

    • Replace :class= with x-bind:class=
    • Replace :aria-expanded= with x-bind:aria-expanded=
    • (And so on for any other attributes using the : shortcut)
    <!-- Old way -->
    <div :class="..."></div>
    
    <!-- New way -->
    <div x-bind:class="..."></div>
  4. Implement a streaming AI chat in LiveView

    main

    To build a streaming chat, your LLM client should communicate with the LiveView PID using two specific message types:

    1. {:llm_delta, text}: Sent for every new token/chunk.
    2. :llm_done: Sent when the stream is complete.

    Implementation Pattern

    1. Handle Drafts: Use phx-change on the input to sync the textarea value to a draft assign.
    2. Start Stream: On send, start a Task to run your LLM client. Pass the current LiveView PID to the task so it can send messages back.
    3. Push Tokens: In handle_info({:llm_delta, text}, ...):
      • Use push_event("pc-chat-token", %{id: @stream_id, text: text}) to update the UI via JS.
      • Accumulate the text in a buffer assign to commit the full message later.
    4. Commit/Stop: When :llm_done is received, move the buffer content into the @messages list and reset the streaming state.
    # Example handle_info for streaming tokens
    def handle_info({:llm_delta, text}, %{assigns: %{streaming?: true}} = socket) do
      {:noreply,
       socket
       |> push_event("pc-chat-token", %{id: @stream_id, text: text})
       |> update(:buffer, &(&1 <> text))}
    end
    
    # Example handle_info for completion
    def handle_info(:llm_done, socket), do: {:noreply, commit(socket, socket.assigns.buffer)}
  5. Migrate Heroicons from V1 to V2

    main

    Petal Components now uses Heroicons V2 via heroicons_elixir. You have two options for handling this migration:

    Option 1: Continue using V1

    To keep using the old icon set, perform the following global replacements:

    • Replace PetalComponents.Heroicons with PetalComponents.HeroiconsV1
    • Replace Heroicons.Solid with HeroiconsV1.Solid
    • Replace Heroicons.Outline with HeroiconsV1.Outline
    1. Delete all references to PetalComponents.Heroicons.
    2. Update your HEEX templates to the new heroicons_elixir syntax.
      • For Solid icons, use the solid attribute: <Heroicons.icon_name solid class="" />
      • For Outline icons, no extra attribute is needed: <Heroicons.icon_name class="" />
    3. Note: Many icon names have changed in V2. Check the Heroicons V2 release notes for a mapping of name changes.

    Updating Icon Buttons

    Because dynamic rendering has changed, you must now pass the icon into the default slot of the icon_button component.

    <!-- Old way (V1) -->
    <Heroicons.Solid.home class="" />
    
    <!-- New way (V2) -->
    <Heroicons.home solid class="" />
    
    <!-- Icon Button Migration -->
    <!-- Old way -->
    <.icon_button to="/" icon={:trash} />
    
    <!-- New way -->
    <.icon_button to="/">
      <Heroicons.trash solid />
    </.icon_button>
  6. Configure Apache ECharts for <.chart>

    main

    The <.chart> component requires an external ECharts engine. It does not bundle the engine itself. You can provide it in one of two ways:

    1. Via CDN: Add a script tag to your root layout: <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>

    2. Via NPM:

      • Run npm i echarts.
      • In app.js, add: import * as echarts from "echarts"; window.echarts = echarts;.

    The PetalChart hook looks for window.echarts. If missing, the chart area will render empty and a warning will appear in the console. Note that <.sparkline> is pure SVG and does not require ECharts.

    <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
  7. Upgrade from v3.x to v4.0.0

    main

    Petal Components v4 removes Alpine.js in favor of Phoenix.LiveView.JS. To upgrade, you must register the bundled JS hooks, remove Alpine.js if it's no longer used elsewhere, and update your component calls to remove the deprecated js_lib attribute. If you use the new Chat components with markdown, you must also add the :mdex dependency.

    ### 1. Register the bundled JS hooks
    
    ```diff
    + import PetalComponents from "../../deps/petal_components/assets/js/petal_components"
    
      const liveSocket = new LiveSocket("/live", Socket, {
        params: { _csrf_token: csrfToken },
    -   hooks: MyHooks,
    +   hooks: { ...MyHooks, ...PetalComponents },
      })

    2. Remove Alpine.js

    If nothing else in your app uses it, drop alpinejs from assets/package.json and remove the Alpine setup from app.js (including import Alpine, window.Alpine = Alpine, Alpine.start(), and the dom: { onBeforeElUpdated } block).

    3. Remove the js_lib attribute

    Delete js_lib from <.dropdown>, <.accordion>, and vertical menu components:

    - <.dropdown label="Menu" js_lib="live_view_js">
    + <.dropdown label="Menu">

    4. Add MDEx for Chat markdown

    Add :mdex to your mix.exs if using <.markdown> or Chat.to_html/1:

      {:petal_components, "~> 4.0"},
    + {:mdex, "~> 0.12"},
  8. Install and setup the Chat component family

    main

    The PetalComponents.Chat family is opt-in and not included in the default use PetalComponents macro to avoid naming collisions with your own helpers. You must import it explicitly using an alias.

    Prerequisites

    1. Ensure petal_components is installed.
    2. Configure the JS hooks in your app.js to enable the PetalChatStream hook, which is required for streaming text:
    import PetalComponents from "../../deps/petal_components/assets/js/petal_components"
    const liveSocket = new LiveSocket("/live", Socket, { hooks: { ...PetelComponents } })
    1. (Optional) For rendered markdown replies, add {:mdex, "~> 0.12"} to your dependencies.
    alias PetalComponents.Chat
    # then use <Chat.conversation>, <Chat.chat_message>, <Chat.markdown>, etc.
  9. Install Petal Components manually

    main

    If you prefer to install the components manually, follow these three steps:

    1. Add dependencies to mix.exs

    Add petal_components to your dependencies. If you plan to use chat markdown components (<Chat.markdown> or <Chat.rich_text>), you must also include mdex.

    2. Configure Tailwind CSS

    In assets/css/app.css, add the @source directive to include Petal's files and @import the default Petal CSS.

    3. Update your Web module

    Add use PetalComponents to your html/0 function in your web module (e.g., MyAppWeb).

    4. Register JS Hooks

    Register the bundled JS hooks in assets/js/app.js. This is required for password/copyable/clearable inputs and chat components. The rest of the library uses CSS and LiveView.JS.

    After these steps, run mix deps.get and mix compile.

    # mix.exs
    def deps do
      [
        {:petal_components, "~> 4.0"},
        # optional — only needed for the chat markdown components (<Chat.markdown>, <Chat.rich_text>)
        {:mdex, "~> 0.12"}
      ]
    end
    /* assets/css/app.css */
    @import "tailwindcss";
    @source "../deps/petal_components/**/*.*ex";
    @import "../deps/petal_components/assets/default.css";
    # MyAppWeb module
    def html do
      quote do
        use PetalComponents
        # ... your other imports
      end
    end
    // assets/js/app.js
    import PetalComponents from "../../deps/petal_components/assets/js/petal_components"
    
    const liveSocket = new LiveSocket("/live", Socket, {
      params: { _csrf_token: csrfToken },
      hooks: { ...PetalComponents }, // merge with your own hooks if you have any,
    })
  10. Register Petal Components JS hooks

    main

    In v4.0.0, Petal Components ships its own JS hooks for interactivity (password toggles, copyable/clearable inputs, accordions, and Chat components). You must import and merge these hooks into your LiveSocket configuration in assets/js/app.js. Note that overlay components like dropdowns, menus, and modals use Phoenix.LiveView.JS and do not require these hooks.

    import PetalComponents from "../../deps/petal_components/assets/js/petal_components"
    
    const liveSocket = new LiveSocket("/live", Socket, {
      params: { _csrf_token: csrfToken },
      hooks: { ...MyHooks, ...PetalComponents },
    })
  11. Install petal_components in a Phoenix project

    main

    Follow these steps to integrate petal_components into your Phoenix application. These steps are idempotent.

    1. Add the dependency

    Add {:petal_components, "~> 4.0"} to your mix.exs file.

    Optional: If you need chat components (<Chat.conversation>, <Chat.markdown>, etc.), also add {:mdex, "~> 0.12"}. Note that the Chat family requires an explicit alias PetalComponents.Chat and is not included in the default use PetalComponents import.

    2. Fetch dependencies

    Run mix deps.get in your terminal.

    3. Configure Tailwind CSS

    Update assets/css/app.css to include the component source and default styles. This requires Tailwind v4. If you are on Tailwind v3, you must upgrade or pin to petal_components ~> 1.0.

    @import "tailwindcss";
    @source "../deps/petal_components/**/*.*ex";
    @source not "../deps/petal_components/lib/petal_components/showcase";
    @import "../deps/petal_components/assets/default.css";

    4. Import components in your web module

    In lib/<your_app>_web.ex (or apps/<your_app>_web/lib/<your_app>_web.ex for umbrella apps), add use PetalComponents inside the quote block of your def html function.

    def html do
      quote do
        use Phoenix.Component
        use PetalComponents
        # ... existing imports
      end
    end

    5. Register JS hooks

    Open assets/js/app.js and merge the PetalComponents hooks into your LiveSocket configuration to enable interactive components (like password inputs, navigation menus, and the command palette).

    import PetalComponents from "../../deps/petal_components/assets/js/petal_components"
    
    const liveSocket = new LiveSocket("/live", Socket, {
      params: { _csrf_token: csrfToken },
      hooks: { ...PetalComponents }, // merge with existing hooks if necessary
    })

    6. Verify

    Run mix compile and test by adding <.button>Hello</.button> to a template.

    {:petal_components, "~> 4.0"}
  12. Install Petal Components via MCP (Recommended)

    main

    The recommended way to install Petal Components is using the companion MCP server. This allows AI coding tools (like Claude Code, Cursor, or Windsurf) to automatically configure your project, including updating mix.exs, patching CSS, and adding necessary imports.

    1. Install the MCP server once in your terminal:
    claude mcp add petal --transport http https://mcp.petal.build/mcp
    1. In your Phoenix project, tell your AI agent: "install petal_components".

    The agent will handle the installation, including running mix deps.get, patching assets/css/app.css, and adding use PetalComponents to your web module.

    claude mcp add petal --transport http https://mcp.petal.build/mcp