Hold the toolbar's widths still while stepping and editing
The stepper's middle is sized to the axis's longest level rather than the current one, and Save and the dirty dot keep their slots while hidden, so nothing shifts under the cursor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
45e45bfa60
commit
b8b5220583
@ -35,6 +35,9 @@ export default function DepthButtons({ label, levels, depth, onPick, totalLabel
|
||||
const known = depth != null
|
||||
const current = !known ? '—' : (depth === 0 ? totalLabel : levels[depth - 1])
|
||||
const step = (d) => onPick(Math.min(max, Math.max(0, d)))
|
||||
// Capped so one long field name cannot push the rest of the toolbar off;
|
||||
// past the cap the label truncates, with the full name in the title.
|
||||
const widest = Math.min(18, Math.max(...[totalLabel, ...levels].map(l => String(l).length)))
|
||||
|
||||
// One joined control, in the style of the operation panel's
|
||||
// Scale | Recode | Clone. The arrows used to be separate outlined boxes a
|
||||
@ -53,9 +56,13 @@ export default function DepthButtons({ label, levels, depth, onPick, totalLabel
|
||||
title={`Collapse ${label.toLowerCase()} by one level`}
|
||||
onClick={() => step((depth ?? 0) - 1)}>◂</button>
|
||||
|
||||
{/* Sized to the axis's longest level, not the current one. Sized to
|
||||
its content it resized on every step, so the ▸ moved out from
|
||||
under the cursor between clicks. */}
|
||||
<button onClick={() => setOpen(o => !o)}
|
||||
title={`${label}: showing ${current}`}
|
||||
className={`px-2 py-0.5 max-w-[10rem] truncate transition-colors
|
||||
style={{ width: `calc(${widest}ch + 1rem)` }}
|
||||
className={`px-2 py-0.5 truncate text-left transition-colors
|
||||
${open ? 'bg-blue-50 text-blue-700' : 'bg-white text-gray-700 hover:bg-gray-100'}`}>
|
||||
{current}
|
||||
</button>
|
||||
|
||||
@ -123,15 +123,22 @@ export default function LayoutMenu({
|
||||
${open ? 'border-blue-300 text-blue-700 bg-blue-50'
|
||||
: 'border-gray-200 text-gray-600 hover:border-gray-400'}`}>
|
||||
<span className="truncate">{active ? active.name : 'Unsaved'}</span>
|
||||
{dirty && <span title="Changed since it was saved" className="text-amber-500 leading-none">•</span>}
|
||||
{/* Always rendered, hidden when clean, so the button does not widen
|
||||
by a dot the moment the pivot changes. */}
|
||||
<span title={dirty ? 'Changed since it was saved' : undefined}
|
||||
className={`text-amber-500 leading-none ${dirty ? '' : 'invisible'}`}>•</span>
|
||||
<span className="text-gray-400 leading-none">▾</span>
|
||||
</button>
|
||||
|
||||
{/* Save is the common action and stays outside the menu, but only when
|
||||
there is something to save it to and it is yours to overwrite. */}
|
||||
{active?.can_edit && dirty && (
|
||||
<button onClick={() => onSave(active)}
|
||||
className="border border-blue-200 text-blue-600 hover:text-blue-800 rounded px-2 py-0.5">
|
||||
there is something to save it to and it is yours to overwrite. Its
|
||||
slot is held while the layout is clean: appearing and vanishing, it
|
||||
shoved the Rows and Columns controls sideways under the cursor. */}
|
||||
{active?.can_edit && (
|
||||
<button onClick={() => onSave(active)} disabled={!dirty}
|
||||
aria-hidden={!dirty} tabIndex={dirty ? 0 : -1}
|
||||
className={`border border-blue-200 text-blue-600 hover:text-blue-800 rounded px-2 py-0.5
|
||||
${dirty ? '' : 'invisible'}`}>
|
||||
Save
|
||||
</button>
|
||||
)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user