From 66f2a36b8c25691e455ada7d18d80f5f0d25ff70 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 18 Aug 2026 14:39:06 +0200 Subject: [PATCH] =?UTF-8?q?chore(web):=20session=20de=20polish=20global=20?= =?UTF-8?q?=E2=80=94=20version,=20checkbox,=20danger=20zone,=20ic=C3=B4nes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Affiche le numéro de version (package.json, injecté via Vite) en bas de la sidebar, masqué en mode collapse et en mobile. - Factorise les checkbox/radio dupliqués (AllergySelect, DietTagSelect, IngredientPicker, UserPreferencesPage) en composants partagés CheckboxOption/RadioOption (components/ui/), et inverse le layout pour que la case soit à gauche du label. - Teinte la "zone de danger" de suppression de compte en rouge (fond + bordure), pas seulement le bouton. - Migre les icônes de navigation générale vers lucide-react (nav-icons.tsx devient un fichier de ré-export) ; les pictogrammes d'ingrédients métier restent en SVG custom (pas d'équivalents fins côté lucide). Vérifié : pnpm build, pnpm lint, pnpm --filter web e2e (43/43), et vérification visuelle manuelle (sidebar desktop/collapsed/mobile, light/dark). --- apps/web/package.json | 3 +- apps/web/src/components/ui/Checkbox.tsx | 41 +++++ apps/web/src/components/ui/Radio.tsx | 38 +++++ .../src/features/profile/AllergySelect.tsx | 16 +- .../src/features/recipes/DietTagSelect.tsx | 7 +- .../features/recipes/FavoriteStarButton.tsx | 2 +- .../src/features/recipes/IngredientPicker.tsx | 23 +-- apps/web/src/features/recipes/RecipeTabs.tsx | 5 +- apps/web/src/layouts/AppLayout.scss | 22 +++ apps/web/src/layouts/AppLayout.tsx | 11 +- apps/web/src/layouts/nav-icons.tsx | 157 +++--------------- .../pages/settings/UserPreferencesPage.tsx | 22 +-- .../src/pages/settings/settings-pages.scss | 3 +- apps/web/src/styles/global.scss | 25 +-- apps/web/src/vite-env.d.ts | 5 + apps/web/vite.config.ts | 11 ++ pnpm-lock.yaml | 12 ++ 17 files changed, 207 insertions(+), 196 deletions(-) create mode 100644 apps/web/src/components/ui/Checkbox.tsx create mode 100644 apps/web/src/components/ui/Radio.tsx create mode 100644 apps/web/src/vite-env.d.ts diff --git a/apps/web/package.json b/apps/web/package.json index cf78bf5..eb06d08 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "0.0.0", + "version": "0.2.0", "private": true, "type": "module", "scripts": { @@ -16,6 +16,7 @@ "@batch-cooking/date-tools": "workspace:*", "@batch-cooking/shared": "workspace:*", "i18next": "^26.3.6", + "lucide-react": "^1.32.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^17.0.11", diff --git a/apps/web/src/components/ui/Checkbox.tsx b/apps/web/src/components/ui/Checkbox.tsx new file mode 100644 index 0000000..fbd6d9f --- /dev/null +++ b/apps/web/src/components/ui/Checkbox.tsx @@ -0,0 +1,41 @@ +import type { ReactNode } from "react"; + +/** + * The app-wide "selectable card" checkbox — see `global.scss`'s + * `label:has(> input[type="checkbox"])` rule for the actual look (hidden + * native input, a `.check-mark` that scales in, `is-selected` driving the + * tinted/bordered state). Factors out the JSX triplet (`label` → hidden + * `input` → `span.check-mark` → label text) that used to be duplicated + * across `AllergySelect`, `DietTagSelect`, `IngredientPicker`'s display + * menu, and `UserPreferencesPage`'s theme picker (see {@link RadioOption} + * for its `type="radio"` sibling) — one place to get the markup/a11y right + * instead of four. + * + * `is-selected` is applied in JS from the same `checked` boolean the caller + * already has, not derived via a CSS `:has(:checked)` chain — that turned + * out unreliable across browsers (see the callers this replaces for the + * original note). + * + * `className` is for the *container* layout only (grid item, flex-wrap + * chip, stacked list…) — the control's own look never varies, so there's + * no `variant` prop here. + */ +export function CheckboxOption({ + checked, + onChange, + children, + className, +}: { + checked: boolean; + onChange: (checked: boolean) => void; + children: ReactNode; + className?: string; +}) { + return ( + + ); +} diff --git a/apps/web/src/components/ui/Radio.tsx b/apps/web/src/components/ui/Radio.tsx new file mode 100644 index 0000000..1a3e15f --- /dev/null +++ b/apps/web/src/components/ui/Radio.tsx @@ -0,0 +1,38 @@ +import type { ReactNode } from "react"; + +/** + * The `type="radio"` sibling of {@link CheckboxOption} — same "selectable + * card" markup/look (see `global.scss`'s `label:has(> input[...])` rule, + * shared by both), just a native radio input under the hood so a group of + * `RadioOption`s sharing `name` behaves as mutually exclusive (see + * `UserPreferencesPage`'s theme picker, the one caller so far). + */ +export function RadioOption({ + name, + value, + checked, + onChange, + children, + className, +}: { + name: string; + value: T; + checked: boolean; + onChange: (value: T) => void; + children: ReactNode; + className?: string; +}) { + return ( + + ); +} diff --git a/apps/web/src/features/profile/AllergySelect.tsx b/apps/web/src/features/profile/AllergySelect.tsx index 824b321..1782279 100644 --- a/apps/web/src/features/profile/AllergySelect.tsx +++ b/apps/web/src/features/profile/AllergySelect.tsx @@ -1,4 +1,5 @@ import type { AllergyView } from "@batch-cooking/shared"; +import { CheckboxOption } from "../../components/ui/Checkbox"; import "./profile-forms.scss"; interface AllergySelectProps { @@ -38,19 +39,14 @@ export function AllergySelect({ legend, allergies, value, onChange }: AllergySel {allergies.map((allergy) => { const checked = value.includes(allergy.id); return ( - + ); })} diff --git a/apps/web/src/features/recipes/DietTagSelect.tsx b/apps/web/src/features/recipes/DietTagSelect.tsx index febbfff..6413e79 100644 --- a/apps/web/src/features/recipes/DietTagSelect.tsx +++ b/apps/web/src/features/recipes/DietTagSelect.tsx @@ -1,5 +1,6 @@ import type { DietView } from "@batch-cooking/shared"; import { useTranslation } from "react-i18next"; +import { CheckboxOption } from "../../components/ui/Checkbox"; import "./recipes.scss"; /** @@ -29,11 +30,9 @@ export function DietTagSelect({ {diets.map((diet) => { const checked = value.includes(diet.id); return ( - + ); })} diff --git a/apps/web/src/features/recipes/FavoriteStarButton.tsx b/apps/web/src/features/recipes/FavoriteStarButton.tsx index 9b9912f..638fc1d 100644 --- a/apps/web/src/features/recipes/FavoriteStarButton.tsx +++ b/apps/web/src/features/recipes/FavoriteStarButton.tsx @@ -52,7 +52,7 @@ export function FavoriteStarButton({ disabled={isSaving} title={t(isFavorite ? "recipes.detail.unfavorite" : "recipes.detail.favorite")} > - +