docs: spec Track Gallery View Toggle (grid hover-reveal + list mode) in PLAN.md

This commit is contained in:
daniel-c-harvey
2026-06-08 07:49:42 -04:00
parent 9169493d41
commit 2eebc04733
+63
View File
@@ -75,6 +75,69 @@ These were flagged during the audit but classified as feature work, not defect f
---
## Track Gallery View Toggle
### Overview
Give the track gallery two switchable view modes behind a page-level toggle: **Mode A — Album Art Grid** (the current responsive 4-column `MudGrid` of 250×250 cards, augmented so that art-bearing cards hide their info overlay at rest and reveal it on hover) and **Mode B — Track Detail List** (a vertical stack of full-width horizontal rows, each a compact track line with play FAB, art thumbnail, artist/title text block, and right-aligned genre/year). The toggle is a two-option control at the top of `TracksView`, defaulting to Grid, with ephemeral page-level state (not persisted). Both modes consume the same `ViewModel.Page.Items` and the same per-card play-state inputs — the only divergence is in `TrackCard`'s rendering, consistent with the "one source, multiple views" convention (`CONTEXT.md §6`).
### Component changes
- **`TracksView.razor` / `.razor.cs` / `.razor.css`** — Add an ephemeral `ViewMode _viewMode = ViewMode.Grid` field and a handler that flips it and calls `StateHasChanged()`. Render the toggle control above `tracks-content` (see Toggle spec). Pass `ViewMode="@_viewMode"` into `<TracksGallery>`. No change to data flow, persistence, or player-state subscription. CSS: a flex row for the toggle header (`justify-content: flex-end`).
- **`TracksGallery.razor` / `.razor.cs` / `.razor.css`** — Add `[Parameter] public ViewMode ViewMode { get; set; } = ViewMode.Grid;`. Branch the template: for `Grid`, keep the existing `MudGrid` / `MudItem` breakpoint layout unchanged; for `List`, render a single flex-column container (`deepdrft-track-list`) that `@foreach`-es the same `Tracks` into `<TrackCard>` rows with no `MudGrid` wrapper. Pass `ViewMode="@ViewMode"` down to each `TrackCard`. The `ActiveTrack` / `IsPlaying` / `IsPaused` / `OnPlay` / `OnPause` wiring is identical in both branches.
- **`TrackCard.razor` / `.razor.cs` / `.razor.css`** — Add `[Parameter] public ViewMode ViewMode { get; set; } = ViewMode.Grid;`. Branch the markup at the top: `ViewMode.Grid` renders the existing card body unchanged (plus the hover behaviour below); `ViewMode.List` renders the horizontal row layout (see Mode B spec). The `hasLink` / `trackHref` computation, `PlayClick`, and `PlayPauseIcon` are shared across both. The `ViewMode` enum lives in a small shared file (e.g. `Controls/GalleryViewMode.cs` or alongside `TrackCard.razor.cs` in the `DeepDrftPublic.Client.Controls` namespace) so both `TracksView`, `TracksGallery`, and `TrackCard` reference one definition.
### Mode A — hover spec (pure CSS, no JS)
- Applies **only** when the card has album art (`deepdrft-track-card-bg` present). The no-art fallback path (`deepdrft-track-card-fallback`) is untouched — its `deepdrft-track-card-content` stays visible at all times exactly as today.
- For art-bearing cards: give `deepdrft-track-card-content` an `opacity: 0` rest state and `opacity: 1` on `.deepdrft-track-card-container:hover .deepdrft-track-card-content`. Add `transition: opacity 180ms ease, background-color 180ms ease`.
- Swap the rest gradient for a **solid navy panel on hover**: at rest the content overlay is transparent/hidden; on hover its background becomes `var(--deepdrft-navy-mid, #162437)` (opaque, full-card) so the info reads cleanly over the art rather than through a gradient. Implement by toggling the `background` on the content layer between transparent (rest) and solid navy (hover), or by fading in a sibling navy panel beneath the content — implementer's call; the observable result is a solid navy reveal, not the current always-on gradient.
- Distinguish art vs. no-art in CSS without new markup by scoping the hide/reveal rules to a container modifier. Add a class to the container when art is present (e.g. `deepdrft-track-card-container--art`) and gate the `opacity: 0` rest rule on it, so fallback cards never pick up the hidden-at-rest behaviour.
- Touch devices have no hover; on coarse pointers the overlay should default to visible. Guard the hidden-at-rest rule with `@media (hover: hover) and (pointer: fine)` so touch users always see the info.
### Mode B — list row spec
- Container: `deepdrft-track-list` is `display: flex; flex-direction: column; gap: 8px;` inside the existing `MudContainer MaxWidth="Large"`. Rows are full-width.
- Row (`deepdrft-track-row`): `display: flex; flex-direction: row; align-items: center; gap: 16px;` with `height: ~7288px`, `padding: 8px 16px`, and the same glass treatment as grid cards — `background: var(--deepdrft-navy-mid, #162437)`, off-white text, `border: 1px solid rgba(250,250,248,0.12)`. This reads on both light and dark themes (matches the fallback-panel rationale already documented in `TrackCard.razor.css`).
- Columns, left to right:
1. **Play FAB** — fixed-width column, vertically centered. Same `<MudFab Color="Color.Tertiary" Size="Size.Medium" StartIcon="@PlayPauseIcon" OnClick="@PlayClick"/>` as grid mode (reuse, do not duplicate logic).
2. **Art thumbnail** — square `~64px` (`flex: 0 0 64px`), vertically centered. Reuse the art `background-image` div for art-present; a `deepdrft-track-card-fallback`-style navy square for art-absent.
3. **Text block**`flex: 1 1 auto; min-width: 0;` two stacked rows: Artist (`Typo.subtitle1`, `deepdrft-track-artist`-weight) on top, Track Name (`Typo.caption`/body, `deepdrft-track-title`) below. Both `text-truncate`. Note the visual order here is Artist-over-Title, inverse of the grid card — intentional per the row sketch.
4. **Right metadata** — fixed/`flex: 0 0 auto` column, `text-align: right`, two stacked rows: Genre chip (`MudChip`, same green-accent outline styling) top-right, Year caption bottom-right.
- Linking: wrap the art + text columns in the same `<a href="@trackHref" class="deepdrft-track-card-link">` pattern used by the grid card, so the row navigates to `/track/{EntryKey}` while the FAB (outside the anchor) remains the sole playback entry point. Preserve the `display: contents` approach so the flex row layout is unaffected by the anchor.
- The active-state icon (`PlayPauseIcon` driven by `IsPlaying`/`IsPaused`) works identically — no list-specific play-state logic.
### Toggle spec
- Component: `MudToggleGroup<ViewMode>` with two `MudToggleItem`s (icon-only), or a pair of `MudToggleIconButton`s — `MudToggleGroup` is the cleaner fit for a 2-value exclusive switch. Icons: `Icons.Material.Filled.ViewModule` (Grid) and `Icons.Material.Filled.ViewList` (List).
- Placement: top of `TracksView`, above `tracks-content`, aligned right. Sits in its own header row; does not displace the existing centered gallery or the footer pagination.
- Binding: `@bind-Value="_viewMode"` (or `SelectedValue` + `SelectedValueChanged`) on the toggle; the setter triggers re-render. State is a plain page field — **not** persisted to cookie or `PersistentComponentState`.
- Default: `ViewMode.Grid`.
- Skeleton/loading state (`ViewModel.Page == null`) is unaffected — keep the existing skeleton grid; the toggle may render disabled or hidden while loading (implementer's call).
### Acceptance criteria
- The TracksView page shows a two-option grid/list toggle, right-aligned at the top, defaulting to grid.
- **Grid mode, art card:** at rest the card shows only album art (no title/artist/genre/year/FAB overlay); on hover a solid navy panel fades in over the art revealing all info and the play FAB; moving the pointer away hides it again. Transition is smooth (~180ms), no flicker.
- **Grid mode, no-art card:** the navy fallback card shows title/artist/genre/year/FAB at all times, with no hover change — identical to current behaviour.
- **Touch / coarse-pointer devices:** grid art cards show their info overlay by default (no permanently hidden info).
- **List mode:** tracks render as a vertical stack of full-width rows, each ≤~88px tall, with play FAB at far left, ~64px art thumbnail (or navy placeholder), artist-over-title text block, and right-aligned genre chip over year.
- Clicking a row (outside the FAB) navigates to that track's detail page; clicking the FAB plays/pauses without navigating, in both modes.
- The play/pause icon and active state reflect the live player exactly as in grid mode, in both modes.
- List rows are legible on both light and dark themes.
- Toggling between modes is instant, preserves the current page and player state, and resets to grid on page reload (no persistence).
### Out of scope
- Persisting the selected view mode (cookie / `PersistentComponentState` / query string) — explicitly ephemeral this ticket.
- Mobile-specific gestures (long-press, swipe) beyond the coarse-pointer hover fallback above.
- Keyboard navigation beyond what the anchor + `MudFab` give by default; no roving-tabindex or arrow-key list traversal.
- Any change to sorting, filtering, pagination, or the `TracksViewModel` data path.
- Album/genre grouping views (covered separately under Phase 2.2).
- Animation of mode transitions (cards/rows reflowing) — a plain re-render is acceptable.
---
## Phase 2 — Product surface: gallery, browsing, ingestion
These follow from `CONTEXT.md §5`. Direction is strongly implied but no specific UI has been committed.