2026-01-05 15:39:41 -05:00
|
|
|
package components
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NavUser holds user display info
|
|
|
|
|
type NavUser struct {
|
|
|
|
|
Name string
|
|
|
|
|
Address string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NavContext holds navigation state for the header
|
|
|
|
|
type NavContext struct {
|
|
|
|
|
User NavUser
|
|
|
|
|
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},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DefaultNavContext returns the default navigation context
|
|
|
|
|
func DefaultNavContext() NavContext {
|
|
|
|
|
return NavContext{
|
|
|
|
|
User: NavUser{Name: "Sonr Wallet", Address: "sonr1x9f...7k2m"},
|
|
|
|
|
Blockchains: DefaultBlockchains(),
|
|
|
|
|
Accounts: DefaultAccounts(),
|
|
|
|
|
SelectedChainID: "sonr",
|
|
|
|
|
SelectedAccountID: "main",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getSelectedChainName returns the name of the currently selected chain
|
|
|
|
|
func getSelectedChainName(chains []Blockchain, id string) string {
|
|
|
|
|
for _, c := range chains {
|
|
|
|
|
if c.ID == id {
|
|
|
|
|
return c.Name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return "Network"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getSelectedAccountName returns the name of the currently selected account
|
|
|
|
|
func getSelectedAccountName(accounts []Account, id string) string {
|
|
|
|
|
for _, a := range accounts {
|
|
|
|
|
if a.ID == id {
|
|
|
|
|
return a.Name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return "Account"
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-05 18:41:05 -05:00
|
|
|
// 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>
|
2026-01-05 15:39:41 -05:00
|
|
|
}
|
2026-01-05 18:41:05 -05:00
|
|
|
}
|
|
|
|
|
<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 }
|
2026-01-05 15:39:41 -05:00
|
|
|
</wa-menu-item>
|
|
|
|
|
}
|
2026-01-05 18:41:05 -05:00
|
|
|
}
|
|
|
|
|
</wa-menu>
|
|
|
|
|
</wa-dropdown>
|
|
|
|
|
<span class="breadcrumb-sep">/</span>
|
|
|
|
|
<!-- Account Dropdown -->
|
2026-01-05 15:39:41 -05:00
|
|
|
<wa-dropdown>
|
2026-01-05 18:41:05 -05:00
|
|
|
<button slot="trigger" class="breadcrumb-btn">
|
|
|
|
|
{ getSelectedAccountName(navCtx.Accounts, navCtx.SelectedAccountID) }
|
|
|
|
|
<wa-icon name="chevron-down"></wa-icon>
|
|
|
|
|
</button>
|
2026-01-05 15:39:41 -05:00
|
|
|
<wa-menu>
|
2026-01-05 18:41:05 -05:00
|
|
|
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>
|
|
|
|
|
}
|
2026-01-05 16:17:24 -05:00
|
|
|
<wa-divider></wa-divider>
|
2026-01-05 18:41:05 -05:00
|
|
|
<wa-menu-item value="__new__">
|
|
|
|
|
<wa-icon slot="prefix" name="plus"></wa-icon>
|
|
|
|
|
New Account
|
2026-01-05 16:17:24 -05:00
|
|
|
</wa-menu-item>
|
2026-01-05 15:39:41 -05:00
|
|
|
</wa-menu>
|
|
|
|
|
</wa-dropdown>
|
2026-01-05 18:41:05 -05:00
|
|
|
</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>
|
|
|
|
|
@BreadcrumbNav(navCtx)
|
|
|
|
|
</nav>
|
|
|
|
|
<!-- User Dropdown -->
|
|
|
|
|
@UserDropdown(navCtx.User)
|
2026-01-05 15:39:41 -05:00
|
|
|
</header>
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-05 18:41:05 -05:00
|
|
|
// TopBarStyles renders the CSS for the top bar (DEPRECATED - use wa-page styles)
|
2026-01-05 16:17:24 -05:00
|
|
|
templ TopBarStyles() {
|
2026-01-05 15:39:41 -05:00
|
|
|
<style>
|
2026-01-05 16:17:24 -05:00
|
|
|
/* Top Bar */
|
|
|
|
|
.topbar {
|
2026-01-05 15:39:41 -05:00
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
2026-01-05 16:17:24 -05:00
|
|
|
height: 56px;
|
|
|
|
|
padding: 0 24px;
|
|
|
|
|
background: var(--wa-color-surface);
|
|
|
|
|
border-bottom: 1px solid var(--wa-color-neutral-200);
|
2026-01-05 15:39:41 -05:00
|
|
|
}
|
2026-01-05 16:17:24 -05:00
|
|
|
|
|
|
|
|
.topbar-nav {
|
2026-01-05 15:39:41 -05:00
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
2026-01-05 15:48:02 -05:00
|
|
|
gap: 0;
|
2026-01-05 15:39:41 -05:00
|
|
|
}
|
2026-01-05 16:17:24 -05:00
|
|
|
|
|
|
|
|
.topbar-title {
|
2026-01-05 15:39:41 -05:00
|
|
|
font-weight: 600;
|
2026-01-05 15:48:02 -05:00
|
|
|
font-size: 14px;
|
2026-01-05 16:17:24 -05:00
|
|
|
color: var(--wa-color-neutral-900);
|
|
|
|
|
padding: 6px 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topbar-sep {
|
|
|
|
|
color: var(--wa-color-neutral-300);
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
padding: 0 4px;
|
2026-01-05 15:39:41 -05:00
|
|
|
}
|
2026-01-05 16:17:24 -05:00
|
|
|
|
|
|
|
|
.topbar-btn {
|
2026-01-05 15:39:41 -05:00
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
2026-01-05 15:48:02 -05:00
|
|
|
gap: 4px;
|
2026-01-05 16:17:24 -05:00
|
|
|
padding: 6px 10px;
|
2026-01-05 15:39:41 -05:00
|
|
|
border: none;
|
2026-01-05 16:17:24 -05:00
|
|
|
background: transparent;
|
2026-01-05 15:39:41 -05:00
|
|
|
font: inherit;
|
2026-01-05 15:48:02 -05:00
|
|
|
font-size: 14px;
|
2026-01-05 15:39:41 -05:00
|
|
|
font-weight: 500;
|
|
|
|
|
color: var(--wa-color-neutral-700);
|
2026-01-05 15:48:02 -05:00
|
|
|
border-radius: 6px;
|
2026-01-05 15:39:41 -05:00
|
|
|
cursor: pointer;
|
2026-01-05 16:17:24 -05:00
|
|
|
transition: background 0.15s;
|
2026-01-05 15:39:41 -05:00
|
|
|
}
|
2026-01-05 16:17:24 -05:00
|
|
|
|
|
|
|
|
.topbar-btn:hover {
|
|
|
|
|
background: var(--wa-color-neutral-100);
|
2026-01-05 15:39:41 -05:00
|
|
|
}
|
2026-01-05 16:17:24 -05:00
|
|
|
|
|
|
|
|
.topbar-btn wa-icon {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: var(--wa-color-neutral-400);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* User Menu */
|
|
|
|
|
.user-menu-header {
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-menu-header strong {
|
|
|
|
|
display: block;
|
2026-01-05 15:48:02 -05:00
|
|
|
font-size: 14px;
|
2026-01-05 16:17:24 -05:00
|
|
|
color: var(--wa-color-neutral-900);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-menu-header span {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: var(--wa-color-neutral-500);
|
|
|
|
|
font-family: var(--wa-font-mono);
|
2026-01-05 15:39:41 -05:00
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
}
|