Move to Sidebar layout, add new theme for Demonic Pacts
This commit is contained in:
@@ -12,6 +12,7 @@ import About from './pages/About';
|
|||||||
import Settings from './pages/Settings';
|
import Settings from './pages/Settings';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import ThemeProvider from './components/ThemeProvider';
|
import ThemeProvider from './components/ThemeProvider';
|
||||||
|
import { SidebarProvider } from './context/SidebarContext';
|
||||||
import Statistics from './pages/Statistics';
|
import Statistics from './pages/Statistics';
|
||||||
import Calculators from './pages/Calculators';
|
import Calculators from './pages/Calculators';
|
||||||
import Faq from './pages/Faq';
|
import Faq from './pages/Faq';
|
||||||
@@ -53,6 +54,7 @@ export default function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
<SidebarProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<div className='App'>
|
<div className='App'>
|
||||||
<BrowserRouter basename='/'>
|
<BrowserRouter basename='/'>
|
||||||
@@ -82,6 +84,7 @@ export default function App() {
|
|||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
</SidebarProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import Page from './common/Page';
|
import Page from './common/Page';
|
||||||
import NavBar, { NavItem } from './common/NavBar';
|
import SideBar from './common/SideBar';
|
||||||
|
import { NavItem } from './common/NavBar';
|
||||||
import FeedbackModal from './FeedbackModal';
|
import FeedbackModal from './FeedbackModal';
|
||||||
import ManageDataModal from './ManageDataModal';
|
import ManageDataModal from './ManageDataModal';
|
||||||
import images from '../assets/images';
|
import images from '../assets/images';
|
||||||
@@ -24,38 +25,51 @@ export default function PageWrapper({ children }) {
|
|||||||
new NavItem('Calculators', 'primary', 0, 2).withRouterLink('/calculators').withIconFont('calculate'),
|
new NavItem('Calculators', 'primary', 0, 2).withRouterLink('/calculators').withIconFont('calculate'),
|
||||||
new NavItem('Groups', 'primary', 0, 3).withRouterLink('/groups').withIconFont('groups'),
|
new NavItem('Groups', 'primary', 0, 3).withRouterLink('/groups').withIconFont('groups'),
|
||||||
new NavItem('Character', 'secondary', 1, 0).withCustomRenderFn(
|
new NavItem('Character', 'secondary', 1, 0).withCustomRenderFn(
|
||||||
() => <Character.NavBarItem key='character' setCharacterModalOpen={setCharacterModalOpen} />,
|
(isCollapsed, onNavigate) => (
|
||||||
() => <Character.CollapsedMenu key='character' setCharacterModalOpen={setCharacterModalOpen} />
|
<Character.SideBarItem
|
||||||
|
key='character'
|
||||||
|
setCharacterModalOpen={setCharacterModalOpen}
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
onNavigate={onNavigate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
),
|
),
|
||||||
new NavItem('Import', 'secondary', 2, 0).withCustomRenderFn(
|
new NavItem('Data', 'secondary', 2, 0).withCustomRenderFn(
|
||||||
() => <ManageData.NavBarItem key='manage' setManageDataModalType={setManageDataModalType} />,
|
(isCollapsed, onNavigate) => (
|
||||||
() => <ManageData.CollapsedMenu key='manage' setManageDataModalType={setManageDataModalType} />
|
<ManageData.SideBarItem
|
||||||
|
key='manage'
|
||||||
|
setManageDataModalType={setManageDataModalType}
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
onNavigate={onNavigate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
),
|
),
|
||||||
// TODO re-enable user login
|
|
||||||
// new NavItem('Login', 'secondary', 3, 0).withCustomRenderFn(
|
|
||||||
// () => <AuthButton.NavBarItem key='login' />,
|
|
||||||
// () => <AuthButton.CollapsedMenu key='login' />
|
|
||||||
// ),
|
|
||||||
new NavItem('Settings', 'overflow', 3, 1).withRouterLink('/settings').withIconFont('settings'),
|
new NavItem('Settings', 'overflow', 3, 1).withRouterLink('/settings').withIconFont('settings'),
|
||||||
|
new NavItem('FAQ', 'overflow', 3, 2).withRouterLink('/faq').withIconFont('help_outline'),
|
||||||
|
new NavItem('About', 'overflow', 3, 3).withRouterLink('/about').withIconFont('info'),
|
||||||
new NavItem('Discord', 'overflow', 4, 0).withHref('https://discord.gg/GQ5kVyU', '_blank').withIconFont('discord'),
|
new NavItem('Discord', 'overflow', 4, 0).withHref('https://discord.gg/GQ5kVyU', '_blank').withIconFont('discord'),
|
||||||
new NavItem('Feedback', 'overflow', 4, 1).withCustomRenderFn(
|
new NavItem('Github', 'overflow', 4, 1)
|
||||||
() => <Feedback.NavBarItem key='feedback' setFeedbackModalOpen={setFeedbackModalOpen} />,
|
|
||||||
() => <Feedback.CollapsedMenu key='feedback' setFeedbackModalOpen={setFeedbackModalOpen} />
|
|
||||||
),
|
|
||||||
new NavItem('Github', 'overflow', 4, 2)
|
|
||||||
.withHref('https://github.com/osrs-reldo/os-league-tools', '_blank')
|
.withHref('https://github.com/osrs-reldo/os-league-tools', '_blank')
|
||||||
.withIconFont('code'),
|
.withIconFont('code'),
|
||||||
|
new NavItem('Feedback', 'overflow', 4, 2).withCustomRenderFn(
|
||||||
|
(isCollapsed, onNavigate) => (
|
||||||
|
<Feedback.SideBarItem
|
||||||
|
key='feedback'
|
||||||
|
setFeedbackModalOpen={setFeedbackModalOpen}
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
onNavigate={onNavigate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
),
|
||||||
new NavItem('Tip Jar', 'overflow', 4, 3)
|
new NavItem('Tip Jar', 'overflow', 4, 3)
|
||||||
.withHref('https://ko-fi.com/osleaguetools', '_blank')
|
.withHref('https://ko-fi.com/osleaguetools', '_blank')
|
||||||
.withIconFont('savings'),
|
.withIconFont('savings'),
|
||||||
new NavItem('FAQ', 'overflow', 4, 4).withRouterLink('/faq').withIconFont('help_outline'),
|
|
||||||
new NavItem('About', 'overflow', 4, 5).withRouterLink('/about').withIconFont('info'),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page limitContentWidth={limitContentWidth}>
|
<Page limitContentWidth={limitContentWidth}>
|
||||||
<Page.Nav>
|
<Page.Nav>
|
||||||
<NavBar navItems={navItems} brandName='OS League Tools' brandLogo={images[`icon-${theme}.png`]} />
|
<SideBar navItems={navItems} brandName='OS League Tools' brandLogo={images[`icon-${theme}.png`]} />
|
||||||
</Page.Nav>
|
</Page.Nav>
|
||||||
<Page.Body>
|
<Page.Body>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,11 +1,27 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getLayoutSlots, LayoutSlot } from './util/layout';
|
import { getLayoutSlots, LayoutSlot } from './util/layout';
|
||||||
|
import { useSidebar } from '../../context/SidebarContext';
|
||||||
|
import useBreakpoint, { MEDIA_QUERIES, MODE } from '../../hooks/useBreakpoint';
|
||||||
|
|
||||||
function Page({ children, sidebarPosition = 'left', limitContentWidth = true }) {
|
function Page({ children, sidebarPosition = 'left', limitContentWidth = true }) {
|
||||||
const { nav, banner, sidebar, body } = getLayoutSlots(children);
|
const { nav, banner, sidebar, body } = getLayoutSlots(children);
|
||||||
|
const { isCollapsed } = useSidebar();
|
||||||
|
const isDesktop = useBreakpoint(MEDIA_QUERIES.LG, MODE.GREATER_OR_EQ);
|
||||||
|
|
||||||
|
// Determine content margin class based on sidebar state
|
||||||
|
const contentMarginClass = isDesktop
|
||||||
|
? isCollapsed
|
||||||
|
? 'main-content-sidebar-collapsed'
|
||||||
|
: 'main-content-sidebar-expanded'
|
||||||
|
: 'main-content-mobile-header';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='bg-secondary w-full h-full min-h-screen'>
|
<div className='flex min-h-screen'>
|
||||||
|
{/* Left sidebar navigation */}
|
||||||
{nav}
|
{nav}
|
||||||
|
|
||||||
|
{/* Main content area */}
|
||||||
|
<div className={`flex-1 bg-secondary main-content-with-sidebar ${contentMarginClass}`}>
|
||||||
<div className='py-5 page-wrapper'>
|
<div className='py-5 page-wrapper'>
|
||||||
{banner && <div>{banner}</div>}
|
{banner && <div>{banner}</div>}
|
||||||
<div className='flex md:flex-row flex-col justify-center'>
|
<div className='flex md:flex-row flex-col justify-center'>
|
||||||
@@ -15,6 +31,7 @@ function Page({ children, sidebarPosition = 'left', limitContentWidth = true })
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
315
os-league-tools-master/src/components/common/SideBar.js
Normal file
315
os-league-tools-master/src/components/common/SideBar.js
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { Link, NavLink } from 'react-router-dom';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import useBreakpoint, { MEDIA_QUERIES, MODE } from '../../hooks/useBreakpoint';
|
||||||
|
import useClickListener from '../../hooks/useClickListener';
|
||||||
|
import { useSidebar } from '../../context/SidebarContext';
|
||||||
|
|
||||||
|
// Re-export NavItem from NavBar for convenience
|
||||||
|
export { NavItem } from './NavBar';
|
||||||
|
|
||||||
|
// Group NavItems by their variant (slot)
|
||||||
|
function groupNavItemsByVariant(navItems) {
|
||||||
|
const groups = {};
|
||||||
|
for (const item of navItems) {
|
||||||
|
const variant = item.variant || item.props?.slot || 'primary';
|
||||||
|
if (groups[variant]) {
|
||||||
|
groups[variant].push(item);
|
||||||
|
} else {
|
||||||
|
groups[variant] = [item];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SideBar({ navItems, brandName, brandLogo }) {
|
||||||
|
const { isCollapsed, toggleCollapse, isDrawerOpen, closeDrawer } = useSidebar();
|
||||||
|
const isDesktop = useBreakpoint(MEDIA_QUERIES.LG, MODE.GREATER_OR_EQ);
|
||||||
|
const drawerRef = useRef(null);
|
||||||
|
|
||||||
|
useClickListener(drawerRef, closeDrawer, true);
|
||||||
|
|
||||||
|
const {
|
||||||
|
primary: primaryNavItems,
|
||||||
|
secondary: secondaryNavItems,
|
||||||
|
overflow: overflowNavItems,
|
||||||
|
} = groupNavItemsByVariant(navItems);
|
||||||
|
|
||||||
|
// Group overflow items by collapseGroup for organization
|
||||||
|
const overflowGroups = getCollapseGroups(overflowNavItems || []);
|
||||||
|
|
||||||
|
// Desktop sidebar
|
||||||
|
if (isDesktop) {
|
||||||
|
return (
|
||||||
|
<aside className={`sidebar-nav ${isCollapsed ? 'sidebar-nav-collapsed' : 'sidebar-nav-expanded'}`}>
|
||||||
|
{/* Brand */}
|
||||||
|
<SideBarBrand logo={brandLogo} name={brandName} isCollapsed={isCollapsed} />
|
||||||
|
|
||||||
|
{/* Navigation content */}
|
||||||
|
<div className='sidebar-nav-content'>
|
||||||
|
{/* Primary navigation */}
|
||||||
|
<SideBarSection>
|
||||||
|
{primaryNavItems &&
|
||||||
|
primaryNavItems.map(navItem => (
|
||||||
|
<SideBarLink key={navItem.id} item={navItem} isCollapsed={isCollapsed} />
|
||||||
|
))}
|
||||||
|
</SideBarSection>
|
||||||
|
|
||||||
|
<SideBarDivider />
|
||||||
|
|
||||||
|
{/* Secondary navigation (Character, Manage Data) */}
|
||||||
|
<SideBarSection>
|
||||||
|
{secondaryNavItems &&
|
||||||
|
secondaryNavItems.map(navItem => (
|
||||||
|
<SideBarLink key={navItem.id} item={navItem} isCollapsed={isCollapsed} />
|
||||||
|
))}
|
||||||
|
</SideBarSection>
|
||||||
|
|
||||||
|
<SideBarDivider />
|
||||||
|
|
||||||
|
{/* Overflow items grouped */}
|
||||||
|
{overflowGroups.map((group, i) => (
|
||||||
|
<React.Fragment key={i}>
|
||||||
|
<SideBarSection>
|
||||||
|
{group.map(navItem => (
|
||||||
|
<SideBarLink key={navItem.id} item={navItem} isCollapsed={isCollapsed} />
|
||||||
|
))}
|
||||||
|
</SideBarSection>
|
||||||
|
{i < overflowGroups.length - 1 && <SideBarDivider />}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Toggle button */}
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='sidebar-nav-toggle'
|
||||||
|
onClick={toggleCollapse}
|
||||||
|
aria-label={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
||||||
|
>
|
||||||
|
<span className='icon-sm text-primary-alt'>
|
||||||
|
{isCollapsed ? 'chevron_right' : 'chevron_left'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile: header + drawer
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MobileHeader logo={brandLogo} name={brandName} />
|
||||||
|
<SideBarDrawer
|
||||||
|
innerRef={drawerRef}
|
||||||
|
isOpen={isDrawerOpen}
|
||||||
|
brandLogo={brandLogo}
|
||||||
|
brandName={brandName}
|
||||||
|
primaryNavItems={primaryNavItems}
|
||||||
|
secondaryNavItems={secondaryNavItems}
|
||||||
|
overflowGroups={overflowGroups}
|
||||||
|
onClose={closeDrawer}
|
||||||
|
/>
|
||||||
|
<SideBarBackdrop isOpen={isDrawerOpen} onClick={closeDrawer} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SideBarBrand({ logo, name, isCollapsed }) {
|
||||||
|
return (
|
||||||
|
<Link to='/' className='sidebar-nav-brand'>
|
||||||
|
<img src={logo} className='sidebar-nav-brand-logo' alt='' />
|
||||||
|
{!isCollapsed && <span className='sidebar-nav-brand-text'>{name}</span>}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SideBarSection({ title, children }) {
|
||||||
|
return (
|
||||||
|
<div className='sidebar-nav-section'>
|
||||||
|
{title && <div className='sidebar-nav-section-header'>{title}</div>}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SideBarDivider() {
|
||||||
|
return <div className='sidebar-nav-divider' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SideBarLink({ item, isCollapsed, onClick }) {
|
||||||
|
// If item has a custom render function for sidebar, use it
|
||||||
|
if (item.renderSidebarFn) {
|
||||||
|
return item.renderSidebarFn(isCollapsed, onClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If item has a standard render function, use it with modifications
|
||||||
|
if (item.renderFn) {
|
||||||
|
return item.renderFn(isCollapsed, onClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = item.iconFont && (
|
||||||
|
<span className='sidebar-nav-link-icon icon-base'>{item.iconFont}</span>
|
||||||
|
);
|
||||||
|
const label = !isCollapsed && <span className='sidebar-nav-link-label'>{item.label}</span>;
|
||||||
|
|
||||||
|
// External link
|
||||||
|
if (item.href) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
className='sidebar-nav-link'
|
||||||
|
href={item.href}
|
||||||
|
target={item.target || '_blank'}
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
onClick={onClick}
|
||||||
|
title={isCollapsed ? item.label : undefined}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router link
|
||||||
|
if (item.to) {
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
className={({ isActive }) => `sidebar-nav-link ${isActive ? 'active' : ''}`}
|
||||||
|
to={item.to}
|
||||||
|
onClick={onClick}
|
||||||
|
title={isCollapsed ? item.label : undefined}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{label}
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button with onClick
|
||||||
|
if (item.onClick) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='sidebar-nav-link w-full text-left'
|
||||||
|
onClick={() => {
|
||||||
|
item.onClick();
|
||||||
|
if (onClick) {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title={isCollapsed ? item.label : undefined}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function MobileHeader({ logo, name }) {
|
||||||
|
const { openDrawer } = useSidebar();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className='mobile-header'>
|
||||||
|
<button type='button' className='mobile-header-menu-btn' onClick={openDrawer} aria-label='Open menu'>
|
||||||
|
<span className='icon-xl text-primary'>menu</span>
|
||||||
|
</button>
|
||||||
|
<Link to='/' className='flex items-center'>
|
||||||
|
<img src={logo} className='h-6 w-6 mr-2' alt='' />
|
||||||
|
<span className='text-primary font-semibold uppercase'>{name}</span>
|
||||||
|
</Link>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SideBarDrawer({
|
||||||
|
innerRef,
|
||||||
|
isOpen,
|
||||||
|
brandLogo,
|
||||||
|
brandName,
|
||||||
|
primaryNavItems,
|
||||||
|
secondaryNavItems,
|
||||||
|
overflowGroups,
|
||||||
|
onClose,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<aside
|
||||||
|
ref={innerRef}
|
||||||
|
className={`sidebar-drawer ${isOpen ? 'sidebar-drawer-open' : 'sidebar-drawer-closed'}`}
|
||||||
|
>
|
||||||
|
{/* Brand */}
|
||||||
|
<div className='sidebar-nav-brand' onClick={onClose}>
|
||||||
|
<Link to='/' className='flex items-center'>
|
||||||
|
<img src={brandLogo} className='sidebar-nav-brand-logo' alt='' />
|
||||||
|
<span className='sidebar-nav-brand-text'>{brandName}</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navigation content */}
|
||||||
|
<div className='sidebar-nav-content'>
|
||||||
|
{/* Primary navigation */}
|
||||||
|
<SideBarSection>
|
||||||
|
{primaryNavItems &&
|
||||||
|
primaryNavItems.map(navItem => (
|
||||||
|
<SideBarLink key={navItem.id} item={navItem} isCollapsed={false} onClick={onClose} />
|
||||||
|
))}
|
||||||
|
</SideBarSection>
|
||||||
|
|
||||||
|
<SideBarDivider />
|
||||||
|
|
||||||
|
{/* Secondary navigation */}
|
||||||
|
<SideBarSection>
|
||||||
|
{secondaryNavItems &&
|
||||||
|
secondaryNavItems.map(navItem => (
|
||||||
|
<SideBarLink key={navItem.id} item={navItem} isCollapsed={false} onClick={onClose} />
|
||||||
|
))}
|
||||||
|
</SideBarSection>
|
||||||
|
|
||||||
|
<SideBarDivider />
|
||||||
|
|
||||||
|
{/* Overflow items grouped */}
|
||||||
|
{overflowGroups.map((group, i) => (
|
||||||
|
<React.Fragment key={i}>
|
||||||
|
<SideBarSection>
|
||||||
|
{group.map(navItem => (
|
||||||
|
<SideBarLink key={navItem.id} item={navItem} isCollapsed={false} onClick={onClose} />
|
||||||
|
))}
|
||||||
|
</SideBarSection>
|
||||||
|
{i < overflowGroups.length - 1 && <SideBarDivider />}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SideBarBackdrop({ isOpen, onClick }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`sidebar-drawer-backdrop ${isOpen ? 'sidebar-drawer-backdrop-visible' : 'sidebar-drawer-backdrop-hidden'}`}
|
||||||
|
onClick={onClick}
|
||||||
|
aria-hidden='true'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCollapseGroups(items) {
|
||||||
|
const groupMapping = {};
|
||||||
|
for (const navItem of items) {
|
||||||
|
const groupId = navItem.collapseGroup || -1;
|
||||||
|
if (groupMapping[groupId]) {
|
||||||
|
groupMapping[groupId].push(navItem);
|
||||||
|
} else {
|
||||||
|
groupMapping[groupId] = [navItem];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedIds = Object.keys(groupMapping).sort();
|
||||||
|
const groups = [];
|
||||||
|
for (const groupId of sortedIds) {
|
||||||
|
const sortedGroup = _.sortBy(groupMapping[groupId], ['collapseOrder']);
|
||||||
|
groups.push(sortedGroup);
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
@@ -133,4 +133,89 @@ function HiscoresIcon({ isHiscoreError }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { NavBarItem, CollapsedMenu };
|
function SideBarItem({ setCharacterModalOpen, isCollapsed, onNavigate }) {
|
||||||
|
const [isExpanded, setExpanded] = useState(false);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const menuRef = useRef(null);
|
||||||
|
const characterState = useSelector(state => state.character);
|
||||||
|
const activeCharacter = characterState.characters[characterState.activeCharacter];
|
||||||
|
const isHiscoreError = !!characterState.hiscoresCache.error;
|
||||||
|
|
||||||
|
useClickListener(menuRef, () => setExpanded(false), true);
|
||||||
|
|
||||||
|
if (!activeCharacter) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className='sidebar-nav-link w-full text-left'
|
||||||
|
type='button'
|
||||||
|
onClick={() => {
|
||||||
|
setCharacterModalOpen(true);
|
||||||
|
if (onNavigate) {
|
||||||
|
onNavigate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title={isCollapsed ? 'Character setup' : undefined}
|
||||||
|
>
|
||||||
|
<span className='sidebar-nav-link-icon icon-base'>manage_accounts</span>
|
||||||
|
{!isCollapsed && <span className='sidebar-nav-link-label'>Character</span>}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative' ref={menuRef}>
|
||||||
|
<button
|
||||||
|
className='sidebar-nav-link w-full text-left'
|
||||||
|
type='button'
|
||||||
|
onClick={() => setExpanded(!isExpanded)}
|
||||||
|
title={isCollapsed ? activeCharacter : undefined}
|
||||||
|
>
|
||||||
|
<span className={`sidebar-nav-link-icon icon-base ${isHiscoreError ? 'text-error' : ''}`}>
|
||||||
|
{isHiscoreError ? 'error' : 'account_circle'}
|
||||||
|
</span>
|
||||||
|
{!isCollapsed && (
|
||||||
|
<>
|
||||||
|
<span className='sidebar-nav-link-label'>{activeCharacter}</span>
|
||||||
|
<span className='ml-auto icon-sm text-primary-alt'>{isExpanded ? 'expand_less' : 'expand_more'}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
{isExpanded && !isCollapsed && (
|
||||||
|
<div className='bg-secondary-alt pl-4'>
|
||||||
|
<button
|
||||||
|
className='sidebar-nav-link w-full text-left text-sm'
|
||||||
|
onClick={() => dispatch(fetchHiscores(characterState, null, true))}
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
{characterState.hiscoresCache.loading ? (
|
||||||
|
<Spinner size={Spinner.SIZE.sm} invertColorForDarkMode={false} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className={`sidebar-nav-link-icon icon-base ${isHiscoreError ? 'text-error' : 'text-primary-alt'}`}>
|
||||||
|
{isHiscoreError ? 'sync_problem' : 'cached'}
|
||||||
|
</span>
|
||||||
|
<span className='sidebar-nav-link-label'>Update hiscores</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='sidebar-nav-link w-full text-left text-sm'
|
||||||
|
onClick={() => {
|
||||||
|
setCharacterModalOpen(true);
|
||||||
|
setExpanded(false);
|
||||||
|
if (onNavigate) {
|
||||||
|
onNavigate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
<span className='sidebar-nav-link-icon icon-base text-primary-alt'>manage_accounts</span>
|
||||||
|
<span className='sidebar-nav-link-label'>Manage characters</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { NavBarItem, CollapsedMenu, SideBarItem };
|
||||||
|
|||||||
@@ -28,4 +28,23 @@ function CollapsedMenu({ setFeedbackModalOpen }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { NavBarItem, CollapsedMenu };
|
function SideBarItem({ setFeedbackModalOpen, isCollapsed, onNavigate }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className='sidebar-nav-link w-full text-left'
|
||||||
|
onClick={() => {
|
||||||
|
setFeedbackModalOpen(true);
|
||||||
|
if (onNavigate) {
|
||||||
|
onNavigate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type='button'
|
||||||
|
title={isCollapsed ? 'Feedback' : undefined}
|
||||||
|
>
|
||||||
|
<span className='sidebar-nav-link-icon icon-base'>pest_control</span>
|
||||||
|
{!isCollapsed && <span className='sidebar-nav-link-label'>Feedback</span>}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { NavBarItem, CollapsedMenu, SideBarItem };
|
||||||
|
|||||||
@@ -81,4 +81,75 @@ function CollapsedMenu({ setManageDataModalType }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { NavBarItem, CollapsedMenu };
|
function SideBarItem({ setManageDataModalType, isCollapsed, onNavigate }) {
|
||||||
|
const [isExpanded, setExpanded] = useState(false);
|
||||||
|
const menuRef = useRef(null);
|
||||||
|
useClickListener(menuRef, () => setExpanded(false), true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative' ref={menuRef}>
|
||||||
|
<button
|
||||||
|
className='sidebar-nav-link w-full text-left'
|
||||||
|
type='button'
|
||||||
|
onClick={() => setExpanded(!isExpanded)}
|
||||||
|
title={isCollapsed ? 'Manage Data' : undefined}
|
||||||
|
>
|
||||||
|
<span className='sidebar-nav-link-icon icon-base'>inventory_2</span>
|
||||||
|
{!isCollapsed && (
|
||||||
|
<>
|
||||||
|
<span className='sidebar-nav-link-label'>Data</span>
|
||||||
|
<span className='ml-auto icon-sm text-primary-alt'>{isExpanded ? 'expand_less' : 'expand_more'}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
{isExpanded && !isCollapsed && (
|
||||||
|
<div className='bg-secondary-alt pl-4'>
|
||||||
|
<button
|
||||||
|
className='sidebar-nav-link w-full text-left text-sm'
|
||||||
|
onClick={() => {
|
||||||
|
setManageDataModalType('import');
|
||||||
|
setExpanded(false);
|
||||||
|
if (onNavigate) {
|
||||||
|
onNavigate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
<span className='sidebar-nav-link-icon icon-base text-primary-alt'>file_download</span>
|
||||||
|
<span className='sidebar-nav-link-label'>Import</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='sidebar-nav-link w-full text-left text-sm'
|
||||||
|
onClick={() => {
|
||||||
|
setManageDataModalType('export');
|
||||||
|
setExpanded(false);
|
||||||
|
if (onNavigate) {
|
||||||
|
onNavigate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
<span className='sidebar-nav-link-icon icon-base text-primary-alt'>file_upload</span>
|
||||||
|
<span className='sidebar-nav-link-label'>Export</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='sidebar-nav-link w-full text-left text-sm'
|
||||||
|
onClick={() => {
|
||||||
|
setManageDataModalType('reset');
|
||||||
|
setExpanded(false);
|
||||||
|
if (onNavigate) {
|
||||||
|
onNavigate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
<span className='sidebar-nav-link-icon icon-base text-primary-alt'>dangerous</span>
|
||||||
|
<span className='sidebar-nav-link-label'>Reset</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { NavBarItem, CollapsedMenu, SideBarItem };
|
||||||
|
|||||||
48
os-league-tools-master/src/context/SidebarContext.js
Normal file
48
os-league-tools-master/src/context/SidebarContext.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React, { createContext, useContext, useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
|
|
||||||
|
const SidebarContext = createContext();
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'sidebar-collapsed';
|
||||||
|
|
||||||
|
export function SidebarProvider({ children }) {
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(() => {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
return stored === 'true';
|
||||||
|
});
|
||||||
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, isCollapsed);
|
||||||
|
}, [isCollapsed]);
|
||||||
|
|
||||||
|
const toggleCollapse = useCallback(() => setIsCollapsed(prev => !prev), []);
|
||||||
|
const toggleDrawer = useCallback(() => setIsDrawerOpen(prev => !prev), []);
|
||||||
|
const closeDrawer = useCallback(() => setIsDrawerOpen(false), []);
|
||||||
|
const openDrawer = useCallback(() => setIsDrawerOpen(true), []);
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
isCollapsed,
|
||||||
|
setIsCollapsed,
|
||||||
|
isDrawerOpen,
|
||||||
|
setIsDrawerOpen,
|
||||||
|
toggleCollapse,
|
||||||
|
toggleDrawer,
|
||||||
|
closeDrawer,
|
||||||
|
openDrawer,
|
||||||
|
}),
|
||||||
|
[isCollapsed, isDrawerOpen, toggleCollapse, toggleDrawer, closeDrawer, openDrawer]
|
||||||
|
);
|
||||||
|
|
||||||
|
return <SidebarContext.Provider value={value}>{children}</SidebarContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSidebar() {
|
||||||
|
const context = useContext(SidebarContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useSidebar must be used within a SidebarProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SidebarContext;
|
||||||
@@ -2180,6 +2180,459 @@ select[multiple]:focus option:checked {
|
|||||||
box-shadow: inset 0px -7px 5px -5px var(--tw-shadow-color), 3px -3px 3px -3px var(--tw-shadow-color),
|
box-shadow: inset 0px -7px 5px -5px var(--tw-shadow-color), 3px -3px 3px -3px var(--tw-shadow-color),
|
||||||
-3px -3px 3px -3px var(--tw-shadow-color);
|
-3px -3px 3px -3px var(--tw-shadow-color);
|
||||||
}
|
}
|
||||||
|
/* SIDEBAR NAVIGATION */
|
||||||
|
.sidebar-nav {
|
||||||
|
position: fixed;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 20;
|
||||||
|
height: 100%;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(63 63 70 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition-property: all;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(113 113 122 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
.sidebar-nav-expanded {
|
||||||
|
width: 16rem;
|
||||||
|
}
|
||||||
|
.sidebar-nav-collapsed {
|
||||||
|
width: 4rem;
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(113 113 122 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand {
|
||||||
|
display: flex;
|
||||||
|
height: 4rem;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand-logo {
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand-text {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(0 0 0 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand-text:is(.dark *) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(244 244 245 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand-text {
|
||||||
|
margin-left: 0.75rem;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.sidebar-nav-content {
|
||||||
|
flex: 1 1 0%;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.sidebar-nav-section {
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
.sidebar-nav-section-header {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(55 65 81 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-section-header:is(.dark *) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(161 161 170 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-section-header {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.sidebar-nav-link {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(0 0 0 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-link:is(.dark *) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(244 244 245 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
.sidebar-nav-link:hover:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-link:hover:hover:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(39 39 42 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-link.active {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-link.active:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(70 70 78 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tl-dark .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(164 206 39 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tl-light .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(100 144 68 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tb-dark .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(229 217 147 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tb-light .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(99 66 40 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-sl-dark .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(19 213 145 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-sl-light .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(0 128 118 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tr-dark .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(220 139 54 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tr-light .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(180 74 30 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-re-dark .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(32 195 254 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-re-light .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(3 79 146 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-mono-dark .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(249 250 251 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-mono-light .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(55 65 81 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-dp-dark .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(235 78 104 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-dp-light .sidebar-nav-link.active {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(107 28 35 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tl-dark .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(164 206 39 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tl-light .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(100 144 68 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tb-dark .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(229 217 147 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tb-light .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(99 66 40 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-sl-dark .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(19 213 145 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-sl-light .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(0 128 118 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tr-dark .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(220 139 54 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-tr-light .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(180 74 30 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-re-dark .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(32 195 254 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-re-light .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(3 79 146 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-mono-dark .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(249 250 251 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-mono-light .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(55 65 81 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-dp-dark .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(235 78 104 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.theme-dp-light .sidebar-nav-link.active {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(107 28 35 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-link.active {
|
||||||
|
border-right-width: 2px;
|
||||||
|
}
|
||||||
|
.sidebar-nav-link-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
.sidebar-nav-link-label {
|
||||||
|
margin-left: 0.75rem;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.sidebar-nav-divider {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-divider:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(113 113 122 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-divider {
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
.sidebar-nav-toggle {
|
||||||
|
position: absolute;
|
||||||
|
right: -0.75rem;
|
||||||
|
top: 50%;
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
--tw-translate-y: -50%;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
border-radius: 9999px;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-toggle:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(63 63 70 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-toggle {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-toggle:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(113 113 122 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-toggle {
|
||||||
|
border-width: 1px;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
.sidebar-nav-toggle:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-toggle:hover:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(70 70 78 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-nav-toggle {
|
||||||
|
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
|
}
|
||||||
|
/* Mobile drawer styles */
|
||||||
|
.sidebar-drawer-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0px;
|
||||||
|
z-index: 30;
|
||||||
|
background-color: rgb(0 0 0 / 0.5);
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 300ms;
|
||||||
|
}
|
||||||
|
.sidebar-drawer-backdrop-hidden {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.sidebar-drawer-backdrop-visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.sidebar-drawer {
|
||||||
|
position: fixed;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 40;
|
||||||
|
height: 100%;
|
||||||
|
width: 16rem;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-drawer:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(63 63 70 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-drawer {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-drawer:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(113 113 122 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.sidebar-drawer {
|
||||||
|
border-right-width: 1px;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
transition-property: transform;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.sidebar-drawer-closed {
|
||||||
|
--tw-translate-x: -100%;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
}
|
||||||
|
.sidebar-drawer-open {
|
||||||
|
--tw-translate-x: 0px;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
}
|
||||||
|
/* Mobile header bar */
|
||||||
|
.mobile-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {.mobile-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-header {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.mobile-header:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(63 63 70 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.mobile-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.mobile-header:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(113 113 122 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
.mobile-header {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
.mobile-header-menu-btn:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.mobile-header-menu-btn:hover:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(39 39 42 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
.mobile-header-menu-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
/* Content offset for fixed sidebar */
|
||||||
|
.main-content-with-sidebar {
|
||||||
|
transition-property: all;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {.main-content-sidebar-expanded {
|
||||||
|
margin-left: 16rem;
|
||||||
|
}.main-content-sidebar-collapsed {
|
||||||
|
margin-left: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.main-content-mobile-header {
|
||||||
|
padding-top: 3.5rem;
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {.main-content-mobile-header {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
.\!visible {
|
.\!visible {
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
}
|
}
|
||||||
@@ -2387,6 +2840,9 @@ select[multiple]:focus option:checked {
|
|||||||
.h-5 {
|
.h-5 {
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
.h-6 {
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
.h-8 {
|
.h-8 {
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
@@ -2459,6 +2915,9 @@ select[multiple]:focus option:checked {
|
|||||||
.w-5 {
|
.w-5 {
|
||||||
width: 1.25rem;
|
width: 1.25rem;
|
||||||
}
|
}
|
||||||
|
.w-6 {
|
||||||
|
width: 1.5rem;
|
||||||
|
}
|
||||||
.w-60 {
|
.w-60 {
|
||||||
width: 15rem;
|
width: 15rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,6 +360,111 @@
|
|||||||
box-shadow: inset 0px -7px 5px -5px var(--tw-shadow-color), 3px -3px 3px -3px var(--tw-shadow-color),
|
box-shadow: inset 0px -7px 5px -5px var(--tw-shadow-color), 3px -3px 3px -3px var(--tw-shadow-color),
|
||||||
-3px -3px 3px -3px var(--tw-shadow-color);
|
-3px -3px 3px -3px var(--tw-shadow-color);
|
||||||
}
|
}
|
||||||
|
/* SIDEBAR NAVIGATION */
|
||||||
|
.sidebar-nav {
|
||||||
|
@apply fixed left-0 top-0 h-full z-20;
|
||||||
|
@apply bg-primary;
|
||||||
|
@apply flex flex-col;
|
||||||
|
@apply transition-all duration-300 ease-in-out;
|
||||||
|
@apply border-r border-subdued;
|
||||||
|
}
|
||||||
|
.sidebar-nav-expanded {
|
||||||
|
@apply w-64;
|
||||||
|
}
|
||||||
|
.sidebar-nav-collapsed {
|
||||||
|
@apply w-16;
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand {
|
||||||
|
@apply flex items-center p-4 border-b border-subdued h-16;
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand-logo {
|
||||||
|
@apply h-8 w-8 flex-shrink-0;
|
||||||
|
}
|
||||||
|
.sidebar-nav-brand-text {
|
||||||
|
@apply ml-3 text-lg font-semibold uppercase text-primary;
|
||||||
|
@apply whitespace-nowrap overflow-hidden;
|
||||||
|
}
|
||||||
|
.sidebar-nav-content {
|
||||||
|
@apply flex-1 overflow-y-auto py-2;
|
||||||
|
}
|
||||||
|
.sidebar-nav-section {
|
||||||
|
@apply py-1;
|
||||||
|
}
|
||||||
|
.sidebar-nav-section-header {
|
||||||
|
@apply px-4 py-2 text-xs uppercase tracking-wider text-secondary-alt;
|
||||||
|
@apply whitespace-nowrap overflow-hidden;
|
||||||
|
}
|
||||||
|
.sidebar-nav-link {
|
||||||
|
@apply flex items-center px-4 py-3 text-primary;
|
||||||
|
@apply hover:bg-hover transition-colors cursor-pointer;
|
||||||
|
}
|
||||||
|
.sidebar-nav-link.active {
|
||||||
|
@apply text-accent border-r-2 border-accent bg-primary-alt;
|
||||||
|
}
|
||||||
|
.sidebar-nav-link-icon {
|
||||||
|
@apply text-xl flex-shrink-0;
|
||||||
|
}
|
||||||
|
.sidebar-nav-link-label {
|
||||||
|
@apply ml-3 font-mono text-sm uppercase whitespace-nowrap overflow-hidden;
|
||||||
|
}
|
||||||
|
.sidebar-nav-divider {
|
||||||
|
@apply h-px mx-4 my-2 bg-subdued;
|
||||||
|
}
|
||||||
|
.sidebar-nav-toggle {
|
||||||
|
@apply absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full;
|
||||||
|
@apply bg-primary border border-subdued;
|
||||||
|
@apply flex items-center justify-center cursor-pointer;
|
||||||
|
@apply hover:bg-primary-alt transition-colors;
|
||||||
|
@apply shadow-sm;
|
||||||
|
}
|
||||||
|
.sidebar-nav-footer {
|
||||||
|
@apply border-t border-subdued p-2;
|
||||||
|
}
|
||||||
|
/* Mobile drawer styles */
|
||||||
|
.sidebar-drawer-backdrop {
|
||||||
|
@apply fixed inset-0 bg-black/50 z-30;
|
||||||
|
@apply transition-opacity duration-300;
|
||||||
|
}
|
||||||
|
.sidebar-drawer-backdrop-hidden {
|
||||||
|
@apply opacity-0 pointer-events-none;
|
||||||
|
}
|
||||||
|
.sidebar-drawer-backdrop-visible {
|
||||||
|
@apply opacity-100;
|
||||||
|
}
|
||||||
|
.sidebar-drawer {
|
||||||
|
@apply fixed left-0 top-0 h-full w-64 z-40;
|
||||||
|
@apply bg-primary border-r border-subdued;
|
||||||
|
@apply transform transition-transform duration-300 ease-in-out;
|
||||||
|
@apply flex flex-col;
|
||||||
|
}
|
||||||
|
.sidebar-drawer-closed {
|
||||||
|
@apply -translate-x-full;
|
||||||
|
}
|
||||||
|
.sidebar-drawer-open {
|
||||||
|
@apply translate-x-0;
|
||||||
|
}
|
||||||
|
/* Mobile header bar */
|
||||||
|
.mobile-header {
|
||||||
|
@apply md:hidden fixed top-0 left-0 right-0 z-10;
|
||||||
|
@apply bg-primary p-3 flex items-center gap-3;
|
||||||
|
@apply border-b border-subdued;
|
||||||
|
}
|
||||||
|
.mobile-header-menu-btn {
|
||||||
|
@apply p-2 bg-hover rounded cursor-pointer;
|
||||||
|
}
|
||||||
|
/* Content offset for fixed sidebar */
|
||||||
|
.main-content-with-sidebar {
|
||||||
|
@apply transition-all duration-300 ease-in-out;
|
||||||
|
}
|
||||||
|
.main-content-sidebar-expanded {
|
||||||
|
@apply lg:ml-64;
|
||||||
|
}
|
||||||
|
.main-content-sidebar-collapsed {
|
||||||
|
@apply lg:ml-16;
|
||||||
|
}
|
||||||
|
.main-content-mobile-header {
|
||||||
|
@apply pt-14 lg:pt-0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|||||||
Reference in New Issue
Block a user