lib/modal/scroll_layout.ex

defmodule FloUI.Modal.ScrollLayout do
  @moduledoc """
  ## Usage in SnapFramework

  Scrolling layout modal component. Great for displaying content within a modal that needs to scroll.

  data is a tuple in the form of ` elixir {label, component, component_data, component_opts}`

  style opts
    `width: :integer`
    `height: :integer`
    `frame_width: :integer`
    `frame_height: :integer`
    `content_height: :integer`
    `content_width: :integer`
    `show_check: :boolean`
    `show_close: :boolean`

  ``` elixir
  <%= graph font_size: 20 %>

  <%= component FloUI.Modal.ScrollLayout,
      {"Label", FloUI.SelectionList, {@selection_list, @selected}, [id: :project_list]},
      id: :scroll_layout,
      width: 500,
      height: 520,
      frame_width: 480,
      frame_height: 500,
      content_width: 480,
      content_height: @content_height,
      show_check: true,
      show_close: true
  %>
  ```
  """

  use Scenic.Component

  alias Scenic.Graph

  import Scenic.Primitives

  alias FloUI.Theme
  alias FloUI.Modal.Body
  alias FloUI.Modal.Header
  import FloUI.Scrollable.Components

  @graph Graph.build(font_size: 16)

  def validate(nil), do: :invalid_data
  def validate(data), do: {:ok, data}

  def init(scene, {title, cmp, data, cmp_opts}, opts) do
    scene =
      assign(scene,
        graph: @graph,
        title: title,
        component: cmp,
        component_data: data,
        component_opts: cmp_opts,
        width: opts[:width] || 500,
        height: opts[:height] || 500,
        frame_width: opts[:frame_width] || 480,
        frame_height: opts[:frame_height] || 500,
        content_width: opts[:content_width] || 500,
        content_height: opts[:content_height] || 500,
        show_check: opts[:show_check] || false,
        show_close: opts[:show_close] || false
      )
      |> render_layout

    {:ok, scene}
  end

  def handle_event({:click, :btn_check}, _from, scene) do
    send_parent_event(scene, :modal_done)
    {:noreply, scene}
  end

  def handle_event({:click, :btn_close}, _from, scene) do
    send_parent_event(scene, :modal_close)
    {:noreply, scene}
  end

  def handle_event(event, _, scene) do
    {:cont, event, scene}
  end

  defp render_layout(%{
    assigns: %{
      graph: graph,
      title: title,
      component: cmp,
      component_data: cmp_data,
      component_opts: cmp_opts,
      width: width,
      height: height,
      frame_width: frame_width,
      frame_height: frame_height,
      content_width: content_width,
      content_height: content_height,
      show_check: show_check,
      show_close: show_close
    }
  } = scene) do
    graph =
      graph
      |> Body.add_to_graph(nil, translate: {0, 50}, width: width, height: height)
      |> scrollable(
        %{
          frame: {frame_width, frame_height},
          content: %{x: 0, y: 10, width: content_width, height: content_height}
        },
        &cmp.add_to_graph(&1, cmp_data, cmp_opts),
        id: :scroll_box,
        translate: {0, 60},
        vertical_scroll_bar: [
          scroll_buttons: true,
          scroll_bar_theme: Scenic.Primitive.Style.Theme.preset(:dark),
          scroll_bar_thickness: 15
        ]
      )
      |> Header.add_to_graph(title, width: width, show_check: show_check, show_close: show_close)

    assign(scene, graph: graph)
    |> push_graph(graph)
  end
end