feat: leftsidebar collapsible tool groups + scroll + resizable width

This commit is contained in:
A0 Orchestrator
2026-07-02 10:52:40 +02:00
parent 0463a793bc
commit 0bf9c61793
4 changed files with 87 additions and 7 deletions
+3
View File
@@ -149,6 +149,7 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
const [mobileRightOpen, setMobileRightOpen] = useState(false); const [mobileRightOpen, setMobileRightOpen] = useState(false);
// Desktop sidebar collapse // Desktop sidebar collapse
const [leftSidebarCollapsed, setLeftSidebarCollapsed] = useState(false); const [leftSidebarCollapsed, setLeftSidebarCollapsed] = useState(false);
const [leftbarWidth, setLeftbarWidth] = useState(200);
const [rightSidebarCollapsed, setRightSidebarCollapsed] = useState(false); const [rightSidebarCollapsed, setRightSidebarCollapsed] = useState(false);
const [settingsOpen, setSettingsOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false);
const [activeDrawerTab, setActiveDrawerTab] = useState<DrawerTab>('tool'); const [activeDrawerTab, setActiveDrawerTab] = useState<DrawerTab>('tool');
@@ -1553,6 +1554,8 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
onSaveGroupAsBlock={handleSaveGroupAsBlock} onSaveGroupAsBlock={handleSaveGroupAsBlock}
onGroup={() => handleRibbonAction('group')} onGroup={() => handleRibbonAction('group')}
onUngroup={() => handleRibbonAction('ungroup')} onUngroup={() => handleRibbonAction('ungroup')}
leftbarWidth={leftbarWidth}
onWidthChange={setLeftbarWidth}
/> />
<CanvasArea <CanvasArea
cursorPos={cursorPos} cursorPos={cursorPos}
+57 -7
View File
@@ -1,4 +1,4 @@
import React from 'react'; import React, { useState, useCallback } from 'react';
import type { LeftSidebarProps } from '../types/ui.types'; import type { LeftSidebarProps } from '../types/ui.types';
import { SEATING_TEMPLATES } from '../services/seatingService'; import { SEATING_TEMPLATES } from '../services/seatingService';
@@ -56,7 +56,53 @@ const sections: Array<{ label: string; tools: ToolDef[] }> = [
{ label: 'Bestuhlung', tools: sectionBestuhlung }, { label: 'Bestuhlung', tools: sectionBestuhlung },
]; ];
const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, selectedTemplate, onTemplateSelect, onCollapse, className, collapsed, onToggleCollapse, onGroup, onUngroup }) => { const MIN_LEFTBAR_WIDTH = 140;
const MAX_LEFTBAR_WIDTH = 400;
const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, selectedTemplate, onTemplateSelect, onCollapse, className, collapsed, onToggleCollapse, onGroup, onUngroup, leftbarWidth = 200, onWidthChange }) => {
const [collapsedSections, setCollapsedSections] = useState<Record<string, boolean>>({});
const toggleSection = useCallback((label: string) => {
setCollapsedSections((prev) => ({ ...prev, [label]: !prev[label] }));
}, []);
const handleResizeMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const startClientX = e.clientX;
const startWidth = leftbarWidth;
const handleMouseMove = (ev: MouseEvent) => {
const delta = ev.clientX - startClientX;
const newWidth = Math.min(MAX_LEFTBAR_WIDTH, Math.max(MIN_LEFTBAR_WIDTH, startWidth + delta));
document.documentElement.style.setProperty('--leftbar-w', newWidth + 'px');
if (onWidthChange) {
onWidthChange(newWidth);
}
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.body.style.cursor = '';
};
document.body.style.cursor = 'col-resize';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}, [leftbarWidth, onWidthChange]);
const renderSectionLabel = (label: string, sectionKey: string) => (
<div
className="tool-section-label"
onClick={() => toggleSection(sectionKey)}
style={{ cursor: 'pointer', userSelect: 'none', display: 'flex', alignItems: 'center', gap: '4px' }}
>
<span className="tool-section-chevron">{collapsedSections[sectionKey] ? '▶' : '▼'}</span>
{label}
</div>
);
return ( return (
<aside className={`leftbar${className ? ' ' + className : ''}${collapsed ? ' leftbar-collapsed' : ''}`} aria-label="Werkzeugpalette"> <aside className={`leftbar${className ? ' ' + className : ''}${collapsed ? ' leftbar-collapsed' : ''}`} aria-label="Werkzeugpalette">
<div className="leftbar-header"> <div className="leftbar-header">
@@ -66,10 +112,12 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, sel
</button> </button>
</div> </div>
<div className="leftbar-content">
{sections.map((section) => ( {sections.map((section) => (
<div className="tool-section" key={section.label}> <div className="tool-section" key={section.label}>
<div className="tool-section-label">{section.label}</div> {renderSectionLabel(section.label, section.label)}
<div className="tool-grid"> <div className="tool-grid" style={collapsedSections[section.label] ? { display: 'none' } : undefined}>
{section.tools.map((t) => ( {section.tools.map((t) => (
<button <button
key={t.tool} key={t.tool}
@@ -89,8 +137,8 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, sel
{(onGroup || onUngroup) && ( {(onGroup || onUngroup) && (
<div className="tool-section" key="grouping"> <div className="tool-section" key="grouping">
<div className="tool-section-label">Gruppierung</div> {renderSectionLabel("Gruppierung", "grouping")}
<div className="tool-grid"> <div className="tool-grid" style={collapsedSections["grouping"] ? { display: "none" } : undefined}>
{onGroup && ( {onGroup && (
<button <button
className="tool-btn" className="tool-btn"
@@ -119,7 +167,7 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, sel
{activeTool === 'seating-template' && onTemplateSelect && ( {activeTool === 'seating-template' && onTemplateSelect && (
<div className="tool-section" key="templates"> <div className="tool-section" key="templates">
<div className="tool-section-label">Vorlagen</div> {renderSectionLabel("Vorlagen", "templates")}
<select <select
className="template-dropdown" className="template-dropdown"
value={selectedTemplate ?? ''} value={selectedTemplate ?? ''}
@@ -149,7 +197,9 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, sel
)} )}
</div> </div>
)} )}
</div>
<div className="leftbar-resize-handle" onMouseDown={handleResizeMouseDown} />
<div className="leftbar-footer"> <div className="leftbar-footer">
{onToggleCollapse && ( {onToggleCollapse && (
+25
View File
@@ -455,14 +455,38 @@ a:hover { text-decoration: underline; }
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
min-width: 0; min-width: 0;
position: relative;
} }
.leftbar-header { .leftbar-header {
flex-shrink: 0;
padding: var(--spacing-md) var(--spacing-md) var(--spacing-sm); padding: var(--spacing-md) var(--spacing-md) var(--spacing-sm);
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
.leftbar-content {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.leftbar-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: 0;
width: 4px;
cursor: col-resize;
z-index: 10;
}
.leftbar-resize-handle:hover {
background: var(--color-primary);
}
.tool-section-chevron {
font-size: 8px;
display: inline-block;
flex-shrink: 0;
}
.leftbar-title { .leftbar-title {
font-size: var(--fs-xs); font-size: var(--fs-xs);
font-weight: 600; font-weight: 600;
@@ -543,6 +567,7 @@ a:hover { text-decoration: underline; }
.tool-btn.active .tool-btn-kbd { color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.15); } .tool-btn.active .tool-btn-kbd { color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.15); }
.leftbar-footer { .leftbar-footer {
flex-shrink: 0;
margin-top: auto; margin-top: auto;
padding: var(--spacing-sm); padding: var(--spacing-sm);
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
+2
View File
@@ -110,6 +110,8 @@ export interface LeftSidebarProps {
onSaveGroupAsBlock?: (name: string) => void; onSaveGroupAsBlock?: (name: string) => void;
onGroup?: () => void; onGroup?: () => void;
onUngroup?: () => void; onUngroup?: () => void;
leftbarWidth?: number;
onWidthChange?: (width: number) => void;
} }
export interface CanvasAreaProps { export interface CanvasAreaProps {