diff --git a/docs/design/ribbon-ui-plan.md b/docs/design/ribbon-ui-plan.md
index 6aa25d0..326dcde 100644
--- a/docs/design/ribbon-ui-plan.md
+++ b/docs/design/ribbon-ui-plan.md
@@ -52,12 +52,15 @@ RIBBON: RibbonTab[]
Kamera, Ebenen-/Zeichnungs-Kombis, Detailgrad, Massstab/Zoom, Darstellungsart)
sind als `ViewRibbonTab` (in `TopBar.tsx`) in den „Ansichten"-Tab gewandert;
App reicht sie als `viewsContent`-Node an `RibbonBar` (wie das Layout-Menü).
- Die TopBar ist jetzt die **schmale globale Leiste**: Marke/Ressourcen ·
- Text-Formatierung · Datei/Export/Einstellungen · Fensterknöpfe.
- - **Offen (visuell iterieren):** Band-Höhe/Abstände des Ansichten-Tabs; ob
- Zoom/Massstab zusätzlich als Dauer-Anzeige (Statusleiste) sichtbar sein
- soll; Default-Tab (aktuell 2D); Text-Gruppe evtl. später eigener
- „Beschriften"-Tab; 3D-Tab noch leer (Kamera-Presets/3D-Aktionen füllen).
+ Die **Tab-Reiter sitzen in der TopBar-Zeile** (`RibbonTabs`, in TopBar
+ gerendert; Tab-State in App), der Ribbon-Inhalt (`RibbonBar`) folgt darunter →
+ nur ZWEI Bänder. Die TopBar ist eine **schmale Leiste** (Höhe 40px): Marke/
+ Ressourcen · Tabs · Datei/Export/Einstellungen · Fensterknöpfe. Die Text-/
+ Font-Formatierung (`TextGroup`) liegt im eigenen **„Text"-Tab** (Reihenfolge
+ 2D·3D·BIM·Text·Ansichten; `RibbonBar.tabContent` trägt „text" + „views").
+ - **Offen (visuell iterieren):** ob Zoom/Massstab zusätzlich als Dauer-Anzeige
+ (Statusleiste) sichtbar sein soll; Default-Tab (aktuell 2D); 3D-Tab noch
+ leer (Kamera-Presets/3D-Aktionen füllen).
3. **Sidebar raus** (ToolsPanel aus Default-Layout) + rechtes Panel volle Höhe;
Objektinfo unter Attribute mergen.
4. **Modulare Custom-Bar**: Item-Picker + persistierte Custom-Bar-Ansicht.
diff --git a/src/App.tsx b/src/App.tsx
index 3699dc0..7b370f6 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -69,7 +69,7 @@ import type { Pick3dHit } from "./viewport/Wasm3DViewport";
import { ResourceManager } from "./ui/ResourceManager";
import type { ResourceManagerHandlers } from "./ui/ResourceManager";
import { HatchSwatch } from "./ui/hatchPreview";
-import { TopBar, ViewRibbonTab } from "./ui/TopBar";
+import { TopBar, ViewRibbonTab, TextGroup } from "./ui/TopBar";
import { RibbonBar } from "./ui/ribbon/RibbonBar";
import type { RibbonTabId } from "./ui/ribbon/ribbonItems";
import { Dropdown } from "./ui/Dropdown";
@@ -3219,7 +3219,6 @@ export default function App() {
onApply={(l) => setLayout(reconcileLayout(l))}
/>
}
- textTarget={textTarget}
/>
{/* Ribbon-Oberleiste (2D · 3D · BIM · Ansichten). Der „Ansichten"-Tab trägt
@@ -3232,7 +3231,13 @@ export default function App() {
toolsEnabled={activeLevel.kind === "floor"}
onSelectTool={onSelectTool}
onRunCommand={onRunCommand}
- viewsContent={
+ tabContent={{
+ text: (
+
+
+
+ ),
+ views: (
- }
+ ),
+ }}
/>
diff --git a/src/i18n/de.ts b/src/i18n/de.ts
index a57b977..218d626 100644
--- a/src/i18n/de.ts
+++ b/src/i18n/de.ts
@@ -124,6 +124,7 @@ export const de = {
"ribbon.tab.2d": "2D",
"ribbon.tab.3d": "3D",
"ribbon.tab.bim": "BIM",
+ "ribbon.tab.text": "Text",
"ribbon.tab.views": "Ansichten",
"ribbon.group.draw": "Zeichnen",
"ribbon.group.modify": "Ändern",
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 58e2540..7936f2c 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -123,6 +123,7 @@ export const en: Record
= {
"ribbon.tab.2d": "2D",
"ribbon.tab.3d": "3D",
"ribbon.tab.bim": "BIM",
+ "ribbon.tab.text": "Text",
"ribbon.tab.views": "Views",
"ribbon.group.draw": "Draw",
"ribbon.group.modify": "Modify",
diff --git a/src/styles.css b/src/styles.css
index d8418a5..91dc6b5 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -158,8 +158,8 @@ body {
display: flex;
align-items: center;
gap: 10px;
- padding: 6px 14px;
- height: 60px;
+ padding: 0 14px;
+ height: 40px;
border-bottom: 1px solid var(--line);
/* Oberleiste auf Ebene 1 (angehoben über dem App-Grund) + weicher Schatten
nach unten → trennt sich klar von der Zeichenfläche (Tiefe). */
diff --git a/src/ui/TopBar.tsx b/src/ui/TopBar.tsx
index 6c34c24..a71e0a7 100644
--- a/src/ui/TopBar.tsx
+++ b/src/ui/TopBar.tsx
@@ -122,14 +122,8 @@ export interface TopBarProps {
/** Projekt aus einer Datei laden. */
onOpenProject: () => void;
- // ── Text-Gruppe (Stempel-/Text-Formatierung) ───────────────────────────────
- /**
- * Ziel der Text-Formatierung: `null` = nichts Text-artiges selektiert (die
- * Controls setzen nur Defaults für neuen Text). Sonst das aktuell selektierte
- * Rich-Text-Dokument (z. B. Raumstempel) samt optionalem Bereich und einem
- * Setter. Ist `range` `null`, wirken Aktionen auf das GANZE Doc.
- */
- textTarget: TextTarget | null;
+ // Die Text-/Font-Formatierung (TextGroup) liegt jetzt im „Text"-Ribbon-Tab
+ // (App reicht sie via RibbonBar.tabContent), nicht mehr in dieser Leiste.
}
/**
@@ -507,7 +501,7 @@ const TEXT_SIZES: number[] = [8, 9, 10, 11, 12, 14, 18, 24, 36, 48];
* `textTarget` gesetzt, tragen die Zeile-2-Pillen einen Akzent-Rand und alle
* Aktionen wirken auf `textTarget.doc` via `textTarget.apply`.
*/
-function TextGroup({
+export function TextGroup({
textTarget,
}: {
textTarget: TextTarget | null;
@@ -1218,7 +1212,6 @@ export function TopBar({
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
@@ -1268,19 +1261,12 @@ 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. */}
+ {/* Ribbon-Tab-Reiter (2D/3D/BIM/Text/Ansichten) — sitzen in dieser Zeile,
+ der Ribbon-Inhalt folgt darunter (RibbonBar). So gibt es nur ZWEI
+ Bänder: diese schmale Leiste (Chrome + Tabs) und den Inhalt. Die Text-/
+ Font-Formatierung liegt jetzt im „Text"-Tab (nicht mehr hier). */}
-
-
- {/* 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). */}
-
-
{/* Rechte Gruppe: Layout-Menü · Projektname · Datei-Burger (ganz rechts,
GTK-Stil) — Ressourcen sitzt jetzt bei der Marke (s. o.). */}
diff --git a/src/ui/ribbon/RibbonBar.tsx b/src/ui/ribbon/RibbonBar.tsx
index c1acd79..89bb242 100644
--- a/src/ui/ribbon/RibbonBar.tsx
+++ b/src/ui/ribbon/RibbonBar.tsx
@@ -35,11 +35,12 @@ export interface RibbonBarProps {
/** Engine-Befehl starten (Ändern-Gruppe u. a.). */
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. Fehlt er, zeigt der Tab den Leer-Hinweis.
+ * Reicher Custom-Inhalt je Tab (statt datengetriebener Item-Gruppen) — z. B.
+ * „Ansichten" (`ViewRibbonTab`) und „Text" (`TextGroup`). Von App geliefert,
+ * damit deren Setter/State dort bleiben. Ein Tab ohne Custom-Inhalt und ohne
+ * Gruppen zeigt den Leer-Hinweis.
*/
- viewsContent?: ReactNode;
+ tabContent?: Partial
>;
}
/** Aktivierungs-/Highlight-Kontext, den jeder Button-Typ braucht. */
@@ -121,6 +122,7 @@ export function RibbonTabs({
export function RibbonBar(props: RibbonBarProps) {
const current = RIBBON_TABS.find((tt) => tt.id === props.tab) ?? RIBBON_TABS[0];
+ const custom = props.tabContent?.[current.id];
const ctx: ButtonCtx = {
activeTool: props.activeTool,
activeCommand: props.activeCommand,
@@ -131,11 +133,11 @@ export function RibbonBar(props: RibbonBarProps) {
return (
- {/* Inhalt des aktiven Tabs. Der „Ansichten"-Tab trägt reichen Custom-Inhalt
- (ViewRibbonTab) statt der datengetriebenen Item-Gruppen. */}
+ {/* Inhalt des aktiven Tabs. Tabs mit reichem Custom-Inhalt („Ansichten",
+ „Text") rendern diesen statt der datengetriebenen Item-Gruppen. */}
- {current.id === "views" && props.viewsContent ? (
- props.viewsContent
+ {custom ? (
+ custom
) : current.groups.length === 0 ? (
{t("ribbon.empty")}
) : (
diff --git a/src/ui/ribbon/ribbonItems.ts b/src/ui/ribbon/ribbonItems.ts
index 7a37df0..50c8b5e 100644
--- a/src/ui/ribbon/ribbonItems.ts
+++ b/src/ui/ribbon/ribbonItems.ts
@@ -28,7 +28,7 @@ export interface RibbonGroup {
}
/** Kennung eines Ribbon-Tabs (fest, für Tab-State + i18n). */
-export type RibbonTabId = "2d" | "3d" | "bim" | "views";
+export type RibbonTabId = "2d" | "3d" | "bim" | "text" | "views";
/** Ein Ribbon-Tab: Beschriftung + gruppierte Elemente. */
export interface RibbonTab {
@@ -97,6 +97,12 @@ export const RIBBON_TABS: RibbonTab[] = [
},
],
},
+ {
+ // Custom-Inhalt: die Text-/Font-Formatierung (TextGroup), via tabContent.
+ id: "text",
+ labelKey: "ribbon.tab.text",
+ groups: [],
+ },
{
id: "views",
labelKey: "ribbon.tab.views",