Portal/rsconcept/frontend/src/components/ui/SelectTree.tsx
IRBorisov 2759f10d09
Some checks failed
Backend CI / build (3.12) (push) Has been cancelled
Frontend CI / build (18.x) (push) Has been cancelled
Initial commit
2024-06-07 20:17:03 +03:00

121 lines
3.7 KiB
TypeScript

import clsx from 'clsx';
import { AnimatePresence, motion } from 'framer-motion';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { animateSideAppear } from '@/styling/animations';
import { globals } from '@/utils/constants';
import { IconDropArrow, IconPageRight } from '../Icons';
import { CProps } from '../props';
import MiniButton from './MiniButton';
import Overlay from './Overlay';
interface SelectTreeProps<ItemType> extends CProps.Styling {
items: ItemType[];
value: ItemType;
setValue: (newItem: ItemType) => void;
getParent: (item: ItemType) => ItemType;
getLabel: (item: ItemType) => string;
getDescription: (item: ItemType) => string;
prefix: string;
}
function SelectTree<ItemType>({
items,
value,
getParent,
getLabel,
getDescription,
setValue,
prefix,
...restProps
}: SelectTreeProps<ItemType>) {
const foldable = useMemo(
() => new Set(items.filter(item => getParent(item) !== item).map(item => getParent(item))),
[items, getParent]
);
const [folded, setFolded] = useState<ItemType[]>(items);
useLayoutEffect(() => {
setFolded(items.filter(item => getParent(value) !== item && getParent(getParent(value)) !== item));
}, [value, getParent, items]);
const onFoldItem = useCallback(
(target: ItemType, showChildren: boolean) => {
setFolded(prev =>
items.filter(item => {
if (item === target) {
return !showChildren;
}
if (!showChildren && (getParent(item) === target || getParent(getParent(item)) === target)) {
return true;
} else {
return prev.includes(item);
}
})
);
},
[items, getParent]
);
const handleClickFold = useCallback(
(event: CProps.EventMouse, target: ItemType, showChildren: boolean) => {
event.preventDefault();
event.stopPropagation();
onFoldItem(target, showChildren);
},
[onFoldItem]
);
const handleSetValue = useCallback(
(event: CProps.EventMouse, target: ItemType) => {
event.preventDefault();
event.stopPropagation();
setValue(target);
},
[setValue]
);
return (
<div {...restProps}>
<AnimatePresence initial={false}>
{items.map((item, index) =>
getParent(item) === item || !folded.includes(getParent(item)) ? (
<motion.div
tabIndex={-1}
key={`${prefix}${index}`}
className={clsx(
'pr-3 pl-6 py-1',
'cc-scroll-row',
'clr-controls clr-hover',
'cursor-pointer',
value === item && 'clr-selected'
)}
data-tooltip-id={globals.tooltip}
data-tooltip-html={getDescription(item)}
onClick={event => handleSetValue(event, item)}
initial={{ ...animateSideAppear.initial }}
animate={{ ...animateSideAppear.animate }}
exit={{ ...animateSideAppear.exit }}
>
{foldable.has(item) ? (
<Overlay position='left-[-1.3rem]' className={clsx(!folded.includes(item) && 'top-[0.1rem]')}>
<MiniButton
noPadding
noHover
icon={!folded.includes(item) ? <IconDropArrow size='1rem' /> : <IconPageRight size='1.25rem' />}
onClick={event => handleClickFold(event, item, folded.includes(item))}
/>
</Overlay>
) : null}
{getParent(item) === item ? getLabel(item) : `- ${getLabel(item).toLowerCase()}`}
</motion.div>
) : null
)}
</AnimatePresence>
</div>
);
}
export default SelectTree;