Files
maxwarden/ui/app_layout.go
2025-03-07 14:13:25 -05:00

176 lines
6.6 KiB
Go

package ui
import (
"maxwarden/auth"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
const (
LAYOUT_SECTION_VAULT = iota
LAYOUT_SECTION_TOOLS = iota
LAYOUT_SECTION_ACCOUNT = iota
LAYOUT_SECTION_API = iota
)
type NavGroup struct {
SectionId int
Title string
URL string
SubGroup []NavGroup
NewTab bool
}
var NavGroups = []NavGroup{
{SectionId: LAYOUT_SECTION_VAULT, Title: "Vault", URL: "/app", SubGroup: nil},
{
Title: "Tools",
SubGroup: []NavGroup{
{SectionId: LAYOUT_SECTION_TOOLS, Title: "Passkey Generator", URL: "/app/generator"},
},
},
}
func AppLayout(title string, identity auth.Identity, session map[string]interface{}, children ...Node) Node {
navbarDropdown := func(dropdownHeader Node, dropdownItems Node) Node {
return Div(
InlineStyle("$me{cursor: pointer; position: relative; margin-left: $3;}"),
Div(
Class("button"),
InlineStyle(`
$me {
cursor: pointer;
display: flex;
position: relative;
padding-top: $2;
padding-bottom: $2;
padding-left: $3;
font-size: var(--text-sm);
line-height: var(--text-sm--line-height);
font-weight: var(--font-weight-medium);
color: $color(white);
}
$me:hover{color: $color(white);}
`),
Button(
Div(InlineStyle("$me{cursor: pointer; display: flex; align-items: center;}"),
dropdownHeader, Span(Text(" ")),
Icon(ICON_CHEVRON_DOWN, 16),
),
),
),
Div(
Class("dropdown"),
InlineStyle(`$me{display: none; position: absolute; right: 0; z-index: 10; padding-top: $1; padding-bottom: $1; margin-top: $2; width: $48; background-color: $color(white); transform-origin: top right; box-shadow: var(--shadow-lg);}`),
TabIndex("-1"),
dropdownItems,
),
InlineScript(`
let button = me(".button", me());
let dropdown = me(".dropdown", me());
button.on("click", ev => { toggleShowHide(dropdown) });
onClickOutsideOrEscape(me(), () => { hide(dropdown) });
`),
)
}
navbarDropdownItem := func(name string, url string, newPage bool) Node {
return A(InlineStyle(`$me{display: block; padding-top: $2; padding-bottom: $2; padding-left: $4; padding-right: $4; font-size: var(--text-sm); line-height: $5; color: $color(neutral-700); } $me:hover{background: $color(neutral-100);}`), Href(url), TabIndex("-1"), Text(name), If(newPage, Target("_blank")))
}
navbarLink := func(name string, url string, newPage bool) Node {
return A(
InlineStyle(`$me{ padding-left: $3; padding-right: $3; padding-top: $2; padding-bottom: $2; font-size: var(--text-sm); font-weight: var(--font-weight-medium); color: $color(white);}`),
InlineStyle("$me:hover{color: $color(white);}"),
Href(url),
Text(name),
If(newPage, Target("_blank")),
)
}
return RootLayout(title+" | MaxWarden",
Body(InlineStyle("$me{background-color: $color(light-grey); height: 100%;}"),
Div(InlineStyle("$me{min-height: 100%}"),
Nav(InlineStyle("$me{background-color: $color(deep-blue);}"),
Div(InlineStyle("$me{margin-left: auto; margin-right: auto; max-width: var(--container-7xl);}"),
Div(InlineStyle("$me{display: flex; height: $16; align-items: center; justify-content: space-between;}"),
Div(InlineStyle("$me{align-items: center; display: flex;}"),
Div(InlineStyle("@media $lg-{ $me{display: block;}}"),
Div(InlineStyle(`$me{margin-left: $1; display: flex; align-items: baseline;} $me:not(:last-child){ margin-left: $4; }`),
Map(NavGroups, func(nav NavGroup) Node {
if len(nav.SubGroup) > 0 {
return navbarDropdown(
Text(nav.Title),
Map(nav.SubGroup, func(sub NavGroup) Node {
return navbarDropdownItem(sub.Title, sub.URL, sub.NewTab)
}),
)
} else {
return navbarLink(nav.Title, nav.URL, nav.NewTab)
}
}),
),
),
),
Div(InlineStyle("$me{display: none;} @media $md { $me{ display: block; }}"),
Div(InlineStyle("$me{ margin-left: $4; display: flex; align-items: center;} @media $md { $me{ margin-left: $6;}}"),
Div(InlineStyle("$me{position: relative; margin-left: $3;}"),
navbarDropdown(
Icon(ICON_USERS, 24),
Group{
navbarDropdownItem("My Profile", "/app/account", false),
navbarDropdownItem("Lock Vault", "/auth/logout", false),
},
),
),
),
),
Div(InlineStyle("$me{margin-right: $2; display: flex;} @media $md{ $me{display: none;}}"),
Button(
InlineStyle("$me{position: relative; display: inline-flex; justify-items: center; padding: $2; color: $color(neutral-400)}"),
InlineStyle("$me:hover{color: $color(white); background-color: $color(neutral-900);}"),
Type("button"),
Span(InlineStyle("$me{position: absolute;}")),
Icon(ICON_MENU, 24),
),
),
),
),
Div(InlineStyle("@media $md { $me {display: none; }}"),
Div(Class("space-y-1 px-2 pb-3 pt-2 sm:px-3"),
A(Href("/app/dashboard"), Class("block hover:bg-neutral-900 px-3 py-2 text-base font-medium text-white"), Text("Dashboard")),
),
Div(Class("border-t border-neutral-700 pb-3 pt-4"),
Div(Class("flex items-center px-5"),
Div(Class("flex-shrink-0"),
Img(Class("h-10 w-10 rounded-full"), Src(""), Alt("profile picture")),
),
Div(Class("ml-3"),
// Div(Class("text-base/5 font-medium text-white"), Text(identity.User.Firstname+" "+identity.User.Lastname)),
// Div(Class("text-sm font-medium text-neutral-400"), Text(identity.User.Email)),
),
),
Div(Class("mt-3 space-y-1 px-2"),
A(Href("/auth/logout"), Class("block px-3 py-2 text-base font-medium text-neutral-200 hover:bg-neutral-900 hover:text-white"), Text("Log out")),
),
),
),
),
Header(InlineStyle("$me{background-color: $color(white); box-shadow: var(--shadow-sm);}"),
Div(InlineStyle("$me{margin-left: auto; margin-right: auto; max-width: var(--container-7xl); padding: $4;} @media $lg { $me{ padding-left: $8; padding-right: $8;}}"),
H1(InlineStyle("$me{font-size: var(--text-3xl); font-weight: var(--font-weight-bold); color: $color(neutral-950); letter-spacing: var(--tracking-tight);}"), Text(title)),
),
),
Main(
Div(InlineStyle("$me{margin-left: auto; margin-right: auto; max-width: var(--container-7xl); padding: $6 $4;} @media $sm { $me{padding-left: $6; padding-right: $6; }} @media $lg { $me{padding-left: $8; padding-right: $8;}}"),
Group(children),
),
),
),
),
)
}