Portal/rsconcept/frontend/src/pages/LibraryPage/LibraryFolders.tsx

140 lines
4.7 KiB
TypeScript
Raw Normal View History

2024-06-19 22:09:31 +03:00
'use client';
import clsx from 'clsx';
import { AnimatePresence, motion } from 'framer-motion';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { IconFolder, IconFolderClosed, IconFolderOpened, IconFolderTree } from '@/components/Icons';
import { CProps } from '@/components/props';
import MiniButton from '@/components/ui/MiniButton';
import { FolderNode, FolderTree } from '@/models/FolderTree';
import { animateSideAppear, animateSideView } from '@/styling/animations';
import { globals, prefixes } from '@/utils/constants';
import { describeFolderNode, labelFolderNode } from '@/utils/labels';
interface LibraryTableProps {
folders: FolderTree;
currentFolder: string;
setFolder: React.Dispatch<React.SetStateAction<string>>;
toggleFolderMode: () => void;
}
function LibraryFolders({ folders, currentFolder, setFolder, toggleFolderMode }: LibraryTableProps) {
const activeNode = useMemo(() => folders.at(currentFolder), [folders, currentFolder]);
const items = useMemo(() => folders.getTree(), [folders]);
const [folded, setFolded] = useState<FolderNode[]>([]);
useLayoutEffect(() => {
setFolded(items.filter(item => item !== activeNode && (!activeNode || !activeNode.hasPredecessor(item))));
}, [items, activeNode]);
const onFoldItem = useCallback(
(target: FolderNode, showChildren: boolean) => {
setFolded(prev =>
items.filter(item => {
if (item === target) {
return !showChildren;
}
if (!showChildren && item.hasPredecessor(target)) {
return true;
} else {
return prev.includes(item);
}
})
);
},
[items]
);
const handleSetValue = useCallback(
(event: CProps.EventMouse, target: FolderNode) => {
event.preventDefault();
event.stopPropagation();
setFolder(target.getPath());
},
[setFolder]
);
const handleClickFold = useCallback(
(event: CProps.EventMouse, target: FolderNode, showChildren: boolean) => {
event.preventDefault();
event.stopPropagation();
onFoldItem(target, showChildren);
},
[onFoldItem]
);
return (
<motion.div
className='flex flex-col text:xs sm:text-sm'
initial={{ ...animateSideView.initial }}
animate={{ ...animateSideView.animate }}
exit={{ ...animateSideView.exit }}
>
<div className='h-[2.08rem] flex justify-end pr-1'>
<MiniButton
icon={<IconFolderTree size='1.25rem' className='icon-green' />}
title='Режим: проводник'
onClick={toggleFolderMode}
/>
</div>
<div
className={clsx(
'max-w-[10rem] sm:max-w-[15rem] min-w-[10rem] sm:min-w-[15rem]',
'flex flex-col',
'cc-scroll-y'
)}
>
<AnimatePresence initial={false}>
{items.map((item, index) =>
!item.parent || !folded.includes(item.parent) ? (
<motion.div
tabIndex={-1}
key={`${prefixes.folders_list}${index}`}
className={clsx(
'min-h-[2.0825rem] sm:min-h-[2.3125rem]',
'pr-3 flex items-center gap-2',
'cc-scroll-row',
'clr-hover',
'cursor-pointer',
activeNode === item && 'clr-selected'
)}
style={{ paddingLeft: `${(item.rank > 5 ? 5 : item.rank) * 0.5 + 0.5}rem` }}
data-tooltip-id={globals.tooltip}
data-tooltip-html={describeFolderNode(item)}
onClick={event => handleSetValue(event, item)}
initial={{ ...animateSideAppear.initial }}
animate={{ ...animateSideAppear.animate }}
exit={{ ...animateSideAppear.exit }}
>
{item.children.size > 0 ? (
<MiniButton
noPadding
noHover
icon={
folded.includes(item) ? (
<IconFolderClosed size='1rem' className='icon-primary' />
) : (
<IconFolderOpened size='1rem' className='icon-green' />
)
}
onClick={event => handleClickFold(event, item, folded.includes(item))}
/>
) : (
<div>
<IconFolder size='1rem' />
</div>
)}
<div className='self-center'>{labelFolderNode(item)}</div>
</motion.div>
) : null
)}
</AnimatePresence>
</div>
</motion.div>
);
}
export default LibraryFolders;