style(recipes): unify custom multiplier pill with preset pills
CI / update (push) Successful in 5m48s
CI / update (push) Successful in 5m48s
Make the custom-multiplier pill behave and look like a single input zone: - Wrapper is now a <label> so clicking anywhere focuses the input. - Replace the explicit \"x\" submit button with a passive <span> suffix and add a visually-hidden first-tree-order submit so no-JS Enter still submits with the typed value (rather than the first preset pill's value). - Wrapper cursor: text end-to-end, no pointer flicker. - Hover/focus selector now matches the wrapper alongside the preset buttons, and an isCustomMultiplier flag highlights the pill in primary whenever a non-preset value is active (e.g. ?multiplier=12). - Input uses field-sizing: content (with min/max) so the pill collapses to fit the placeholder. - align-items: center (was baseline) so the input doesn't sit high in its pill. - Tighten the multipliers row (gap 0.5rem -> 0.3rem, button min-width 2em -> 1.8em, matching paddings) so all six pills fit on one line in the ingredients column.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.57.2",
|
"version": "1.57.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -239,6 +239,8 @@ const multiplierOptions = [
|
|||||||
{ value: 3, label: '3x' }
|
{ value: 3, label: '3x' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const isCustomMultiplier = $derived(!multiplierOptions.some(o => o.value === multiplier));
|
||||||
|
|
||||||
// Calculate yeast IDs for each yeast ingredient
|
// Calculate yeast IDs for each yeast ingredient
|
||||||
const yeastIds = $derived.by(() => {
|
const yeastIds = $derived.by(() => {
|
||||||
/** @type {Record<string, number>} */
|
/** @type {Record<string, number>} */
|
||||||
@@ -449,7 +451,7 @@ const nutritionFlatIngredients = $derived.by(() => {
|
|||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-block: 1rem;
|
padding-block: 1rem;
|
||||||
padding-inline: 2rem;
|
padding-inline: 1.25rem;
|
||||||
}
|
}
|
||||||
.ingredients_grid{
|
.ingredients_grid{
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -462,18 +464,21 @@ const nutritionFlatIngredients = $derived.by(() => {
|
|||||||
}
|
}
|
||||||
.multipliers{
|
.multipliers{
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.3rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
/* Size overrides for multiplier buttons */
|
/* Size overrides for multiplier buttons */
|
||||||
.multipliers button{
|
.multipliers button{
|
||||||
min-width: 2em;
|
min-width: 1.8em;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
|
padding-inline: 0.35em;
|
||||||
}
|
}
|
||||||
/* Hover scale override - larger than default */
|
/* Hover/focus on a whole pill (number button or custom-multiplier wrapper)
|
||||||
.multipliers :is(button, form):is(:hover, :focus-within){
|
flips its background to primary; :focus-within covers focus on the
|
||||||
|
nested <input> / <button> inside the custom-multiplier pill. */
|
||||||
|
.multipliers :is(button, .custom-multiplier):is(:hover, :focus-within){
|
||||||
scale: 1.2;
|
scale: 1.2;
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-primary);
|
||||||
color: var(--color-text-on-primary);
|
color: var(--color-text-on-primary);
|
||||||
@@ -485,15 +490,24 @@ const nutritionFlatIngredients = $derived.by(() => {
|
|||||||
scale: 1.2 !important;
|
scale: 1.2 !important;
|
||||||
}
|
}
|
||||||
.custom-multiplier {
|
.custom-multiplier {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-width: 2em;
|
min-width: 1.8em;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 1px 0.35em;
|
||||||
|
/* Whole pill behaves like one input zone — no cursor flicker between the
|
||||||
|
typing area and the trailing "x" suffix. */
|
||||||
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-input {
|
.custom-input {
|
||||||
width: 3em;
|
/* Grow with the typed value (Chromium 123+, Safari 18.4+); falls back to
|
||||||
|
the fixed width on older browsers. min/max keep the wrap-around tame. */
|
||||||
|
field-sizing: content;
|
||||||
|
width: 1.4em;
|
||||||
|
min-width: 1ch;
|
||||||
|
max-width: 4em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -504,6 +518,10 @@ const nutritionFlatIngredients = $derived.by(() => {
|
|||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
.custom-input::placeholder {
|
||||||
|
color: currentColor;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
/* Remove number input arrows */
|
/* Remove number input arrows */
|
||||||
.custom-input::-webkit-outer-spin-button,
|
.custom-input::-webkit-outer-spin-button,
|
||||||
@@ -512,16 +530,24 @@ const nutritionFlatIngredients = $derived.by(() => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-suffix {
|
||||||
.custom-button {
|
margin-left: 0.05em;
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: inherit;
|
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
cursor: pointer;
|
color: inherit;
|
||||||
box-shadow: none;
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Off-screen submit button used as the form's implicit submitter when the
|
||||||
|
user presses Enter inside the custom-multiplier input (no-JS path). */
|
||||||
|
.implicit-submit {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cake-form {
|
.cake-form {
|
||||||
@@ -752,10 +778,14 @@ const nutritionFlatIngredients = $derived.by(() => {
|
|||||||
<input type="hidden" name={key} {value} />
|
<input type="hidden" name={key} {value} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
<!-- Implicit submitter for no-JS Enter on the custom input. Must be the
|
||||||
|
first submit in tree order so it wins over the pill buttons. Has no
|
||||||
|
name/value, so the form submits with only the input's typed value. -->
|
||||||
|
<button type="submit" class="implicit-submit" tabindex="-1" aria-hidden="true"></button>
|
||||||
{#each multiplierOptions as opt}
|
{#each multiplierOptions as opt}
|
||||||
<button type="submit" name="multiplier" value={opt.value} class="g-pill g-btn-light g-interactive" class:selected={multiplier === opt.value} onclick={(e) => handleMultiplierClick(e, opt.value)}>{@html opt.label}</button>
|
<button type="submit" name="multiplier" value={opt.value} class="g-pill g-btn-light g-interactive" class:selected={multiplier === opt.value} onclick={(e) => handleMultiplierClick(e, opt.value)}>{@html opt.label}</button>
|
||||||
{/each}
|
{/each}
|
||||||
<span class="custom-multiplier g-pill g-btn-light g-interactive">
|
<label class="custom-multiplier g-pill g-btn-light g-interactive" class:selected={isCustomMultiplier}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="multiplier"
|
name="multiplier"
|
||||||
@@ -763,11 +793,11 @@ const nutritionFlatIngredients = $derived.by(() => {
|
|||||||
title="Enter a positive number (e.g., 2.5, 0.75, 3.14)"
|
title="Enter a positive number (e.g., 2.5, 0.75, 3.14)"
|
||||||
placeholder="…"
|
placeholder="…"
|
||||||
class="custom-input"
|
class="custom-input"
|
||||||
value={!multiplierOptions.some(o => o.value === multiplier) ? multiplier : ''}
|
value={isCustomMultiplier ? multiplier : ''}
|
||||||
oninput={handleCustomInput}
|
oninput={handleCustomInput}
|
||||||
/>
|
/>
|
||||||
<button type="submit" class="custom-button">x</button>
|
<span class="custom-suffix" aria-hidden="true">x</span>
|
||||||
</span>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{#if hasDefaultForm}
|
{#if hasDefaultForm}
|
||||||
|
|||||||
Reference in New Issue
Block a user