feat(ui): Implement adaptive navigation system (Stage 1)
- Added useDisplay hook for breakpoint-based layout - Added useScrollDirection hook for hide-on-scroll logic - Implemented three-tier navigation: - Navigation Bar (Compact < 600px) with auto-hide - Navigation Rail (Medium 600-1240px) - Expanded Navigation Rail (Expanded > 1240px) - Added support for sub-menus in expanded rail - Added notification badges support - Integrated ThemeProvider into App Refs #3 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Navigation } from './Navigation';
|
import { Navigation } from './Navigation';
|
||||||
|
import { useStore } from '../store/useStore';
|
||||||
|
import { useDisplay } from '../hooks/useDisplay';
|
||||||
|
|
||||||
export const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const { isRailExpanded } = useStore();
|
||||||
|
const { navMode } = useDisplay();
|
||||||
|
|
||||||
|
const isExpanded = navMode === 'rail-expanded' && isRailExpanded;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-container">
|
<div className="app-container">
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<main className="main-content">
|
<main className={`main-content ${isExpanded ? 'with-expanded-rail' : ''}`}>
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,37 +1,84 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { User, Calendar, GraduationCap, MessageSquare, Settings } from 'lucide-react';
|
import {
|
||||||
|
Calendar,
|
||||||
|
GraduationCap,
|
||||||
|
FileText,
|
||||||
|
MessageSquare,
|
||||||
|
User,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronRight,
|
||||||
|
Menu
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { useStore } from '../store/useStore';
|
||||||
|
import { useDisplay } from '../hooks/useDisplay';
|
||||||
|
import { useScrollDirection } from '../hooks/useScrollDirection';
|
||||||
|
|
||||||
const navItems = [
|
interface NavItem {
|
||||||
{ path: '/profile', label: 'Profile', icon: User },
|
path: string;
|
||||||
{ path: '/schedule', label: 'Schedule', icon: Calendar },
|
label: string;
|
||||||
{ path: '/grades', label: 'Grades', icon: GraduationCap },
|
icon: any;
|
||||||
{ path: '/messages', label: 'Messages', icon: MessageSquare },
|
iconFilled: any;
|
||||||
{ path: '/settings', label: 'Settings', icon: Settings },
|
badgeKey?: 'messages';
|
||||||
|
children?: { path: string; label: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const navItems: NavItem[] = [
|
||||||
|
{ path: '/profile', label: 'Профиль', icon: User, iconFilled: User },
|
||||||
|
{
|
||||||
|
path: '/schedule',
|
||||||
|
label: 'Расписание',
|
||||||
|
icon: Calendar,
|
||||||
|
iconFilled: Calendar,
|
||||||
|
children: [
|
||||||
|
{ path: '/schedule/week', label: 'Текущая неделя' },
|
||||||
|
{ path: '/schedule/session', label: 'Сессия' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/grades',
|
||||||
|
label: 'Успеваемость',
|
||||||
|
icon: GraduationCap,
|
||||||
|
iconFilled: GraduationCap,
|
||||||
|
children: [
|
||||||
|
{ path: '/grades/list', label: 'Оценки' },
|
||||||
|
{ path: '/grades/statements', label: 'Ведомости' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/docs',
|
||||||
|
label: 'Документы',
|
||||||
|
icon: FileText,
|
||||||
|
iconFilled: FileText,
|
||||||
|
children: [
|
||||||
|
{ path: '/docs/certs', label: 'Справки' },
|
||||||
|
{ path: '/docs/apps', label: 'Заявления' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ path: '/messages', label: 'Сообщения', icon: MessageSquare, iconFilled: MessageSquare, badgeKey: 'messages' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Navigation: React.FC = () => {
|
export const Navigation: React.FC = () => {
|
||||||
return (
|
const { navMode } = useDisplay();
|
||||||
<>
|
const { isRailExpanded, toggleRail, badges } = useStore();
|
||||||
<nav className="navigation-rail">
|
const scrollDir = useScrollDirection();
|
||||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
const [expandedItems, setExpandedItems] = useState<string[]>([]);
|
||||||
{navItems.map((item) => (
|
|
||||||
<NavLink
|
|
||||||
key={item.path}
|
|
||||||
to={item.path}
|
|
||||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
|
||||||
>
|
|
||||||
<div className="nav-item-icon-wrapper">
|
|
||||||
<item.icon size={24} />
|
|
||||||
</div>
|
|
||||||
<span className="nav-item-label">{item.label}</span>
|
|
||||||
</NavLink>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<nav className="navigation-bar">
|
const toggleSubmenu = (label: string) => {
|
||||||
{navItems.map((item) => (
|
setExpandedItems(prev =>
|
||||||
|
prev.includes(label) ? prev.filter(i => i !== label) : [...prev, label]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderBadge = (count?: number) => {
|
||||||
|
if (!count) return null;
|
||||||
|
return <span className="m3-badge">{count > 99 ? '99+' : count}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (navMode === 'bar') {
|
||||||
|
return (
|
||||||
|
<nav className={`navigation-bar ${scrollDir === 'down' ? 'hidden' : ''}`}>
|
||||||
|
{navItems.slice(0, 5).map((item) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.path}
|
key={item.path}
|
||||||
to={item.path}
|
to={item.path}
|
||||||
@@ -39,11 +86,71 @@ export const Navigation: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<div className="nav-item-icon-wrapper">
|
<div className="nav-item-icon-wrapper">
|
||||||
<item.icon size={24} />
|
<item.icon size={24} />
|
||||||
|
{item.badgeKey && renderBadge(badges[item.badgeKey])}
|
||||||
</div>
|
</div>
|
||||||
<span className="nav-item-label">{item.label}</span>
|
<span className="nav-item-label">{item.label}</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
</>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isExpanded = navMode === 'rail-expanded' && isRailExpanded;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className={`navigation-rail ${isExpanded ? 'expanded' : ''}`}>
|
||||||
|
<div className="rail-header">
|
||||||
|
<button className="icon-button" onClick={toggleRail}>
|
||||||
|
<Menu size={24} />
|
||||||
|
</button>
|
||||||
|
{isExpanded && <span className="rail-title">Bonch</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rail-items">
|
||||||
|
{navItems.map((item) => {
|
||||||
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
|
const isItemExpanded = expandedItems.includes(item.label);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={item.label} className="nav-group">
|
||||||
|
<NavLink
|
||||||
|
to={item.path}
|
||||||
|
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (hasChildren && isExpanded) {
|
||||||
|
toggleSubmenu(item.label);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="nav-item-icon-wrapper">
|
||||||
|
<item.icon size={24} />
|
||||||
|
{item.badgeKey && renderBadge(badges[item.badgeKey])}
|
||||||
|
</div>
|
||||||
|
{isExpanded && <span className="nav-item-label">{item.label}</span>}
|
||||||
|
{isExpanded && hasChildren && (
|
||||||
|
<div className="submenu-arrow">
|
||||||
|
{isItemExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
{isExpanded && isItemExpanded && hasChildren && (
|
||||||
|
<div className="submenu">
|
||||||
|
{item.children!.map(child => (
|
||||||
|
<NavLink
|
||||||
|
key={child.path}
|
||||||
|
to={child.path}
|
||||||
|
className={({ isActive }) => `submenu-item ${isActive ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
{child.label}
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
19
src/hooks/useDisplay.ts
Normal file
19
src/hooks/useDisplay.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
export function useDisplay() {
|
||||||
|
const [width, setWidth] = useState(window.innerWidth);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => setWidth(window.innerWidth);
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const navMode = useMemo(() => {
|
||||||
|
if (width < 600) return 'bar';
|
||||||
|
if (width < 1240) return 'rail';
|
||||||
|
return 'rail-expanded';
|
||||||
|
}, [width]);
|
||||||
|
|
||||||
|
return { width, navMode };
|
||||||
|
}
|
||||||
23
src/hooks/useScrollDirection.ts
Normal file
23
src/hooks/useScrollDirection.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export function useScrollDirection() {
|
||||||
|
const [scrollDir, setScrollDirection] = useState<'up' | 'down'>('up');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let lastScrollY = window.pageYOffset;
|
||||||
|
|
||||||
|
const updateScrollDirection = () => {
|
||||||
|
const scrollY = window.pageYOffset;
|
||||||
|
const direction = scrollY > lastScrollY ? 'down' : 'up';
|
||||||
|
if (direction !== scrollDir && (scrollY - lastScrollY > 10 || scrollY - lastScrollY < -10)) {
|
||||||
|
setScrollDirection(direction);
|
||||||
|
}
|
||||||
|
lastScrollY = scrollY > 0 ? scrollY : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', updateScrollDirection);
|
||||||
|
return () => window.removeEventListener('scroll', updateScrollDirection);
|
||||||
|
}, [scrollDir]);
|
||||||
|
|
||||||
|
return scrollDir;
|
||||||
|
}
|
||||||
@@ -7,11 +7,18 @@ interface AppState {
|
|||||||
isMockMode: boolean;
|
isMockMode: boolean;
|
||||||
theme: 'system' | 'light' | 'dark';
|
theme: 'system' | 'light' | 'dark';
|
||||||
seedColor: string;
|
seedColor: string;
|
||||||
|
isRailExpanded: boolean;
|
||||||
|
badges: {
|
||||||
|
messages: number;
|
||||||
|
};
|
||||||
setApiDomain: (domain: string) => void;
|
setApiDomain: (domain: string) => void;
|
||||||
setApiKey: (key: string) => void;
|
setApiKey: (key: string) => void;
|
||||||
setMockMode: (isMock: boolean) => void;
|
setMockMode: (isMock: boolean) => void;
|
||||||
setTheme: (theme: 'system' | 'light' | 'dark') => void;
|
setTheme: (theme: 'system' | 'light' | 'dark') => void;
|
||||||
setSeedColor: (color: string) => void;
|
setSeedColor: (color: string) => void;
|
||||||
|
toggleRail: () => void;
|
||||||
|
setRailExpanded: (expanded: boolean) => void;
|
||||||
|
setBadge: (key: keyof AppState['badges'], count: number) => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,12 +29,21 @@ export const useStore = create<AppState>()(
|
|||||||
apiKey: '',
|
apiKey: '',
|
||||||
isMockMode: import.meta.env.VITE_MOCK_MODE === 'true',
|
isMockMode: import.meta.env.VITE_MOCK_MODE === 'true',
|
||||||
theme: 'system',
|
theme: 'system',
|
||||||
seedColor: '#0061a4', // Default M3 Blue
|
seedColor: '#0061a4',
|
||||||
|
isRailExpanded: true,
|
||||||
|
badges: {
|
||||||
|
messages: 3, // Mock value
|
||||||
|
},
|
||||||
setApiDomain: (apiDomain) => set({ apiDomain }),
|
setApiDomain: (apiDomain) => set({ apiDomain }),
|
||||||
setApiKey: (apiKey) => set({ apiKey }),
|
setApiKey: (apiKey) => set({ apiKey }),
|
||||||
setMockMode: (isMockMode) => set({ isMockMode }),
|
setMockMode: (isMockMode) => set({ isMockMode }),
|
||||||
setTheme: (theme) => set({ theme }),
|
setTheme: (theme) => set({ theme }),
|
||||||
setSeedColor: (seedColor) => set({ seedColor }),
|
setSeedColor: (seedColor) => set({ seedColor }),
|
||||||
|
toggleRail: () => set((state) => ({ isRailExpanded: !state.isRailExpanded })),
|
||||||
|
setRailExpanded: (isRailExpanded) => set({ isRailExpanded }),
|
||||||
|
setBadge: (key, count) => set((state) => ({
|
||||||
|
badges: { ...state.badges, [key]: count }
|
||||||
|
})),
|
||||||
reset: () => set({ apiKey: '' }),
|
reset: () => set({ apiKey: '' }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,8 +14,9 @@
|
|||||||
--md-sys-color-outline: #73777f;
|
--md-sys-color-outline: #73777f;
|
||||||
--md-sys-color-error: #ba1a1a;
|
--md-sys-color-error: #ba1a1a;
|
||||||
|
|
||||||
--nav-width: 80px;
|
--nav-rail-width: 80px;
|
||||||
--bottom-nav-height: 80px;
|
--nav-rail-expanded-width: 280px;
|
||||||
|
--nav-bar-height: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark {
|
:root.dark {
|
||||||
@@ -49,7 +50,41 @@ body {
|
|||||||
transition: background-color 0.3s, color 0.3s;
|
transition: background-color 0.3s, color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Material 3 Components */
|
/* M3 Components */
|
||||||
|
|
||||||
|
.m3-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
right: -4px;
|
||||||
|
background-color: var(--md-sys-color-error);
|
||||||
|
color: white;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--md-sys-color-on-surface-variant);
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:hover {
|
||||||
|
background-color: var(--md-sys-color-surface-variant);
|
||||||
|
}
|
||||||
|
|
||||||
.m3-button {
|
.m3-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -65,6 +100,7 @@ body {
|
|||||||
transition: box-shadow 0.2s, background-color 0.2s;
|
transition: box-shadow 0.2s, background-color 0.2s;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--md-sys-color-primary);
|
color: var(--md-sys-color-primary);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m3-button-filled {
|
.m3-button-filled {
|
||||||
@@ -97,12 +133,7 @@ body {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m3-text-field:focus {
|
/* Layout */
|
||||||
outline: none;
|
|
||||||
border-bottom: 2px solid var(--md-sys-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Layout and Navigation */
|
|
||||||
|
|
||||||
.app-container {
|
.app-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -111,59 +142,107 @@ body {
|
|||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-bottom: var(--bottom-nav-height);
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
/* Navigation Bar (Mobile) */
|
||||||
.main-content {
|
|
||||||
padding-bottom: 0;
|
|
||||||
margin-left: var(--nav-width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.navigation-rail {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: var(--nav-width);
|
|
||||||
background-color: var(--md-sys-color-surface);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px 0;
|
|
||||||
border-right: 1px solid var(--md-sys-color-outline);
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navigation-bar {
|
.navigation-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: var(--bottom-nav-height);
|
height: var(--nav-bar-height);
|
||||||
background-color: var(--md-sys-color-surface-variant);
|
background-color: var(--md-sys-color-surface-variant);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navigation-bar.hidden {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
.navigation-bar { display: none; }
|
||||||
|
.main-content { margin-left: var(--nav-rail-width); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1240px) {
|
||||||
|
.main-content.with-expanded-rail { margin-left: var(--nav-rail-expanded-width); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 599px) {
|
||||||
|
.main-content { padding-bottom: calc(var(--nav-bar-height) + 16px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation Rail (Tablet/Desktop) */
|
||||||
|
|
||||||
|
.navigation-rail {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: var(--nav-rail-width);
|
||||||
|
background-color: var(--md-sys-color-surface);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px 0;
|
||||||
|
border-right: 1px solid var(--md-sys-color-outline);
|
||||||
|
z-index: 100;
|
||||||
|
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation-rail.expanded {
|
||||||
|
width: var(--nav-rail-expanded-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 56px;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rail-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rail-items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav Items Shared */
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--md-sys-color-on-surface-variant);
|
color: var(--md-sys-color-on-surface-variant);
|
||||||
gap: 4px;
|
height: 56px;
|
||||||
flex: 1;
|
padding: 0 12px;
|
||||||
height: 100%;
|
position: relative;
|
||||||
border-radius: 16px;
|
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navigation-bar .nav-item {
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-item-icon-wrapper {
|
.nav-item-icon-wrapper {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -172,6 +251,7 @@ body {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item.active .nav-item-icon-wrapper {
|
.nav-item.active .nav-item-icon-wrapper {
|
||||||
@@ -182,16 +262,47 @@ body {
|
|||||||
.nav-item-label {
|
.nav-item-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
.navigation-rail.expanded .nav-item {
|
||||||
.navigation-bar {
|
height: 56px;
|
||||||
display: none;
|
border-radius: 28px;
|
||||||
}
|
margin: 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 599px) {
|
.navigation-rail.expanded .nav-item-label {
|
||||||
.navigation-rail {
|
font-size: 14px;
|
||||||
display: none;
|
margin-left: 12px;
|
||||||
}
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Submenu */
|
||||||
|
|
||||||
|
.submenu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-left: 56px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item {
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--md-sys-color-on-surface-variant);
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 24px;
|
||||||
|
margin: 0 12px;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item.active {
|
||||||
|
background-color: var(--md-sys-color-secondary-container);
|
||||||
|
color: var(--md-sys-color-on-secondary-container);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-arrow {
|
||||||
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user