diff --git a/src/panels/PanelIcons.tsx b/src/panels/PanelIcons.tsx
new file mode 100644
index 0000000..df61a43
--- /dev/null
+++ b/src/panels/PanelIcons.tsx
@@ -0,0 +1,99 @@
+// Tab-Symbole der eingebauten Dock-Panels.
+//
+// Gleiche Konvention wie ToolIcon (ToolsPanel.tsx) und EyeIcon (ui/EyeIcon.tsx):
+// schlichte Inline-SVG-Glyphen, ~16×16, stroke=currentColor, damit das Icon die
+// Textfarbe des Tabs (inaktiv/hover/aktiv) übernimmt. Keine echten Asset-Icons
+// vorhanden — daher hier je Panel eine eigene kleine Glyphe.
+//
+// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
+
+const common = {
+ width: 16,
+ height: 16,
+ viewBox: "0 0 16 16",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: 1.4,
+ strokeLinecap: "round" as const,
+ strokeLinejoin: "round" as const,
+ "aria-hidden": true,
+ focusable: false,
+};
+
+/** Werkzeuge — Palette (2×2-Raster). */
+export function ToolsTabIcon() {
+ return (
+
+ );
+}
+
+/** Attribute — Federkiel (Stift/Füllung/Strichstärke der Auswahl). */
+export function AttributesTabIcon() {
+ return (
+
+ );
+}
+
+/** Objekt-Info — umkreistes „i". */
+export function ObjectInfoTabIcon() {
+ return (
+
+ );
+}
+
+/** Zeichnungsebenen — gestapelte Geschosse (drei Bodenplatten). */
+export function DrawingLevelsTabIcon() {
+ return (
+
+ );
+}
+
+/** Ebenen — überlappende Kategorie-Blätter. */
+export function LayersTabIcon() {
+ return (
+
+ );
+}
+
+/** Gelände — Höhenlinien/Terrain. */
+export function SiteTabIcon() {
+ return (
+
+ );
+}
+
+/** Flächenbilanz — Balken unterschiedlicher Höhe (SIA-416-Auswertung). */
+export function RoomBalanceTabIcon() {
+ return (
+
+ );
+}
diff --git a/src/panels/TabStrip.tsx b/src/panels/TabStrip.tsx
index 29b290c..579a215 100644
--- a/src/panels/TabStrip.tsx
+++ b/src/panels/TabStrip.tsx
@@ -1,4 +1,4 @@
-// Tab-Leiste eines Docks — ein Tab je Panel-ID, Titel aus der Registry.
+// Tab-Leiste eines Docks — ein Tab je Panel-ID, Icon+Titel aus der Registry.
//
// Rein darstellend: bekommt einen DockState (Reihenfolge der Panel-IDs +
// aktiver Tab) und meldet das Greifen eines Tabs über `onStartDrag`. Der
@@ -6,8 +6,12 @@
// Klick (Tab aktivieren) oder ein Ziehen (Reorder/Andocken/Lösen) wird — die
// Leiste selbst kennt diese Logik nicht.
//
-// Titel werden über die Registry (getPanel) aufgelöst, sodass die Leiste nichts
-// über konkrete Panels weiß. IDs ohne registriertes Panel (z. B. ein
+// Jeder Tab zeigt nur das Panel-Icon; der Titel steht als Tooltip (title-
+// Attribut) und aria-label am Button. Hat ein Panel kein Icon (z. B. ein
+// Plugin ohne eigenes Symbol), fällt der Tab auf den Titeltext zurück.
+//
+// Icon+Titel werden über die Registry (getPanel) aufgelöst, sodass die Leiste
+// nichts über konkrete Panels weiß. IDs ohne registriertes Panel (z. B. ein
// abgeschaltetes Plugin) werden übersprungen — vgl. Hinweis in layout.ts.
//
// Datenattribute für die Drag-Trefferprüfung (panelDrag.resolveHover):
@@ -64,12 +68,13 @@ export function TabStrip({ side, groupIndex, group, onStartDrag }: TabStripProps
type="button"
role="tab"
aria-selected={active}
+ aria-label={title}
title={title}
data-tab-index={index}
className={`tab${active ? " active" : ""}`}
onPointerDown={(e) => onStartDrag(id, e)}
>
- {title}
+ {def.icon ?? title}
);
})}
diff --git a/src/panels/builtinPanels.tsx b/src/panels/builtinPanels.tsx
index 617626c..5885aa4 100644
--- a/src/panels/builtinPanels.tsx
+++ b/src/panels/builtinPanels.tsx
@@ -22,6 +22,15 @@ import { AttributesPanel } from "./AttributesPanel";
import { ObjectInfoPanel } from "./ObjectInfoPanel";
import { SitePanel } from "./SitePanel";
import { RoomBalancePanel } from "./RoomBalancePanel";
+import {
+ ToolsTabIcon,
+ AttributesTabIcon,
+ ObjectInfoTabIcon,
+ DrawingLevelsTabIcon,
+ LayersTabIcon,
+ SiteTabIcon,
+ RoomBalanceTabIcon,
+} from "./PanelIcons";
/** IDs der eingebauten Dock-Panels (auch für defaultLayout/Filter nutzbar). */
export const BUILTIN_PANEL_IDS = {
@@ -38,6 +47,7 @@ export const BUILTIN_PANEL_IDS = {
registerPanel({
id: BUILTIN_PANEL_IDS.tools,
title: "nav.tools",
+ icon: ,
hasDisplayMode: false,
render: () => ,
});
@@ -46,6 +56,7 @@ registerPanel({
registerPanel({
id: BUILTIN_PANEL_IDS.attributes,
title: "nav.attributes",
+ icon: ,
hasDisplayMode: false,
render: () => ,
});
@@ -54,6 +65,7 @@ registerPanel({
registerPanel({
id: BUILTIN_PANEL_IDS.objectInfo,
title: "nav.objectInfo",
+ icon: ,
hasDisplayMode: false,
render: () => ,
});
@@ -63,6 +75,7 @@ registerPanel({
// i18n-Key statt fertigem Text: TabStrip/PanelFrame lösen ihn über t() auf,
// damit der Titel der gewählten Sprache folgt.
title: "nav.drawingLevels",
+ icon: ,
hasDisplayMode: true,
render: () => ,
});
@@ -70,6 +83,7 @@ registerPanel({
registerPanel({
id: BUILTIN_PANEL_IDS.layers,
title: "nav.layers",
+ icon: ,
hasDisplayMode: true,
render: () => ,
});
@@ -78,6 +92,7 @@ registerPanel({
registerPanel({
id: BUILTIN_PANEL_IDS.site,
title: "nav.site",
+ icon: ,
hasDisplayMode: false,
render: () => ,
});
@@ -86,6 +101,7 @@ registerPanel({
registerPanel({
id: BUILTIN_PANEL_IDS.roomBalance,
title: "nav.roomBalance",
+ icon: ,
hasDisplayMode: false,
render: () => ,
});
diff --git a/src/panels/types.ts b/src/panels/types.ts
index 439ae5e..89c6d1e 100644
--- a/src/panels/types.ts
+++ b/src/panels/types.ts
@@ -136,6 +136,14 @@ export interface PanelDef {
* unverändert zurück (Plugins können also auch einen fertigen Text setzen).
*/
title: string;
+ /**
+ * Symbol für den Tab in der TabStrip (der Titeltext erscheint dort nur noch
+ * als Tooltip). Erwartet eine kleine Inline-SVG (~16×16, stroke=currentColor),
+ * damit sie die Textfarbe des Tabs (inaktiv/hover/aktiv) übernimmt. Fehlt es
+ * (z. B. bei einem Plugin-Panel ohne eigenes Icon), fällt die TabStrip auf den
+ * Titeltext zurück.
+ */
+ icon?: ReactNode;
/** Rendert den Panel-Inhalt. Erhält den (generischen) PanelContext. */
render: (ctx: PanelContext) => ReactNode;
/**
diff --git a/src/styles.css b/src/styles.css
index 115f780..0cf84b8 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -2402,23 +2402,24 @@ body {
.tab {
flex: 0 0 auto;
- max-width: 160px;
+ width: 34px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
border: 1px solid transparent;
border-bottom: none;
background: transparent;
color: var(--muted);
font-family: inherit;
- font-size: 12px;
- font-weight: 600;
- letter-spacing: 0.03em;
- padding: 8px 14px;
+ padding: 0;
border-radius: 8px 8px 0 0;
cursor: pointer;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
transition: background 0.14s, color 0.14s, border-color 0.14s;
}
+.tab svg {
+ flex: 0 0 auto;
+}
.tab:hover {
background: var(--accent-dim);
color: var(--ink);