feat: leftsidebar collapsible tool groups + scroll + resizable width
This commit is contained in:
@@ -149,6 +149,7 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
const [mobileRightOpen, setMobileRightOpen] = useState(false);
|
||||
// Desktop sidebar collapse
|
||||
const [leftSidebarCollapsed, setLeftSidebarCollapsed] = useState(false);
|
||||
const [leftbarWidth, setLeftbarWidth] = useState(200);
|
||||
const [rightSidebarCollapsed, setRightSidebarCollapsed] = useState(false);
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [activeDrawerTab, setActiveDrawerTab] = useState<DrawerTab>('tool');
|
||||
@@ -1553,6 +1554,8 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
onSaveGroupAsBlock={handleSaveGroupAsBlock}
|
||||
onGroup={() => handleRibbonAction('group')}
|
||||
onUngroup={() => handleRibbonAction('ungroup')}
|
||||
leftbarWidth={leftbarWidth}
|
||||
onWidthChange={setLeftbarWidth}
|
||||
/>
|
||||
<CanvasArea
|
||||
cursorPos={cursorPos}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import type { LeftSidebarProps } from '../types/ui.types';
|
||||
import { SEATING_TEMPLATES } from '../services/seatingService';
|
||||
|
||||
@@ -56,7 +56,53 @@ const sections: Array<{ label: string; tools: ToolDef[] }> = [
|
||||
{ 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 (
|
||||
<aside className={`leftbar${className ? ' ' + className : ''}${collapsed ? ' leftbar-collapsed' : ''}`} aria-label="Werkzeugpalette">
|
||||
<div className="leftbar-header">
|
||||
@@ -66,10 +112,12 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, sel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="leftbar-content">
|
||||
|
||||
{sections.map((section) => (
|
||||
<div className="tool-section" key={section.label}>
|
||||
<div className="tool-section-label">{section.label}</div>
|
||||
<div className="tool-grid">
|
||||
{renderSectionLabel(section.label, section.label)}
|
||||
<div className="tool-grid" style={collapsedSections[section.label] ? { display: 'none' } : undefined}>
|
||||
{section.tools.map((t) => (
|
||||
<button
|
||||
key={t.tool}
|
||||
@@ -89,8 +137,8 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, sel
|
||||
|
||||
{(onGroup || onUngroup) && (
|
||||
<div className="tool-section" key="grouping">
|
||||
<div className="tool-section-label">Gruppierung</div>
|
||||
<div className="tool-grid">
|
||||
{renderSectionLabel("Gruppierung", "grouping")}
|
||||
<div className="tool-grid" style={collapsedSections["grouping"] ? { display: "none" } : undefined}>
|
||||
{onGroup && (
|
||||
<button
|
||||
className="tool-btn"
|
||||
@@ -119,7 +167,7 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, sel
|
||||
|
||||
{activeTool === 'seating-template' && onTemplateSelect && (
|
||||
<div className="tool-section" key="templates">
|
||||
<div className="tool-section-label">Vorlagen</div>
|
||||
{renderSectionLabel("Vorlagen", "templates")}
|
||||
<select
|
||||
className="template-dropdown"
|
||||
value={selectedTemplate ?? ''}
|
||||
@@ -149,7 +197,9 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ activeTool, onToolChange, sel
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="leftbar-resize-handle" onMouseDown={handleResizeMouseDown} />
|
||||
|
||||
<div className="leftbar-footer">
|
||||
{onToggleCollapse && (
|
||||
|
||||
@@ -455,14 +455,38 @@ a:hover { text-decoration: underline; }
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
.leftbar-header {
|
||||
flex-shrink: 0;
|
||||
padding: var(--spacing-md) var(--spacing-md) var(--spacing-sm);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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 {
|
||||
font-size: var(--fs-xs);
|
||||
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); }
|
||||
|
||||
.leftbar-footer {
|
||||
flex-shrink: 0;
|
||||
margin-top: auto;
|
||||
padding: var(--spacing-sm);
|
||||
border-top: 1px solid var(--color-border);
|
||||
|
||||
@@ -110,6 +110,8 @@ export interface LeftSidebarProps {
|
||||
onSaveGroupAsBlock?: (name: string) => void;
|
||||
onGroup?: () => void;
|
||||
onUngroup?: () => void;
|
||||
leftbarWidth?: number;
|
||||
onWidthChange?: (width: number) => void;
|
||||
}
|
||||
|
||||
export interface CanvasAreaProps {
|
||||
|
||||
Reference in New Issue
Block a user