Portal/rsconcept/frontend/src/components/Input/SelectTree.tsx

126 lines
3.9 KiB
TypeScript
Raw Normal View History

import { useEffect, useState } from 'react';
2025-02-12 21:36:03 +03:00
import clsx from 'clsx';
2024-06-07 20:17:03 +03:00
2025-02-20 18:10:34 +03:00
import { globalIDs, PARAMETER } from '@/utils/constants';
2024-06-07 20:17:03 +03:00
2025-02-07 10:53:49 +03:00
import { Overlay } from '../Container';
import { MiniButton } from '../Control';
import { IconDropArrow, IconPageRight } from '../Icons';
import { CProps } from '../props';
2024-06-07 20:17:03 +03:00
interface SelectTreeProps<ItemType> extends CProps.Styling {
/** Current value. */
2024-06-07 20:17:03 +03:00
value: ItemType;
/** List of available items. */
items: ItemType[];
/** Prefix for the ids of the elements. */
prefix: string;
/** Callback to be called when the value changes. */
onChange: (newItem: ItemType) => void;
/** Callback providing the parent of the item. */
2024-06-07 20:17:03 +03:00
getParent: (item: ItemType) => ItemType;
/** Callback providing the label of the item. */
2024-06-07 20:17:03 +03:00
getLabel: (item: ItemType) => string;
/** Callback providing the description of the item. */
2024-06-07 20:17:03 +03:00
getDescription: (item: ItemType) => string;
}
/**
* Displays a tree of items and allows user to select one.
*/
2025-02-07 10:53:49 +03:00
export function SelectTree<ItemType>({
2024-06-07 20:17:03 +03:00
items,
value,
getParent,
getLabel,
getDescription,
onChange,
2024-06-07 20:17:03 +03:00
prefix,
...restProps
}: SelectTreeProps<ItemType>) {
const foldable = new Set(items.filter(item => getParent(item) !== item).map(item => getParent(item)));
2024-06-07 20:17:03 +03:00
const [folded, setFolded] = useState<ItemType[]>(items);
useEffect(() => {
2024-06-07 20:17:03 +03:00
setFolded(items.filter(item => getParent(value) !== item && getParent(getParent(value)) !== item));
}, [value, getParent, items]);
function onFoldItem(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);
}
})
);
}
2024-06-07 20:17:03 +03:00
function handleClickFold(event: CProps.EventMouse, target: ItemType, showChildren: boolean) {
event.preventDefault();
event.stopPropagation();
onFoldItem(target, showChildren);
}
2024-06-07 20:17:03 +03:00
function handleSetValue(event: CProps.EventMouse, target: ItemType) {
event.preventDefault();
event.stopPropagation();
onChange(target);
}
2024-06-07 20:17:03 +03:00
return (
<div tabIndex={-1} {...restProps}>
2024-12-11 23:37:23 +03:00
{items.map((item, index) => {
const isActive = getParent(item) === item || !folded.includes(getParent(item));
return (
<div
key={`${prefix}${index}`}
className={clsx(
'pr-3 pl-6 border-b',
'cc-scroll-row',
2024-12-17 11:37:42 +03:00
'bg-prim-200 clr-hover cc-animate-color',
2024-12-11 23:37:23 +03:00
'cursor-pointer',
value === item && 'clr-selected',
!isActive && 'pointer-events-none'
2024-12-11 23:37:23 +03:00
)}
2025-02-20 18:10:34 +03:00
data-tooltip-id={globalIDs.tooltip}
data-tooltip-html={getDescription(item)}
onClick={event => handleSetValue(event, item)}
2024-12-11 23:37:23 +03:00
style={{
borderBottomWidth: isActive ? '1px' : '0px',
transitionProperty: 'height, opacity, padding',
transitionDuration: `${PARAMETER.moveDuration}ms`,
paddingTop: isActive ? '0.25rem' : '0',
paddingBottom: isActive ? '0.25rem' : '0',
height: isActive ? 'min-content' : '0',
opacity: isActive ? '1' : '0'
}}
>
{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()}`}
</div>
);
})}
2024-06-07 20:17:03 +03:00
</div>
);
}