mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-10 06:12:43 +02:00
adjust playlist folder design, add animations
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
gap: var(--theme-spacing-md);
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: var(--theme-spacing-xs) var(--theme-spacing-md);
|
||||
padding: var(--theme-spacing-sm) var(--theme-spacing-md);
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
@@ -32,12 +32,18 @@
|
||||
border-radius: var(--theme-radius-md);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--theme-colors-surface);
|
||||
background-color: var(--theme-colors-background);
|
||||
}
|
||||
}
|
||||
|
||||
.chevron {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.name {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import clsx from 'clsx';
|
||||
import { motion } from 'motion/react';
|
||||
import {
|
||||
ComponentPropsWithoutRef,
|
||||
CSSProperties,
|
||||
MouseEvent,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
@@ -33,6 +35,25 @@ import { DragData, DragOperation, DragTarget } from '/@/shared/types/drag-and-dr
|
||||
|
||||
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 => {
|
||||
if (!separator) return name;
|
||||
const segments = name.split(separator).filter((segment) => segment.length > 0);
|
||||
@@ -457,7 +478,7 @@ const PlaylistFolderHeader = ({
|
||||
type="button"
|
||||
>
|
||||
<div className={styles.navFolderIcon}>
|
||||
<Icon color="muted" icon="folder" size="xl" />
|
||||
<Icon color="muted" icon={isOpen ? 'folder' : 'folderClosed'} size="md" />
|
||||
</div>
|
||||
<Text className={styles.name} fw={500} size="md">
|
||||
{name}
|
||||
@@ -482,18 +503,21 @@ const PlaylistFolderHeader = ({
|
||||
style={{ opacity: isDragging ? 0.5 : 1 }}
|
||||
type="button"
|
||||
>
|
||||
<Icon
|
||||
className={styles.chevron}
|
||||
icon={isOpen ? 'arrowDownS' : 'arrowRightS'}
|
||||
size="sm"
|
||||
/>
|
||||
<Icon color="muted" icon="folder" size="sm" />
|
||||
<Icon color="muted" icon={isOpen ? 'folder' : 'folderClosed'} size="md" />
|
||||
<Text className={styles.name} fw={500} size="md">
|
||||
{name}
|
||||
</Text>
|
||||
<Text className={styles.count} isMuted size="sm">
|
||||
{leafCount}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
@@ -724,20 +748,18 @@ export const PlaylistFolderTree = ({
|
||||
onClick={() => onToggleFolder(group.name)}
|
||||
variant="header"
|
||||
/>
|
||||
{isOpen && (
|
||||
<div className={styles.children}>
|
||||
{group.items.map((item) => (
|
||||
<PlaylistRowButton
|
||||
item={item}
|
||||
key={item.id}
|
||||
name={item.name.slice(group.name.length + 1)}
|
||||
onContextMenu={onContextMenu}
|
||||
onReorder={onReorder}
|
||||
to={item.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<PlaylistFolderCollapse className={styles.children} open={isOpen}>
|
||||
{group.items.map((item) => (
|
||||
<PlaylistRowButton
|
||||
item={item}
|
||||
key={item.id}
|
||||
name={item.name.slice(group.name.length + 1)}
|
||||
onContextMenu={onContextMenu}
|
||||
onReorder={onReorder}
|
||||
to={item.id}
|
||||
/>
|
||||
))}
|
||||
</PlaylistFolderCollapse>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -788,15 +810,13 @@ export const PlaylistFolderTreeView = ({
|
||||
onClick={() => onToggleFolder(node.path)}
|
||||
variant="header"
|
||||
/>
|
||||
{isOpen && (
|
||||
<div className={styles.treeChildren}>
|
||||
{node.children.map((child) => (
|
||||
<div className={styles.treeBranch} key={getNodeKey(child)}>
|
||||
{renderNode(child)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<PlaylistFolderCollapse className={styles.treeChildren} open={isOpen}>
|
||||
{node.children.map((child) => (
|
||||
<div className={styles.treeBranch} key={getNodeKey(child)}>
|
||||
{renderNode(child)}
|
||||
</div>
|
||||
))}
|
||||
</PlaylistFolderCollapse>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -571,7 +571,7 @@ export const SidebarPlaylistList = () => {
|
||||
{inNavigation ? navigation.currentName : t('page.sidebar.playlists')}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<ActionIcon
|
||||
icon="add"
|
||||
iconProps={{
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
LuExternalLink,
|
||||
LuFileJson,
|
||||
LuFlag,
|
||||
LuFolderClosed,
|
||||
LuFolderOpen,
|
||||
LuGauge,
|
||||
LuGithub,
|
||||
@@ -278,6 +279,7 @@ export const AppIcon = {
|
||||
fileJson: LuFileJson,
|
||||
filter: LuListFilter,
|
||||
folder: LuFolderOpen,
|
||||
folderClosed: LuFolderClosed,
|
||||
genre: LuFlag,
|
||||
goToItem: LuCornerDownRight,
|
||||
hash: LuHash,
|
||||
|
||||
Reference in New Issue
Block a user