From 0534b373f072dd61484655c7dc65ee46343a09fc Mon Sep 17 00:00:00 2001 From: gnezim Date: Fri, 17 Apr 2026 14:25:52 +0300 Subject: [PATCH] Add design spec for CityAutocomplete + regional picker parity Cross-feature Angular-parity component: composite CityAutocomplete (typeahead + clear + regional-picker trigger button) + CityPickerPopup (regional tabs + country/city grid + optional GPS) + pure buildCountryCityRows helper. Consumers: OnlineBoardFilter Route tab and FlightsMapFilter. Time-selector switches to compact view in both. --- ...ity-autocomplete-regional-picker-design.md | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-17-city-autocomplete-regional-picker-design.md diff --git a/docs/superpowers/specs/2026-04-17-city-autocomplete-regional-picker-design.md b/docs/superpowers/specs/2026-04-17-city-autocomplete-regional-picker-design.md new file mode 100644 index 00000000..b67bada3 --- /dev/null +++ b/docs/superpowers/specs/2026-04-17-city-autocomplete-regional-picker-design.md @@ -0,0 +1,261 @@ +# City Autocomplete + Regional Picker — Design + +**Date:** 2026-04-17 +**Scope:** Cross-feature Angular-parity component for city selection, used by OnlineBoard Route filter and Flights Map filter. +**Status:** Approved +**Depends on:** Flights Map C.1 (dictionaries) — landed. + +## Goal + +Bring React's city-selection inputs to full Angular parity by introducing a composite `` component (label + typeahead + clear button + regional-picker trigger button) plus a `` (regional tabs + country/city grid + optional GPS). Apply to both OnlineBoardFilter (Route tab, departure + arrival) and FlightsMapFilter (departure + arrival). Also switch the time-selector in both to Angular's compact (`fullView=false`) layout. + +## Non-Goals + +- New feature-flag infrastructure. GPS button visibility is driven by whether the consumer passes `onLocate`. +- Changes to the Flight-Number tab's flight number input — that's a different composite (`number-input-composite`) that already matches Angular. +- Changes to dictionary data or transform rules (C.1 is unchanged). +- Replacing PrimeReact's `` primitive — it stays as the internal typeahead engine. + +## Architecture + +``` +src/ui/city-autocomplete/ +├── CityAutocomplete.tsx Composite: label + typeahead + clear + popup-trigger +├── CityAutocomplete.scss +├── CityAutocomplete.test.tsx +├── CityPickerPopup.tsx Regional tabs + country/city grid + optional GPS +├── CityPickerPopup.scss +├── CityPickerPopup.test.tsx +├── buildCountryCityRows.ts Pure row-building logic (Angular parity) +├── buildCountryCityRows.test.ts +└── index.ts Barrel +``` + +Consumer changes: +- `src/features/online-board/components/OnlineBoardFilter.tsx` — Route tab uses `` for both departure + arrival; removes the two inline `` blocks and their `useCitySearch` usage for the Route tab only (Flight-Number tab unchanged). +- `src/features/flights-map/components/FlightsMapFilter.tsx` — both inputs use ``; departure receives `onLocate` callback wired to `navigator.geolocation.getCurrentPosition` + `findCityByCoord`. +- Both pages: time-selector wrapper switches from `full-view` to `compact-view` class, layout reshapes to label+value single row. + +## Component: `CityAutocomplete` + +**Props:** + +```ts +interface CityAutocompleteProps { + label: string; + placeholder: string; + value: string; // IATA code (empty when cleared) + onChange: (code: string) => void; + dictionaries: IDictionaries | null; + onLocate?: () => void | Promise; // hides GPS button when undefined + error?: string; // translation key, shown as tooltip + testIdPrefix?: string; // default "city-autocomplete" +} +``` + +**Behavior:** + +- Typeahead search order (matches Angular `filterCity`): + 1. Exact 3-char uppercase city code → one suggestion. + 2. Substring match on `city.name` (localized) → up to 15 results. + 3. Airport code → resolve to city, one suggestion. +- Clear button (`×`) resets `value` to `""`. SCSS `--has-value` shows it only when a city is selected. +- Popup-trigger button (square, right of input) toggles ``. +- Outside-click closes the popup via `document.addEventListener("mousedown", ...)`. +- `error` prop: renders tooltip under the label row, applies `--has-error` class to input container. +- When `value` changes externally (e.g., geolocation sets departure), internal input state syncs via `useEffect`. + +**Internal structure (JSX outline):** + +``` +
+
+ + +
+ {error &&
{error}
} +
+ ← PrimeReact typeahead +
+ {popupOpen && } +
+``` + +## Component: `CityPickerPopup` + +**Props:** + +```ts +interface CityPickerPopupProps { + dictionaries: IDictionaries; + selectedCode?: string; + onSelect: (code: string) => void; + onLocate?: () => void | Promise; +} +``` + +**Behavior:** + +- Renders one `