diff --git a/DeepDrftManager/Components/Pages/Tracks/CmsGenreBrowser.razor b/DeepDrftManager/Components/Pages/Tracks/CmsGenreBrowser.razor
new file mode 100644
index 0000000..e6e1901
--- /dev/null
+++ b/DeepDrftManager/Components/Pages/Tracks/CmsGenreBrowser.razor
@@ -0,0 +1,52 @@
+@using DeepDrftModels.DTOs
+
+@if (IsLoading)
+{
+
+}
+else if (Genres.Count == 0)
+{
+ No genres found.
+}
+else
+{
+
+ @foreach (var genre in Genres)
+ {
+ var isExpanded = ExpandedGenre == genre.Genre;
+
+ ToggleGenre(genre.Genre))">
+
+
+ @genre.Genre
+ @genre.TrackCount track(s)
+
+
+
+ }
+
+
+ @if (ExpandedGenre is not null)
+ {
+
+ @ExpandedGenre
+
+ }
+}
+
+@code {
+ [Parameter] public IReadOnlyList Genres { get; set; } = Array.Empty();
+ [Parameter] public bool IsLoading { get; set; }
+ [Parameter] public string? ExpandedGenre { get; set; }
+ [Parameter] public EventCallback OnExpandedGenreChanged { get; set; }
+
+ // The view model owns the toggle (selecting the open genre collapses it), so we pass the raw
+ // clicked genre rather than pre-computing the next state here — keeps the toggle logic single-sourced.
+ private async Task ToggleGenre(string genre) =>
+ await OnExpandedGenreChanged.InvokeAsync(genre);
+
+ private static string SwatchClass(bool isExpanded) =>
+ isExpanded ? "cms-genre-swatch cms-genre-swatch--active" : "cms-genre-swatch";
+}
diff --git a/DeepDrftManager/Components/Pages/Tracks/CmsGenreBrowser.razor.css b/DeepDrftManager/Components/Pages/Tracks/CmsGenreBrowser.razor.css
new file mode 100644
index 0000000..34f29a3e
--- /dev/null
+++ b/DeepDrftManager/Components/Pages/Tracks/CmsGenreBrowser.razor.css
@@ -0,0 +1,10 @@
+.cms-genre-swatch {
+ width: 100%;
+ height: 80px;
+ background-color: var(--mud-palette-action-default-hover);
+ transition: background-color 0.2s ease;
+}
+
+.cms-genre-swatch--active {
+ background-color: var(--mud-palette-primary-hover);
+}
diff --git a/DeepDrftManager/Components/Pages/Tracks/TrackList.razor b/DeepDrftManager/Components/Pages/Tracks/TrackList.razor
index 9db5052..3372307 100644
--- a/DeepDrftManager/Components/Pages/Tracks/TrackList.razor
+++ b/DeepDrftManager/Components/Pages/Tracks/TrackList.razor
@@ -59,7 +59,10 @@
}
else
{
- Genre browser — coming in the next wave.
+
}
@@ -99,6 +102,12 @@
StateHasChanged();
}
+ private void OnExpandedGenreChanged(string? genre)
+ {
+ VM.SetExpandedGenre(genre);
+ StateHasChanged();
+ }
+
///
/// Backfill every track missing a waveform profile, one request at a time so a large backfill
/// does not flood the API with concurrent WAV decodes. On completion, refreshes the grid's