SHA256
UI v4: новый стиль по макету V3/V4 и доводка экранов
Палитра «Сияние» по умолчанию, новые роли цветов. Лента канала: плоские записи, ряд действий с равным шагом иконок, дата у записи. Обсуждение со строкой ввода, личный чат, шторки. Вход и регистрация: стартовый экран (надпись «СИЯНИЕ», сияние мандалы), настройки входа со статусами серверов, статус логина в одну строку, вход с другого устройства (код, таймер, шаги), сохранение ключей с переключателями. Корневые разделы – заголовок по центру; фильтр чатов – переключатель; уведомления – список как в чатах; фото – просмотр на весь экран. Общее: один зазор под шапкой (--page-top-gap), переключатели высотой с поле ввода, второстепенные кнопки по одной роли (в том числе на экранах устройств и серверов), иконки только из ui-icon.js, полоса прокрутки скрыта, среднее тире в текстах интерфейса. Что нужно от сервера и разработчика – docs/UI-Design/UI-v4-for-dev.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { iconHtml } from './ui-icon.js';
|
||||
|
||||
// Шторка снизу (на широком экране — окно по центру) с заголовком и «✕».
|
||||
// Закрывается по «✕», Escape и тапу мимо; фокус возвращается туда, откуда открыли.
|
||||
// Возвращает { root, body, footer, close }. content — Node или функция (sheet) => Node.
|
||||
export function openSheet({ title = '', content = null, className = '', onClose = null } = {}) {
|
||||
const opener = document.activeElement;
|
||||
const modal = document.createElement('div');
|
||||
modal.className = `modal sheet${className ? ` ${className}` : ''}`;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'modal-card sheet__card';
|
||||
card.setAttribute('role', 'dialog');
|
||||
card.setAttribute('aria-modal', 'true');
|
||||
|
||||
const head = document.createElement('div');
|
||||
head.className = 'sheet__head';
|
||||
const heading = document.createElement('h2');
|
||||
heading.className = 'modal-title sheet__title';
|
||||
heading.textContent = title;
|
||||
heading.id = `sheet-title-${Math.random().toString(36).slice(2, 8)}`;
|
||||
card.setAttribute('aria-labelledby', heading.id);
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.type = 'button';
|
||||
closeBtn.className = 'icon-btn sheet__close';
|
||||
closeBtn.setAttribute('aria-label', 'Закрыть');
|
||||
closeBtn.innerHTML = iconHtml('close');
|
||||
head.append(heading, closeBtn);
|
||||
|
||||
const body = document.createElement('div');
|
||||
body.className = 'sheet__body';
|
||||
const footer = document.createElement('div');
|
||||
footer.className = 'sheet__footer';
|
||||
footer.hidden = true;
|
||||
card.append(head, body, footer);
|
||||
modal.append(card);
|
||||
|
||||
let closed = false;
|
||||
const close = () => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
document.removeEventListener('keydown', onKeydown, true);
|
||||
modal.remove();
|
||||
opener?.focus?.();
|
||||
onClose?.();
|
||||
};
|
||||
const onKeydown = (event) => {
|
||||
if (event.key === 'Escape') { event.stopPropagation(); close(); }
|
||||
};
|
||||
modal.addEventListener('click', (event) => { if (event.target === modal) close(); });
|
||||
closeBtn.addEventListener('click', close);
|
||||
document.addEventListener('keydown', onKeydown, true);
|
||||
|
||||
const sheet = { root: modal, body, footer, close };
|
||||
const node = typeof content === 'function' ? content(sheet) : content;
|
||||
if (node instanceof Node) body.append(node);
|
||||
if (footer.childElementCount) footer.hidden = false;
|
||||
document.body.append(modal);
|
||||
(card.querySelector('[autofocus]') || closeBtn).focus();
|
||||
return sheet;
|
||||
}
|
||||
Reference in New Issue
Block a user