Skip to content

Header & Sidebar User Controls

The header and sidebar split responsibilities: the header handles global actions (dialpad, notifications, assistant toggle), while the sidebar owns all user-related controls through a popover menu.

The header renders the top bar with a hamburger toggle, action buttons, and the assistant trigger.

src/shared/components/header.rs
#[component]
pub fn Header(
#[props(default = false)] is_assistant_open: bool,
#[props(default = false)] can_use_assistant: bool,
on_toggle_assistant: Option<EventHandler<()>>,
sidebar_collapsed: Option<Signal<bool>>,
header_actions: Option<Element>,
) -> Element
PropTypeDescription
is_assistant_openboolWhether the assistant panel is visible
can_use_assistantboolGates the assistant toggle button
on_toggle_assistantOption<EventHandler<()>>Fires when the user clicks the assistant icon
sidebar_collapsedOption<Signal<bool>>Controls the hamburger menu state
header_actionsOption<Element>Slot for custom action buttons

The header renders actions in this order: DialPad → NotificationBell → Assistant.

The assistant toggle uses a fully circular button with a breathing ring animation:

// Closed state
"rounded-full assistant-trigger-ring"
// Open state
"rounded-full bg-primary/10 shadow-md ring-2 ring-primary/30"

The assistant-trigger-ring class in tailwind.css applies a 2.5-second infinite ease-in-out animation with an amber glow. The icon color switches from text-muted-foreground (closed) to primary (open).

The sidebar renders a user menu at its bottom edge. Clicking it opens an upward popover with settings, theme toggle, and logout.

src/shared/components/sidebar.rs
let mut is_user_menu_open = use_signal(|| false);
let mut is_light = use_signal(|| false);
SignalTypePurpose
is_user_menu_openSignal<bool>Controls popover visibility
is_lightSignal<bool>Tracks current theme, synced from DOM on mount

On mount, a use_effect reads the data-theme attribute from <html> to initialize is_light.

Collapsed sidebar — shows only an avatar circle (first letter of the user’s name).

Expanded sidebar — shows the avatar, full name, and a ChevronUp icon.

Clicking either state toggles is_user_menu_open.

The popover opens upward with a backdrop at z-[999] and the panel at z-[1000]:

ActionBehavior
SettingsNavigates to /settings via nav.push(Route::SettingsView {})
Theme toggleSwaps is_light, updates data-theme on <html>, saves to localStorage key "loquent-theme"
LogoutCollapses sidebar, closes popover, calls on_logout()logout_api() → redirects to /login
// Toggle handler inside the popover
let new_theme = if *is_light.read() { "dark" } else { "light" };
is_light.set(new_theme == "light");
// Updates DOM attribute and localStorage
document.set_attribute("data-theme", new_theme);
localStorage.set_item("loquent-theme", new_theme);

The theme persists across sessions through localStorage. On page load, the app reads loquent-theme and applies it before first paint.

FileRole
src/shared/components/header.rsHeader bar with actions and assistant toggle
src/shared/components/sidebar.rsSidebar with navigation groups and user menu
src/shared/layouts/app_layout.rsWires header and sidebar into the main layout
tailwind.cssAssistant breathing ring animation styles