refactor(components): extract breadcrumb into sub-components

fix(docs): document breadcrumb components

refactor(sdk): migrate to new navigation components
feat(sdk): create isolated sidebar component

breaking(components): deprecate old topbar component

feat(components): create new topbar components

feat(components): create user dropdown

feat(components): create breadcrumb

feat(components): create navigation items

docs(components): document new components

perf(components): improve performance of sidebar

test(components): add test for navigation components
This commit is contained in:
2026-01-05 18:41:05 -05:00
parent 22bd6bb70a
commit 30ed4e9ec7
11 changed files with 1223 additions and 1147 deletions

View File

@@ -86,95 +86,109 @@ func getSelectedAccountName(accounts []Account, id string) string {
return "Account"
}
// TopBar renders the header bar with breadcrumbs and user dropdown
// BreadcrumbNav renders the breadcrumb navigation for the subheader
templ BreadcrumbNav(navCtx NavContext) {
<nav class="breadcrumb-nav">
<!-- Chain Dropdown -->
<wa-dropdown>
<button slot="trigger" class="breadcrumb-btn">
{ getSelectedChainName(navCtx.Blockchains, navCtx.SelectedChainID) }
<wa-icon name="chevron-down"></wa-icon>
</button>
<wa-menu>
<wa-menu-label>Mainnets</wa-menu-label>
for _, chain := range navCtx.Blockchains {
if !chain.Testnet {
<wa-menu-item value={ chain.ID }>
<wa-icon slot="prefix" name={ chain.Icon } style={ "color:" + chain.Color }></wa-icon>
{ chain.Name }
</wa-menu-item>
}
}
<wa-divider></wa-divider>
<wa-menu-label>Testnets</wa-menu-label>
for _, chain := range navCtx.Blockchains {
if chain.Testnet {
<wa-menu-item value={ chain.ID }>
<wa-icon slot="prefix" name={ chain.Icon } style={ "color:" + chain.Color }></wa-icon>
{ chain.Name }
</wa-menu-item>
}
}
</wa-menu>
</wa-dropdown>
<span class="breadcrumb-sep">/</span>
<!-- Account Dropdown -->
<wa-dropdown>
<button slot="trigger" class="breadcrumb-btn">
{ getSelectedAccountName(navCtx.Accounts, navCtx.SelectedAccountID) }
<wa-icon name="chevron-down"></wa-icon>
</button>
<wa-menu>
for _, account := range navCtx.Accounts {
<wa-menu-item value={ account.ID }>
<wa-avatar slot="prefix" initials={ account.Initials } style={ "--size:18px;background:" + account.Color }></wa-avatar>
{ account.Name }
</wa-menu-item>
}
<wa-divider></wa-divider>
<wa-menu-item value="__new__">
<wa-icon slot="prefix" name="plus"></wa-icon>
New Account
</wa-menu-item>
</wa-menu>
</wa-dropdown>
</nav>
}
// UserDropdown renders the user avatar dropdown menu
templ UserDropdown(user NavUser) {
<wa-dropdown>
<wa-avatar slot="trigger" initials={ string(user.Name[0]) } style="--size:32px;cursor:pointer;"></wa-avatar>
<wa-menu>
<div class="user-menu-header">
<strong>{ user.Name }</strong>
<span>{ user.Address }</span>
</div>
<wa-divider></wa-divider>
<wa-menu-item value="connections">
<wa-icon slot="prefix" name="plug"></wa-icon>
Connections
</wa-menu-item>
<wa-menu-item value="devices">
<wa-icon slot="prefix" name="mobile"></wa-icon>
Devices
</wa-menu-item>
<wa-menu-item value="settings">
<wa-icon slot="prefix" name="gear"></wa-icon>
Settings
</wa-menu-item>
<wa-divider></wa-divider>
<wa-menu-item value="logout">
<wa-icon slot="prefix" name="arrow-right-from-bracket"></wa-icon>
Sign Out
</wa-menu-item>
</wa-menu>
</wa-dropdown>
}
// Legacy components for backwards compatibility
// TopBar renders the header bar with breadcrumbs and user dropdown (DEPRECATED - use wa-page slots)
templ TopBar(navCtx NavContext) {
<header class="topbar">
<!-- Breadcrumb Navigation -->
<nav class="topbar-nav">
<span class="topbar-title">Sonr</span>
<span class="topbar-sep">/</span>
<!-- Chain Dropdown -->
<wa-dropdown>
<button slot="trigger" class="topbar-btn">
{ getSelectedChainName(navCtx.Blockchains, navCtx.SelectedChainID) }
<wa-icon name="chevron-down"></wa-icon>
</button>
<wa-menu>
<wa-menu-label>Mainnets</wa-menu-label>
for _, chain := range navCtx.Blockchains {
if !chain.Testnet {
<wa-menu-item value={ chain.ID }>
<wa-icon slot="prefix" name={ chain.Icon } style={ "color:" + chain.Color }></wa-icon>
{ chain.Name }
</wa-menu-item>
}
}
<wa-divider></wa-divider>
<wa-menu-label>Testnets</wa-menu-label>
for _, chain := range navCtx.Blockchains {
if chain.Testnet {
<wa-menu-item value={ chain.ID }>
<wa-icon slot="prefix" name={ chain.Icon } style={ "color:" + chain.Color }></wa-icon>
{ chain.Name }
</wa-menu-item>
}
}
</wa-menu>
</wa-dropdown>
<span class="topbar-sep">/</span>
<!-- Account Dropdown -->
<wa-dropdown>
<button slot="trigger" class="topbar-btn">
{ getSelectedAccountName(navCtx.Accounts, navCtx.SelectedAccountID) }
<wa-icon name="chevron-down"></wa-icon>
</button>
<wa-menu>
for _, account := range navCtx.Accounts {
<wa-menu-item value={ account.ID }>
<wa-avatar slot="prefix" initials={ account.Initials } style={ "--size:18px;background:" + account.Color }></wa-avatar>
{ account.Name }
</wa-menu-item>
}
<wa-divider></wa-divider>
<wa-menu-item value="__new__">
<wa-icon slot="prefix" name="plus"></wa-icon>
New Account
</wa-menu-item>
</wa-menu>
</wa-dropdown>
@BreadcrumbNav(navCtx)
</nav>
<!-- User Dropdown -->
<wa-dropdown>
<wa-avatar slot="trigger" initials={ string(navCtx.User.Name[0]) } style="--size:32px;cursor:pointer;"></wa-avatar>
<wa-menu>
<div class="user-menu-header">
<strong>{ navCtx.User.Name }</strong>
<span>{ navCtx.User.Address }</span>
</div>
<wa-divider></wa-divider>
<wa-menu-item value="connections">
<wa-icon slot="prefix" name="plug"></wa-icon>
Connections
</wa-menu-item>
<wa-menu-item value="devices">
<wa-icon slot="prefix" name="mobile"></wa-icon>
Devices
</wa-menu-item>
<wa-menu-item value="settings">
<wa-icon slot="prefix" name="gear"></wa-icon>
Settings
</wa-menu-item>
<wa-divider></wa-divider>
<wa-menu-item value="logout">
<wa-icon slot="prefix" name="arrow-right-from-bracket"></wa-icon>
Sign Out
</wa-menu-item>
</wa-menu>
</wa-dropdown>
@UserDropdown(navCtx.User)
</header>
}
// TopBarStyles renders the CSS for the top bar
// TopBarStyles renders the CSS for the top bar (DEPRECATED - use wa-page styles)
templ TopBarStyles() {
<style>
/* Top Bar */

View File

@@ -94,8 +94,8 @@ func getSelectedAccountName(accounts []Account, id string) string {
return "Account"
}
// TopBar renders the header bar with breadcrumbs and user dropdown
func TopBar(navCtx NavContext) templ.Component {
// BreadcrumbNav renders the breadcrumb navigation for the subheader
func BreadcrumbNav(navCtx NavContext) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -116,14 +116,14 @@ func TopBar(navCtx NavContext) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<header class=\"topbar\"><!-- Breadcrumb Navigation --><nav class=\"topbar-nav\"><span class=\"topbar-title\">Sonr</span> <span class=\"topbar-sep\">/</span><!-- Chain Dropdown --><wa-dropdown><button slot=\"trigger\" class=\"topbar-btn\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<nav class=\"breadcrumb-nav\"><!-- Chain Dropdown --><wa-dropdown><button slot=\"trigger\" class=\"breadcrumb-btn\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(getSelectedChainName(navCtx.Blockchains, navCtx.SelectedChainID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 99, Col: 71}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 95, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
@@ -142,7 +142,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(chain.ID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 106, Col: 37}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 102, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -155,7 +155,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(chain.Icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 107, Col: 48}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 103, Col: 47}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -168,7 +168,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("color:" + chain.Color)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 107, Col: 81}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 103, Col: 80}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@@ -181,7 +181,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(chain.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 108, Col: 20}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 104, Col: 19}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@@ -206,7 +206,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(chain.ID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 116, Col: 37}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 112, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
@@ -219,7 +219,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(chain.Icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 117, Col: 48}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 113, Col: 47}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@@ -232,7 +232,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("color:" + chain.Color)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 117, Col: 81}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 113, Col: 80}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
@@ -245,7 +245,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(chain.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 118, Col: 20}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 114, Col: 19}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
@@ -257,14 +257,14 @@ func TopBar(navCtx NavContext) templ.Component {
}
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</wa-menu></wa-dropdown> <span class=\"topbar-sep\">/</span><!-- Account Dropdown --><wa-dropdown><button slot=\"trigger\" class=\"topbar-btn\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</wa-menu></wa-dropdown> <span class=\"breadcrumb-sep\">/</span><!-- Account Dropdown --><wa-dropdown><button slot=\"trigger\" class=\"breadcrumb-btn\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(getSelectedAccountName(navCtx.Accounts, navCtx.SelectedAccountID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 128, Col: 72}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 124, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
@@ -282,7 +282,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(account.ID)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 133, Col: 38}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 129, Col: 37}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
@@ -295,7 +295,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(account.Initials)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 134, Col: 59}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 130, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
@@ -308,7 +308,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("--size:18px;background:" + account.Color)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 134, Col: 111}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 130, Col: 110}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
@@ -321,7 +321,7 @@ func TopBar(navCtx NavContext) templ.Component {
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(account.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 135, Col: 21}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 131, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
@@ -332,46 +332,7 @@ func TopBar(navCtx NavContext) templ.Component {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<wa-divider></wa-divider> <wa-menu-item value=\"__new__\"><wa-icon slot=\"prefix\" name=\"plus\"></wa-icon> New Account</wa-menu-item></wa-menu></wa-dropdown></nav><!-- User Dropdown --><wa-dropdown><wa-avatar slot=\"trigger\" initials=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(string(navCtx.User.Name[0]))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 148, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" style=\"--size:32px;cursor:pointer;\"></wa-avatar> <wa-menu><div class=\"user-menu-header\"><strong>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(navCtx.User.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 151, Col: 31}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</strong> <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(navCtx.User.Address)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 152, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</span></div><wa-divider></wa-divider> <wa-menu-item value=\"connections\"><wa-icon slot=\"prefix\" name=\"plug\"></wa-icon> Connections</wa-menu-item> <wa-menu-item value=\"devices\"><wa-icon slot=\"prefix\" name=\"mobile\"></wa-icon> Devices</wa-menu-item> <wa-menu-item value=\"settings\"><wa-icon slot=\"prefix\" name=\"gear\"></wa-icon> Settings</wa-menu-item> <wa-divider></wa-divider> <wa-menu-item value=\"logout\"><wa-icon slot=\"prefix\" name=\"arrow-right-from-bracket\"></wa-icon> Sign Out</wa-menu-item></wa-menu></wa-dropdown></header>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<wa-divider></wa-divider> <wa-menu-item value=\"__new__\"><wa-icon slot=\"prefix\" name=\"plus\"></wa-icon> New Account</wa-menu-item></wa-menu></wa-dropdown></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -379,7 +340,124 @@ func TopBar(navCtx NavContext) templ.Component {
})
}
// TopBarStyles renders the CSS for the top bar
// UserDropdown renders the user avatar dropdown menu
func UserDropdown(user NavUser) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var16 := templ.GetChildren(ctx)
if templ_7745c5c3_Var16 == nil {
templ_7745c5c3_Var16 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<wa-dropdown><wa-avatar slot=\"trigger\" initials=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(string(user.Name[0]))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 147, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" style=\"--size:32px;cursor:pointer;\"></wa-avatar> <wa-menu><div class=\"user-menu-header\"><strong>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 150, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</strong> <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(user.Address)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/navbar.templ`, Line: 151, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</span></div><wa-divider></wa-divider> <wa-menu-item value=\"connections\"><wa-icon slot=\"prefix\" name=\"plug\"></wa-icon> Connections</wa-menu-item> <wa-menu-item value=\"devices\"><wa-icon slot=\"prefix\" name=\"mobile\"></wa-icon> Devices</wa-menu-item> <wa-menu-item value=\"settings\"><wa-icon slot=\"prefix\" name=\"gear\"></wa-icon> Settings</wa-menu-item> <wa-divider></wa-divider> <wa-menu-item value=\"logout\"><wa-icon slot=\"prefix\" name=\"arrow-right-from-bracket\"></wa-icon> Sign Out</wa-menu-item></wa-menu></wa-dropdown>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Legacy components for backwards compatibility
// TopBar renders the header bar with breadcrumbs and user dropdown (DEPRECATED - use wa-page slots)
func TopBar(navCtx NavContext) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var20 := templ.GetChildren(ctx)
if templ_7745c5c3_Var20 == nil {
templ_7745c5c3_Var20 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<header class=\"topbar\"><!-- Breadcrumb Navigation --><nav class=\"topbar-nav\"><span class=\"topbar-title\">Sonr</span> <span class=\"topbar-sep\">/</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = BreadcrumbNav(navCtx).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</nav><!-- User Dropdown -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = UserDropdown(navCtx.User).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</header>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// TopBarStyles renders the CSS for the top bar (DEPRECATED - use wa-page styles)
func TopBarStyles() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -396,12 +474,12 @@ func TopBarStyles() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var19 := templ.GetChildren(ctx)
if templ_7745c5c3_Var19 == nil {
templ_7745c5c3_Var19 = templ.NopComponent
templ_7745c5c3_Var21 := templ.GetChildren(ctx)
if templ_7745c5c3_Var21 == nil {
templ_7745c5c3_Var21 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<style>\n\t\t/* Top Bar */\n\t\t.topbar {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: space-between;\n\t\t\theight: 56px;\n\t\t\tpadding: 0 24px;\n\t\t\tbackground: var(--wa-color-surface);\n\t\t\tborder-bottom: 1px solid var(--wa-color-neutral-200);\n\t\t}\n\t\t\n\t\t.topbar-nav {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: 0;\n\t\t}\n\t\t\n\t\t.topbar-title {\n\t\t\tfont-weight: 600;\n\t\t\tfont-size: 14px;\n\t\t\tcolor: var(--wa-color-neutral-900);\n\t\t\tpadding: 6px 8px;\n\t\t}\n\t\t\n\t\t.topbar-sep {\n\t\t\tcolor: var(--wa-color-neutral-300);\n\t\t\tfont-size: 16px;\n\t\t\tpadding: 0 4px;\n\t\t}\n\t\t\n\t\t.topbar-btn {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: 4px;\n\t\t\tpadding: 6px 10px;\n\t\t\tborder: none;\n\t\t\tbackground: transparent;\n\t\t\tfont: inherit;\n\t\t\tfont-size: 14px;\n\t\t\tfont-weight: 500;\n\t\t\tcolor: var(--wa-color-neutral-700);\n\t\t\tborder-radius: 6px;\n\t\t\tcursor: pointer;\n\t\t\ttransition: background 0.15s;\n\t\t}\n\t\t\n\t\t.topbar-btn:hover {\n\t\t\tbackground: var(--wa-color-neutral-100);\n\t\t}\n\t\t\n\t\t.topbar-btn wa-icon {\n\t\t\tfont-size: 12px;\n\t\t\tcolor: var(--wa-color-neutral-400);\n\t\t}\n\t\t\n\t\t/* User Menu */\n\t\t.user-menu-header {\n\t\t\tpadding: 12px 16px;\n\t\t}\n\t\t\n\t\t.user-menu-header strong {\n\t\t\tdisplay: block;\n\t\t\tfont-size: 14px;\n\t\t\tcolor: var(--wa-color-neutral-900);\n\t\t}\n\t\t\n\t\t.user-menu-header span {\n\t\t\tfont-size: 12px;\n\t\t\tcolor: var(--wa-color-neutral-500);\n\t\t\tfont-family: var(--wa-font-mono);\n\t\t}\n\t</style>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<style>\n\t\t/* Top Bar */\n\t\t.topbar {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: space-between;\n\t\t\theight: 56px;\n\t\t\tpadding: 0 24px;\n\t\t\tbackground: var(--wa-color-surface);\n\t\t\tborder-bottom: 1px solid var(--wa-color-neutral-200);\n\t\t}\n\t\t\n\t\t.topbar-nav {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: 0;\n\t\t}\n\t\t\n\t\t.topbar-title {\n\t\t\tfont-weight: 600;\n\t\t\tfont-size: 14px;\n\t\t\tcolor: var(--wa-color-neutral-900);\n\t\t\tpadding: 6px 8px;\n\t\t}\n\t\t\n\t\t.topbar-sep {\n\t\t\tcolor: var(--wa-color-neutral-300);\n\t\t\tfont-size: 16px;\n\t\t\tpadding: 0 4px;\n\t\t}\n\t\t\n\t\t.topbar-btn {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: 4px;\n\t\t\tpadding: 6px 10px;\n\t\t\tborder: none;\n\t\t\tbackground: transparent;\n\t\t\tfont: inherit;\n\t\t\tfont-size: 14px;\n\t\t\tfont-weight: 500;\n\t\t\tcolor: var(--wa-color-neutral-700);\n\t\t\tborder-radius: 6px;\n\t\t\tcursor: pointer;\n\t\t\ttransition: background 0.15s;\n\t\t}\n\t\t\n\t\t.topbar-btn:hover {\n\t\t\tbackground: var(--wa-color-neutral-100);\n\t\t}\n\t\t\n\t\t.topbar-btn wa-icon {\n\t\t\tfont-size: 12px;\n\t\t\tcolor: var(--wa-color-neutral-400);\n\t\t}\n\t\t\n\t\t/* User Menu */\n\t\t.user-menu-header {\n\t\t\tpadding: 12px 16px;\n\t\t}\n\t\t\n\t\t.user-menu-header strong {\n\t\t\tdisplay: block;\n\t\t\tfont-size: 14px;\n\t\t\tcolor: var(--wa-color-neutral-900);\n\t\t}\n\t\t\n\t\t.user-menu-header span {\n\t\t\tfont-size: 12px;\n\t\t\tcolor: var(--wa-color-neutral-500);\n\t\t\tfont-family: var(--wa-font-mono);\n\t\t}\n\t</style>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -1,13 +1,13 @@
package components
// SidebarItem represents a navigation item in the sidebar
// SidebarItem represents a navigation item
type SidebarItem struct {
ID string
Icon string
Label string
Href string
Active bool
Badge string
ID string
Icon string
Label string
Href string
Active bool
Badge string
}
// DefaultSidebarItems returns the main navigation items
@@ -28,154 +28,34 @@ func SecondarySidebarItems() []SidebarItem {
}
}
// Sidebar renders the Supabase-style icon sidebar
templ Sidebar(items []SidebarItem, secondaryItems []SidebarItem) {
<aside class="sidebar">
<div class="sidebar-inner">
<!-- Logo -->
<a href="/dashboard" class="sidebar-logo">
<wa-icon name="cube"></wa-icon>
templ NavigationItems(activeTab string) {
for _, item := range DefaultSidebarItems(activeTab) {
<a
href={ templ.SafeURL(item.Href) }
class={ "nav-item", templ.KV("active", item.Active) }
hx-get={ item.Href }
hx-target="#dashboard-content"
hx-swap="outerHTML"
hx-push-url="true"
preload="mousedown"
>
<wa-icon name={ item.Icon }></wa-icon>
{ item.Label }
if item.Badge != "" {
<wa-badge variant="danger" pill>{ item.Badge }</wa-badge>
}
</a>
}
<div class="nav-section">
<div class="nav-section-title">Settings</div>
for _, item := range SecondarySidebarItems() {
<a
href={ templ.SafeURL(item.Href) }
class={ "nav-item", templ.KV("active", item.Active) }
>
<wa-icon name={ item.Icon }></wa-icon>
{ item.Label }
</a>
<!-- Main Navigation -->
<nav class="sidebar-nav">
for _, item := range items {
<wa-tooltip content={ item.Label } placement="right">
<a
href={ templ.SafeURL(item.Href) }
class={ "sidebar-item", templ.KV("active", item.Active) }
hx-get={ item.Href }
hx-target="#main-content"
hx-push-url="true"
hx-swap="innerHTML"
>
<wa-icon name={ item.Icon }></wa-icon>
if item.Badge != "" {
<span class="sidebar-badge">{ item.Badge }</span>
}
</a>
</wa-tooltip>
}
</nav>
<!-- Secondary Navigation (bottom) -->
<nav class="sidebar-nav sidebar-nav-bottom">
for _, item := range secondaryItems {
<wa-tooltip content={ item.Label } placement="right">
<a
href={ templ.SafeURL(item.Href) }
class={ "sidebar-item", templ.KV("active", item.Active) }
>
<wa-icon name={ item.Icon }></wa-icon>
</a>
</wa-tooltip>
}
</nav>
</div>
</aside>
}
// SidebarStyles renders the CSS for the sidebar
templ SidebarStyles() {
<style>
/* Sidebar Container */
.sidebar {
position: fixed;
top: 0;
left: 0;
width: var(--sidebar-width, 64px);
height: 100vh;
background: var(--wa-color-neutral-900);
border-right: 1px solid var(--wa-color-neutral-800);
z-index: 100;
}
.sidebar-inner {
display: flex;
flex-direction: column;
height: 100%;
padding: 12px 0;
}
/* Logo */
.sidebar-logo {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
margin: 0 auto 16px;
background: linear-gradient(135deg, #17c2ff, #0090ff);
border-radius: 10px;
color: white;
text-decoration: none;
}
.sidebar-logo wa-icon {
font-size: 20px;
}
/* Navigation */
.sidebar-nav {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 0 12px;
}
.sidebar-nav-bottom {
margin-top: auto;
}
/* Sidebar Item */
.sidebar-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 8px;
color: var(--wa-color-neutral-400);
text-decoration: none;
transition: all 0.15s ease;
}
.sidebar-item:hover {
background: var(--wa-color-neutral-800);
color: var(--wa-color-neutral-100);
}
.sidebar-item.active {
background: var(--wa-color-neutral-800);
color: var(--wa-color-primary);
}
.sidebar-item wa-icon {
font-size: 20px;
}
/* Badge */
.sidebar-badge {
position: absolute;
top: 4px;
right: 4px;
min-width: 16px;
height: 16px;
padding: 0 4px;
background: var(--wa-color-danger);
border-radius: 8px;
font-size: 10px;
font-weight: 600;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
/* Main content offset */
.app-layout {
margin-left: var(--sidebar-width, 64px);
min-height: 100vh;
}
</style>
}
</div>
}

View File

@@ -8,7 +8,7 @@ package components
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// SidebarItem represents a navigation item in the sidebar
// SidebarItem represents a navigation item
type SidebarItem struct {
ID string
Icon string
@@ -36,8 +36,7 @@ func SecondarySidebarItems() []SidebarItem {
}
}
// Sidebar renders the Supabase-style icon sidebar
func Sidebar(items []SidebarItem, secondaryItems []SidebarItem) templ.Component {
func NavigationItems(activeTab string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -58,215 +57,173 @@ func Sidebar(items []SidebarItem, secondaryItems []SidebarItem) templ.Component
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<aside class=\"sidebar\"><div class=\"sidebar-inner\"><!-- Logo --><a href=\"/dashboard\" class=\"sidebar-logo\"><wa-icon name=\"cube\"></wa-icon></a><!-- Main Navigation --><nav class=\"sidebar-nav\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, item := range items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<wa-tooltip content=\"")
for _, item := range DefaultSidebarItems(activeTab) {
var templ_7745c5c3_Var2 = []any{"nav-item", templ.KV("active", item.Active)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(item.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 42, Col: 37}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" placement=\"right\">")
var templ_7745c5c3_Var3 templ.SafeURL
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(item.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 34, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 = []any{"sidebar-item", templ.KV("active", item.Active)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<a href=\"")
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(item.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 44, Col: 38}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" class=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String())
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(item.Href)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 1, Col: 0}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 36, Col: 21}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" hx-get=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" hx-target=\"#dashboard-content\" hx-swap=\"outerHTML\" hx-push-url=\"true\" preload=\"mousedown\"><wa-icon name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(item.Href)
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(item.Icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 46, Col: 25}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 42, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" hx-target=\"#main-content\" hx-push-url=\"true\" hx-swap=\"innerHTML\"><wa-icon name=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"></wa-icon> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(item.Icon)
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(item.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 51, Col: 32}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 43, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"></wa-icon> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.Badge != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<span class=\"sidebar-badge\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<wa-badge variant=\"danger\" pill>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.Badge)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 53, Col: 48}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 45, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</span>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</wa-badge>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</a></wa-tooltip>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</nav><!-- Secondary Navigation (bottom) --><nav class=\"sidebar-nav sidebar-nav-bottom\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"nav-section\"><div class=\"nav-section-title\">Settings</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, item := range secondaryItems {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<wa-tooltip content=\"")
for _, item := range SecondarySidebarItems() {
var templ_7745c5c3_Var9 = []any{"nav-item", templ.KV("active", item.Active)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(item.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 62, Col: 37}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" placement=\"right\">")
var templ_7745c5c3_Var10 templ.SafeURL
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(item.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 53, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 = []any{"sidebar-item", templ.KV("active", item.Active)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<a href=\"")
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var9).String())
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 templ.SafeURL
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(item.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 64, Col: 38}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" class=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\"><wa-icon name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var10).String())
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 1, Col: 0}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 56, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\"><wa-icon name=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\"></wa-icon> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(item.Icon)
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(item.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 67, Col: 32}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/sidebar.templ`, Line: 57, Col: 16}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\"></wa-icon></a></wa-tooltip>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</nav></div></aside>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// SidebarStyles renders the CSS for the sidebar
func SidebarStyles() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var14 := templ.GetChildren(ctx)
if templ_7745c5c3_Var14 == nil {
templ_7745c5c3_Var14 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<style>\n\t\t/* Sidebar Container */\n\t\t.sidebar {\n\t\t\tposition: fixed;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\twidth: var(--sidebar-width, 64px);\n\t\t\theight: 100vh;\n\t\t\tbackground: var(--wa-color-neutral-900);\n\t\t\tborder-right: 1px solid var(--wa-color-neutral-800);\n\t\t\tz-index: 100;\n\t\t}\n\t\t\n\t\t.sidebar-inner {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\theight: 100%;\n\t\t\tpadding: 12px 0;\n\t\t}\n\t\t\n\t\t/* Logo */\n\t\t.sidebar-logo {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\twidth: 40px;\n\t\t\theight: 40px;\n\t\t\tmargin: 0 auto 16px;\n\t\t\tbackground: linear-gradient(135deg, #17c2ff, #0090ff);\n\t\t\tborder-radius: 10px;\n\t\t\tcolor: white;\n\t\t\ttext-decoration: none;\n\t\t}\n\t\t\n\t\t.sidebar-logo wa-icon {\n\t\t\tfont-size: 20px;\n\t\t}\n\t\t\n\t\t/* Navigation */\n\t\t.sidebar-nav {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\talign-items: center;\n\t\t\tgap: 4px;\n\t\t\tpadding: 0 12px;\n\t\t}\n\t\t\n\t\t.sidebar-nav-bottom {\n\t\t\tmargin-top: auto;\n\t\t}\n\t\t\n\t\t/* Sidebar Item */\n\t\t.sidebar-item {\n\t\t\tposition: relative;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\twidth: 40px;\n\t\t\theight: 40px;\n\t\t\tborder-radius: 8px;\n\t\t\tcolor: var(--wa-color-neutral-400);\n\t\t\ttext-decoration: none;\n\t\t\ttransition: all 0.15s ease;\n\t\t}\n\t\t\n\t\t.sidebar-item:hover {\n\t\t\tbackground: var(--wa-color-neutral-800);\n\t\t\tcolor: var(--wa-color-neutral-100);\n\t\t}\n\t\t\n\t\t.sidebar-item.active {\n\t\t\tbackground: var(--wa-color-neutral-800);\n\t\t\tcolor: var(--wa-color-primary);\n\t\t}\n\t\t\n\t\t.sidebar-item wa-icon {\n\t\t\tfont-size: 20px;\n\t\t}\n\t\t\n\t\t/* Badge */\n\t\t.sidebar-badge {\n\t\t\tposition: absolute;\n\t\t\ttop: 4px;\n\t\t\tright: 4px;\n\t\t\tmin-width: 16px;\n\t\t\theight: 16px;\n\t\t\tpadding: 0 4px;\n\t\t\tbackground: var(--wa-color-danger);\n\t\t\tborder-radius: 8px;\n\t\t\tfont-size: 10px;\n\t\t\tfont-weight: 600;\n\t\t\tcolor: white;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t}\n\t\t\n\t\t/* Main content offset */\n\t\t.app-layout {\n\t\t\tmargin-left: var(--sidebar-width, 64px);\n\t\t\tmin-height: 100vh;\n\t\t}\n\t</style>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -205,5 +205,11 @@ func handleDashboard(w http.ResponseWriter, r *http.Request) {
tab = "overview"
}
data := views.DefaultDashboardData()
if r.Header.Get("HX-Request") == "true" {
views.DashboardContent(data, tab).Render(r.Context(), w)
return
}
views.DashboardPage(data, tab).Render(r.Context(), w)
}

View File

@@ -7,63 +7,145 @@ type WalletUser struct {
Address string
}
// DashboardLayout renders the dashboard with sidebar and topbar
// DashboardLayout renders the dashboard using wa-page component
templ DashboardLayout(title string, user WalletUser, activeTab string) {
@Base(title) {
@components.SidebarStyles()
@components.TopBarStyles()
<!-- Sidebar -->
@components.Sidebar(
components.DefaultSidebarItems(activeTab),
components.SecondarySidebarItems(),
)
<!-- Main App Area -->
<div class="app-layout">
<!-- Top Bar -->
@components.TopBar(components.NavContext{
User: components.NavUser{Name: user.Name, Address: user.Address},
Blockchains: components.DefaultBlockchains(),
Accounts: components.DefaultAccounts(),
SelectedChainID: "sonr",
SelectedAccountID: "main",
})
<!-- Main Content -->
<main id="main-content" class="main-content">
@pageStyles()
<wa-page mobile-breakpoint="920">
<div slot="header">
<a href="/dashboard" class="header-logo">
<wa-icon name="cube"></wa-icon>
<span>Sonr</span>
</a>
<wa-button data-toggle-nav variant="neutral" appearance="plain" class="wa-mobile-only">
<wa-icon name="bars"></wa-icon>
</wa-button>
@components.UserDropdown(components.NavUser{Name: user.Name, Address: user.Address})
</div>
<nav slot="navigation">
@components.NavigationItems(activeTab)
</nav>
<main>
{ children... }
</main>
</div>
<style>
.main-content {
padding: 24px;
background: var(--wa-color-surface-alt);
min-height: calc(100vh - 56px);
}
</style>
</wa-page>
}
}
// AppLayout is a simplified layout without sidebar (for non-dashboard pages)
// AppLayout is a simplified layout without navigation (for non-dashboard pages)
templ AppLayout(title string, user WalletUser) {
@Base(title) {
@components.TopBarStyles()
<!-- Top Bar -->
@components.TopBar(components.NavContext{
User: components.NavUser{Name: user.Name, Address: user.Address},
Blockchains: components.DefaultBlockchains(),
Accounts: components.DefaultAccounts(),
SelectedChainID: "sonr",
SelectedAccountID: "main",
})
<!-- Main Content -->
<main class="main-content">
{ children... }
</main>
<style>
.main-content {
padding: 24px;
background: var(--wa-color-surface-alt);
min-height: calc(100vh - 56px);
}
</style>
@pageStyles()
<wa-page>
<!-- Header: Logo + User Avatar -->
<div slot="header">
<a href="/dashboard" class="header-logo">
<wa-icon name="cube"></wa-icon>
<span>Sonr</span>
</a>
@components.UserDropdown(components.NavUser{Name: user.Name, Address: user.Address})
</div>
<!-- Main Content -->
<main>
{ children... }
</main>
</wa-page>
}
}
templ pageStyles() {
<style>
/* Header Logo */
.header-logo {
display: flex;
align-items: center;
gap: var(--wa-space-s);
text-decoration: none;
color: var(--wa-color-neutral-900);
font-weight: 600;
font-size: var(--wa-font-size-l);
}
.header-logo wa-icon {
font-size: 24px;
color: var(--wa-color-primary);
}
/* Navigation Items */
.nav-item {
display: flex;
align-items: center;
gap: var(--wa-space-s);
padding: var(--wa-space-s) var(--wa-space-m);
border-radius: var(--wa-radius-m);
color: var(--wa-color-neutral-600);
text-decoration: none;
font-size: var(--wa-font-size-s);
font-weight: 500;
transition: all 0.15s ease;
}
.nav-item:hover {
background: var(--wa-color-neutral-100);
color: var(--wa-color-neutral-900);
}
.nav-item.active {
background: var(--wa-color-primary-subtle);
color: var(--wa-color-primary);
}
.nav-item wa-icon {
font-size: 18px;
}
/* Navigation Section */
.nav-section {
margin-top: var(--wa-space-l);
padding-top: var(--wa-space-l);
border-top: 1px solid var(--wa-color-neutral-200);
}
.nav-section-title {
padding: var(--wa-space-xs) var(--wa-space-m);
font-size: var(--wa-font-size-xs);
font-weight: 600;
color: var(--wa-color-neutral-500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* User Menu */
.user-menu-header {
padding: var(--wa-space-m) var(--wa-space-l);
}
.user-menu-header strong {
display: block;
font-size: var(--wa-font-size-s);
color: var(--wa-color-neutral-900);
}
.user-menu-header span {
font-size: var(--wa-font-size-xs);
color: var(--wa-color-neutral-500);
font-family: var(--wa-font-mono);
}
/* Page customizations */
wa-page {
--menu-width: 240px;
}
wa-page[view='mobile'] {
--menu-width: auto;
}
/* Main content padding */
wa-page main {
padding: var(--wa-space-l);
background: var(--wa-color-surface-alt);
min-height: 100%;
}
</style>
}

View File

@@ -15,7 +15,7 @@ type WalletUser struct {
Address string
}
// DashboardLayout renders the dashboard with sidebar and topbar
// DashboardLayout renders the dashboard using wa-page component
func DashboardLayout(title string, user WalletUser, activeTab string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -49,44 +49,27 @@ func DashboardLayout(title string, user WalletUser, activeTab string) templ.Comp
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = components.SidebarStyles().Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = pageStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " <wa-page mobile-breakpoint=\"920\"><div slot=\"header\"><a href=\"/dashboard\" class=\"header-logo\"><wa-icon name=\"cube\"></wa-icon> <span>Sonr</span></a> <wa-button data-toggle-nav variant=\"neutral\" appearance=\"plain\" class=\"wa-mobile-only\"><wa-icon name=\"bars\"></wa-icon></wa-button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.TopBarStyles().Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = components.UserDropdown(components.NavUser{Name: user.Name, Address: user.Address}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " <!-- Sidebar --> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><nav slot=\"navigation\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.Sidebar(
components.DefaultSidebarItems(activeTab),
components.SecondarySidebarItems(),
).Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = components.NavigationItems(activeTab).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " <!-- Main App Area --> <div class=\"app-layout\"><!-- Top Bar -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.TopBar(components.NavContext{
User: components.NavUser{Name: user.Name, Address: user.Address},
Blockchains: components.DefaultBlockchains(),
Accounts: components.DefaultAccounts(),
SelectedChainID: "sonr",
SelectedAccountID: "main",
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<!-- Main Content --><main id=\"main-content\" class=\"main-content\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</nav><main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -94,7 +77,7 @@ func DashboardLayout(title string, user WalletUser, activeTab string) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</main></div><style>\n\t\t\t.main-content {\n\t\t\t\tpadding: 24px;\n\t\t\t\tbackground: var(--wa-color-surface-alt);\n\t\t\t\tmin-height: calc(100vh - 56px);\n\t\t\t}\n\t\t</style>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</main></wa-page>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -108,7 +91,7 @@ func DashboardLayout(title string, user WalletUser, activeTab string) templ.Comp
})
}
// AppLayout is a simplified layout without sidebar (for non-dashboard pages)
// AppLayout is a simplified layout without navigation (for non-dashboard pages)
func AppLayout(title string, user WalletUser) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -142,25 +125,19 @@ func AppLayout(title string, user WalletUser) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = components.TopBarStyles().Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = pageStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " <!-- Top Bar --> ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " <wa-page><!-- Header: Logo + User Avatar --><div slot=\"header\"><a href=\"/dashboard\" class=\"header-logo\"><wa-icon name=\"cube\"></wa-icon> <span>Sonr</span></a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.TopBar(components.NavContext{
User: components.NavUser{Name: user.Name, Address: user.Address},
Blockchains: components.DefaultBlockchains(),
Accounts: components.DefaultAccounts(),
SelectedChainID: "sonr",
SelectedAccountID: "main",
}).Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = components.UserDropdown(components.NavUser{Name: user.Name, Address: user.Address}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " <!-- Main Content --> <main class=\"main-content\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><!-- Main Content --><main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -168,7 +145,7 @@ func AppLayout(title string, user WalletUser) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</main><style>\n\t\t\t.main-content {\n\t\t\t\tpadding: 24px;\n\t\t\t\tbackground: var(--wa-color-surface-alt);\n\t\t\t\tmin-height: calc(100vh - 56px);\n\t\t\t}\n\t\t</style>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</main></wa-page>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -182,4 +159,33 @@ func AppLayout(title string, user WalletUser) templ.Component {
})
}
func pageStyles() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<style>\n\t\t/* Header Logo */\n\t\t.header-logo {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: var(--wa-space-s);\n\t\t\ttext-decoration: none;\n\t\t\tcolor: var(--wa-color-neutral-900);\n\t\t\tfont-weight: 600;\n\t\t\tfont-size: var(--wa-font-size-l);\n\t\t}\n\t\t\n\t\t.header-logo wa-icon {\n\t\t\tfont-size: 24px;\n\t\t\tcolor: var(--wa-color-primary);\n\t\t}\n\t\t\n\t\t/* Navigation Items */\n\t\t.nav-item {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: var(--wa-space-s);\n\t\t\tpadding: var(--wa-space-s) var(--wa-space-m);\n\t\t\tborder-radius: var(--wa-radius-m);\n\t\t\tcolor: var(--wa-color-neutral-600);\n\t\t\ttext-decoration: none;\n\t\t\tfont-size: var(--wa-font-size-s);\n\t\t\tfont-weight: 500;\n\t\t\ttransition: all 0.15s ease;\n\t\t}\n\t\t\n\t\t.nav-item:hover {\n\t\t\tbackground: var(--wa-color-neutral-100);\n\t\t\tcolor: var(--wa-color-neutral-900);\n\t\t}\n\t\t\n\t\t.nav-item.active {\n\t\t\tbackground: var(--wa-color-primary-subtle);\n\t\t\tcolor: var(--wa-color-primary);\n\t\t}\n\t\t\n\t\t.nav-item wa-icon {\n\t\t\tfont-size: 18px;\n\t\t}\n\t\t\n\t\t/* Navigation Section */\n\t\t.nav-section {\n\t\t\tmargin-top: var(--wa-space-l);\n\t\t\tpadding-top: var(--wa-space-l);\n\t\t\tborder-top: 1px solid var(--wa-color-neutral-200);\n\t\t}\n\t\t\n\t\t.nav-section-title {\n\t\t\tpadding: var(--wa-space-xs) var(--wa-space-m);\n\t\t\tfont-size: var(--wa-font-size-xs);\n\t\t\tfont-weight: 600;\n\t\t\tcolor: var(--wa-color-neutral-500);\n\t\t\ttext-transform: uppercase;\n\t\t\tletter-spacing: 0.05em;\n\t\t}\n\t\t\n\t\t/* User Menu */\n\t\t.user-menu-header {\n\t\t\tpadding: var(--wa-space-m) var(--wa-space-l);\n\t\t}\n\t\t\n\t\t.user-menu-header strong {\n\t\t\tdisplay: block;\n\t\t\tfont-size: var(--wa-font-size-s);\n\t\t\tcolor: var(--wa-color-neutral-900);\n\t\t}\n\t\t\n\t\t.user-menu-header span {\n\t\t\tfont-size: var(--wa-font-size-xs);\n\t\t\tcolor: var(--wa-color-neutral-500);\n\t\t\tfont-family: var(--wa-font-mono);\n\t\t}\n\t\t\n\t\t/* Page customizations */\n\t\twa-page {\n\t\t\t--menu-width: 240px;\n\t\t}\n\t\t\n\t\twa-page[view='mobile'] {\n\t\t\t--menu-width: auto;\n\t\t}\n\t\t\n\t\t/* Main content padding */\n\t\twa-page main {\n\t\t\tpadding: var(--wa-space-l);\n\t\t\tbackground: var(--wa-color-surface-alt);\n\t\t\tmin-height: 100%;\n\t\t}\n\t</style>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -7,12 +7,17 @@ templ Base(title string) {
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="color-scheme" content="light dark"/>
<title>{ title } - Sonr Motr Wallet</title>
<!-- Web Awesome Components -->
<script>
(function() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDark) document.documentElement.classList.add('wa-theme-dark');
})();
</script>
<script src="https://kit.webawesome.com/47c7425b971f443c.js" crossorigin="anonymous"></script>
<!-- HTMX -->
<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/htmx.min.js"></script>
<!-- D3.js for charts -->
<script src="https://cdn.jsdelivr.net/npm/htmx-ext-preload@2.1.1/preload.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<style>
:root {
@@ -30,8 +35,13 @@ templ Base(title string) {
}
</style>
</head>
<body>
<body hx-ext="preload">
{ children... }
<script>
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
document.documentElement.classList.toggle('wa-theme-dark', e.matches);
});
</script>
</body>
</html>
}

View File

@@ -30,20 +30,20 @@ func Base(title string) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" class=\"wa-cloak\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" class=\"wa-cloak\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light dark\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 10, Col: 17}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 11, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Sonr Motr Wallet</title><!-- Web Awesome Components --><script src=\"https://kit.webawesome.com/47c7425b971f443c.js\" crossorigin=\"anonymous\"></script><!-- HTMX --><script src=\"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/htmx.min.js\"></script><!-- D3.js for charts --><script src=\"https://cdn.jsdelivr.net/npm/d3@7\"></script><style>\n\t\t\t\t:root {\n\t\t\t\t\t--wa-color-primary: #17c2ff;\n\t\t\t\t\t--sidebar-width: 64px;\n\t\t\t\t}\n\t\t\t\thtml, body {\n\t\t\t\t\tmin-height: 100%;\n\t\t\t\t\tpadding: 0;\n\t\t\t\t\tmargin: 0;\n\t\t\t\t\tcursor: default;\n\t\t\t\t}\n\t\t\t\twa-button, a, [onclick], [hx-get], [hx-post] {\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t</style></head><body>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Sonr Motr Wallet</title><script>\n\t\t\t\t(function() {\n\t\t\t\t\tconst prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n\t\t\t\t\tif (prefersDark) document.documentElement.classList.add('wa-theme-dark');\n\t\t\t\t})();\n\t\t\t</script><script src=\"https://kit.webawesome.com/47c7425b971f443c.js\" crossorigin=\"anonymous\"></script><script src=\"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/htmx.min.js\"></script><script src=\"https://cdn.jsdelivr.net/npm/htmx-ext-preload@2.1.1/preload.js\"></script><script src=\"https://cdn.jsdelivr.net/npm/d3@7\"></script><style>\n\t\t\t\t:root {\n\t\t\t\t\t--wa-color-primary: #17c2ff;\n\t\t\t\t\t--sidebar-width: 64px;\n\t\t\t\t}\n\t\t\t\thtml, body {\n\t\t\t\t\tmin-height: 100%;\n\t\t\t\t\tpadding: 0;\n\t\t\t\t\tmargin: 0;\n\t\t\t\t\tcursor: default;\n\t\t\t\t}\n\t\t\t\twa-button, a, [onclick], [hx-get], [hx-post] {\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t</style></head><body hx-ext=\"preload\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -51,7 +51,7 @@ func Base(title string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</body></html>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<script>\n\t\t\t\twindow.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {\n\t\t\t\t\tdocument.documentElement.classList.toggle('wa-theme-dark', e.matches);\n\t\t\t\t});\n\t\t\t</script></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -138,25 +138,30 @@ func DefaultMarketCapBubbleData() []components.BubbleData {
templ DashboardPage(data DashboardData, activeTab string) {
@layouts.DashboardLayout("Dashboard - Sonr", layouts.WalletUser{Name: "Sonr Wallet", Address: "sonr1x9f...7k2m"}, activeTab) {
@dashboardStyles()
<div id="dashboard-content">
if activeTab == "" || activeTab == "overview" {
@OverviewPanel(data)
} else if activeTab == "transactions" {
@TransactionsPanel(data.Transactions)
} else if activeTab == "tokens" {
@TokensPanel(data.Tokens)
} else if activeTab == "nfts" {
@NFTsPanel(data.NFTs)
} else if activeTab == "activity" {
@ActivityPanel()
}
</div>
@DashboardContent(data, activeTab)
<!-- Drawers -->
@components.AllDrawers(components.DefaultTokenOptions(), "sonr1x9f4h2k8m3n5p7q2r4s6t8v0w3x5y7z9a1b3c5d7k2m")
@components.DrawerTriggers()
}
}
// DashboardContent renders just the tab content (for HTMX partial updates)
templ DashboardContent(data DashboardData, activeTab string) {
<div id="dashboard-content">
if activeTab == "" || activeTab == "overview" {
@OverviewPanel(data)
} else if activeTab == "transactions" {
@TransactionsPanel(data.Transactions)
} else if activeTab == "tokens" {
@TokensPanel(data.Tokens)
} else if activeTab == "nfts" {
@NFTsPanel(data.NFTs)
} else if activeTab == "activity" {
@ActivityPanel()
}
</div>
}
templ OverviewPanel(data DashboardData) {
<header style="margin-bottom: var(--wa-space-l);">
<div class="wa-flank">

File diff suppressed because one or more lines are too long