SHA256
113 lines
3.3 KiB
JavaScript
113 lines
3.3 KiB
JavaScript
import { createDropdownMenu } from './dropdown-menu.js';
|
|
|
|
function appendNode(target, node) {
|
|
if (!target || !(node instanceof Node)) return;
|
|
target.append(node);
|
|
}
|
|
|
|
function createActionButton(action = {}, cleanupFns) {
|
|
const button = document.createElement('button');
|
|
button.type = 'button';
|
|
button.className = `icon-btn topbar__action${action.className ? ` ${action.className}` : ''}`;
|
|
if (action.title) button.title = action.title;
|
|
if (action.ariaLabel) button.setAttribute('aria-label', action.ariaLabel);
|
|
if (action.id) button.dataset.action = action.id;
|
|
|
|
if (action.iconNode instanceof Node) {
|
|
button.append(action.iconNode);
|
|
} else {
|
|
button.textContent = String(action.label ?? '');
|
|
}
|
|
|
|
if (action.menu) {
|
|
const menu = createDropdownMenu({
|
|
anchorEl: button,
|
|
transparent: true,
|
|
dimBackground: true,
|
|
keepAnchorPressed: true,
|
|
...(typeof action.menu === 'object' ? action.menu : {}),
|
|
});
|
|
cleanupFns.add(() => menu.destroy());
|
|
} else if (typeof action.onClick === 'function') {
|
|
button.addEventListener('click', action.onClick);
|
|
}
|
|
|
|
return button;
|
|
}
|
|
|
|
export function createTopBar({
|
|
title = '',
|
|
className = '',
|
|
left = null,
|
|
center = null,
|
|
back = null,
|
|
leftLabel = '',
|
|
actions = [],
|
|
} = {}) {
|
|
const cleanupFns = new Set();
|
|
const topbar = document.createElement('header');
|
|
topbar.className = `topbar${className ? ` ${className}` : ''}`;
|
|
|
|
const leftSlot = document.createElement('div');
|
|
leftSlot.className = 'topbar__left';
|
|
|
|
const backAction = back;
|
|
if (backAction?.visible !== false && typeof backAction?.onClick === 'function') {
|
|
const button = document.createElement('button');
|
|
button.type = 'button';
|
|
button.className = `icon-btn topbar__back${backAction.className ? ` ${backAction.className}` : ''}`;
|
|
button.textContent = '←';
|
|
button.setAttribute('aria-label', backAction.ariaLabel || 'Назад');
|
|
button.title = backAction.title || 'Назад';
|
|
button.addEventListener('click', backAction.onClick);
|
|
leftSlot.append(button);
|
|
}
|
|
|
|
appendNode(leftSlot, left);
|
|
|
|
if (leftLabel) {
|
|
const label = document.createElement('span');
|
|
label.className = 'topbar__left-label';
|
|
label.textContent = leftLabel;
|
|
leftSlot.append(label);
|
|
}
|
|
|
|
const centerSlot = document.createElement('div');
|
|
centerSlot.className = 'topbar__center';
|
|
const resolvedCenter = center;
|
|
if (resolvedCenter instanceof Node) {
|
|
centerSlot.append(resolvedCenter);
|
|
} else {
|
|
const heading = document.createElement('h1');
|
|
heading.className = 'topbar__title';
|
|
heading.textContent = String(title || '');
|
|
centerSlot.append(heading);
|
|
}
|
|
|
|
const rightSlot = document.createElement('div');
|
|
rightSlot.className = 'topbar__right';
|
|
const normalizedActions = actions;
|
|
normalizedActions.forEach((action) => {
|
|
rightSlot.append(createActionButton(action, cleanupFns));
|
|
});
|
|
|
|
topbar.append(leftSlot, centerSlot, rightSlot);
|
|
|
|
topbar.addCleanup = (cleanup) => {
|
|
if (typeof cleanup === 'function') cleanupFns.add(cleanup);
|
|
return cleanup;
|
|
};
|
|
topbar.cleanup = () => {
|
|
for (const cleanup of cleanupFns) {
|
|
try {
|
|
cleanup();
|
|
} catch (error) {
|
|
console.warn('[TopBar] cleanup failed', error);
|
|
}
|
|
}
|
|
cleanupFns.clear();
|
|
};
|
|
|
|
return topbar;
|
|
}
|