ux: sidebar — группировка по 4 секциям, сворачивание со state
Раньше: плоский список из 25 пунктов в один столбец, для учителя
видно ~20+, новички терялись.
Теперь: 4 группы со свёртывающимися заголовками + always-visible
top-блок и admin-нижний блок.
Группы:
• (top, без заголовка) Поиск · Дашборд · Путеводитель · Руководство
• «УЧЕБНЫЙ ПРОЦЕСС» (6): Классы, Мои ученики, Онлайн-урок,
Архив уроков, Live-квиз, Доска
• «КОНТЕНТ» (6): Учебники, Библиотека, Теория, Карта знаний,
Банк вопросов, Экзамен 9 класс
• «ПРАКТИКА И ИГРЫ» (7): Лаборатория, Биохимия, Красная книга,
Кроссворд, Виселица, Питомец, Коллекция
• «ОТЧЁТЫ И УПРАВЛЕНИЕ» (3, teacher+): Аналитика, Журнал, Управление
Технически:
- Helper G(slug, label, body) — создаёт группу с уникальным slug
- localStorage 'ls_sb_g_<slug>' хранит свёрнутое состояние
- Click на заголовок группы → toggle .collapsed
- Sidebar-collapsed (icon-only) режим: заголовки групп скрыты,
все пункты остаются видны (компактный режим работает как раньше)
- Стили инжектятся inline через id=sb-group-styles (защита от
повторной инжекции при HMR)
Совместимость: все ссылки и их id/class сохранены, нет правок в
других файлах. Существующие onClick'и через id (btn-board, btn-classes,
btn-admin, btn-join) работают.
This commit is contained in:
+90
-23
@@ -37,6 +37,19 @@
|
||||
return `<a href="${esc(href)}" class="${classes}"${idA}${hidA}><i data-lucide="${icon}" class="sb-icon"></i><span class="sb-lbl">${label}</span></a>`;
|
||||
}
|
||||
|
||||
// Collapsible group: section header + body. State persisted in localStorage.
|
||||
function G(slug, label, body) {
|
||||
const stored = localStorage.getItem('ls_sb_g_' + slug);
|
||||
const collapsed = stored === '1';
|
||||
return `<div class="sb-group${collapsed ? ' collapsed' : ''}" data-sb-group="${slug}">
|
||||
<button class="sb-group-hdr" onclick="window.__lsSbToggle('${slug}')">
|
||||
<span class="sb-lbl sb-group-lbl">${label}</span>
|
||||
<i data-lucide="chevron-down" class="sb-group-chev"></i>
|
||||
</button>
|
||||
<div class="sb-group-body">${body}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const initials = (user?.name || 'LS').split(' ').slice(0, 2).map(w => (w[0] || '').toUpperCase()).join('') || 'LS';
|
||||
const displayName = esc(user?.name || user?.email || '—');
|
||||
|
||||
@@ -50,29 +63,40 @@
|
||||
${L('/dashboard', 'home', 'Дашборд')}
|
||||
${L('/sitemap', 'map', 'Путеводитель')}
|
||||
${L('/teacher-guide', 'book-marked', 'Руководство', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/board', 'layout-dashboard', 'Доска', { id: 'btn-board', hidden: true })}
|
||||
${L('/classes', 'graduation-cap', 'Классы', { id: 'btn-classes', hidden: !isTch })}
|
||||
${L('/my-students', 'user-plus', 'Мои ученики', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/library', 'book-open', 'Библиотека')}
|
||||
${L('/theory', 'brain', 'Теория')}
|
||||
${L('/lab', 'atom', 'Лаборатория')}
|
||||
${L('/biochem', 'flask-conical', 'Биохимия')}
|
||||
${L('/hangman', 'gamepad-2', 'Виселица')}
|
||||
${L('/crossword', 'grid-3x3', 'Кроссворд')}
|
||||
${L('/pet', 'heart', 'Питомец')}
|
||||
${L('/collection', 'layers', 'Коллекция')}
|
||||
${L('/knowledge-map', 'share-2', 'Карта знаний')}
|
||||
${L('/red-book', 'leaf', 'Красная книга')}
|
||||
${L('/exam9', 'clipboard-check', 'Экзамен 9 класс')}
|
||||
${L('/textbooks', 'book-open-text', 'Учебники')}
|
||||
${L('/classroom', 'presentation', 'Онлайн-урок')}
|
||||
${L('/lesson-history','archive', 'Архив уроков')}
|
||||
<div class="sb-divider"></div>
|
||||
${L('/analytics', 'bar-chart-2', 'Аналитика', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/question-bank', 'database', 'Банк вопросов', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/live-quiz', 'radio', 'Live-квиз', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/gradebook', 'table', 'Журнал', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/admin', 'settings', 'Управление', { id: 'btn-admin', hidden: !isTch })}
|
||||
|
||||
${G('learning', 'Учебный процесс', `
|
||||
${L('/classes', 'graduation-cap', 'Классы', { id: 'btn-classes', hidden: !isTch })}
|
||||
${L('/my-students', 'user-plus', 'Мои ученики', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/classroom', 'presentation', 'Онлайн-урок')}
|
||||
${L('/lesson-history','archive', 'Архив уроков')}
|
||||
${L('/live-quiz', 'radio', 'Live-квиз', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/board', 'layout-dashboard', 'Доска', { id: 'btn-board', hidden: true })}
|
||||
`)}
|
||||
|
||||
${G('content', 'Контент', `
|
||||
${L('/textbooks', 'book-open-text', 'Учебники')}
|
||||
${L('/library', 'book-open', 'Библиотека')}
|
||||
${L('/theory', 'brain', 'Теория')}
|
||||
${L('/knowledge-map', 'share-2', 'Карта знаний')}
|
||||
${L('/question-bank', 'database', 'Банк вопросов', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/exam9', 'clipboard-check', 'Экзамен 9 класс')}
|
||||
`)}
|
||||
|
||||
${G('practice', 'Практика и игры', `
|
||||
${L('/lab', 'atom', 'Лаборатория')}
|
||||
${L('/biochem', 'flask-conical', 'Биохимия')}
|
||||
${L('/red-book', 'leaf', 'Красная книга')}
|
||||
${L('/crossword', 'grid-3x3', 'Кроссворд')}
|
||||
${L('/hangman', 'gamepad-2', 'Виселица')}
|
||||
${L('/pet', 'heart', 'Питомец')}
|
||||
${L('/collection', 'layers', 'Коллекция')}
|
||||
`)}
|
||||
|
||||
${isTch ? G('management', 'Отчёты и управление', `
|
||||
${L('/analytics', 'bar-chart-2', 'Аналитика', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/gradebook', 'table', 'Журнал', { cls: 'sb-teacher-only', hidden: !isTch })}
|
||||
${L('/admin', 'settings', 'Управление', { id: 'btn-admin', hidden: !isTch })}
|
||||
`) : ''}
|
||||
</nav>
|
||||
<div style="padding:4px 2px;flex-shrink:0">
|
||||
${isStu ? '<button class="sb-link" id="btn-join" style="display:none" onclick="typeof openJoinModal!==\'undefined\'&&openJoinModal()"><i data-lucide="user-plus" class="sb-icon"></i><span class="sb-lbl">Вступить в класс</span></button>' : ''}
|
||||
@@ -93,6 +117,49 @@
|
||||
</a>
|
||||
</div>`;
|
||||
|
||||
// Inject group styles once
|
||||
if (!document.getElementById('sb-group-styles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'sb-group-styles';
|
||||
style.textContent = `
|
||||
.sb-group { margin: 8px 0 2px; }
|
||||
.sb-group-hdr {
|
||||
width: 100%; padding: 4px 12px; border: none; background: none;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
font-family: 'Manrope', system-ui, sans-serif;
|
||||
font-size: 0.66rem; font-weight: 800; letter-spacing: 0.08em;
|
||||
color: rgba(255, 255, 255, 0.45); text-transform: uppercase;
|
||||
cursor: pointer; transition: color .12s;
|
||||
}
|
||||
.sb-group-hdr:hover { color: rgba(255, 255, 255, 0.75); }
|
||||
.sb-group-chev {
|
||||
width: 12px; height: 12px; transition: transform .18s;
|
||||
}
|
||||
.sb-group.collapsed .sb-group-chev { transform: rotate(-90deg); }
|
||||
.sb-group-body {
|
||||
display: flex; flex-direction: column;
|
||||
max-height: 800px; overflow: hidden;
|
||||
transition: max-height .25s ease, opacity .18s;
|
||||
opacity: 1;
|
||||
}
|
||||
.sb-group.collapsed .sb-group-body {
|
||||
max-height: 0; opacity: 0; pointer-events: none;
|
||||
}
|
||||
/* When sidebar collapsed (icon-only mode) — hide group headers */
|
||||
.app-layout.sb-collapsed .sb-group-hdr { display: none; }
|
||||
.app-layout.sb-collapsed .sb-group-body { max-height: none !important; opacity: 1 !important; pointer-events: auto !important; }
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Toggle handler (global so onclick works)
|
||||
window.__lsSbToggle = function (slug) {
|
||||
const g = el.querySelector(`[data-sb-group="${slug}"]`);
|
||||
if (!g) return;
|
||||
const collapsed = g.classList.toggle('collapsed');
|
||||
try { localStorage.setItem('ls_sb_g_' + slug, collapsed ? '1' : '0'); } catch {}
|
||||
};
|
||||
|
||||
// Insert notif-drop sibling after <aside> if not already present
|
||||
if (!document.getElementById('notif-drop')) {
|
||||
const nd = document.createElement('div');
|
||||
|
||||
Reference in New Issue
Block a user