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)}
endLayouts
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
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) -sidebaris the main sidebar with its panel,minimalshows the minimized panel while the panel is closed,sideblockis one wide sidebar,horizontalputs 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 forsidebarandsideblock. Defaults tonil.header_blur(:boolean) - LineOne'sis-header-blur. Defaults totrue.header_sticky(:boolean) -falseis LineOne'sis-header-not-sticky. Defaults totrue.persist(:boolean) - Keep the sidebar choice inlocalStorage. Defaults totrue.preloader(:boolean) - LineOne's app preloader, faded out on mount. Defaults tofalse.on_toggle(:any) - An event name or a%JS{}. Defaults tonil.target(:any) - Thephx-targetofon_toggle. Defaults tonil.main_class(:any) - Merged into the classes of<main class="main-content">. Defaults tonil.class(:any) - Defaults tonil.- Global attributes are accepted.
Slots
sidebar- The main sidebar and its panel, or the sideblock.header- Theheader/1.aside- Fixed parts: the right sidebar, drawers.inner_block
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 totrue.search(:boolean) - Defaults totrue.search_placeholder(:string) - Defaults to"Search here...".search_input(:map) - Defaults to%{}.theme_toggle(:boolean) - Defaults totrue.monochrome_toggle(:boolean) - Defaults totrue.right_sidebar(:string) - The id of theright_sidebar/1its button toggles. Defaults tonil.class(:any) - Defaults tonil.- 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.
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 tonil.centered(:boolean) - Centers the links between the logo and the bottom. Defaults tofalse.class(:any) - Defaults tonil.- 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
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 tonil.active(:boolean) - Defaults tofalse.current(:string) - The current path; the link is active when it matches. Defaults tonil.class(:any) - Defaults tonil.- Global attributes are accepted. Supports all globals plus:
["href", "navigate", "patch", "method", "replace"].
Slots
inner_block(required) - The icon.
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 tonil.target(:any) - Defaults tonil.class(:any) - Defaults tonil.- Global attributes are accepted.
Slots
inner_block
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 tonil.- 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.
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 tofalse.label(:string) - Defaults to"Sidebar".on_open(:any) - Defaults tonil.on_close(:any) - Defaults tonil.target(:any) - Defaults tonil.class(:any) - Defaults tonil.- Global attributes are accepted.
Slots
titleinner_blockfooter
The header button (LineOne's grid icon) that toggles a right_sidebar/1; header/1 renders one with
right_sidebar="id".
<.right_sidebar_toggle target="right-sidebar" />Attributes
target(:string) (required)class(:any) - Defaults tonil.- Global attributes are accepted.
The line between the lists of the minimized panel.
<.sidebar_min_divider />Attributes
class(:any) - Defaults tonil.
A list of icon links of the minimized panel; the first one keeps LineOne's top margin.
<.sidebar_min_group>
<.sidebar_min_link href="#" label="Home"><svg>…</svg></.sidebar_min_link>
</.sidebar_min_group>Attributes
class(:any) - Defaults tonil.
Slots
inner_block(required)
An icon link of the minimized panel. Active links are primary soft, the others take color.
<.sidebar_min_link navigate={~p"/starred"} label="Starred" color="warning"><svg>…</svg></.sidebar_min_link>Attributes
label(:string) - The accessible name. Defaults tonil.color(:string) - Defaults to"neutral". Must be one of"neutral","primary","secondary","info","success","warning", or"error".active(:boolean) - Defaults tofalse.current(:string) - The current path; the link is active when it matches. Defaults tonil.class(:any) - Defaults tonil.- Global attributes are accepted. Supports all globals plus:
["href", "navigate", "patch", "method", "replace"].
Slots
inner_block(required)
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).
<.sidebar_panel title="Elements">
<.sidebar_nav>
<.sidebar_nav_link navigate={~p"/avatar"} current={@current_path}>Avatar</.sidebar_nav_link>
</.sidebar_nav>
<.sidebar_nav_divider />
<.sidebar_nav>…</.sidebar_nav>
</.sidebar_panel>Attributes
title(:string) (required)class(:any) - Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)
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.
<.sidebar_panel_min>
<:header><div class="avatar size-10">…</div></:header>
<.sidebar_min_group>
<.sidebar_min_link navigate={~p"/inbox"} label="Inbox" current={@current_path}><svg>…</svg></.sidebar_min_link>
<.sidebar_min_link navigate={~p"/trash"} label="Trash" color="error"><svg>…</svg></.sidebar_min_link>
</.sidebar_min_group>
<.sidebar_min_divider />
<.sidebar_min_group>…</.sidebar_min_group>
<:footer><.button variant="outline" shape="pill" icon aria-label="More">…</.button></:footer>
</.sidebar_panel_min>Attributes
class(:any) - Defaults tonil.- Global attributes are accepted.
Slots
headerinner_block(required)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).
<.sidebar_profile id="profile" src={@user.avatar} name="Travis Fuller" subtitle="Product Designer">
<:item navigate={~p"/profile"} color="warning" title="Profile" description="Your profile setting">
<svg>…</svg>
</:item>
<:footer><.button color="primary" class="h-9 w-full">Logout</.button></:footer>
</.sidebar_profile>A link of the box closes it.
Attributes
id(:string) (required)src(:string) (required)alt(:string) - Defaults to"avatar".name(:string) (required)subtitle(:string) - Defaults tonil.status(:string) - Defaults to"success". Must be one ofnil,"success","warning","error","info", or"neutral".href(:string) - Defaults tonil.navigate(:string) - Defaults tonil.class(:any) - Defaults tonil.- Global attributes are accepted.
Slots
item- Accepts attributes:href(:string)navigate(:string)patch(:string)method(:string)color(:string) - Must be one of"primary","secondary","info","success","warning", or"error".title(:string) (required)description(:string)
footer
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.
<.sidebar_toggle />Attributes
class(:any) - Defaults tonil.- Global attributes are accepted.
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 tonil.- Global attributes are accepted.
Slots
logo- Accepts attributes:href(:string)navigate(:string)patch(:string)src(:string)alt(:string)
inner_block(required)
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.
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 tonil.target(:any) - Defaults tonil.class(:any) - Defaults tonil.- Global attributes are accepted.