Skip to main content

lib/attached_dashboard/web/components/layouts/sidebar.ex

defmodule AttachedDashboard.Web.Components.Layouts.Sidebar do
  @moduledoc false

  use AttachedDashboard.Web, :html

  alias AttachedDashboard.Web.Components.Core.Icon
  alias AttachedDashboard.Web.PageMeta

  attr :prefix, :string, required: true
  attr :page_meta, PageMeta, required: true
  attr :backlink, :string, default: nil

  def dashboard(assigns) do
    ~H"""
    <nav class="w-64 shrink-0 h-full flex flex-col pt-6 pb-5 px-3 bg-base-100 border border-base-300 rounded-lg shadow-sm">
      <div class="flex items-center justify-between px-2 mb-4">
        <.link
          :if={@backlink}
          href={@backlink}
          class="flex items-center gap-1.5 text-base font-bold text-primary tracking-tight hover:opacity-75 transition-opacity"
          title="Back to app"
        >
          <Icon.hero name={:arrow_left} class="w-4 h-4" /> Attached Dashboard
        </.link>
        <span :if={!@backlink} class="text-base font-bold text-primary tracking-tight">Attached Dashboard</span>
        <button
          class="sm:hidden p-1 rounded text-base-content/40 hover:text-base-content hover:bg-base-200"
          aria-label="Close menu"
          phx-click={
            Phoenix.LiveView.JS.hide(to: "#sidebar-backdrop")
            |> Phoenix.LiveView.JS.add_class("-translate-x-full", to: "#sidebar")
          }
        >
          <svg
            class="w-4 h-4"
            aria-hidden="true"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
            stroke-width="1.5"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
          </svg>
        </button>
      </div>
      <div class="border-t border-base-300 mb-4" />
      <div class="flex-1 flex flex-col">
        <.nav_items page_meta={@page_meta}>
          <:item label="Overview" path={dashboard_path(@prefix, ~p"/")} icon={:squares_2x2} exact />
        </.nav_items>

        <.nav_items page_meta={@page_meta} group="Viewer">
          <:item label="Originals" path={dashboard_path(@prefix, ~p"/originals")} icon={:document} />
          <:item label="Variants" path={dashboard_path(@prefix, ~p"/variants")} icon={:photo} />
          <:item label="Owners" path={dashboard_path(@prefix, ~p"/owners")} icon={:users} />
        </.nav_items>

        <.nav_items page_meta={@page_meta} group="Cleanup">
          <:item label="Orphans" path={dashboard_path(@prefix, ~p"/orphans")} icon={:exclamation_triangle} />
        </.nav_items>

        <.nav_items page_meta={@page_meta} group="System">
          <:item label="Processors" path={dashboard_path(@prefix, ~p"/processors")} icon={:cog_6_tooth} />
        </.nav_items>
      </div>
      <.theme_toggle />
    </nav>
    """
  end

  attr :page_meta, PageMeta, required: true
  attr :group, :string, default: nil

  slot :item, required: true do
    attr :label, :string, required: true
    attr :path, :string, required: true
    attr :icon, :atom
    attr :exact, :boolean
  end

  defp nav_items(assigns) do
    ~H"""
    <div>
      <div
        :if={@group}
        class="px-3 mt-3 mb-2 text-xs font-semibold uppercase tracking-wider text-base-content/40"
      >
        {@group}
      </div>
      <ul>
        <li :for={item <- @item}>
          <.link
            navigate={item.path}
            class={[
              "flex items-center gap-2.5 px-3 py-2 rounded-md text-sm font-medium transition-colors",
              if(is_active?(@page_meta, item.path, Map.get(item, :exact, false)),
                do: "bg-primary/10 text-primary",
                else: "text-base-content/60 hover:bg-base-200 hover:text-base-content"
              )
            ]}
          >
            <Icon.hero :if={Map.get(item, :icon)} name={item.icon} class="w-4 h-4 shrink-0" />
            {item.label}
          </.link>
        </li>
      </ul>
    </div>
    """
  end

  defp theme_toggle(assigns) do
    ~H"""
    <div class="relative flex flex-row items-center bg-base-200 rounded-full mt-4 text-xs">
      <div class="absolute w-1/3 h-full rounded-full bg-base-100 shadow-sm left-0 in-data-[theme=light]:left-1/3 in-data-[theme=dark]:left-2/3 transition-[left]" />
      <button
        class="flex-1 py-1.5 text-center cursor-pointer relative z-10 text-base-content/60 hover:text-base-content transition-colors"
        phx-click={Phoenix.LiveView.JS.dispatch("phx:set-theme")}
        data-phx-theme="system"
      >
        System
      </button>
      <button
        class="flex-1 py-1.5 text-center cursor-pointer relative z-10 text-base-content/60 hover:text-base-content transition-colors"
        phx-click={Phoenix.LiveView.JS.dispatch("phx:set-theme")}
        data-phx-theme="light"
      >
        Light
      </button>
      <button
        class="flex-1 py-1.5 text-center cursor-pointer relative z-10 text-base-content/60 hover:text-base-content transition-colors"
        phx-click={Phoenix.LiveView.JS.dispatch("phx:set-theme")}
        data-phx-theme="dark"
      >
        Dark
      </button>
    </div>
    """
  end

  defp is_active?(%PageMeta{path: current_path}, item_path, true),
    do: current_path == item_path

  defp is_active?(%PageMeta{path: current_path}, item_path, false),
    do: current_path == item_path or String.starts_with?(current_path, item_path <> "/")
end