sidebar working as expected
modalbox working as expected
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import sys
|
||||
sys.path.append('/workspace')
|
||||
|
||||
from dash import Dash, html, dcc, page_container
|
||||
from dash import Dash, html, dcc, page_container, Output, Input, State, callback
|
||||
from flask import Flask
|
||||
import dash_bootstrap_components as dbc
|
||||
import dash_mantine_components as dmc
|
||||
@@ -29,11 +29,13 @@ app = Dash(
|
||||
app.layout = dmc.MantineProvider([
|
||||
Header(),
|
||||
html.Div([
|
||||
html.Div(id="sidebar", className="sidebar"),
|
||||
html.Div(id="sidebar"), # KEINE className="sidebar" hier!
|
||||
html.Div(page_container, className="page-content"),
|
||||
dcc.Store(id="sidebar-state", data={"collapsed": False}),
|
||||
], style={"display": "flex"}),
|
||||
])
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8050, debug=(ENV=="development"))
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
/* ==========================
|
||||
Allgemeines Layout
|
||||
========================== */
|
||||
:root {
|
||||
--mb-z-index: 2000 !important;
|
||||
--mantine-z-index-popover: 2100 !important;
|
||||
--mantine-z-index-overlay: 2999 !important;
|
||||
--mantine-z-index-dropdown: 2100 !important;
|
||||
--mantine-z-index-max: 9999 !important;
|
||||
--mantine-z-index-modal: 3000 !important;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
@@ -15,6 +23,7 @@ body {
|
||||
min-width: 0; /* verhindert Überlauf bei zu breitem Inhalt */
|
||||
transition: margin-left 0.3s ease;
|
||||
min-height: calc(100vh - 60px); /* Mindesthöhe minus Header-Höhe */
|
||||
margin-left: 220px; /* <--- Ergänzen */
|
||||
}
|
||||
|
||||
/* Wenn Sidebar collapsed ist, reduziere margin-left */
|
||||
@@ -73,7 +82,7 @@ body {
|
||||
top: 60px; /* Den gleichen Wert wie Header-Höhe verwenden */
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
position: relative; /* oder fixed, je nach Layout */
|
||||
position: fixed; /* <--- Ändere das von relative zu fixed */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -164,10 +173,10 @@ body {
|
||||
/* ==========================
|
||||
Responsive (bei Bedarf)
|
||||
========================== */
|
||||
@media (max-width: 768px) {
|
||||
/* @media (max-width: 768px) {
|
||||
body {
|
||||
padding-top: 60px; /* Header-Platz auch auf mobilen Geräten */
|
||||
}
|
||||
/* }
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
@@ -186,34 +195,36 @@ body {
|
||||
.sidebar.collapsed ~ .page-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* :root {
|
||||
--mb-z-index: 2000 !important;
|
||||
--mantine-z-index-popover: 2100 !important;
|
||||
--mantine-z-index-overlay: 2100 !important;
|
||||
--mantine-z-index-dropdown: 2100 !important;
|
||||
--mantine-z-index-max: 9999 !important;
|
||||
}
|
||||
|
||||
.mantine-Modal-root,
|
||||
.mantine-Modal-modal,
|
||||
.mantine-Modal-overlay {
|
||||
z-index: 3000 !important;
|
||||
}
|
||||
|
||||
.mantine-Modal-overlay {
|
||||
z-index: 2000 !important;
|
||||
}
|
||||
|
||||
.mantine-Modal-modal {
|
||||
z-index: 2100 !important;
|
||||
}
|
||||
|
||||
.mantine-Popover-dropdown,
|
||||
.mantine-DatePicker-dropdown,
|
||||
.mantine-Select-dropdown {
|
||||
z-index: 2200 !important;
|
||||
} */
|
||||
|
||||
.mantine-Modal-modal {
|
||||
z-index: var(--mantine-z-index-modal, 3000) !important;
|
||||
}
|
||||
|
||||
.mantine-Modal-inner,
|
||||
.mantine-Modal-content {
|
||||
z-index: 4000 !important;
|
||||
}
|
||||
|
||||
/* Sidebar collapsed: Icon-Farbe normal */
|
||||
.sidebar.collapsed .sidebar-item-collapsed svg {
|
||||
color: #7c5617; /* Icon-Linie/Text */
|
||||
fill: #e4d5c1; /* Icon-Fläche */
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
transition: color 0.2s, fill 0.2s;
|
||||
}
|
||||
|
||||
/* Sidebar collapsed: Hintergrund und Icon invertieren bei Hover/Active */
|
||||
.sidebar.collapsed .nav-link:hover,
|
||||
.sidebar.collapsed .nav-link.active {
|
||||
background-color: #7c5617 !important;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .nav-link:hover svg,
|
||||
.sidebar.collapsed .nav-link.active svg {
|
||||
color: #e4d5c1; /* Icon-Linie/Text invertiert */
|
||||
fill: #7c5617; /* Icon-Fläche invertiert */
|
||||
}
|
||||
|
||||
@@ -3,29 +3,24 @@
|
||||
from dash import Input, Output, State, callback
|
||||
from components.sidebar import Sidebar
|
||||
|
||||
# 1) Toggle-Callback: Umschalten von collapsed = False ↔ True
|
||||
@callback(
|
||||
Output("sidebar", "children"),
|
||||
Output("sidebar", "className"),
|
||||
Input("sidebar-state", "data"),
|
||||
)
|
||||
def render_sidebar(data):
|
||||
collapsed = data.get("collapsed", False)
|
||||
return Sidebar(collapsed=collapsed), f"sidebar{' collapsed' if collapsed else ''}"
|
||||
|
||||
@callback(
|
||||
Output("sidebar-state", "data"),
|
||||
Input("btn-toggle-sidebar", "n_clicks"),
|
||||
State("sidebar-state", "data"),
|
||||
prevent_initial_call=True
|
||||
prevent_initial_call=True,
|
||||
)
|
||||
def toggle_sidebar(n_clicks, state):
|
||||
# Wenn der Button geklickt wurde, invertiere den collapsed-Wert
|
||||
collapsed = state.get("collapsed", False)
|
||||
return {"collapsed": not collapsed}
|
||||
|
||||
|
||||
# 2) Render-Callback: Zeichnet die Sidebar neu und setzt die CSS-Klasse
|
||||
@callback(
|
||||
[Output("sidebar", "children"), Output("sidebar", "className")],
|
||||
Input("sidebar-state", "data")
|
||||
)
|
||||
def render_sidebar(state):
|
||||
collapsed = state.get("collapsed", False)
|
||||
sidebar_class = "sidebar collapsed" if collapsed else "sidebar"
|
||||
|
||||
# Sidebar() gibt jetzt nur den Inhalt zurück
|
||||
sidebar_content = Sidebar(collapsed=collapsed)
|
||||
|
||||
return sidebar_content, sidebar_class
|
||||
def toggle_sidebar(n, data):
|
||||
if n is None:
|
||||
# Kein Klick, nichts ändern!
|
||||
return data
|
||||
collapsed = not data.get("collapsed", False)
|
||||
return {"collapsed": collapsed}
|
||||
@@ -3,9 +3,8 @@
|
||||
from dash import html
|
||||
import dash_bootstrap_components as dbc
|
||||
from dash_iconify import DashIconify
|
||||
from typing import List, Any
|
||||
|
||||
def Sidebar(collapsed: bool = False) -> List[Any]:
|
||||
def Sidebar(collapsed: bool = False):
|
||||
"""
|
||||
Gibt nur den Inhalt der Sidebar zurück (ohne das äußere div mit id="sidebar").
|
||||
Das äußere div wird bereits in app.py definiert.
|
||||
@@ -19,14 +18,20 @@ def Sidebar(collapsed: bool = False) -> List[Any]:
|
||||
{"label": "Benutzer", "href": "/users", "icon": "mdi:account"},
|
||||
]
|
||||
|
||||
nav_links = []
|
||||
|
||||
for item in nav_items:
|
||||
# Die ID muss in den Callbacks exakt so referenziert werden:
|
||||
link_id = {"type": "nav-item", "index": item["label"]}
|
||||
|
||||
# ✅ NavLink erstellen
|
||||
nav_link = dbc.NavLink(
|
||||
if collapsed:
|
||||
nav_links = [
|
||||
dbc.NavLink(
|
||||
DashIconify(icon=item["icon"], width=24),
|
||||
href=item["href"],
|
||||
active="exact",
|
||||
className="sidebar-item-collapsed",
|
||||
id={"type": "nav-item", "index": item["label"]},
|
||||
)
|
||||
for item in nav_items
|
||||
]
|
||||
else:
|
||||
nav_links = [
|
||||
dbc.NavLink(
|
||||
[
|
||||
DashIconify(icon=item["icon"], width=24),
|
||||
html.Span(item["label"], className="ms-2 sidebar-label"),
|
||||
@@ -34,30 +39,11 @@ def Sidebar(collapsed: bool = False) -> List[Any]:
|
||||
href=item["href"],
|
||||
active="exact",
|
||||
className="sidebar-item",
|
||||
id=link_id,
|
||||
id={"type": "nav-item", "index": item["label"]},
|
||||
)
|
||||
for item in nav_items
|
||||
]
|
||||
|
||||
# ✅ Conditional List Construction - keine append() nötig
|
||||
if collapsed:
|
||||
tooltip = dbc.Tooltip(
|
||||
item["label"],
|
||||
target=link_id,
|
||||
placement="right",
|
||||
id=f"tooltip-{item['label']}",
|
||||
)
|
||||
link_components = [nav_link, tooltip]
|
||||
else:
|
||||
link_components = [nav_link]
|
||||
|
||||
# ✅ Alle Komponenten in einem div-Container
|
||||
nav_links.append(
|
||||
html.Div(
|
||||
children=link_components,
|
||||
className="nav-item-container"
|
||||
)
|
||||
)
|
||||
|
||||
# Gib nur den Inhalt zurück, nicht das äußere div
|
||||
return [
|
||||
html.Div(
|
||||
className="sidebar-toggle",
|
||||
@@ -67,5 +53,20 @@ def Sidebar(collapsed: bool = False) -> List[Any]:
|
||||
className="toggle-button",
|
||||
)
|
||||
),
|
||||
dbc.Nav(nav_links, vertical=True, pills=True, className="sidebar-nav")
|
||||
dbc.Collapse(
|
||||
dbc.Nav(
|
||||
nav_links,
|
||||
vertical=True,
|
||||
pills=True,
|
||||
className="sidebar-nav",
|
||||
),
|
||||
is_open=not collapsed,
|
||||
className="sidebar-nav",
|
||||
) if not collapsed else
|
||||
dbc.Nav(
|
||||
nav_links,
|
||||
vertical=True,
|
||||
pills=True,
|
||||
className="sidebar-nav-collapsed",
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user