LineaUI.Components.AppShell (linea_ui v0.1.1)

Copy Markdown View Source

LineOne's application shell: the page frame, the main sidebar (the icon rail), the sidebar panel with its collapsible groups, the minimized panel, the sideblock and the horizontal navigation, the header, the right sidebar, the page header and the dark mode and monochrome toggles.

<.app_shell id="shell" sidebar_open={@sidebar_open} on_toggle="sidebar">
  <:sidebar>
    <.main_sidebar current={@current_path}>
      <:logo navigate={~p"/"} src={~p"/images/logo.svg"} />
      <:item navigate={~p"/dashboards"} label="Dashboards"><.icon name="home" /></:item>
      <:bottom>
        <.sidebar_profile id="profile" src={@user.avatar} name={@user.name} subtitle="Designer">
          <:item navigate={~p"/profile"} color="warning" title="Profile" description="Your profile setting">
            <.icon name="user" />
          </:item>
        </.sidebar_profile>
      </:bottom>
    </.main_sidebar>
    <.sidebar_panel title="Layouts">
      <.sidebar_nav>
        <.sidebar_nav_group label="Starter" current={@current_path}>
          <:item navigate={~p"/starter/blurred"}>Blurred Header</:item>
        </.sidebar_nav_group>
      </.sidebar_nav>
    </.sidebar_panel>
  </:sidebar>
  <:header>
    <.header right_sidebar="right-sidebar">
      <:notifications>…</:notifications>
    </.header>
  </:header>
  <:aside>
    <.right_sidebar id="right-sidebar">…</.right_sidebar>
  </:aside>
  <.page_header title="Avatar">
    <:crumb navigate={~p"/elements"}>Elements</:crumb>
    <:crumb>Avatar</:crumb>
  </.page_header>
  …
</.app_shell>

The state LineOne keeps in <body> classes lives on the shell's root element: is-sidebar-open, has-min-sidebar (layout="minimal"), navigation:sideblock and navigation:horizontal, is-header-blur and is-header-not-sticky.

LiveView

sidebar_open is the server's state; the browser toggles without a round trip and tells the server through on_toggle. A choice made in the browser survives patches and live navigation (navigate between LiveViews rendering the same shell id) unless the server changes sidebar_open; at xl and up it is also kept in localStorage["linea-sidebar"] (persist). Below xl the sidebar closes, as in LineOne. LineaUI.open/close/toggle(socket_or_js, "shell") drive it from the server or a JS pipeline.

Active items come from active on each item or from current (the current path, compared with the item's navigate, patch or href), so they follow live navigation.

Dark mode (<html class="dark">, localStorage["dark-mode"]) and monochrome mode (<body class="is-monochrome">, localStorage["monochrome-mode"]) are client state; put <.theme_script /> in the root layout's <head> so the first paint already has them.

Setup

The hooks (LineaUI.AppShell, LineaUI.SidebarGroup, LineaUI.ShellPopper, LineaUI.ShellTooltips, LineaUI.RightSidebar, LineaUI.ThemeToggle, LineaUI.MonochromeToggle) come with LineaUI's Hooks. Render the shell in the app layout of a live_session and give it the current path from an on_mount hook, so the active items follow navigate:

def on_mount(:current_path, _params, _session, socket) do
  {:cont,
   Phoenix.LiveView.attach_hook(socket, :current_path, :handle_params, fn _params, url, socket ->
     {:cont, assign(socket, :current_path, URI.parse(url).path)}
   end)}
end

Layouts

layout="sidebar" (the default) takes a main_sidebar/1 and a sidebar_panel/1; "minimal" adds a sidebar_panel_min/1; "sideblock" takes a sideblock/1; "horizontal" keeps the main sidebar and panel for phones and puts horizontal_nav_menu/1s in the :nav slot of header/1.

Summary

Functions

The page frame: the sidebar, the header, the drawers and the <main> content, with LineOne's body classes.

The header: LineOne's animated sidebar toggle, the search bar (and the full screen search of phones), the dark mode and monochrome toggles, the notifications, the right sidebar toggle and a profile slot.

The line between the links of a horizontal navigation box.

A link of a horizontal navigation box (inside horizontal_nav_menu/1). A click closes the box.

A dropdown of the horizontal navigation (the :nav slot of header/1): an icon and a label, and a box of links (:item slots, or horizontal_nav_link/1 and horizontal_nav_divider/1 as content). It is active with active, or when one of its items is active or matches current.

The main sidebar: LineOne's icon rail with the logo, the section links (with a tooltip on the right and an active state) and the bottom links.

One icon link of the main sidebar. With label it gets LineOne's tooltip on its right (inside a main_sidebar/1, which positions it) and that accessible name.

LineOne's monochrome mode: the palette button of the header (variant="button") or the switch of its monochrome page (variant="switch", the label as content). It toggles the is-monochrome class of <body> and keeps it in localStorage["monochrome-mode"]; every toggle on the page follows. on_change gets %{"monochrome" => boolean} or runs.

The title of a page with LineOne's breadcrumb next to it: the crumbs with a link are followed by a chevron, the last one is plain text.

LineOne's right sidebar: a drawer from the right with an overlay, a top row (:title and the close button), the content and a :footer (LineOne's floating tab bar).

