Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol examples components navigation select_list_showcase.ex
Raw

examples/components/navigation/select_list_showcase.ex

defmodule Raxol.Examples.SelectListShowcase do
@moduledoc """
Showcase for the enhanced SelectList component with its various features.
This example demonstrates:
- Basic selection (single and multiple)
- Searching and filtering
- Pagination
- Keyboard navigation
- Styling options
"""
use Raxol.UI.Components.Base.Component
require Raxol.Core.Runtime.Log
require Raxol.View.Elements
import Raxol.View.Elements
@impl Raxol.UI.Components.Base.Component
def init(_props) do
%{
# Current tab selection
active_tab: :basic,
# Selected options from each list
basic_selected: nil,
multiple_selected: [],
search_selected: nil,
pagination_selected: nil,
# Search text
search_text: "",
# Sample data
users: [
%{
id: 1,
name: "Alice Smith",
email: "alice@example.com",
role: "Admin"
},
%{id: 2, name: "Bob Johnson", email: "bob@example.com", role: "User"},
%{
id: 3,
name: "Carol Williams",
email: "carol@example.com",
role: "User"
},
%{
id: 4,
name: "Dave Brown",
email: "dave@example.com",
role: "Moderator"
},
%{id: 5, name: "Eve Davis", email: "eve@example.com", role: "Admin"},
%{
id: 6,
name: "Frank Miller",
email: "frank@example.com",
role: "User"
},
%{
id: 7,
name: "Grace Wilson",
email: "grace@example.com",
role: "User"
},
%{
id: 8,
name: "Heidi Moore",
email: "heidi@example.com",
role: "Moderator"
},
%{id: 9, name: "Ivan Taylor", email: "ivan@example.com", role: "User"},
%{id: 10, name: "Judy White", email: "judy@example.com", role: "Admin"},
%{
id: 11,
name: "Kevin Lewis",
email: "kevin@example.com",
role: "User"
},
%{
id: 12,
name: "Laura Harris",
email: "laura@example.com",
role: "Moderator"
},
%{id: 13, name: "Mike Clark", email: "mike@example.com", role: "User"},
%{
id: 14,
name: "Nancy Young",
email: "nancy@example.com",
role: "User"
},
%{
id: 15,
name: "Oscar Scott",
email: "oscar@example.com",
role: "Admin"
}
],
countries: [
%{
code: "US",
name: "United States",
population: "331 million",
continent: "North America"
},
%{
code: "CN",
name: "China",
population: "1.4 billion",
continent: "Asia"
},
%{
code: "IN",
name: "India",
population: "1.38 billion",
continent: "Asia"
},
%{
code: "ID",
name: "Indonesia",
population: "273 million",
continent: "Asia"
},
%{
code: "PK",
name: "Pakistan",
population: "220 million",
continent: "Asia"
},
%{
code: "BR",
name: "Brazil",
population: "212 million",
continent: "South America"
},
%{
code: "NG",
name: "Nigeria",
population: "206 million",
continent: "Africa"
},
%{
code: "BD",
name: "Bangladesh",
population: "164 million",
continent: "Asia"
},
%{
code: "RU",
name: "Russia",
population: "144 million",
continent: "Europe/Asia"
},
%{
code: "MX",
name: "Mexico",
population: "128 million",
continent: "North America"
},
%{
code: "JP",
name: "Japan",
population: "126 million",
continent: "Asia"
},
%{
code: "ET",
name: "Ethiopia",
population: "114 million",
continent: "Africa"
},
%{
code: "PH",
name: "Philippines",
population: "109 million",
continent: "Asia"
},
%{
code: "EG",
name: "Egypt",
population: "102 million",
continent: "Africa"
},
%{
code: "VN",
name: "Vietnam",
population: "97 million",
continent: "Asia"
},
%{
code: "DE",
name: "Germany",
population: "83 million",
continent: "Europe"
},
%{
code: "IR",
name: "Iran",
population: "83 million",
continent: "Asia"
},
%{
code: "TR",
name: "Turkey",
population: "83 million",
continent: "Europe/Asia"
},
%{
code: "FR",
name: "France",
population: "67 million",
continent: "Europe"
},
%{
code: "GB",
name: "United Kingdom",
population: "67 million",
continent: "Europe"
},
%{
code: "TH",
name: "Thailand",
population: "66 million",
continent: "Asia"
},
%{
code: "IT",
name: "Italy",
population: "60 million",
continent: "Europe"
},
%{
code: "ZA",
name: "South Africa",
population: "59 million",
continent: "Africa"
},
%{
code: "MM",
name: "Myanmar",
population: "54 million",
continent: "Asia"
},
%{
code: "KR",
name: "South Korea",
population: "51 million",
continent: "Asia"
},
%{
code: "CO",
name: "Colombia",
population: "50 million",
continent: "South America"
},
%{
code: "KE",
name: "Kenya",
population: "53 million",
continent: "Africa"
},
%{
code: "ES",
name: "Spain",
population: "47 million",
continent: "Europe"
},
%{
code: "AR",
name: "Argentina",
population: "45 million",
continent: "South America"
},
%{
code: "DZ",
name: "Algeria",
population: "43 million",
continent: "Africa"
}
],
fruits: [
{"Apple", :apple},
{"Banana", :banana},
{"Cherry", :cherry},
{"Date", :date},
{"Elderberry", :elderberry},
{"Fig", :fig},
{"Grape", :grape},
{"Honeydew", :honeydew},
{"Imbe", :imbe},
{"Jackfruit", :jackfruit}
]
}
end
@impl Raxol.UI.Components.Base.Component
def handle_event({:select_tab, tab}, _props, state) when is_atom(tab) do
{%{state | active_tab: tab}, []}
end
def handle_event({:select_fruit, fruit_value}, _props, state) do
# Basic single selection
{%{state | basic_selected: fruit_value}, []}
end
def handle_event({:select_user, user_id}, _props, state) do
# For multiple selection
handle_user_selection(
state.multiple_selected |> Enum.member?(user_id),
user_id,
state
)
end
def handle_event({:select_user_with_search, user_id}, _props, state) do
# Search list selection
user = state.users |> Enum.find(&(&1.id == user_id))
{%{state | search_selected: user}, []}
end
def handle_event({:select_country, country_code}, _props, state) do
# Pagination list selection
country = state.countries |> Enum.find(&(&1.code == country_code))
{%{state | pagination_selected: country}, []}
end
def handle_event({:update_search, search_text}, _props, state) do
{%{state | search_text: search_text}, []}
end
def handle_event(_event, _props, state) do
{state, []}
end
@impl Raxol.UI.Components.Base.Component
def render(state, _context) do
# Custom theme settings
theme = %{
select_list: %{
label_fg: :cyan,
focused_fg: :black,
focused_bg: :cyan,
selected_fg: :black,
selected_bg: :green,
search_fg: :white,
search_bg: :blue,
pagination_fg: :white,
pagination_bg: :blue
}
}
panel title: "SelectList Component Showcase", border: :single, width: 100 do
column padding: 1, gap: 1 do
# Description
label(
"This showcase demonstrates the enhanced SelectList component with various features."
)
# Tab navigation
row gap: 2 do
button(
label: "Basic",
on_click: {:select_tab, :basic},
style: get_tab_style(state.active_tab == :basic)
)
button(
label: "Multiple Selection",
on_click: {:select_tab, :multiple},
style: get_tab_style(state.active_tab == :multiple)
)
button(
label: "Search & Filter",
on_click: {:select_tab, :search},
style: get_tab_style(state.active_tab == :search)
)
button(
label: "Pagination",
on_click: {:select_tab, :pagination},
style: get_tab_style(state.active_tab == :pagination)
)
end
# Content based on selected tab
panel title: tab_title(state.active_tab), border: :single, height: 20 do
column padding: 1, gap: 1 do
case state.active_tab do
:basic -> render_basic_tab(state, theme)
:multiple -> render_multiple_tab(state, theme)
:search -> render_search_tab(state, theme)
:pagination -> render_pagination_tab(state, theme)
end
end
end
# Selection display
panel title: "Selected Items", border: :single do
column padding: 1, gap: 1 do
case state.active_tab do
:basic ->
render_basic_selection(state.basic_selected)
:multiple ->
render_multiple_selection(state.multiple_selected, state.users)
:search ->
render_search_selection(state.search_selected)
:pagination ->
render_pagination_selection(state.pagination_selected)
end
end
end
# Instructions panel
panel title: "Keyboard Navigation", border: :single do
column padding: 1 do
label(text: "Arrow Up/Down: Navigate items")
label(text: "Page Up/Down: Navigate pages")
label(text: "Home/End: Jump to first/last item")
label(
text:
"Tab: Switch between search box and list (when search enabled)"
)
label(text: "Enter: Select item")
label(text: "Space: Toggle selection (in multiple selection mode)")
label(text: "Backspace: Delete search text (when search focused)")
label(text: "Type any text: Incremental search in list")
end
end
end
end
end
# Helper functions for refactored if statements
# Handle user selection instead of if statement
defp handle_user_selection(true, user_id, state) do
# Remove from selection
{%{
state
| multiple_selected:
state.multiple_selected |> Enum.reject(&(&1 == user_id))
}, []}
end
defp handle_user_selection(false, user_id, state) do
# Add to selection
{%{state | multiple_selected: [user_id | state.multiple_selected]}, []}
end
# Get tab style instead of if statement
defp get_tab_style(true), do: [bg: :blue, fg: :white]
defp get_tab_style(false), do: []
# Render basic selection instead of if statement
defp render_basic_selection(nil), do: label(text: "No fruit selected")
defp render_basic_selection(selected),
do: label(text: "Selected fruit: #{selected}")
# Render multiple selection instead of if statement
defp render_multiple_selection(selected, _users) when selected == [] do
label(text: "No users selected")
end
defp render_multiple_selection(selected, users) do
selected_users =
users
|> Enum.filter(&Enum.member?(selected, &1.id))
|> Enum.map_join(", ", & &1.name)
label(text: "Selected users: #{selected_users}")
end
# Render search selection instead of if statement
defp render_search_selection(nil), do: label(text: "No user selected")
defp render_search_selection(selected) do
column do
label(text: "Selected user:")
label(text: "Name: #{selected.name}")
label(text: "Email: #{selected.email}")
label(text: "Role: #{selected.role}")
end
end
# Render pagination selection instead of if statement
defp render_pagination_selection(nil), do: label(text: "No country selected")
defp render_pagination_selection(selected) do
column do
label(text: "Selected country:")
label(text: "Name: #{selected.name}")
label(text: "Population: #{selected.population}")
label(text: "Continent: #{selected.continent}")
end
end
# Tab-specific rendering functions
defp render_basic_tab(state, theme) do
column gap: 1 do
label(text: "Basic single-selection list with fruits:")
# Convert fruits to expected format for SelectList options
options = Enum.map(state.fruits, fn {label, value} -> {label, value} end)
%{
type: Raxol.UI.Components.Input.SelectList,
id: :basic_select,
assigns: %{
options: options,
label: "Select a fruit:",
on_select: {:select_fruit},
max_height: 10,
theme: theme.select_list
}
}
end
end
defp render_multiple_tab(state, theme) do
column gap: 1 do
label(text: "Multiple selection list with users:")
# Convert users to expected format for SelectList options
options =
Enum.map(state.users, fn user ->
{"#{user.name} (#{user.role})", user.id}
end)
%{
type: Raxol.UI.Components.Input.SelectList,
id: :multiple_select,
assigns: %{
options: options,
label: "Select users (use Space to toggle):",
on_select: {:select_user},
max_height: 12,
multiple: true,
theme: theme.select_list
}
}
end
end
defp render_search_tab(state, theme) do
column gap: 1 do
label(text: "Searchable list with users:")
# Convert users to expected format for SelectList options
options =
Enum.map(state.users, fn user ->
{"#{user.name} (#{user.email})", user.id}
end)
%{
type: Raxol.UI.Components.Input.SelectList,
id: :search_select,
assigns: %{
options: options,
label: "Search users (by name or email):",
on_select: {:select_user_with_search},
max_height: 12,
enable_search: true,
searchable_fields: [:name, :email],
placeholder: "Type to search...",
theme: theme.select_list
}
}
end
end
defp render_pagination_tab(state, theme) do
column gap: 1 do
label(text: "Paginated list with countries:")
# Convert countries to expected format for SelectList options
options =
Enum.map(state.countries, fn country ->
{"#{country.name} (#{country.continent})", country.code}
end)
%{
type: Raxol.UI.Components.Input.SelectList,
id: :pagination_select,
assigns: %{
options: options,
label: "Select a country:",
on_select: {:select_country},
max_height: 12,
page_size: 8,
show_pagination: true,
theme: theme.select_list
}
}
end
end
defp tab_title(tab) do
case tab do
:basic -> "Basic Selection"
:multiple -> "Multiple Selection"
:search -> "Search & Filter"
:pagination -> "Pagination"
end
end
@impl Raxol.UI.Components.Base.Component
def update({:update_props, new_props}, state) do
{:noreply, Map.merge(state, new_props)}
end
def update({:tab_selected, tab}, state) do
{:noreply, %{state | active_tab: tab}}
end
end