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);
|
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}
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user