feat: gamepad navigation for Mesh tab — zone-based panel nav
- Peer rows: tabindex + role=button + Enter handler for D-pad selection - Zone attributes: mesh-left, mesh-chat, mesh-tools for cross-panel nav - Actions row: data-controller-container for Up from peers - Right from peers → chat input, Right from chat → tools tabs (wide) - Down from tabs → panel fields/buttons in grid fashion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ea8877d20
commit
377195f7e0
@ -344,7 +344,7 @@ function truncatePubkey(hex: string | null): string {
|
|||||||
<!-- Responsive column layout -->
|
<!-- Responsive column layout -->
|
||||||
<div class="mesh-columns" :class="{ 'mesh-columns-wide': isWideDesktop }">
|
<div class="mesh-columns" :class="{ 'mesh-columns-wide': isWideDesktop }">
|
||||||
<!-- LEFT COLUMN: Status + Peers -->
|
<!-- LEFT COLUMN: Status + Peers -->
|
||||||
<div class="mesh-left" :class="{ 'mobile-hidden': mobileShowChat }">
|
<div class="mesh-left" data-controller-zone="mesh-left" :class="{ 'mobile-hidden': mobileShowChat }">
|
||||||
<!-- Device Status -->
|
<!-- Device Status -->
|
||||||
<div data-controller-container tabindex="0" class="glass-card mesh-status-card">
|
<div data-controller-container tabindex="0" class="glass-card mesh-status-card">
|
||||||
<div class="mesh-status-header">
|
<div class="mesh-status-header">
|
||||||
@ -410,7 +410,7 @@ function truncatePubkey(hex: string | null): string {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Actions row -->
|
<!-- Actions row -->
|
||||||
<div class="mesh-actions">
|
<div class="mesh-actions" data-controller-container tabindex="0">
|
||||||
<button class="glass-button mesh-action-btn" :disabled="configuring" @click="handleToggleEnabled">
|
<button class="glass-button mesh-action-btn" :disabled="configuring" @click="handleToggleEnabled">
|
||||||
{{ mesh.status?.enabled ? 'Disable' : 'Enable' }}
|
{{ mesh.status?.enabled ? 'Disable' : 'Enable' }}
|
||||||
</button>
|
</button>
|
||||||
@ -441,7 +441,10 @@ function truncatePubkey(hex: string | null): string {
|
|||||||
<div
|
<div
|
||||||
class="mesh-peer-row is-channel"
|
class="mesh-peer-row is-channel"
|
||||||
:class="{ active: archChannelActive }"
|
:class="{ active: archChannelActive }"
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
@click="openArchChannel"
|
@click="openArchChannel"
|
||||||
|
@keydown.enter="openArchChannel"
|
||||||
>
|
>
|
||||||
<div class="mesh-peer-avatar channel" style="background: rgba(251,146,60,0.2); color: #fb923c;">A</div>
|
<div class="mesh-peer-avatar channel" style="background: rgba(251,146,60,0.2); color: #fb923c;">A</div>
|
||||||
<div class="mesh-peer-info">
|
<div class="mesh-peer-info">
|
||||||
@ -454,7 +457,10 @@ function truncatePubkey(hex: string | null): string {
|
|||||||
<div
|
<div
|
||||||
class="mesh-peer-row is-channel"
|
class="mesh-peer-row is-channel"
|
||||||
:class="{ active: activeChatChannel?.index === 0 }"
|
:class="{ active: activeChatChannel?.index === 0 }"
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
@click="openChannelChat(publicChannel)"
|
@click="openChannelChat(publicChannel)"
|
||||||
|
@keydown.enter="openChannelChat(publicChannel)"
|
||||||
>
|
>
|
||||||
<div class="mesh-peer-avatar channel">#</div>
|
<div class="mesh-peer-avatar channel">#</div>
|
||||||
<div class="mesh-peer-info">
|
<div class="mesh-peer-info">
|
||||||
@ -466,7 +472,10 @@ function truncatePubkey(hex: string | null): string {
|
|||||||
v-for="peer in sortedPeers" :key="peer.contact_id"
|
v-for="peer in sortedPeers" :key="peer.contact_id"
|
||||||
class="mesh-peer-row"
|
class="mesh-peer-row"
|
||||||
:class="{ active: activeChatPeer?.contact_id === peer.contact_id, 'is-archy': isArchyNode(peer) }"
|
:class="{ active: activeChatPeer?.contact_id === peer.contact_id, 'is-archy': isArchyNode(peer) }"
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
@click="openChat(peer)"
|
@click="openChat(peer)"
|
||||||
|
@keydown.enter="openChat(peer)"
|
||||||
>
|
>
|
||||||
<div class="mesh-peer-avatar" :class="{ archy: isArchyNode(peer) }">
|
<div class="mesh-peer-avatar" :class="{ archy: isArchyNode(peer) }">
|
||||||
<AnimatedLogo v-if="isArchyNode(peer)" size="sm" />
|
<AnimatedLogo v-if="isArchyNode(peer)" size="sm" />
|
||||||
@ -493,7 +502,7 @@ function truncatePubkey(hex: string | null): string {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RIGHT COLUMN: Tabbed panels -->
|
<!-- RIGHT COLUMN: Tabbed panels -->
|
||||||
<div class="mesh-right" :class="{ 'mobile-hidden': !mobileShowChat }">
|
<div class="mesh-right" data-controller-zone="mesh-chat" :class="{ 'mobile-hidden': !mobileShowChat }">
|
||||||
<!-- Tab bar (medium desktop only) -->
|
<!-- Tab bar (medium desktop only) -->
|
||||||
<div v-if="showTabBar" class="mesh-tab-bar">
|
<div v-if="showTabBar" class="mesh-tab-bar">
|
||||||
<button class="mesh-tab" :class="{ active: activeTab === 'chat' }" @click="activeTab = 'chat'">Chat</button>
|
<button class="mesh-tab" :class="{ active: activeTab === 'chat' }" @click="activeTab = 'chat'">Chat</button>
|
||||||
@ -614,8 +623,8 @@ function truncatePubkey(hex: string | null): string {
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tools panels -->
|
<!-- Tools panels (3rd column on wide screens) -->
|
||||||
<div class="mesh-tools-wrapper">
|
<div class="mesh-tools-wrapper" data-controller-zone="mesh-tools">
|
||||||
<!-- Tools tab bar (wide desktop only) -->
|
<!-- Tools tab bar (wide desktop only) -->
|
||||||
<div v-if="isWideDesktop" class="mesh-tools-tab-bar">
|
<div v-if="isWideDesktop" class="mesh-tools-tab-bar">
|
||||||
<button class="mesh-tab" :class="{ active: toolsTab === 'bitcoin' }" @click="toolsTab = 'bitcoin'">
|
<button class="mesh-tab" :class="{ active: toolsTab === 'bitcoin' }" @click="toolsTab = 'bitcoin'">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user