Group toolbar items into visual pill containers

Four groups: connection, quad view, themes, panel config.
Each group shares one background pill. Bigger icons (w-6 h-6),
36px hit area with minimal padding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nico 2026-04-03 22:39:44 +02:00
parent f8ef8141f5
commit 4abbe86963

View File

@ -1,93 +1,69 @@
<template> <template>
<div v-if="isLoggedIn" class="app-toolbar"> <div v-if="isLoggedIn" class="app-toolbar">
<!-- Connection pill -->
<button class="toolbar-pill" :class="{ active: connActive }" @click="toggleDropdown('conn')" :title="connLabel || 'Connection'"> <!-- Group: Connection + Takeover -->
<WifiIcon class="w-4 h-4" /> <div class="toolbar-group">
<span v-if="connLabel" class="toolbar-pill-label">{{ connLabel }}</span> <button class="toolbar-btn" :class="{ active: connActive }" @click="toggleDropdown('conn')" :title="connLabel || 'Connection'">
<WifiIcon class="w-6 h-6" />
<span v-if="connLabel" class="toolbar-btn-label">{{ connLabel }}</span>
</button> </button>
<button v-if="takeoverToken" class="toolbar-btn" :class="{ active: captureActive }" @click="toggleDropdown('takeover')" title="Takeover">
<SignalIcon class="w-6 h-6" />
</button>
</div>
<div class="toolbar-spacer" /> <div class="toolbar-spacer" />
<!-- Pane toggles --> <!-- Group: Quad view (pane toggles) -->
<button <div class="toolbar-group">
class="toolbar-pill toolbar-pill-icon" <button class="toolbar-btn" :class="{ active: isPaneOpen('chat') }" @click="togglePane('chat')" title="Chat">
:class="{ active: isPaneOpen('chat') }" <ChatBubbleLeftIcon class="w-6 h-6" />
@click="togglePane('chat')"
title="Chat"
>
<ChatBubbleLeftIcon class="w-4 h-4" />
</button> </button>
<button <button class="toolbar-btn" :class="{ active: isPaneOpen('dashboard') }" @click="togglePane('dashboard')" title="Display">
class="toolbar-pill toolbar-pill-icon" <RectangleGroupIcon class="w-6 h-6" />
:class="{ active: isPaneOpen('dashboard') }"
@click="togglePane('dashboard')"
title="Display"
>
<RectangleGroupIcon class="w-4 h-4" />
</button> </button>
<button <button class="toolbar-btn" :class="{ active: isPaneOpen('files') }" @click="togglePane('files')" title="Files">
class="toolbar-pill toolbar-pill-icon" <FolderIcon class="w-6 h-6" />
:class="{ active: isPaneOpen('files') }"
@click="togglePane('files')"
title="Files"
>
<FolderIcon class="w-4 h-4" />
</button> </button>
<button <button class="toolbar-btn" :class="{ active: isPaneOpen('artifacts') }" @click="togglePane('artifacts')" title="Konsole">
class="toolbar-pill toolbar-pill-icon" <SparklesIcon class="w-6 h-6" />
:class="{ active: isPaneOpen('artifacts') }"
@click="togglePane('artifacts')"
title="Konsole"
>
<SparklesIcon class="w-4 h-4" />
</button> </button>
</div>
<div class="toolbar-divider" /> <!-- Group: Themes -->
<div class="toolbar-group">
<!-- Theme pills (icon only) -->
<button <button
v-for="t in THEMES" v-for="t in THEMES"
:key="t" :key="t"
class="toolbar-pill toolbar-pill-icon" class="toolbar-btn"
:class="{ active: theme === t }" :class="{ active: theme === t }"
@click="setTheme(t)" @click="setTheme(t)"
:title="THEME_NAMES[t]" :title="THEME_NAMES[t]"
> >
<component :is="THEME_ICONS[t]" class="w-4 h-4" /> <component :is="THEME_ICONS[t]" class="w-6 h-6" />
</button> </button>
</div>
<!-- Panel toggles (dev/operator tools) --> <!-- Group: Panel config + version -->
<div class="toolbar-group">
<button <button
v-for="panel in availablePanels" v-for="panel in availablePanels"
:key="panel.id" :key="panel.id"
class="toolbar-pill toolbar-pill-icon" class="toolbar-btn"
:class="{ active: isPanelOpen(panel.id) }" :class="{ active: isPanelOpen(panel.id) }"
@click="togglePanelBtn(panel.id)" @click="togglePanelBtn(panel.id)"
:title="panel.label" :title="panel.label"
> >
<component :is="panelIcon(panel.id)" class="w-4 h-4" /> <component :is="panelIcon(panel.id)" class="w-6 h-6" />
</button> </button>
<button class="toolbar-btn toolbar-btn-dim" @click="copyVersionDetails" :title="versionFull">
<!-- Takeover / Capture -->
<button
v-if="takeoverToken"
class="toolbar-pill"
:class="{ active: captureActive }"
@click="toggleDropdown('takeover')"
title="Takeover"
>
<SignalIcon class="w-4 h-4" />
</button>
<!-- Version pill -->
<button class="toolbar-pill toolbar-pill-dim" @click="copyVersionDetails" :title="versionFull">
<span class="toolbar-version-text">{{ versionShort }}</span> <span class="toolbar-version-text">{{ versionShort }}</span>
</button> </button>
</div>
<!-- Panels (dropdown from toolbar) --> <!-- Dropdowns -->
<div v-if="openDropdown" class="toolbar-panel-backdrop" @click="openDropdown = null" /> <div v-if="openDropdown" class="toolbar-panel-backdrop" @click="openDropdown = null" />
<!-- Connection panel -->
<div v-if="openDropdown === 'conn'" class="toolbar-panel" style="right: auto; left: 0;"> <div v-if="openDropdown === 'conn'" class="toolbar-panel" style="right: auto; left: 0;">
<div class="toolbar-panel-header">Connection</div> <div class="toolbar-panel-header">Connection</div>
<div class="toolbar-panel-row"><span>HTTP</span><span>{{ chatStore.connectionState }}</span></div> <div class="toolbar-panel-row"><span>HTTP</span><span>{{ chatStore.connectionState }}</span></div>
@ -96,7 +72,6 @@
<div class="toolbar-panel-row"><span>Mode</span><span>{{ selectedMode }}</span></div> <div class="toolbar-panel-row"><span>Mode</span><span>{{ selectedMode }}</span></div>
</div> </div>
<!-- Takeover panel -->
<div v-if="openDropdown === 'takeover'" class="toolbar-panel" style="right: 0;"> <div v-if="openDropdown === 'takeover'" class="toolbar-panel" style="right: 0;">
<div class="toolbar-panel-header">Takeover</div> <div class="toolbar-panel-header">Takeover</div>
<div class="toolbar-panel-token" @click="copyToken" :title="tokenCopied ? 'Copied!' : 'Click to copy'"> <div class="toolbar-panel-token" @click="copyToken" :title="tokenCopied ? 'Copied!' : 'Click to copy'">
@ -107,11 +82,10 @@
<span>Capture</span> <span>Capture</span>
<span :style="{ color: captureActive ? 'var(--success, #22c55e)' : 'var(--text-dim)' }">{{ captureActive ? 'ON' : 'OFF' }}</span> <span :style="{ color: captureActive ? 'var(--success, #22c55e)' : 'var(--text-dim)' }">{{ captureActive ? 'ON' : 'OFF' }}</span>
</div> </div>
<button class="toolbar-panel-action" @click="toggleCapture"> <button class="toolbar-panel-action" @click="toggleCapture">{{ captureActive ? 'Disable Capture' : 'Enable Capture' }}</button>
{{ captureActive ? 'Disable Capture' : 'Enable Capture' }}
</button>
<button class="toolbar-panel-action" @click="revokeAndClose">Revoke</button> <button class="toolbar-panel-action" @click="revokeAndClose">Revoke</button>
</div> </div>
</div> </div>
</template> </template>
@ -218,51 +192,57 @@ function toggleDropdown(panel: 'conn' | 'takeover') {
.toolbar-spacer { flex: 1; } .toolbar-spacer { flex: 1; }
.toolbar-divider { /* Group: shared pill container */
width: 1px; .toolbar-group {
height: 18px;
background: var(--border);
opacity: 0.5;
flex-shrink: 0;
}
.toolbar-pill {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px;
background: var(--panel-bg); background: var(--panel-bg);
border-radius: var(--radius-panel, 12px); border-radius: var(--radius-panel, 12px);
box-shadow: var(--panel-shadow); box-shadow: var(--panel-shadow);
padding: 0 12px; padding: 2px;
height: 34px; gap: 1px;
font-size: 0.85rem; flex-shrink: 0;
}
/* Button inside a group */
.toolbar-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
width: 36px;
height: 36px;
border-radius: calc(var(--radius-panel, 12px) - 3px);
color: var(--text-dim); color: var(--text-dim);
border: none; border: none;
background: none;
cursor: pointer; cursor: pointer;
transition: color 0.12s, background 0.12s; font-size: 0.85rem;
white-space: nowrap; white-space: nowrap;
transition: color 0.12s, background 0.12s;
flex-shrink: 0;
} }
.toolbar-pill:hover { color: var(--text); } .toolbar-btn:hover { color: var(--text); background: color-mix(in srgb, var(--text) 6%, transparent); }
.toolbar-pill.active { color: var(--accent); } .toolbar-btn.active { color: var(--accent); }
.toolbar-pill-icon { /* Connection button widens when it has a label */
width: 34px; .toolbar-btn:has(.toolbar-btn-label) {
padding: 0; width: auto;
justify-content: center; padding: 0 8px;
} }
.toolbar-btn-label {
.toolbar-pill-dim {
opacity: 0.5;
}
.toolbar-pill-dim:hover { opacity: 0.8; }
.toolbar-pill-label {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.toolbar-version-text { .toolbar-btn-dim { opacity: 0.45; }
font-size: 0.65rem; .toolbar-btn-dim:hover { opacity: 0.8; }
.toolbar-version-text { font-size: 0.65rem; }
@media (max-width: 480px) {
.toolbar-btn-label { display: none; }
.toolbar-btn:has(.toolbar-btn-label) { width: 32px; padding: 0; }
} }
/* Panel dropdown */ /* Panel dropdown */
@ -334,9 +314,7 @@ function toggleDropdown(panel: 'conn' | 'takeover') {
} }
.toolbar-panel-action:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); color: var(--accent); } .toolbar-panel-action:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); color: var(--accent); }
/* Mobile: hide labels, shrink pills */
@media (max-width: 480px) { @media (max-width: 480px) {
.toolbar-pill-label { display: none; }
.toolbar-panel { width: min(220px, calc(100vw - 16px)); } .toolbar-panel { width: min(220px, calc(100vw - 16px)); }
} }
</style> </style>