fix(recipes): menu d'options d'affichage explicite dans le picker d'ingrédients
Remplace les deux boutons icône seule (régimes/allergènes) par un bouton réglages unique ouvrant un menu avec deux cases à cocher labellisées — les icônes seules n'étaient pas assez explicites sur ce qu'elles activaient/désactivaient. En chemin, corrige un bug réel découvert pendant l'implémentation : les cases à cocher rendaient invisibles (la règle globale "selectable card" de global.scss masque le <input type="checkbox"> natif et attend un <span class="check-mark"> + une classe is-selected sur le <label> pour dessiner l'état coché — mes cases n'avaient ni l'un ni l'autre). Corrigé en suivant exactement le même pattern que AllergySelect.tsx. Supprime AllergenIcon (nav-icons.tsx), devenu inutilisé. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
84003d2009
commit
8d1c763add
4 changed files with 71 additions and 41 deletions
|
|
@ -7,7 +7,7 @@ import {
|
||||||
} from "@batch-cooking/shared";
|
} from "@batch-cooking/shared";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AllergenIcon, DietPreferencesIcon } from "../../layouts/nav-icons";
|
import { SettingsIcon } from "../../layouts/nav-icons";
|
||||||
import { AllergenBadges } from "./AllergenBadges";
|
import { AllergenBadges } from "./AllergenBadges";
|
||||||
import { DietBadges } from "./DietBadges";
|
import { DietBadges } from "./DietBadges";
|
||||||
import { CategoryIcon, IngredientTypeIcon, SubcategoryIcon } from "./ingredient-icons";
|
import { CategoryIcon, IngredientTypeIcon, SubcategoryIcon } from "./ingredient-icons";
|
||||||
|
|
@ -32,10 +32,12 @@ const ALL = "ALL" as const;
|
||||||
* ingredients — a recipe's ingredient list, or a profile's disliked list)
|
* ingredients — a recipe's ingredient list, or a profile's disliked list)
|
||||||
* keeps the same one from being added twice.
|
* keeps the same one from being added twice.
|
||||||
*
|
*
|
||||||
* The two toggle buttons trailing the search input show/hide the
|
* The settings menu trailing the search input shows/hides the allergen/diet
|
||||||
* allergen/diet badge rows on every card — a display preference local to
|
* badge rows on every card — a display preference local to this picker (not
|
||||||
* this picker (not persisted), for whoever finds two rows of badges per
|
* persisted), for whoever finds two rows of badges per card too noisy while
|
||||||
* card too noisy while just browsing/searching by name.
|
* just browsing/searching by name. A labeled checkbox menu rather than two
|
||||||
|
* bare icon-only toggle buttons — those turned out too ambiguous on their
|
||||||
|
* own (unclear what each icon meant without a label attached).
|
||||||
*/
|
*/
|
||||||
export function IngredientPicker({
|
export function IngredientPicker({
|
||||||
ingredients,
|
ingredients,
|
||||||
|
|
@ -57,6 +59,7 @@ export function IngredientPicker({
|
||||||
// badges per card too noisy while just browsing/searching by name.
|
// badges per card too noisy while just browsing/searching by name.
|
||||||
const [showAllergens, setShowAllergens] = useState(true);
|
const [showAllergens, setShowAllergens] = useState(true);
|
||||||
const [showDiets, setShowDiets] = useState(true);
|
const [showDiets, setShowDiets] = useState(true);
|
||||||
|
const [isDisplayMenuOpen, setIsDisplayMenuOpen] = useState(false);
|
||||||
|
|
||||||
function selectCategory(next: IngredientCategory | typeof ALL) {
|
function selectCategory(next: IngredientCategory | typeof ALL) {
|
||||||
setCategory(next);
|
setCategory(next);
|
||||||
|
|
@ -90,25 +93,38 @@ export function IngredientPicker({
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
placeholder={t("recipes.form.searchIngredientPlaceholder")}
|
placeholder={t("recipes.form.searchIngredientPlaceholder")}
|
||||||
/>
|
/>
|
||||||
<div className="ingredient-picker__search-toggles">
|
<div className="ingredient-picker__display-options">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`ingredient-picker__search-toggle${showDiets ? " active" : ""}`}
|
className={`ingredient-picker__display-toggle${isDisplayMenuOpen ? " active" : ""}`}
|
||||||
onClick={() => setShowDiets((v) => !v)}
|
onClick={() => setIsDisplayMenuOpen((v) => !v)}
|
||||||
aria-pressed={showDiets}
|
aria-expanded={isDisplayMenuOpen}
|
||||||
title={t("recipes.form.toggleDiets")}
|
title={t("recipes.form.displayOptions")}
|
||||||
>
|
>
|
||||||
<DietPreferencesIcon />
|
<SettingsIcon />
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`ingredient-picker__search-toggle${showAllergens ? " active" : ""}`}
|
|
||||||
onClick={() => setShowAllergens((v) => !v)}
|
|
||||||
aria-pressed={showAllergens}
|
|
||||||
title={t("recipes.form.toggleAllergens")}
|
|
||||||
>
|
|
||||||
<AllergenIcon />
|
|
||||||
</button>
|
</button>
|
||||||
|
{isDisplayMenuOpen && (
|
||||||
|
<div className="ingredient-picker__display-menu">
|
||||||
|
<label className={showDiets ? "is-selected" : ""}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={showDiets}
|
||||||
|
onChange={(e) => setShowDiets(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span className="check-mark" aria-hidden="true" />
|
||||||
|
{t("recipes.form.showDietsLabel")}
|
||||||
|
</label>
|
||||||
|
<label className={showAllergens ? "is-selected" : ""}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={showAllergens}
|
||||||
|
onChange={(e) => setShowAllergens(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span className="check-mark" aria-hidden="true" />
|
||||||
|
{t("recipes.form.showAllergensLabel")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -726,20 +726,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show/hide toggles for the allergen/diet badge rows on every card below
|
// Settings menu for the allergen/diet badge rows on every card below —
|
||||||
// — trailing the search input, same "icon button with an active state"
|
// trailing the search input. A single gear button + labeled checkboxes
|
||||||
// pattern as `FavoriteStarButton`.
|
// (not two bare icon-only toggles, the earlier design here) — an icon
|
||||||
&__search-toggles {
|
// alone couldn't say clearly enough which badge row it hid.
|
||||||
display: flex;
|
&__display-options {
|
||||||
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
gap: var(--space-xs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__search-toggle {
|
&__display-toggle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
|
||||||
width: 2.25rem;
|
width: 2.25rem;
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
@ -765,6 +764,31 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Anchored below-right of the toggle — same transient-overlay reasoning
|
||||||
|
// as `.app-sidebar__account-menu` (AppLayout.scss).
|
||||||
|
&__display-menu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
top: calc(100% + var(--space-xs));
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
padding: var(--space-sm);
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-base);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
|
||||||
|
// Box/checkmark look comes from the app-wide "selectable card" rule in
|
||||||
|
// global.scss (`label:has(> input[type="checkbox"])`) — this only adds
|
||||||
|
// what's specific to sitting inside a dropdown menu.
|
||||||
|
label {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__categories {
|
&__categories {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
|
|
|
||||||
|
|
@ -128,14 +128,3 @@ export function PublicIcon() {
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Allergens — the show/hide allergen-badges toggle on `IngredientPicker`'s search bar. */
|
|
||||||
export function AllergenIcon() {
|
|
||||||
return (
|
|
||||||
<Icon>
|
|
||||||
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
||||||
<path d="M12 9v4" />
|
|
||||||
<path d="M12 17h.01" />
|
|
||||||
</Icon>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -166,8 +166,9 @@
|
||||||
},
|
},
|
||||||
"dietsLabel": "Régime(s) associé(s)",
|
"dietsLabel": "Régime(s) associé(s)",
|
||||||
"searchIngredientPlaceholder": "Rechercher un ingrédient…",
|
"searchIngredientPlaceholder": "Rechercher un ingrédient…",
|
||||||
"toggleDiets": "Afficher/masquer les régimes alimentaires",
|
"displayOptions": "Options d'affichage",
|
||||||
"toggleAllergens": "Afficher/masquer les allergènes",
|
"showDietsLabel": "Régimes alimentaires",
|
||||||
|
"showAllergensLabel": "Allergènes",
|
||||||
"allCategories": "Tout",
|
"allCategories": "Tout",
|
||||||
"allSubcategories": "Tout",
|
"allSubcategories": "Tout",
|
||||||
"noIngredientFound": "Aucun ingrédient trouvé.",
|
"noIngredientFound": "Aucun ingrédient trouvé.",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue