Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: layout component #19

Open
lessless opened this issue May 10, 2024 · 1 comment
Open

Proposal: layout component #19

lessless opened this issue May 10, 2024 · 1 comment

Comments

@lessless
Copy link

lessless commented May 10, 2024

Hi,

The layout component should implement at least the stack and the sidebar layouts, ref: https://every-layout.dev
The cover would be an excellent addition.

Petal's approach to structure is a decent start https://docs.petal.build/petal-pro-documentation/fundamentals/layouts-and-menus

Here is what I did to port the sidebar layout from the https://github.com/themesberg/flowbite-astro-admin-dashboard/tree/main (it also implements the stack) into one of my experiments.

  1. The root layout didn't change, but I removed all classes from the main tag
  <.flash_group flash={@flash} />
  <%= @inner_content %>
</main>
  1. Added components/astro.ex
defmodule SesameWeb.AstroComponents do
  use Phoenix.Component
  import SesameWeb.AstroComponents.NavBarSidebar
  import SesameWeb.AstroComponents.Sidebar

  attr :main_menu_items, :list
  attr :user_menu_items, :list
  attr :user, :map
  attr :current_page, :atom, required: true
  slot(:inner_block)

  def layout_sidebar(assigns) do
    assigns =
      assigns
      |> assign_new(:main_menu_items, fn -> SesameWeb.Menus.main_menu_items(assigns[:user]) end)
      |> assign_new(:user_menu_items, fn -> SesameWeb.Menus.user_menu_items(assigns[:user]) end)

    ~H"""
    <.navbar_sidebar user={@user} user_menu_items={@user_menu_items} />
    <.sidebar main_menu_items={@main_menu_items} current_page={@current_page} />

    <div class="flex pt-16 overflow-hidden  dark:bg-gray-900">
      <div
        id="main-content"
        class="relative w-full h-full overflow-y-auto  lg:ml-64 dark:bg-gray-900 min-h-[calc(100vh-64px)]"
      >
        <%= render_slot(@inner_block) %>
      </div>
    </div>
    """
  end
end

I don't remember much now, but astro/sidebar.ex and astro/navbar_sidebar.ex basically loop over the menu items and render them accordingly:

defmodule SesameWeb.AstroComponents.Sidebar do
  use Phoenix.Component
  use SesameWeb, :verified_routes
  import SesameWeb.CoreComponents, only: [icon: 1]

  attr :main_menu_items, :list, required: true
  attr :current_page, :atom, required: true

  def sidebar(assigns) do
    ~H"""
    <aside
      id="sidebar"
      class="fixed top-0 left-0 z-20 flex flex-col flex-shrink-0 hidden w-64 h-full pt-16 font-normal duration-75 lg:flex transition-width"
      aria-label="Sidebar"
      phx-hook="SideBar"
    >
      <div class="relative flex flex-col flex-1 min-h-0 pt-0 bg-white border-r border-gray-200 dark:bg-gray-800 dark:border-gray-700">
        <div class="flex flex-col flex-1 pt-5 pb-28 overflow-y-auto scrollbar scrollbar-w-2 scrollbar-thumb-rounded-[0.1667rem] scrollbar-thumb-slate-200 scrollbar-track-gray-400 dark:scrollbar-thumb-slate-900 dark:scrollbar-track-gray-800">
          <div class="flex-1 px-3 space-y-1 bg-white divide-y divide-gray-200 dark:bg-gray-800 dark:divide-gray-700">
            <ul class="pb-2 space-y-2">
              <%= for menu_item <- @main_menu_items do %>
                <li>
                  <.link class={main_menu_item_class(@current_page, menu_item.name)} navigate={menu_item.path}>
                    <%= if is_binary(menu_item.icon) do %>
                      <.icon
                        name={"hero-#{menu_item.icon}"}
                        class="w-6 h-6 text-gray-500 transition duration-75 group-hover:text-gray-900 dark:text-gray-400 dark:group-hover:text-white"
                      />
                    <% end %>

                    <span class="ml-3" sidebar-toggle-item><%= menu_item.label %></span>
                  </.link>
                </li>
              <% end %>
            </ul>
          </div>
        </div>
      </div>
    </aside>

    <div class="fixed inset-0 z-10 hidden bg-gray-900/50 dark:bg-gray-900/90" id="sidebarBackdrop"></div>
    """
  end

  def main_menu_item_class(current_page, current_page) do
    main_menu_item_class_base_class() <> " bg-gray-100 dark:bg-gray-700"
  end

  def main_menu_item_class(_current_page, _page) do
    main_menu_item_class_base_class()
  end

  def main_menu_item_class_base_class() do
    "flex items-center p-2 text-base text-gray-900 rounded-lg hover:bg-gray-100 group dark:text-gray-200 dark:hover:bg-gray-700"
  end
