Обновить UI для меню и языка

This commit is contained in:
AidarKC
2026-08-29 15:56:01 +04:00
parent b47440dd72
commit 3e4583bbe9
12 changed files with 282 additions and 53 deletions
+11 -1
View File
@@ -966,6 +966,7 @@ function openTopChannelsMenu({
onFindChannel,
}) {
closeTopChannelsMenu(listState);
document.dispatchEvent(new CustomEvent('shine:dropdown-open', { detail: { owner: anchorEl } }));
const root = document.getElementById('modal-root');
if (!root || !anchorEl) return;
@@ -975,7 +976,9 @@ function openTopChannelsMenu({
left = Math.max(12, Math.min(left, window.innerWidth - menuWidth - 12));
const estimatedHeight = 250;
let top = rect.bottom + 8;
const titleAnchor = document.querySelector('.channels-filter-title');
const titleRect = titleAnchor?.getBoundingClientRect?.();
let top = (titleRect?.bottom || rect.bottom) + 7;
if (top + estimatedHeight > window.innerHeight - 10) {
top = Math.max(12, rect.top - estimatedHeight - 8);
}
@@ -1022,13 +1025,19 @@ function openTopChannelsMenu({
const onOverlayClick = (event) => {
if (event.target === overlay) closeTopChannelsMenu(listState);
};
const onPeerDropdownOpen = (event) => {
if (event?.detail?.owner === anchorEl) return;
closeTopChannelsMenu(listState);
};
const onWindowResize = () => closeTopChannelsMenu(listState);
overlay.addEventListener('click', onOverlayClick);
window.addEventListener('resize', onWindowResize);
document.addEventListener('shine:dropdown-open', onPeerDropdownOpen);
listState.topMenuCleanup = () => {
overlay.removeEventListener('click', onOverlayClick);
window.removeEventListener('resize', onWindowResize);
document.removeEventListener('shine:dropdown-open', onPeerDropdownOpen);
};
}
@@ -1368,6 +1377,7 @@ export function render({ navigate, route, chrome }) {
const channelsFilterMenu = createDropdownMenu({
anchorEl: topTitle,
align: 'left',
leftShift: 72,
minWidth: 220,
items: [
{ label: 'Все каналы', iconHtml: channelMenuIcon('all'), action: () => navigate(buildChannelsViewRoute(CHANNELS_VIEW_ALL)) },
+5 -5
View File
@@ -50,7 +50,7 @@ export function render({ navigate }) {
controls.className = 'row wrap-row';
const checkButton = document.createElement('button');
checkButton.className = 'ghost-btn server-check-btn';
checkButton.className = 'shine-btn shine-btn--settings server-check-btn';
checkButton.type = 'button';
checkButton.textContent = 'Проверить';
@@ -132,7 +132,7 @@ export function render({ navigate }) {
if (isLocalDemoAvailable()) {
const localDemoButton = document.createElement('button');
localDemoButton.className = 'ghost-btn preauth-local-demo-btn';
localDemoButton.className = 'shine-btn shine-btn--settings preauth-local-demo-btn';
localDemoButton.type = 'button';
localDemoButton.textContent = 'Открыть локальный тестовый режим';
localDemoButton.addEventListener('click', () => {
@@ -143,7 +143,7 @@ export function render({ navigate }) {
}
const serverUiButton = document.createElement('button');
serverUiButton.className = 'ghost-btn';
serverUiButton.className = 'shine-btn shine-btn--settings';
serverUiButton.type = 'button';
serverUiButton.textContent = 'Настроить свой сервер';
serverUiButton.addEventListener('click', () => {
@@ -152,13 +152,13 @@ export function render({ navigate }) {
});
const cancelButton = document.createElement('button');
cancelButton.className = 'ghost-btn';
cancelButton.className = 'shine-btn shine-btn--settings';
cancelButton.type = 'button';
cancelButton.textContent = 'Отмена';
cancelButton.addEventListener('click', () => navigate('start-view'));
const saveButton = document.createElement('button');
saveButton.className = 'primary-btn';
saveButton.className = 'shine-btn shine-btn--primary';
saveButton.type = 'button';
saveButton.textContent = 'Сохранить';
saveButton.addEventListener('click', async () => {
+30 -18
View File
@@ -12,6 +12,7 @@ export function render({ navigate }) {
const screen = document.createElement('section');
screen.className = 'stack language-screen';
const returnPage = resolveReturnPage();
let pendingLanguage = state.entrySettings.language === 'en' ? 'en' : 'ru';
screen.append(
renderHeader({
@@ -27,31 +28,42 @@ export function render({ navigate }) {
card.className = 'card stack language-choice-card';
card.innerHTML = `
<p class="meta-muted language-choice-hint">Выберите язык интерфейса</p>
<div class="language-choice-grid" role="radiogroup" aria-label="Язык интерфейса">
<button class="language-choice-option${state.entrySettings.language === 'ru' ? ' is-selected' : ''}" type="button" data-language="ru" role="radio" aria-checked="${state.entrySettings.language === 'ru'}">
<span class="language-choice-name">Русский</span>
<span class="language-choice-code">RU</span>
</button>
<button class="language-choice-option${state.entrySettings.language === 'en' ? ' is-selected' : ''}" type="button" data-language="en" role="radio" aria-checked="${state.entrySettings.language === 'en'}">
<span class="language-choice-name">English</span>
<span class="language-choice-code">EN</span>
</button>
<div class="language-segmented-control" role="radiogroup" aria-label="Язык интерфейса">
<button class="language-segment${pendingLanguage === 'ru' ? ' is-selected' : ''}" type="button" data-language="ru" role="radio" aria-checked="${pendingLanguage === 'ru'}">Русский</button>
<button class="language-segment${pendingLanguage === 'en' ? ' is-selected' : ''}" type="button" data-language="en" role="radio" aria-checked="${pendingLanguage === 'en'}">English</button>
<span class="language-segmented-thumb" aria-hidden="true"></span>
</div>
<div class="language-choice-actions">
<button class="shine-btn shine-btn--settings" type="button" data-action="cancel">Отмена</button>
<button class="shine-btn shine-btn--primary" type="button" data-action="ok">OK</button>
</div>
`;
const syncSelection = () => {
card.dataset.language = pendingLanguage;
card.querySelectorAll('[data-language]').forEach((item) => {
const selected = item.dataset.language === pendingLanguage;
item.classList.toggle('is-selected', selected);
item.setAttribute('aria-checked', String(selected));
});
};
syncSelection();
card.querySelectorAll('[data-language]').forEach((button) => {
button.addEventListener('click', () => {
const language = String(button.dataset.language || 'ru');
saveEntryLanguage(language);
card.querySelectorAll('[data-language]').forEach((item) => {
const selected = item === button;
item.classList.toggle('is-selected', selected);
item.setAttribute('aria-checked', String(selected));
});
sessionStorage.removeItem('shine-language-return-page');
navigate(returnPage);
pendingLanguage = button.dataset.language === 'en' ? 'en' : 'ru';
syncSelection();
});
});
card.querySelector('[data-action="cancel"]')?.addEventListener('click', () => {
sessionStorage.removeItem('shine-language-return-page');
navigate(returnPage);
});
card.querySelector('[data-action="ok"]')?.addEventListener('click', () => {
saveEntryLanguage(pendingLanguage);
sessionStorage.removeItem('shine-language-return-page');
navigate(returnPage);
});
screen.append(card);
return screen;
+10 -1
View File
@@ -227,6 +227,7 @@ export function render({ navigate, chrome }) {
const chatFilterMenu = createDropdownMenu({
anchorEl: filterTitle,
align: 'left',
leftShift: 72,
minWidth: 225,
items: [
{ label: 'Все чаты', action: () => { currentChatFilter = 'all'; filterTitle.textContent = filterLabels.all; reloadForFilter(); } },
@@ -259,11 +260,13 @@ export function render({ navigate, chrome }) {
const menuWidth = menuPortal.offsetWidth || 206;
const left = Math.max(margin, Math.min(window.innerWidth - menuWidth - margin, rect.right - menuWidth));
menuPortal.style.left = `${Math.round(left)}px`;
menuPortal.style.top = `${Math.round(rect.bottom + 7)}px`;
const titleRect = filterTitle?.getBoundingClientRect?.();
menuPortal.style.top = `${Math.round((titleRect?.bottom || rect.bottom) + 7)}px`;
};
const openHeadMenu = () => {
if (!menuButton || menuPortal) return;
document.dispatchEvent(new CustomEvent('shine:dropdown-open', { detail: { owner: menuButton } }));
const portal = document.createElement('div');
portal.className = 'dm-head-menu dm-head-menu--portal';
portal.setAttribute('role', 'menu');
@@ -304,6 +307,10 @@ export function render({ navigate, chrome }) {
if (menuPortal.contains(event.target) || menuButton?.contains(event.target)) return;
closeHeadMenu();
};
const onPeerDropdownOpen = (event) => {
if (!menuPortal || event?.detail?.owner === menuButton) return;
closeHeadMenu();
};
const onMenuKeydown = (event) => {
if (event.key !== 'Escape' || !menuPortal) return;
closeHeadMenu();
@@ -312,6 +319,7 @@ export function render({ navigate, chrome }) {
const onMenuViewportChange = () => positionHeadMenu();
document.addEventListener('click', onOutsideClick);
document.addEventListener('keydown', onMenuKeydown);
document.addEventListener('shine:dropdown-open', onPeerDropdownOpen);
window.addEventListener('resize', onMenuViewportChange, { passive: true });
window.addEventListener('scroll', onMenuViewportChange, { passive: true, capture: true });
@@ -487,6 +495,7 @@ function renderRow(item) {
chatFilterMenu.destroy();
document.removeEventListener('click', onOutsideClick);
document.removeEventListener('keydown', onMenuKeydown);
document.removeEventListener('shine:dropdown-open', onPeerDropdownOpen);
window.removeEventListener('resize', onMenuViewportChange);
window.removeEventListener('scroll', onMenuViewportChange, true);
};