feat: panel-toggle-button

This commit is contained in:
2026-02-17 11:41:35 +01:00
parent 70bc0b1b09
commit 4ac29a6528
3 changed files with 67 additions and 1 deletions

View File

@@ -181,6 +181,25 @@
background-color: currentColor; background-color: currentColor;
} }
.window-titlebar-panel-icon {
width: 14px;
height: 14px;
border: 1.5px solid currentColor;
border-radius: 2px;
display: block;
position: relative;
overflow: hidden;
}
.window-titlebar-panel-pane {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 33.3333%;
background-color: currentColor;
}
.window-titlebar-action-button:hover { .window-titlebar-action-button:hover {
background-color: var(--vscode-toolbar-hoverBackground, rgba(90, 93, 94, 0.31)); background-color: var(--vscode-toolbar-hoverBackground, rgba(90, 93, 94, 0.31));
} }

View File

@@ -11,7 +11,7 @@ type WindowControlsOverlayLike = {
}; };
export const WindowTitleBar: React.FC = () => { export const WindowTitleBar: React.FC = () => {
const { sidebarVisible, toggleSidebar } = useAppStore(); const { sidebarVisible, panelVisible, toggleSidebar, togglePanel } = useAppStore();
const [windowTitle, setWindowTitle] = useState<string>(document.title || 'Blogging Desktop Server'); const [windowTitle, setWindowTitle] = useState<string>(document.title || 'Blogging Desktop Server');
const [openMenu, setOpenMenu] = useState<{ label: string; left: number } | null>(null); const [openMenu, setOpenMenu] = useState<{ label: string; left: number } | null>(null);
const [showMnemonics, setShowMnemonics] = useState<boolean>(false); const [showMnemonics, setShowMnemonics] = useState<boolean>(false);
@@ -416,6 +416,16 @@ export const WindowTitleBar: React.FC = () => {
<span className="window-titlebar-sidebar-pane" data-shape="left-half" /> <span className="window-titlebar-sidebar-pane" data-shape="left-half" />
</span> </span>
</button> </button>
<button
className="window-titlebar-action-button"
aria-label="Toggle Panel"
onClick={togglePanel}
title={`${panelVisible ? 'Hide' : 'Show'} Panel (Ctrl+J)`}
>
<span className="window-titlebar-panel-icon" data-shape="frame-square" aria-hidden="true">
<span className="window-titlebar-panel-pane" data-shape="bottom-half" />
</span>
</button>
</div> </div>
{openMenu && activeMenu && ( {openMenu && activeMenu && (
<div <div

View File

@@ -8,6 +8,7 @@ describe('WindowTitleBar', () => {
beforeEach(() => { beforeEach(() => {
useAppStore.setState({ useAppStore.setState({
sidebarVisible: true, sidebarVisible: true,
panelVisible: false,
}); });
}); });
@@ -37,6 +38,42 @@ describe('WindowTitleBar', () => {
expect(iconPane).toHaveAttribute('data-shape', 'left-half'); expect(iconPane).toHaveAttribute('data-shape', 'left-half');
}); });
it('renders a right-side panel toggle button and toggles panel visibility', () => {
render(<WindowTitleBar />);
const toggleButton = screen.getByLabelText('Toggle Panel');
expect(toggleButton).toBeInTheDocument();
expect(toggleButton).toHaveAttribute('title', 'Show Panel (Ctrl+J)');
fireEvent.click(toggleButton);
expect(useAppStore.getState().panelVisible).toBe(true);
expect(toggleButton).toHaveAttribute('title', 'Hide Panel (Ctrl+J)');
});
it('uses a VS Code-like panel toggle icon shape', () => {
render(<WindowTitleBar />);
const toggleButton = screen.getByLabelText('Toggle Panel');
const iconFrame = toggleButton.querySelector('.window-titlebar-panel-icon');
const iconPane = toggleButton.querySelector('.window-titlebar-panel-pane');
expect(iconFrame).not.toBeNull();
expect(iconPane).not.toBeNull();
expect(iconFrame).toHaveAttribute('data-shape', 'frame-square');
expect(iconPane).toHaveAttribute('data-shape', 'bottom-half');
});
it('places panel toggle to the right of sidebar toggle', () => {
render(<WindowTitleBar />);
const actionButtons = Array.from(document.querySelectorAll('.window-titlebar-actions .window-titlebar-action-button'));
expect(actionButtons).toHaveLength(2);
expect(actionButtons[0]).toHaveAttribute('aria-label', 'Toggle Sidebar');
expect(actionButtons[1]).toHaveAttribute('aria-label', 'Toggle Panel');
});
it('updates overlay inset CSS variables when window controls geometry changes', () => { it('updates overlay inset CSS variables when window controls geometry changes', () => {
const geometryListeners = new Set<EventListener>(); const geometryListeners = new Set<EventListener>();
let rect = { let rect = {