Dockbare Panel-Tabs nur als Symbol
Die Tab-Leiste zeigt je Panel nur noch ein Inline-SVG-Icon; der Titel wandert in Tooltip (title) und aria-label. PanelDef bekommt ein optionales icon-Feld, die sieben eingebauten Panels je ein schlichtes stroke-Icon (currentColor, erbt die Tab-Farbe). Fehlt ein Icon (Plugin), fällt der Tab auf den Titeltext zurück. Tabs sind jetzt kompakt und quadratisch; Klick-, Drag- und Dock-Logik unverändert.
This commit is contained in:
@@ -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 (
|
||||||
|
<svg {...common}>
|
||||||
|
<rect x="2" y="2" width="5" height="5" />
|
||||||
|
<rect x="9" y="2" width="5" height="5" />
|
||||||
|
<rect x="2" y="9" width="5" height="5" />
|
||||||
|
<rect x="9" y="9" width="5" height="5" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attribute — Federkiel (Stift/Füllung/Strichstärke der Auswahl). */
|
||||||
|
export function AttributesTabIcon() {
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<path d="M12.5 2.5 L14 4 L6 12 L3 13 L4 10 Z" />
|
||||||
|
<line x1="9.5" y1="5.5" x2="11.5" y2="7.5" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Objekt-Info — umkreistes „i". */
|
||||||
|
export function ObjectInfoTabIcon() {
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<circle cx="8" cy="8" r="6" />
|
||||||
|
<line x1="8" y1="7" x2="8" y2="11.2" />
|
||||||
|
<circle cx="8" cy="4.8" r="0.15" fill="currentColor" stroke="none" />
|
||||||
|
<circle cx="8" cy="4.8" r="0.7" fill="currentColor" stroke="none" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Zeichnungsebenen — gestapelte Geschosse (drei Bodenplatten). */
|
||||||
|
export function DrawingLevelsTabIcon() {
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<line x1="2" y1="3" x2="14" y2="3" />
|
||||||
|
<line x1="2" y1="8" x2="14" y2="8" />
|
||||||
|
<line x1="2" y1="13" x2="14" y2="13" />
|
||||||
|
<line x1="4" y1="3" x2="4" y2="8" />
|
||||||
|
<line x1="12" y1="8" x2="12" y2="13" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ebenen — überlappende Kategorie-Blätter. */
|
||||||
|
export function LayersTabIcon() {
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<path d="M8 2.5 13.5 5.5 8 8.5 2.5 5.5 Z" />
|
||||||
|
<path d="M2.5 8.5 8 11.5 13.5 8.5" />
|
||||||
|
<path d="M2.5 11 8 14 13.5 11" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gelände — Höhenlinien/Terrain. */
|
||||||
|
export function SiteTabIcon() {
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<path d="M1.5 12.5 5 6 7.5 10 10 5.5 14.5 12.5 Z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Flächenbilanz — Balken unterschiedlicher Höhe (SIA-416-Auswertung). */
|
||||||
|
export function RoomBalanceTabIcon() {
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<line x1="3" y1="13.5" x2="3" y2="7" />
|
||||||
|
<line x1="8" y1="13.5" x2="8" y2="2.5" />
|
||||||
|
<line x1="13" y1="13.5" x2="13" y2="9.5" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 +
|
// Rein darstellend: bekommt einen DockState (Reihenfolge der Panel-IDs +
|
||||||
// aktiver Tab) und meldet das Greifen eines Tabs über `onStartDrag`. Der
|
// 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
|
// Klick (Tab aktivieren) oder ein Ziehen (Reorder/Andocken/Lösen) wird — die
|
||||||
// Leiste selbst kennt diese Logik nicht.
|
// Leiste selbst kennt diese Logik nicht.
|
||||||
//
|
//
|
||||||
// Titel werden über die Registry (getPanel) aufgelöst, sodass die Leiste nichts
|
// Jeder Tab zeigt nur das Panel-Icon; der Titel steht als Tooltip (title-
|
||||||
// über konkrete Panels weiß. IDs ohne registriertes Panel (z. B. ein
|
// 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.
|
// abgeschaltetes Plugin) werden übersprungen — vgl. Hinweis in layout.ts.
|
||||||
//
|
//
|
||||||
// Datenattribute für die Drag-Trefferprüfung (panelDrag.resolveHover):
|
// Datenattribute für die Drag-Trefferprüfung (panelDrag.resolveHover):
|
||||||
@@ -64,12 +68,13 @@ export function TabStrip({ side, groupIndex, group, onStartDrag }: TabStripProps
|
|||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
aria-selected={active}
|
aria-selected={active}
|
||||||
|
aria-label={title}
|
||||||
title={title}
|
title={title}
|
||||||
data-tab-index={index}
|
data-tab-index={index}
|
||||||
className={`tab${active ? " active" : ""}`}
|
className={`tab${active ? " active" : ""}`}
|
||||||
onPointerDown={(e) => onStartDrag(id, e)}
|
onPointerDown={(e) => onStartDrag(id, e)}
|
||||||
>
|
>
|
||||||
{title}
|
{def.icon ?? title}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ import { AttributesPanel } from "./AttributesPanel";
|
|||||||
import { ObjectInfoPanel } from "./ObjectInfoPanel";
|
import { ObjectInfoPanel } from "./ObjectInfoPanel";
|
||||||
import { SitePanel } from "./SitePanel";
|
import { SitePanel } from "./SitePanel";
|
||||||
import { RoomBalancePanel } from "./RoomBalancePanel";
|
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). */
|
/** IDs der eingebauten Dock-Panels (auch für defaultLayout/Filter nutzbar). */
|
||||||
export const BUILTIN_PANEL_IDS = {
|
export const BUILTIN_PANEL_IDS = {
|
||||||
@@ -38,6 +47,7 @@ export const BUILTIN_PANEL_IDS = {
|
|||||||
registerPanel({
|
registerPanel({
|
||||||
id: BUILTIN_PANEL_IDS.tools,
|
id: BUILTIN_PANEL_IDS.tools,
|
||||||
title: "nav.tools",
|
title: "nav.tools",
|
||||||
|
icon: <ToolsTabIcon />,
|
||||||
hasDisplayMode: false,
|
hasDisplayMode: false,
|
||||||
render: () => <ToolsPanel />,
|
render: () => <ToolsPanel />,
|
||||||
});
|
});
|
||||||
@@ -46,6 +56,7 @@ registerPanel({
|
|||||||
registerPanel({
|
registerPanel({
|
||||||
id: BUILTIN_PANEL_IDS.attributes,
|
id: BUILTIN_PANEL_IDS.attributes,
|
||||||
title: "nav.attributes",
|
title: "nav.attributes",
|
||||||
|
icon: <AttributesTabIcon />,
|
||||||
hasDisplayMode: false,
|
hasDisplayMode: false,
|
||||||
render: () => <AttributesPanel />,
|
render: () => <AttributesPanel />,
|
||||||
});
|
});
|
||||||
@@ -54,6 +65,7 @@ registerPanel({
|
|||||||
registerPanel({
|
registerPanel({
|
||||||
id: BUILTIN_PANEL_IDS.objectInfo,
|
id: BUILTIN_PANEL_IDS.objectInfo,
|
||||||
title: "nav.objectInfo",
|
title: "nav.objectInfo",
|
||||||
|
icon: <ObjectInfoTabIcon />,
|
||||||
hasDisplayMode: false,
|
hasDisplayMode: false,
|
||||||
render: () => <ObjectInfoPanel />,
|
render: () => <ObjectInfoPanel />,
|
||||||
});
|
});
|
||||||
@@ -63,6 +75,7 @@ registerPanel({
|
|||||||
// i18n-Key statt fertigem Text: TabStrip/PanelFrame lösen ihn über t() auf,
|
// i18n-Key statt fertigem Text: TabStrip/PanelFrame lösen ihn über t() auf,
|
||||||
// damit der Titel der gewählten Sprache folgt.
|
// damit der Titel der gewählten Sprache folgt.
|
||||||
title: "nav.drawingLevels",
|
title: "nav.drawingLevels",
|
||||||
|
icon: <DrawingLevelsTabIcon />,
|
||||||
hasDisplayMode: true,
|
hasDisplayMode: true,
|
||||||
render: () => <DrawingLevelsPanel />,
|
render: () => <DrawingLevelsPanel />,
|
||||||
});
|
});
|
||||||
@@ -70,6 +83,7 @@ registerPanel({
|
|||||||
registerPanel({
|
registerPanel({
|
||||||
id: BUILTIN_PANEL_IDS.layers,
|
id: BUILTIN_PANEL_IDS.layers,
|
||||||
title: "nav.layers",
|
title: "nav.layers",
|
||||||
|
icon: <LayersTabIcon />,
|
||||||
hasDisplayMode: true,
|
hasDisplayMode: true,
|
||||||
render: () => <LayersPanel />,
|
render: () => <LayersPanel />,
|
||||||
});
|
});
|
||||||
@@ -78,6 +92,7 @@ registerPanel({
|
|||||||
registerPanel({
|
registerPanel({
|
||||||
id: BUILTIN_PANEL_IDS.site,
|
id: BUILTIN_PANEL_IDS.site,
|
||||||
title: "nav.site",
|
title: "nav.site",
|
||||||
|
icon: <SiteTabIcon />,
|
||||||
hasDisplayMode: false,
|
hasDisplayMode: false,
|
||||||
render: () => <SitePanel />,
|
render: () => <SitePanel />,
|
||||||
});
|
});
|
||||||
@@ -86,6 +101,7 @@ registerPanel({
|
|||||||
registerPanel({
|
registerPanel({
|
||||||
id: BUILTIN_PANEL_IDS.roomBalance,
|
id: BUILTIN_PANEL_IDS.roomBalance,
|
||||||
title: "nav.roomBalance",
|
title: "nav.roomBalance",
|
||||||
|
icon: <RoomBalanceTabIcon />,
|
||||||
hasDisplayMode: false,
|
hasDisplayMode: false,
|
||||||
render: () => <RoomBalancePanel />,
|
render: () => <RoomBalancePanel />,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -136,6 +136,14 @@ export interface PanelDef {
|
|||||||
* unverändert zurück (Plugins können also auch einen fertigen Text setzen).
|
* unverändert zurück (Plugins können also auch einen fertigen Text setzen).
|
||||||
*/
|
*/
|
||||||
title: string;
|
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. */
|
/** Rendert den Panel-Inhalt. Erhält den (generischen) PanelContext. */
|
||||||
render: (ctx: PanelContext) => ReactNode;
|
render: (ctx: PanelContext) => ReactNode;
|
||||||
/**
|
/**
|
||||||
|
|||||||
+9
-8
@@ -2402,23 +2402,24 @@ body {
|
|||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
max-width: 160px;
|
width: 34px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 12px;
|
padding: 0;
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 0.03em;
|
|
||||||
padding: 8px 14px;
|
|
||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
transition: background 0.14s, color 0.14s, border-color 0.14s;
|
transition: background 0.14s, color 0.14s, border-color 0.14s;
|
||||||
}
|
}
|
||||||
|
.tab svg {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
.tab:hover {
|
.tab:hover {
|
||||||
background: var(--accent-dim);
|
background: var(--accent-dim);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
|
|||||||
Reference in New Issue
Block a user