Ribbon: TopBar-Ansichtssteuerung in Ansichten-Tab gemergt; TopBar auf schmale globale Leiste reduziert

This commit is contained in:
2026-07-05 17:57:17 +02:00
parent 3a206060e3
commit 85011cb8ef
5 changed files with 206 additions and 223 deletions
+34 -26
View File
@@ -69,7 +69,7 @@ import type { Pick3dHit } from "./viewport/Wasm3DViewport";
import { ResourceManager } from "./ui/ResourceManager"; import { ResourceManager } from "./ui/ResourceManager";
import type { ResourceManagerHandlers } from "./ui/ResourceManager"; import type { ResourceManagerHandlers } from "./ui/ResourceManager";
import { HatchSwatch } from "./ui/hatchPreview"; import { HatchSwatch } from "./ui/hatchPreview";
import { TopBar } from "./ui/TopBar"; import { TopBar, ViewRibbonTab } from "./ui/TopBar";
import { RibbonBar } from "./ui/ribbon/RibbonBar"; import { RibbonBar } from "./ui/ribbon/RibbonBar";
import { Dropdown } from "./ui/Dropdown"; import { Dropdown } from "./ui/Dropdown";
import type { DropdownItem } from "./ui/Dropdown"; import type { DropdownItem } from "./ui/Dropdown";
@@ -3191,8 +3191,41 @@ export default function App() {
e.target.value = ""; e.target.value = "";
}} }}
/> />
{/* Schmale globale Leiste: Marke/Ressourcen · Text-Formatierung ·
Datei/Export/Einstellungen. Ansichts-/Zoom-/Darstellungs-Steuerung ist
in den „Ansichten"-Ribbon-Tab gewandert (ViewRibbonTab, unten). */}
<TopBar <TopBar
project={project} project={project}
zoomEnabled={planActive}
onExportPdf={() => setExportOpen(true)}
onExportDxf={() => setExportDxfOpen(true)}
onExportSchedule={onExportSchedule}
resourcesOpen={resourcesOpen}
onToggleResources={() => setResourcesOpen((v) => !v)}
onOpenSettings={() => setSettingsOpen(true)}
onImport={() => dxfInputRef.current?.click()}
onSaveProject={onSaveProject}
onOpenProject={onOpenProject}
layoutMenu={
<LayoutMenu
layout={layout}
onApply={(l) => setLayout(reconcileLayout(l))}
/>
}
textTarget={textTarget}
/>
{/* Ribbon-Oberleiste (2D · 3D · BIM · Ansichten). Der „Ansichten"-Tab trägt
die aus der TopBar gemergte Ansichts-/Zoom-/Darstellungs-Steuerung
(ViewRibbonTab); die Werkzeug-Sidebar bleibt vorerst erhalten. */}
<RibbonBar
activeTool={activeTool}
activeCommand={engineView.commandName}
toolsEnabled={activeLevel.kind === "floor"}
onSelectTool={onSelectTool}
onRunCommand={onRunCommand}
viewsContent={
<ViewRibbonTab
viewToggleEnabled={activeLevel.kind === "floor"} viewToggleEnabled={activeLevel.kind === "floor"}
viewType={viewType} viewType={viewType}
onViewType={setViewType} onViewType={setViewType}
@@ -3211,9 +3244,6 @@ export default function App() {
onFit={fitPlan} onFit={fitPlan}
onFitSelection={fitSelection} onFitSelection={fitSelection}
onZoom100={zoomToApplied} onZoom100={zoomToApplied}
onExportPdf={() => setExportOpen(true)}
onExportDxf={() => setExportDxfOpen(true)}
onExportSchedule={onExportSchedule}
renderMode={renderMode} renderMode={renderMode}
onRenderMode={setRenderMode} onRenderMode={setRenderMode}
renderModeEnabled={viewType === "perspektive"} renderModeEnabled={viewType === "perspektive"}
@@ -3240,30 +3270,8 @@ export default function App() {
}, },
deleteDrawingCombo, deleteDrawingCombo,
}} }}
resourcesOpen={resourcesOpen}
onToggleResources={() => setResourcesOpen((v) => !v)}
onOpenSettings={() => setSettingsOpen(true)}
onImport={() => dxfInputRef.current?.click()}
onSaveProject={onSaveProject}
onOpenProject={onOpenProject}
layoutMenu={
<LayoutMenu
layout={layout}
onApply={(l) => setLayout(reconcileLayout(l))}
/> />
} }
textTarget={textTarget}
/>
{/* Ribbon-Oberleiste (2D · 3D · BIM · Ansichten) — additiv unter der TopBar.
Teilt sich denselben Aktivierungs-Pfad wie die Werkzeug-Sidebar; die
Sidebar bleibt vorerst erhalten (Plan-Doc Phase 3 entfernt sie). */}
<RibbonBar
activeTool={activeTool}
activeCommand={engineView.commandName}
toolsEnabled={activeLevel.kind === "floor"}
onSelectTool={onSelectTool}
onRunCommand={onRunCommand}
/> />
<div className="body"> <div className="body">
+9
View File
@@ -5472,3 +5472,12 @@ body {
font-style: italic; font-style: italic;
padding: 0 8px; padding: 0 8px;
} }
/* „Ansichten"-Tab: die aus der TopBar gemergten Cluster (View-Grid, Kombis,
Detail, Zoom, Darstellung) in EINER Zeile — nutzt die bestehenden `.tb-*`-
Stile unverändert weiter. */
.ribbon-views {
display: flex;
align-items: center;
gap: 10px;
min-height: 48px;
}
+120 -166
View File
@@ -84,49 +84,12 @@ const ZOOM_CLUSTER_WIDTH = 140;
export interface TopBarProps { export interface TopBarProps {
project: Project; project: Project;
// Ansicht. // Ansichts-/Zoom-/Darstellungs-Steuerung ist in den „Ansichten"-Ribbon-Tab
/** Ob der Ansichts-Umschalter sinnvoll ist (nur für Geschosse). */ // gewandert (siehe ViewRibbonTab). Die TopBar ist nur noch die schmale globale
viewToggleEnabled: boolean; // Leiste: Marke/Ressourcen · Text-Formatierung · Datei/Export/Einstellungen.
viewType: ViewType;
onViewType: (v: ViewType) => void;
// 3D-Ansicht: kanonischer Blickwinkel + Kamera-Sichtwinkel (FOV). Wirkt nur in /** Ob Zoom/Plan aktiv ist — steuert nur noch, ob Export im Datei-Menü geht. */
// der Perspektive; ein Preset-Klick wechselt nötigenfalls auch dorthin.
view3d: View3d;
onView3d: (v: View3d) => void;
/** Sichtwinkel der Perspektivkamera in Grad (Kamera-Einstellungen). */
fov: number;
onFov: (n: number) => void;
// Zeichenwerkzeuge (Auswahl/Wand/Linie …), Wandtyp-Wahl und Fang-Menü leben
// jetzt in der Werkzeug-Palette (ToolsPanel), nicht mehr in der Oberleiste.
// Detailgrad.
detail: DetailLevel;
onDetail: (d: DetailLevel) => void;
/** Detailgrad wirkt nur, wenn ein Plan gezeichnet wird (sonst deaktiviert). */
detailEnabled: boolean;
// Massstab + Zoom.
/**
* Gewählter (angewandter) Papier-Massstab-Nenner (1:N) — steuert das Dropdown
* und „100 %". Setzt beim Wählen den Plan-Zoom auf exakt 1:N.
*/
scaleDenominator: number;
/** Einen Papier-Massstab anwenden (Preset/frei): setzt N + Plan-Zoom. */
onApplyScale: (n: number) => void;
/**
* LIVE Papier-Massstab-Nenner aus der aktuellen View-Transform (ändert sich
* beim Wheel-Zoom) oder `null`, wenn keine Plan-Ansicht aktiv ist.
*/
liveScale: number | null;
/** Live-Zoom in Prozent (aus der Plan-View-Transform). */
zoomPercent: number;
/** Zoom-Aktionen — nur wirksam, wenn eine Plan-Ansicht aktiv ist. */
zoomEnabled: boolean; zoomEnabled: boolean;
onFit: () => void;
onFitSelection: () => void;
onZoom100: () => void;
/** Öffnet den PDF-Export-Dialog (nur im Grundriss sinnvoll). */ /** Öffnet den PDF-Export-Dialog (nur im Grundriss sinnvoll). */
onExportPdf: () => void; onExportPdf: () => void;
/** Öffnet den DXF-Export-Dialog (nur im Grundriss sinnvoll). */ /** Öffnet den DXF-Export-Dialog (nur im Grundriss sinnvoll). */
@@ -134,21 +97,6 @@ export interface TopBarProps {
/** Lädt die Bauteilliste des Projekts als CSV herunter. */ /** Lädt die Bauteilliste des Projekts als CSV herunter. */
onExportSchedule: () => void; onExportSchedule: () => void;
// Darstellungsart — kontextabhängig: Perspektive nutzt `renderMode`,
// Grundriss nutzt `planColorMode`. Genau einer der beiden ist je View aktiv.
renderMode: RenderMode;
onRenderMode: (m: RenderMode) => void;
/** Render-Modus nur in der Perspektive sinnvoll (sonst deaktiviert). */
renderModeEnabled: boolean;
// Farb-Modus des Grundrisses (Farbig ↔ Schwarz-Weiss). Nur im Grundriss.
planColorMode: PlanColorMode;
onPlanColorMode: (m: PlanColorMode) => void;
// Sichtbarkeits-Kombinationen (Ebenen-/Zeichnungskombinationen).
/** Handler-Satz für die beiden Kombinations-Dropdowns. */
combos: ComboMenuHandlers;
// Ressourcen-Fenster. // Ressourcen-Fenster.
resourcesOpen: boolean; resourcesOpen: boolean;
onToggleResources: () => void; onToggleResources: () => void;
@@ -958,8 +906,45 @@ function AppMenu({
); );
} }
export function TopBar({ /**
project, * Props des „Ansichten"-Tabs — die Ansichts-/Zoom-/Darstellungs-Steuerung, die
* früher in der Mitte der TopBar sass. Eine Teilmenge von {@link TopBarProps}.
*/
export interface ViewRibbonTabProps {
viewToggleEnabled: boolean;
viewType: ViewType;
onViewType: (v: ViewType) => void;
view3d: View3d;
onView3d: (v: View3d) => void;
fov: number;
onFov: (n: number) => void;
detail: DetailLevel;
onDetail: (d: DetailLevel) => void;
detailEnabled: boolean;
scaleDenominator: number;
onApplyScale: (n: number) => void;
liveScale: number | null;
zoomPercent: number;
zoomEnabled: boolean;
onFit: () => void;
onFitSelection: () => void;
onZoom100: () => void;
renderMode: RenderMode;
onRenderMode: (m: RenderMode) => void;
renderModeEnabled: boolean;
planColorMode: PlanColorMode;
onPlanColorMode: (m: PlanColorMode) => void;
combos: ComboMenuHandlers;
}
/**
* „Ansichten"-Tab des Ribbons — trägt die aus der TopBar gemergten Mittelcluster:
* 2D/3D-Ansichts-Raster (+ Kamera-FOV), Sichtbarkeits-Kombinationen, Detailgrad,
* Massstab/Zoom-Cluster und Darstellungsart. EIN Ribbon für alles; Handler/Setter
* kommen unverändert von App (gleiche Props wie zuvor an die TopBar). Wird von
* `RibbonBar` als Inhalt des „Ansichten"-Tabs gerendert.
*/
export function ViewRibbonTab({
viewToggleEnabled, viewToggleEnabled,
viewType, viewType,
onViewType, onViewType,
@@ -978,44 +963,13 @@ export function TopBar({
onFit, onFit,
onFitSelection, onFitSelection,
onZoom100, onZoom100,
onExportPdf,
onExportDxf,
onExportSchedule,
renderMode, renderMode,
onRenderMode, onRenderMode,
renderModeEnabled, renderModeEnabled,
planColorMode, planColorMode,
onPlanColorMode, onPlanColorMode,
combos, combos,
resourcesOpen, }: ViewRibbonTabProps) {
onToggleResources,
onOpenSettings,
layoutMenu,
onImport,
onSaveProject,
onOpenProject,
textTarget,
}: TopBarProps) {
// Dezente Scrollleiste: die Oberleiste scrollt horizontal (overflow-x). Statt
// der prägnanten Standard-Leiste blenden wir eine eigene, sehr ruhige Leiste
// NUR während des Scrollens ein (Klasse `is-scrolling`, ~900 ms nachlaufend).
const headerRef = useRef<HTMLElement | null>(null);
useEffect(() => {
const el = headerRef.current;
if (!el) return;
let timer: ReturnType<typeof setTimeout> | undefined;
const onScroll = () => {
el.classList.add("is-scrolling");
if (timer) clearTimeout(timer);
timer = setTimeout(() => el.classList.remove("is-scrolling"), 900);
};
el.addEventListener("scroll", onScroll, { passive: true });
return () => {
el.removeEventListener("scroll", onScroll);
if (timer) clearTimeout(timer);
};
}, []);
// „frei" wird über einen Prompt erfasst; akzeptiert „1:35" ODER „35". // „frei" wird über einen Prompt erfasst; akzeptiert „1:35" ODER „35".
const onScaleChange = (value: string) => { const onScaleChange = (value: string) => {
if (value === "__custom__") { if (value === "__custom__") {
@@ -1030,14 +984,7 @@ export function TopBar({
const n = Number(value); const n = Number(value);
if (Number.isFinite(n) && n > 0) onApplyScale(n); if (Number.isFinite(n) && n > 0) onApplyScale(n);
}; };
// Aktueller Nenner steht evtl. nicht in den Presets (frei gesetzt) → dann
// einen Eintrag „1:N" voranstellen, damit das Select den Wert anzeigt.
const inPresets = SCALE_PRESETS.includes(scaleDenominator); const inPresets = SCALE_PRESETS.includes(scaleDenominator);
// Optionsliste des Massstab-Dropdowns: ggf. der frei gesetzte aktuelle Wert
// (damit er sichtbar bleibt), dann die Presets, zuletzt „frei …" als
// Sonder-Wert, der über onScaleChange den Prompt öffnet (Funktion unverändert).
const scaleOptions: DropdownOption[] = [ const scaleOptions: DropdownOption[] = [
...(inPresets ...(inPresets
? [] ? []
@@ -1047,41 +994,8 @@ export function TopBar({
]; ];
return ( return (
<header className="topbar" ref={headerRef}> <div className="ribbon-views">
{/* Marke (DOSSIER-Wortmarke) + Settings/Ressourcen-Icons daneben, {/* Ansichten als EIN kompaktes Icon-Grid (2D-Grundriss + 3D-Blicke). */}
gestapelt wie im Rhino-Plugin (ToolbarApp.jsx: Wortmarke links,
rechts daneben ein schmaler Icon-Stapel). */}
<div className="tb-brandgroup">
<div className="brand">
<span className="brand-word">
DOSSIER<span className="brand-dot">.</span>
</span>
<span className="tag">{t("brand.tag")}</span>
</div>
<div className="tb-brandicons">
<button
type="button"
className={`tb-brandicon${resourcesOpen ? " active" : ""}`}
onClick={onToggleResources}
aria-pressed={resourcesOpen}
aria-label={t("topbar.resources")}
title={`${t("topbar.resources")}${t("topbar.resources.hint")}`}
>
<TbIcon name="widgets" />
</button>
</div>
</div>
<Sep />
{/* Ansichten als EIN kompaktes Icon-Grid (Vectorworks/DOSSIER-Stil): der
2D-Grundriss UND die 3D-Blicke sind eine durchgängige Icon-Serie —
„Grundriss" schaltet auf den 2D-Plan, die übrigen auf die jeweilige
3D-Ansicht (Perspektive wird dabei automatisch aktiviert). 4 oben /
3 unten. Tooltip nennt jede Ansicht im Klartext. */}
{/* Ansichts-Icons bleiben immer sichtbar; auf Nicht-Geschoss-Ebenen
(Schnitt/Ansicht/2D-Zeichnung) sind sie deaktiviert, weil dort keine
2D/3D-Umschaltung greift — die Serie verschwindet nicht mehr ganz. */}
<div className="tb-group" role="group" aria-label={t("topbar.viewGroup")}> <div className="tb-group" role="group" aria-label={t("topbar.viewGroup")}>
<div className="view-grid view-grid-4"> <div className="view-grid view-grid-4">
<button <button
@@ -1139,10 +1053,7 @@ export function TopBar({
</div> </div>
<Sep /> <Sep />
{/* Sichtbarkeits-Kombinationen (Vectorworks: Ebenen-/Zeichnungs- {/* Sichtbarkeits-Kombinationen (Ebenen-/Zeichnungskombinationen). */}
kombinationen). Sitzt direkt nach dem Ansichts-Umschalter, weil beides
„Navigations-/Sicht"-Steuerung ist — ruhige Stelle vor dem Detailgrad.
Nur die Dropdowns; eine grafische Überschreibung kommt später. */}
<div className="tb-group tb-stack" role="group" aria-label={t("combo.layers.title")}> <div className="tb-group tb-stack" role="group" aria-label={t("combo.layers.title")}>
<label className="tb-field tb-field-row"> <label className="tb-field tb-field-row">
<span className="tb-label">{t("combo.layers")}</span> <span className="tb-label">{t("combo.layers")}</span>
@@ -1167,27 +1078,9 @@ export function TopBar({
/> />
</label> </label>
</div> </div>
<Sep /> <Sep />
{/* Text-Gruppe (immer sichtbar) — Stil/Schrift/Grösse + B/I/U · L/C/R + {/* Detailgrad (grob/mittel/fein) — wirkt im Grundriss. */}
Zeilenhöhe. Formatiert das aktuelle Text-Ziel (Raumstempel) live.
Sitzt bewusst zwischen den Ebenen-Kombis und Detailgrad/Massstab.
„+ Text" ist raus — Text wird ein eigenständiges Zeichenwerkzeug
(HANDOVER-Backlog AUDIT B1), kein TopBar-Knopf mehr. */}
<TextGroup textTarget={textTarget} />
<Sep />
{/* Zeichenwerkzeuge liegen jetzt in der Werkzeug-Palette (ToolsPanel,
dockbar) — nicht mehr in der Oberleiste (Vectorworks-Bild: Werkzeuge
in der Palette). Auch Wandtyp-Wahl + Fang-Menü sind dort. */}
{/* Detailgrad (grob/mittel/fein) — wirkt im Grundriss auf Symbole +
Schraffuren/Linien. In der Perspektive (noch) ohne Wirkung → dort
deaktiviert mit Tooltip statt stiller No-Op. */}
{/* Detailgrad — Massstab lebt nur noch EINMAL im Zoom-Cluster (die
Dropdown hier war ein redundantes Relikt der TopBar-Reorg). */}
<div className="tb-group" role="group" aria-label={t("topbar.detailGroup")}> <div className="tb-group" role="group" aria-label={t("topbar.detailGroup")}>
<label className="tb-field tb-field-row"> <label className="tb-field tb-field-row">
<span className="tb-label">{t("topbar.detail")}</span> <span className="tb-label">{t("topbar.detail")}</span>
@@ -1208,14 +1101,9 @@ export function TopBar({
/> />
</label> </label>
</div> </div>
<Sep /> <Sep />
{/* Massstab/Zoom-Cluster (DOSSIER, 2×2). Links über beide Zeilen die {/* Massstab/Zoom-Cluster (2×2). */}
kombinierte Stat-Pille (Live-Massstab 1:N oben / Zoom% unten); rechts
oben das Massstab-Dropdown, rechts unten die Zoom-Segmentpille. „am
Massstab" (Live-Massstab == gewählter Massstab) hebt die Stat-Pille im
Akzent hervor. */}
<div className="tb-group tb-zoomcluster" role="group" aria-label={t("topbar.scaleGroup")}> <div className="tb-group tb-zoomcluster" role="group" aria-label={t("topbar.scaleGroup")}>
<div <div
className={`tb-stat${ className={`tb-stat${
@@ -1271,13 +1159,9 @@ export function TopBar({
/> />
</div> </div>
</div> </div>
<Sep /> <Sep />
{/* Darstellungsart (Vectorworks-Stil) — EIN Dropdown, dessen Optionen vom {/* Darstellungsart — Grundriss: Farbig/SW; Perspektive: Render-Modus. */}
Ansichtstyp abhängen: Grundriss → Farbig/Schwarz-Weiss (planColorMode);
Perspektive → Schattiert/Weiss/Drahtgitter/Kanten (renderMode). Der
Linien-Modus (Display/Print) bleibt separat. */}
<div className="tb-group" role="group" aria-label={t("display.style.group")}> <div className="tb-group" role="group" aria-label={t("display.style.group")}>
<label className="tb-field"> <label className="tb-field">
<span className="tb-label">{t("display.style")}</span> <span className="tb-label">{t("display.style")}</span>
@@ -1308,9 +1192,79 @@ export function TopBar({
)} )}
</label> </label>
</div> </div>
</div>
);
}
export function TopBar({
project,
zoomEnabled,
onExportPdf,
onExportDxf,
onExportSchedule,
resourcesOpen,
onToggleResources,
onOpenSettings,
layoutMenu,
onImport,
onSaveProject,
onOpenProject,
textTarget,
}: TopBarProps) {
// Dezente Scrollleiste: die Oberleiste scrollt horizontal (overflow-x). Statt
// der prägnanten Standard-Leiste blenden wir eine eigene, sehr ruhige Leiste
// NUR während des Scrollens ein (Klasse `is-scrolling`, ~900 ms nachlaufend).
const headerRef = useRef<HTMLElement | null>(null);
useEffect(() => {
const el = headerRef.current;
if (!el) return;
let timer: ReturnType<typeof setTimeout> | undefined;
const onScroll = () => {
el.classList.add("is-scrolling");
if (timer) clearTimeout(timer);
timer = setTimeout(() => el.classList.remove("is-scrolling"), 900);
};
el.addEventListener("scroll", onScroll, { passive: true });
return () => {
el.removeEventListener("scroll", onScroll);
if (timer) clearTimeout(timer);
};
}, []);
return (
<header className="topbar" ref={headerRef}>
{/* Marke (DOSSIER-Wortmarke) + Settings/Ressourcen-Icons daneben,
gestapelt wie im Rhino-Plugin (ToolbarApp.jsx: Wortmarke links,
rechts daneben ein schmaler Icon-Stapel). */}
<div className="tb-brandgroup">
<div className="brand">
<span className="brand-word">
DOSSIER<span className="brand-dot">.</span>
</span>
<span className="tag">{t("brand.tag")}</span>
</div>
<div className="tb-brandicons">
<button
type="button"
className={`tb-brandicon${resourcesOpen ? " active" : ""}`}
onClick={onToggleResources}
aria-pressed={resourcesOpen}
aria-label={t("topbar.resources")}
title={`${t("topbar.resources")}${t("topbar.resources.hint")}`}
>
<TbIcon name="widgets" />
</button>
</div>
</div>
<Sep /> <Sep />
{/* 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-
Steuerung ist (kein Ansichts-/Ausgabe-Element). */}
<TextGroup textTarget={textTarget} />
{/* Rechte Gruppe: Layout-Menü · Projektname · Datei-Burger (ganz rechts, {/* Rechte Gruppe: Layout-Menü · Projektname · Datei-Burger (ganz rechts,
GTK-Stil) — Ressourcen sitzt jetzt bei der Marke (s. o.). */} GTK-Stil) — Ressourcen sitzt jetzt bei der Marke (s. o.). */}
<div className="topbar-right"> <div className="topbar-right">
+13 -2
View File
@@ -12,6 +12,7 @@
// Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md). // Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md).
import { useState } from "react"; import { useState } from "react";
import type { ReactNode } from "react";
import { t } from "../../i18n"; import { t } from "../../i18n";
import { ToolIcon } from "../../panels/ToolsPanel"; import { ToolIcon } from "../../panels/ToolsPanel";
import { getTool } from "../../tools/tools"; import { getTool } from "../../tools/tools";
@@ -32,6 +33,13 @@ export interface RibbonBarProps {
onSelectTool: (id: ToolId) => void; onSelectTool: (id: ToolId) => void;
/** Engine-Befehl starten (Ändern-Gruppe u. a.). */ /** Engine-Befehl starten (Ändern-Gruppe u. a.). */
onRunCommand: (name: string) => void; onRunCommand: (name: string) => void;
/**
* 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.
*/
viewsContent?: ReactNode;
} }
/** Ein einzelner Ribbon-Button (Werkzeug ODER Befehl). */ /** Ein einzelner Ribbon-Button (Werkzeug ODER Befehl). */
@@ -104,9 +112,12 @@ export function RibbonBar(props: RibbonBarProps) {
))} ))}
</div> </div>
{/* Gruppen des aktiven Tabs */} {/* Gruppen des aktiven Tabs. Der „Ansichten"-Tab trägt reichen Custom-
Inhalt (ViewRibbonTab) statt der datengetriebenen Item-Gruppen. */}
<div className="ribbon-body"> <div className="ribbon-body">
{current.groups.length === 0 ? ( {current.id === "views" && props.viewsContent ? (
props.viewsContent
) : current.groups.length === 0 ? (
<div className="ribbon-empty">{t("ribbon.empty")}</div> <div className="ribbon-empty">{t("ribbon.empty")}</div>
) : ( ) : (
current.groups.map((g) => ( current.groups.map((g) => (
+9 -8
View File
@@ -41,9 +41,10 @@ const t = (id: ToolId): RibbonItem => ({ kind: "tool", id });
const c = (name: string): RibbonItem => ({ kind: "command", name }); const c = (name: string): RibbonItem => ({ kind: "command", name });
/** /**
* Die Ribbon-Tabs. Phase 1 füllt 2D (Zeichnen + Ändern) und BIM (Bauteile) mit * Die Ribbon-Tabs (Reihenfolge 2D·3D·BIM·Ansichten). 2D (Zeichnen + Ändern) und
* den vorhandenen Werkzeugen/Befehlen; 3D und Ansichten bleiben vorerst leer * BIM (Bauteile) sind datengetrieben aus Werkzeugen/Befehlen; Ansichten" trägt
* (Platzhalter, in einer späteren Phase gefüllt — siehe Plan-Doc). * reichen Custom-Inhalt (aus der TopBar gemergt, siehe `ViewRibbonTab` — die
* Registry lässt seine `groups` leer). 3D ist vorerst leer (spätere Phase).
*/ */
export const RIBBON_TABS: RibbonTab[] = [ export const RIBBON_TABS: RibbonTab[] = [
{ {
@@ -74,6 +75,11 @@ export const RIBBON_TABS: RibbonTab[] = [
}, },
], ],
}, },
{
id: "3d",
labelKey: "ribbon.tab.3d",
groups: [],
},
{ {
id: "bim", id: "bim",
labelKey: "ribbon.tab.bim", labelKey: "ribbon.tab.bim",
@@ -91,11 +97,6 @@ export const RIBBON_TABS: RibbonTab[] = [
}, },
], ],
}, },
{
id: "3d",
labelKey: "ribbon.tab.3d",
groups: [],
},
{ {
id: "views", id: "views",
labelKey: "ribbon.tab.views", labelKey: "ribbon.tab.views",