Ribbon-Tabs in die TopBar-Zeile verlegt: eine Leiste (Chrome + Tabs), Inhalt darunter
This commit is contained in:
+17
-1
@@ -15,6 +15,8 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { Dropdown } from "./Dropdown";
|
||||
import type { DropdownItem, DropdownOption } from "./Dropdown";
|
||||
import { WindowControls } from "./WindowControls";
|
||||
import { RibbonTabs } from "./ribbon/RibbonBar";
|
||||
import type { RibbonTabId } from "./ribbon/ribbonItems";
|
||||
import {
|
||||
applyMark,
|
||||
applyPreset,
|
||||
@@ -84,9 +86,14 @@ const ZOOM_CLUSTER_WIDTH = 140;
|
||||
export interface TopBarProps {
|
||||
project: Project;
|
||||
|
||||
// Ribbon-Tab-Reiter (2D/3D/BIM/Ansichten) sitzen in DIESER Zeile — der Ribbon-
|
||||
// Inhalt darunter (RibbonBar). Der aktive Tab liegt in App (geteilter State).
|
||||
ribbonTab: RibbonTabId;
|
||||
onRibbonTab: (id: RibbonTabId) => 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({
|
||||
|
||||
<Sep />
|
||||
|
||||
{/* 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. */}
|
||||
<RibbonTabs tab={ribbonTab} onTab={onRibbonTab} />
|
||||
|
||||
<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-
|
||||
|
||||
+67
-52
@@ -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<RibbonBarProps, "activeCommand"> & { 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 (
|
||||
<button
|
||||
@@ -66,7 +66,7 @@ function RibbonButton({
|
||||
className={`ribbon-btn${active ? " active" : ""}`}
|
||||
disabled={disabled}
|
||||
title={disabled ? t("tool.floorOnlyDisabled") : label}
|
||||
onClick={() => onSelectTool(item.id)}
|
||||
onClick={() => ctx.onSelectTool(item.id)}
|
||||
>
|
||||
<ToolIcon id={item.id} />
|
||||
<span>{label}</span>
|
||||
@@ -76,13 +76,13 @@ function RibbonButton({
|
||||
// Befehls-Element: Label aus der Befehls-Registry ableiten.
|
||||
const cmd = getCommand(item.name);
|
||||
const label = cmd ? t(cmd.labelKey) : item.name;
|
||||
const active = activeCommand === item.name;
|
||||
const active = ctx.activeCommand === item.name;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`ribbon-btn${active ? " active" : ""}`}
|
||||
title={label}
|
||||
onClick={() => onRunCommand(item.name)}
|
||||
onClick={() => ctx.onRunCommand(item.name)}
|
||||
>
|
||||
<CommandIcon name={item.name} />
|
||||
<span>{label}</span>
|
||||
@@ -90,30 +90,49 @@ function RibbonButton({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Tab-REITER — als eigene Komponente exportiert, weil sie in der TopBar-Zeile
|
||||
* gerendert werden (nicht hier). Rein gesteuert über `tab`/`onTab`.
|
||||
*/
|
||||
export function RibbonTabs({
|
||||
tab,
|
||||
onTab,
|
||||
}: {
|
||||
tab: RibbonTabId;
|
||||
onTab: (id: RibbonTabId) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="ribbon-tabs" role="tablist">
|
||||
{RIBBON_TABS.map((tt) => (
|
||||
<button
|
||||
key={tt.id}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={tt.id === tab}
|
||||
className={`ribbon-tab${tt.id === tab ? " active" : ""}`}
|
||||
onClick={() => onTab(tt.id)}
|
||||
>
|
||||
{t(tt.labelKey)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RibbonBar(props: RibbonBarProps) {
|
||||
const [tab, setTab] = useState<RibbonTabId>("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 (
|
||||
<div className="ribbon">
|
||||
{/* Tab-Reiter */}
|
||||
<div className="ribbon-tabs" role="tablist">
|
||||
{RIBBON_TABS.map((tt) => (
|
||||
<button
|
||||
key={tt.id}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={tt.id === tab}
|
||||
className={`ribbon-tab${tt.id === tab ? " active" : ""}`}
|
||||
onClick={() => setTab(tt.id)}
|
||||
>
|
||||
{t(tt.labelKey)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 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. */}
|
||||
<div className="ribbon-body">
|
||||
{current.id === "views" && props.viewsContent ? (
|
||||
props.viewsContent
|
||||
@@ -127,11 +146,7 @@ export function RibbonBar(props: RibbonBarProps) {
|
||||
<RibbonButton
|
||||
key={item.kind === "tool" ? `t:${item.id}` : `c:${item.name}`}
|
||||
item={item}
|
||||
activeTool={props.activeTool}
|
||||
activeCommand={props.activeCommand}
|
||||
toolsEnabled={props.toolsEnabled}
|
||||
onSelectTool={props.onSelectTool}
|
||||
onRunCommand={props.onRunCommand}
|
||||
ctx={ctx}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user