Files
nebula/components/navbar.templ
Prad Nukala 30ed4e9ec7 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
2026-01-05 18:41:05 -05:00

267 lines
7.1 KiB
Plaintext

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"
}
// 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>
@BreadcrumbNav(navCtx)
</nav>
<!-- User Dropdown -->
@UserDropdown(navCtx.User)
</header>
}
// TopBarStyles renders the CSS for the top bar (DEPRECATED - use wa-page styles)
templ TopBarStyles() {
<style>
/* Top Bar */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 0 24px;
background: var(--wa-color-surface);
border-bottom: 1px solid var(--wa-color-neutral-200);
}
.topbar-nav {
display: flex;
align-items: center;
gap: 0;
}
.topbar-title {
font-weight: 600;
font-size: 14px;
color: var(--wa-color-neutral-900);
padding: 6px 8px;
}
.topbar-sep {
color: var(--wa-color-neutral-300);
font-size: 16px;
padding: 0 4px;
}
.topbar-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 10px;
border: none;
background: transparent;
font: inherit;
font-size: 14px;
font-weight: 500;
color: var(--wa-color-neutral-700);
border-radius: 6px;
cursor: pointer;
transition: background 0.15s;
}
.topbar-btn:hover {
background: var(--wa-color-neutral-100);
}
.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;
font-size: 14px;
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);
}
</style>
}