Current section
Files
Jump to
Current section
Files
src/gbr/ui/admin/header.gleam
////
//// 🔝 Gleam UI header super element
////
import gleam/option.{type Option, None, Some}
import lustre/attribute as a
import lustre/element
import lustre/element/html
import gbr/ui/core/el
import gbr/ui/core/model.{type UIRender}
import gbr/ui/logo.{type UILogo}
import gbr/ui/admin/button
import gbr/ui/admin/user
type Header =
UIHeader
type Render(a) =
UIHeaderRender(a)
type Logo =
UILogo
type User =
user.UIUser
/// Header super element
///
/// - id: Identification html element
/// - sidebar: Sidebar visible
/// - app_nav: App nav visible to mobile responsive
///
pub opaque type UIHeader {
UIHeader(
el: el.UIEl,
logo: Option(Logo),
user: Option(User),
sidebar: Bool,
appnav: Bool,
)
}
/// Render header element
///
/// - user: User info
/// - logo: Logotype info
/// - on_...: Events
///
pub opaque type UIHeaderRender(a) {
UIHeaderRender(
in: Header,
on_app: Option(a),
on_sidebar: Option(a),
on_darkmode: Option(a),
on_submit: Option(fn(String) -> a),
on_dropdown: Option(a),
)
}
/// New header super element
///
/// - id: Identification html element
/// - sidebar: Logo info
/// - appnav: User info
///
pub fn new(id: String) {
let el =
el.new(id)
|> el.class(header_class)
UIHeader(el:, logo: None, user: None, sidebar: False, appnav: False)
}
/// Set header logo
///
pub fn logo(in: Header, logo: Logo) -> Header {
let logo =
logo.class(logo, "lg:hidden")
|> Some
UIHeader(..in, logo:)
}
/// Set header user
///
pub fn user(in: Header, user: User) -> Header {
UIHeader(..in, user: Some(user))
}
/// Toggle mobile screen
///
pub fn toggle_mobile(in: Header) -> Header {
UIHeader(..in, appnav: !in.appnav)
}
/// Toggle sidebar open
///
pub fn toggle_sidebar(in: Header) -> Header {
UIHeader(..in, sidebar: !in.sidebar)
}
/// Toggle dropdown user info
///
pub fn toggle_dropdown(in: Header) -> Header {
let user =
in.user
|> option.map(user.toggle)
UIHeader(..in, user:)
}
/// New render header element
///
/// - in: Header info
/// - logo: Logo info
/// - user: User render element
///
pub fn at(in: Header) -> Render(a) {
UIHeaderRender(
in:,
on_app: None,
on_sidebar: None,
on_darkmode: None,
on_dropdown: None,
on_submit: None,
)
}
/// On mobile screen toggle
///
/// Uses with `toggle_mobile`
///
/// When chance or open in mobile screen
///
pub fn on_mobile(in: Render(a), on_app: a) -> Render(a) {
UIHeaderRender(..in, on_app: Some(on_app))
}
/// On sidebar toggle
///
/// Uses with `toggle_sidebar`
///
/// When sidebar button is clicked
///
pub fn on_sidebar(in: Render(a), on_sidebar: a) -> Render(a) {
UIHeaderRender(..in, on_sidebar: Some(on_sidebar))
}
/// On dropdown user info toggle
///
/// Uses with `toggle_dropdown`
///
/// When header user element is clicked
///
pub fn on_dropdown(in: Render(a), on_dropdown: a) -> Render(a) {
UIHeaderRender(..in, on_dropdown: Some(on_dropdown))
}
/// On submit header generic event
///
/// Uses when TODO?
///
/// When clicked generic button TODO?
///
pub fn on_submit(in: Render(a), onsubmit: fn(String) -> a) -> Render(a) {
UIHeaderRender(..in, on_submit: Some(onsubmit))
}
/// On dark mode toggle
///
/// When dark mode button is clicked
///
pub fn on_darkmode(in: Render(a), on_darkmode: a) -> Render(a) {
UIHeaderRender(..in, on_darkmode: Some(on_darkmode))
}
/// Render header super element
///
/// - at: Render header element info
///
pub fn render(at: Render(a)) -> UIRender(a) {
let UIHeaderRender(
in:,
on_app:,
on_sidebar:,
on_darkmode:,
on_dropdown:,
on_submit:,
) = at
let UIHeader(el, logo:, user:, appnav:, sidebar:) = in
let logo =
option.map(logo, logo.render)
|> option.unwrap(element.none())
let user =
option.map(user, user.at)
|> option.map(user.on_submit_opt(_, on_submit))
|> option.map(user.on_dropdown_opt(_, on_dropdown))
|> option.map(user.render)
|> option.map(fn(user) {
html.div([a.class(header_left_user_class)], [
user,
])
})
|> option.unwrap(element.none())
let attrs = el.attrs(el)
html.header(attrs, [
html.div([a.class(header_content_class)], [
html.div([a.class(header_right_class)], [
button.sidebar("header-toggle-sidebar", sidebar, on_sidebar),
logo,
button.app_nav("header-toggle-appnav", appnav, on_app),
]),
html.div(
[
a.class(header_left_class),
a.classes([#("flex", appnav), #("hidden", !appnav)]),
],
[
html.div([a.class(header_left_content_class)], [
button.dark_mode("header-btn-toggle-darkmode", on_darkmode),
]),
user,
],
),
]),
])
}
// PRIVATE
//
const header_class = "sticky top-0 z-99999 flex w-full border-gray-200 lg:border-b bg-white dark:border-gray-800 dark:bg-gray-900"
const header_content_class = "flex grow flex-col items-center justify-between lg:flex-row lg:px-6"
const header_right_class = "flex w-full items-center justify-between gap-2 border-b border-gray-200 px-3 py-3 sm:gap-4 lg:justify-normal lg:border-b-0 lg:px-0 lg:py-4 dark:border-gray-800"
const header_left_class = "shadow-theme-md w-full items-center justify-between gap-4 px-5 py-4 lg:flex lg:justify-end lg:px-0 lg:shadow-none"
const header_left_content_class = "2xsm:gap-3 flex items-center gap-2"
const header_left_user_class = "2xsm:gap-3 flex items-center gap-2"