2024-12-13 21:30:49 +03:00
|
|
|
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 { MiniButton } from '../Control';
|
2025-02-10 01:32:16 +03:00
|
|
|
import { IconDropArrow, IconPageRight } from '../Icons';
|
2025-02-22 14:03:13 +03:00
|
|
|
import { type Styling } from '../props';
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-02-20 20:22:05 +03:00
|
|
|
interface SelectTreeProps<ItemType> extends Styling {
|
2024-11-21 15:09:31 +03:00
|
|
|
/** Current value. */
|
2024-06-07 20:17:03 +03:00
|
|
|
value: ItemType;
|
2024-11-21 15:09:31 +03:00
|
|
|
|
|
|
|
/** List of available items. */
|
|
|
|
items: ItemType[];
|
|
|
|
|
|
|
|
/** Prefix for the ids of the elements. */
|
|
|
|
prefix: string;
|
|
|
|
|
|
|
|
/** Callback to be called when the value changes. */
|
2025-02-04 20:35:18 +03:00
|
|
|
onChange: (newItem: ItemType) => void;
|
2024-11-21 15:09:31 +03:00
|
|
|
|
|
|
|
/** Callback providing the parent of the item. */
|
2024-06-07 20:17:03 +03:00
|
|
|
getParent: (item: ItemType) => ItemType;
|
2024-11-21 15:09:31 +03:00
|
|
|
|
|
|
|
/** Callback providing the label of the item. */
|
2024-06-07 20:17:03 +03:00
|
|
|
getLabel: (item: ItemType) => string;
|
2024-11-21 15:09:31 +03:00
|
|
|
|
|
|
|
/** Callback providing the description of the item. */
|
2024-06-07 20:17:03 +03:00
|
|
|
getDescription: (item: ItemType) => string;
|
|
|
|
}
|
|
|
|
|
2024-11-21 15:09:31 +03:00
|
|
|
/**
|
|
|
|
* 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,
|
2025-02-04 20:35:18 +03:00
|
|
|
onChange,
|
2024-06-07 20:17:03 +03:00
|
|
|
prefix,
|
|
|
|
...restProps
|
|
|
|
}: SelectTreeProps<ItemType>) {
|
2024-12-13 21:30:49 +03:00
|
|
|
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);
|
|
|
|
|
2024-12-12 20:57:45 +03:00
|
|
|
useEffect(() => {
|
2024-06-07 20:17:03 +03:00
|
|
|
setFolded(items.filter(item => getParent(value) !== item && getParent(getParent(value)) !== item));
|
|
|
|
}, [value, getParent, items]);
|
|
|
|
|
2025-03-10 16:01:40 +03:00
|
|
|
function onFoldItem(target: ItemType) {
|
2024-12-13 21:30:49 +03:00
|
|
|
setFolded(prev =>
|
|
|
|
items.filter(item => {
|
|
|
|
if (item === target) {
|
2025-03-10 16:01:40 +03:00
|
|
|
return !prev.includes(target);
|
2024-12-13 21:30:49 +03:00
|
|
|
}
|
2025-03-10 16:01:40 +03:00
|
|
|
if (!prev.includes(target) && (getParent(item) === target || getParent(getParent(item)) === target)) {
|
2024-12-13 21:30:49 +03:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return prev.includes(item);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-03-10 16:01:40 +03:00
|
|
|
function handleClickFold(event: React.MouseEvent<Element>, target: ItemType) {
|
2024-12-13 21:30:49 +03:00
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
2025-03-10 16:01:40 +03:00
|
|
|
onFoldItem(target);
|
2024-12-13 21:30:49 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-03-10 16:01:40 +03:00
|
|
|
function handleClickItem(event: React.MouseEvent<Element>, target: ItemType) {
|
2024-12-13 21:30:49 +03:00
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
2025-03-10 16:01:40 +03:00
|
|
|
if (event.ctrlKey || event.metaKey) {
|
|
|
|
onFoldItem(target);
|
|
|
|
} else {
|
|
|
|
onChange(target);
|
|
|
|
}
|
2024-12-13 21:30:49 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
|
|
return (
|
2025-01-28 19:45:31 +03:00
|
|
|
<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(
|
2025-03-07 16:21:18 +03:00
|
|
|
'relative',
|
2024-12-11 23:37:23 +03:00
|
|
|
'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',
|
2025-01-28 19:45:31 +03:00
|
|
|
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}
|
2025-01-28 19:45:31 +03:00
|
|
|
data-tooltip-html={getDescription(item)}
|
2025-03-10 16:01:40 +03:00
|
|
|
onClick={event => handleClickItem(event, item)}
|
2024-12-11 23:37:23 +03:00
|
|
|
style={{
|
|
|
|
borderBottomWidth: isActive ? '1px' : '0px',
|
2025-02-22 16:12:29 +03:00
|
|
|
willChange: 'max-height, opacity, padding',
|
|
|
|
transitionProperty: 'max-height, opacity, padding',
|
2024-12-11 23:37:23 +03:00
|
|
|
transitionDuration: `${PARAMETER.moveDuration}ms`,
|
|
|
|
paddingTop: isActive ? '0.25rem' : '0',
|
|
|
|
paddingBottom: isActive ? '0.25rem' : '0',
|
2025-02-22 16:12:29 +03:00
|
|
|
maxHeight: isActive ? '1.75rem' : '0',
|
2024-12-11 23:37:23 +03:00
|
|
|
opacity: isActive ? '1' : '0'
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{foldable.has(item) ? (
|
2025-03-07 16:21:18 +03:00
|
|
|
<MiniButton
|
2025-03-10 16:01:40 +03:00
|
|
|
className={clsx('absolute left-1', !folded.includes(item) ? 'top-1.5' : 'top-1')}
|
2025-03-07 16:21:18 +03:00
|
|
|
noPadding
|
|
|
|
noHover
|
|
|
|
icon={!folded.includes(item) ? <IconDropArrow size='1rem' /> : <IconPageRight size='1.25rem' />}
|
2025-03-10 16:01:40 +03:00
|
|
|
onClick={event => handleClickFold(event, item)}
|
2025-03-07 16:21:18 +03:00
|
|
|
/>
|
2024-12-11 23:37:23 +03:00
|
|
|
) : null}
|
|
|
|
{getParent(item) === item ? getLabel(item) : `- ${getLabel(item).toLowerCase()}`}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
})}
|
2024-06-07 20:17:03 +03:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|