UI-Konsistenz: shared BarControls + Tabellen-Look fuer Panels
Pill-basierte Toolbar-Primitiven aus OberleisteApp extrahiert nach src/components/BarControls.jsx — BarCombo (Dropdown), BarButton (Icon-Button), BarToggle (Label/Icon mit Active-State), BAR_H=22. OberleisteApp nutzt jetzt die geteilten Komponenten (Verhalten unveraendert). EbenenManager + GeschossManager: - Sichtbarkeits-Toolbar: native <select> + btn-icon-sm → BarCombo (mit visibility-Icon links) + BarButton add/settings. - GeschossManager Stift-Icon (edit) → Settings-Icon. - Zeilen-Layout: eckig statt Pill (margin 0, borderRadius 0, 3px Accent-Strip links fuer aktive Zeile), minHeight 24, gap 4, kompaktere Padding/Icon-Sizes — Vectorworks-naeher. DimensionenApp: - Welt/CPlane: 2x BarToggle statt btn-contained/outlined - Z-Selektor: 3x BarToggle (icon-only) - Drehen-Apply + 90°-CCW/CW: BarButton mit rotate_*-Icons (4 Preset-Buttons -90/-45/45/90 ersetzt durch 2 schnelle 90°-Buttons — passt besser in die schmale Sidebar) README aktualisiert: Runtime jetzt CPython 3.9, TextEntity-RTF-Limit dokumentiert, BarControls + text_editor/text_create erwaehnt. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+6
-130
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import Icon from './components/Icon'
|
||||
import { BarCombo, BarButton, BAR_H } from './components/BarControls'
|
||||
import {
|
||||
onMessage, notifyReady,
|
||||
requestMassstab, setMassstab,
|
||||
@@ -73,7 +74,6 @@ function parseScale(input) {
|
||||
// zwischen Icon-Kompartiment und Inhalt.
|
||||
|
||||
const PILL_H = 20 // alte Pill-Hoehe (Buttons/Chips die nicht migriert sind)
|
||||
const BAR_H = 22 // neue Widget-Hoehe (BarSelect, BarButton, BarGroup)
|
||||
|
||||
const sep = {
|
||||
width: 1, height: 20,
|
||||
@@ -92,135 +92,9 @@ const pillSelect = {
|
||||
fontSize: 10,
|
||||
}
|
||||
|
||||
// BarCombo: dunklerer (bg-input) Pill-Container der select + optional gear
|
||||
// als EINE nahtlose Box rendert. Icon roh links daneben (kein Container).
|
||||
// iconClickable=true macht das Icon zum Toggle-Button (Overrides etc.).
|
||||
// valueAccent=true faerbt den Select-Text accent (fuer Massstab "gesetzt").
|
||||
function BarCombo({
|
||||
icon, iconActive, iconClickable, onIconClick, iconTitle,
|
||||
value, onChange, width, title, children, disabled,
|
||||
onGear, gearTitle, valueAccent,
|
||||
}) {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: 5,
|
||||
opacity: disabled ? 0.5 : 1, flexShrink: 0,
|
||||
}}>
|
||||
{/* Icon links — fixe Breite fuer X-Axis-Alignment zwischen Reihen.
|
||||
Wenn icon=null/undefined wird kein Icon-Slot reserviert. */}
|
||||
{icon && (iconClickable ? (
|
||||
<button onClick={onIconClick} title={iconTitle}
|
||||
style={{
|
||||
width: 18, height: BAR_H,
|
||||
background: 'transparent', border: 'none',
|
||||
cursor: 'pointer', flexShrink: 0, padding: 0,
|
||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<Icon name={icon} size={13}
|
||||
style={{ color: iconActive ? 'var(--accent)' : 'var(--text-muted)' }} />
|
||||
</button>
|
||||
) : (
|
||||
<span style={{
|
||||
width: 18, height: BAR_H, flexShrink: 0,
|
||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<Icon name={icon} size={13} style={{ color: 'var(--text-muted)' }} />
|
||||
</span>
|
||||
))}
|
||||
{/* Combined pill: select + optional gear, gemeinsamer bg + border */}
|
||||
<div title={title}
|
||||
onMouseEnter={(e) => {
|
||||
if (disabled) return
|
||||
e.currentTarget.style.borderColor = 'var(--accent)'
|
||||
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.borderColor = 'var(--border)'
|
||||
e.currentTarget.style.background = 'var(--bg-input)'
|
||||
}}
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'stretch',
|
||||
height: BAR_H + 2, width, boxSizing: 'border-box',
|
||||
background: 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 999,
|
||||
overflow: 'hidden',
|
||||
transition: 'border-color 0.15s, background 0.15s',
|
||||
}}>
|
||||
<select
|
||||
value={value || ''}
|
||||
disabled={disabled}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
style={{
|
||||
flex: 1, minWidth: 0,
|
||||
background: 'transparent',
|
||||
color: valueAccent ? 'var(--accent-light)' : 'var(--text-primary)',
|
||||
border: 'none', outline: 'none',
|
||||
padding: '0 22px 0 12px',
|
||||
fontSize: 11, fontFamily: 'var(--font)',
|
||||
fontWeight: valueAccent ? 600 : 500,
|
||||
appearance: 'none', WebkitAppearance: 'none',
|
||||
backgroundImage: 'var(--select-arrow)',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
// Caret-Position differenziert: ohne Gear normaler Abstand
|
||||
// (10px vom Pill-Rand), mit Gear minimaler Abstand damit
|
||||
// er an den Gear ranruckt.
|
||||
backgroundPosition: onGear ? 'right 1px center' : 'right 10px center',
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
letterSpacing: 0,
|
||||
}}
|
||||
>{children}</select>
|
||||
{onGear && (
|
||||
<button onClick={onGear} title={gearTitle}
|
||||
style={{
|
||||
background: 'transparent', border: 'none',
|
||||
padding: '0 8px', cursor: 'pointer',
|
||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<Icon name="settings" size={12}
|
||||
style={{ color: 'var(--text-muted)' }} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
// BarCombo + BarButton + BAR_H jetzt zentral in ./components/BarControls.jsx —
|
||||
// werden auch in Ebenen/anderen Panels verwendet.
|
||||
|
||||
// BarButton: pill-foermiger Icon-Button im selben Stil wie BarSelect.
|
||||
// joinedLeft = linke Kante flach (dockt rechts an einen BarSelect-joinedRight).
|
||||
function BarButton({ icon, onClick, title, disabled, active, joinedLeft }) {
|
||||
return (
|
||||
<button onClick={onClick} disabled={disabled} title={title}
|
||||
onMouseEnter={(e) => {
|
||||
if (disabled || active) return
|
||||
e.currentTarget.style.borderColor = 'var(--accent)'
|
||||
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (active) return
|
||||
e.currentTarget.style.borderColor = 'var(--border)'
|
||||
e.currentTarget.style.background = 'var(--bg-input)'
|
||||
}}
|
||||
style={{
|
||||
height: BAR_H, width: BAR_H,
|
||||
background: active ? 'var(--accent)' : 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
borderTopLeftRadius: joinedLeft ? 0 : 999,
|
||||
borderBottomLeftRadius: joinedLeft ? 0 : 999,
|
||||
borderTopRightRadius: 999, borderBottomRightRadius: 999,
|
||||
borderLeft: joinedLeft ? 'none' : '1px solid var(--border)',
|
||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
opacity: disabled ? 0.5 : 1, flexShrink: 0,
|
||||
padding: 0,
|
||||
transition: 'border-color 0.15s, background 0.15s',
|
||||
}}>
|
||||
<Icon name={icon} size={13}
|
||||
style={{ color: active ? 'var(--bg-panel)' : 'var(--text-muted)' }} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
const pillInput = {
|
||||
height: PILL_H, lineHeight: PILL_H + 'px',
|
||||
padding: '0 8px', boxSizing: 'border-box',
|
||||
@@ -796,7 +670,9 @@ export default function OberleisteApp() {
|
||||
const ts = sel || state.textSettings || {}
|
||||
const fonts = state.textFonts || []
|
||||
const styles = state.textStyles || []
|
||||
const activeStyleId = state.textStyleActiveId
|
||||
// Bei Selektion: Style-ID vom Text selber (falls per apply_style gesetzt),
|
||||
// sonst auf globalen Active-Style fallen
|
||||
const activeStyleId = (sel && sel.styleId) || state.textStyleActiveId
|
||||
const updateTs = (patch) => setTextSettings({ ...ts, ...patch })
|
||||
const STYLE_W = 110
|
||||
const FONT_W = 130
|
||||
|
||||
Reference in New Issue
Block a user