fix: aligned more to old shell

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-26 07:55:13 +02:00
parent 689166a80d
commit 2891e96069
4 changed files with 159 additions and 24 deletions

View File

@@ -134,9 +134,15 @@
<div class="tab-bar-tabs">
<%= for tab <- @workbench.tabs do %>
<div
class={["tab", if(@workbench.active_tab == {tab.type, tab.id}, do: "active"), if(tab.is_transient, do: "transient")]}
class={[
"tab",
if(@workbench.active_tab == {tab.type, tab.id}, do: "active"),
if(tab.is_transient, do: "transient"),
if(Workbench.dirty?(@workbench, tab.type, tab.id), do: "dirty")
]}
data-tab-type={tab.type}
data-tab-id={tab.id}
tabindex="0"
>
<button
class="tab-select"
@@ -148,20 +154,25 @@
<span class="tab-icon"><%= raw(ShellData.activity_icon(tab_icon_id(tab))) %></span>
<span class="tab-title"><%= tab_title(tab, @tab_meta) %></span>
</button>
<button
class="tab-close"
data-testid="tab-close"
data-tab-type={tab.type}
data-tab-id={tab.id}
type="button"
phx-click="close_tab"
phx-value-type={tab.type}
phx-value-id={tab.id}
aria-label={translated("Close tab")}
title={translated("Close tab")}
>
×
</button>
<div class="tab-actions">
<%= if Workbench.dirty?(@workbench, tab.type, tab.id) do %>
<span class="tab-dirty-indicator">●</span>
<% end %>
<button
class="tab-close"
data-testid="tab-close"
data-tab-type={tab.type}
data-tab-id={tab.id}
type="button"
phx-click="close_tab"
phx-value-type={tab.type}
phx-value-id={tab.id}
aria-label={translated("Close tab")}
title={translated("Close tab")}
>
×
</button>
</div>
</div>
<% end %>
</div>

View File

