feat: sidebar layout cleaned up and aligned with old app

This commit is contained in:
2026-04-25 21:02:51 +02:00
parent ddc51da83b
commit e02e5eb6f6
3 changed files with 447 additions and 140 deletions

View File

@@ -1,4 +1,6 @@
:root { :root {
--accent-color: #007acc;
--accent-color-transparent: rgba(0, 122, 204, 0.25);
--vscode-editor-background: #1e1e1e; --vscode-editor-background: #1e1e1e;
--vscode-editor-foreground: #cccccc; --vscode-editor-foreground: #cccccc;
--vscode-sideBar-background: #252526; --vscode-sideBar-background: #252526;
@@ -31,10 +33,13 @@
--vscode-activityBarBadge-foreground: #ffffff; --vscode-activityBarBadge-foreground: #ffffff;
--vscode-testing-iconPassed: #73c991; --vscode-testing-iconPassed: #73c991;
--vscode-editorWarning-foreground: #cca700; --vscode-editorWarning-foreground: #cca700;
--vscode-input-foreground: #cccccc;
--vscode-input-placeholderForeground: #a6a6a6;
--vscode-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--vscode-font-size: 13px;
--sidebar-width: 280px; --sidebar-width: 280px;
--assistant-width: 360px; --assistant-width: 360px;
color-scheme: dark; color-scheme: dark;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
} }
* { * {
@@ -53,6 +58,8 @@ body {
body { body {
overflow: hidden; overflow: hidden;
user-select: none; user-select: none;
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
} }
button { button {
@@ -377,20 +384,17 @@ button {
background-color: var(--vscode-panel-border); background-color: var(--vscode-panel-border);
} }
.sidebar-header,
.assistant-header { .assistant-header {
padding: 10px 12px; padding: 10px 12px;
border-bottom: 1px solid var(--vscode-panel-border); border-bottom: 1px solid var(--vscode-panel-border);
} }
.sidebar-title-row,
.assistant-header { .assistant-header {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
} }
.sidebar-subtitle,
.assistant-card span, .assistant-card span,
.panel-entry span, .panel-entry span,
.editor-meta-row span, .editor-meta-row span,
@@ -436,7 +440,7 @@ button {
background: var(--vscode-list-hoverBackground); background: var(--vscode-list-hoverBackground);
} }
.sidebar-item.active { .sidebar-item.selected {
background: var(--vscode-list-activeSelectionBackground); background: var(--vscode-list-activeSelectionBackground);
color: var(--vscode-list-activeSelectionForeground); color: var(--vscode-list-activeSelectionForeground);
} }
@@ -1441,18 +1445,41 @@ button {
white-space: nowrap; white-space: nowrap;
} }
.sidebar-content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 0;
}
.sidebar-section {
margin-bottom: 4px;
padding-bottom: 0;
}
.sidebar-section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--vscode-sideBar-foreground);
}
.sidebar-section-title { .sidebar-section-title {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 6px;
padding: 0 12px 8px; padding: 4px 12px;
font-size: 12px; font-size: 12px;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
} }
.section-icon { .section-icon {
font-size: 11px; font-size: 8px;
line-height: 1;
} }
.section-icon.status-draft { .section-icon.status-draft {
@@ -1472,17 +1499,60 @@ button {
flex-direction: column; flex-direction: column;
} }
.sidebar-item {
width: 100%;
display: flex;
align-items: flex-start;
gap: 8px;
padding: 6px 12px 6px 12px;
border: none;
border-left: 2px solid transparent;
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
text-align: left;
}
.sidebar-item:hover {
background-color: var(--vscode-list-hoverBackground);
}
.sidebar-item.selected {
background-color: var(--vscode-list-activeSelectionBackground);
border-left-color: var(--vscode-focusBorder);
color: var(--vscode-list-activeSelectionForeground);
}
.sidebar-post-item { .sidebar-post-item {
flex-direction: row; flex-direction: row;
align-items: flex-start; }
gap: 10px;
.sidebar-item.post-type-picture {
background: linear-gradient(90deg, rgba(139, 92, 246, 0.05) 0%, transparent 100%);
}
.sidebar-item.post-type-aside {
background: linear-gradient(90deg, rgba(245, 158, 11, 0.05) 0%, transparent 100%);
}
.sidebar-item.post-type-quote {
background: linear-gradient(90deg, rgba(34, 197, 94, 0.05) 0%, transparent 100%);
}
.sidebar-item.post-type-link {
background: linear-gradient(90deg, rgba(59, 130, 246, 0.05) 0%, transparent 100%);
}
.sidebar-item.post-type-video {
background: linear-gradient(90deg, rgba(239, 68, 68, 0.05) 0%, transparent 100%);
} }
.post-type-icon { .post-type-icon {
width: 18px; font-size: 14px;
flex: 0 0 18px; line-height: 1.4;
text-align: center; flex-shrink: 0;
line-height: 1.2; opacity: 0.85;
} }
.sidebar-item-content { .sidebar-item-content {
@@ -1490,118 +1560,154 @@ button {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
flex-direction: column; flex-direction: column;
gap: 2px;
} }
.sidebar-item-title-row { .sidebar-item-title-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
min-width: 0;
} }
.sidebar-item-title { .sidebar-item-title {
min-width: 0; font-size: 13px;
color: var(--vscode-sideBar-foreground);
white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap;
} }
.sidebar-item-language-badge { .sidebar-item-language-badge {
display: inline-flex; flex-shrink: 0;
align-items: center;
justify-content: center;
min-width: 18px; min-width: 18px;
padding: 0 5px; padding: 1px 5px;
border-radius: 999px; border-radius: 999px;
background: rgba(79, 179, 255, 0.14); background: color-mix(in srgb, var(--vscode-badge-background) 82%, transparent);
color: var(--vscode-titleBar-activeForeground); color: var(--vscode-badge-foreground);
font-size: 11px; font-size: 10px;
font-weight: 700;
text-align: center;
} }
.sidebar-item-meta { .sidebar-item-meta {
font-size: 11px;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
font-size: 12px; margin-top: 2px;
} }
.media-grid { .media-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: 1fr;
gap: 10px; gap: 2px;
padding: 0 12px 12px; padding: 4px;
} }
.media-item { .media-item {
display: flex; display: flex;
flex-direction: column; align-items: center;
gap: 8px; gap: 8px;
width: 100%; width: 100%;
padding: 12px; padding: 6px 8px;
border: none; border: none;
border-radius: 10px; border-radius: 4px;
background: transparent; background: transparent;
color: inherit; color: inherit;
text-align: left; text-align: left;
} }
.media-item:hover { .media-item:hover {
background: var(--vscode-list-hoverBackground); background-color: var(--vscode-list-hoverBackground);
} }
.media-item.selected { .media-item.selected {
background: var(--vscode-list-activeSelectionBackground); background-color: var(--vscode-list-activeSelectionBackground);
color: var(--vscode-list-activeSelectionForeground); color: var(--vscode-list-activeSelectionForeground);
} }
.media-thumbnail { .media-thumbnail {
width: 40px;
height: 40px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 72px; background-color: var(--vscode-input-background);
border-radius: 8px; border-radius: 4px;
background: var(--vscode-input-background); flex-shrink: 0;
font-size: 28px; overflow: hidden;
font-size: 20px;
}
.media-item-info {
flex: 1;
min-width: 0;
}
.media-item-name {
font-size: 12px;
color: var(--vscode-sideBar-foreground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.media-item-size {
font-size: 10px;
color: var(--vscode-descriptionForeground);
} }
.sidebar-actions { .sidebar-actions {
display: flex; display: flex;
align-items: center; gap: 4px;
gap: 6px;
} }
.sidebar-action { .sidebar-action {
display: inline-flex; background: transparent;
border: none;
padding: 2px;
color: var(--vscode-sideBar-foreground);
cursor: pointer;
opacity: 0.7;
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 28px; border-radius: 3px;
height: 28px; }
border: none;
border-radius: 6px; .sidebar-action:hover {
background: transparent; opacity: 1;
color: var(--vscode-descriptionForeground); background-color: var(--vscode-list-hoverBackground);
cursor: pointer;
} }
.sidebar-action:hover,
.sidebar-action.active { .sidebar-action.active {
background: var(--vscode-toolbar-hoverBackground); background-color: var(--vscode-list-activeSelectionBackground);
color: var(--vscode-foreground); opacity: 1;
} }
.search-box { .search-box {
display: grid; display: flex;
grid-template-columns: minmax(0, 1fr) auto auto; align-items: center;
gap: 6px; gap: 4px;
padding: 10px 12px 0; padding: 4px 12px 8px;
position: relative;
} }
.search-box input { .search-box input {
flex: 1;
min-width: 0; min-width: 0;
padding: 6px 28px 6px 8px;
font-size: 12px;
background-color: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border); border: 1px solid var(--vscode-input-border);
border-radius: 6px; color: var(--vscode-input-foreground);
background: var(--vscode-input-background); border-radius: 3px;
color: var(--vscode-foreground); }
padding: 7px 10px;
.search-box input::placeholder {
color: var(--vscode-input-placeholderForeground);
}
.search-box input:focus {
outline: none;
border-color: var(--vscode-focusBorder);
} }
.search-box button, .search-box button,
@@ -1616,97 +1722,277 @@ button {
cursor: pointer; cursor: pointer;
} }
.search-box button, .search-box button[type="submit"] {
.clear-search { position: absolute;
display: inline-flex; right: 40px;
background: transparent;
border: none;
padding: 4px;
color: var(--vscode-descriptionForeground);
cursor: pointer;
opacity: 0.7;
}
.search-box button[type="submit"]:hover {
opacity: 1;
background: transparent;
}
.search-box .clear-search {
position: absolute;
right: 16px;
background: transparent;
border: none;
padding: 4px;
color: var(--vscode-descriptionForeground);
cursor: pointer;
font-size: 10px;
opacity: 0.7;
}
.search-box .clear-search:hover {
opacity: 1;
background: transparent;
}
.calendar-view {
padding: 8px 12px;
border-bottom: 1px solid var(--vscode-sideBar-border);
}
.calendar-header {
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: space-between;
min-width: 30px; font-size: 11px;
height: 30px; font-weight: 600;
padding: 0 8px; text-transform: uppercase;
border-radius: 6px; letter-spacing: 0.5px;
background: var(--vscode-input-background); color: var(--vscode-descriptionForeground);
color: var(--vscode-foreground); margin-bottom: 8px;
}
.search-box button:hover,
.clear-search:hover,
.clear-filter:hover,
.load-more-button:hover,
.calendar-year-header:hover,
.calendar-month:hover,
.filter-header:hover,
.filter-chip:hover {
background: var(--vscode-list-hoverBackground);
}
.calendar-view,
.filter-panel,
.filter-status,
.sidebar-load-more {
padding: 10px 12px 0;
} }
.collapsible-header { .collapsible-header {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px;
padding: 6px 0;
background: transparent;
color: var(--vscode-foreground);
text-align: left; text-align: left;
} }
.calendar-header.collapsible-header,
.filter-header.collapsible-header {
cursor: pointer;
padding: 4px 6px;
margin: 0 -6px 8px -6px;
border-radius: 3px;
user-select: none;
background: transparent;
}
.calendar-header.collapsible-header:hover,
.filter-header.collapsible-header:hover {
background-color: var(--vscode-list-hoverBackground);
}
.calendar-header.collapsible-header.collapsed,
.filter-header.collapsible-header.collapsed {
margin-bottom: 0;
}
.collapse-icon { .collapse-icon {
width: 12px; font-size: 9px;
margin-right: 4px;
opacity: 0.7;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
} }
.calendar-years, .calendar-header .clear-filter,
.calendar-months, .filter-header .clear-filter {
.filter-chips { background: transparent;
display: flex; border: none;
flex-direction: column; color: var(--vscode-descriptionForeground);
gap: 6px; cursor: pointer;
padding-top: 6px; font-size: 10px;
padding: 2px 4px;
opacity: 0.7;
margin-left: auto;
} }
.calendar-year-header, .calendar-header .clear-filter:hover,
.calendar-month { .filter-header .clear-filter:hover {
width: 100%; opacity: 1;
}
.calendar-years {
display: flex;
flex-direction: column;
gap: 2px;
}
.calendar-year-header {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 6px;
padding: 6px 8px; padding: 4px 6px;
border-radius: 6px; cursor: pointer;
border-radius: 3px;
font-size: 12px;
color: var(--vscode-sideBar-foreground);
background: transparent; background: transparent;
color: var(--vscode-foreground);
text-align: left; text-align: left;
} }
.calendar-year-header:hover,
.calendar-month:hover {
background-color: var(--vscode-list-hoverBackground);
}
.calendar-year-header.selected, .calendar-year-header.selected,
.calendar-month.selected, .calendar-month.selected {
.filter-chip.active { background-color: var(--vscode-list-activeSelectionBackground);
background: var(--vscode-list-activeSelectionBackground);
color: var(--vscode-list-activeSelectionForeground);
} }
.year-count, .calendar-year-header .expand-icon {
.month-count, font-size: 8px;
.sidebar-section-count {
margin-left: auto;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
font-size: 11px; width: 10px;
} }
.month-label, .year-label,
.year-label { .month-label {
flex: 1; flex: 1;
} }
.calendar-year-header .year-count {
font-size: 10px;
color: var(--vscode-descriptionForeground);
background-color: var(--vscode-badge-background);
padding: 1px 6px;
border-radius: 8px;
}
.calendar-months { .calendar-months {
padding-left: 18px; display: flex;
flex-direction: column;
gap: 1px;
padding-left: 16px;
margin-top: 2px;
}
.calendar-month {
display: flex;
align-items: center;
justify-content: space-between;
padding: 3px 6px;
cursor: pointer;
border-radius: 3px;
font-size: 12px;
color: var(--vscode-sideBar-foreground);
background: transparent;
text-align: left;
}
.month-count,
.sidebar-section-count {
font-size: 10px;
color: var(--vscode-descriptionForeground);
}
.filter-panel {
padding: 8px 12px;
border-bottom: 1px solid var(--vscode-sideBar-border);
}
.filter-section {
margin-bottom: 12px;
}
.filter-section:last-child {
margin-bottom: 0;
}
.filter-header {
display: flex;
align-items: center;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--vscode-descriptionForeground);
margin-bottom: 6px;
}
.filter-chips {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.filter-chip {
background-color: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: none;
padding: 2px 8px;
font-size: 11px;
border-radius: 12px;
cursor: pointer;
transition: background-color 0.15s, opacity 0.15s;
}
.filter-chip:hover {
background-color: var(--vscode-button-secondaryHoverBackground);
}
.filter-chip.active {
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
.filter-status {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 12px;
font-size: 11px;
color: var(--vscode-descriptionForeground);
background-color: var(--vscode-list-hoverBackground);
border-bottom: 1px solid var(--vscode-sideBar-border);
}
.filter-status button {
background: transparent;
border: none;
color: var(--accent-color);
cursor: pointer;
font-size: 11px;
padding: 0;
}
.filter-status button:hover {
text-decoration: underline;
background: transparent;
}
.sidebar-load-more {
padding: 12px 16px;
display: flex;
justify-content: center;
}
.load-more-button {
width: 100%;
padding: 8px 16px;
background-color: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: background-color 0.2s;
}
.load-more-button:hover:not(:disabled) {
background-color: var(--vscode-button-secondaryHoverBackground);
} }
.filter-section { .filter-section {

View File

@@ -141,28 +141,35 @@ function renderSidebar() {
const filterState = currentSidebarFilterState(view.id); const filterState = currentSidebarFilterState(view.id);
root.querySelector(".sidebar").innerHTML = ` root.querySelector(".sidebar").innerHTML = `
<div class="sidebar-header">
<div class="sidebar-title-row">
<strong>${escapeHtml(tText(data.title))}</strong>
<span class="sidebar-subtitle">${escapeHtml(tText(data.subtitle))}</span>
</div>
${data.filters?.enabled ? `
<div class="sidebar-actions">
<button class="sidebar-action ${filterState.showFilters ? "active" : ""}" data-sidebar-toggle-filters="${escapeHtmlAttribute(view.id)}" type="button" title="${escapeHtmlAttribute(t(data.filters.toggle_filters_label))}">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M6 12v-1h4v1H6zM4 8v-1h8v1H4zm-2-4v-1h12v1H2z"></path>
</svg>
</button>
</div>
` : ""}
</div>
${renderSidebarSearchBox(data, view, filterState)}
${renderSidebarFilterPanel(data, view, filterState)}
${renderSidebarFilterStatus(data, view, filterState)}
<div class="sidebar-content sidebar-body"> <div class="sidebar-content sidebar-body">
${renderSidebarViewHeader(data, view, filterState)}
${renderSidebarSearchBox(data, view, filterState)}
${renderSidebarFilterPanel(data, view, filterState)}
${renderSidebarFilterStatus(data, view, filterState)}
${renderSidebarBody(data, view)} ${renderSidebarBody(data, view)}
${renderSidebarLoadMore(data, view)}
</div>
`;
}
function renderSidebarViewHeader(data, view, filterState) {
const label = String(tText(view.label || data.title || "")).toUpperCase();
return `
<div class="sidebar-section">
<div class="sidebar-section-header">
<span>${escapeHtml(label)}</span>
${data.filters?.enabled ? `
<div class="sidebar-actions">
<button class="sidebar-action ${filterState.showFilters ? "active" : ""}" data-sidebar-toggle-filters="${escapeHtmlAttribute(view.id)}" type="button" title="${escapeHtmlAttribute(t(data.filters.toggle_filters_label))}">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M6 12v-1h4v1H6zM4 8v-1h8v1H4zm-2-4v-1h12v1H2z"></path>
</svg>
</button>
</div>
` : ""}
</div>
</div> </div>
${renderSidebarLoadMore(data, view)}
`; `;
} }
@@ -395,7 +402,7 @@ function renderSidebarPostItem(item, view) {
return ` return `
<button <button
class="sidebar-item sidebar-post-item post-type-${escapeHtmlAttribute(postType.type)} ${active ? "active" : ""}" class="sidebar-item sidebar-post-item post-type-${escapeHtmlAttribute(postType.type)} ${active ? "selected" : ""}"
data-open-tab="${escapeHtmlAttribute(tabId)}" data-open-tab="${escapeHtmlAttribute(tabId)}"
data-open-route="${escapeHtmlAttribute(itemRoute)}" data-open-route="${escapeHtmlAttribute(itemRoute)}"
data-open-title="${escapeHtmlAttribute(item.title || routeLabel(itemRoute))}" data-open-title="${escapeHtmlAttribute(item.title || routeLabel(itemRoute))}"
@@ -458,7 +465,7 @@ function renderSidebarEntityList(data, view) {
return renderSidebarEmpty(data.empty_message || "No items"); return renderSidebarEmpty(data.empty_message || "No items");
} }
return items.map((item) => renderSidebarEntityItem(item, view)).join(""); return `<div class="settings-nav-list">${items.map((item) => renderSidebarEntityItem(item, view)).join("")}</div>`;
} }
function renderSidebarEntityItem(item, view) { function renderSidebarEntityItem(item, view) {

View File

@@ -216,6 +216,20 @@ defmodule BDS.UI.ShellTest do
refute js =~ "defaultSidebarFilterState(viewId, state.sidebarContent[viewId])" refute js =~ "defaultSidebarFilterState(viewId, state.sidebarContent[viewId])"
end end
test "sidebar bundle follows the old app header and styling model instead of a subtitle header" do
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
assert js =~ "sidebar-section-header"
refute js =~ "sidebar-subtitle"
assert css =~ "--accent-color: #007acc"
assert css =~ "--vscode-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI'"
assert css =~ "border-left-color: var(--vscode-focusBorder);"
assert css =~ ".sidebar-section-header"
refute css =~ ".sidebar-subtitle"
end
test "static shell bundle exists for direct browser inspection" do test "static shell bundle exists for direct browser inspection" do
assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/index.html") assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/index.html")
assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/app.css") assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/app.css")