Updated: 2026-07-14 12:20 ET
Audited: 2026-07-14 12:20 ET
ALPAMobile.Presentation/Components/Library/SavedSearchCard.razor · decision D55 (2026-07-14)flight card-saved & recent searches).
.alpa-saved-card* in ALPAMobile/wwwroot/css/alpa-components.css) and the
Figma frames in file key R6hPjBdjSIn6946qTJasUC: Recent Searches 4713:25203, Saved Searches 4713:25253
(row instance flight card-saved & recent searches, e.g. 0:284), Saved Flights 4713:25365.
Captured 2026-06-29.
SwipeRevealRow (Presentation library) — flick left reveals the Figma action tray (100 px zones,
22 px white icons; Recent → save heart, Saved → trash per 4713:25203/4713:25253), flick right or a
body tap closes it, and tapping the row body re-runs the search. The persistent Action Column was removed from these pages;
the component keeps its optional in-row ActionColumn only as a legacy shape for other consumers. The gesture is the repo's
touchstart/touchend flick idiom (no native SwipeView exists in a WebView), and UI tests drive the tray via synthesized
TouchEvents because Appium's native swipe is unreliable inside WKWebView.
Row 1 (Recent Searches): unsaved item — outline heart toggles to filled/red on save, search-history re-runs the query. Row 2 (Saved Searches): a saved item — always filled/red, tap removes it from the list (no unsave/toggle state). Both compose the shared Action Column atom (D53/D54) — icons are real exported vectors, not text glyphs. The optional Status Badge ("Round-trip" here) composes the shared atom, but no current consumer page actually sets it — illustrative only.
Tier: domain control (standalone — does not derive from a card base). Implemented (D55) in both
ALPAMobile/Components/ViewModels/ComponentViewModels.cs (live) and ALPAMobile.Presentation/ViewModels/SavedSearchCardViewModel.cs (staged copy).
Items and handles ActionKey
No IsSaved/OnToggleSave/OnReSearch parameters — those were the original spec-only draft's guess at a fixed
2-action shape. The real atom (D53) is a generic ActionColumnViewModel composed as Actions; the card renders whatever
items the caller puts there and raises a single OnActionClicked, keyed by ActionKey.
Home: ALPAMobile.Presentation/Components/Library/SavedSearchCard.razor (migrated 2026-07-15, WI-2254/D61).
| Parameter | Type | Required | Description |
|---|---|---|---|
Vm | SavedSearchCardViewModel | Yes | Route, meta, optional badge, composed Actions. |
OnActionClicked | EventCallback<ActionColumnItemViewModel> | No | Fires with the clicked action item — switch on item.ActionKey. |
JumpseatRecentSearchesPage.razor)@foreach (var (search, i) in _searches.Select((s, i) => (s, i)))
{
<SavedSearchCard Vm="@BuildVm(search, i)"
OnActionClicked="@(item => OnActionClicked(item, search, i))" />
}
@code {
private SavedSearchCardViewModel BuildVm(SavedSearch search, int index)
{
var saved = _savedIndices.Contains(index);
return new SavedSearchCardViewModel
{
Origin = search.Origin, Destination = search.Destination,
TripType = search.TripType, Date = search.Date,
Actions = new ActionColumnViewModel
{
Items = [
new ActionColumnItemViewModel { ActionKey = "toggle-save",
Icon = saved ? ActionColumnIconAsset.HeartSelected : ActionColumnIconAsset.Heart,
Title = saved ? "Saved" : "Save search",
Variant = saved ? ActionColumnItemVariant.Accent : ActionColumnItemVariant.Neutral },
new ActionColumnItemViewModel { ActionKey = "search-again",
Icon = ActionColumnIconAsset.SearchHistory, Title = "Search again",
Variant = ActionColumnItemVariant.Highlight },
],
},
};
}
private void OnActionClicked(ActionColumnItemViewModel item, SavedSearch search, int index)
{
switch (item.ActionKey)
{
case "toggle-save": ToggleSave(index); break;
case "search-again": SearchAgain(search); break;
}
}
}
Classes live in ALPAMobile/wwwroot/css/alpa-components.css under the alpa-saved-card BEM block.
The action buttons are the shared Action Column vocabulary (.alpa-action-column*), not bespoke to this control.
| Class | Scope | Key rules |
|---|---|---|
.alpa-saved-card | Card | display:flex; align-items:center; background:white; border-bottom:1px solid rgba(0,0,0,0.08); padding:14px 16px; gap:12px |
.alpa-saved-card-body | Body | flex:1; min-width:0 |
.alpa-saved-card-route | Route row | display:flex; align-items:center; gap:6px; margin-bottom:4px |
.alpa-saved-card-iata | IATA | font-size:18px; font-weight:700; color:var(--navy); letter-spacing:1px |
.alpa-saved-card-arrow | Arrow | font-size:18px; color:var(--gray) |
.alpa-saved-card-meta | Meta | font-size:12px; color:var(--gray) |
| List | Control | Trailing actions |
|---|---|---|
| Saved / Recent Searches | Saved Search Card (this spec) | save Heart/HeartSelected · re-search SearchHistory (swipe-to-delete in Figma, not built) |
| Saved Flights | Saved Flight Card (standalone, this section) | alert Notifications (colour-only on/off) · delete Trash (Compact) |
Home: ALPAMobile.Presentation/Components/Library/SavedFlightCard.razor (migrated 2026-07-15, WI-2254/D61).
| Parameter | Type | Required | Description |
|---|---|---|---|
Vm | SavedFlightCardViewModel | Yes | Route, carrier/flight details, times, details link, composed Actions. |
OnActionClicked | EventCallback<ActionColumnItemViewModel> | No | Fires with the clicked action item — toggle-alert or delete. |
.alpa-saved-flight-card-body is a link (<a>) to DetailsRoute; the trailing Action Column sits outside it.
The former duplicate .alpa-saved-flight-card-actions CSS rule (byte-identical to .alpa-action-column) was removed —
the page composes <ActionColumn> directly, no wrapper class needed.
SavedSearchCardViewModel nor SavedFlightCardViewModel derives from CardViewModel — a saved query/flight has its own shape (D40).IsSaved/OnToggleSave/OnReSearch shape. The real atom is generic — each page builds its own ActionColumnItemViewModel items and routes on ActionKey._savedIndices/_alertedIndices), per the project's interactive-control rule.SavedSearchCard (Figma: flight card-saved & recent searches); the list source and action behavior differ, not the row.ComponentView.razor's dispatcher — unlike the shared atoms, these are page-composed domain controls with no ItemComponentFactory-driven dynamic-feed path; all 3 Jumpseat lists are local hardcoded mock records.