Tools + Elements panels: ragged pills → uniform grid tiles

Both panels shared the same flex-wrap pill layout where each pill sized to
its content, producing a ragged, hard-to-scan right edge. Switch to a uniform
CSS grid (auto-fill, minmax(68px, 1fr)) of icon-over-label tiles:

- ToolsApp: ToolPill → ToolTile, PillGroup → GridSection
- ElementeApp: PillButton → vertical tile (keeps hasMenu chevron, badge as
  corner pill, disabled state); PillGroup → grid section
- Less chrome: borderless tiles on subtle --bg-item, accent border on hover
- Dropdown tiles (Treppe/Stütze/Träger) keep their relative-positioned
  wrapper so PopupMenu still anchors correctly
This commit is contained in:
2026-06-06 14:43:17 +02:00
parent b6ba83eb5d
commit e531217cb7
2 changed files with 63 additions and 41 deletions
+34 -20
View File
@@ -49,7 +49,8 @@ function ReferenzSelector({ value, onChange }) {
) )
} }
// Pill-Button — kompakt, Icon + Label horizontal, abgerundet // Raster-Kachel — Icon oben, Label darunter, einheitliche Zellgroesse.
// hasMenu zeigt ein kleines Chevron neben dem Label (Rechtsklick = Untertypen).
function PillButton({ icon, label, hint, onClick, onContextMenu, disabled, function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
hasMenu, badge }) { hasMenu, badge }) {
return ( return (
@@ -59,49 +60,58 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
disabled={disabled} disabled={disabled}
title={hint} title={hint}
style={{ style={{
display: 'flex', alignItems: 'center', gap: 6, display: 'flex', flexDirection: 'column',
padding: '5px 10px 5px 8px', alignItems: 'center', justifyContent: 'center', gap: 6,
background: 'var(--bg-input)', padding: '10px 4px',
border: '1px solid var(--border-light)', minHeight: 56,
borderRadius: 999, background: 'var(--bg-item)',
border: '1px solid transparent',
borderRadius: 10,
cursor: disabled ? 'not-allowed' : 'pointer', cursor: disabled ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.4 : 1, opacity: disabled ? 0.4 : 1,
transition: 'background 0.1s, border-color 0.1s', transition: 'background 0.12s, border-color 0.12s',
fontSize: 11, fontWeight: 500, fontSize: 10, fontWeight: 500,
color: 'var(--text-primary)', color: 'var(--text-primary)',
whiteSpace: 'nowrap',
position: 'relative', position: 'relative',
width: '100%',
appearance: 'none', WebkitAppearance: 'none',
}} }}
onMouseEnter={(e) => { if (!disabled) { onMouseEnter={(e) => { if (!disabled) {
e.currentTarget.style.background = 'var(--bg-item-hover)' e.currentTarget.style.background = 'var(--bg-item-hover)'
e.currentTarget.style.borderColor = 'var(--accent)' e.currentTarget.style.borderColor = 'var(--accent)'
}}} }}}
onMouseLeave={(e) => { onMouseLeave={(e) => {
e.currentTarget.style.background = 'var(--bg-input)' e.currentTarget.style.background = 'var(--bg-item)'
e.currentTarget.style.borderColor = 'var(--border-light)' e.currentTarget.style.borderColor = 'transparent'
}} }}
> >
<Icon name={icon} size={14} style={{ color: 'var(--accent)' }} /> <Icon name={icon} size={19} style={{ color: 'var(--accent)', flexShrink: 0 }} />
<span>{label}</span> <span style={{
display: 'flex', alignItems: 'center', gap: 2,
maxWidth: '100%', overflow: 'hidden',
textOverflow: 'ellipsis', whiteSpace: 'nowrap',
}}>
{label}
{hasMenu && ( {hasMenu && (
<Icon name="expand_more" size={12} <Icon name="expand_more" size={11}
style={{ color: 'var(--text-muted)', marginLeft: -2 }} /> style={{ color: 'var(--text-muted)', flexShrink: 0 }} />
)} )}
</span>
{badge && ( {badge && (
<span style={{ <span style={{
fontSize: 9, padding: '1px 5px', borderRadius: 8, position: 'absolute', top: 4, right: 4,
fontSize: 8, padding: '0px 4px', borderRadius: 8,
background: 'var(--bg-section)', color: 'var(--text-muted)', background: 'var(--bg-section)', color: 'var(--text-muted)',
marginLeft: 2,
}}>{badge}</span> }}>{badge}</span>
)} )}
</button> </button>
) )
} }
// Vertikale Kategorie-Gruppe mit Label + Pills, die wrappen // Kategorie-Gruppe: Label + einheitliches Raster (auto-fill Spalten)
function PillGroup({ label, children }) { function PillGroup({ label, children }) {
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ <span style={{
fontSize: 9, color: 'var(--text-muted)', fontSize: 9, color: 'var(--text-muted)',
letterSpacing: '0.08em', textTransform: 'uppercase', letterSpacing: '0.08em', textTransform: 'uppercase',
@@ -109,7 +119,11 @@ function PillGroup({ label, children }) {
}}> }}>
{label} {label}
</span> </span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}> <div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(68px, 1fr))',
gap: 6,
}}>
{children} {children}
</div> </div>
</div> </div>
+27 -19
View File
@@ -54,7 +54,7 @@ const TOOLS = {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function ToolPill({ icon, label, cmd, tip }) { function ToolTile({ icon, label, cmd, tip }) {
return ( return (
<button <button
onClick={() => runRhinoCommand(cmd)} onClick={() => runRhinoCommand(cmd)}
@@ -64,32 +64,36 @@ function ToolPill({ icon, label, cmd, tip }) {
e.currentTarget.style.background = 'var(--bg-item-hover)' e.currentTarget.style.background = 'var(--bg-item-hover)'
}} }}
onMouseLeave={(e) => { onMouseLeave={(e) => {
e.currentTarget.style.borderColor = 'var(--border-light)' e.currentTarget.style.borderColor = 'transparent'
e.currentTarget.style.background = 'var(--bg-input)' e.currentTarget.style.background = 'var(--bg-item)'
}} }}
style={{ style={{
display: 'flex', alignItems: 'center', gap: 6, display: 'flex', flexDirection: 'column',
padding: '5px 10px 5px 8px', alignItems: 'center', justifyContent: 'center', gap: 6,
background: 'var(--bg-input)', padding: '10px 4px',
border: '1px solid var(--border-light)', minHeight: 56,
borderRadius: 999, background: 'var(--bg-item)',
border: '1px solid transparent',
borderRadius: 10,
cursor: 'pointer', cursor: 'pointer',
transition: 'background 0.1s, border-color 0.1s', transition: 'background 0.12s, border-color 0.12s',
fontSize: 11, fontWeight: 500, fontSize: 10, fontWeight: 500,
color: 'var(--text-primary)', color: 'var(--text-primary)',
whiteSpace: 'nowrap',
appearance: 'none', WebkitAppearance: 'none', appearance: 'none', WebkitAppearance: 'none',
}} }}
> >
<Icon name={icon} size={14} style={{ color: 'var(--accent)', flexShrink: 0 }} /> <Icon name={icon} size={19} style={{ color: 'var(--accent)', flexShrink: 0 }} />
<span>{label}</span> <span style={{
maxWidth: '100%', overflow: 'hidden',
textOverflow: 'ellipsis', whiteSpace: 'nowrap',
}}>{label}</span>
</button> </button>
) )
} }
function PillGroup({ label, children }) { function GridSection({ label, children }) {
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ <span style={{
fontSize: 9, color: 'var(--text-muted)', fontSize: 9, color: 'var(--text-muted)',
letterSpacing: '0.08em', textTransform: 'uppercase', letterSpacing: '0.08em', textTransform: 'uppercase',
@@ -97,7 +101,11 @@ function PillGroup({ label, children }) {
}}> }}>
{label} {label}
</span> </span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}> <div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(68px, 1fr))',
gap: 6,
}}>
{children} {children}
</div> </div>
</div> </div>
@@ -122,11 +130,11 @@ export default function WerkzeugeApp() {
overflowY: 'auto', overflowX: 'hidden', overflowY: 'auto', overflowX: 'hidden',
}}> }}>
{groups.map(([title, items]) => ( {groups.map(([title, items]) => (
<PillGroup key={title} label={title}> <GridSection key={title} label={title}>
{items.map(([icon, label, cmd, tip]) => ( {items.map(([icon, label, cmd, tip]) => (
<ToolPill key={cmd} icon={icon} label={label} cmd={cmd} tip={tip} /> <ToolTile key={cmd} icon={icon} label={label} cmd={cmd} tip={tip} />
))} ))}
</PillGroup> </GridSection>
))} ))}
</div> </div>
) )