Files
SHiNE-server/shine-UI/js/components/sheet.js
T
qandClaude Opus 5.5 2e62616b46 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>
2026-09-29 01:36:52 +03:00

61 lines
2.5 KiB
JavaScript

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;
}