From 456ebc809852865763dbaecd7de71787368f5ba6 Mon Sep 17 00:00:00 2001 From: Karim Date: Sun, 5 Jul 2026 18:04:48 +0200 Subject: [PATCH] Ribbon-Tabs in die TopBar-Zeile verlegt: eine Leiste (Chrome + Tabs), Inhalt darunter --- src/App.tsx | 8 +++ src/styles.css | 18 +++--- src/ui/TopBar.tsx | 18 +++++- src/ui/ribbon/RibbonBar.tsx | 119 ++++++++++++++++++++---------------- 4 files changed, 103 insertions(+), 60 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6146ed2..3699dc0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -71,6 +71,7 @@ import type { ResourceManagerHandlers } from "./ui/ResourceManager"; import { HatchSwatch } from "./ui/hatchPreview"; import { TopBar, ViewRibbonTab } from "./ui/TopBar"; import { RibbonBar } from "./ui/ribbon/RibbonBar"; +import type { RibbonTabId } from "./ui/ribbon/ribbonItems"; import { Dropdown } from "./ui/Dropdown"; import type { DropdownItem } from "./ui/Dropdown"; import { TextEditorDialog } from "./ui/TextEditorDialog"; @@ -355,6 +356,10 @@ export default function App() { // Einstellungen sowie der laufende Werkzeug-Zustand (Ref, kein Re-Render je // Mausbewegung) und die Live-Vorschau (Draft → Overlay in PlanView). const [activeTool, setActiveTool] = useState("select"); + // Aktiver Ribbon-Tab (2D/3D/BIM/Ansichten). Liegt hier (nicht in der RibbonBar), + // weil die Tab-Reiter in der TopBar-Zeile sitzen und der Ribbon-Inhalt darunter + // — beide teilen sich denselben aktiven Tab. + const [ribbonTab, setRibbonTab] = useState("2d"); const [activeWallTypeId, setActiveWallTypeId] = useState( project.wallTypes[0].id, ); @@ -3196,6 +3201,8 @@ export default function App() { in den „Ansichten"-Ribbon-Tab gewandert (ViewRibbonTab, unten). */} setExportOpen(true)} onExportDxf={() => setExportDxfOpen(true)} @@ -3219,6 +3226,7 @@ export default function App() { die aus der TopBar gemergte Ansichts-/Zoom-/Darstellungs-Steuerung (ViewRibbonTab); die Werkzeug-Sidebar bleibt vorerst erhalten. */} void; + // Ansichts-/Zoom-/Darstellungs-Steuerung ist in den „Ansichten"-Ribbon-Tab // gewandert (siehe ViewRibbonTab). Die TopBar ist nur noch die schmale globale - // Leiste: Marke/Ressourcen · Text-Formatierung · Datei/Export/Einstellungen. + // Leiste: Marke/Ressourcen · Tabs · Text-Formatierung · Datei/Export/Einstell. /** Ob Zoom/Plan aktiv ist — steuert nur noch, ob Export im Datei-Menü geht. */ zoomEnabled: boolean; @@ -1198,6 +1205,8 @@ export function ViewRibbonTab({ export function TopBar({ project, + ribbonTab, + onRibbonTab, zoomEnabled, onExportPdf, onExportDxf, @@ -1259,6 +1268,13 @@ export function TopBar({ + {/* Ribbon-Tab-Reiter (2D/3D/BIM/Ansichten) — sitzen in dieser Zeile, der + Ribbon-Inhalt folgt darunter (RibbonBar). So gibt es nur ZWEI Bänder: + diese Leiste (Chrome + Tabs) und den Inhalt. */} + + + + {/* Text-Gruppe (kontextabhängig) — Stil/Schrift/Grösse + B/I/U · L/C/R + Zeilenhöhe. Formatiert das aktuelle Text-Ziel (Raumstempel) live. Bleibt in der schmalen globalen Leiste, weil sie eine kontextuelle Formatier- diff --git a/src/ui/ribbon/RibbonBar.tsx b/src/ui/ribbon/RibbonBar.tsx index 4f18a2c..c1acd79 100644 --- a/src/ui/ribbon/RibbonBar.tsx +++ b/src/ui/ribbon/RibbonBar.tsx @@ -1,17 +1,16 @@ -// Ribbon-Oberleiste (docs/design/ribbon-ui-plan.md, Phase 1) — datengetriebene -// Tab-Leiste unter der TopBar. Zeigt je Tab (2D · 3D · BIM · Ansichten) die -// gruppierten Werkzeuge/Befehle aus `RIBBON_TABS`. ADDITIV: die Werkzeug-Sidebar -// (ToolsPanel) bleibt vorerst funktionsfähig — beide teilen sich denselben -// Aktivierungs-Pfad (onSelectTool / engine.start), es gibt keine Duplikat-Logik. +// Ribbon-Inhaltsleiste (docs/design/ribbon-ui-plan.md) — rendert den INHALT des +// aktiven Ribbon-Tabs. Die Tab-REITER selbst sitzen in der TopBar-Zeile (eine +// Leiste: globales Chrome + Tabs), damit es nur ZWEI Bänder gibt: TopBar-Zeile +// (mit Tabs) oben, Ribbon-Inhalt darunter. Der aktive Tab wird als `tab`-Prop +// hereingereicht (State liegt in App, geteilt mit der TopBar). // -// Werkzeug-Elemente laufen über `onSelectTool` (gekoppelte Befehle inklusive, -// wie in der Sidebar); Befehls-Elemente über `onRunCommand` (engine.start). Das -// Aktiv-Highlight spiegelt für Werkzeuge `activeTool`, für Befehle den laufenden -// Befehl (`activeCommand`). +// Werkzeug-Elemente laufen über `onSelectTool` (gekoppelte Befehle inklusive, wie +// in der Sidebar); Befehls-Elemente über `onRunCommand` (engine.start). Der +// „Ansichten"-Tab trägt reichen Custom-Inhalt (`viewsContent`, aus der TopBar +// gemergt) statt datengetriebener Item-Gruppen. // // Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md). -import { useState } from "react"; import type { ReactNode } from "react"; import { t } from "../../i18n"; import { ToolIcon } from "../../panels/ToolsPanel"; @@ -23,6 +22,8 @@ import type { RibbonItem, RibbonTabId } from "./ribbonItems"; import { CommandIcon } from "./CommandIcon"; export interface RibbonBarProps { + /** Aktiver Tab (State liegt in App, geteilt mit den Reitern in der TopBar). */ + tab: RibbonTabId; /** Aktives Werkzeug (Highlight der `tool`-Elemente). */ activeTool: ToolId; /** Name des laufenden Engine-Befehls (Highlight der `command`-Elemente). */ @@ -36,29 +37,28 @@ export interface RibbonBarProps { /** * Reicher Inhalt des „Ansichten"-Tabs (aus der TopBar gemergte Ansichts-/Zoom-/ * Darstellungs-Steuerung, `ViewRibbonTab`). Von App geliefert, damit dessen - * Setter/State dort bleiben — analog dazu, wie die TopBar das Layout-Menü als - * Node bekommt. Fehlt er, zeigt der Tab den Leer-Hinweis. + * Setter/State dort bleiben. Fehlt er, zeigt der Tab den Leer-Hinweis. */ viewsContent?: ReactNode; } +/** Aktivierungs-/Highlight-Kontext, den jeder Button-Typ braucht. */ +interface ButtonCtx { + activeTool: ToolId; + activeCommand: string | null; + toolsEnabled: boolean; + onSelectTool: (id: ToolId) => void; + onRunCommand: (name: string) => void; +} + /** Ein einzelner Ribbon-Button (Werkzeug ODER Befehl). */ -function RibbonButton({ - item, - activeTool, - activeCommand, - toolsEnabled, - onSelectTool, - onRunCommand, -}: { - item: RibbonItem; -} & Omit & { activeCommand: string | null }) { +function RibbonButton({ item, ctx }: { item: RibbonItem; ctx: ButtonCtx }) { if (item.kind === "tool") { const tool = getTool(item.id); // Nur Boden-gebundene Werkzeuge sind ohne aktives Geschoss gesperrt; freie // 2D-Werkzeuge (Linie/Kreis/…) bleiben auch auf Zeichnungsebenen nutzbar. - const disabled = !!tool.floorOnly && !toolsEnabled; - const active = activeTool === item.id; + const disabled = !!tool.floorOnly && !ctx.toolsEnabled; + const active = ctx.activeTool === item.id; const label = t(tool.labelKey); return ( + ))} + + ); +} + export function RibbonBar(props: RibbonBarProps) { - const [tab, setTab] = useState("2d"); - const current = RIBBON_TABS.find((tt) => tt.id === tab) ?? RIBBON_TABS[0]; + const current = RIBBON_TABS.find((tt) => tt.id === props.tab) ?? RIBBON_TABS[0]; + const ctx: ButtonCtx = { + activeTool: props.activeTool, + activeCommand: props.activeCommand, + toolsEnabled: props.toolsEnabled, + onSelectTool: props.onSelectTool, + onRunCommand: props.onRunCommand, + }; return (
- {/* Tab-Reiter */} -
- {RIBBON_TABS.map((tt) => ( - - ))} -
- - {/* Gruppen des aktiven Tabs. Der „Ansichten"-Tab trägt reichen Custom- - Inhalt (ViewRibbonTab) statt der datengetriebenen Item-Gruppen. */} + {/* Inhalt des aktiven Tabs. Der „Ansichten"-Tab trägt reichen Custom-Inhalt + (ViewRibbonTab) statt der datengetriebenen Item-Gruppen. */}
{current.id === "views" && props.viewsContent ? ( props.viewsContent @@ -127,11 +146,7 @@ export function RibbonBar(props: RibbonBarProps) { ))}