Tools + Elements: smaller tiles, display-mode dropdown, dark pill look

- Tiles shrunk (minHeight 46/38/30 per mode, tighter padding, smaller icons)
- Add display-mode dropdown (Symbol / Text / Symbol+Text) at top of each
  panel; choice persists in localStorage under 'dossier_tile_mode' and is
  shared between both panels
- Grid column min-width adapts per mode (icon 40 / text 66 / both 58)
- Restore dark pill look: --bg-input background + border-light, full
  border-radius — tiles read as distinct chips again
- Icon-only mode shows a small corner chevron on dropdown tiles
  (Treppe/Stuetze/Traeger) so the right-click menu stays discoverable
This commit is contained in:
2026-06-06 15:14:29 +02:00
parent e531217cb7
commit c0624c0a62
2 changed files with 152 additions and 68 deletions
+59 -21
View File
@@ -1,9 +1,20 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import Icon from './components/Icon'
import { notifyReady, runRhinoCommand } from './lib/rhinoBridge'
// Anzeige-Modus der Kacheln, geteilt mit Elemente-Panel via localStorage.
const TILE_MODE_KEY = 'dossier_tile_mode' // 'both' | 'icon' | 'text'
function readTileMode() {
try { return localStorage.getItem(TILE_MODE_KEY) || 'both' } catch { return 'both' }
}
function writeTileMode(m) {
try { localStorage.setItem(TILE_MODE_KEY, m) } catch { /* WebView ohne Storage */ }
}
const TILE_MIN_COL = { icon: 40, text: 66, both: 58 }
const TILE_MIN_H = { icon: 38, text: 30, both: 46 }
// Tool-Definitionen: [icon, label, rhino-command, tooltip]
// Material-Symbol-Namen siehe https://fonts.google.com/icons
const TOOLS = {
@@ -54,7 +65,9 @@ const TOOLS = {
// ---------------------------------------------------------------------------
function ToolTile({ icon, label, cmd, tip }) {
function ToolTile({ icon, label, cmd, tip, mode }) {
const showIcon = mode !== 'text'
const showLabel = mode !== 'icon'
return (
<button
onClick={() => runRhinoCommand(cmd)}
@@ -64,17 +77,17 @@ function ToolTile({ icon, label, cmd, tip }) {
e.currentTarget.style.background = 'var(--bg-item-hover)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = 'transparent'
e.currentTarget.style.background = 'var(--bg-item)'
e.currentTarget.style.borderColor = 'var(--border-light)'
e.currentTarget.style.background = 'var(--bg-input)'
}}
style={{
display: 'flex', flexDirection: 'column',
alignItems: 'center', justifyContent: 'center', gap: 6,
padding: '10px 4px',
minHeight: 56,
background: 'var(--bg-item)',
border: '1px solid transparent',
borderRadius: 10,
alignItems: 'center', justifyContent: 'center', gap: 4,
padding: '6px 4px',
minHeight: TILE_MIN_H[mode],
background: 'var(--bg-input)',
border: '1px solid var(--border-light)',
borderRadius: 999,
cursor: 'pointer',
transition: 'background 0.12s, border-color 0.12s',
fontSize: 10, fontWeight: 500,
@@ -82,18 +95,23 @@ function ToolTile({ icon, label, cmd, tip }) {
appearance: 'none', WebkitAppearance: 'none',
}}
>
<Icon name={icon} size={19} style={{ color: 'var(--accent)', flexShrink: 0 }} />
<span style={{
maxWidth: '100%', overflow: 'hidden',
textOverflow: 'ellipsis', whiteSpace: 'nowrap',
}}>{label}</span>
{showIcon && (
<Icon name={icon} size={mode === 'icon' ? 18 : 16}
style={{ color: 'var(--accent)', flexShrink: 0 }} />
)}
{showLabel && (
<span style={{
maxWidth: '100%', overflow: 'hidden',
textOverflow: 'ellipsis', whiteSpace: 'nowrap',
}}>{label}</span>
)}
</button>
)
}
function GridSection({ label, children }) {
function GridSection({ label, mode, children }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
<span style={{
fontSize: 9, color: 'var(--text-muted)',
letterSpacing: '0.08em', textTransform: 'uppercase',
@@ -103,8 +121,8 @@ function GridSection({ label, children }) {
</span>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(68px, 1fr))',
gap: 6,
gridTemplateColumns: `repeat(auto-fill, minmax(${TILE_MIN_COL[mode]}px, 1fr))`,
gap: 5,
}}>
{children}
</div>
@@ -112,11 +130,28 @@ function GridSection({ label, children }) {
)
}
function TileModeDropdown({ mode, onChange }) {
return (
<select
value={mode}
onChange={(e) => onChange(e.target.value)}
title="Anzeige: Symbol / Text / Symbol + Text"
style={{ fontSize: 10, padding: '3px 6px', maxWidth: 130 }}
>
<option value="icon">Symbol</option>
<option value="text">Text</option>
<option value="both">Symbol + Text</option>
</select>
)
}
// ---------------------------------------------------------------------------
export default function WerkzeugeApp() {
const [mode, setMode] = useState(readTileMode)
useEffect(() => { notifyReady() }, [])
const changeMode = (m) => { setMode(m); writeTileMode(m) }
const groups = Object.entries(TOOLS)
return (
@@ -129,10 +164,13 @@ export default function WerkzeugeApp() {
boxSizing: 'border-box',
overflowY: 'auto', overflowX: 'hidden',
}}>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<TileModeDropdown mode={mode} onChange={changeMode} />
</div>
{groups.map(([title, items]) => (
<GridSection key={title} label={title}>
<GridSection key={title} label={title} mode={mode}>
{items.map(([icon, label, cmd, tip]) => (
<ToolTile key={cmd} icon={icon} label={label} cmd={cmd} tip={tip} />
<ToolTile key={cmd} icon={icon} label={label} cmd={cmd} tip={tip} mode={mode} />
))}
</GridSection>
))}