diff --git a/os-league-tools-master/src/App.js b/os-league-tools-master/src/App.js index 1a868e37..e525ee5b 100644 --- a/os-league-tools-master/src/App.js +++ b/os-league-tools-master/src/App.js @@ -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 ( - -
- - + +
+ + } /> - - -
-
+
+
+
+
+
); } diff --git a/os-league-tools-master/src/components/PageWrapper.js b/os-league-tools-master/src/components/PageWrapper.js index 178c2383..3219cf49 100644 --- a/os-league-tools-master/src/components/PageWrapper.js +++ b/os-league-tools-master/src/components/PageWrapper.js @@ -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( - () => , - () => + (isCollapsed, onNavigate) => ( + + ) ), - new NavItem('Import', 'secondary', 2, 0).withCustomRenderFn( - () => , - () => + new NavItem('Data', 'secondary', 2, 0).withCustomRenderFn( + (isCollapsed, onNavigate) => ( + + ) ), - // TODO re-enable user login - // new NavItem('Login', 'secondary', 3, 0).withCustomRenderFn( - // () => , - // () => - // ), 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( - () => , - () => - ), - 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) => ( + + ) + ), 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 ( - + {children} diff --git a/os-league-tools-master/src/components/common/Page.js b/os-league-tools-master/src/components/common/Page.js index 7e3de480..dfe6d8c5 100644 --- a/os-league-tools-master/src/components/common/Page.js +++ b/os-league-tools-master/src/components/common/Page.js @@ -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 ( -
+
+ {/* Left sidebar navigation */} {nav} -
- {banner &&
{banner}
} -
- {sidebar && sidebarPosition === 'left' &&
{sidebar}
} - {body &&
{body}
} - {sidebar && sidebarPosition === 'right' &&
{sidebar}
} + + {/* Main content area */} +
+
+ {banner &&
{banner}
} +
+ {sidebar && sidebarPosition === 'left' &&
{sidebar}
} + {body &&
{body}
} + {sidebar && sidebarPosition === 'right' &&
{sidebar}
} +
diff --git a/os-league-tools-master/src/components/common/SideBar.js b/os-league-tools-master/src/components/common/SideBar.js new file mode 100644 index 00000000..f6685c86 --- /dev/null +++ b/os-league-tools-master/src/components/common/SideBar.js @@ -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 ( + + ); + } + + // Mobile: header + drawer + return ( + <> + + + + + ); +} + +function SideBarBrand({ logo, name, isCollapsed }) { + return ( + + + {!isCollapsed && {name}} + + ); +} + +function SideBarSection({ title, children }) { + return ( +
+ {title &&
{title}
} + {children} +
+ ); +} + +function SideBarDivider() { + return
; +} + +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 && ( + {item.iconFont} + ); + const label = !isCollapsed && {item.label}; + + // External link + if (item.href) { + return ( + + {icon} + {label} + + ); + } + + // Router link + if (item.to) { + return ( + `sidebar-nav-link ${isActive ? 'active' : ''}`} + to={item.to} + onClick={onClick} + title={isCollapsed ? item.label : undefined} + > + {icon} + {label} + + ); + } + + // Button with onClick + if (item.onClick) { + return ( + + ); + } + + return null; +} + +function MobileHeader({ logo, name }) { + const { openDrawer } = useSidebar(); + + return ( +
+ + + + {name} + +
+ ); +} + +function SideBarDrawer({ + innerRef, + isOpen, + brandLogo, + brandName, + primaryNavItems, + secondaryNavItems, + overflowGroups, + onClose, +}) { + return ( + + ); +} + +function SideBarBackdrop({ isOpen, onClick }) { + return ( +