end
defmodule SesameWeb.AstroComponents.NavBarSidebar do
  use Phoenix.Component
  import SesameWeb.CoreComponents, only: [icon: 1]
  import PetalComponents.Dropdown, only: [dropdown_menu_item: 1]

  attr :user, :map, required: true
  attr :user_menu_items, :list, required: true

  def navbar_sidebar(assigns) do
    ~H"""
    <nav class="fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
      <div class="px-3 py-3 lg:px-5 lg:pl-3">
        <div class="flex items-center justify-between">
          <div class="flex items-center justify-start">
            <button
              id="toggleSidebarMobile"
              aria-expanded="true"
              aria-controls="sidebar"
              class="p-2 text-gray-600 rounded cursor-pointer lg:hidden hover:text-gray-900 hover:bg-gray-100 focus:bg-gray-100 dark:focus:bg-gray-700 focus:ring-2 focus:ring-gray-100 dark:focus:ring-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
            >
              <.icon id="toggleSidebarMobileHamburger" class="w-6 h-6" name="hero-bars-3" />
              <.icon id="toggleSidebarMobileClose" class="hidden w-6 h-6" name="hero-x-mark" />
            </button>
            <a href="/" class="flex ml-2 md:mr-24">
              <%!-- <img src="images/logo.svg" class="h-8 mr-3" alt="FlowBite Logo" /> --%>
              <span class="self-center text-xl  font-black sm:text-2xl whitespace-nowrap dark:text-white">
                🏔️Sesame
              </span>
            </a>

            <%!-- <SearchInput /> --%>
          </div>

          <div class="flex items-center">
            <.notifications />
            <.apps />

            <%!-- <ColorModeSwitcher /> --%>
            <!-- Profile -->
            <.user_menu user={@user} user_menu_items={@user_menu_items} />
          </div>
        </div>
      </div>
    </nav>
    """
  end

  defp user_menu(assigns) do
    ~H"""
    <div class="flex items-center ml-3">
      <div>
        <button
          type="button"
          class="flex text-sm bg-gray-800 rounded-full focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600"
          id="user-menu-button-2"
          aria-expanded="false"
          data-dropdown-toggle="dropdown-2"
        >
          <span class="sr-only">Open user menu</span>
          <%!-- <img
            class="w-8 h-8 rounded-full"
            src="https://flowbite.com/docs/images/people/profile-picture-5.jpg"
            alt="user photo"
          /> --%>

          <.icon class="w-8 h-8 bg-stone-50" name="hero-user-circle-solid" />
        </button>
      </div>
      <!-- Dropdown menu -->
      <div
        class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:bg-gray-700 dark:divide-gray-600"
        id="dropdown-2"
      >
        <div class="px-4 py-3" role="none">
          <p class="text-sm text-gray-900 dark:text-white" role="none">
            <%= @user.name %>
          </p>
          <p class="text-sm font-medium text-gray-900 truncate dark:text-gray-300" role="none">
            <%= @user.email %>
          </p>
        </div>
        <ul class="py-1" role="none">
   
          <%= for menu_item <-  @user_menu_items do %>
            <li>
              <.dropdown_menu_item
                link_type={if menu_item[:method], do: "a", else: "live_redirect"}
                method={if menu_item[:method], do: menu_item[:method], else: nil}
                to={menu_item.path}
                class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white"
              >
                <%= if is_binary(menu_item.icon) do %>
                  <.icon name={"hero-#{menu_item.icon}"} class="w-5 h-5 text-gray-500 dark:text-gray-400" />
                <% end %>

                <%= menu_item.label %>
              </.dropdown_menu_item>
            </li>
          <% end %>
        </ul>
      </div>
    </div>
    """
  end
end

Then, I was able to choose which layout I wanted to use on a LiveView basis:

<.layout_sidebar user={@current_user} current_page={:participants}>
   Content
</.layout_sidebar>

The SesameWeb.Menus contains definitions of menus as described in Petal docs https://docs.petal.build/petal-pro-documentation/fundamentals/layouts-and-menus#menus

defmodule SesameWeb.Menus do

  use SesameWeb, :verified_routes

  # Public menu 
  def public_menu_items(_user \\ nil),
    do: [
      %{label: "Features", path: "/#features"},
    ]

  # Signed out main menu
  def main_menu_items(nil) do 
    []
  end

  # Signed in main menu
  def main_menu_items(current_user) do
     build_menu([:my_notifications, :participants], current_user)
  end
end

Hope this helps!

@chrisgreg
Copy link
Owner

@lessless this is excellent, would you like to raise a PR to implement this in Bloom?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants