Packages

Advanced configurable navigation component for Phoenix LiveView with native DaisyUI integration, Tailwind CSS support, permissions filtering, dropdowns, mega menus, and modern responsive design. Optimized for Phoenix 1.8+ and LiveView 1.1+.

Current section

Files

Jump to
navbuddy lib navbuddy.ex
Raw

lib/navbuddy.ex

defmodule NavBuddy do
@moduledoc """
NavBuddy - Advanced Configurable Navigation Component for Phoenix LiveView
NavBuddy provides comprehensive navigation components for Phoenix LiveView applications,
featuring dropdowns, mega menus, mobile-responsive design, and accessibility support.
## Basic Usage
defmodule MyAppWeb.LayoutView do
import NavBuddy.Component
def navigation_items do
[
%{id: "home", label: "Home", navigate: "/"},
%{id: "about", label: "About", navigate: "/about"},
%{
id: "products",
label: "Products",
navigate: "/products",
dropdown: [
%{id: "web", label: "Web Apps", navigate: "/products/web"},
%{id: "mobile", label: "Mobile Apps", navigate: "/products/mobile"}
]
}
]
end
end
In your LiveView template:
<.nav_menu items={navigation_items()} config={NavBuddy.default_config()} />
## Configuration
NavBuddy supports extensive configuration options:
config = %NavBuddy.Config{
orientation: :horizontal, # :horizontal | :vertical
theme: :auto, # :light | :dark | :auto
dropdown_trigger: :hover, # :hover | :click
mobile_behavior: :drawer, # :drawer | :collapse | :overlay
animations: true, # boolean
accessibility: true # boolean
}
## Navigation Structure
Navigation items support multiple levels of nesting and different types.
All navigation uses the `navigate` field which triggers LiveView's `push_navigate`
to maintain the LiveView connection without page reloads.
### Simple Links
%{id: "home", label: "Home", navigate: "/"}
### Dropdown Menus
%{
id: "products",
label: "Products",
navigate: "/products",
dropdown: [
%{id: "web", label: "Web Development", navigate: "/products/web"},
%{id: "mobile", label: "Mobile Apps", navigate: "/products/mobile"}
]
}
### Multi-level Dropdowns
%{
id: "services",
label: "Services",
dropdown: [
%{
id: "development",
label: "Development",
dropdown: [
%{id: "frontend", label: "Frontend", navigate: "/services/frontend"},
%{id: "backend", label: "Backend", navigate: "/services/backend"}
]
}
]
}
### Mega Menus
%{
id: "solutions",
label: "Solutions",
mega_menu: %{
title: "Our Solutions",
description: "Comprehensive business solutions",
sections: [
%{
title: "For Small Business",
items: [
%{id: "starter", label: "Starter Package", navigate: "/solutions/starter"},
%{id: "growth", label: "Growth Package", navigate: "/solutions/growth"}
]
},
%{
title: "For Enterprise",
items: [
%{id: "enterprise", label: "Enterprise Suite", navigate: "/solutions/enterprise"},
%{id: "custom", label: "Custom Solutions", navigate: "/solutions/custom"}
]
}
]
}
}
### Permissions-based Navigation
%{
id: "admin",
label: "Admin Panel",
navigate: "/admin",
permissions: "admin" # Only visible to users with "admin" permission
}
%{
id: "reports",
label: "Reports",
navigate: "/reports",
permissions: ["manager", "admin"] # Visible to users with either permission
}
%{
id: "profile",
label: "Profile",
navigate: "/profile" # No permissions = visible to everyone
}
## Accessibility Features
NavBuddy includes comprehensive accessibility support:
- ARIA attributes for screen readers
- Keyboard navigation (arrow keys, enter, escape, tab)
- Focus management
- High contrast theme support
- Reduced motion support
## Mobile Optimization
Multiple mobile behaviors are supported:
- **Drawer**: Slide-out navigation drawer
- **Collapse**: Collapsible menu sections
- **Overlay**: Full-screen navigation overlay
## Theming
NavBuddy supports automatic theme detection and manual theme selection:
- Light theme
- Dark theme
- Auto (follows system preference)
- Custom themes via CSS custom properties
"""
# Note: We don't alias Config here to avoid circular dependency
# The Config module will be referenced as NavBuddy.Config throughout
@type nav_item :: %{
required(:id) => String.t(),
required(:label) => String.t(),
optional(:navigate) => String.t(),
optional(:icon) => String.t(),
optional(:dropdown) => [nav_item()],
optional(:mega_menu) => mega_menu() | simple_mega_menu(),
optional(:active) => boolean(),
optional(:disabled) => boolean(),
optional(:permissions) => String.t() | [String.t()]
}
@type simple_mega_menu :: %{String.t() => [nav_item()]}
@type mega_menu :: %{
required(:title) => String.t(),
optional(:description) => String.t(),
required(:sections) => [mega_menu_section()]
}
@type mega_menu_section :: %{
required(:title) => String.t(),
optional(:description) => String.t(),
required(:items) => [nav_item()]
}
@doc """
Returns the default configuration for NavBuddy.
## Examples
iex> config = NavBuddy.default_config()
iex> config.orientation
:horizontal
iex> config = NavBuddy.default_config()
iex> config.theme
:auto
"""
def default_config do
%NavBuddy.Config{
orientation: :horizontal,
theme: :auto,
dropdown_trigger: :hover,
mobile_behavior: :drawer,
animations: true,
accessibility: true,
mobile_breakpoint: 768,
max_dropdown_depth: 5,
auto_close_delay: 300,
touch_gestures: true,
analytics: false
}
end
@doc """
Validates a navigation item structure.
Returns `{:ok, item}` if valid, `{:error, reason}` if invalid.
## Examples
iex> item = %{id: "home", label: "Home", navigate: "/"}
iex> NavBuddy.validate_nav_item(item)
{:ok, %{id: "home", label: "Home", navigate: "/"}}
iex> NavBuddy.validate_nav_item(%{label: "Home"})
{:error, "Navigation item missing required field 'id'. Required fields: id (string), label (string). Optional fields: navigate, icon, dropdown, mega_menu, active, disabled, permissions"}
"""
def validate_nav_item(item) when is_map(item) do
with :ok <- validate_required_fields(item),
:ok <- validate_field_types(item),
:ok <- validate_navigation_logic(item),
{:ok, _} <- validate_dropdown(Map.get(item, :dropdown)),
{:ok, _} <- validate_mega_menu(Map.get(item, :mega_menu)) do
{:ok, item}
else
{:error, reason} -> {:error, reason}
end
end
def validate_nav_item(item) do
{:error, "Navigation item must be a map, got: #{inspect(item)}"}
end
@doc """
Validates a list of navigation items.
## Examples
iex> items = [%{id: "home", label: "Home"}]
iex> NavBuddy.validate_nav_items(items)
{:ok, [%{id: "home", label: "Home"}]}
"""
def validate_nav_items(items) when is_list(items) do
Enum.reduce_while(items, {:ok, []}, fn item, {:ok, acc} ->
case validate_nav_item(item) do
{:ok, validated_item} -> {:cont, {:ok, [validated_item | acc]}}
{:error, reason} -> {:halt, {:error, "Invalid navigation item: #{reason}"}}
end
end)
|> case do
{:ok, validated_items} -> {:ok, Enum.reverse(validated_items)}
error -> error
end
end
def validate_nav_items(items) do
{:error, "Navigation items must be a list, got: #{inspect(items)}"}
end
defp validate_required_fields(%{id: id, label: label})
when is_binary(id) and is_binary(label) do
:ok
end
defp validate_required_fields(%{id: id}) when is_binary(id) do
{:error,
"Navigation item missing required field 'label'. Required fields: id (string), label (string). Optional fields: navigate, icon, dropdown, mega_menu, active, disabled, permissions"}
end
defp validate_required_fields(%{label: label}) when is_binary(label) do
{:error,
"Navigation item missing required field 'id'. Required fields: id (string), label (string). Optional fields: navigate, icon, dropdown, mega_menu, active, disabled, permissions"}
end
defp validate_required_fields(%{id: id}) do
{:error, "Navigation item field 'id' must be a string, got: #{inspect(id)}"}
end
defp validate_required_fields(%{label: label}) do
{:error, "Navigation item field 'label' must be a string, got: #{inspect(label)}"}
end
defp validate_required_fields(_) do
{:error,
"Navigation item missing required fields 'id' and 'label'. Required fields: id (string), label (string). Optional fields: navigate, icon, dropdown, mega_menu, active, disabled, permissions"}
end
defp validate_field_types(item) do
Enum.reduce_while(item, :ok, fn {key, value}, :ok ->
case validate_field_type(key, value) do
:ok -> {:cont, :ok}
{:error, reason} -> {:halt, {:error, reason}}
end
end)
end
defp validate_field_type(:navigate, value) when is_binary(value), do: :ok
defp validate_field_type(:navigate, nil), do: :ok
defp validate_field_type(:navigate, value),
do: {:error, "Field 'navigate' must be a string, got: #{inspect(value)}"}
defp validate_field_type(:icon, value) when is_binary(value), do: :ok
defp validate_field_type(:icon, nil), do: :ok
defp validate_field_type(:icon, value),
do: {:error, "Field 'icon' must be a string, got: #{inspect(value)}"}
defp validate_field_type(:active, value) when is_boolean(value), do: :ok
defp validate_field_type(:active, nil), do: :ok
defp validate_field_type(:active, value),
do: {:error, "Field 'active' must be a boolean, got: #{inspect(value)}"}
defp validate_field_type(:disabled, value) when is_boolean(value), do: :ok
defp validate_field_type(:disabled, nil), do: :ok
defp validate_field_type(:disabled, value),
do: {:error, "Field 'disabled' must be a boolean, got: #{inspect(value)}"}
defp validate_field_type(:permissions, value) when is_binary(value), do: :ok
defp validate_field_type(:permissions, value) when is_list(value) do
if Enum.all?(value, &is_binary/1) do
:ok
else
{:error,
"Field 'permissions' when a list must contain only strings, got: #{inspect(value)}"}
end
end
defp validate_field_type(:permissions, nil), do: :ok
defp validate_field_type(:permissions, value),
do:
{:error, "Field 'permissions' must be a string or list of strings, got: #{inspect(value)}"}
defp validate_field_type(:dropdown, value) when is_list(value), do: :ok
defp validate_field_type(:dropdown, nil), do: :ok
defp validate_field_type(:dropdown, value),
do: {:error, "Field 'dropdown' must be a list of navigation items, got: #{inspect(value)}"}
defp validate_field_type(:mega_menu, value) when is_map(value), do: :ok
defp validate_field_type(:mega_menu, nil), do: :ok
defp validate_field_type(:mega_menu, value),
do: {:error, "Field 'mega_menu' must be a map, got: #{inspect(value)}"}
# Allow id and label since they're already validated above
defp validate_field_type(:id, _), do: :ok
defp validate_field_type(:label, _), do: :ok
defp validate_field_type(key, value),
do:
{:error,
"Unknown field '#{key}' with value #{inspect(value)}. Allowed fields: id, label, navigate, icon, dropdown, mega_menu, active, disabled, permissions"}
defp validate_navigation_logic(%{dropdown: dropdown, mega_menu: mega_menu})
when not is_nil(dropdown) and not is_nil(mega_menu) do
{:error,
"Navigation item cannot have both 'dropdown' and 'mega_menu'. Choose one or the other."}
end
defp validate_navigation_logic(%{dropdown: dropdown, navigate: navigate})
when not is_nil(dropdown) and not is_nil(navigate) do
{:error,
"Navigation item with 'dropdown' should not have 'navigate' field. Dropdown items should navigate through their child items."}
end
defp validate_navigation_logic(%{mega_menu: mega_menu, navigate: navigate})
when not is_nil(mega_menu) and not is_nil(navigate) do
{:error,
"Navigation item with 'mega_menu' should not have 'navigate' field. Mega menu items should navigate through their child items."}
end
defp validate_navigation_logic(_), do: :ok
defp validate_dropdown(nil), do: {:ok, nil}
defp validate_dropdown(dropdown) when is_list(dropdown) do
case validate_nav_items(dropdown) do
{:ok, _} -> {:ok, dropdown}
{:error, reason} -> {:error, "Invalid dropdown items: #{reason}"}
end
end
defp validate_dropdown(dropdown),
do: {:error, "Dropdown must be a list of navigation items, got: #{inspect(dropdown)}"}
defp validate_mega_menu(nil), do: {:ok, nil}
# Handle simple mega menu: %{"Category" => [items]}
defp validate_mega_menu(mega_menu) when is_map(mega_menu) do
# Check if it's a simple mega menu (all values are lists)
if Enum.all?(mega_menu, fn {key, value} -> is_binary(key) and is_list(value) end) do
validate_simple_mega_menu(mega_menu)
else
validate_structured_mega_menu(mega_menu)
end
end
defp validate_mega_menu(mega_menu),
do: {:error, "Mega menu must be a map, got: #{inspect(mega_menu)}"}
defp validate_simple_mega_menu(mega_menu) do
Enum.reduce_while(mega_menu, {:ok, mega_menu}, fn {category, items}, {:ok, acc} ->
case validate_nav_items(items) do
{:ok, _} ->
{:cont, {:ok, acc}}
{:error, reason} ->
{:halt, {:error, "Invalid items in mega menu category '#{category}': #{reason}"}}
end
end)
end
defp validate_structured_mega_menu(%{title: title, sections: sections})
when is_binary(title) and is_list(sections) do
case validate_mega_menu_sections(sections) do
{:ok, _} -> {:ok, %{title: title, sections: sections}}
{:error, reason} -> {:error, "Invalid mega menu sections: #{reason}"}
end
end
defp validate_structured_mega_menu(%{sections: sections}) when is_list(sections) do
{:error,
"Structured mega menu missing required field 'title'. Required: title (string), sections (list). Optional: description (string)"}
end
defp validate_structured_mega_menu(%{title: title}) when is_binary(title) do
{:error,
"Structured mega menu missing required field 'sections'. Required: title (string), sections (list). Optional: description (string)"}
end
defp validate_structured_mega_menu(%{title: title}) do
{:error, "Structured mega menu field 'title' must be a string, got: #{inspect(title)}"}
end
defp validate_structured_mega_menu(%{sections: sections}) do
{:error, "Structured mega menu field 'sections' must be a list, got: #{inspect(sections)}"}
end
defp validate_structured_mega_menu(mega_menu) do
{:error,
"Structured mega menu missing required fields. Required: title (string), sections (list). Optional: description (string). Got: #{inspect(mega_menu)}"}
end
defp validate_mega_menu_sections(sections) do
Enum.reduce_while(sections, {:ok, []}, fn section, {:ok, acc} ->
case validate_mega_menu_section(section) do
{:ok, validated_section} -> {:cont, {:ok, [validated_section | acc]}}
{:error, reason} -> {:halt, {:error, reason}}
end
end)
|> case do
{:ok, validated_sections} -> {:ok, Enum.reverse(validated_sections)}
error -> error
end
end
defp validate_mega_menu_section(%{title: title, items: items})
when is_binary(title) and is_list(items) do
case validate_nav_items(items) do
{:ok, _} -> {:ok, %{title: title, items: items}}
{:error, reason} -> {:error, "Invalid items in mega menu section '#{title}': #{reason}"}
end
end
defp validate_mega_menu_section(%{items: items}) when is_list(items) do
{:error,
"Mega menu section missing required field 'title'. Required: title (string), items (list). Optional: description (string)"}
end
defp validate_mega_menu_section(%{title: title}) when is_binary(title) do
{:error,
"Mega menu section missing required field 'items'. Required: title (string), items (list). Optional: description (string)"}
end
defp validate_mega_menu_section(%{title: title}) do
{:error, "Mega menu section field 'title' must be a string, got: #{inspect(title)}"}
end
defp validate_mega_menu_section(%{items: items}) do
{:error, "Mega menu section field 'items' must be a list, got: #{inspect(items)}"}
end
defp validate_mega_menu_section(section) do
{:error,
"Mega menu section must have 'title' (string) and 'items' (list). Got: #{inspect(section)}"}
end
@doc """
Creates a simple navigation item.
## Examples
iex> NavBuddy.nav_item("home", "Home", "/")
%{id: "home", label: "Home", navigate: "/"}
iex> NavBuddy.nav_item("admin", "Admin", "/admin", permissions: "admin")
%{id: "admin", label: "Admin", navigate: "/admin", permissions: "admin"}
"""
def nav_item(id, label, navigate \\ nil, opts \\ []) do
base = %{id: id, label: label}
base = if navigate, do: Map.put(base, :navigate, navigate), else: base
Enum.reduce(opts, base, fn {key, value}, acc ->
Map.put(acc, key, value)
end)
end
@doc """
Creates a navigation item with dropdown.
## Examples
iex> dropdown_items = [
...> %{id: "web", label: "Web Apps", navigate: "/products/web"},
...> %{id: "mobile", label: "Mobile Apps", navigate: "/products/mobile"}
...> ]
iex> NavBuddy.dropdown_item("products", "Products", dropdown_items)
%{id: "products", label: "Products", dropdown: [%{id: "web", label: "Web Apps", navigate: "/products/web"}, %{id: "mobile", label: "Mobile Apps", navigate: "/products/mobile"}]}
"""
def dropdown_item(id, label, dropdown_items, opts \\ []) do
base = %{id: id, label: label, dropdown: dropdown_items}
Enum.reduce(opts, base, fn {key, value}, acc ->
Map.put(acc, key, value)
end)
end
@doc """
Creates a navigation item with simple mega menu.
## Examples
iex> categories = %{
...> "Frontend" => [%{id: "react", label: "React", navigate: "/tools/react"}],
...> "Backend" => [%{id: "elixir", label: "Elixir", navigate: "/tools/elixir"}]
...> }
iex> NavBuddy.mega_menu_item("tools", "Tools", categories)
%{id: "tools", label: "Tools", mega_menu: %{"Frontend" => [%{id: "react", label: "React", navigate: "/tools/react"}], "Backend" => [%{id: "elixir", label: "Elixir", navigate: "/tools/elixir"}]}}
"""
def mega_menu_item(id, label, categories, opts \\ []) do
base = %{id: id, label: label, mega_menu: categories}
Enum.reduce(opts, base, fn {key, value}, acc ->
Map.put(acc, key, value)
end)
end
@doc """
Filters navigation items based on user permissions.
Removes items that the user doesn't have permission to see. Items without
permissions are always visible. When an item has a list of permissions,
the user must have ALL of them to see the item.
## Examples
iex> items = [
...> %{id: "home", label: "Home", navigate: "/"},
...> %{id: "admin", label: "Admin", navigate: "/admin", permissions: "admin"},
...> %{id: "user", label: "Profile", navigate: "/profile", permissions: "user"}
...> ]
iex> NavBuddy.filter_by_permissions(items, ["user"])
[
%{id: "home", label: "Home", navigate: "/"},
%{id: "user", label: "Profile", navigate: "/profile", permissions: "user"}
]
"""
def filter_by_permissions(items, user_permissions)
when is_list(items) and is_list(user_permissions) do
Enum.filter(items, &has_permission?(&1, user_permissions))
|> Enum.map(&filter_item_permissions(&1, user_permissions))
|> Enum.filter(&item_has_content?/1)
end
def filter_by_permissions(items, user_permissions)
when is_list(items) and is_binary(user_permissions) do
filter_by_permissions(items, [user_permissions])
end
def filter_by_permissions(items, _) when is_list(items) do
items
end
defp has_permission?(%{permissions: nil}, _), do: true
defp has_permission?(%{permissions: item_perms}, user_perms) when is_binary(item_perms) do
item_perms in user_perms
end
defp has_permission?(%{permissions: item_perms}, user_perms) when is_list(item_perms) do
# Item requires ALL permissions in the list
Enum.all?(item_perms, &(&1 in user_perms))
end
defp has_permission?(_, _), do: true
defp filter_item_permissions(item, user_permissions) do
item
|> filter_dropdown_permissions(user_permissions)
|> filter_mega_menu_permissions(user_permissions)
end
defp filter_dropdown_permissions(%{dropdown: dropdown} = item, user_permissions)
when is_list(dropdown) do
filtered_dropdown = filter_by_permissions(dropdown, user_permissions)
%{item | dropdown: filtered_dropdown}
end
defp filter_dropdown_permissions(item, _), do: item
defp filter_mega_menu_permissions(%{mega_menu: mega_menu} = item, user_permissions)
when is_map(mega_menu) do
case mega_menu do
# Handle simple map structure: %{"category" => [items]}
%{sections: _} = structured_mega_menu ->
filtered_sections =
Enum.map(structured_mega_menu.sections, fn section ->
%{section | items: filter_by_permissions(section.items, user_permissions)}
end)
%{item | mega_menu: %{structured_mega_menu | sections: filtered_sections}}
# Handle simple map structure: %{"category" => [items]}
simple_mega_menu ->
filtered_mega_menu =
simple_mega_menu
|> Enum.map(fn {category, items} ->
{category, filter_by_permissions(items, user_permissions)}
end)
|> Enum.filter(fn {_category, items} -> length(items) > 0 end)
|> Enum.into(%{})
%{item | mega_menu: filtered_mega_menu}
end
end
defp filter_mega_menu_permissions(item, _), do: item
# Check if an item still has content after filtering
defp item_has_content?(%{dropdown: dropdown}) when is_list(dropdown) do
length(dropdown) > 0
end
defp item_has_content?(%{mega_menu: mega_menu}) when is_map(mega_menu) do
case mega_menu do
%{sections: sections} when is_list(sections) ->
Enum.any?(sections, fn section -> length(section.items) > 0 end)
simple_mega_menu ->
map_size(simple_mega_menu) > 0
end
end
defp item_has_content?(_), do: true
@doc """
Generates a unique menu ID for accessibility.
## Examples
iex> NavBuddy.generate_menu_id("main-nav", "products")
"main-nav-products-menu"
"""
def generate_menu_id(nav_id, item_id) do
"#{nav_id}-#{item_id}-menu"
end
@doc """
Finds the active navigation item based on the current path.
## Examples
iex> items = [%{id: "home", navigate: "/"}, %{id: "about", navigate: "/about"}]
iex> NavBuddy.find_active_item(items, "/about")
[%{id: "home", navigate: "/", active: false}, %{id: "about", navigate: "/about", active: true}]
"""
def find_active_item(items, current_path) when is_list(items) and is_binary(current_path) do
Enum.map(items, fn item ->
active = determine_active(item, current_path)
Map.put(item, :active, active)
end)
end
defp determine_active(%{navigate: navigate} = item, current_path) when is_binary(navigate) do
cond do
navigate == current_path -> true
has_dropdown_active?(Map.get(item, :dropdown), current_path) -> true
has_mega_menu_active?(Map.get(item, :mega_menu), current_path) -> true
true -> false
end
end
defp determine_active(_, _), do: false
defp has_dropdown_active?(nil, _), do: false
defp has_dropdown_active?(dropdown, current_path) when is_list(dropdown) do
Enum.any?(dropdown, &determine_active(&1, current_path))
end
defp has_mega_menu_active?(nil, _), do: false
defp has_mega_menu_active?(%{sections: sections}, current_path) when is_list(sections) do
Enum.any?(sections, fn %{items: items} ->
Enum.any?(items, &determine_active(&1, current_path))
end)
end
@doc """
Generates breadcrumb navigation from the active path.
## Examples
iex> items = [%{id: "home", label: "Home", navigate: "/"}]
iex> NavBuddy.generate_breadcrumbs(items, "/")
[%{id: "home", label: "Home", navigate: "/"}]
"""
def generate_breadcrumbs(items, current_path) when is_list(items) and is_binary(current_path) do
case find_breadcrumb_path(items, current_path, []) do
[] -> []
path -> Enum.reverse(path)
end
end
defp find_breadcrumb_path([], _, _), do: []
defp find_breadcrumb_path([item | rest], current_path, path) do
case find_in_item(item, current_path, [item | path]) do
[] -> find_breadcrumb_path(rest, current_path, path)
found_path -> found_path
end
end
defp find_in_item(%{navigate: navigate}, current_path, path) when navigate == current_path do
path
end
defp find_in_item(%{dropdown: dropdown} = item, current_path, path) when is_list(dropdown) do
case find_breadcrumb_path(dropdown, current_path, [item | path]) do
[] -> []
found_path -> found_path
end
end
defp find_in_item(%{mega_menu: %{sections: sections}}, current_path, path)
when is_list(sections) do
sections
|> Enum.flat_map(fn %{items: items} -> items end)
|> find_breadcrumb_path(current_path, path)
end
defp find_in_item(_, _, _), do: []
@doc """
Example navigation items showcasing all features.
Returns a comprehensive navigation structure demonstrating:
- Simple links
- Single-level dropdowns
- Multi-level dropdowns
- Mega menus
- Icons and descriptions
"""
def example_navigation do
[
%{
id: "home",
label: "Home",
href: "/",
icon: "home"
},
%{
id: "products",
label: "Products",
href: "/products",
icon: "package",
dropdown: [
%{
id: "web-apps",
label: "Web Applications",
href: "/products/web",
icon: "globe"
},
%{
id: "mobile-apps",
label: "Mobile Applications",
href: "/products/mobile",
icon: "smartphone"
},
%{
id: "desktop",
label: "Desktop Software",
href: "/products/desktop",
icon: "monitor"
}
]
},
%{
id: "services",
label: "Services",
icon: "settings",
dropdown: [
%{
id: "development",
label: "Development",
icon: "code",
dropdown: [
%{id: "frontend", label: "Frontend Development", href: "/services/frontend"},
%{id: "backend", label: "Backend Development", href: "/services/backend"},
%{id: "fullstack", label: "Full Stack Development", href: "/services/fullstack"}
]
},
%{
id: "design",
label: "Design",
icon: "palette",
dropdown: [
%{id: "ui-design", label: "UI Design", href: "/services/ui"},
%{id: "ux-design", label: "UX Design", href: "/services/ux"},
%{id: "branding", label: "Branding", href: "/services/branding"}
]
},
%{
id: "consulting",
label: "Consulting",
href: "/services/consulting",
icon: "users"
}
]
},
%{
id: "solutions",
label: "Solutions",
icon: "layers",
mega_menu: %{
title: "Business Solutions",
description: "Comprehensive solutions tailored to your business needs",
sections: [
%{
title: "For Small Business",
description: "Perfect for startups and growing companies",
items: [
%{
id: "starter",
label: "Starter Package",
href: "/solutions/starter",
icon: "zap"
},
%{
id: "growth",
label: "Growth Package",
href: "/solutions/growth",
icon: "trending-up"
},
%{id: "pro", label: "Professional", href: "/solutions/pro", icon: "star"}
]
},
%{
title: "For Enterprise",
description: "Scalable solutions for large organizations",
items: [
%{
id: "enterprise",
label: "Enterprise Suite",
href: "/solutions/enterprise",
icon: "building"
},
%{
id: "custom",
label: "Custom Solutions",
href: "/solutions/custom",
icon: "tool"
},
%{
id: "support",
label: "24/7 Support",
href: "/solutions/support",
icon: "headphones"
}
]
},
%{
title: "Industry Specific",
description: "Specialized solutions by industry",
items: [
%{
id: "healthcare",
label: "Healthcare",
href: "/solutions/healthcare",
icon: "heart"
},
%{
id: "finance",
label: "Financial Services",
href: "/solutions/finance",
icon: "dollar-sign"
},
%{id: "education", label: "Education", href: "/solutions/education", icon: "book"}
]
}
]
}
},
%{
id: "about",
label: "About",
href: "/about",
icon: "info"
},
%{
id: "contact",
label: "Contact",
href: "/contact",
icon: "mail"
}
]
end
end