mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-11 14:53:47 +02:00
adjust playlist folder design, add animations
This commit is contained in:
@@ -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={{
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user