The header button (LineOne's grid icon) that toggles a right_sidebar/1; header/1 renders one with right_sidebar="id".

The line between the lists of the minimized panel.

A list of icon links of the minimized panel; the first one keeps LineOne's top margin.

An icon link of the minimized panel. Active links are primary soft, the others take color.

A list of the sidebar panel (or the sideblock): sidebar_nav_link/1 and sidebar_nav_group/1.

The line between the lists of the sidebar panel.

A collapsible group of the sidebar panel. It opens when one of its items is active (or with open); the groups that share name open one at a time, like LineOne's accordion. A group opened or closed in the browser stays so across patches unless the server changes open.

A plain link of the sidebar panel, active with active or when its path is current.

The sidebar panel next to the main sidebar: its title, the close button LineOne shows below xl, and the scrolling body (sidebar_nav/1, sidebar_nav_divider/1).

LineOne's minimized sidebar panel (layout="minimal"): shown next to the main sidebar while the panel is closed. A header (an avatar), the icon links (sidebar_min_group/1, sidebar_min_link/1, sidebar_min_divider/1) and a footer.

The avatar at the bottom of the main sidebar and the profile box it opens on its right: the person, a list of links with a colored icon, and a footer (LineOne's logout button).

LineOne's animated hamburger: three bars that turn into an arrow while the sidebar is open. Inside an app_shell/1 it toggles the sidebar; header/1 already renders one.

LineOne's sideblock (layout="sideblock"): one wide sidebar with the logo, the close button and the navigation (sidebar_nav/1). It goes in the :sidebar slot of <.app_shell layout="sideblock">.

The script for the root layout's <head> that applies the stored dark mode before the first paint (LineOne's "prevent flickering" script) and the stored monochrome mode as soon as <body> exists.

LineOne's dark mode button: a moon in dark mode, a sun in light mode. It toggles the dark class of <html> and keeps it in localStorage["dark-mode"]. on_change gets %{"dark" => boolean} (an event name) or runs (a %JS{}).

Functions

app_shell(assigns)

The page frame: the sidebar, the header, the drawers and the <main> content, with LineOne's body classes.

on_toggle runs when the browser opens or closes the sidebar (a toggle button, a close button, a breakpoint change, or a remembered choice applied on mount). An event name is pushed with %{"id" => id, "open" => boolean}; a %JS{} is executed.

<.app_shell id="shell" layout="minimal" sidebar_open={@sidebar_open} on_toggle="sidebar">
  <:sidebar>…</:sidebar>
  <:header><.header /></:header>
  <.page_header title="Orders" />
</.app_shell>

def handle_event("sidebar", %{"open" => open}, socket),
  do: {:noreply, assign(socket, :sidebar_open, open)}

Attributes

  • id (:string) - Defaults to "app-shell".
  • layout (:string) - sidebar is the main sidebar with its panel, minimal shows the minimized panel while the panel is closed, sideblock is one wide sidebar, horizontal puts the navigation in the header. Defaults to "sidebar". Must be one of "sidebar", "minimal", "sideblock", or "horizontal".
  • sidebar_open (:boolean) - Whether the sidebar is open; defaults to open for sidebar and sideblock. Defaults to nil.
  • header_blur (:boolean) - LineOne's is-header-blur. Defaults to true.
  • header_sticky (:boolean) - false is LineOne's is-header-not-sticky. Defaults to true.
  • persist (:boolean) - Keep the sidebar choice in localStorage. Defaults to true.
  • preloader (:boolean) - LineOne's app preloader, faded out on mount. Defaults to false.
  • on_toggle (:any) - An event name or a %JS{}. Defaults to nil.
  • target (:any) - The phx-target of on_toggle. Defaults to nil.
  • main_class (:any) - Merged into the classes of <main class="main-content">. Defaults to nil.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • sidebar - The main sidebar and its panel, or the sideblock.
  • header - The header/1.
  • aside - Fixed parts: the right sidebar, drawers.
  • inner_block

header(assigns)

The header: LineOne's animated sidebar toggle, the search bar (and the full screen search of phones), the dark mode and monochrome toggles, the notifications, the right sidebar toggle and a profile slot.

With a :nav slot it is LineOne's horizontal navigation header: the toggle only on phones, the :brand on larger screens and the navigation row (horizontal_nav_menu/1) below.

<.header right_sidebar="right-sidebar">
  <:start><.page_header title="Orders" class="py-0 lg:py-0" /></:start>
  <:search_results>…</:search_results>
  <:notifications>…</:notifications>
</.header>

The search input takes search_input (a map of attributes: name, value, phx-change, phx-debounce…).

Attributes

  • id (:string) - Defaults to "app-header".
  • toggle (:boolean) - The sidebar toggle. Defaults to true.
  • search (:boolean) - Defaults to true.
  • search_placeholder (:string) - Defaults to "Search here...".
  • search_input (:map) - Defaults to %{}.
  • theme_toggle (:boolean) - Defaults to true.
  • monochrome_toggle (:boolean) - Defaults to true.
  • right_sidebar (:string) - The id of the right_sidebar/1 its button toggles. Defaults to nil.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • brand - The logo and name of a horizontal header. Accepts attributes:
    • href (:string)
    • navigate (:string)
    • src (:string)
    • label (:string)
  • start - Content after the toggle: a title, a breadcrumb.
  • search_results - The box under the search bar, shown while it has the focus.
  • mobile_search - What the full screen search of phones shows under its input.
  • actions - Buttons before the theme toggle.
  • notifications - The notification box; the bell opens it. Accepts attributes:
    • dot (:boolean) - LineOne's pinging dot; true by default.
    • label (:string)
  • profile - Content at the end, such as an avatar menu.
  • nav - The navigation row of a horizontal header.

horizontal_nav_divider(assigns)

The line between the links of a horizontal navigation box.

<.horizontal_nav_divider />

Attributes

  • class (:any) - Defaults to nil.

horizontal_nav_link(assigns)

A link of a horizontal navigation box (inside horizontal_nav_menu/1). A click closes the box.

<.horizontal_nav_link navigate={~p"/reports"} current={@current_path}>Reports</.horizontal_nav_link>

Attributes

  • active (:boolean) - Defaults to false.
  • current (:string) - The current path; the link is active when it matches. Defaults to nil.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted. Supports all globals plus: ["href", "navigate", "patch", "method", "replace"].

Slots

  • inner_block (required)

horizontal_nav_menu(assigns)

A dropdown of the horizontal navigation (the :nav slot of header/1): an icon and a label, and a box of links (:item slots, or horizontal_nav_link/1 and horizontal_nav_divider/1 as content). It is active with active, or when one of its items is active or matches current.

<.horizontal_nav_menu id="nav-pages" label="Pages" current={@current_path}>
  <:icon><svg>…</svg></:icon>
  <:item navigate={~p"/pages/blank"}>Blank</:item>
</.horizontal_nav_menu>

Attributes

  • id (:string) (required)
  • label (:string) (required)
  • active (:boolean) - Defaults to false.
  • current (:string) - Defaults to nil.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • icon
  • item - Accepts attributes:
    • href (:string)
    • navigate (:string)
    • patch (:string)
    • active (:boolean)
  • inner_block

main_sidebar(assigns)

The main sidebar: LineOne's icon rail with the logo, the section links (with a tooltip on the right and an active state) and the bottom links.

<.main_sidebar current={@current_path}>
  <:logo navigate={~p"/"} src={~p"/images/app-logo.svg"} />
  <:item navigate={~p"/forms"} label="Forms"><svg>…</svg></:item>
  <:bottom><.main_sidebar_link navigate={~p"/settings"}><svg>…</svg></.main_sidebar_link></:bottom>
</.main_sidebar>

Attributes

  • id (:string) - Defaults to "main-sidebar".
  • current (:string) - The current path; an item whose link matches it is active. Defaults to nil.
  • centered (:boolean) - Centers the links between the logo and the bottom. Defaults to false.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • logo - Accepts attributes:
    • href (:string)
    • navigate (:string)
    • patch (:string)
    • src (:string)
    • alt (:string)
  • item - Accepts attributes:
    • href (:string)
    • navigate (:string)
    • patch (:string)
    • label (:string) - The tooltip and the accessible name.
    • active (:boolean)
  • bottom

main_sidebar_link(assigns)

One icon link of the main sidebar. With label it gets LineOne's tooltip on its right (inside a main_sidebar/1, which positions it) and that accessible name.

<.main_sidebar_link navigate={~p"/settings"} label="Settings" current={@current_path}>
  <svg class="size-7">…</svg>
</.main_sidebar_link>

Attributes

  • label (:string) - The tooltip and the accessible name. Defaults to nil.
  • active (:boolean) - Defaults to false.
  • current (:string) - The current path; the link is active when it matches. Defaults to nil.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted. Supports all globals plus: ["href", "navigate", "patch", "method", "replace"].

Slots

  • inner_block (required) - The icon.

monochrome_toggle(assigns)

LineOne's monochrome mode: the palette button of the header (variant="button") or the switch of its monochrome page (variant="switch", the label as content). It toggles the is-monochrome class of <body> and keeps it in localStorage["monochrome-mode"]; every toggle on the page follows. on_change gets %{"monochrome" => boolean} or runs.

<.monochrome_toggle id="mono" />
<.monochrome_toggle id="mono-switch" variant="switch">Toggle Monochrome Mode</.monochrome_toggle>

Attributes

  • id (:string) - Defaults to "monochrome-toggle".
  • variant (:string) - Defaults to "button". Must be one of "button", or "switch".
  • on_change (:any) - Defaults to nil.
  • target (:any) - Defaults to nil.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • inner_block

page_header(assigns)

The title of a page with LineOne's breadcrumb next to it: the crumbs with a link are followed by a chevron, the last one is plain text.

<.page_header title="Avatar">
  <:crumb navigate={~p"/elements"}>Elements</:crumb>
  <:crumb>Avatar</:crumb>
  <:actions><.button color="primary">New</.button></:actions>
</.page_header>

Attributes

  • title (:string) (required)
  • class (:any) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • crumb - Accepts attributes:
    • href (:string)
    • navigate (:string)
    • patch (:string)
  • actions - Buttons or filters at the right end of the row.
  • inner_block - Content after the breadcrumb.

right_sidebar(assigns)

LineOne's right sidebar: a drawer from the right with an overlay, a top row (:title and the close button), the content and a :footer (LineOne's floating tab bar).

