feat(recipes): toggles afficher/masquer allergènes et régimes dans le picker
Deux boutons icône en bout de la barre de recherche de l'IngredientPicker (régimes/feuille, allergènes/triangle), même pattern bouton-icône + état actif que FavoriteStarButton. Préférence d'affichage locale au picker (non persistée) — n'affecte pas quels ingrédients apparaissent, seulement si leurs badges allergènes/régimes sont visibles sur les cartes. Nouvelle icône AllergenIcon dans nav-icons.tsx, réutilise DietPreferencesIcon existante. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
5890c62462
commit
11b03b0e56
4 changed files with 96 additions and 3 deletions
|
|
@ -5,6 +5,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 { AllergenBadges } from "./AllergenBadges";
|
import { AllergenBadges } from "./AllergenBadges";
|
||||||
import { DietBadges } from "./DietBadges";
|
import { DietBadges } from "./DietBadges";
|
||||||
import "./recipes.scss";
|
import "./recipes.scss";
|
||||||
|
|
@ -23,6 +24,11 @@ const ALL_CATEGORIES = "ALL" as const;
|
||||||
* rationale as `AllergySelect`/`DietSelect`. `excludeIds` (already-selected
|
* rationale as `AllergySelect`/`DietSelect`. `excludeIds` (already-selected
|
||||||
* 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
|
||||||
|
* allergen/diet badge rows on every card — a display preference local to
|
||||||
|
* this picker (not persisted), for whoever finds two rows of badges per
|
||||||
|
* card too noisy while just browsing/searching by name.
|
||||||
*/
|
*/
|
||||||
export function IngredientPicker({
|
export function IngredientPicker({
|
||||||
ingredients,
|
ingredients,
|
||||||
|
|
@ -38,6 +44,13 @@ export function IngredientPicker({
|
||||||
ALL_CATEGORIES,
|
ALL_CATEGORIES,
|
||||||
);
|
);
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
// Purely a display preference for this picker's own card grid — doesn't
|
||||||
|
// touch which ingredients `visible` includes, only whether their
|
||||||
|
// allergen/diet badges render. Defaults to shown (the previous, only
|
||||||
|
// behavior); collapsing them is an opt-in for whoever finds two rows of
|
||||||
|
// badges per card too noisy while just browsing/searching by name.
|
||||||
|
const [showAllergens, setShowAllergens] = useState(true);
|
||||||
|
const [showDiets, setShowDiets] = useState(true);
|
||||||
|
|
||||||
const normalizedQuery = query.trim().toLowerCase();
|
const normalizedQuery = query.trim().toLowerCase();
|
||||||
const visible = ingredients.filter((ingredient) => {
|
const visible = ingredients.filter((ingredient) => {
|
||||||
|
|
@ -63,6 +76,26 @@ 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">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`ingredient-picker__search-toggle${showDiets ? " active" : ""}`}
|
||||||
|
onClick={() => setShowDiets((v) => !v)}
|
||||||
|
aria-pressed={showDiets}
|
||||||
|
title={t("recipes.form.toggleDiets")}
|
||||||
|
>
|
||||||
|
<DietPreferencesIcon />
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ingredient-picker__categories">
|
<div className="ingredient-picker__categories">
|
||||||
|
|
@ -100,8 +133,8 @@ export function IngredientPicker({
|
||||||
{ingredient.icon}
|
{ingredient.icon}
|
||||||
</span>
|
</span>
|
||||||
<span className="ingredient-picker__card-name">{ingredient.name}</span>
|
<span className="ingredient-picker__card-name">{ingredient.name}</span>
|
||||||
<AllergenBadges allergens={ingredient.allergens} />
|
{showAllergens && <AllergenBadges allergens={ingredient.allergens} />}
|
||||||
<DietBadges diets={ingredient.diets} />
|
{showDiets && <DietBadges diets={ingredient.diets} />}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -684,8 +684,16 @@
|
||||||
border-radius: var(--radius-base);
|
border-radius: var(--radius-base);
|
||||||
|
|
||||||
&__search {
|
&__search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
flex: 1 1 auto;
|
||||||
|
// Own min-width: 0 (not just relying on the flex row's) — same
|
||||||
|
// shrink-to-fit gotcha as `.disliked-ingredients-field`, belt and
|
||||||
|
// suspenders since a text input's natural min width is its content.
|
||||||
|
min-width: 0;
|
||||||
padding: var(--space-sm);
|
padding: var(--space-sm);
|
||||||
font-family: var(--font-body);
|
font-family: var(--font-body);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
|
|
@ -696,6 +704,45 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show/hide toggles for the allergen/diet badge rows on every card below
|
||||||
|
// — trailing the search input, same "icon button with an active state"
|
||||||
|
// pattern as `FavoriteStarButton`.
|
||||||
|
&__search-toggles {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__search-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-base);
|
||||||
|
background: var(--color-surface-alt);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.1rem;
|
||||||
|
height: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__categories {
|
&__categories {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
|
|
|
||||||
|
|
@ -128,3 +128,14 @@ 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,6 +166,8 @@
|
||||||
},
|
},
|
||||||
"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",
|
||||||
|
"toggleAllergens": "Afficher/masquer les allergènes",
|
||||||
"allCategories": "Tout",
|
"allCategories": "Tout",
|
||||||
"noIngredientFound": "Aucun ingrédient trouvé.",
|
"noIngredientFound": "Aucun ingrédient trouvé.",
|
||||||
"category": {
|
"category": {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue