feat(tabs): add tab bar styling matching stacked panes
- Add .tab-bar, .tab-button, .tab-button-active CSS classes - Match stacked pane title aesthetics (no border-radius, lighter active bg) - Add orange border for tab mode (distinct from blue pane mode) - Remove colored border-bottom on active tabs per user feedback - Tab buttons use same padding and fonts as stacked titles - Support UI scaling with ui_scale config multiplier - Add pulse animation for tab mode active state - Theme-aware colors for light/dark mode
- commit
- d13852bad5809bb3856f1c106eb7cce845be563b
- author
- bnema · · 2025-11-17 19:41 UTC
- parent
- 965849a
- signature
- unsigned
1 file · +85 -2
M internal/app/browser/workspace_css.go +85 -2
| @@ -99,10 +99,16 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string { | |||
| 99 | 99 | activeBorderColor := getActivePaneBorderColor(styling, isDark) | |
| 100 | 100 | inactiveBorderColor := getInactivePaneBorderColor(styling, isDark) | |
| 101 | 101 | ||
| 102 | - | // Pane mode border color - use configured value or fallback to orange | |
| 102 | + | // Pane mode border color - use configured value or fallback to blue | |
| 103 | 103 | paneModeColor := styling.PaneModeBorderColor | |
| 104 | 104 | if paneModeColor == "" { | |
| 105 | - | paneModeColor = "#FFA500" // Fallback to orange if not configured | |
| 105 | + | paneModeColor = "#4A90E2" // Fallback to blue if not configured | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | // Tab mode border color - use configured value or fallback to orange | |
| 109 | + | tabModeColor := styling.TabModeBorderColor | |
| 110 | + | if tabModeColor == "" { | |
| 111 | + | tabModeColor = "#FFA500" // Fallback to orange if not configured | |
| 106 | 112 | } | |
| 107 | 113 | ||
| 108 | 114 | // Stacked title border - subtle separator between titles | |
| @@ -193,6 +199,66 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string { | |||
| 193 | 199 | ||
| 194 | 200 | .stacked-pane-collapsed { | |
| 195 | 201 | /* Collapsed panes are hidden - handled in code via widget visibility */ | |
| 202 | + | } | |
| 203 | + | ||
| 204 | + | /* Tab bar styling */ | |
| 205 | + | .tab-bar { | |
| 206 | + | background-color: %s; | |
| 207 | + | border-top: 2px solid %s; | |
| 208 | + | padding: 0; | |
| 209 | + | min-height: %dpx; | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | /* Tab mode active - window background changes to border color (like pane mode) */ | |
| 213 | + | window.tab-mode-active { | |
| 214 | + | background-color: %s; | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | /* Tab button styling - matches stacked pane titles */ | |
| 218 | + | .tab-button { | |
| 219 | + | background-color: %s; | |
| 220 | + | border: none; | |
| 221 | + | border-right: 1px solid %s; | |
| 222 | + | border-radius: 0; | |
| 223 | + | padding: %dpx %dpx; | |
| 224 | + | min-width: 100px; | |
| 225 | + | max-width: 200px; | |
| 226 | + | transition: background-color %dms ease-in-out; | |
| 227 | + | } | |
| 228 | + | ||
| 229 | + | .tab-button:hover { | |
| 230 | + | background-color: %s; | |
| 231 | + | } | |
| 232 | + | ||
| 233 | + | .tab-button-active { | |
| 234 | + | background-color: %s; | |
| 235 | + | border: none; | |
| 236 | + | border-right: 1px solid %s; | |
| 237 | + | border-radius: 0; | |
| 238 | + | font-weight: 600; | |
| 239 | + | } | |
| 240 | + | ||
| 241 | + | /* Tab title text */ | |
| 242 | + | .tab-title { | |
| 243 | + | font-size: %dpx; | |
| 244 | + | color: %s; | |
| 245 | + | font-weight: 500; | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | /* Tab content area */ | |
| 249 | + | .tab-content-area { | |
| 250 | + | background-color: %s; | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | /* Tab workspace containers */ | |
| 254 | + | .tab-workspace-container { | |
| 255 | + | background-color: %s; | |
| 256 | + | } | |
| 257 | + | ||
| 258 | + | /* Pulse animation for tab mode */ | |
| 259 | + | @keyframes pulse-border { | |
| 260 | + | 0%%, 100%% { opacity: 1.0; } | |
| 261 | + | 50%% { opacity: 0.6; } | |
| 196 | 262 | }`, | |
| 197 | 263 | windowBackgroundColor, // window background | |
| 198 | 264 | paneModeColor, // window.pane-mode-active background (border color) | |
| @@ -216,6 +282,23 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string { | |||
| 216 | 282 | titleFontSize, // stacked-pane-title-text font-size | |
| 217 | 283 | getStackTitleTextColor(isDark), // stacked-pane-title-text color | |
| 218 | 284 | faviconMargin, // stacked-pane-favicon margin-right | |
| 285 | + | // Tab bar styles | |
| 286 | + | windowBackgroundColor, // tab-bar background | |
| 287 | + | getStackTitleBorderColor(isDark), // tab-bar border-top | |
| 288 | + | int(32 * uiScale), // tab-bar min-height | |
| 289 | + | tabModeColor, // window.tab-mode-active .tab-bar border-top-color (orange, distinct from pane mode blue) | |
| 290 | + | getStackTitleBg(isDark), // tab-button background (same as stacked title) | |
| 291 | + | getStackTitleBorderColor(isDark), // tab-button border-right | |
| 292 | + | titlePaddingVertical, // tab-button padding vertical | |
| 293 | + | titlePaddingHorizontal, // tab-button padding horizontal | |
| 294 | + | styling.TransitionDuration, // tab-button transition | |
| 295 | + | getStackTitleHoverBg(isDark), // tab-button:hover background | |
| 296 | + | getStackTitleHoverBg(isDark), // tab-button-active background (lighter/brighter) | |
| 297 | + | getStackTitleBorderColor(isDark), // tab-button-active border-right | |
| 298 | + | titleFontSize, // tab-title font-size | |
| 299 | + | getStackTitleTextColor(isDark), // tab-title color | |
| 300 | + | windowBackgroundColor, // tab-content-area background | |
| 301 | + | windowBackgroundColor, // tab-workspace-container background | |
| 219 | 302 | ) | |
| 220 | 303 | ||
| 221 | 304 | return css | |
| @@ -99,10 +99,16 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string { | |||||
| 99 | activeBorderColor := getActivePaneBorderColor(styling, isDark) | 99 | activeBorderColor := getActivePaneBorderColor(styling, isDark) | ||
| 100 | inactiveBorderColor := getInactivePaneBorderColor(styling, isDark) | 100 | inactiveBorderColor := getInactivePaneBorderColor(styling, isDark) | ||
| 101 | 101 | ||||
| 102 | - | // Pane mode border color - use configured value or fallback to orange | 102 | + | // Pane mode border color - use configured value or fallback to blue |
| 103 | paneModeColor := styling.PaneModeBorderColor | 103 | paneModeColor := styling.PaneModeBorderColor | ||
| 104 | if paneModeColor == "" { | 104 | if paneModeColor == "" { | ||
| 105 | - | paneModeColor = "#FFA500" // Fallback to orange if not configured | 105 | + | paneModeColor = "#4A90E2" // Fallback to blue if not configured |
| 106 | + | } | |||
| 107 | + | ||||
| 108 | + | // Tab mode border color - use configured value or fallback to orange | |||
| 109 | + | tabModeColor := styling.TabModeBorderColor | |||
| 110 | + | if tabModeColor == "" { | |||
| 111 | + | tabModeColor = "#FFA500" // Fallback to orange if not configured | |||
| 106 | } | 112 | } | ||
| 107 | 113 | ||||
| 108 | // Stacked title border - subtle separator between titles | 114 | // Stacked title border - subtle separator between titles | ||
| @@ -193,6 +199,66 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string { | |||||
| 193 | 199 | ||||
| 194 | .stacked-pane-collapsed { | 200 | .stacked-pane-collapsed { | ||
| 195 | /* Collapsed panes are hidden - handled in code via widget visibility */ | 201 | /* Collapsed panes are hidden - handled in code via widget visibility */ | ||
| 202 | + | } | |||
| 203 | + | ||||
| 204 | + | /* Tab bar styling */ | |||
| 205 | + | .tab-bar { | |||
| 206 | + | background-color: %s; | |||
| 207 | + | border-top: 2px solid %s; | |||
| 208 | + | padding: 0; | |||
| 209 | + | min-height: %dpx; | |||
| 210 | + | } | |||
| 211 | + | ||||
| 212 | + | /* Tab mode active - window background changes to border color (like pane mode) */ | |||
| 213 | + | window.tab-mode-active { | |||
| 214 | + | background-color: %s; | |||
| 215 | + | } | |||
| 216 | + | ||||
| 217 | + | /* Tab button styling - matches stacked pane titles */ | |||
| 218 | + | .tab-button { | |||
| 219 | + | background-color: %s; | |||
| 220 | + | border: none; | |||
| 221 | + | border-right: 1px solid %s; | |||
| 222 | + | border-radius: 0; | |||
| 223 | + | padding: %dpx %dpx; | |||
| 224 | + | min-width: 100px; | |||
| 225 | + | max-width: 200px; | |||
| 226 | + | transition: background-color %dms ease-in-out; | |||
| 227 | + | } | |||
| 228 | + | ||||
| 229 | + | .tab-button:hover { | |||
| 230 | + | background-color: %s; | |||
| 231 | + | } | |||
| 232 | + | ||||
| 233 | + | .tab-button-active { | |||
| 234 | + | background-color: %s; | |||
| 235 | + | border: none; | |||
| 236 | + | border-right: 1px solid %s; | |||
| 237 | + | border-radius: 0; | |||
| 238 | + | font-weight: 600; | |||
| 239 | + | } | |||
| 240 | + | ||||
| 241 | + | /* Tab title text */ | |||
| 242 | + | .tab-title { | |||
| 243 | + | font-size: %dpx; | |||
| 244 | + | color: %s; | |||
| 245 | + | font-weight: 500; | |||
| 246 | + | } | |||
| 247 | + | ||||
| 248 | + | /* Tab content area */ | |||
| 249 | + | .tab-content-area { | |||
| 250 | + | background-color: %s; | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | /* Tab workspace containers */ | |||
| 254 | + | .tab-workspace-container { | |||
| 255 | + | background-color: %s; | |||
| 256 | + | } | |||
| 257 | + | ||||
| 258 | + | /* Pulse animation for tab mode */ | |||
| 259 | + | @keyframes pulse-border { | |||
| 260 | + | 0%%, 100%% { opacity: 1.0; } | |||
| 261 | + | 50%% { opacity: 0.6; } | |||
| 196 | }`, | 262 | }`, | ||
| 197 | windowBackgroundColor, // window background | 263 | windowBackgroundColor, // window background | ||
| 198 | paneModeColor, // window.pane-mode-active background (border color) | 264 | paneModeColor, // window.pane-mode-active background (border color) | ||
| @@ -216,6 +282,23 @@ func (wm *WorkspaceManager) generateWorkspaceCSS() string { | |||||
| 216 | titleFontSize, // stacked-pane-title-text font-size | 282 | titleFontSize, // stacked-pane-title-text font-size | ||
| 217 | getStackTitleTextColor(isDark), // stacked-pane-title-text color | 283 | getStackTitleTextColor(isDark), // stacked-pane-title-text color | ||
| 218 | faviconMargin, // stacked-pane-favicon margin-right | 284 | faviconMargin, // stacked-pane-favicon margin-right | ||
| 285 | + | // Tab bar styles | |||
| 286 | + | windowBackgroundColor, // tab-bar background | |||
| 287 | + | getStackTitleBorderColor(isDark), // tab-bar border-top | |||
| 288 | + | int(32 * uiScale), // tab-bar min-height | |||
| 289 | + | tabModeColor, // window.tab-mode-active .tab-bar border-top-color (orange, distinct from pane mode blue) | |||
| 290 | + | getStackTitleBg(isDark), // tab-button background (same as stacked title) | |||
| 291 | + | getStackTitleBorderColor(isDark), // tab-button border-right | |||
| 292 | + | titlePaddingVertical, // tab-button padding vertical | |||
| 293 | + | titlePaddingHorizontal, // tab-button padding horizontal | |||
| 294 | + | styling.TransitionDuration, // tab-button transition | |||
| 295 | + | getStackTitleHoverBg(isDark), // tab-button:hover background | |||
| 296 | + | getStackTitleHoverBg(isDark), // tab-button-active background (lighter/brighter) | |||
| 297 | + | getStackTitleBorderColor(isDark), // tab-button-active border-right | |||
| 298 | + | titleFontSize, // tab-title font-size | |||
| 299 | + | getStackTitleTextColor(isDark), // tab-title color | |||
| 300 | + | windowBackgroundColor, // tab-content-area background | |||
| 301 | + | windowBackgroundColor, // tab-workspace-container background | |||
| 219 | ) | 302 | ) | ||
| 220 | 303 | ||||
| 221 | return css | 304 | return css | ||