feat(reports/category): searchable combobox for category zoom (#103) #108

Merged
maximus merged 1 commit from issue-103-category-combobox into main 2026-04-19 01:10:26 +00:00
Owner

Summary

  • Replace the native <select> in CategoryZoomHeader with the reusable CategoryCombobox (typeable input, accent-insensitive filtering, ↑/↓/Enter/Esc navigation, close on outside click).
  • Enhance CategoryCombobox with full ARIA compliance (role="combobox"/listbox/option, aria-expanded, aria-controls, aria-activedescendant, aria-selected) and hierarchical indentation based on parent_id depth.
  • New i18n key reports.category.searchPlaceholder in FR/EN.

Notes

  • The combobox already existed in src/components/shared/CategoryCombobox.tsx (used by transaction filters, transaction rows, split modal, page help). Reused and extended it rather than building a new one.
  • Hierarchy depth is computed from the flat category list via parent_id chain, then applied as paddingLeft plus a preserved text prefix inside a <span class='whitespace-pre'> for good screen-reader output.
  • aria-label is passed from the Zoom header (localized selectCategory text) since the native <label> wraps a custom <input>.

Test plan

  • npm run build (tsc + vite) — green
  • npm test -- --run — 100/100 pass
  • cargo check — green
  • Manual: open /reports/category, type a partial accented name (e.g. epice matches Épicerie), verify arrow keys + Enter + Esc + click-outside + hierarchy indent.

Closes #103

## Summary - Replace the native `<select>` in `CategoryZoomHeader` with the reusable `CategoryCombobox` (typeable input, accent-insensitive filtering, ↑/↓/Enter/Esc navigation, close on outside click). - Enhance `CategoryCombobox` with full ARIA compliance (`role="combobox"`/`listbox`/`option`, `aria-expanded`, `aria-controls`, `aria-activedescendant`, `aria-selected`) and hierarchical indentation based on `parent_id` depth. - New i18n key `reports.category.searchPlaceholder` in FR/EN. ## Notes - The combobox already existed in `src/components/shared/CategoryCombobox.tsx` (used by transaction filters, transaction rows, split modal, page help). Reused and extended it rather than building a new one. - Hierarchy depth is computed from the flat category list via `parent_id` chain, then applied as `paddingLeft` plus a preserved ` ` text prefix inside a `<span class='whitespace-pre'>` for good screen-reader output. - `aria-label` is passed from the Zoom header (localized `selectCategory` text) since the native `<label>` wraps a custom `<input>`. ## Test plan - [x] `npm run build` (tsc + vite) — green - [x] `npm test -- --run` — 100/100 pass - [x] `cargo check` — green - [ ] Manual: open `/reports/category`, type a partial accented name (e.g. `epice` matches `Épicerie`), verify arrow keys + Enter + Esc + click-outside + hierarchy indent. Closes #103
maximus added 1 commit 2026-04-19 01:08:06 +00:00
feat(reports/category): replace select with searchable combobox (#103)
All checks were successful
PR Check / rust (push) Successful in 21m2s
PR Check / frontend (push) Successful in 2m13s
PR Check / rust (pull_request) Successful in 21m14s
PR Check / frontend (pull_request) Successful in 2m9s
01869462f4
Swap the native <select> in CategoryZoomHeader for the reusable
CategoryCombobox. Enhances the combobox with ARIA compliance
(combobox, listbox, option roles + aria-expanded, aria-controls,
aria-activedescendant) and hierarchy indentation based on parent_id
depth. Adds reports.category.searchPlaceholder in FR/EN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maximus added the
status:review
label 2026-04-19 01:08:15 +00:00
Author
Owner

Revue — APPROVE

Verdict

Bonne découverte : CategoryCombobox existait déjà dans src/components/shared/ et était utilisé par 4 composants. L'Agent l'a étendu plutôt que dupliquer, ce qui est la bonne décision.

Points validés

  • ARIA complet ajouté : role="combobox", aria-expanded, aria-controls, aria-autocomplete="list", aria-activedescendant, role="listbox", role="option", aria-selected. useId() pour générer des IDs uniques et éviter les collisions entre instances.
  • Indentation hiérarchique via computeDepths() avec garde-fou de cycle (Set<seen>). Utilise paddingLeft: calc(...) + préfixe texte pour les lecteurs d'écran.
  • Matching accent-insensible : la fonction normalize() (NFD + strip diacritics) est hissée en top-level (plus re-créée à chaque render).
  • CategoryZoomHeader.tsx mappe proprement CategoryRowCategory (avec created_at: "" placeholder, acceptable car le combobox ne l'utilise pas).
  • i18n FR/EN (reports.category.searchPlaceholder) ajoutés.
  • CHANGELOG entrées sous Changed / Modifié.

Impact non-intentionnel mais bénéfique

L'indentation est désormais appliquée partoutCategoryCombobox est utilisé (TransactionFilterBar, TransactionTable, SplitAdjustmentModal, PageHelp). C'est un gain UX gratuit — les autres consommateurs n'affichaient pas la hiérarchie auparavant.

Vérifications locales

  • npm test -- --run : 100/100 verts
  • npm run build :

Suggestions non-bloquantes

  • Le mapping CategoryRow → Category avec created_at: "" est un signe que le type Category est trop large pour ce use case — un CategoryLite dédié simplifierait. Follow-up optionnel.

Rien ne bloque le merge.

## Revue — APPROVE ✅ ### Verdict Bonne découverte : `CategoryCombobox` existait déjà dans `src/components/shared/` et était utilisé par 4 composants. L'Agent l'a étendu plutôt que dupliquer, ce qui est la bonne décision. ### Points validés - **ARIA complet** ajouté : `role="combobox"`, `aria-expanded`, `aria-controls`, `aria-autocomplete="list"`, `aria-activedescendant`, `role="listbox"`, `role="option"`, `aria-selected`. `useId()` pour générer des IDs uniques et éviter les collisions entre instances. - **Indentation hiérarchique** via `computeDepths()` avec garde-fou de cycle (`Set<seen>`). Utilise `paddingLeft: calc(...)` + préfixe texte pour les lecteurs d'écran. - **Matching accent-insensible** : la fonction `normalize()` (NFD + strip diacritics) est hissée en top-level (plus re-créée à chaque render). - `CategoryZoomHeader.tsx` mappe proprement `CategoryRow` → `Category` (avec `created_at: ""` placeholder, acceptable car le combobox ne l'utilise pas). - i18n FR/EN (`reports.category.searchPlaceholder`) ajoutés. - CHANGELOG entrées sous `Changed` / `Modifié`. ### Impact non-intentionnel mais bénéfique L'indentation est désormais appliquée **partout** où `CategoryCombobox` est utilisé (TransactionFilterBar, TransactionTable, SplitAdjustmentModal, PageHelp). C'est un gain UX gratuit — les autres consommateurs n'affichaient pas la hiérarchie auparavant. ### Vérifications locales - `npm test -- --run` : 100/100 verts - `npm run build` : ✅ ### Suggestions non-bloquantes - Le mapping `CategoryRow → Category` avec `created_at: ""` est un signe que le type `Category` est trop large pour ce use case — un `CategoryLite` dédié simplifierait. Follow-up optionnel. Rien ne bloque le merge.
maximus merged commit 8d916a1283 into main 2026-04-19 01:10:26 +00:00
maximus deleted branch issue-103-category-combobox 2026-04-19 01:10:27 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#108
No description provided.