Rework navigation for a consistent music/room toggle and back button
- Replace the "Mein Zimmer"/"Musik" text pills and the help button with
two fixed circular corner buttons: a music/room toggle (top-right)
and a single "back one level" button (top-left) used everywhere -
search, categories, and the play view.
- Split `view` (browse/play) from a new `page` (music/room) in UiState
so toggling to the room and back leaves the music side - search,
selection, even the full-screen play view - exactly as it was.
- Back peels one step at a time (search, then track-search mode, then
group+category together, since a root shelf tile sets both at once
and undoing that jump should be one step too).
- Album cards: click the cover to open the track list, click the title
to start playing immediately (matching what Enter already did from
the keyboard). Audiobook cards drop the artist line and clamp the
title to a fixed height so covers stay aligned across a row.
- Root shelf group headings ("Musik", "Hörbücher", "Podcasts") are now
the link into that group's full category grid, replacing the
separate search-icon button.
- "Taste zuweisen" is now an icon-only round button, moved to the
bottom-right so it doesn't sit under the new toggle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,9 +32,12 @@ interface Props {
|
||||
currentAlbumId: string | null;
|
||||
gridRef: (element: HTMLElement | null) => void;
|
||||
onEnterGroup: (group: Group, category: string | null) => void;
|
||||
onBackToRoot: () => void;
|
||||
onCategory: (key: string | null) => void;
|
||||
/** Cover click: open the track list (or, for a podcast episode, play it - there's
|
||||
* nothing to pick between). */
|
||||
onOpenAlbum: (album: Album, navIndex: number) => void;
|
||||
/** Title click: start the album from the top right away. */
|
||||
onPlayAlbum: (album: Album, navIndex: number) => void;
|
||||
onPlaySong: (hit: SongHit) => void;
|
||||
}
|
||||
|
||||
@@ -71,9 +74,9 @@ export function BrowseView({
|
||||
currentAlbumId,
|
||||
gridRef,
|
||||
onEnterGroup,
|
||||
onBackToRoot,
|
||||
onCategory,
|
||||
onOpenAlbum,
|
||||
onPlayAlbum,
|
||||
onPlaySong,
|
||||
}: Props) {
|
||||
const scroller = useRef<HTMLDivElement | null>(null);
|
||||
@@ -116,34 +119,26 @@ export function BrowseView({
|
||||
<div ref={scroller} style={{ flex: 1, overflow: "auto", minHeight: 0, padding: "14px 32px 250px" }}>
|
||||
{shelves.map(({ group: shelfGroup, categories: shelfCategories }) => (
|
||||
<div key={shelfGroup} style={{ marginBottom: 34 }}>
|
||||
<div
|
||||
<button
|
||||
onClick={() => onEnterGroup(shelfGroup, null)}
|
||||
aria-label={`Alle ${GROUP_TITLE[shelfGroup]} durchsuchen`}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
gap: 12,
|
||||
gap: 6,
|
||||
marginBottom: 12,
|
||||
border: "none",
|
||||
background: "none",
|
||||
padding: 0,
|
||||
cursor: "pointer",
|
||||
fontSize: 20,
|
||||
fontWeight: 900,
|
||||
color: "var(--paper)",
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 20, fontWeight: 900, color: "var(--paper)" }}>
|
||||
{GROUP_TITLE[shelfGroup]}
|
||||
</div>
|
||||
<button
|
||||
className="round"
|
||||
onClick={() => onEnterGroup(shelfGroup, null)}
|
||||
aria-label={`${GROUP_TITLE[shelfGroup]} durchsuchen`}
|
||||
title="Nur hier suchen"
|
||||
style={{
|
||||
width: 34,
|
||||
height: 34,
|
||||
fontSize: 15,
|
||||
background: "oklch(97% 0.01 210 / .18)",
|
||||
color: "var(--paper)",
|
||||
}}
|
||||
>
|
||||
🔎
|
||||
</button>
|
||||
</div>
|
||||
{GROUP_TITLE[shelfGroup]}
|
||||
<span style={{ opacity: 0.6, fontSize: 17 }}>›</span>
|
||||
</button>
|
||||
{shelfCategories.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
@@ -185,20 +180,15 @@ export function BrowseView({
|
||||
{group !== null && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 10,
|
||||
textAlign: "center",
|
||||
padding: "4px 32px 6px",
|
||||
flex: "none",
|
||||
fontSize: 18,
|
||||
fontWeight: 900,
|
||||
color: "var(--paper)",
|
||||
}}
|
||||
>
|
||||
<button className="pill" onClick={onBackToRoot}>
|
||||
← Start
|
||||
</button>
|
||||
<div style={{ fontSize: 18, fontWeight: 900, color: "var(--paper)" }}>
|
||||
{GROUP_TITLE[group]}
|
||||
</div>
|
||||
{GROUP_TITLE[group]}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -352,23 +342,14 @@ export function BrowseView({
|
||||
{category !== null && !search && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 12,
|
||||
textAlign: "center",
|
||||
marginBottom: 14,
|
||||
fontSize: 22,
|
||||
fontWeight: 900,
|
||||
color: "var(--paper)",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="pill"
|
||||
data-active="true"
|
||||
onClick={() => onCategory(null)}
|
||||
>
|
||||
← Alle
|
||||
</button>
|
||||
<div style={{ fontSize: 22, fontWeight: 900, color: "var(--paper)" }}>
|
||||
{category}
|
||||
</div>
|
||||
{category}
|
||||
</div>
|
||||
)}
|
||||
{search.length > 0 && group !== null && (
|
||||
@@ -378,23 +359,51 @@ export function BrowseView({
|
||||
{albumResults.map((album, index) => {
|
||||
const navIndex = songs.length + categories.length + index;
|
||||
const podcast = groupOf(album) === "podcasts";
|
||||
// An audiobook's artist is almost always the same as the character/
|
||||
// series it's already grouped under, so dropping it gives the title
|
||||
// more room. A podcast's artist is the show name, which does carry
|
||||
// information, so it stays.
|
||||
const showArtist = groupOf(album) !== "audiobooks";
|
||||
// Fixed regardless of how many lines the title actually needs, so
|
||||
// every card - and thus every row of covers - is the same height.
|
||||
const titleLines = showArtist ? 2 : 3;
|
||||
return (
|
||||
<button
|
||||
<div
|
||||
key={album.id}
|
||||
className="card"
|
||||
data-nav-index={navIndex}
|
||||
data-selected={selected === navIndex}
|
||||
data-current={album.id === currentAlbumId}
|
||||
onClick={() => onOpenAlbum(album, navIndex)}
|
||||
style={{
|
||||
background: cardBackground(album),
|
||||
borderRadius: isBook(album) ? "6px 18px 18px 6px" : "16px",
|
||||
boxShadow: cardShadow(album),
|
||||
}}
|
||||
>
|
||||
<Cover album={album} size={180} radius="0" label />
|
||||
<div
|
||||
<button
|
||||
onClick={() => onOpenAlbum(album, navIndex)}
|
||||
aria-label={podcast ? `${album.title} abspielen` : `${album.title}: Titel wählen`}
|
||||
style={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
border: "none",
|
||||
padding: 0,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<Cover album={album} size={180} radius="0" label />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onPlayAlbum(album, navIndex)}
|
||||
aria-label={`${album.title} abspielen`}
|
||||
style={{
|
||||
display: "block",
|
||||
width: "100%",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
textAlign: "left",
|
||||
font: "inherit",
|
||||
background: "none",
|
||||
padding: "10px 12px 14px",
|
||||
paddingRight: isBook(album) ? 22 : 12,
|
||||
}}
|
||||
@@ -405,20 +414,30 @@ export function BrowseView({
|
||||
fontWeight: 800,
|
||||
color: "oklch(22% 0.03 210)",
|
||||
lineHeight: 1.2,
|
||||
height: `${titleLines * 1.2}em`,
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: titleLines,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{album.title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
color: "oklch(30% 0.03 210)",
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
{album.artist}
|
||||
</div>
|
||||
{showArtist && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
color: "oklch(30% 0.03 210)",
|
||||
marginTop: 2,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{album.artist}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
fontSize: 12,
|
||||
@@ -430,8 +449,8 @@ export function BrowseView({
|
||||
{podcast ? clock(album.duration) : unitLabel(album, album.tracks.length)}
|
||||
{album.figure && " · 🧸"}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user