feat(layouts): add app header with blockchain and account selectors

This commit is contained in:
2026-01-05 15:02:50 -05:00
parent d13b2c088d
commit 019feeb759
5 changed files with 767 additions and 395 deletions

View File

@@ -5,6 +5,66 @@ type WalletUser struct {
Address string
}
// Blockchain represents a supported blockchain network
type Blockchain struct {
ID string
Name string
Symbol string
Icon string
Color string
Testnet bool
}
// Account represents a wallet account
type Account struct {
ID string
Name string
Address string
Initials string
Color string
Active bool
}
// AppContext holds navigation state for the header
type AppContext struct {
User WalletUser
Blockchains []Blockchain
Accounts []Account
SelectedChainID string
SelectedAccountID string
}
// DefaultBlockchains returns the available blockchain networks
func DefaultBlockchains() []Blockchain {
return []Blockchain{
{ID: "sonr", Name: "Sonr", Symbol: "SNR", Icon: "cube", Color: "var(--wa-color-primary)", Testnet: false},
{ID: "ethereum", Name: "Ethereum", Symbol: "ETH", Icon: "ethereum", Color: "#627eea", Testnet: false},
{ID: "avalanche", Name: "Avalanche", Symbol: "AVAX", Icon: "mountain", Color: "#e84142", Testnet: false},
{ID: "polygon", Name: "Polygon", Symbol: "MATIC", Icon: "hexagon", Color: "#8247e5", Testnet: false},
{ID: "sonr-testnet", Name: "Sonr Testnet", Symbol: "tSNR", Icon: "cube", Color: "var(--wa-color-warning)", Testnet: true},
}
}
// DefaultAccounts returns the user's wallet accounts
func DefaultAccounts() []Account {
return []Account{
{ID: "main", Name: "Main Wallet", Address: "sonr1x9f...7k2m", Initials: "M", Color: "var(--wa-color-primary)", Active: true},
{ID: "trading", Name: "Trading", Address: "sonr1k4m...9p3q", Initials: "T", Color: "var(--wa-color-success)", Active: false},
{ID: "savings", Name: "Savings", Address: "sonr1r7t...2w8x", Initials: "S", Color: "var(--wa-color-warning)", Active: false},
}
}
// DefaultAppContext returns the default navigation context
func DefaultAppContext() AppContext {
return AppContext{
User: WalletUser{Name: "Sonr Wallet", Address: "sonr1x9f...7k2m"},
Blockchains: DefaultBlockchains(),
Accounts: DefaultAccounts(),
SelectedChainID: "sonr",
SelectedAccountID: "main",
}
}
templ AppLayout(title string, user WalletUser) {
@Base(title) {
@appStyles()
@@ -22,19 +82,71 @@ templ AppLayout(title string, user WalletUser) {
}
templ AppHeader(user WalletUser) {
@AppHeaderWithContext(DefaultAppContext())
}
templ AppHeaderWithContext(ctx AppContext) {
<div class="header-wrapper">
<header class="app-header">
<div class="header-left">
<wa-icon name="cube" style="font-size: 24px; color: var(--wa-color-primary);"></wa-icon>
<span class="logo-text">Sonr Wallet</span>
<wa-breadcrumb>
<span slot="separator">/</span>
<!-- Sonr Logo -->
<wa-breadcrumb-item>
<a href="/dashboard" class="breadcrumb-logo">
<wa-icon name="cube" style="font-size: 20px; color: var(--wa-color-primary);"></wa-icon>
<span>Sonr</span>
</a>
</wa-breadcrumb-item>
<!-- Blockchain Selector -->
<wa-breadcrumb-item>
<wa-combobox placeholder="Network" value={ ctx.SelectedChainID } size="small" class="nav-combobox">
<small>Mainnets</small>
for _, chain := range ctx.Blockchains {
if !chain.Testnet {
<wa-option value={ chain.ID }>
<wa-icon slot="prefix" name={ chain.Icon } style={ "color: " + chain.Color + ";" }></wa-icon>
{ chain.Name }
</wa-option>
}
}
<wa-divider></wa-divider>
<small>Testnets</small>
for _, chain := range ctx.Blockchains {
if chain.Testnet {
<wa-option value={ chain.ID }>
<wa-icon slot="prefix" name={ chain.Icon } style={ "color: " + chain.Color + ";" }></wa-icon>
{ chain.Name }
</wa-option>
}
}
</wa-combobox>
</wa-breadcrumb-item>
<!-- Account Selector -->
<wa-breadcrumb-item>
<wa-combobox placeholder="Account" value={ ctx.SelectedAccountID } size="small" class="nav-combobox">
for _, account := range ctx.Accounts {
<wa-option value={ account.ID }>
<wa-avatar slot="prefix" initials={ account.Initials } style={ "--size: 20px; background: " + account.Color + ";" }></wa-avatar>
{ account.Name }
</wa-option>
}
<wa-divider></wa-divider>
<wa-option value="__create__">
<wa-icon slot="prefix" name="plus"></wa-icon>
Create Account
</wa-option>
</wa-combobox>
</wa-breadcrumb-item>
</wa-breadcrumb>
</div>
<div class="header-right">
<wa-dropdown>
<wa-avatar slot="trigger" initials="S" style="--size: 36px; background: var(--wa-color-primary); cursor: pointer;"></wa-avatar>
<div style="padding: var(--wa-space-m); border-bottom: 1px solid var(--wa-color-neutral-200);">
<div class="wa-stack wa-gap-0">
<span class="wa-heading-s">{ user.Name }</span>
<span class="wa-caption-xs" style="color: var(--wa-color-neutral-500);">{ user.Address }</span>
<span class="wa-heading-s">{ ctx.User.Name }</span>
<span class="wa-caption-xs" style="color: var(--wa-color-neutral-500);">{ ctx.User.Address }</span>
</div>
</div>
<wa-dropdown-item href="/connections">
@@ -95,11 +207,6 @@ templ appStyles() {
align-items: center;
gap: var(--wa-space-m);
}
.logo-text {
font-weight: 600;
font-size: var(--wa-font-size-l);
color: var(--wa-color-neutral-900);
}
.main-content {
flex: 1;
overflow-y: auto;
@@ -109,5 +216,39 @@ templ appStyles() {
margin: 0 auto;
padding: 0 var(--wa-space-l);
}
/* Breadcrumb Navigation */
.header-left wa-breadcrumb {
--separator-color: var(--wa-color-neutral-400);
}
.header-left wa-breadcrumb::part(base) {
align-items: center;
}
.breadcrumb-logo {
display: flex;
align-items: center;
gap: var(--wa-space-xs);
text-decoration: none;
color: var(--wa-color-neutral-900);
font-weight: 600;
font-size: var(--wa-font-size-m);
}
.breadcrumb-logo:hover {
color: var(--wa-color-primary);
}
.nav-combobox {
--wa-input-border-color: transparent;
--wa-input-background-color: var(--wa-color-surface-alt);
min-width: 140px;
}
.nav-combobox::part(combobox) {
border-radius: var(--wa-radius-m);
padding: var(--wa-space-2xs) var(--wa-space-s);
}
.nav-combobox:hover::part(combobox) {
background: var(--wa-color-neutral-100);
}
.nav-combobox::part(display-input) {
font-weight: 500;
}
</style>
}

View File

@@ -10,6 +10,7 @@ templ Base(title string) {
<title>{ title } - Sonr Motr Wallet</title>
<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/d3@7"></script>
<style>
:root {
--wa-color-primary: #17c2ff;

View File

@@ -43,7 +43,7 @@ func Base(title string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Sonr Motr Wallet</title><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><style>\n\t\t\t\t:root {\n\t\t\t\t\t--wa-color-primary: #17c2ff;\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 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/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}\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>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}