Hints become a question mark, not a line of prose
"01 - YTD Sales · 02 - Open Orders can't move — the forecast absorbs it" was printed under its toggle, and a line of prose beside a toggle is skipped while still costing the dock a row. Every Field's hint is now a ? next to the control: hovering gives it (a native title, which nothing clips), clicking pins it in a small popover. The Per slice row goes with them -- what it said belongs to Apply, whose help now covers both modes, and the ledger already marks its rows (all) and (each). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
91b88cd809
commit
e88f52203c
@ -93,6 +93,30 @@ function Button({ onClick, active, children, title, disabled }) {
|
|||||||
// "copy rows from" sitting above "scale cloned rows by" above "tag".
|
// "copy rows from" sitting above "scale cloned rows by" above "tag".
|
||||||
const LABEL_W = 'w-24'
|
const LABEL_W = 'w-24'
|
||||||
|
|
||||||
|
// Context on demand. The explanations used to be printed under the controls,
|
||||||
|
// which put a line of prose beside every toggle -- and a line of prose beside a
|
||||||
|
// toggle is skipped, while still costing the dock a row. Hovering gives it
|
||||||
|
// (native title, which nothing can clip), clicking pins it.
|
||||||
|
function Help({ text }) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
if (!text) return null
|
||||||
|
return (
|
||||||
|
<span className="relative inline-flex shrink-0">
|
||||||
|
<button type="button" title={text} onClick={() => setOpen(o => !o)} onBlur={() => setOpen(false)}
|
||||||
|
aria-label={text}
|
||||||
|
className={`w-4 h-4 rounded-full border leading-none flex items-center justify-center transition-colors ${
|
||||||
|
open ? 'border-blue-400 text-blue-600 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-blue-600 hover:border-blue-400'}`}
|
||||||
|
style={{ fontSize: '10px' }}>?</button>
|
||||||
|
{open && (
|
||||||
|
<span className="absolute z-20 left-0 top-5 w-64 rounded border border-gray-200 bg-white shadow-lg px-2 py-1.5
|
||||||
|
text-gray-600 leading-snug normal-case tracking-normal">
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// `stack` puts the label above its control instead of beside it. The aligned
|
// `stack` puts the label above its control instead of beside it. The aligned
|
||||||
// label column costs 6rem, which is most of a narrow zone -- the tag field in
|
// label column costs 6rem, which is most of a narrow zone -- the tag field in
|
||||||
// the bottom dock had 12rem of box left to type an initiative name into.
|
// the bottom dock had 12rem of box left to type an initiative name into.
|
||||||
@ -100,18 +124,17 @@ function Field({ label, children, hint, stack }) {
|
|||||||
if (stack) {
|
if (stack) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1 min-w-0">
|
<div className="flex flex-col gap-1 min-w-0">
|
||||||
<span className="text-gray-600 whitespace-nowrap">{label}</span>
|
<span className="text-gray-600 whitespace-nowrap flex items-center gap-1.5">{label}<Help text={hint} /></span>
|
||||||
<div className="flex items-center gap-2 min-w-0">{children}</div>
|
<div className="flex items-center gap-2 min-w-0">{children}</div>
|
||||||
{hint && <p className="text-gray-500 text-[11px] leading-snug">{hint}</p>}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span className={`text-gray-600 whitespace-nowrap shrink-0 pt-1 ${LABEL_W}`}>{label}</span>
|
<span className={`text-gray-600 whitespace-nowrap shrink-0 pt-1 ${LABEL_W}`}>{label}</span>
|
||||||
<div className="flex flex-col gap-1 min-w-0">
|
<div className="flex items-center gap-2 flex-wrap min-w-0">
|
||||||
<div className="flex items-center gap-2 flex-wrap">{children}</div>
|
{children}
|
||||||
{hint && <p className="text-gray-500 text-[11px] leading-snug max-w-xs">{hint}</p>}
|
<Help text={hint} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -1063,11 +1086,7 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se
|
|||||||
</Field>
|
</Field>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{perSlice && (
|
|
||||||
<Field label="Per slice">
|
|
||||||
<span className="text-gray-500">figures above are combined totals</span>
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -1342,7 +1361,10 @@ export default function OperationPanel({
|
|||||||
// it is one line, they are one line, and stacked they cost three rows of a
|
// it is one line, they are one line, and stacked they cost three rows of a
|
||||||
// dock that is 260px tall to begin with.
|
// dock that is 260px tall to begin with.
|
||||||
const applyField = multi ? (
|
const applyField = multi ? (
|
||||||
<Field label="Apply">
|
<Field label="Apply"
|
||||||
|
hint={applyMode === 'each'
|
||||||
|
? `Each of the ${slices.length} slices reaches the target on its own, and each gets its own log entry. The ledger's figures are combined totals, so a slice's own change will differ from them.`
|
||||||
|
: `The ${slices.length} slices are one pool: a target is the new combined total, and each slice keeps its share of the mix. One log entry.`}>
|
||||||
<ApplyModeChooser op={activeOp} applyMode={applyMode} setApplyMode={setApplyMode} count={slices.length} />
|
<ApplyModeChooser op={activeOp} applyMode={applyMode} setApplyMode={setApplyMode} count={slices.length} />
|
||||||
</Field>
|
</Field>
|
||||||
) : null
|
) : null
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user