286 lines
8.7 KiB
Plaintext
286 lines
8.7 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
|
|
}
|
|
|
|
// NavTab represents a tab in the navigation
|
|
type NavTab struct {
|
|
ID string
|
|
Label string
|
|
Icon string
|
|
Active bool
|
|
}
|
|
|
|
// NavContext holds navigation state for the header
|
|
type NavContext struct {
|
|
User NavUser
|
|
Blockchains []Blockchain
|
|
Accounts []Account
|
|
Tabs []NavTab
|
|
SelectedChainID string
|
|
SelectedAccountID string
|
|
ActiveTab 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},
|
|
}
|
|
}
|
|
|
|
// DashboardTabs returns the default dashboard tabs
|
|
func DashboardTabs(activeTab string) []NavTab {
|
|
return []NavTab{
|
|
{ID: "overview", Label: "Overview", Icon: "grid-2", Active: activeTab == "overview" || activeTab == ""},
|
|
{ID: "transactions", Label: "Transactions", Icon: "arrow-right-arrow-left", Active: activeTab == "transactions"},
|
|
{ID: "tokens", Label: "Tokens", Icon: "coins", Active: activeTab == "tokens"},
|
|
{ID: "nfts", Label: "NFTs", Icon: "image", Active: activeTab == "nfts"},
|
|
{ID: "activity", Label: "Activity", Icon: "chart-line", Active: activeTab == "activity"},
|
|
}
|
|
}
|
|
|
|
// DefaultNavContext returns the default navigation context
|
|
func DefaultNavContext() NavContext {
|
|
return NavContext{
|
|
User: NavUser{Name: "Sonr Wallet", Address: "sonr1x9f...7k2m"},
|
|
Blockchains: DefaultBlockchains(),
|
|
Accounts: DefaultAccounts(),
|
|
Tabs: DashboardTabs(""),
|
|
SelectedChainID: "sonr",
|
|
SelectedAccountID: "main",
|
|
ActiveTab: "overview",
|
|
}
|
|
}
|
|
|
|
// 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"
|
|
}
|
|
|
|
// AppHeader renders the header section for wa-page header slot
|
|
templ AppHeader(navCtx NavContext) {
|
|
<header slot="header" class="app-header">
|
|
<nav class="header-nav">
|
|
<!-- Logo -->
|
|
<a href="/dashboard" class="nav-logo">
|
|
<wa-icon name="cube"></wa-icon>
|
|
<span>Sonr</span>
|
|
</a>
|
|
<span class="nav-sep">/</span>
|
|
<!-- Chain Dropdown -->
|
|
<wa-dropdown>
|
|
<button slot="trigger" class="nav-btn">
|
|
{ getSelectedChainName(navCtx.Blockchains, navCtx.SelectedChainID) }
|
|
<wa-icon name="chevron-down"></wa-icon>
|
|
</button>
|
|
<wa-menu>
|
|
<small class="menu-label">Mainnets</small>
|
|
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>
|
|
<small class="menu-label">Testnets</small>
|
|
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="nav-sep">/</span>
|
|
<!-- Account Dropdown -->
|
|
<wa-dropdown>
|
|
<button slot="trigger" class="nav-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:16px;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>
|
|
<!-- User Avatar -->
|
|
<wa-dropdown>
|
|
<wa-avatar slot="trigger" initials={ string(navCtx.User.Name[0]) } style="--size:28px;cursor:pointer;"></wa-avatar>
|
|
<wa-menu>
|
|
<div class="user-info">
|
|
<strong>{ navCtx.User.Name }</strong>
|
|
<span>{ navCtx.User.Address }</span>
|
|
</div>
|
|
<wa-divider></wa-divider>
|
|
<wa-menu-item value="settings"><wa-icon slot="prefix" name="gear"></wa-icon>Settings</wa-menu-item>
|
|
<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>
|
|
}
|
|
|
|
// AppSubheader renders the tabs section for wa-page subheader slot
|
|
templ AppSubheader(tabs []NavTab) {
|
|
<nav slot="subheader" class="tab-bar">
|
|
for _, tab := range tabs {
|
|
<a href={ templ.SafeURL("/dashboard?tab=" + tab.ID) } class={ "tab", templ.KV("active", tab.Active) }>
|
|
<wa-icon name={ tab.Icon }></wa-icon>
|
|
{ tab.Label }
|
|
</a>
|
|
}
|
|
</nav>
|
|
}
|
|
|
|
// AppNavStyles renders the CSS styles for the navigation components
|
|
templ AppNavStyles() {
|
|
<style>
|
|
/* Reset wa-page slots */
|
|
wa-page::part(header), wa-page::part(subheader) {
|
|
background: var(--wa-color-surface);
|
|
padding: 0;
|
|
}
|
|
wa-page::part(header) { border-bottom: 1px solid var(--wa-color-neutral-200); }
|
|
wa-page::part(main-content) { background: var(--wa-color-surface-alt); }
|
|
|
|
/* Header Bar */
|
|
.app-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
height: 40px;
|
|
padding: 0 12px;
|
|
}
|
|
.header-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
}
|
|
.nav-logo {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 4px 6px;
|
|
text-decoration: none;
|
|
color: var(--wa-color-primary);
|
|
font-weight: 600;
|
|
font-size: 13px;
|
|
border-radius: 4px;
|
|
}
|
|
.nav-logo:hover { background: var(--wa-color-neutral-100); }
|
|
.nav-logo wa-icon { font-size: 16px; }
|
|
.nav-sep { color: var(--wa-color-neutral-300); font-size: 14px; padding: 0 2px; }
|
|
.nav-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 3px;
|
|
padding: 4px 6px;
|
|
border: none;
|
|
background: none;
|
|
font: inherit;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--wa-color-neutral-700);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
.nav-btn:hover { background: var(--wa-color-neutral-100); }
|
|
.nav-btn wa-icon { font-size: 10px; color: var(--wa-color-neutral-400); }
|
|
.menu-label { display: block; padding: 6px 12px; font-size: 11px; color: var(--wa-color-neutral-500); }
|
|
.user-info { padding: 8px 12px; }
|
|
.user-info strong { display: block; font-size: 13px; }
|
|
.user-info span { font-size: 11px; color: var(--wa-color-neutral-500); }
|
|
|
|
/* Tab Bar */
|
|
.tab-bar {
|
|
display: flex;
|
|
gap: 0;
|
|
padding: 0 12px;
|
|
border-bottom: 1px solid var(--wa-color-neutral-200);
|
|
}
|
|
.tab {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 8px 12px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--wa-color-neutral-600);
|
|
text-decoration: none;
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -1px;
|
|
}
|
|
.tab:hover { color: var(--wa-color-neutral-900); }
|
|
.tab.active { color: var(--wa-color-primary); border-bottom-color: var(--wa-color-primary); }
|
|
.tab wa-icon { font-size: 14px; }
|
|
|
|
/* Content */
|
|
.content-container { max-width: 1200px; margin: 0 auto; padding: 16px; }
|
|
</style>
|
|
}
|