Opened with open, right_sidebar_toggle/1, or LineaUI.open/close/toggle(id) (also from the server with a socket). Esc, the overlay and the close button close it. on_open and on_close take an event name, pushed with %{"id" => id}, or a %JS{}.

<.right_sidebar id="right-sidebar" open={@panel_open} on_close="panel-closed">
  <:title><span class="text-xs">25 May, 2022</span></:title>
  <div class="px-3">…</div>
</.right_sidebar>

Attributes

  • id (:string) (required)
  • open (:boolean) - Defaults to false.
  • label (:string) - Defaults to "Sidebar".
  • on_open (:any) - Defaults to nil.
  • on_close (:any) - Defaults to nil.
  • target (:any) - Defaults to nil.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • title
  • inner_block
  • footer

sideblock(assigns)

LineOne's sideblock (layout="sideblock"): one wide sidebar with the logo, the close button and the navigation (sidebar_nav/1). It goes in the :sidebar slot of <.app_shell layout="sideblock">.

<.sideblock>
  <:logo navigate={~p"/"} src={~p"/images/app-logo.svg"} />
  <.sidebar_nav>
    <.sidebar_nav_group label="Orders" current={@current_path}>
      <:item navigate={~p"/orders"}>All orders</:item>
    </.sidebar_nav_group>
  </.sidebar_nav>
</.sideblock>

Attributes

  • class (:any) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • logo - Accepts attributes:
    • href (:string)
    • navigate (:string)
    • patch (:string)
    • src (:string)
    • alt (:string)
  • inner_block (required)

theme_script(assigns)

The script for the root layout's <head> that applies the stored dark mode before the first paint (LineOne's "prevent flickering" script) and the stored monochrome mode as soon as <body> exists.

<head>
  <.theme_script />
</head>

Attributes

  • Global attributes are accepted.

theme_toggle(assigns)

LineOne's dark mode button: a moon in dark mode, a sun in light mode. It toggles the dark class of <html> and keeps it in localStorage["dark-mode"]. on_change gets %{"dark" => boolean} (an event name) or runs (a %JS{}).

<.theme_toggle id="theme" on_change="theme-changed" />

Attributes

  • id (:string) - Defaults to "theme-toggle".
  • on_change (:any) - Defaults to nil.
  • target (:any) - Defaults to nil.
  • class (:any) - Defaults to nil.
  • Global attributes are accepted.