Subscribe to access all episodes. View plans →

#190: Petal Components and Phoenix 1.7

Published May 13, 2024

Petal Components

Follow along with the episode starter on GitHub


Here we have a table that has a couple different Widgets for sale. Our application is using Phoenix 1.7 and the table is styled with some pretty basic TailwindCSS, which ships by default with new Phoenix 1.7 applications.

As we continue to build out our application, we don’t want to spend a lot of time designing and implementing components. And while Phoenix 1.7 does come with a CoreComponents module, which helps you get started with components for things like modals, tables, and forms, let’s bring in a more comprehensive solution with Petal Components. Petal Components is a set of open source, HEEX components that make it easy to build beautiful web applications. They have great documentation and, while we cover them in episode 145 - that was with a Phoenix 1.6 application, which didn’t have the CoreComponents module.

Let’s get started. The first thing we’ll need to do is go to Hex and copy the petal_components config. We’ll then open our proejct’s Mixfile and add it to our list of dependencies.

mix.exs

...

defp deps do
  [
    ...
    {:petal_components, "~> 1.9"},
    ...
  ]
end

...

Then we’ll go to the command line and get our new dependency with mix deps.get.

$ mix deps.get
...
New:
  petal_components 1.9.2

Great, now we need to configure our application to use petal_components. Let’s open our tailwind.config.js file and we need to configure the path to our petal components folder in the content section, so let’s do that.

Then let’s update the color palette for our application. You can choose different colors, but I’ll paste in a few different ones from TailwindCSS’s default color palette. To use these we’ll need to require them above.

assets/tailwind.config.js

const colors = require("tailwindcss/colors");
...

content: [

  ...

  "../deps/petal_components/**/*.*ex",

],

...

  colors: {
    primary: colors.blue,
    secondary: colors.pink,
    success: colors.green,
    danger: colors.red,
    warning: colors.yellow,
    info: colors.sky,
    gray: colors.gray,
  }
...

Great, now let’s open our app.css and import the petal components stylesheet.

assets/css/app.css

...

@import "../../deps/petal_components/assets/default.css";

...

Now we need to import the components. Let’s open the teacher_web.ex module. And in the html_helpers function we’ll add use PetalComponents.

lib/teacher_web.ex

...

defp html_helpers do
  quote do
    ...

    use PetalComponents
  end

end

...

With that let’s start up our server.

$ mix phx.server
...

And when we do we get an error. This is because there’s a naming conflict - both Petal Components and the Phoenix generated CoreComponents module implement components with the same name.

There are a couple ways to fix this. We could delete the core_components.ex module here, but then generators like mix phx.gen.live won’t work properly. We could also go back to our teacher_web.ex module and if we don’t want to use all of the Petal Components we could update this it to only include the ones we want.

lib/teacher_web.ex

...

defp html_helpers do
  quote do
    ...

    import PetalComponents.{Accordion, Icon}
  end

end

...

While this works, I’d like our application here to have the option of using any of the Petal Components or the CoreComponents. To do that let’s namespace our Petal Component modules. We’ll define a module PC, and then by using defdelegate we can essentially forward functions in our PC module to their corresponding PetalComponent function.

Here we’re delegating the accordion function in our PC module to the accordion function in the PetalComponents.Accordion module. I’ll go ahead and paste in the rest of the components. Great, now we can use any of the Petal Components via our PC module.

lib/teacher_web.ex

...

defp html_helpers do
  quote do
    ...

    defmodule PC do
      defdelegate accordion(assigns), to: PetalComponents.Accordion
      defdelegate alert(assigns), to: PetalComponents.Alert
      defdelegate avatar(assigns), to: PetalComponents.Avatar
      defdelegate badge(assigns), to: PetalComponents.Badge
      defdelegate breadcrumbs(assigns), to: PetalComponents.Breadcrumbs
      defdelegate button(assigns), to: PetalComponents.Button
      defdelegate icon_button(assigns), to: PetalComponents.Button
      defdelegate card(assigns), to: PetalComponents.Card
      defdelegate container(assigns), to: PetalComponents.Container
      defdelegate dropdown(assigns), to: PetalComponents.Dropdown
      defdelegate form_label(assigns), to: PetalComponents.Form
      defdelegate field(assigns), to: PetalComponents.Field
      defdelegate icon(assigns), to: PetalComponents.Icon
      defdelegate input(assigns), to: PetalComponents.Input
      defdelegate a(assigns), to: PetalComponents.Link
      defdelegate spinner(assigns), to: PetalComponents.Loading
      defdelegate modal(assigns), to: PetalComponents.Modal
      defdelegate pagination(assigns), to: PetalComponents.Pagination
      defdelegate progress(assigns), to: PetalComponents.Progress
      defdelegate rating(assigns), to: PetalComponents.Rating
      defdelegate slide_over(assigns), to: PetalComponents.SlideOver
      defdelegate table(assigns), to: PetalComponents.Table
      defdelegate td(assigns), to: PetalComponents.Table
      defdelegate tr(assigns), to: PetalComponents.Table
      defdelegate th(assigns), to: PetalComponents.Table
      defdelegate tabs(assigns), to: PetalComponents.Tabs
      defdelegate h1(assigns), to: PetalComponents.Typography
      defdelegate h2(assigns), to: PetalComponents.Typography
      defdelegate h3(assigns), to: PetalComponents.Typography
      defdelegate h4(assigns), to: PetalComponents.Typography
      defdelegate h5(assigns), to: PetalComponents.Typography
      defdelegate p(assigns), to: PetalComponents.Typography
      defdelegate prose(assigns), to: PetalComponents.Typography
      defdelegate ol(assigns), to: PetalComponents.Typography
      defdelegate ul(assigns), to: PetalComponents.Typography
    end
  end
end

...

All we need to do now is update our Widget table to use the Petal Components. Let’s open the widget_live/index.html.heex template and first let’s update our h1 to use a component. If we hadn’t used the PC module namespace, our component would look like this <.h1>...</.h1>. To use the component we’ll just need to prefix it with the PC module namespace like this: <PC.h1></PC.h1>. Then let’s do the same for our table.

Now for our link here, let’s actually change this to be a button component. If we look at the Petal Components docs, we see the different options we can use with the <.button> component.

We’ll update this to be a button component. Let’s use a link_type="live_redirect" and update navigate to to.

Template path: lib/teacher_web/live/widget_live/index.html.heex

<PC.h1>Widget Shop</PC.h1>
<PC.table rows={@widgets} id="widgets-table">
  <:col :let={widget} label="Name">
    <%= widget.name %>
  </:col>
  <:col :let={widget}>
    <PC.button link_type="live_redirect" to={~p"/widgets/#{widget}"}>Show</PC.button>
  </:col>
</PC.table>

With those changes done let’s start up the server.

$ mix phx.server
...

Great! There aren’t any errors, so let’s go back to the browser. And perfect - we see our page is now being styled with Petal Components and our button component works. Our application is now configured to use Petal Components.

© 2024 HEXMONSTER LLC