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 demos ux_refinement_demo.ex
Raw

examples/demos/ux_refinement_demo.ex

defmodule Raxol.Examples.UXRefinementDemo do
use Raxol.Core.Runtime.Application
# Remove use Component
# use Raxol.UI.Components.Base.Component
# Removed conflicting behaviour
# @behaviour Raxol.UI.Components.Base.Component
@moduledoc """
Demo example showcasing the User Experience Refinement components.
This example demonstrates:
1. Focus management with keyboard navigation
2. Contextual hints for different components
3. Visual focus indication
4. Accessibility support
Run this example with:
```
$ mix run -e "Raxol.Examples.UXRefinementDemo.run()"
```
"""
alias Raxol.Core.UXRefinement
alias Raxol.Core.FocusManager
alias Raxol.UI.Components.FocusRing
alias Raxol.Core.Runtime.Command
alias Raxol.View.Elements, as: UI
require Raxol.Core.Runtime.Log
require UI
defstruct [
# Focus state
current_view: :main, # Keep track of current UI view if needed
form_data: %{username: "", password: ""}, # Store form input values
focused_component: "username_input", # ID of the currently focused component
show_help: false, # Flag to control help dialog visibility
focus_ring_model: nil # State for the focus ring component (handled by component itself)
# Potentially add other UI state here
]
@doc """
Run the UX Refinement demo application.
"""
def run do
Raxol.Core.Runtime.Log.info("Starting UX Refinement Demo Application...")
# Start Raxol with this module as the Application
# Pass an empty map as initial opts for init/1
Raxol.Core.Runtime.Lifecycle.start_application(__MODULE__, %{})
end
@doc """
Initialize the application state and perform setup.
"""
@impl Raxol.Core.Runtime.Application
def init(_opts) do
Raxol.Core.Runtime.Log.info("Initializing UX Refinement Demo...") # Add logging
# --- Setup moved from run/0 ---
# Enable UX refinement features
UXRefinement.enable_feature(:keyboard_nav)
UXRefinement.enable_feature(:focus_management)
UXRefinement.enable_feature(:hints)
UXRefinement.enable_feature(:response_optimization)
# Note: FocusRing feature enabled by default or via theme integration
# Register hints for components
UXRefinement.register_hint(
"username_input",
"Enter your username (letters and numbers only)"
)
UXRefinement.register_hint(
"password_input",
"Enter a secure password (minimum 8 characters)"
)
UXRefinement.register_hint("login_button", "Press Enter to log in")
UXRefinement.register_hint(
"reset_button",
"Press Enter to reset the form"
)
UXRefinement.register_hint(
"help_button",
"Press Enter to open the help dialog"
)
# Register focusable components
FocusManager.register_focusable("username_input", 1,
announce: "Username input field. Enter your username."
)
FocusManager.register_focusable("password_input", 2,
announce: "Password input field. Enter your password."
)
FocusManager.register_focusable("login_button", 3,
group: :buttons,
announce: "Login button"
)
FocusManager.register_focusable("reset_button", 4,
group: :buttons,
announce: "Reset button"
)
FocusManager.register_focusable("help_button", 5,
group: :buttons,
announce: "Help button"
)
# Set initial focus
FocusManager.set_initial_focus("username_input") # Initial focus is username_input
# Mock element positions for the focus ring
element_registry = %{
"username_input" => {10, 5, 30, 3},
"password_input" => {10, 9, 30, 3},
"login_button" => {10, 13, 10, 3},
"reset_button" => {22, 13, 10, 3},
"help_button" => {34, 13, 10, 3}
}
Process.put(:element_position_registry, element_registry)
# --- End of setup from run/0 ---
# Return initial state
{:ok, %__MODULE__{ # Return the struct
form_data: %{username: "", password: ""},
focused_component: "username_input", # Updated initial focus ID
show_help: false,
focus_ring_model: nil # FocusRing manages its own state
}}
end
@doc """
Render the UX refinement demo UI.
"""
@impl Raxol.Core.Runtime.Application
@dialyzer {:nowarn_function, view: 1}
def view(state) do
focused_id = state.focused_component
# all_hints = get_hints_for(focused_id) # Assuming get_hints_for exists and is used elsewhere
# --- Calculate focus position BEFORE the main component list ---
element_registry = Process.get(:element_position_registry, %{})
focused_position = Map.get(element_registry, focused_id)
# --- Conditionally create the focus ring component ---
focus_ring_component =
if focused_position do
# Raxol.View.Elements.component(
# Raxol.UI.Components.FocusRing,
# id: :focus_ring,
# model: state.focus_ring_model,
# focused_element_id: focused_id,
# focused_element_position: focused_position
# )
# Construct component map directly
%{
type: Raxol.UI.Components.FocusRing,
id: :focus_ring,
# Pass props directly, assuming component handles its own model state
# model: state.focus_ring_model,
focused_element_id: focused_id,
focused_element_position: focused_position
}
else
nil # Explicitly return nil if no position
end
# Raxol.View.Elements.component Raxol.UI.Components.AppContainer, id: :app_container do
# Use AppContainer map directly as the root element
%{
type: Raxol.UI.Components.AppContainer,
id: :app_container,
children: [
# START OF LIST
UI.panel title: "Login Form" do
UI.box do
[
# Main content area (takes up most space)
UI.box style: %{height: "fill-1"} do
# Layout the three main sections
UI.row height: "100%" do
UI.column padding: 1 do
# Wrap all children in an explicit list
[
if state.show_help do
render_help_dialog()
else
# Form elements need to be a list too for the outer list
[
UI.row padding_bottom: 1 do
label_element = Raxol.View.Elements.label("Username:", style: %{width: 10})
input_element =
UI.text_input(
id: "username_input",
value: state.form_data.username,
width: 30,
focus: focused_id == "username_input"
)
[label_element, input_element]
end,
UI.row padding_bottom: 1 do
label_element = Raxol.View.Elements.label("Password:", style: %{width: 10})
input_element =
UI.text_input(
id: "password_input",
value: state.form_data.password,
width: 30,
password: true,
focus: focused_id == "password_input"
)
[label_element, input_element]
end,
UI.row padding_top: 1 do
login_button =
UI.button(
id: "login_button",
label: "Login",
width: 10,
focus: focused_id == "login_button"
)
space_element = Raxol.View.Elements.label(" ")
reset_button =
UI.button(
id: "reset_button",
label: "Reset",
width: 10,
focus: focused_id == "reset_button"
)
space_element2 = Raxol.View.Elements.label(" ")
help_button =
UI.button(
id: "help_button",
label: "Help",
width: 10,
focus: focused_id == "help_button"
)
[login_button, space_element, reset_button, space_element2, help_button]
end
]
end
]
end
end
end,
# Hint display at the bottom
# Raxol.View.Elements.component(
# Raxol.UI.Components.HintDisplay,
# id: :hint_display,
# hints: all_hints,
# position: :bottom
# # Style can be added here if needed
# )
# Construct component map directly
%{
type: Raxol.UI.Components.HintDisplay,
id: :hint_display,
hints: get_hints_for(state.focused_component),
position: :bottom
# Style can be added here if needed
},
# Add the pre-calculated focus ring component (will be nil if not needed)
focus_ring_component
]
end
end
]
}
end
@doc """
Update the application state based on events.
"""
@impl Raxol.Core.Runtime.Application
def update(model, msg) do
new_model =
case msg do
# Text input updates
{:input, "username_input", value} ->
%{model | form_data: %{model.form_data | username: value}}
{:input, "password_input", value} ->
%{model | form_data: %{model.form_data | password: value}}
# Button clicks
{:click, "login_button"} ->
# In a real app, this would handle authentication
model
{:click, "reset_button"} ->
%{model | username: "", password: ""}
{:click, "help_button"} ->
%{model | show_help: true}
# Focus change events
{:focus_change, _old_focus, new_focus} ->
# Update the focus ring position
updated_focus_ring =
FocusRing.update(
model.focus_ring_model,
{:focus_change, nil, new_focus}
)
%{
model
| focused_component: new_focus,
focus_ring_model: updated_focus_ring
}
# Close help dialog
{:close_help} ->
%{model | show_help: false}
# Default case
_ ->
model
end
# Return {new_state, commands}
{new_model, []}
end
# Private functions
@dialyzer {:nowarn_function, render_help_dialog: 0}
defp render_help_dialog do
# Use the panel macro directly
rendered_dialog =
UI.panel title: "Help",
padding: 1,
height: 12,
width: 40,
border: true,
style: %{
position: :absolute,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)"
} do
UI.column do
[
Raxol.View.Elements.label(content: "This is a demo of the UX Refinement features in Raxol."),
Raxol.View.Elements.label(content: "Use Tab and Shift+Tab to navigate between form fields."),
Raxol.View.Elements.label(content: "The focus ring indicates which element is focused."),
Raxol.View.Elements.label(content: "Hints at the bottom provide context for each element."),
UI.row padding_top: 2 do
[
UI.button(
id: "close_help",
label: "Close",
width: 10,
on_click: {:close_help}
)
]
end
] # End list of children for column
end
end
rendered_dialog
end
# --- Placeholder Hint Function ---
defp get_hints_for(_focused_component_id), do: []
# Correct arity for Application behaviour
@impl Raxol.Core.Runtime.Application
def handle_event(_event) do
# State is managed by Dispatcher, just return commands
[]
end
# Correct arity for Application behaviour
@impl Raxol.Core.Runtime.Application
def handle_message(_msg, state), do: {state, []}
# Correct arity for Application behaviour
@impl Raxol.Core.Runtime.Application
def handle_tick(_tick) do
# State is managed by Dispatcher, just return commands
[]
end
@impl Raxol.Core.Runtime.Application
def subscriptions(_state), do: []
@impl Raxol.Core.Runtime.Application
def terminate(_reason, _state), do: :ok
end