Packages
noora
0.37.0
0.85.0
0.84.1
0.84.0
0.83.2
0.83.1
0.83.0
0.82.7
0.82.6
0.82.5
0.82.4
0.82.3
0.82.2
0.82.1
0.82.0
0.81.4
0.81.3
0.81.2
0.81.1
0.81.0
0.80.1
0.80.0
0.79.1
0.79.0
0.78.1
0.78.0
0.77.4
0.77.3
0.77.2
0.77.1
0.77.0
0.76.0
0.75.0
0.74.0
0.73.0
0.72.0
0.71.0
0.70.0
0.69.1
0.69.0
0.68.0
0.67.0
0.66.0
0.65.0
0.64.2
0.64.1
0.64.0
0.63.2
0.63.1
0.63.0
0.62.0
0.61.0
0.60.0
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.0
0.54.0
0.53.1
0.53.0
0.52.1
0.52.0
0.51.0
0.50.1
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.3
0.44.2
0.44.1
0.44.0
0.43.0
0.42.0
0.41.0
0.40.6
0.40.5
0.40.4
0.40.3
0.40.2
0.40.1
0.40.0
0.39.1
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.1
0.32.0
0.31.0
0.30.0
0.29.2
0.29.1
0.29.0
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.0
0.26.1
0.26.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.1
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.2
0.2.0
0.1.0
0.1.0-rc.2
0.1.0-rc.1
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
A component library for Phoenix LiveView applications
Current section
Files
Jump to
Current section
Files
lib/noora/dropdown.ex
defmodule Noora.Dropdown do
@moduledoc """
Renders a customizable dropdown component with a trigger, menu, and item options, supporting icons, labels, and event handling.
## Example
```elixir
<.dropdown id="menu" label="Options">
<.dropdown_item label="Edit" navigate="/edit" />
<.dropdown_item label="Delete" on_click="delete" />
</.dropdown>
```
"""
use Phoenix.Component
import Noora.Icon
import Noora.LineDivider
import Noora.Utils
attr(:id, :string, required: true, doc: "Unique identifier for the dropdown component")
attr(:label, :string, default: nil, doc: "Main text displayed in the dropdown trigger")
attr(:secondary_text, :string,
default: nil,
doc: "Secondary text displayed to the left of the main label"
)
attr(:icon_only, :boolean, default: false, doc: "Whether the dropdown trigger is icon-only")
attr(:hint, :string, default: nil, doc: "Hint text for the dropdown")
attr(:disabled, :boolean, default: nil, doc: "Whether the dropdown is disabled")
attr(:on_open_change, :string, default: nil, doc: "Event handler for when the dropdown opens")
attr(:on_highlight_change, :string,
default: nil,
doc: "Event handler for when the highlighted option changes"
)
attr(:on_select, :string, default: nil, doc: "Event handler for when an option is selected")
attr(:on_escape_key_down, :string,
default: nil,
doc: "Event handler for when the escape key is pressed"
)
attr(:on_pointer_down_outside, :string,
default: nil,
doc: "Event handler for when the pointer is pressed outside the dropdown"
)
attr(:on_focus_outside, :string,
default: nil,
doc: "Function called when the focus is moved outside the component"
)
attr(:on_interact_outside, :string,
default: nil,
doc: "Function called when an interaction happens outside the component"
)
slot(:icon, doc: "Icon to be rendered in the dropdown trigger")
slot(:inner_block, doc: "Content to be rendered inside the dropdown menu")
def dropdown(assigns) do
~H"""
<div
id={@id}
class="noora-dropdown"
phx-hook="NooraDropdown"
data-loop-focus
data-close-on-select
data-typeahead
data-on-open-change={@on_open_change}
data-on-highlight-change={@on_highlight_change}
data-on-select={@on_select}
data-on-escape-key-down={@on_escape_key_down}
data-on-pointer-down-outside={@on_pointer_down_outside}
data-on-focus-outside={@on_focus_outside}
data-on-interact-outside={@on_interact_outside}
>
<button :if={!@icon_only} data-part="trigger" disabled={@disabled} type="button">
<div data-part="label-wrapper">
<div :if={has_slot_content?(@icon, assigns)} data-part="icon">
{render_slot(@icon)}
</div>
<span :if={@secondary_text} data-part="secondary-text">
{@secondary_text}
</span>
<span data-part="label">{@label}</span>
</div>
<div data-part="indicator">
<div data-part="indicator-down">
<.chevron_down />
</div>
<div data-part="indicator-up">
<.chevron_up />
</div>
</div>
</button>
<button :if={@icon_only} data-part="trigger" data-icon-only disabled={@disabled} type="button">
<div data-part="icon">
{render_slot(@icon)}
</div>
</button>
<div data-part="positioner">
<div class="noora-dropdown-content" data-part="content">
{render_slot(@inner_block)}
</div>
</div>
<span :if={@hint} data-part="hint">
<.info_circle />
<span>{@hint}</span>
</span>
</div>
"""
end
attr(:id, :string, required: true, doc: "Unique identifier for the dropdown component")
attr(:label, :string, default: nil, doc: "Main text displayed in the dropdown trigger")
attr(:disabled, :boolean, default: nil, doc: "Whether the dropdown is disabled")
attr(:on_open_change, :string, default: nil, doc: "Event handler for when the dropdown opens")
attr(:on_highlight_change, :string,
default: nil,
doc: "Event handler for when the highlighted option changes"
)
attr(:on_select, :string, default: nil, doc: "Event handler for when an option is selected")
attr(:on_escape_key_down, :string,
default: nil,
doc: "Event handler for when the escape key is pressed"
)
attr(:on_pointer_down_outside, :string,
default: nil,
doc: "Event handler for when the pointer is pressed outside the dropdown"
)
attr(:on_focus_outside, :string,
default: nil,
doc: "Function called when the focus is moved outside the component"
)
attr(:on_interact_outside, :string,
default: nil,
doc: "Function called when an interaction happens outside the component"
)
slot(:icon, doc: "Icon to be rendered in the dropdown trigger")
slot(:inner_block, doc: "Content to be rendered inside the dropdown menu")
def inline_dropdown(assigns) do
~H"""
<div
id={@id}
class="noora-inline-dropdown"
phx-hook="NooraDropdown"
data-loop-focus
data-close-on-select
data-typeahead
data-on-open-change={@on_open_change}
data-on-highlight-change={@on_highlight_change}
data-on-select={@on_select}
data-on-escape-key-down={@on_escape_key_down}
data-on-pointer-down-outside={@on_pointer_down_outside}
data-on-focus-outside={@on_focus_outside}
data-on-interact-outside={@on_interact_outside}
>
<button data-part="trigger" disabled={@disabled}>
<div :if={has_slot_content?(@icon, assigns)} data-part="icon">
{render_slot(@icon)}
</div>
<span data-part="label">{@label}</span>
<div data-part="indicator">
<div data-part="indicator-down">
<.chevron_down />
</div>
<div data-part="indicator-up">
<.chevron_up />
</div>
</div>
</button>
<div data-part="positioner">
<div class="noora-dropdown-content" data-part="content">
{render_slot(@inner_block)}
</div>
</div>
</div>
"""
end
attr(:value, :string, required: false, doc: "Value associated with the dropdown item")
attr(:on_click, :string,
default: nil,
doc: "Event handler for when the dropdown item is clicked"
)
attr(:patch, :string, default: nil, doc: "Phoenix LiveView patch navigation path")
attr(:navigate, :string, default: nil, doc: "Phoenix LiveView navigation path")
attr(:href, :string, default: nil, doc: "Standard URL for navigation")
attr(:size, :string, values: ~w(small large), default: "small", doc: "Size of the dropdown item")
attr(:label, :string, required: true, doc: "Text displayed as the main content of the item")
attr(:secondary_text, :string,
default: nil,
doc: "Secondary text displayed in parentheses after the label"
)
attr(:description, :string,
default: nil,
doc: "Additional description text (only visible when size is 'large')"
)
slot(:right_icon,
required: false,
doc: "Optional slot for rendering an icon on the right side of the item"
)
attr(:rest, :global, doc: "Additional HTML attributes")
slot(:left_icon,
required: false,
doc: "Optional slot for rendering an icon on the left side of the item"
)
def dropdown_item(assigns) do
~H"""
<%= if is_nil(@patch) and is_nil(@navigate) and is_nil(@href) do %>
<span
class="noora-dropdown-item"
data-part="item"
data-value={@value || @label}
data-label={@label}
phx-click={@on_click}
phx-value-data={@value}
data-size={@size}
{@rest}
>
<div :if={has_slot_content?(@left_icon, assigns)} data-part="left-icon">
{render_slot(@left_icon)}
</div>
<div data-part="body">
<span data-part="label">{@label}</span>
<span :if={@secondary_text} data-part="secondary-text">
({@secondary_text})
</span>
<div :if={@size == "large"}>
<span data-part="description">
{@description}
</span>
</div>
</div>
<div :if={has_slot_content?(@right_icon, assigns)} data-part="right-icon">
{render_slot(@right_icon)}
</div>
</span>
<% else %>
<.link
class="noora-dropdown-item"
data-part="item"
data-value={@value || @label}
data-label={@label}
phx-click={@on_click}
phx-value-data={@value}
patch={@patch}
navigate={@navigate}
href={@href}
data-size={@size}
{@rest}
>
<div :if={has_slot_content?(@left_icon, assigns)} data-part="left-icon">
{render_slot(@left_icon)}
</div>
<div data-part="body">
<span data-part="label">{@label}</span>
<span :if={@secondary_text} data-part="secondary-text">
({@secondary_text})
</span>
<div :if={@size == "large"}>
<span data-part="description">
{@description}
</span>
</div>
</div>
<div :if={has_slot_content?(@right_icon, assigns)} data-part="right-icon">
{render_slot(@right_icon)}
</div>
</.link>
<% end %>
"""
end
def dropdown_separator(assigns) do
~H"""
<.line_divider data-part="separator" />
"""
end
end