From 64527ebd5bbc0c2f1109f754f2e6ba7243d60568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D1=83=D1=80=20=D0=93=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Fri, 14 Aug 2026 11:35:49 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=BD=D0=B0=20ui-kit=20SIdebar=20+=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B1=D0=B8=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20drawer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/RequestPortal.module.scss | 17 +- .../components/RequestPortal.tsx | 31 +++- .../components/SideNav/SideNav.module.scss | 154 ------------------ .../components/SideNav/SideNav.tsx | 87 ---------- 4 files changed, 34 insertions(+), 255 deletions(-) delete mode 100644 src/webparts/requestPortal/components/SideNav/SideNav.module.scss delete mode 100644 src/webparts/requestPortal/components/SideNav/SideNav.tsx diff --git a/src/webparts/requestPortal/components/RequestPortal.module.scss b/src/webparts/requestPortal/components/RequestPortal.module.scss index 73d0694..0b1549c 100644 --- a/src/webparts/requestPortal/components/RequestPortal.module.scss +++ b/src/webparts/requestPortal/components/RequestPortal.module.scss @@ -81,15 +81,22 @@ display: none; } -// На узких экранах рейл уходит наверх. -@media (max-width: 900px) { +// Мобильная кнопка-бургер (открывает выезжающий Sidebar). На десктопе скрыта. +.mobileNav { + display: none; + margin-bottom: $sp-4; +} + +// < 768px: рейл превращается в выезжающий drawer (см. @tnsu/ui-kit Sidebar). Он +// position:fixed и выходит из потока — колонка .side схлопывается сама, main +// занимает всю ширину. Показываем бургер в шапке контента. +@media (max-width: 768px) { .layout { - flex-direction: column; min-height: 0; } - .side { - width: 100%; + .mobileNav { + display: block; } } diff --git a/src/webparts/requestPortal/components/RequestPortal.tsx b/src/webparts/requestPortal/components/RequestPortal.tsx index f99288d..35b3144 100644 --- a/src/webparts/requestPortal/components/RequestPortal.tsx +++ b/src/webparts/requestPortal/components/RequestPortal.tsx @@ -2,7 +2,8 @@ import * as React from 'react'; import styles from './RequestPortal.module.scss'; import '@tnsu/ui-kit/styles.css'; import type { IRequestPortalProps } from './IRequestPortalProps'; -import { IconSprite } from '@tnsu/ui-kit'; +import { IconSprite, Sidebar, SidebarToggle } from '@tnsu/ui-kit'; +import type { ISidebarItem } from '@tnsu/ui-kit'; import { ConfirmHost } from '../shared/ui'; import { navigate, @@ -20,7 +21,6 @@ import { } from '../shared/routing'; import { onRequestsChanged, emitRequestsChanged } from '../shared/requestEvents'; import { confirmDiscardFormChanges, markFormClean, markFormDirty } from '../shared/unsavedChanges'; -import { SideNav } from './SideNav/SideNav'; import { Drawer } from './Drawer/Drawer'; import { CategoryTabs } from './CategoryTabs/CategoryTabs'; import { CATEGORIES, DEFAULT_CATEGORY_ID } from '../data/categories'; @@ -59,6 +59,7 @@ const RequestPortal: React.FC = ({ hasTeamsContext, context const route = useRoute(); const [navCollapsed, setNavCollapsed] = React.useState(false); + const [mobileNavOpen, setMobileNavOpen] = React.useState(false); const [navCounts, setNavCounts] = React.useState(undefined); const [activeCategoryId, setActiveCategoryId] = React.useState(() => { @@ -250,26 +251,38 @@ const RequestPortal: React.FC = ({ hasTeamsContext, context }).catch(() => undefined); }; + // Разделы бокового меню для пакетного Sidebar (счётчики — из navCounts). + const navItems: ISidebarItem[] = [ + { id: 'new', title: 'Новая заявка', icon: 'plus' }, + { id: 'approvals', title: 'На согласовании', icon: 'user-check', count: navCounts?.approvals }, + { id: 'execution', title: 'На исполнении', icon: 'helmet', count: navCounts?.execution }, + { id: 'rework', title: 'Доработка', icon: 'edit', count: navCounts?.rework }, + { id: 'own', title: 'Мои заявки', icon: 'clipboard' }, + { id: 'search', title: 'Поиск', icon: 'search' }, + ]; + return (
- goto(id as NavSection)} + brand="Портал заявок" collapsed={navCollapsed} onToggleCollapsed={() => setNavCollapsed((v) => !v)} + mobileOpen={mobileNavOpen} + onMobileClose={() => setMobileNavOpen(false)} />
+
+ setMobileNavOpen(true)} /> +
{/* Раздел «Новая заявка» держим СМОНТИРОВАННЫМ и просто прячем стилем, когда открыта другая вкладка. Иначе React размонтировал бы форму, и всё введённое пропадало бы при переключении на «Мои заявки» и diff --git a/src/webparts/requestPortal/components/SideNav/SideNav.module.scss b/src/webparts/requestPortal/components/SideNav/SideNav.module.scss deleted file mode 100644 index 4b4a3e0..0000000 --- a/src/webparts/requestPortal/components/SideNav/SideNav.module.scss +++ /dev/null @@ -1,154 +0,0 @@ -@import '../../../../styles/design-tokens'; - -// Тёмный боковой рейл портала на всю высоту (фирменный navy-градиент, золотой -// маркер активного пункта). Не «плавает»: примыкает к контенту, тянется по высоте. -.nav { - display: flex; - flex-direction: column; - width: 248px; - padding: $sp-4 0 $sp-3; - background: $grad-header; - box-shadow: inset -1px 0 0 rgba(255, 255, 255, .06); - transition: width .2s; -} - -.navCollapsed { - width: 64px; -} - -// ---- Шапка рейла: бренд + сворачивание ---- -.head { - display: flex; - align-items: center; - justify-content: space-between; - gap: $sp-2; - padding: 0 $sp-4 $sp-4; - border-bottom: 1px solid rgba(255, 255, 255, .08); -} - -.navCollapsed .head { - flex-direction: column; - gap: $sp-3; - padding: 0 0 $sp-3; -} - -.brand { - display: flex; - align-items: center; - gap: $sp-2; - min-width: 0; -} - -.brandText { - font-family: $font-serif; - font-size: 15px; - font-weight: 600; - color: $white; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.toggle { - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - width: 26px; - height: 26px; - padding: 0; - border: 1px solid rgba(255, 255, 255, .18); - border-radius: $r-sm; - background: transparent; - color: rgba(255, 255, 255, .6); - cursor: pointer; - transition: .16s; - - &:hover { - border-color: $gold-400; - color: $gold-300; - } -} - -// ---- Пункты разделов ---- -.items { - display: flex; - flex-direction: column; - gap: 2px; - margin-top: $sp-4; -} - -.navCollapsed .items { - margin-top: $sp-3; -} - -.item { - position: relative; - display: flex; - align-items: center; - gap: $sp-3; - width: 100%; - padding: 12px 18px; - border: 0; - border-left: 3px solid transparent; - background: transparent; - color: rgba(255, 255, 255, .62); - font-family: $font-sans; - font-size: 13.5px; - font-weight: 500; - text-align: left; - white-space: nowrap; - cursor: pointer; - transition: color .18s, background .18s, border-color .18s; - - &:hover { - color: rgba(255, 255, 255, .92); - background: rgba(255, 255, 255, .04); - } -} - -.navCollapsed .item { - justify-content: center; - padding: 12px 0; -} - -.itemActive { - color: $white; - background: rgba(255, 255, 255, .07); - border-left-color: $gold-500; - - .itemIcon { - color: $gold-400; - } -} - -.itemIcon { - flex-shrink: 0; - display: inline-flex; - color: inherit; -} - -.itemTitle { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} - -.badge { - flex-shrink: 0; - min-width: 18px; - padding: 1px 6px; - border-radius: $r-full; - background: $gold-500; - color: $navy-950; - font-size: $fs-overline; - font-weight: 700; - text-align: center; -} - -.navCollapsed .badge { - position: absolute; - top: 6px; - right: 8px; -} diff --git a/src/webparts/requestPortal/components/SideNav/SideNav.tsx b/src/webparts/requestPortal/components/SideNav/SideNav.tsx deleted file mode 100644 index 581b343..0000000 --- a/src/webparts/requestPortal/components/SideNav/SideNav.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import * as React from 'react'; -import styles from './SideNav.module.scss'; -import { Icon, IconName } from '@tnsu/ui-kit'; -import { NavSection } from '../../shared/routing'; - -interface INavItem { - id: NavSection; - title: string; - icon: IconName; -} - -// Порядок неслучаен: сверху действие (создать), затем три очереди — то, что -// ждёт пользователя прямо сейчас, — и только потом архив и поиск. -const ITEMS: INavItem[] = [ - { id: 'new', title: 'Новая заявка', icon: 'plus' }, - { id: 'approvals', title: 'На согласовании', icon: 'user-check' }, - { id: 'execution', title: 'На исполнении', icon: 'helmet' }, - { id: 'rework', title: 'Доработка', icon: 'edit' }, - { id: 'own', title: 'Мои заявки', icon: 'clipboard' }, - { id: 'search', title: 'Поиск', icon: 'search' }, -]; - -interface ISideNavProps { - activeId: NavSection; - onChange: (id: NavSection) => void; - /** Счётчики очередей: сколько заявок ждёт пользователя в каждом разделе. */ - counts?: Partial>; - collapsed: boolean; - onToggleCollapsed: () => void; -} - -// Тёмный боковой рейл портала на всю высоту: бренд сверху, разделы снизу. -// Сворачивается до узкой полосы с одними иконками. -export const SideNav: React.FC = ({ - activeId, - onChange, - counts, - collapsed, - onToggleCollapsed, -}) => { - const navClasses = [styles.nav, collapsed ? styles.navCollapsed : ''].filter(Boolean).join(' '); - - return ( - - ); -};