adjust playlist folder design, add animations

This commit is contained in:
jeffvli
2026-05-18 18:34:58 -07:00
parent da4284bac0
commit 3d1095dbd8
4 changed files with 61 additions and 33 deletions
@@ -22,7 +22,7 @@
gap: var(--theme-spacing-md); gap: var(--theme-spacing-md);
align-items: center; align-items: center;
width: 100%; width: 100%;
padding: var(--theme-spacing-xs) var(--theme-spacing-md); padding: var(--theme-spacing-sm) var(--theme-spacing-md);
font: inherit; font: inherit;
color: inherit; color: inherit;
text-align: left; text-align: left;
@@ -32,12 +32,18 @@
border-radius: var(--theme-radius-md); border-radius: var(--theme-radius-md);
&:hover { &:hover {
background-color: var(--theme-colors-surface); background-color: var(--theme-colors-background);
} }
} }
.chevron { .chevron {
display: flex;
flex-shrink: 0; flex-shrink: 0;
align-items: center;
}
.collapse {
overflow: hidden;
} }
.name { .name {
@@ -1,9 +1,11 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { motion } from 'motion/react';
import { import {
ComponentPropsWithoutRef, ComponentPropsWithoutRef,
CSSProperties, CSSProperties,
MouseEvent, MouseEvent,
ReactElement, ReactElement,
ReactNode,
useCallback, useCallback,
useMemo, useMemo,
useState, useState,
@@ -33,6 +35,25 @@ import { DragData, DragOperation, DragTarget } from '/@/shared/types/drag-and-dr
const STORAGE_KEY_PREFIX = 'feishin:playlist-folder-state'; const STORAGE_KEY_PREFIX = 'feishin:playlist-folder-state';
const FOLDER_COLLAPSE_TRANSITION = { duration: 0.2, ease: 'easeInOut' } as const;
interface PlaylistFolderCollapseProps {
children: ReactNode;
className?: string;
open: boolean;
}
const PlaylistFolderCollapse = ({ children, className, open }: PlaylistFolderCollapseProps) => (
<motion.div
animate={{ height: open ? 'auto' : 0, opacity: open ? 1 : 0 }}
className={clsx(styles.collapse, className)}
initial={false}
transition={FOLDER_COLLAPSE_TRANSITION}
>
{children}
</motion.div>
);
export const getPlaylistLeafName = (name: string, separator: string): string => { export const getPlaylistLeafName = (name: string, separator: string): string => {
if (!separator) return name; if (!separator) return name;
const segments = name.split(separator).filter((segment) => segment.length > 0); const segments = name.split(separator).filter((segment) => segment.length > 0);
@@ -457,7 +478,7 @@ const PlaylistFolderHeader = ({
type="button" type="button"
> >
<div className={styles.navFolderIcon}> <div className={styles.navFolderIcon}>
<Icon color="muted" icon="folder" size="xl" /> <Icon color="muted" icon={isOpen ? 'folder' : 'folderClosed'} size="md" />
</div> </div>
<Text className={styles.name} fw={500} size="md"> <Text className={styles.name} fw={500} size="md">
{name} {name}
@@ -482,18 +503,21 @@ const PlaylistFolderHeader = ({
style={{ opacity: isDragging ? 0.5 : 1 }} style={{ opacity: isDragging ? 0.5 : 1 }}
type="button" type="button"
> >
<Icon <Icon color="muted" icon={isOpen ? 'folder' : 'folderClosed'} size="md" />
className={styles.chevron}
icon={isOpen ? 'arrowDownS' : 'arrowRightS'}
size="sm"
/>
<Icon color="muted" icon="folder" size="sm" />
<Text className={styles.name} fw={500} size="md"> <Text className={styles.name} fw={500} size="md">
{name} {name}
</Text> </Text>
<Text className={styles.count} isMuted size="sm"> <Text className={styles.count} isMuted size="sm">
{leafCount} {leafCount}
</Text> </Text>
<motion.span
animate={{ rotate: isOpen ? 180 : 0 }}
className={styles.chevron}
initial={false}
transition={FOLDER_COLLAPSE_TRANSITION}
>
<Icon icon="arrowUpS" size="md" />
</motion.span>
</button> </button>
); );
}; };
@@ -724,20 +748,18 @@ export const PlaylistFolderTree = ({
onClick={() => onToggleFolder(group.name)} onClick={() => onToggleFolder(group.name)}
variant="header" variant="header"
/> />
{isOpen && ( <PlaylistFolderCollapse className={styles.children} open={isOpen}>
<div className={styles.children}> {group.items.map((item) => (
{group.items.map((item) => ( <PlaylistRowButton
<PlaylistRowButton item={item}
item={item} key={item.id}
key={item.id} name={item.name.slice(group.name.length + 1)}
name={item.name.slice(group.name.length + 1)} onContextMenu={onContextMenu}
onContextMenu={onContextMenu} onReorder={onReorder}
onReorder={onReorder} to={item.id}
to={item.id} />
/> ))}
))} </PlaylistFolderCollapse>
</div>
)}
</div> </div>
); );
})} })}
@@ -788,15 +810,13 @@ export const PlaylistFolderTreeView = ({
onClick={() => onToggleFolder(node.path)} onClick={() => onToggleFolder(node.path)}
variant="header" variant="header"
/> />
{isOpen && ( <PlaylistFolderCollapse className={styles.treeChildren} open={isOpen}>
<div className={styles.treeChildren}> {node.children.map((child) => (
{node.children.map((child) => ( <div className={styles.treeBranch} key={getNodeKey(child)}>
<div className={styles.treeBranch} key={getNodeKey(child)}> {renderNode(child)}
{renderNode(child)} </div>
</div> ))}
))} </PlaylistFolderCollapse>
</div>
)}
</div> </div>
); );
}; };
@@ -571,7 +571,7 @@ export const SidebarPlaylistList = () => {
{inNavigation ? navigation.currentName : t('page.sidebar.playlists')} {inNavigation ? navigation.currentName : t('page.sidebar.playlists')}
</Text> </Text>
</Group> </Group>
<Group gap="xs"> <Group gap="xs" wrap="nowrap">
<ActionIcon <ActionIcon
icon="add" icon="add"
iconProps={{ iconProps={{
+2
View File
@@ -54,6 +54,7 @@ import {
LuExternalLink, LuExternalLink,
LuFileJson, LuFileJson,
LuFlag, LuFlag,
LuFolderClosed,
LuFolderOpen, LuFolderOpen,
LuGauge, LuGauge,
LuGithub, LuGithub,
@@ -278,6 +279,7 @@ export const AppIcon = {
fileJson: LuFileJson, fileJson: LuFileJson,
filter: LuListFilter, filter: LuListFilter,
folder: LuFolderOpen, folder: LuFolderOpen,
folderClosed: LuFolderClosed,
genre: LuFlag, genre: LuFlag,
goToItem: LuCornerDownRight, goToItem: LuCornerDownRight,
hash: LuHash, hash: LuHash,