import React, { useState, useEffect, useRef } from 'react';
import Icon from './Icon';
// Хелпер: если icon — строка (эмодзи или name), рендерим Icon. Если JSX — как есть.
const renderItemIcon = (val) => {
if (!val) return null;
if (typeof val === 'string') {
const isAscii = /^[\x20-\x7E_-]+$/.test(val);
return isAscii
?
: ;
}
return val;
};
/**
* HoverPlusMenu — компактная «+»-кнопка справа от элемента иерархии.
*
* Отображается только при hover родителя (`visible` пропс). Клик по «+»
* раскрывает выпадающее меню действий вниз.
*
* Пример использования:
*
* ...название...
* addScript(obj) },
* { id: 'duplicate', label: 'Дублировать', icon: '📋', onClick: ... },
* { divider: true },
* { id: 'delete', label: 'Удалить', icon: '🗑', danger: true, onClick: ... },
* ]}
* />
*
*
* Props:
* visible — если false, кнопка не рендерится (или невидима)
* items — массив пунктов меню
* align — 'right' (default) или 'left'
*/
const HoverPlusMenu = ({ visible, items = [], align = 'right', alwaysVisible = false }) => {
const [open, setOpen] = useState(false);
const wrapperRef = useRef(null);
// Закрытие по клику снаружи
useEffect(() => {
if (!open) return;
const onDoc = (e) => {
if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
setOpen(false);
}
};
// setTimeout чтобы текущий клик (по +) не закрыл сразу
const t = setTimeout(() => document.addEventListener('mousedown', onDoc), 0);
return () => {
clearTimeout(t);
document.removeEventListener('mousedown', onDoc);
};
}, [open]);
// Закрытие по Esc
useEffect(() => {
if (!open) return;
const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [open]);
if (!visible && !open && !alwaysVisible) return null;
if (!items || items.length === 0) return null;
return (
e.stopPropagation()}
>
{open && (
{items.map((it, i) => {
if (it.divider) {
return
;
}
return (
);
})}
)}
);
};
export default HoverPlusMenu;