From 4dbf79f2963495dbe038b321a1bb5be3072c9f76 Mon Sep 17 00:00:00 2001 From: Dylan Audius Date: Mon, 19 Oct 2026 15:23:13 -0700 Subject: [PATCH 1/2] feat(web): TrackRow on profile Tracks and Reposts behind track_rows The profile page passes the row variant to its Tracks and Reposts lineups when the `track_rows` flag is on. `TrackRow` gains an optional `context` line above the row, which the connected row uses to label the artist pick. In row mode the lineup drops the two-column grid it uses for small tiles so rows span the full width. Feed stays on tiles on purpose: a feed item is a post with who reposted it and inline playlists, and the tile carries that better than a row. Co-Authored-By: Claude Fable 5.1 --- .../src/components/track-row/TrackRow.tsx | 30 ++++++++++++++++--- .../harmony/src/components/track-row/types.ts | 5 ++++ .../web/src/components/lineup/TrackLineup.tsx | 5 +++- .../components/track/desktop/TrackRowTile.tsx | 16 +++++++++- .../components/desktop/ProfilePage.tsx | 9 ++++++ 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/packages/harmony/src/components/track-row/TrackRow.tsx b/packages/harmony/src/components/track-row/TrackRow.tsx index e7a7c449b0b..30d71033363 100644 --- a/packages/harmony/src/components/track-row/TrackRow.tsx +++ b/packages/harmony/src/components/track-row/TrackRow.tsx @@ -84,6 +84,7 @@ export const TrackRow = forwardRef( onTogglePlay, onToggleFavorite, menu, + context, playLabel = isPlaying ? 'Pause' : 'Play', favoriteLabel = isFavorited ? 'Unfavorite' : 'Favorite', className, @@ -123,6 +124,16 @@ export const TrackRow = forwardRef( [onToggleFavorite] ) + // Title column starts after the rank and artwork cells and two gaps. + const contextCss = css({ + display: 'flex', + alignItems: 'center', + gap: spacing.xs, + height: 16, + marginTop: spacing.xs, + paddingLeft: spacing.s + RANK_SIZE + ART_SIZE + spacing.m * 2 + }) + const rowCss = css({ display: 'grid', gridTemplateColumns: `${RANK_SIZE}px ${ART_SIZE}px minmax(0, 1fr) auto 32px 48px 32px`, @@ -189,14 +200,14 @@ export const TrackRow = forwardRef( const showEqualizer = isActive && isPlaying const PlayIcon = showEqualizer ? IconPause : IconPlay - return ( + const row = (
{rank != null && !showEqualizer ? ( @@ -297,5 +308,16 @@ export const TrackRow = forwardRef(
) + + if (!context) return row + + return ( +
+ + {context} + + {row} +
+ ) } ) diff --git a/packages/harmony/src/components/track-row/types.ts b/packages/harmony/src/components/track-row/types.ts index 9669b278ff3..722bc2aebac 100644 --- a/packages/harmony/src/components/track-row/types.ts +++ b/packages/harmony/src/components/track-row/types.ts @@ -32,6 +32,11 @@ export type TrackRowProps = { onToggleFavorite?: () => void /** Overflow menu trigger. Hidden until hover or focus. */ menu?: ReactNode + /** + * One short line above the row, aligned with the title column, for + * context such as "Artist Pick". Adds 16px to the row's height. + */ + context?: ReactNode /** Accessible name for the play button. */ playLabel?: string /** Accessible name for the favorite button. */ diff --git a/packages/web/src/components/lineup/TrackLineup.tsx b/packages/web/src/components/lineup/TrackLineup.tsx index c743ad9fe3f..d7990d69ace 100644 --- a/packages/web/src/components/lineup/TrackLineup.tsx +++ b/packages/web/src/components/lineup/TrackLineup.tsx @@ -287,7 +287,10 @@ export const TrackLineup = ({ } else if (variant === LineupVariant.GRID) { tileSize = TrackTileSize.SMALL statSize = 'small' - containerClassName = styles.searchTrackTileContainer + // Rows are full width; the two-column grid is for small tiles only. + containerClassName = isRowLayout + ? undefined + : styles.searchTrackTileContainer } else if (variant === LineupVariant.CONDENSED) { tileSize = TrackTileSize.SMALL } diff --git a/packages/web/src/components/track/desktop/TrackRowTile.tsx b/packages/web/src/components/track/desktop/TrackRowTile.tsx index 77a5f3e308d..247075bd252 100644 --- a/packages/web/src/components/track/desktop/TrackRowTile.tsx +++ b/packages/web/src/components/track/desktop/TrackRowTile.tsx @@ -21,6 +21,7 @@ import { Artwork, IconButton, IconKebabHorizontal, + IconPin, Text, TrackRow } from '@audius/harmony' @@ -62,6 +63,8 @@ export type TrackRowTileProps = { isFeed?: boolean onClick?: (trackId: ID) => void containerClassName?: string + /** Mark the owner's pinned track with an "Artist Pick" line. */ + showArtistPick?: boolean } /** @@ -79,7 +82,8 @@ export const TrackRowTile = ({ hasLoaded, isFeed = false, onClick, - containerClassName + containerClassName, + showArtistPick = false }: TrackRowTileProps) => { const dispatch = useDispatch() const { data: currentUserId } = useCurrentUserId() @@ -130,6 +134,8 @@ export const TrackRowTile = ({ const trackPosition = useSelector((state: CommonState) => getTrackPosition(state, { trackId, userId: currentUserId }) ) + const isArtistPick = + showArtistPick && partialUser?.artist_pick_track_id === trackId const { isFetchingNFTAccess, hasStreamAccess, isPreviewable } = useGatedContentAccess(trackWithFallback) @@ -319,6 +325,14 @@ export const TrackRowTile = ({ } menu={overflowMenu} playLabel={playLabel} + context={ + isArtistPick ? ( + <> + + {messages.artistPick} + + ) : undefined + } data-testid='track-row' /> ) diff --git a/packages/web/src/pages/profile-page/components/desktop/ProfilePage.tsx b/packages/web/src/pages/profile-page/components/desktop/ProfilePage.tsx index 7655641af96..ac25f22a969 100644 --- a/packages/web/src/pages/profile-page/components/desktop/ProfilePage.tsx +++ b/packages/web/src/pages/profile-page/components/desktop/ProfilePage.tsx @@ -17,8 +17,10 @@ import { getProfileRepostsQueryKey } from '@audius/common/api' import { useMuteUser } from '@audius/common/context' +import { useFeatureFlag } from '@audius/common/hooks' import { commentsMessages } from '@audius/common/messages' import { Status } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' import { ProfilePageTabs } from '@audius/common/store' import { route } from '@audius/common/utils' import { @@ -89,6 +91,10 @@ const LeftColumnSpacer = () => ( ) const ProfilePage = ({ containerRef }: ProfilePageProps) => { + const { isEnabled: isTrackRowsEnabled } = useFeatureFlag( + FeatureFlags.TRACK_ROWS + ) + const tileVariant = isTrackRowsEnabled ? 'row' : 'tile' const profileFocusRootRef = useRef(null) usePreventOffscreenFocus(profileFocusRootRef) @@ -303,6 +309,7 @@ const ProfilePage = ({ containerRef }: ProfilePageProps) => { hasNextPage={userRepostsQuery.hasNextPage} loadNextPage={userRepostsQuery.loadNextPage} variant={LineupVariant.CONDENSED} + tileVariant={tileVariant} scrollParent={containerRef?.current ?? null} /> ) @@ -337,6 +344,7 @@ const ProfilePage = ({ containerRef }: ProfilePageProps) => { hasNextPage={artistTracksQuery.hasNextPage} loadNextPage={artistTracksQuery.loadNextPage} variant={LineupVariant.GRID} + tileVariant={tileVariant} leadingElementId={profile.artist_pick_track_id ?? undefined} showArtistPick scrollParent={containerRef?.current ?? null} @@ -382,6 +390,7 @@ const ProfilePage = ({ containerRef }: ProfilePageProps) => { hasNextPage={userRepostsQuery.hasNextPage} loadNextPage={userRepostsQuery.loadNextPage} variant={LineupVariant.CONDENSED} + tileVariant={tileVariant} maxEntries={profile.repost_count} scrollParent={containerRef?.current ?? null} /> From 326e8043c02348f33b3bb8949dc0edfee916be0b Mon Sep 17 00:00:00 2001 From: Dylan Audius Date: Mon, 19 Oct 2026 17:11:44 -0700 Subject: [PATCH 2/2] fix(web): space collection cards between rows on profile Reposts Reposts mixes playlists into the track lineup. With rows the cards sat flush against the row above; give them a top margin in row layout. Co-Authored-By: Claude Opus 5.5 --- packages/web/src/components/lineup/Lineup.module.css | 5 +++++ packages/web/src/components/lineup/TrackLineup.tsx | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/web/src/components/lineup/Lineup.module.css b/packages/web/src/components/lineup/Lineup.module.css index cec044eaf31..edeef9a84f4 100644 --- a/packages/web/src/components/lineup/Lineup.module.css +++ b/packages/web/src/components/lineup/Lineup.module.css @@ -28,3 +28,8 @@ margin: 0px !important; margin-bottom: 24px !important; } + +/* Collection cards mixed into a row lineup (profile Reposts) */ +.collectionAmongRows { + margin-top: var(--harmony-spacing-m); +} diff --git a/packages/web/src/components/lineup/TrackLineup.tsx b/packages/web/src/components/lineup/TrackLineup.tsx index d7990d69ace..9f44b9b15ce 100644 --- a/packages/web/src/components/lineup/TrackLineup.tsx +++ b/packages/web/src/components/lineup/TrackLineup.tsx @@ -439,7 +439,9 @@ export const TrackLineup = ({ playTrack: handlePlayCollectionTrack, pauseTrack, size: tileSize, - containerClassName, + containerClassName: cn(containerClassName, { + [styles.collectionAmongRows]: isRowLayout + }), id: item.id, isLoading: false, hasLoaded: () => {}, @@ -469,7 +471,8 @@ export const TrackLineup = ({ onClickTile, showArtistPick, TrackTile, - CollectionTile + CollectionTile, + isRowLayout ]) const isInitialLoad = isPending && tiles.length === 0