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 store from './store';
|
||||
import ThemeProvider from './components/ThemeProvider';
|
||||
import { SidebarProvider } from './context/SidebarContext';
|
||||
import Statistics from './pages/Statistics';
|
||||
import Calculators from './pages/Calculators';
|
||||
import Faq from './pages/Faq';
|
||||
@@ -53,10 +54,11 @@ export default function App() {
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<ThemeProvider>
|
||||
<div className='App'>
|
||||
<BrowserRouter basename='/'>
|
||||
<Auth0Provider
|
||||
<SidebarProvider>
|
||||
<ThemeProvider>
|
||||
<div className='App'>
|
||||
<BrowserRouter basename='/'>
|
||||
<Auth0Provider
|
||||
domain='login.osleague.tools'
|
||||
clientId='yfqwKEhQO8FL7MlxWmWo7ekuGgzSrfmh'
|
||||
redirectUri={window.location.origin}
|
||||
@@ -78,10 +80,11 @@ export default function App() {
|
||||
<Route path='faq' element={<Faq />} />
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
</Auth0Provider>
|
||||
</BrowserRouter>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</Auth0Provider>
|
||||
</BrowserRouter>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</SidebarProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
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 ManageDataModal from './ManageDataModal';
|
||||
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('Groups', 'primary', 0, 3).withRouterLink('/groups').withIconFont('groups'),
|
||||
new NavItem('Character', 'secondary', 1, 0).withCustomRenderFn(
|
||||
() => <Character.NavBarItem key='character' setCharacterModalOpen={setCharacterModalOpen} />,
|
||||
() => <Character.CollapsedMenu key='character' setCharacterModalOpen={setCharacterModalOpen} />
|
||||
(isCollapsed, onNavigate) => (
|
||||
<Character.SideBarItem
|
||||
key='character'
|
||||
setCharacterModalOpen={setCharacterModalOpen}
|
||||
isCollapsed={isCollapsed}
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)
|
||||
),
|
||||
new NavItem('Import', 'secondary', 2, 0).withCustomRenderFn(
|
||||
() => <ManageData.NavBarItem key='manage' setManageDataModalType={setManageDataModalType} />,
|
||||
() => <ManageData.CollapsedMenu key='manage' setManageDataModalType={setManageDataModalType} />
|
||||
new NavItem('Data', 'secondary', 2, 0).withCustomRenderFn(
|
||||
(isCollapsed, onNavigate) => (
|
||||
<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('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('Feedback', 'overflow', 4, 1).withCustomRenderFn(
|
||||
() => <Feedback.NavBarItem key='feedback' setFeedbackModalOpen={setFeedbackModalOpen} />,
|
||||
() => <Feedback.CollapsedMenu key='feedback' setFeedbackModalOpen={setFeedbackModalOpen} />
|
||||
),
|
||||
new NavItem('Github', 'overflow', 4, 2)
|
||||
new NavItem('Github', 'overflow', 4, 1)
|
||||
.withHref('https://github.com/osrs-reldo/os-league-tools', '_blank')
|
||||
.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)
|
||||
.withHref('https://ko-fi.com/osleaguetools', '_blank')
|
||||
.withIconFont('savings'),
|
||||
new NavItem('FAQ', 'overflow', 4, 4).withRouterLink('/faq').withIconFont('help_outline'),
|
||||
new NavItem('About', 'overflow', 4, 5).withRouterLink('/about').withIconFont('info'),
|
||||
];
|
||||
|
||||
return (
|
||||
<Page limitContentWidth={limitContentWidth}>
|
||||
<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.Body>
|
||||
{children}
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
import React from 'react';
|
||||
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 }) {
|
||||
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 (
|
||||
<div className='bg-secondary w-full h-full min-h-screen'>
|
||||
<div className='flex min-h-screen'>
|
||||
{/* Left sidebar navigation */}
|
||||
{nav}
|
||||
<div className='py-5 page-wrapper'>
|
||||
{banner && <div>{banner}</div>}
|
||||
<div className='flex md:flex-row flex-col justify-center'>
|
||||
{sidebar && sidebarPosition === 'left' && <div className='sidebar-wrapper'>{sidebar}</div>}
|
||||
{body && <div className={`w-full ${limitContentWidth ? '2xl:container' : ''}`}>{body}</div>}
|
||||
{sidebar && sidebarPosition === 'right' && <div className='sidebar-wrapper'>{sidebar}</div>}
|
||||
|
||||
{/* Main content area */}
|
||||
<div className={`flex-1 bg-secondary main-content-with-sidebar ${contentMarginClass}`}>
|
||||
<div className='py-5 page-wrapper'>
|
||||
{banner && <div>{banner}</div>}
|
||||
<div className='flex md:flex-row flex-col justify-center'>
|
||||
{sidebar && sidebarPosition === 'left' && <div className='sidebar-wrapper'>{sidebar}</div>}
|
||||
{body && <div className={`w-full ${limitContentWidth ? '2xl:container' : ''}`}>{body}</div>}
|
||||
{sidebar && sidebarPosition === 'right' && <div className='sidebar-wrapper'>{sidebar}</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),
|
||||
-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 {
|
||||
visibility: visible !important;
|
||||
}
|
||||
@@ -2387,6 +2840,9 @@ select[multiple]:focus option:checked {
|
||||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
.h-6 {
|
||||
height: 1.5rem;
|
||||
}
|
||||
.h-8 {
|
||||
height: 2rem;
|
||||
}
|
||||
@@ -2459,6 +2915,9 @@ select[multiple]:focus option:checked {
|
||||
.w-5 {
|
||||
width: 1.25rem;
|
||||
}
|
||||
.w-6 {
|
||||
width: 1.5rem;
|
||||
}
|
||||
.w-60 {
|
||||
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),
|
||||
-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 {
|
||||
|
||||
Reference in New Issue
Block a user