@@ -107,7 +107,7 @@ button {
flex-shrink: 0;
app-region: drag;
-webkit-app-region: drag;
padding-right: 10px;
padding-right: calc(10px + var(--bds-titlebar-overlay-right, 0px));
}
.window-titlebar-menu-bar {
@@ -125,6 +125,10 @@ button {
display: none;
}
.window-titlebar.is-mac .window-titlebar-menu-bar {
margin-left: max(var(--bds-titlebar-macos-left-inset, 78px), calc(6px + var(--bds-titlebar-overlay-left, 0px)));
}
.window-titlebar-menu-button {
height: 24px;
border: none;
@@ -142,6 +146,14 @@ button {
background-color: var(--vscode-toolbar-hoverBackground);
}
.window-titlebar-menu-button:focus,
.window-titlebar-menu-button:focus-visible,
.window-titlebar-action-button:focus,
.window-titlebar-action-button:focus-visible {
outline: none;
box-shadow: none;
}
.window-titlebar-drag-region {
flex: 1;
height: 100%;
@@ -296,7 +308,7 @@ button {
justify-content: center;
background: transparent;
border: none;
color: var(--vscode-titleBar-activeForeground);
color: var(--vscode-activityBar-foreground);
opacity: 0.6;
cursor: pointer;
position: relative;
@@ -319,7 +331,7 @@ button {
top: 0;
bottom: 0;
width: 2px;
background-color: var(--vscode-titleBar-activeForeground);
background-color: var(--vscode-activityBar-foreground);
}
.activity-bar-item svg,
@@ -551,6 +563,20 @@ button {
font-style: italic;
}
.tab-actions {
display: flex;
align-items: center;
gap: 2px;
margin-left: auto;
flex-shrink: 0;
}
.tab-dirty-indicator {
color: var(--vscode-editorWarning-foreground, #e2c08d);
font-size: 10px;
line-height: 1;
}
.tab-icon {
display: flex;
align-items: center;
@@ -567,28 +593,63 @@ button {
}
.tab-close {
margin-left: auto;
width: 18px;
height: 18px;
display: inline-flex;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
line-height: 1;
color: var(--vscode-descriptionForeground);
border-radius: 4px;
color: var(--vscode-icon-foreground, #c5c5c5);
border-radius: 3px;
cursor: pointer;
flex-shrink: 0;
border: none;
background: transparent;
padding: 0;
opacity: 0;
}
.tab:hover .tab-close {
opacity: 0.7;
}
.tab.active .tab-close {
opacity: 0.7;
}
.tab-close:hover {
opacity: 1 !important;
background-color: var(--vscode-toolbar-hoverBackground);
color: var(--vscode-tab-activeForeground);
}
.tab-close:active {
background-color: var(--vscode-toolbar-activeBackground, rgba(99, 102, 103, 0.31));
}
.tab.dirty .tab-dirty-indicator {
display: block;
}
.tab.dirty .tab-close {
display: none;
}
.tab.dirty:hover .tab-close {
display: flex;
opacity: 0.7;
}
.tab.dirty:hover .tab-dirty-indicator {
display: none;
}
.tab:focus-visible {
outline: 1px solid var(--vscode-focusBorder, #007fd4);
outline-offset: -1px;
}
.output-item-details {
margin: 4px 0 0;
padding: 8px;

View File

@@ -50,11 +50,52 @@ document.addEventListener("DOMContentLoaded", () => {
window.localStorage.setItem(key, String(width));
};
const syncTitlebarOverlayInsets = () => {
const rootStyle = document.documentElement.style;
const setInsets = (left, right) => {
rootStyle.setProperty("--bds-titlebar-overlay-left", `${left}px`);
rootStyle.setProperty("--bds-titlebar-overlay-right", `${right}px`);
};
const overlay = navigator.windowControlsOverlay;
if (!overlay) {
setInsets(0, 0);
return () => {};
}
const updateInsets = () => {
if (!overlay.visible) {
setInsets(0, 0);
return;
}
const titlebarRect = overlay.getTitlebarAreaRect();
const viewportWidth = window.innerWidth || document.documentElement.clientWidth || titlebarRect.right;
const leftInset = Math.max(0, Math.round(titlebarRect.left));
const rightInset = Math.max(0, Math.round(viewportWidth - titlebarRect.right));
setInsets(leftInset, rightInset);
};
const onGeometryChange = () => updateInsets();
const onResize = () => updateInsets();
updateInsets();
overlay.addEventListener("geometrychange", onGeometryChange);
window.addEventListener("resize", onResize);
return () => {
overlay.removeEventListener("geometrychange", onGeometryChange);
window.removeEventListener("resize", onResize);
};
};
const Hooks = {
AppShell: {
mounted() {
this.syncStoredLayout();
this.syncStoredUiLanguage();
this.destroyOverlaySync = syncTitlebarOverlayInsets();
this.handleMouseDown = (event) => {
const handle = event.target.closest("[data-role='resize-handle']");
@@ -126,6 +167,9 @@ document.addEventListener("DOMContentLoaded", () => {
this.el.removeEventListener("mousedown", this.handleMouseDown);
this.el.removeEventListener("change", this.handleChange);
window.removeEventListener("bds:native-menu-action", this.handleNativeMenuAction);
if (this.destroyOverlaySync) {
this.destroyOverlaySync();
}
},
syncStoredLayout() {

View File

@@ -126,4 +126,23 @@ defmodule BDS.UI.ShellTest do
assert css =~ ".status-bar-item.language-badge"
assert css =~ ".status-bar-item.offline-badge"
end
test "desktop shell assets keep old activity, tab, focus, and titlebar overlay parity rules" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
live_js = File.read!("/Users/gb/Projects/bDS2/priv/ui/live.js")
template = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/index.html.heex")
assert css =~ "color: var(--vscode-activityBar-foreground)"
assert css =~ ".tab-actions"
assert css =~ ".tab-dirty-indicator"
assert css =~ ".tab.dirty .tab-close"
assert css =~ ".tab:focus-visible"
assert css =~ ".window-titlebar-action-button:focus"
assert css =~ "padding-right: calc(10px + var(--bds-titlebar-overlay-right, 0px));"
assert live_js =~ "windowControlsOverlay"
assert live_js =~ "geometrychange"
assert live_js =~ "--bds-titlebar-overlay-left"
assert template =~ "tab-actions"
assert template =~ "tab-dirty-indicator"
end
end