defmodule FloUI.Dropdown do
@moduledoc """
## Usage in SnapFramework
Dropdown component that scrolls. You can pass two separate themes as options. one for the dropdown, and one for the scroll bar.
Options
``` elixir
theme: theme,
scroll_bar: %{
show: true,
show_buttons: true,
theme: Scenic.Primitive.Style.Theme.preset(:dark),
thickness: 15
}
```
data is a tuple in the form of
``` elixir
{
[
{{"option 1", :option_1}, :option_1},
{{"option 2", :option_2}, :option_2},
{{"option 3", :option_3}, :option_3}
],
:option_1
}
```
Events emitted
`{:value_changed, id, value}`
``` elixir
<%= component FloUI.Dropdown,
{@items, @selected},
id: :dropdown,
theme: @theme,
scroll_bar: %{
show: true,
show_buttons: true,
theme: Scenic.Primitive.Style.Theme.preset(:dark),
thickness: 15
}
%>
```
"""
@default_height 50
@default_frame_height 300
@default_theme FloUI.Theme.preset(:base)
@default_scroll_bar %{
show: true,
show_buttons: true,
theme: Scenic.Primitive.Style.Theme.preset(:dark),
thickness: 15
}
alias FloUI.Dropdown.Items
use SnapFramework.Component,
name: :dropdown,
template: "lib/dropdown/dropdown.eex",
controller: FloUI.DropdownController,
assigns: [],
opts: []
defcomponent(:dropdown, :tuple)
use_effect [assigns: [open?: :any]], [
run: [:on_open_change]
]
use_effect [assigns: [selected_label: :any]], [
run: [:on_selected_change]
]
@impl true
def setup(%{assigns: %{data: {items, selected} = data, opts: opts}} = scene) do
width = get_width(data, opts)
frame_height = get_frame_height(data, opts)
content_height = get_content_height(items)
scroll_bar = opts[:scroll_bar] || @default_scroll_bar
show_vertical_scroll = content_height > frame_height and scroll_bar.show
assign(scene,
items: items,
selected_label: "",
selected_key: nil,
selected: selected,
open?: false,
button_width: if(show_vertical_scroll, do: width + 20, else: width),
button_height: opts[:height] || @default_height,
background_height: frame_height + 20,
frame_width: if(show_vertical_scroll, do: width, else: width),
frame_height: frame_height,
content_height: content_height,
scroll_bar: scroll_bar,
show_vertical_scroll: show_vertical_scroll,
theme: get_theme(opts)
)
end
@impl true
def bounds(data, opts) do
{0.0, 0.0, get_width(data, opts), opts[:height] || @default_height}
end
@impl true
def process_event({:value_changed, {{label, value}, key}}, _, scene) do
{:cont, {:value_changed, scene.assigns.opts[:id], value}, assign(scene, selected_label: label, selected_key: key, open?: false)}
end
@impl true
def process_input({:cursor_button, {:btn_left, 0, _, _}}, :bg, %{assigns: %{open?: open?}} = scene) do
{:noreply, assign(scene, open?: not open?)}
end
def process_input({:cursor_button, {:btn_left, 1, _, _}}, :clickout, %{assigns: %{open?: open?}} = scene) do
{:noreply, assign(scene, open?: not open?)}
end
def process_input(_, _, scene) do
{:noreply, scene}
end
defp get_width(data, opts) do
{_, _, w, _h} = Items.bounds(data, opts)
w
end
defp get_frame_height(data, opts) do
{_, _, _w, h} = Items.bounds(data, opts)
frame_height = opts[:frame_height] || @default_frame_height
if(h > frame_height, do: frame_height, else: h)
end
defp get_content_height(items) do
Items.get_height(items)
end
defp get_theme(opts) do
case opts[:theme] do
nil -> @default_theme
:dark -> @default_theme
:light -> @default_theme
theme -> theme
end
|> FloUI.Theme.normalize()
end
end