Migrate from tailwind-sidebar to syncfusion sidebar component
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Link, Outlet } from 'react-router-dom';
|
||||
import { SidebarComponent } from '@syncfusion/ej2-react-navigations';
|
||||
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
|
||||
import { TooltipComponent } from '@syncfusion/ej2-react-popups';
|
||||
import logo from './assets/logo.png';
|
||||
import './App.css';
|
||||
|
||||
@@ -49,8 +52,9 @@ import Logout from './logout';
|
||||
// const ENV = import.meta.env.VITE_ENV || 'development';
|
||||
|
||||
const Layout: React.FC = () => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [version, setVersion] = useState('');
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
let sidebarRef: SidebarComponent | null;
|
||||
|
||||
React.useEffect(() => {
|
||||
fetch('/program-info.json')
|
||||
@@ -59,84 +63,236 @@ const Layout: React.FC = () => {
|
||||
.catch(err => console.error('Failed to load version info:', err));
|
||||
}, []);
|
||||
|
||||
const toggleSidebar = () => {
|
||||
if (sidebarRef) {
|
||||
sidebarRef.toggle();
|
||||
}
|
||||
};
|
||||
|
||||
const onSidebarChange = () => {
|
||||
// Syncfusion unterscheidet zwischen isOpen (true/false) und dem Dock-Modus
|
||||
// Im Dock-Modus ist isOpen=true, aber die Sidebar ist kollabiert
|
||||
const sidebar = sidebarRef?.element;
|
||||
if (sidebar) {
|
||||
const currentWidth = sidebar.style.width;
|
||||
const newCollapsedState = currentWidth === '60px' || currentWidth.includes('60');
|
||||
|
||||
setIsCollapsed(newCollapsedState);
|
||||
}
|
||||
};
|
||||
|
||||
const sidebarTemplate = () => (
|
||||
<div
|
||||
className={`sidebar-theme ${isCollapsed ? 'collapsed' : 'expanded'}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
minHeight: '100vh',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
borderColor: 'var(--sidebar-border)',
|
||||
height: '68px',
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderBottom: '1px solid var(--sidebar-border)',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
style={{
|
||||
height: '64px',
|
||||
maxHeight: '60px',
|
||||
display: 'block',
|
||||
margin: '0 auto',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<nav
|
||||
style={{
|
||||
flex: '1 1 auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginTop: '1rem',
|
||||
overflowY: 'auto',
|
||||
minHeight: 0, // Wichtig für Flex-Shrinking
|
||||
}}
|
||||
>
|
||||
{sidebarItems.map(item => {
|
||||
const Icon = item.icon;
|
||||
const linkContent = (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className="sidebar-link no-underline w-full"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
gap: '8px',
|
||||
padding: '12px 24px',
|
||||
transition: 'background 0.2s, color 0.2s, justify-content 0.3s',
|
||||
textDecoration: 'none',
|
||||
color: 'var(--sidebar-fg)',
|
||||
backgroundColor: 'var(--sidebar-bg)',
|
||||
}}
|
||||
>
|
||||
<Icon size={22} style={{ flexShrink: 0, marginRight: 0 }} />
|
||||
<span className="sidebar-text" style={{ marginLeft: 0, transition: 'opacity 0.3s' }}>
|
||||
{item.name}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
|
||||
// Syncfusion Tooltip nur im collapsed state
|
||||
return isCollapsed ? (
|
||||
<TooltipComponent
|
||||
key={item.path}
|
||||
content={item.name}
|
||||
position="RightCenter"
|
||||
opensOn="Hover"
|
||||
showTipPointer={true}
|
||||
animation={{
|
||||
open: { effect: 'FadeIn', duration: 200 },
|
||||
close: { effect: 'FadeOut', duration: 200 },
|
||||
}}
|
||||
>
|
||||
{linkContent}
|
||||
</TooltipComponent>
|
||||
) : (
|
||||
linkContent
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
marginTop: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: 'auto',
|
||||
}}
|
||||
>
|
||||
{(() => {
|
||||
const logoutContent = (
|
||||
<Link
|
||||
to="/logout"
|
||||
className="sidebar-logout no-underline w-full"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
gap: '8px',
|
||||
padding: '12px 24px',
|
||||
transition: 'background 0.2s, color 0.2s, justify-content 0.3s',
|
||||
textDecoration: 'none',
|
||||
color: 'var(--sidebar-fg)',
|
||||
backgroundColor: 'var(--sidebar-bg)',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: '1.15rem',
|
||||
}}
|
||||
>
|
||||
<LogOut size={22} style={{ flexShrink: 0, marginRight: 0 }} />
|
||||
<span className="sidebar-text" style={{ marginLeft: 0, transition: 'opacity 0.3s' }}>
|
||||
Abmelden
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
|
||||
// Syncfusion Tooltip nur im collapsed state
|
||||
return isCollapsed ? (
|
||||
<TooltipComponent
|
||||
content="Abmelden"
|
||||
position="RightCenter"
|
||||
opensOn="Hover"
|
||||
showTipPointer={true}
|
||||
animation={{
|
||||
open: { effect: 'FadeIn', duration: 200 },
|
||||
close: { effect: 'FadeOut', duration: 200 },
|
||||
}}
|
||||
>
|
||||
{logoutContent}
|
||||
</TooltipComponent>
|
||||
) : (
|
||||
logoutContent
|
||||
);
|
||||
})()}
|
||||
{version && (
|
||||
<div
|
||||
className="version-info px-6 py-2 text-xs text-center opacity-70 border-t"
|
||||
style={{ borderColor: 'var(--sidebar-border)' }}
|
||||
>
|
||||
Version {version}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="layout-container">
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`sidebar-theme flex flex-col transition-all duration-300 ${collapsed ? 'w-20' : 'w-64'}`}
|
||||
<SidebarComponent
|
||||
id="sidebar"
|
||||
ref={(sidebar: SidebarComponent | null) => {
|
||||
sidebarRef = sidebar;
|
||||
}}
|
||||
width="256px"
|
||||
target=".layout-container"
|
||||
isOpen={true}
|
||||
closeOnDocumentClick={false}
|
||||
enableGestures={false}
|
||||
type="Auto"
|
||||
enableDock={true}
|
||||
dockSize="60px"
|
||||
change={onSidebarChange}
|
||||
>
|
||||
<div
|
||||
className="h-20 flex items-center justify-center border-b"
|
||||
style={{ borderColor: 'var(--sidebar-border)' }}
|
||||
>
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
className="h-12"
|
||||
style={{ display: collapsed ? 'none' : 'block' }}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="sidebar-btn p-2 focus:outline-none transition-colors"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
aria-label={collapsed ? 'Sidebar ausklappen' : 'Sidebar einklappen'}
|
||||
type="button"
|
||||
>
|
||||
<span style={{ fontSize: 20 }}>{collapsed ? '▶' : '◀'}</span>
|
||||
</button>
|
||||
<nav className="flex-1 mt-4">
|
||||
{sidebarItems.map(item => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className="sidebar-link flex items-center gap-3 px-6 py-3 transition-colors no-underline"
|
||||
title={collapsed ? item.name : undefined}
|
||||
>
|
||||
<Icon size={22} />
|
||||
{!collapsed && item.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
{/* Abmelden-Button und Version immer ganz unten */}
|
||||
<div className="mt-auto mb-2">
|
||||
<Link
|
||||
to="/logout"
|
||||
className="sidebar-logout flex items-center gap-3 px-6 py-3 w-full transition-colors no-underline"
|
||||
title={collapsed ? 'Abmelden' : undefined}
|
||||
>
|
||||
<LogOut size={22} />
|
||||
{!collapsed && 'Abmelden'}
|
||||
</Link>
|
||||
{!collapsed && version && (
|
||||
<div className="px-6 pt-2 text-xs text-center opacity-70">Version {version}</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
{/* Main Content */}
|
||||
<div className={`content-area ${collapsed ? 'collapsed' : ''}`}>
|
||||
{/* Header */}
|
||||
{sidebarTemplate()}
|
||||
</SidebarComponent>
|
||||
|
||||
<div className="content-area">
|
||||
<header
|
||||
className="content-header flex items-center px-8 shadow"
|
||||
className="content-header flex items-center shadow"
|
||||
style={{
|
||||
backgroundColor: '#e5d8c7',
|
||||
color: '#78591c',
|
||||
height: 'calc(48px + 20px)',
|
||||
height: '68px', // Exakt gleiche Höhe wie Sidebar-Header
|
||||
fontSize: '1.15rem',
|
||||
fontFamily:
|
||||
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif',
|
||||
margin: 0,
|
||||
padding: '0 2rem 0 0', // Nur rechts Padding, links kein Padding
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
className="h-12 mr-4"
|
||||
style={{ marginTop: 10, marginBottom: 10 }}
|
||||
<ButtonComponent
|
||||
cssClass="e-inherit"
|
||||
iconCss="e-icons e-menu"
|
||||
onClick={toggleSidebar}
|
||||
isToggle={true}
|
||||
style={{
|
||||
margin: '0 1rem 0 0', // Nur rechts Margin für Abstand zum Logo
|
||||
padding: '8px 12px',
|
||||
minWidth: '44px',
|
||||
height: '44px',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<span className="text-2xl font-bold mr-8">Infoscreen-Management</span>
|
||||
<span className="ml-auto" style={{ color: '#78591c' }}>
|
||||
<img src={logo} alt="Logo" className="h-16 mr-4" style={{ maxHeight: '60px' }} />
|
||||
<span className="text-2xl font-bold mr-8" style={{ color: '#78591c' }}>
|
||||
Infoscreen-Management
|
||||
</span>
|
||||
<span className="ml-auto text-lg font-medium" style={{ color: '#78591c' }}>
|
||||
[Organisationsname]
|
||||
</span>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user