feat(ui): auto-size batch size and yield inputs snug to content with click-to-highlight and pointer cursor

This commit is contained in:
2026-08-18 08:50:31 -05:00
parent 0e3ff8f74f
commit bebab112fc
2 changed files with 55 additions and 17 deletions
+26 -11
View File
@@ -166,6 +166,11 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
}
};
const currentScaleLabel = !showCustomInput && matchedScaleOption ? matchedScaleOption.label : "Custom";
const yieldUnitSymbol = yieldUnits.find((u) => u.id === yieldDisplayUnitId)?.symbol ?? yieldDisplayUnitId;
const yieldQtyString = String(roundForDisplay(scaledYield));
const customFactorString = String(roundForDisplay(factor));
return (
<section class="calculator" aria-label="Recipe scaling calculator">
<div class="calculator-heading" data-testid="metaItem">
@@ -176,6 +181,7 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
aria-label="Batch size multiplier"
class="meez-scale-select"
data-testid="ScaleSelectorBase_scale_select"
style={{ width: `${currentScaleLabel.length + 2.4}ch` }}
value={!showCustomInput && matchedScaleOption ? String(matchedScaleOption.value) : "custom"}
onChange={handleScaleSelectChange}
>
@@ -187,12 +193,14 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
{showCustomInput && (
<span class="custom-scale-wrap">
<input
type="number"
min="0"
step="any"
type="text"
inputMode="decimal"
aria-label="Custom batch multiplier"
class="custom-scale-input"
value={roundForDisplay(factor)}
style={{ width: `${Math.max(2, customFactorString.length + 0.8)}ch` }}
value={customFactorString}
onFocus={(event) => (event.currentTarget as HTMLInputElement).select()}
onClick={(event) => (event.currentTarget as HTMLInputElement).select()}
onInput={(event) => {
const val = Number((event.currentTarget as HTMLInputElement).value);
if (!isNaN(val) && val > 0) setFactor(val);
@@ -208,14 +216,21 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
<span class="scale-label">Yield:</span>
<span class="recipe-scale-control quantity-control">
<input
type="number"
min="0"
step="any"
type="text"
inputMode="decimal"
aria-label="Finished yield quantity"
value={roundForDisplay(scaledYield)}
style={{ width: `${Math.max(2, yieldQtyString.length + 0.5)}ch` }}
value={yieldQtyString}
onFocus={(event) => (event.currentTarget as HTMLInputElement).select()}
onClick={(event) => (event.currentTarget as HTMLInputElement).select()}
onInput={(event) => changeYield(Number((event.currentTarget as HTMLInputElement).value))}
/>
<select aria-label="Finished yield unit" value={yieldDisplayUnitId} onChange={(event) => setYieldDisplayUnitId((event.currentTarget as HTMLSelectElement).value)}>
<select
aria-label="Finished yield unit"
value={yieldDisplayUnitId}
style={{ width: `${yieldUnitSymbol.length + 2.2}ch` }}
onChange={(event) => setYieldDisplayUnitId((event.currentTarget as HTMLSelectElement).value)}
>
{yieldUnits.map((unit) => <option value={unit.id} key={unit.id}>{unit.symbol}</option>)}
</select>
</span>
@@ -283,7 +298,7 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
)}
</div>
{calculatePercent&&percentMode==="bakers"&&baseItems.length>0&&<section class="bakers-base-summary"><strong>Base</strong><div>{baseItems.map(item=>{const displayUnitId=lineUnits[item.id]??item.amount.unit_id;const displayQuantity=convertItem(item.amount.quantity*validFactor,item.amount.unit_id,displayUnitId,item,units);return <p><span><input aria-label={`${item.label} base quantity`} type="number" min="0" step="any" value={roundForDisplay(displayQuantity)} onInput={(event)=>{const canonicalQuantity=convertItem(Number(event.currentTarget.value),displayUnitId,item.amount.unit_id,item,units);setFactor(item.amount.quantity>0?canonicalQuantity/item.amount.quantity:1)}}/><select aria-label={`${item.label} base unit`} value={displayUnitId} onChange={(event)=>setLineUnits((current)=>({...current,[item.id]:event.currentTarget.value}))}>{itemUnits(item,units).map(unit=><option value={unit.id}>{unit.symbol}</option>)}</select></span><a href={item.href}>{item.label}</a></p>})}</div></section>}
{calculatePercent&&percentMode==="bakers"&&baseItems.length>0&&<section class="bakers-base-summary"><strong>Base</strong><div>{baseItems.map(item=>{const displayUnitId=lineUnits[item.id]??item.amount.unit_id;const displayQuantity=convertItem(item.amount.quantity*validFactor,item.amount.unit_id,displayUnitId,item,units);return <p><span><input aria-label={`${item.label} base quantity`} type="text" inputMode="decimal" style={{ width: `${Math.max(2, String(roundForDisplay(displayQuantity)).length + 0.5)}ch` }} value={roundForDisplay(displayQuantity)} onFocus={(event) => (event.currentTarget as HTMLInputElement).select()} onClick={(event) => (event.currentTarget as HTMLInputElement).select()} onInput={(event)=>{const canonicalQuantity=convertItem(Number(event.currentTarget.value),displayUnitId,item.amount.unit_id,item,units);setFactor(item.amount.quantity>0?canonicalQuantity/item.amount.quantity:1)}}/><select aria-label={`${item.label} base unit`} value={displayUnitId} onChange={(event)=>setLineUnits((current)=>({...current,[item.id]:event.currentTarget.value}))}>{itemUnits(item,units).map(unit=><option value={unit.id}>{unit.symbol}</option>)}</select></span><a href={item.href}>{item.label}</a></p>})}</div></section>}
{components.map((component) => (
<div class="formula-component" key={component.id}>
{Boolean(component.name?.trim()) && <h3 class="formula-component-heading">{component.name}</h3>}
@@ -304,7 +319,7 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
};
return (
<tr key={item.id} class={item.basisMember ? "basis-row" : ""}>
<td><span class="quantity-control line-quantity"><input aria-label={`${item.label} quantity`} type="number" min="0" step="any" value={roundForDisplay(displayQuantity)} onInput={(event) => changeLineQuantity(Number(event.currentTarget.value))}/><select aria-label={`${item.label} unit`} value={displayUnitId} style={{width:`${(compatibleUnits.find((unit) => unit.id === displayUnitId)?.symbol ?? displayUnitId).length + 0.75}ch`}} onChange={(event) => { const next = event.currentTarget.value; try { convertItem(item.amount.quantity * validFactor, item.amount.unit_id, next, item, units); } catch { event.currentTarget.value = displayUnitId; return; } setLineUnits((current) => ({ ...current, [item.id]: next })); }}>{compatibleUnits.map((unit) => <option value={unit.id} key={unit.id}>{unit.symbol}</option>)}</select></span></td>
<td><span class="quantity-control line-quantity"><input aria-label={`${item.label} quantity`} type="text" inputMode="decimal" style={{ width: `${Math.max(2, String(roundForDisplay(displayQuantity)).length + 0.5)}ch` }} value={roundForDisplay(displayQuantity)} onFocus={(event) => (event.currentTarget as HTMLInputElement).select()} onClick={(event) => (event.currentTarget as HTMLInputElement).select()} onInput={(event) => changeLineQuantity(Number(event.currentTarget.value))}/><select aria-label={`${item.label} unit`} value={displayUnitId} style={{width:`${(compatibleUnits.find((unit) => unit.id === displayUnitId)?.symbol ?? displayUnitId).length + 0.75}ch`}} onChange={(event) => { const next = event.currentTarget.value; try { convertItem(item.amount.quantity * validFactor, item.amount.unit_id, next, item, units); } catch { event.currentTarget.value = displayUnitId; return; } setLineUnits((current) => ({ ...current, [item.id]: next })); }}>{compatibleUnits.map((unit) => <option value={unit.id} key={unit.id}>{unit.symbol}</option>)}</select></span></td>
<td>
<span class="calculator-ingredient-name">
{item.href ? <a href={item.href}>{item.label}</a> : item.label}
+29 -6
View File
@@ -2983,11 +2983,13 @@ input[type="search"]::-webkit-search-results-decoration,
cursor: pointer !important;
outline: none !important;
height: 24px !important;
field-sizing: content;
transition: border-color 0.15s ease !important;
}
.application-body .recipe-view-formula .calculator-heading .meez-scale-select:hover {
border-bottom-color: #050841 !important;
cursor: pointer !important;
}
.application-body .recipe-view-formula .calculator-heading .meez-scale-select:focus {
@@ -3007,9 +3009,8 @@ input[type="search"]::-webkit-search-results-decoration,
}
.application-body .recipe-view-formula .calculator-heading .custom-scale-input {
width: 42px !important;
min-width: 24px !important;
max-width: 5ch !important;
width: auto !important;
min-width: 16px !important;
font-family: var(--meez-font-sans) !important;
font-size: 14px !important;
font-weight: 500 !important;
@@ -3019,14 +3020,19 @@ input[type="search"]::-webkit-search-results-decoration,
outline: 0 !important;
padding: 0 !important;
text-align: right !important;
cursor: pointer !important;
field-sizing: content;
-moz-appearance: textfield !important;
}
.application-body .recipe-view-formula .calculator-heading .custom-scale-input:focus {
cursor: text !important;
}
.application-body .recipe-view-formula .calculator-heading .recipe-scale-control input,
.application-body .immersive-main .recipe-read-shell .recipe-view-formula .calculator-heading .recipe-scale-control input {
width: auto !important;
min-width: 16px !important;
max-width: 6ch !important;
height: auto !important;
padding: 0 !important;
color: #050841 !important;
@@ -3037,9 +3043,25 @@ input[type="search"]::-webkit-search-results-decoration,
font-size: 15px !important;
font-weight: 500 !important;
text-align: left !important;
cursor: pointer !important;
field-sizing: content;
-moz-appearance: textfield !important;
}
.application-body .recipe-view-formula .calculator-heading .recipe-scale-control input:focus,
.application-body .immersive-main .recipe-read-shell .recipe-view-formula .calculator-heading .recipe-scale-control input:focus {
cursor: text !important;
}
.application-body .recipe-ingredients .quantity-control input {
cursor: pointer !important;
field-sizing: content;
}
.application-body .recipe-ingredients .quantity-control input:focus {
cursor: text !important;
}
.application-body .recipe-view-formula .calculator-heading .batch-unit-suffix {
font-family: var(--meez-font-sans) !important;
font-size: 14px !important;
@@ -3052,10 +3074,10 @@ input[type="search"]::-webkit-search-results-decoration,
.application-body .recipe-view-formula .calculator-heading .quantity-control input,
.application-body .immersive-main .recipe-read-shell .recipe-view-formula .calculator-heading .quantity-control input {
min-width: 24px !important;
max-width: 6ch !important;
font-size: 15px !important;
font-weight: 500 !important;
padding-right: 4px !important;
padding-right: 2px !important;
cursor: pointer !important;
}
.application-body .recipe-view-formula .calculator-heading .recipe-scale-control select,
@@ -3073,6 +3095,7 @@ input[type="search"]::-webkit-search-results-decoration,
font-weight: 500 !important;
cursor: pointer !important;
text-align: left !important;
field-sizing: content;
}
/* Meez HelperTooltipBase_help_icon & Meez Tooltip Popups */