Phoenix Blog Post: Creating a Frontend Style Guide with Phoenix Components

Our take on how to build a frontend style guide with Phoenix Components, Atomic Design and plain CSS, with focus on reusability and code organization.

10 Likes

This blog post is worth reading. I wonder how your guys deal with live components and where they are placed in the atomic design?

5 Likes

Thanks @zhangzhen!

I wonder how your guys deal with live components and where they are placed in the atomic design?

Great question - we don’t use live components yet. Assuming they are more complex than the “static” ones, I would create them as organisms with the prefix live_.

3 Likes

Function components are implemented in the form of functions that take the assigns param, which means one module defines multiple function components. Live components are implemented in the form of modules, which means one module defines only one live component. Can you give some details about how you create them as organisms? It would be better if you could give me one example.

1 Like

I’m glad this resurfaced.

I loved the defdelegate approach to avoid cluttering up the Organism.

Will implement this in my own project, that is just starting out.


Have you considered using Storybook?

I am bit scared of business logic seeping into the components over time, so I was thinking of separating components as well as adding storybook to avoid those pitfalls.

Plus whenever we have to change something in a component, we won’t have to jump through our UI to make it happen.

2 Likes

I really did consider phoenix_storybook. I heard this good project from thinking elixir podcast and read its documentation. Does it export its results as core_components.ex, so i can easily replace the default one with this? By the way, currently I’m using flowbite pro to build my frontend.

Sorry for the delay. I’ve changed my mind about the first answer.

I would probably wrap a functional organism in a live component, and all data required for that organism would come from the live wrapper. I believe you will have access to the CoreComponents from a live_component by default, so the following should work:

defmodule AppWeb.LiveComponents.Form do
  use AppWeb :live_component

  def render(assigns) do
    ~H"""
    <.functional_organism arg={@arg} arg2={@arg2} />
    """
  end
end

Depending on the number of live components, you could even have a file to wrap all of them the same way core_components works.

Thanks @derpycoder!

I’m not a big fan of storybook (the react one), so I didn’t consider using phoenix_storybook. I prefere https://patternlab.io/ instead. Thanks for the suggestion, maybe I’ll test it in the future and try to connect everything.

2 Likes

The phoenix version of storybook will become part of the Phoenix itself, eventually.

Well, I hope it does.

If not part of it, then at least reach v1, so that I can use it to my heart’s content.

1 Like

Thank you for your reply.

Would you please give me an example that shows how you do that?

What do you use to showcase the components?

  • A custom tool?
  • A demo Phoenix project?
    • if so, in the same repo as the component library or in a separate one?
  • Nothing
  • Something else

I tend to don’t like storybook as well.
What I love instead is to have unit tests and assert the produced html (this may be overkill, but I find myself refactoring components very often…)

Cheers

It depends. I like to keep everything in the same project unless I need to use the components in multiple, different projects. If this is the case, I would have a different Phoenix project for it, showcasing them using routes.

I know that having everything together with the main app is not the most scalable solution, but it works (if the components are organized, of course - thanks to the atomic design, I can manage them in a sane way). Listing all the components along with their docs in a simple route worked well in most of my use cases.

I want to explore how far I can use ex_docs (or LiveBook, idk) for documenting the components. Depending on the results, I’ll write a follow-up post.

@mssantosdev I think using defdelegate to wrap live components in one file doesn’t work because each live component has one render function. To use a live component one has to specify its module. I don’t know if using alias can achieve this wrapping?

You are right. I didn’t test if defdelegate would work with live_components when I answered you. It was a bad suggestion anyways, sorry.

I do think live_components can access atoms, molecules, and organisms, but they don’t require a similar code organization.

/live
  /live_components
    contact_form.ex
    header.ex
    drawer.ex
# contact_form
# You need to make sure this module can use core_components (I think it can by default, but need to verify)
defmodule AppWeb.LiveComponents.ContactForm do
  # If you generated an app with mix phx.new --live,
  # the line below would be: use MyAppWeb, :live_component
  use Phoenix.LiveComponent

  # The can access atoms, molecules, and organisms the same way liveviews can
  def render(assigns) do
    ~H"""
    <.my_organism />
    """
  end
end
<.live_component module={AppWeb.LiveComponents.ContactForm} id="contact_form" />

What do you think?

1 Like

This makes sense.

1 Like