feat(scaling): implement Meez-styled batch size select dropdown with presets and custom multiplier option

This commit is contained in:
2026-08-18 08:46:25 -05:00
parent 9feb81957c
commit 8757a66edd
2 changed files with 107 additions and 9 deletions
+53 -9
View File
@@ -48,8 +48,18 @@ function convertItem(quantity: number, fromUnitId: string, toUnitId: string, ite
throw new Error(`No reviewed equivalency from ${fromUnitId} to ${toUnitId}`);
}
const SCALE_OPTIONS = [
{ value: 0.5, label: "1/2x" },
{ value: 1, label: "1x" },
{ value: 2, label: "2x" },
{ value: 3, label: "3x" },
{ value: 4, label: "4x" },
{ value: 5, label: "5x" },
];
export default function RecipeCalculator({ components, units, basisUnitId, yieldQuantity, yieldUnitId, nutrition, cost, servings, showDerived = true, showPercentControls = true, yieldConversions = [], shelfLife }: Props) {
const [factor, setFactor] = useState(1);
const [isCustomScale, setIsCustomScale] = useState(false);
const [calculatePercent,setCalculatePercent]=useState(showPercentControls);
const [percentMode,setPercentMode]=useState<"standard"|"bakers">("standard");
const [yieldDisplayUnitId, setYieldDisplayUnitId] = useState(yieldUnitId);
@@ -59,6 +69,19 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
const [activeAttentionId, setActiveAttentionId] = useState<string | null>(null);
const validFactor = Number.isFinite(factor) && factor >= 0 ? factor : 0;
const matchedScaleOption = SCALE_OPTIONS.find((opt) => Math.abs(opt.value - factor) < 0.001);
const showCustomInput = isCustomScale || !matchedScaleOption;
const handleScaleSelectChange = (e: Event) => {
const val = (e.currentTarget as HTMLSelectElement).value;
if (val === "custom") {
setIsCustomScale(true);
} else {
setIsCustomScale(false);
setFactor(Number(val));
}
};
const handleScaleTooltipEnter = () => {
if (scaleTooltipTimer) clearTimeout(scaleTooltipTimer);
setShowScaleTooltip(true);
@@ -149,15 +172,36 @@ export default function RecipeCalculator({ components, units, basisUnitId, yield
<div class="basis-input batch-multiplier-field">
<span class="scale-label">Batch Size:</span>
<span class="recipe-scale-control batch-size-control">
<input
type="number"
min="0"
step="any"
aria-label="Batch multiplier"
value={roundForDisplay(factor)}
onInput={(event) => setFactor(Number((event.currentTarget as HTMLInputElement).value))}
/>
<span class="batch-unit-suffix">x</span>
<select
aria-label="Batch size multiplier"
class="meez-scale-select"
data-testid="ScaleSelectorBase_scale_select"
value={!showCustomInput && matchedScaleOption ? String(matchedScaleOption.value) : "custom"}
onChange={handleScaleSelectChange}
>
{SCALE_OPTIONS.map((opt) => (
<option key={opt.value} value={String(opt.value)}>{opt.label}</option>
))}
<option value="custom">Custom</option>
</select>
{showCustomInput && (
<span class="custom-scale-wrap">
<input
type="number"
min="0"
step="any"
aria-label="Custom batch multiplier"
class="custom-scale-input"
value={roundForDisplay(factor)}
onInput={(event) => {
const val = Number((event.currentTarget as HTMLInputElement).value);
if (!isNaN(val) && val > 0) setFactor(val);
}}
autoFocus
/>
<span class="batch-unit-suffix">x</span>
</span>
)}
</span>
</div>
<div class="basis-input finished-yield-field">
+54
View File
@@ -2956,6 +2956,60 @@ input[type="search"]::-webkit-search-results-decoration,
.application-body .immersive-main .recipe-read-shell .recipe-view-formula .calculator-heading .recipe-scale-control:focus-within {
border-bottom: 2px solid #3d5df6 !important;
padding-bottom: 0 !important;
}.application-body .recipe-view-formula .calculator-heading .batch-size-control {
border-bottom: none !important;
padding: 0 !important;
}
.application-body .recipe-view-formula .calculator-heading .meez-scale-select {
font-family: var(--meez-font-sans) !important;
font-size: 14px !important;
font-weight: 500 !important;
color: #050841 !important;
background: transparent !important;
border: none !important;
border-bottom: 1px solid #a5a9c1 !important;
padding: 0 4px 1px 0 !important;
cursor: pointer !important;
outline: none !important;
height: 24px !important;
transition: border-color 0.15s ease !important;
}
.application-body .recipe-view-formula .calculator-heading .meez-scale-select:hover {
border-bottom-color: #050841 !important;
}
.application-body .recipe-view-formula .calculator-heading .meez-scale-select:focus {
border-bottom: 2px solid #3d5df6 !important;
}
.application-body .recipe-view-formula .calculator-heading .custom-scale-wrap {
display: inline-flex !important;
align-items: center !important;
margin-left: 6px !important;
border-bottom: 1px solid #a5a9c1 !important;
padding-bottom: 1px !important;
}
.application-body .recipe-view-formula .calculator-heading .custom-scale-wrap:focus-within {
border-bottom: 2px solid #3d5df6 !important;
}
.application-body .recipe-view-formula .calculator-heading .custom-scale-input {
width: 42px !important;
min-width: 24px !important;
max-width: 5ch !important;
font-family: var(--meez-font-sans) !important;
font-size: 14px !important;
font-weight: 500 !important;
color: #050841 !important;
background: transparent !important;
border: 0 !important;
outline: 0 !important;
padding: 0 !important;
text-align: right !important;
-moz-appearance: textfield !important;
}
.application-body .recipe-view-formula .calculator-heading .recipe-scale-control input,