Tools + Elements: layout per display mode

- Symbol only ('icon'): uniform grid (square aligned tiles)
- Text / Symbol+Text: original ragged content-width pills that wrap freely
  (flex-wrap), like before the grid redesign
- Container switches grid↔flex-wrap on mode; pill switches full-width↔auto
- Dropdown picks the layout: density grid for icons, readable pills for text
This commit is contained in:
2026-06-06 15:34:07 +02:00
parent cd87a0b7f4
commit e8da519d29
2 changed files with 27 additions and 18 deletions
+14 -10
View File
@@ -29,8 +29,7 @@ function readTileMode() {
function writeTileMode(m) { function writeTileMode(m) {
try { localStorage.setItem(TILE_MODE_KEY, m) } catch { /* WebView ohne Storage */ } try { localStorage.setItem(TILE_MODE_KEY, m) } catch { /* WebView ohne Storage */ }
} }
const TILE_MIN_COL = { icon: 40, text: 64, both: 86 } const ICON_GRID_MIN = 42 // min Spaltenbreite im reinen Symbol-Raster
const TILE_MIN_H = { icon: 32, text: 30, both: 32 }
function TileModeDropdown({ mode, onChange }) { function TileModeDropdown({ mode, onChange }) {
return ( return (
@@ -82,6 +81,7 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
hasMenu, badge, mode = 'both' }) { hasMenu, badge, mode = 'both' }) {
const showIcon = mode !== 'text' const showIcon = mode !== 'text'
const showLabel = mode !== 'icon' const showLabel = mode !== 'icon'
const isGrid = mode === 'icon' // reines Symbol = gleichmaessiges Raster
return ( return (
<button <button
onClick={onClick} onClick={onClick}
@@ -91,10 +91,10 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
style={{ style={{
display: 'flex', flexDirection: 'row', display: 'flex', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: mode === 'both' ? 'flex-start' : 'center', justifyContent: isGrid ? 'center' : 'flex-start',
gap: showIcon && showLabel ? 7 : 0, gap: showIcon && showLabel ? 7 : 0,
padding: mode === 'icon' ? '6px' : '5px 11px', padding: isGrid ? '7px' : '5px 11px',
minHeight: TILE_MIN_H[mode], minHeight: isGrid ? 34 : 30,
background: 'var(--bg-input)', background: 'var(--bg-input)',
border: '1px solid var(--border-light)', border: '1px solid var(--border-light)',
borderRadius: 999, borderRadius: 999,
@@ -104,7 +104,8 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
fontSize: 11, fontWeight: 500, fontSize: 11, fontWeight: 500,
color: 'var(--text-primary)', color: 'var(--text-primary)',
position: 'relative', position: 'relative',
width: '100%', width: isGrid ? '100%' : 'auto',
whiteSpace: 'nowrap',
appearance: 'none', WebkitAppearance: 'none', appearance: 'none', WebkitAppearance: 'none',
}} }}
onMouseEnter={(e) => { if (!disabled) { onMouseEnter={(e) => { if (!disabled) {
@@ -117,7 +118,7 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
}} }}
> >
{showIcon && ( {showIcon && (
<Icon name={icon} size={16} <Icon name={icon} size={isGrid ? 18 : 16}
style={{ color: 'var(--accent)', flexShrink: 0 }} /> style={{ color: 'var(--accent)', flexShrink: 0 }} />
)} )}
{showLabel && ( {showLabel && (
@@ -150,8 +151,9 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
) )
} }
// Kategorie-Gruppe: Label + einheitliches Raster (auto-fill Spalten) // Kategorie-Gruppe: Symbol-Modus = Raster, sonst frei umbrechende Pills
function PillGroup({ label, mode = 'both', children }) { function PillGroup({ label, mode = 'both', children }) {
const isGrid = mode === 'icon'
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
<span style={{ <span style={{
@@ -161,10 +163,12 @@ function PillGroup({ label, mode = 'both', children }) {
}}> }}>
{label} {label}
</span> </span>
<div style={{ <div style={isGrid ? {
display: 'grid', display: 'grid',
gridTemplateColumns: `repeat(auto-fill, minmax(${TILE_MIN_COL[mode]}px, 1fr))`, gridTemplateColumns: `repeat(auto-fill, minmax(${ICON_GRID_MIN}px, 1fr))`,
gap: 5, gap: 5,
} : {
display: 'flex', flexWrap: 'wrap', gap: 5,
}}> }}>
{children} {children}
</div> </div>
+13 -8
View File
@@ -12,8 +12,7 @@ function readTileMode() {
function writeTileMode(m) { function writeTileMode(m) {
try { localStorage.setItem(TILE_MODE_KEY, m) } catch { /* WebView ohne Storage */ } try { localStorage.setItem(TILE_MODE_KEY, m) } catch { /* WebView ohne Storage */ }
} }
const TILE_MIN_COL = { icon: 40, text: 64, both: 86 } const ICON_GRID_MIN = 42 // min Spaltenbreite im reinen Symbol-Raster
const TILE_MIN_H = { icon: 32, text: 30, both: 32 }
// Tool-Definitionen: [icon, label, rhino-command, tooltip] // Tool-Definitionen: [icon, label, rhino-command, tooltip]
// Material-Symbol-Namen siehe https://fonts.google.com/icons // Material-Symbol-Namen siehe https://fonts.google.com/icons
@@ -68,6 +67,7 @@ const TOOLS = {
function ToolTile({ icon, label, cmd, tip, mode }) { function ToolTile({ icon, label, cmd, tip, mode }) {
const showIcon = mode !== 'text' const showIcon = mode !== 'text'
const showLabel = mode !== 'icon' const showLabel = mode !== 'icon'
const isGrid = mode === 'icon' // reines Symbol = gleichmaessiges Raster
return ( return (
<button <button
onClick={() => runRhinoCommand(cmd)} onClick={() => runRhinoCommand(cmd)}
@@ -83,10 +83,11 @@ function ToolTile({ icon, label, cmd, tip, mode }) {
style={{ style={{
display: 'flex', flexDirection: 'row', display: 'flex', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: mode === 'both' ? 'flex-start' : 'center', justifyContent: isGrid ? 'center' : 'flex-start',
gap: showIcon && showLabel ? 7 : 0, gap: showIcon && showLabel ? 7 : 0,
padding: mode === 'icon' ? '6px' : '5px 11px', padding: isGrid ? '7px' : '5px 11px',
minHeight: TILE_MIN_H[mode], minHeight: isGrid ? 34 : 30,
width: isGrid ? '100%' : 'auto',
background: 'var(--bg-input)', background: 'var(--bg-input)',
border: '1px solid var(--border-light)', border: '1px solid var(--border-light)',
borderRadius: 999, borderRadius: 999,
@@ -94,11 +95,12 @@ function ToolTile({ icon, label, cmd, tip, mode }) {
transition: 'background 0.12s, border-color 0.12s', transition: 'background 0.12s, border-color 0.12s',
fontSize: 11, fontWeight: 500, fontSize: 11, fontWeight: 500,
color: 'var(--text-primary)', color: 'var(--text-primary)',
whiteSpace: 'nowrap',
appearance: 'none', WebkitAppearance: 'none', appearance: 'none', WebkitAppearance: 'none',
}} }}
> >
{showIcon && ( {showIcon && (
<Icon name={icon} size={16} <Icon name={icon} size={isGrid ? 18 : 16}
style={{ color: 'var(--accent)', flexShrink: 0 }} /> style={{ color: 'var(--accent)', flexShrink: 0 }} />
)} )}
{showLabel && ( {showLabel && (
@@ -112,6 +114,7 @@ function ToolTile({ icon, label, cmd, tip, mode }) {
} }
function GridSection({ label, mode, children }) { function GridSection({ label, mode, children }) {
const isGrid = mode === 'icon'
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
<span style={{ <span style={{
@@ -121,10 +124,12 @@ function GridSection({ label, mode, children }) {
}}> }}>
{label} {label}
</span> </span>
<div style={{ <div style={isGrid ? {
display: 'grid', display: 'grid',
gridTemplateColumns: `repeat(auto-fill, minmax(${TILE_MIN_COL[mode]}px, 1fr))`, gridTemplateColumns: `repeat(auto-fill, minmax(${ICON_GRID_MIN}px, 1fr))`,
gap: 5, gap: 5,
} : {
display: 'flex', flexWrap: 'wrap', gap: 5,
}}> }}>
{children} {children}
</div> </div>