Packages

🎨 Gleam UI lustre library by @gleam-br

Current section

Files

Jump to
gbr_ui src gbr ui admin user.gleam
Raw

src/gbr/ui/admin/user.gleam

////
//// Gleam UI admin user super element.
////
import gleam/option.{type Option, None, Some}
import lustre/attribute as a
import lustre/element
import lustre/element/html
import lustre/event
import gbr/ui/core/el
import gbr/ui/core/model.{type UIRender, type UIRenders}
import gbr/ui/svg
import gbr/ui/svg/icons as svg_icons
import gbr/ui/admin/user/dropdown.{type UIDropdown}
type User =
UIUser
type Render(a) =
UIUserRender(a)
type Profile =
UIProfile
type Dropdown =
UIDropdown
pub opaque type UIProfile {
UIProfile(
username: String,
email: String,
department: String,
full_name: String,
picture: String,
)
}
pub opaque type UIUser {
UIUser(el: el.UIEl, profile: Profile, dropdown: Option(Dropdown))
}
pub opaque type UIUserRender(a) {
UIUserRender(
in: User,
on_dropdown: Option(a),
on_submit: Option(fn(String) -> a),
)
}
/// New user element
///
/// - id: html id
/// - username: Username
///
pub fn new(id: String, username: String) -> User {
let el =
el.new(id)
|> el.class("relative")
let profile =
UIProfile(username:, email: "", department: "", full_name: "", picture: "")
UIUser(el:, profile:, dropdown: None)
}
pub fn username(in: User, username: String) -> User {
UIUser(..in, profile: UIProfile(..in.profile, username:))
}
pub fn email(in: User, email: String) -> User {
UIUser(..in, profile: UIProfile(..in.profile, email:))
}
pub fn department(in: User, department: String) -> User {
UIUser(..in, profile: UIProfile(..in.profile, department:))
}
pub fn name_full(in: User, full_name: String) -> User {
UIUser(..in, profile: UIProfile(..in.profile, full_name:))
}
pub fn picture(in: User, picture: String) -> User {
UIUser(..in, profile: UIProfile(..in.profile, picture:))
}
pub fn dropdown(in: User, dropdown: Dropdown) -> User {
dropdown_opt(in, Some(dropdown))
}
pub fn dropdown_opt(in: User, dropdown: Option(Dropdown)) -> User {
UIUser(..in, dropdown:)
}
pub fn toggle(in: User) -> User {
let dropdown =
in.dropdown
|> option.map(dropdown.toggle)
UIUser(..in, dropdown:)
}
pub fn at(in: User) -> Render(a) {
UIUserRender(in:, on_dropdown: None, on_submit: None)
}
pub fn on_submit_opt(
at: Render(a),
on_submit: Option(fn(String) -> a),
) -> Render(a) {
UIUserRender(..at, on_submit:)
}
pub fn on_submit(at: Render(a), onsubmit: fn(String) -> a) -> Render(a) {
on_submit_opt(at, Some(onsubmit))
}
pub fn on_dropdown(at: Render(a), ondropdown: a) -> Render(a) {
on_dropdown_opt(at, Some(ondropdown))
}
pub fn on_dropdown_opt(at: Render(a), on_dropdown: Option(a)) -> Render(a) {
UIUserRender(..at, on_dropdown:)
}
pub fn render(at: Render(a)) -> UIRender(a) {
let UIUserRender(in:, on_submit:, on_dropdown:) = at
let UIUser(el:, profile:, dropdown:) = in
let UIProfile(username:, email:, department:, full_name:, picture:) = profile
let is_visible =
option.map(dropdown, dropdown.is_visible)
|> option.unwrap(False)
let evt_dropdown = get_on_dropdown(on_dropdown)
let evt_dropdown_click_out = get_on_dropdown(on_dropdown)
let evt_dropdown_mouse_out = get_on_dropdown_mouse(on_dropdown)
let attrs = el.attrs(el)
html.div(attrs, [
html.a(
[
a.class(user_profile_class),
evt_dropdown,
],
[
html.span([], [
html.img([a.src(picture), a.class(user_profile_picture_class)]),
]),
html.span([a.class(user_profile_username_class)], [
html.text(username),
]),
svg.new(20, 18)
|> svg.class(user_arrow_class)
|> svg.classes([#("rotate-180", is_visible)])
|> svg_icons.arrow()
|> svg.render(),
],
),
case dropdown, is_visible {
Some(dropdown), True ->
html.div(
[
a.class(user_dropdown_class),
a.classes([#("block", is_visible), #("hidden", !is_visible)]),
evt_dropdown_click_out,
evt_dropdown_mouse_out,
],
[
html.div([a.class(user_dropdown_profile_class)], [
html.div([a.class("inline-flex justify-between w-full")], [
html.span([a.class(user_dropdown_username_class)], [
html.text(full_name),
]),
html.span(
[
a.class(user_dropdown_username_class),
a.class("text-orange-400 dark:text-orange-700"),
],
[
html.text(department),
],
),
]),
html.span([a.class(user_dropdown_email_class)], [
html.text(email),
]),
]),
html.ul(
[a.class(user_dropdown_menugrp_class)],
menu_list_(dropdown, on_submit),
),
..button_list_(dropdown, on_submit)
],
)
_, _ -> element.none()
},
])
}
// PRIVATE
//
fn menu_list_(dropdown: Dropdown, onsubmit) -> UIRenders(a) {
use id, text, icon <- dropdown.in_menus(dropdown)
let item = case icon {
Some(identity) -> [
svg.new(24, 24)
|> identity()
|> svg.class(user_menu_icon_class)
|> svg.render(),
html.text(text),
]
None -> [html.text(text)]
}
html.li([a.id(id)], [
html.a(
[
a.class(user_dropdown_menu_class),
get_on_submit(id, onsubmit),
],
item,
),
])
}
fn button_list_(dropdown: Dropdown, onsubmit) -> UIRenders(a) {
use id, label, icon <- dropdown.in_buttons(dropdown)
let item = case icon {
Some(identity) -> [
svg.new(24, 24)
|> identity()
|> svg.class(user_btn_icon_class)
|> svg.render(),
html.text(label),
]
None -> [html.text(label)]
}
html.button(
[
a.class(user_btn_class),
get_on_submit(id, onsubmit),
],
item,
)
}
fn get_on_dropdown(ondropdown) {
case ondropdown {
Some(ondropdown) -> event.on_click(ondropdown)
None -> a.none()
}
}
fn get_on_dropdown_mouse(ondropdown) {
case ondropdown {
Some(ondropdown) -> event.on_mouse_leave(ondropdown)
None -> a.none()
}
}
fn get_on_submit(id, onsubmit) {
case onsubmit {
Some(onsubmit) -> event.on_click(onsubmit(id))
None -> a.none()
}
}
const user_arrow_class = "stroke-gray-500 dark:stroke-gray-400"
const user_dropdown_class = "shadow-theme-lg dark:bg-gray-dark absolute right-0 mt-[17px] flex w-[260px] flex-col rounded-2xl border border-gray-200 bg-white p-3 dark:border-gray-800"
const user_profile_class = "flex items-center text-gray-700 dark:text-gray-400 cursor-pointer"
const user_profile_picture_class = "mr-3 h-11 w-11 overflow-hidden rounded-full"
const user_profile_username_class = "text-theme-sm mr-1 block font-medium"
const user_dropdown_profile_class = "pb-2 border-b border-gray-200 dark:border-gray-800"
const user_btn_icon_class = "fill-gray-500 group-hover:fill-gray-700 dark:group-hover:fill-gray-300"
const user_menu_icon_class = "fill-gray-500 group-hover:fill-gray-700 dark:fill-gray-400 dark:group-hover:fill-gray-300"
const user_dropdown_menugrp_class = "flex flex-col gap-1 order-b order-gray-200 ark:border-gray-800 t-4 b-3"
const user_dropdown_menu_class = "group text-theme-sm flex items-center gap-3 rounded-lg px-3 py-2 font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
const user_dropdown_email_class = "text-theme-xs mt-0.5 block text-gray-500 dark:text-gray-400"
const user_dropdown_username_class = "text-theme-sm block ont-medium text-gray-700 dark:text-gray-400"
const user_btn_class = "group text-theme-sm mt-3 flex items-center gap-3 rounded-lg px-3 py-2 font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"