feat: titlebar icon for sidebar toggle first try
This commit is contained in:
@@ -48,6 +48,7 @@ function createWindow(): void {
|
|||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
title: 'Blogging Desktop Server',
|
title: 'Blogging Desktop Server',
|
||||||
backgroundColor: '#1e1e1e', // VS Code dark background
|
backgroundColor: '#1e1e1e', // VS Code dark background
|
||||||
|
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { ActivityBar, Sidebar, Editor, StatusBar, Panel, TabBar, ToastContainer, showToast, ResizablePanel } from './components';
|
import { ActivityBar, Sidebar, Editor, StatusBar, Panel, TabBar, ToastContainer, showToast, ResizablePanel, WindowTitleBar } from './components';
|
||||||
import { useAppStore, PostData, MediaData, TaskProgress } from './store';
|
import { useAppStore, PostData, MediaData, TaskProgress } from './store';
|
||||||
import { loadTabsForProject, saveTabsForProject } from './utils';
|
import { loadTabsForProject, saveTabsForProject } from './utils';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
@@ -315,6 +315,7 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
|
<WindowTitleBar />
|
||||||
<div className="app-main">
|
<div className="app-main">
|
||||||
<ActivityBar />
|
<ActivityBar />
|
||||||
{sidebarVisible && (
|
{sidebarVisible && (
|
||||||
|
|||||||
@@ -45,29 +45,6 @@
|
|||||||
background-color: var(--vscode-toolbar-hoverBackground, rgba(90, 93, 94, 0.31));
|
background-color: var(--vscode-toolbar-hoverBackground, rgba(90, 93, 94, 0.31));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar toggle button */
|
|
||||||
.tab-bar-toggle-sidebar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 35px;
|
|
||||||
height: 100%;
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
border-right: 1px solid var(--vscode-tab-border, #252526);
|
|
||||||
color: var(--vscode-icon-foreground, #c5c5c5);
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-bar-toggle-sidebar:hover {
|
|
||||||
background-color: var(--vscode-toolbar-hoverBackground, rgba(90, 93, 94, 0.31));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-bar-toggle-sidebar:active {
|
|
||||||
background-color: var(--vscode-toolbar-activeBackground, rgba(99, 102, 103, 0.31));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-scroll-left {
|
.tab-scroll-left {
|
||||||
border-right: 1px solid var(--vscode-tab-border, #252526);
|
border-right: 1px solid var(--vscode-tab-border, #252526);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,6 @@ export const TabBar: React.FC = () => {
|
|||||||
media,
|
media,
|
||||||
activeProject,
|
activeProject,
|
||||||
dirtyPosts,
|
dirtyPosts,
|
||||||
sidebarVisible,
|
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
closeTab,
|
closeTab,
|
||||||
@@ -475,18 +474,6 @@ export const TabBar: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tab-bar">
|
<div className="tab-bar">
|
||||||
<button
|
|
||||||
className="tab-bar-toggle-sidebar"
|
|
||||||
onClick={toggleSidebar}
|
|
||||||
title={`${sidebarVisible ? 'Hide' : 'Show'} Sidebar (Ctrl+B)`}
|
|
||||||
>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
||||||
<path d="M0 1h16v14H0V1zm1 1v12h4V2H1zm5 0v12h9V2H6z"/>
|
|
||||||
{!sidebarVisible && <path d="M2 4h2v1H2V4zm0 2h2v1H2V6zm0 2h2v1H2V8z" opacity="0.5"/>}
|
|
||||||
{sidebarVisible && <path d="M2 4h2v1H2V4zm0 2h2v1H2V6zm0 2h2v1H2V8z"/>}
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{showLeftArrow && (
|
{showLeftArrow && (
|
||||||
<button
|
<button
|
||||||
className="tab-scroll-button tab-scroll-left"
|
className="tab-scroll-button tab-scroll-left"
|
||||||
|
|||||||
48
src/renderer/components/WindowTitleBar/WindowTitleBar.css
Normal file
48
src/renderer/components/WindowTitleBar/WindowTitleBar.css
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.window-titlebar {
|
||||||
|
height: 34px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: var(--vscode-editorGroupHeader-tabsBackground, #252526);
|
||||||
|
border-bottom: 1px solid var(--vscode-editorGroupHeader-tabsBorder, #1e1e1e);
|
||||||
|
flex-shrink: 0;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-titlebar-drag-region {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-titlebar-actions {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-titlebar-action-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--vscode-titleBar-activeForeground, var(--vscode-icon-foreground, #c5c5c5));
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-titlebar-action-button svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-titlebar-action-button:hover {
|
||||||
|
background-color: var(--vscode-toolbar-hoverBackground, rgba(90, 93, 94, 0.31));
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-titlebar-action-button:active {
|
||||||
|
background-color: var(--vscode-toolbar-activeBackground, rgba(99, 102, 103, 0.31));
|
||||||
|
}
|
||||||
33
src/renderer/components/WindowTitleBar/WindowTitleBar.tsx
Normal file
33
src/renderer/components/WindowTitleBar/WindowTitleBar.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useAppStore } from '../../store';
|
||||||
|
import './WindowTitleBar.css';
|
||||||
|
|
||||||
|
export const WindowTitleBar: React.FC = () => {
|
||||||
|
const { sidebarVisible, toggleSidebar } = useAppStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="window-titlebar" data-testid="window-titlebar">
|
||||||
|
<div className="window-titlebar-drag-region" />
|
||||||
|
<div className="window-titlebar-actions">
|
||||||
|
<button
|
||||||
|
className="window-titlebar-action-button"
|
||||||
|
aria-label="Toggle Sidebar"
|
||||||
|
onClick={toggleSidebar}
|
||||||
|
title={`${sidebarVisible ? 'Hide' : 'Show'} Sidebar (Ctrl+B)`}
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
|
||||||
|
<path
|
||||||
|
d="M3 3.75A1.75 1.75 0 0 1 4.75 2h6.5A1.75 1.75 0 0 1 13 3.75v8.5A1.75 1.75 0 0 1 11.25 14h-6.5A1.75 1.75 0 0 1 3 12.25v-8.5z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1"
|
||||||
|
/>
|
||||||
|
<path d="M4.5 3.5h3.75v9H4.5z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WindowTitleBar;
|
||||||
1
src/renderer/components/WindowTitleBar/index.ts
Normal file
1
src/renderer/components/WindowTitleBar/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { WindowTitleBar } from './WindowTitleBar';
|
||||||
@@ -22,3 +22,4 @@ export { AISuggestionsModal, type AISuggestions, type CurrentValues } from './AI
|
|||||||
export { ChatPanel } from './ChatPanel';
|
export { ChatPanel } from './ChatPanel';
|
||||||
export { ImportAnalysisView } from './ImportAnalysisView';
|
export { ImportAnalysisView } from './ImportAnalysisView';
|
||||||
export { InsertModal } from './InsertModal';
|
export { InsertModal } from './InsertModal';
|
||||||
|
export { WindowTitleBar } from './WindowTitleBar';
|
||||||
|
|||||||
38
tests/renderer/components/WindowTitleBar.test.tsx
Normal file
38
tests/renderer/components/WindowTitleBar.test.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { WindowTitleBar } from '../../../src/renderer/components/WindowTitleBar/WindowTitleBar';
|
||||||
|
import { useAppStore } from '../../../src/renderer/store';
|
||||||
|
|
||||||
|
describe('WindowTitleBar', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useAppStore.setState({
|
||||||
|
sidebarVisible: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a right-side sidebar toggle button and toggles store state', () => {
|
||||||
|
render(<WindowTitleBar />);
|
||||||
|
|
||||||
|
const toggleButton = screen.getByLabelText('Toggle Sidebar');
|
||||||
|
expect(toggleButton).toBeInTheDocument();
|
||||||
|
expect(toggleButton).toHaveAttribute('title', 'Hide Sidebar (Ctrl+B)');
|
||||||
|
|
||||||
|
fireEvent.click(toggleButton);
|
||||||
|
|
||||||
|
expect(useAppStore.getState().sidebarVisible).toBe(false);
|
||||||
|
expect(toggleButton).toHaveAttribute('title', 'Show Sidebar (Ctrl+B)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses a VS Code-like sidebar toggle icon shape', () => {
|
||||||
|
render(<WindowTitleBar />);
|
||||||
|
|
||||||
|
const toggleButton = screen.getByLabelText('Toggle Sidebar');
|
||||||
|
const svg = toggleButton.querySelector('svg');
|
||||||
|
const paths = svg?.querySelectorAll('path');
|
||||||
|
|
||||||
|
expect(svg).not.toBeNull();
|
||||||
|
expect(paths?.[0]?.getAttribute('d')).toBe('M3 3.75A1.75 1.75 0 0 1 4.75 2h6.5A1.75 1.75 0 0 1 13 3.75v8.5A1.75 1.75 0 0 1 11.25 14h-6.5A1.75 1.75 0 0 1 3 12.25v-8.5z');
|
||||||
|
expect(paths?.[1]?.getAttribute('d')).toBe('M4.5 3.5h3.75v9H4.5z');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user