From 4103ed722108ed86d6d8ad692bdbbe853fb2f79a Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 12 Dec 2025 01:08:03 -0800 Subject: [PATCH] redesign sidebar playlist item --- .../sidebar-playlist-list.module.css | 54 ++++++++++++++++++- .../components/sidebar-playlist-list.tsx | 52 ++++++++++++------ src/renderer/utils/format.tsx | 5 ++ 3 files changed, 92 insertions(+), 19 deletions(-) diff --git a/src/renderer/features/sidebar/components/sidebar-playlist-list.module.css b/src/renderer/features/sidebar/components/sidebar-playlist-list.module.css index 2324e1ef9..f67862702 100644 --- a/src/renderer/features/sidebar/components/sidebar-playlist-list.module.css +++ b/src/renderer/features/sidebar/components/sidebar-playlist-list.module.css @@ -8,18 +8,24 @@ position: relative; display: flex; width: 100%; + cursor: default; + border-radius: var(--theme-radius-md); } .row-hover { - :global(.label) { - margin-right: 135px; + .metadata { + margin-right: 100px; } + + background-color: var(--theme-colors-surface); } .controls { position: absolute; top: 50%; right: var(--theme-spacing-xs); + padding: var(--theme-spacing-md); + background: var(--theme-colors-surface); transform: translateY(-50%); } @@ -28,3 +34,47 @@ box-shadow: 0 0 0 2px var(--theme-colors-primary); opacity: 0.8; } + +.row-group { + display: flex; + gap: var(--theme-spacing-md); + align-items: center; + width: 100%; + height: 100%; + padding: var(--theme-spacing-xs) var(--theme-spacing-md); +} + +.metadata { + display: flex; + flex: 1; + flex-direction: column; + gap: var(--theme-spacing-xs); + min-width: 0; + overflow: hidden; +} + +.metadata-group { + display: flex; + flex-wrap: nowrap; + gap: var(--theme-spacing-md); + align-items: center; +} + +.metadata-group-item { + display: flex; + gap: var(--theme-spacing-xs); + align-items: center; +} + +.name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.image-container { + flex-shrink: 0; + width: 3rem; + min-width: 3rem; + height: 3rem; +} diff --git a/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx b/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx index a276177ee..48ca9775b 100644 --- a/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx +++ b/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx @@ -1,7 +1,7 @@ import { closeAllModals, openContextModal, openModal } from '@mantine/modals'; import { useQuery } from '@tanstack/react-query'; import clsx from 'clsx'; -import { MouseEvent, useCallback, useMemo, useState } from 'react'; +import { memo, MouseEvent, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { generatePath, Link } from 'react-router'; @@ -17,14 +17,16 @@ import { PlayTooltip, } from '/@/renderer/features/shared/components/play-button-group'; import { usePlayButtonClick } from '/@/renderer/features/shared/hooks/use-play-button-click'; -import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item'; import { useDragDrop } from '/@/renderer/hooks/use-drag-drop'; import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServer, useCurrentServerId } from '/@/renderer/store'; +import { formatDurationStringShort } from '/@/renderer/utils'; import { Accordion } from '/@/shared/components/accordion/accordion'; import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon'; import { ButtonProps } from '/@/shared/components/button/button'; import { Group } from '/@/shared/components/group/group'; +import { Icon } from '/@/shared/components/icon/icon'; +import { Image } from '/@/shared/components/image/image'; import { Text } from '/@/shared/components/text/text'; import { LibraryItem, @@ -44,7 +46,7 @@ interface PlaylistRowButtonProps extends Omit { +const PlaylistRowButton = memo(({ item, name, onContextMenu, to }: PlaylistRowButtonProps) => { const url = { pathname: generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: to }), state: { item }, @@ -53,7 +55,7 @@ const PlaylistRowButton = ({ item, name, onContextMenu, to }: PlaylistRowButtonP const [isHovered, setIsHovered] = useState(false); - const { isDraggedOver, isDragging, ref } = useDragDrop({ + const { isDraggedOver, isDragging, ref } = useDragDrop({ drag: { getId: () => { const draggedItems = getDraggedItems(item, undefined); @@ -158,31 +160,47 @@ const PlaylistRowButton = ({ item, name, onContextMenu, to }: PlaylistRowButtonP ); return ( -
onContextMenu(e as MouseEvent, item)} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} ref={ref} style={{ opacity: isDragging ? 0.5 : 1, }} + to={url} > - onContextMenu(e, item)} - to={url} - variant="subtle" - > - {name} - +
+ +
+ + {name} + +
+
+ + + {item.songCount || 0} + +
+
+ + + {formatDurationStringShort(item.duration ?? 0)} + +
+
+
+
+ {isHovered && } -
+ ); -}; +}); const RowControls = ({ id, diff --git a/src/renderer/utils/format.tsx b/src/renderer/utils/format.tsx index db5b2b788..47749ca69 100644 --- a/src/renderer/utils/format.tsx +++ b/src/renderer/utils/format.tsx @@ -55,6 +55,11 @@ export const formatDurationString = (duration: number) => { return string; }; +export const formatDurationStringShort = (duration: number) => { + const rawDuration = formatDuration(duration).split(':'); + return `${rawDuration[0]}h ${rawDuration[1]}m`; +}; + export const formatRating = (item: Album | AlbumArtist | Song) => item.userRating !== null ? : null;