Mobile-Responsive Contacts
The Contacts module adapts its layout for mobile screens (<1024px). The detail view switches from a 3-column grid to tab-based panels, and the create form adds inline phone/email fields with multi-step API orchestration.
Detail View — Tab Panels
Section titled “Detail View — Tab Panels”On desktop, the contact detail view renders three columns simultaneously: sidebar (profile, phones, emails, tags), communication feed, and notes/tasks. On mobile, a tab bar replaces the columns.
MobilePanel Enum
Section titled “MobilePanel Enum”#[derive(Clone, Copy, PartialEq)]enum MobilePanel { Info, // Contact sidebar Messages, // Communication feed Notes, // Notes/tasks/activity}State is managed with use_signal(|| MobilePanel::Info). The tab bar renders below the header and is hidden on desktop with lg:hidden.
Tab Labels
Section titled “Tab Labels”| Tab | Content | Desktop equivalent |
|---|---|---|
| Info | Profile, phones, emails, addresses, tags, assignments | Left column (1/4) |
| Comms | Calls, messages, emails feed | Center column (2/4) |
| More | Notes, tasks, activity sub-tabs | Right column (1/4) |
Visibility Pattern
Section titled “Visibility Pattern”Each panel uses conditional CSS classes to show/hide based on the active tab:
// Info panelclass: if active_panel() == MobilePanel::Info { "lg:col-span-1 overflow-y-auto"} else { "hidden lg:block lg:col-span-1 overflow-y-auto"}On desktop, panels are always visible via lg:block / lg:flex. On mobile, only the active panel renders — the others get hidden.
Create Contact Form
Section titled “Create Contact Form”The create form collects six fields across three sections, with client-side validation and multi-step API calls.
Form Fields
Section titled “Form Fields”Basic Info:
first_name—Stringlast_name—String
Contact Info:
phone—String(optional)email—String, input typeemail(optional)
Work:
company—String(optional)job_title—String(optional)
The layout uses a responsive grid: single column on mobile, two columns on md+ screens.
CreateContactPayload
Section titled “CreateContactPayload”A UI-only wrapper that bundles the contact data with optional phone/email for the view layer:
#[derive(Debug, Clone, PartialEq)]pub struct CreateContactPayload { pub contact: ContactData, pub phone: Option<String>, pub email: Option<String>,}This type is never serialized. Empty strings are converted to None before submission.
Validation
Section titled “Validation”At least one name field (first or last) must be non-empty. The error displays inline:
if first_name().trim().is_empty() && last_name().trim().is_empty() { name_error.set(Some("At least a first or last name is required.".into())); return;}The error clears when either name field receives input.
Multi-Step Creation Flow
Section titled “Multi-Step Creation Flow”The view orchestrates three sequential API calls:
- Create contact —
POST /api/contactswithContactData. On failure, stop and show error. - Create phone —
POST /api/contacts/{id}/phoneswithContactPhoneData. On failure, show toast but continue. - Create email —
POST /api/contacts/{id}/emailswithContactEmailData. On failure, show toast but continue. - Navigate to the new contact’s detail view.
Phone and email default to is_preferred: true and label: None. The contact always gets created even if phone/email fail (e.g., duplicate validation on the server).
Touch & UX Polish
Section titled “Touch & UX Polish”Touch Targets
Section titled “Touch Targets”Quick action buttons (phone, email, video, note) in the sidebar header increased from w-8 h-8 (32px) to w-10 h-10 (40px), meeting the 40px minimum for comfortable touch interaction.
Responsive Button Layout
Section titled “Responsive Button Layout”- Create Contact button — full-width on mobile (
w-full sm:w-auto), auto-width on desktop - Profile save/cancel — vertical stack on mobile (
flex-col-reverse), horizontal row on desktop - Admin buttons (Custom Fields, Manage Assignments) — hidden on mobile (
hidden sm:flex)
ViewContainer Wrapping
Section titled “ViewContainer Wrapping”The shared ViewContainer action slot adds flex-wrap so toolbar buttons wrap gracefully on narrow screens instead of overflowing.
Communication History Header
Section titled “Communication History Header”The “Communication History” heading is hidden on mobile (hidden lg:block) since the tab label already identifies the panel.