diff --git a/frontends/main/src/app-pages/PodcastPage/PodcastDetailPage.tsx b/frontends/main/src/app-pages/PodcastPage/PodcastDetailPage.tsx index 2e4eae2b0e..d85ffe28a4 100644 --- a/frontends/main/src/app-pages/PodcastPage/PodcastDetailPage.tsx +++ b/frontends/main/src/app-pages/PodcastPage/PodcastDetailPage.tsx @@ -1,6 +1,8 @@ "use client" import React, { useState, useEffect, useRef } from "react" +import { notFound } from "next/navigation" +import { useRouter } from "next-nprogress-bar" import { Breadcrumbs, Typography, styled, useMediaQuery } from "ol-components" import type { Theme } from "ol-components" import { Button, ActionButton } from "@mitodl/smoot-design" @@ -15,12 +17,11 @@ import { ResourceTypeEnum } from "api/v1" import type { LearningResource } from "api/v1" import moment from "moment" import { formatDate } from "ol-utilities" -import { HOME } from "@/common/urls" +import { HOME, podcastEpisodePageView } from "@/common/urls" import PodcastContainer from "./PodcastContainer" import { useFeatureFlagsLoaded } from "@/common/useFeatureFlagsLoaded" import { useFeatureFlagEnabled } from "posthog-js/react" import { FeatureFlags } from "@/common/feature_flags" -import { notFound } from "next/navigation" const HeaderSection = styled.div(({ theme }) => ({ borderBottom: `1px solid ${theme.custom.colors.lightGray2}`, @@ -121,8 +122,10 @@ const HeaderTextContent = styled.div({ /* ── Episodes list ── */ -const EpisodesSection = styled.div(({ theme }) => ({ - padding: "0 48px", +const EpisodesSection = styled("div", { + shouldForwardProp: (prop) => prop !== "hasMoreEpisodes", +})<{ hasMoreEpisodes?: boolean }>(({ theme, hasMoreEpisodes }) => ({ + padding: hasMoreEpisodes ? "0 48px" : "0 48px 40px 48px", [theme.breakpoints.down("sm")]: { padding: "0 0 48px", }, @@ -148,17 +151,17 @@ const EpisodesHeading = styled(Typography)(({ theme }) => ({ }, })) -const EpisodeList = styled.ul({ - listStyle: "none", +const EpisodeList = styled.div({ margin: 0, padding: 0, display: "grid", gridTemplateColumns: "1fr", }) -const EpisodeRow = styled("li", { +const EpisodeRow = styled("div", { shouldForwardProp: (prop) => prop !== "isEpisodePage", })<{ isEpisodePage?: boolean }>(({ theme, isEpisodePage }) => ({ + cursor: "pointer", margin: 0, display: "flex", flexDirection: "row", @@ -182,6 +185,10 @@ const EpisodeRow = styled("li", { backgroundColor: theme.custom.colors.lightGray1, cursor: "pointer", }, + "&:focus-visible": { + outline: `2px solid ${theme.custom.colors.red}`, + outlineOffset: "-2px", + }, "&:hover .episode-title, &:focus-visible .episode-title": { color: theme.custom.colors.red, }, @@ -310,6 +317,7 @@ const PlayButton = styled(ActionButton, { export type EpisodeItemProps = { episode: LearningResource + href: string onPlayClick: (episode: LearningResource) => void onPauseClick?: () => void isPlaying: boolean @@ -319,12 +327,18 @@ export type EpisodeItemProps = { export const EpisodeItem: React.FC = ({ episode, + href, onPlayClick, onPauseClick, isPlaying, isPlayable, isEpisodePage = false, }) => { + const router = useRouter() + + const handleRowNavigate = () => { + if (href) router.push(href) + } const podcastEpisode = episode.resource_type === "podcast_episode" ? episode.podcast_episode : null @@ -340,7 +354,15 @@ export const EpisodeItem: React.FC = ({ return ( (isPlaying ? onPauseClick?.() : onPlayClick(episode))} + onClick={handleRowNavigate} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + handleRowNavigate() + } + }} + role="link" + tabIndex={0} isEpisodePage={isEpisodePage} > @@ -364,6 +386,15 @@ export const EpisodeItem: React.FC = ({ aria-label={ isPlaying ? `Pause ${episode.title}` : `Play ${episode.title}` } + onClick={(e) => { + e.preventDefault() + e.stopPropagation() + if (isPlaying) { + onPauseClick?.() + } else { + onPlayClick(episode) + } + }} isPlaying={isPlaying} disabled={!isPlayable} variant="secondary" @@ -566,22 +597,27 @@ export const PodcastDetailPage: React.FC = ({ - + Episodes {episodes && episodes.length > 0 && ( - + {episodes.map((episode) => ( - playerRef.current?.pause()} - isPlaying={ - playingEpisode?.id === episode.id && isAudioPlaying - } - isPlayable={Boolean(getEpisodeAudioUrl(episode))} - /> +
+ playerRef.current?.pause()} + isPlaying={ + playingEpisode?.id === episode.id && isAudioPlaying + } + isPlayable={Boolean(getEpisodeAudioUrl(episode))} + /> +
))}
)} diff --git a/frontends/main/src/app-pages/PodcastPage/PodcastEpisodeDetailPage.tsx b/frontends/main/src/app-pages/PodcastPage/PodcastEpisodeDetailPage.tsx index 4253e4df4a..2f8805045a 100644 --- a/frontends/main/src/app-pages/PodcastPage/PodcastEpisodeDetailPage.tsx +++ b/frontends/main/src/app-pages/PodcastPage/PodcastEpisodeDetailPage.tsx @@ -25,7 +25,7 @@ import { ResourceTypeEnum } from "api/v1" import type { LearningResource } from "api/v1" import moment from "moment" import { formatDate } from "ol-utilities" -import { HOME, podcastPageView } from "@/common/urls" +import { HOME, podcastPageView, podcastEpisodePageView } from "@/common/urls" import DOMPurify from "isomorphic-dompurify" import { EpisodeItem } from "./PodcastDetailPage" import PodcastContainer from "./PodcastContainer" @@ -47,9 +47,13 @@ const PageSection = styled.div(({ theme }) => ({ minHeight: "100vh", })) -const HeaderSection = styled.div(({ theme }) => ({ - borderBottom: `1px solid ${theme.custom.colors.lightGray2}`, - marginBottom: "64px", +const HeaderSection = styled("div", { + shouldForwardProp: (prop) => prop !== "hasEpisodes", +})<{ hasEpisodes?: boolean }>(({ theme, hasEpisodes }) => ({ + ...(hasEpisodes && { + borderBottom: `1px solid ${theme.custom.colors.lightGray2}`, + marginBottom: "64px", + }), paddingBottom: "64px", [theme.breakpoints.down("sm")]: { marginBottom: "24px", @@ -305,7 +309,7 @@ export const PodcastEpisodeDetailPage: React.FC< />
- + 0}> {podcast?.title && ( {podcast.title} @@ -354,6 +358,11 @@ export const PodcastEpisodeDetailPage: React.FC< page.results.map((rel) => rel.resource), @@ -117,7 +118,6 @@ const VideoPlaylistCollectionPage: React.FC< ) const playlistType = isOcwPlaylist(playlist) - const totalVideos = videos.length const totalVideoSeconds = videos.reduce((acc, video) => { const duration = video.video?.duration ?? "" const match = /PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/.exec(duration) @@ -141,7 +141,7 @@ const VideoPlaylistCollectionPage: React.FC< {isLoading ? ( @@ -151,7 +151,7 @@ const VideoPlaylistCollectionPage: React.FC< video={videos[0]} href={getVideoHref(videos[0])} isSeries={playlistType} - totalVideos={totalVideos} + totalVideos={totalCount} totalTime={totalTime} /> ) : null}