lib/supabase_surface/components/tabs.ex

defmodule SupabaseSurface.Components.Tabs.AddOnBefore do
  use Surface.Component, slot: "add_on_before"

  slot default, required: true

  def render(assigns) do
    ~F"""
    <#slot />
    """
  end
end

defmodule SupabaseSurface.Components.Tab do
  use Surface.Component, slot: "tabs"

  prop label, :string, required: true

  slot default, required: true

  def render(assigns) do
    ~F"""
    <#slot />
    """
  end
end

defmodule SupabaseSurface.Components.Tabs do
  use Surface.LiveComponent

  alias SupabaseSurface.Components.Button
  alias SupabaseSurface.Components.Divider
  alias SupabaseSurface.Components.Space
  alias SupabaseSurface.Components.Tab

  prop type, :string, values: ["pills", "underlined", "cards"], default: "pills"
  prop size, :string, values: ["tiny", "small", "medium", "large", "xlarge"], default: "tiny"
  prop block, :boolean, default: false
  data active_tab, :string
  data animation, :string, default: ""
  slot tabs
  slot add_on_before

  @impl true
  def update(assigns, socket) do
    active_tab = get_active_tab(assigns)
    {:ok, assign(socket, assigns) |> assign(active: active_tab)}
  end

  @impl true
  def render(assigns) do
    underlined = assigns.type == "underlined"

    active_tab = get_active_tab(assigns)

    ~F"""
    <Space direction="vertical" size={4}
      opts={"x-data": "{ activeTab: '#{active_tab}' }"}
    >
      <div id={@id} role="tablist" aria-label={@id}
      >
        <Space class="sbui-tab-bar-container" size={0}>
          <Space size={if underlined, do: 6, else: 3} class="sbui-tab-bar-inner-container">
            <#slot name="add_on_before" />
            <Button
             :for={{tab, _index} <- Enum.with_index(@tabs)}
             click="tab_click"
             block={@block}
             size={@size}
             type={button_type(active_tab, tab.label, underlined)}
             opts={role: "tab", "phx-value-label": tab.label}
             class={button_classes(active_tab, tab.label, underlined)}
            >{tab.label}
            </Button>
          </Space>
        </Space>
        {#if underlined}
        <Divider />
        {/if}
      </div>
      <div
        :for={{tab, index} <- Enum.with_index(@tabs)}
        :show={active_tab == tab.label}
        id={tab.label}
        role="tabpanel"
        aria-labelledby={tab.label}
        class={@animation}
      >
        <#slot name="tabs" index={index} />
      </div>
    </Space>
    """
  end

  @impl true
  def handle_event(
        "tab_click",
        %{"label" => label},
        socket
      ) do
    animation = get_animation(socket.assigns, label)

    {:noreply, assign(socket, active: label, animation: animation)}
  end

  defp get_animation(assigns, next_label) do
    active_index = Enum.find_index(assigns.tabs, &(&1.label == assigns.active))
    next_index = Enum.find_index(assigns.tabs, &(&1.label == next_label))

    cond do
      active_index < next_index -> "slide-in-right"
      active_index > next_index -> "slide-in-left"
      true -> assigns.animation
    end
  end

  defp get_active_tab(%{active: active_tab}) when not is_nil(active_tab), do: active_tab

  defp get_active_tab(%{tabs: tabs}) do
    first_tab = List.first(tabs)
    first_tab.label
  end

  defp button_classes(active_tab, active_tab, true) do
    "sbui-tab-button-underline sbui-tab-button-underline--active"
  end

  defp button_classes(_, _, true) do
    "sbui-tab-button-underline"
  end

  defp button_classes(_, _, _), do: ""

  defp button_type(active_tab, active_tab, false), do: "primary"
  defp button_type(_, _, _), do: "text"
end

defmodule SupabaseSurface.Catalogue.Tabs.Example do
  use Surface.Catalogue.Example,
    catalogue: SupabaseSurface.Catalogue,
    subject: SupabaseSurface.Components.Tabs,
    height: "200px",
    title: "Tabs"

  alias SupabaseSurface.Components.Tabs
  alias SupabaseSurface.Components.Tab

  def render(assigns) do
    ~F"""
    <Tabs id="example-tabs">
      <Tab label="tab 1">Nunc pellentesque gravida ultricies. Integer non mollis lorem. Morbi vel hendrerit nibh, in egestas magna.</Tab>
      <Tab label="tab 2">Etiam varius lectus lectus, nec pharetra lacus commodo quis.</Tab>
    </Tabs>
    """
  end
end

defmodule SupabaseSurface.Catalogue.Tabs.Underlined do
  @moduledoc """
  TODO: investigate how to correctly apply styling, currently "sbui-tab-button-underline" and
  "sbui-tab-button-underline--active" are overridden by other styles
  """
  use Surface.Catalogue.Example,
    catalogue: SupabaseSurface.Catalogue,
    subject: SupabaseSurface.Components.Tabs,
    height: "200px",
    title: "Underlined"

  alias SupabaseSurface.Components.Tabs
  alias SupabaseSurface.Components.Tab

  def render(assigns) do
    ~F"""
    <Tabs id="example-tabs" type="underlined">
      <Tab label="tab 1">tab 1</Tab>
      <Tab label="tab 2">tab 2</Tab>
    </Tabs>
    """
  end
end

defmodule SupabaseSurface.Catalogue.Tabs.Block do
  use Surface.Catalogue.Example,
    catalogue: SupabaseSurface.Catalogue,
    subject: SupabaseSurface.Components.Tabs,
    height: "200px",
    title: "Block"

  alias SupabaseSurface.Components.Tabs
  alias SupabaseSurface.Components.Tab

  def render(assigns) do
    ~F"""
    <Tabs id="example-tabs" block>
      <Tab label="tab 1">Nunc pellentesque gravida ultricies. Integer non mollis lorem. Morbi vel hendrerit nibh, in egestas magna.</Tab>
      <Tab label="tab 2">Etiam varius lectus lectus, nec pharetra lacus commodo quis.</Tab>
    </Tabs>
    """
  end
end

defmodule SupabaseSurface.Catalogue.Tabs.ExampleAddOnBefore do
  use Surface.Catalogue.Example,
    catalogue: SupabaseSurface.Catalogue,
    subject: SupabaseSurface.Components.Tabs,
    height: "200px",
    title: "AddOnBefore"

  alias SupabaseSurface.Components.Button
  alias SupabaseSurface.Components.Tabs
  alias SupabaseSurface.Components.Tab

  def render(assigns) do
    ~F"""
    <Tabs id="example-tabs">
      <Tabs.AddOnBefore><Button type="outline">Action Button</Button></Tabs.AddOnBefore>
      <Tab label="tab 1">Nunc pellentesque gravida ultricies. Integer non mollis lorem. Morbi vel hendrerit nibh, in egestas magna.</Tab>
      <Tab label="tab 2">Etiam varius lectus lectus, nec pharetra lacus commodo quis.</Tab>
    </Tabs>
    """
  end
end