From 5ff9efb7d6014714fc3636497fb6773a414322d7 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 28 Sep 2025 19:16:55 -0700 Subject: [PATCH] add expanded list item component --- .../item-list/expanded-list-item.module.css | 2 +- .../item-list/expanded-list-item.tsx | 7 +- .../expanded-album-list-item.module.css | 128 ++++++++++++++++++ .../components/expanded-album-list-item.tsx | 123 +++++++++++------ 4 files changed, 219 insertions(+), 41 deletions(-) diff --git a/src/renderer/components/item-list/expanded-list-item.module.css b/src/renderer/components/item-list/expanded-list-item.module.css index bfe342bbf..5d139ef3d 100644 --- a/src/renderer/components/item-list/expanded-list-item.module.css +++ b/src/renderer/components/item-list/expanded-list-item.module.css @@ -1,7 +1,7 @@ .container { width: 100%; height: 100%; - padding: var(--theme-spacing-lg); + padding: var(--theme-spacing-sm); } .inner { diff --git a/src/renderer/components/item-list/expanded-list-item.tsx b/src/renderer/components/item-list/expanded-list-item.tsx index d03b01815..cbf8992a7 100644 --- a/src/renderer/components/item-list/expanded-list-item.tsx +++ b/src/renderer/components/item-list/expanded-list-item.tsx @@ -1,3 +1,5 @@ +import { Suspense } from 'react'; + import styles from './expanded-list-item.module.css'; import { @@ -5,6 +7,7 @@ import { ItemListStateActions, } from '/@/renderer/components/item-list/helpers/item-list-state'; import { ExpandedAlbumListItem } from '/@/renderer/features/albums/components/expanded-album-list-item'; +import { Spinner } from '/@/shared/components/spinner/spinner'; import { LibraryItem } from '/@/shared/types/domain-types'; interface ExpandedListItemProps { @@ -23,7 +26,9 @@ export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemPr return (
- + }> + +
); diff --git a/src/renderer/features/albums/components/expanded-album-list-item.module.css b/src/renderer/features/albums/components/expanded-album-list-item.module.css index 2e0a1ff17..c94abeed5 100644 --- a/src/renderer/features/albums/components/expanded-album-list-item.module.css +++ b/src/renderer/features/albums/components/expanded-album-list-item.module.css @@ -2,6 +2,8 @@ position: relative; width: 100%; height: 100%; + container-name: expanded-album-list-item; + container-type: inline-size; background-color: var(--theme-colors-surface); border-radius: var(--theme-radius-md); } @@ -12,3 +14,129 @@ bottom: 0; padding: var(--theme-spacing-sm); } + +.expanded { + position: relative; + gap: var(--theme-spacing-md); + width: 100%; + height: 100%; + overflow: hidden; + user-select: none; + border-radius: var(--theme-radius-md); +} + +.header { + display: flex; + flex-direction: column; + gap: var(--theme-spacing-xs); +} + +.content { + display: flex; + flex-direction: column; + gap: var(--theme-spacing-md); + width: 100%; + height: 100%; + min-height: 0; + padding: var(--theme-spacing-md); + overflow: hidden; +} + +.item-title { + z-index: 100; + display: -webkit-box; + overflow: hidden; + -webkit-line-clamp: 2; + line-clamp: 2; + line-height: 1.3; + color: black; + -webkit-box-orient: vertical; +} + +.item-subtitle { + color: black; + white-space: nowrap; +} + +.dark { + color: white; +} + +.image-container { + position: absolute; + top: 0; + right: 0; + z-index: 1; + display: flex; + grid-area: image; + align-items: center; + justify-content: center; + width: 50%; + height: 100%; +} + +.background-image { + position: absolute; + right: 0; + width: 60%; + height: 100%; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + filter: blur(2px); + + &::before { + position: absolute; + inset: 0; + content: ''; + background: linear-gradient(to right, var(--bg-color) 0%, transparent 100%); + } + + @container (min-width: 640px) { + width: 80%; + } + + @container (min-width: 768px) { + width: 90%; + } + + @container (min-width: 1200px) { + width: 100%; + } +} + +.tracks { + display: flex; + flex: 1; + flex-direction: column; + gap: var(--theme-spacing-sm); + width: 60%; + max-width: 700px; + min-height: 0; + + @container (min-width: 640px) { + width: 50%; + } + + @container (min-width: 768px) { + width: 40%; + } + + @container (min-width: 1200px) { + width: 30%; + } +} + +.tracks * { + color: black; + + /* stylelint-disable-next-line selector-class-pattern */ + :global(.table-row-module_row) { + /* height: var(--table-row-config-condensed-height); */ + border-bottom: none; + } +} + +.tracks.dark * { + color: white; +} diff --git a/src/renderer/features/albums/components/expanded-album-list-item.tsx b/src/renderer/features/albums/components/expanded-album-list-item.tsx index 9a694dee9..5d8a5070c 100644 --- a/src/renderer/features/albums/components/expanded-album-list-item.tsx +++ b/src/renderer/features/albums/components/expanded-album-list-item.tsx @@ -1,77 +1,122 @@ -import { useQuery } from '@tanstack/react-query'; +import { useSuspenseQuery } from '@tanstack/react-query'; +import clsx from 'clsx'; +import formatDuration from 'format-duration'; import { motion } from 'motion/react'; -import { useEffect, useRef, useTransition } from 'react'; +import { Fragment, Suspense } from 'react'; import styles from './expanded-album-list-item.module.css'; import { ItemListItem } from '/@/renderer/components/item-list/helpers/item-list-state'; import { albumQueries } from '/@/renderer/features/albums/api/album-api'; import { useFastAverageColor } from '/@/renderer/hooks'; +import { Group } from '/@/shared/components/group/group'; +import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area'; +import { Separator } from '/@/shared/components/separator/separator'; import { Spinner } from '/@/shared/components/spinner/spinner'; +import { Table } from '/@/shared/components/table/table'; +import { TextTitle } from '/@/shared/components/text-title/text-title'; +import { Text } from '/@/shared/components/text/text'; interface ExpandedAlbumListItemProps { item: ItemListItem; - previousItem?: ItemListItem | null; } -export const ExpandedAlbumListItem = ({ item, previousItem }: ExpandedAlbumListItemProps) => { - const [, startTransition] = useTransition(); - const previousDataRef = useRef(null); - - const { data, isLoading } = useQuery( +export const ExpandedAlbumListItem = ({ item }: ExpandedAlbumListItemProps) => { + const { data, isLoading } = useSuspenseQuery( albumQueries.detail({ - options: {}, query: { id: item.id }, serverId: item.serverId, }), ); - // Store the previous data when we have new data - useEffect(() => { - if (data && !isLoading) { - previousDataRef.current = data; - } - }, [data, isLoading]); - - // Use current data if available, otherwise use previous data for smooth transition - const displayData = data || previousDataRef.current; - const isDataTransitioning = isLoading && previousDataRef.current; - const color = useFastAverageColor({ + algorithm: 'sqrt', id: item.id, - src: displayData?.imageUrl, - srcLoaded: !isDataTransitioning, + src: data?.imageUrl, + srcLoaded: true, }); - // Start transition when item changes - useEffect(() => { - if (previousItem && previousItem.id !== item.id) { - startTransition(() => {}); - } - }, [item.id, previousItem, startTransition]); + if (color.isLoading) { + return null; + } return ( - {isDataTransitioning && ( + {isLoading && (
)} -
- ExpandedAlbumListItem - {displayData?.name || 'Loading...'} -
+ +
+
+
+ + {data?.name} + + + {data?.albumArtists.map((artist, index) => ( + + + {artist.name} + + {index < data?.albumArtists.length - 1 && } + + ))} + +
+
+ + + + {data?.songs?.map((song) => ( + + + {song.discNumber} - {song.trackNumber} + + {song.name} + + {formatDuration(song.duration)} + + + ))} + +
+
+
+
+
+
+
+
+ ); };