Massstab: Pills gleich breit + Icon weg + weisse Texte + Hover-Akzent

User-Wunsch:
- Aktueller Massstab (live) + Zoom-% weiss, accent bei 100%
- Dropdown immer weiss (kein valueAccent mehr)
- Buttons unten wie View-Toggle (weiss default, gruen on hover)
- Massstabs-Icon weg, Pills (Dropdown + Buttons-Bar) gleich breit + exakt
  uebereinander

Aenderungen:
- BarCombo: Icon-Slot nur wenn `icon` truthy gerendert (kein leerer Slot)
- Massstab-Dropdown ohne icon-Prop → kein 18px Slot links, Pill sitzt
  direkt in der Grid-Zelle
- PILL_W=140 für Dropdown UND Buttons-Pill, jeder Button BTN_W=35
- statChipStyle: color text-primary (weiss) statt text-muted, accent-light
  nur bei atScale=true
- valueAccent prop entfernt aus Massstab-Dropdown
- SegBtn mit Hover-Logik analog View-Toggle: bg → bg-item-hover,
  color → accent-light, active bleibt accent-fill

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 23:40:45 +02:00
parent 3f5f48cb2c
commit e2c13e7844
+34 -18
View File
@@ -139,8 +139,9 @@ function BarCombo({
display: 'inline-flex', alignItems: 'center', gap: 5, display: 'inline-flex', alignItems: 'center', gap: 5,
opacity: disabled ? 0.5 : 1, flexShrink: 0, opacity: disabled ? 0.5 : 1, flexShrink: 0,
}}> }}>
{/* Icon links — fixe Breite fuer X-Axis-Alignment zwischen Reihen */} {/* Icon links — fixe Breite fuer X-Axis-Alignment zwischen Reihen.
{iconClickable ? ( Wenn icon=null/undefined wird kein Icon-Slot reserviert. */}
{icon && (iconClickable ? (
<button onClick={onIconClick} title={iconTitle} <button onClick={onIconClick} title={iconTitle}
style={{ style={{
width: 18, height: BAR_H, width: 18, height: BAR_H,
@@ -158,7 +159,7 @@ function BarCombo({
}}> }}>
<Icon name={icon} size={13} style={{ color: 'var(--text-muted)' }} /> <Icon name={icon} size={13} style={{ color: 'var(--text-muted)' }} />
</span> </span>
)} ))}
{/* Combined pill: select + optional gear, gemeinsamer bg + border */} {/* Combined pill: select + optional gear, gemeinsamer bg + border */}
<div title={title} <div title={title}
onMouseEnter={(e) => { onMouseEnter={(e) => {
@@ -601,20 +602,36 @@ export default function OberleisteApp() {
Reihe 2: [Zoom-Verhaeltnis %] [Buttons] Reihe 2: [Zoom-Verhaeltnis %] [Buttons]
*/} */}
{(() => { {(() => {
// Buttons-Pill: gleiche Logik wie View-Toggle (weiss default,
// grün on hover, accent-fill wenn active)
const PILL_W = 140 // Gleiche Breite fuer Dropdown + Buttons-Pill
const N_BTN = 4
const BTN_W = Math.floor(PILL_W / N_BTN) // jeder Button gleich breit
const SegBtn = ({ icon, onClick, title, disabled, active, isFirst, isLast }) => ( const SegBtn = ({ icon, onClick, title, disabled, active, isFirst, isLast }) => (
<button onClick={onClick} disabled={disabled} title={title} <button onClick={onClick} disabled={disabled} title={title}
onMouseEnter={(e) => {
if (disabled || active) return
e.currentTarget.style.background = 'var(--bg-item-hover)'
e.currentTarget.style.color = 'var(--accent-light)'
}}
onMouseLeave={(e) => {
if (active) return
e.currentTarget.style.background = 'var(--bg-input)'
e.currentTarget.style.color = 'var(--text-primary)'
}}
style={{ style={{
height: BAR_H, width: 30, height: BAR_H, width: BTN_W,
background: active ? 'var(--accent)' : 'var(--bg-input)', background: active ? 'var(--accent)' : 'var(--bg-input)',
color: active ? 'var(--bg-panel)' : 'var(--text-primary)',
border: 'none', border: 'none',
borderLeft: isFirst ? 'none' : '1px solid var(--border)', borderLeft: isFirst ? 'none' : '1px solid var(--border)',
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
cursor: disabled ? 'not-allowed' : 'pointer', cursor: disabled ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.4 : 1, flexShrink: 0, opacity: disabled ? 0.4 : 1, flexShrink: 0,
padding: 0, padding: 0,
transition: 'background 0.15s, color 0.15s',
}}> }}>
<Icon name={icon} size={13} <Icon name={icon} size={13} />
style={{ color: active ? 'var(--bg-panel)' : 'var(--text-muted)' }} />
</button> </button>
) )
const ratio = (!isPerspective && appliedScale && scaleVal) const ratio = (!isPerspective && appliedScale && scaleVal)
@@ -626,12 +643,12 @@ export default function OberleisteApp() {
? Math.round(ratio * 100) + '%' ? Math.round(ratio * 100) + '%'
: (ratio * 100).toFixed(ratio < 0.1 ? 1 : 0) + '%' : (ratio * 100).toFixed(ratio < 0.1 ? 1 : 0) + '%'
const atScale = ratio != null && Math.abs(ratio - 1) < 0.005 const atScale = ratio != null && Math.abs(ratio - 1) < 0.005
const STAT_W = 80 // Breite der linken Stat-Chips (1:N / %) const STAT_W = 70 // Breite der linken Stat-Chips
const statChipStyle = (accentTint) => ({ const statChipStyle = (accentTint) => ({
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
height: BAR_H, width: STAT_W, height: BAR_H, width: STAT_W,
background: accentTint ? 'var(--accent-dim)' : 'var(--bg-input)', background: accentTint ? 'var(--accent-dim)' : 'var(--bg-input)',
color: accentTint ? 'var(--accent-light)' : 'var(--text-muted)', color: accentTint ? 'var(--accent-light)' : 'var(--text-primary)',
border: '1px solid var(--border)', border: '1px solid var(--border)',
borderRadius: 999, borderRadius: 999,
fontFamily: 'DM Mono, monospace', fontSize: 11, fontFamily: 'DM Mono, monospace', fontSize: 11,
@@ -643,12 +660,13 @@ export default function OberleisteApp() {
display: 'grid', gridTemplateColumns: 'auto auto', gap: '4px 6px', display: 'grid', gridTemplateColumns: 'auto auto', gap: '4px 6px',
alignItems: 'center', flexShrink: 0, alignItems: 'center', flexShrink: 0,
}}> }}>
{/* Reihe 1, Spalte 1: Aktueller Live-Massstab */} {/* Reihe 1, Spalte 1: Aktueller Live-Massstab — weiss, accent bei 100% */}
<div style={statChipStyle(false)} <div style={statChipStyle(atScale)}
title={isPerspective ? 'Perspektive — kein Massstab' : 'Aktueller Live-Massstab'}> title={isPerspective ? 'Perspektive — kein Massstab' : 'Aktueller Live-Massstab'}>
{isPerspective ? '—' : fmtScale(scaleVal)} {isPerspective ? '—' : fmtScale(scaleVal)}
</div> </div>
{/* Reihe 1, Spalte 2: Gesetzter Massstab Dropdown */} {/* Reihe 1, Spalte 2: Gesetzter Massstab Dropdown — KEIN Icon, gleiche
Breite wie Buttons-Pill darunter, exakt uebereinander */}
{customMode ? ( {customMode ? (
<input <input
ref={customInputRef} ref={customInputRef}
@@ -662,7 +680,7 @@ export default function OberleisteApp() {
}} }}
onBlur={applyDraft} onBlur={applyDraft}
style={{ style={{
height: BAR_H, width: 158, height: BAR_H, width: PILL_W,
background: 'var(--bg-input)', background: 'var(--bg-input)',
color: 'var(--text-primary)', color: 'var(--text-primary)',
border: '1px solid var(--border)', border: '1px solid var(--border)',
@@ -675,12 +693,10 @@ export default function OberleisteApp() {
/> />
) : ( ) : (
<BarCombo <BarCombo
icon="straighten"
value={dropdownValue} value={dropdownValue}
onChange={(v) => applyDropdown(v)} onChange={(v) => applyDropdown(v)}
disabled={isPerspective} disabled={isPerspective}
width={140} width={PILL_W}
valueAccent={appliedScale != null}
title="Gesetzter Massstab" title="Gesetzter Massstab"
> >
<option value="__none__"></option> <option value="__none__"></option>
@@ -693,16 +709,16 @@ export default function OberleisteApp() {
<option value="__custom__">Eigener</option> <option value="__custom__">Eigener</option>
</BarCombo> </BarCombo>
)} )}
{/* Reihe 2, Spalte 1: Zoom-Verhaeltnis zum gesetzten Massstab */} {/* Reihe 2, Spalte 1: Zoom-Verhaeltnis */}
<div style={statChipStyle(atScale)} <div style={statChipStyle(atScale)}
title={ratio != null title={ratio != null
? `Aktueller Zoom = ${ratioText} des gesetzten Massstabs` ? `Aktueller Zoom = ${ratioText} des gesetzten Massstabs`
: (isPerspective ? 'Perspektive' : 'Kein Massstab gesetzt')}> : (isPerspective ? 'Perspektive' : 'Kein Massstab gesetzt')}>
{ratioText} {ratioText}
</div> </div>
{/* Reihe 2, Spalte 2: Buttons-Pill */} {/* Reihe 2, Spalte 2: Buttons-Pill — gleiche Breite wie Dropdown */}
<div style={{ <div style={{
display: 'inline-flex', display: 'inline-flex', width: PILL_W,
border: '1px solid var(--border)', borderRadius: 999, border: '1px solid var(--border)', borderRadius: 999,
overflow: 'hidden', flexShrink: 0, justifySelf: 'start', overflow: 'hidden', flexShrink: 0, justifySelf: 'start',
}}> }}>