npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xsolla/xui-b2b-sidebar

v0.186.1

Published

A composable navigation sidebar for B2B admin surfaces. Renders an expanded panel with grouped menu items and a parallel collapsed icon strip with hover popovers, both driven by the same `SidebarProvider` so the active route, link component, and collapsed

Readme

Sidebar

A composable navigation sidebar for B2B admin surfaces. Renders an expanded panel with grouped menu items and a parallel collapsed icon strip with hover popovers, both driven by the same SidebarProvider so the active route, link component, and collapsed state stay in sync.

Installation

npm install @xsolla/xui-b2b-sidebar

Imports

import {
  Sidebar,
  SidebarProvider,
  useSidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarMenu,
  SidebarMenuItem,
  SidebarMenuCollapsible,
  SidebarMenuSub,
  SidebarTrigger,
  SidebarChatButton,
  SidebarCollapsed,
  type SidebarProps,
  type SidebarProviderProps,
  type SidebarMenuItemProps,
  type SidebarMenuCollapsibleProps,
  type SidebarCollapsedProps,
  type SidebarItemType,
  type SidebarLinkProps,
  type SidebarLinkActiveCheck,
  type SidebarLinkClickHandler,
} from "@xsolla/xui-b2b-sidebar";

Quick start

Wrap the sidebar in SidebarProvider, supplying the current pathname, the controlled collapsed state, and your app's link component (React Router, Next.js Link, etc.). The default link is a plain <a> if you omit linkComponent.

import { useState } from "react";
import { Home, Wallet, Settings } from "@xsolla/xui-icons-base";
import {
  Sidebar,
  SidebarProvider,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarMenu,
  SidebarMenuItem,
  SidebarMenuCollapsible,
  SidebarMenuSub,
  SidebarTrigger,
} from "@xsolla/xui-b2b-sidebar";

function AppShell({ pathname }: { pathname: string }) {
  const [collapsed, setCollapsed] = useState(false);

  return (
    <SidebarProvider
      collapsed={collapsed}
      onCollapsedChange={setCollapsed}
      pathname={pathname}
    >
      <Sidebar>
        <SidebarContent>
          <SidebarGroup label="Main">
            <SidebarMenu>
              <SidebarMenuItem
                to="/dashboard"
                icon={<Home size={18} variant="line" aria-hidden />}
                label="Dashboard"
              />
              <SidebarMenuCollapsible
                icon={<Wallet size={18} variant="line" aria-hidden />}
                label="Finance"
                matchPaths={["/finance"]}
              >
                <SidebarMenuSub>
                  <SidebarMenuItem
                    to="/finance/payouts"
                    label="Payouts"
                    isNested
                  />
                  <SidebarMenuItem
                    to="/finance/reports"
                    label="Reports"
                    isNested
                  />
                </SidebarMenuSub>
              </SidebarMenuCollapsible>
            </SidebarMenu>
          </SidebarGroup>
          <SidebarGroup label="Workspace">
            <SidebarMenu>
              <SidebarMenuItem
                to="/settings"
                icon={<Settings size={18} variant="line" aria-hidden />}
                label="Settings"
              />
            </SidebarMenu>
          </SidebarGroup>
        </SidebarContent>
        <SidebarFooter>
          <SidebarTrigger />
        </SidebarFooter>
      </Sidebar>
    </SidebarProvider>
  );
}

API Reference

None of the sidebar components extend ThemeOverrideProps — they read theme via useResolvedTheme() from the surrounding provider. Wrap the app in a theme provider if you need to override mode or product context.

<SidebarProvider>

| Prop | Type | Default | Description | | ------------------- | --------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------- | | testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. | | collapsed | boolean | uncontrolled | Controlled collapsed state. Omit to use the internal state. | | onCollapsedChange | (collapsed: boolean) => void | — | Called when the user toggles via SidebarTrigger or the collapsed-mode toggle. | | pathname | string | "" | Current route, used for active-state matching and matchPaths auto-expand. | | linkComponent | ComponentType<SidebarLinkProps> | plain <a> | Router link component. | | children | ReactNode | — | Sidebar tree. |

useSidebar()

Read the provider's state from anywhere inside the tree.

function useSidebar(): {
  collapsed: boolean;
  onCollapsedChange: (collapsed: boolean) => void;
  pathname: string;
  LinkComponent: React.ComponentType<SidebarLinkProps>;
  expandedId: string | null;
  onExpandedIdChange: (id: string | null) => void;
};

expandedId / onExpandedIdChange coordinate which SidebarMenuCollapsible is open — only one at a time.

<Sidebar>

| Prop | Type | Default | Description | | ---------------------- | ------------------- | ------- | ------------------------------------------------------ | | collapsedItems | SidebarItemType[] | [] | Top icons in the collapsed strip. | | collapsedToolItems | SidebarItemType[] | [] | Tool icons rendered after a spacer. | | collapsedBottomItems | SidebarItemType[] | [] | Bottom icons (e.g. Settings, Billing). | | showChat | boolean | true | Render the chat button in the collapsed footer. | | onChatClick | () => void | — | Click handler for the chat button. | | chatBadge | boolean | false | Show an unread-indicator dot on the chat button. | | children | ReactNode | — | The expanded tree (SidebarContent, SidebarFooter). |

<SidebarMenuItem>

| Prop | Type | Default | Description | | ----------------- | ------------------------- | ------- | ----------------------------------------------------------------------------------- | | to | string | — | Route URL passed through to linkComponent. | | label | ReactNode | — | Text label. | | icon | ReactNode | — | Leading icon (hidden when isNested). | | exact | boolean | — | Exact-match flag forwarded to the link component. | | external | boolean | — | Marks the link as external (sets target/rel). | | hasExternalIcon | boolean | — | Shows the external-link icon at the trailing edge. | | target | string \| null | — | Anchor target. | | onClick | SidebarLinkClickHandler | — | Click handler. | | dataId | string | — | data-id attribute for tests/analytics. | | isActive | SidebarLinkActiveCheck | — | Custom active-check forwarded to the link. | | isPinned | boolean | — | Renders the icon at 12px to indicate a pinned item. | | showBadge | boolean | — | Shows a small alert dot next to the label. | | hasTooltip | boolean | — | Truncates labels longer than 20 chars and shows the full text on hover. | | beta | boolean | — | Renders a "Beta" tag at the trailing edge. | | multiLine | boolean | — | Allows the label to wrap to multiple lines. | | extra | ReactNode | — | Slot rendered between the icon and the label. | | isNested | boolean | false | Set when rendered inside SidebarMenuSub (drops the icon, indents under the rail). |

<SidebarMenuCollapsible>

| Prop | Type | Default | Description | | ------------ | ----------- | ------- | ------------------------------------------------------------------------- | | icon | ReactNode | — | Leading icon. | | label | ReactNode | — | Section label. | | dataId | string | — | data-id attribute. Also seeds the stable id used by aria-controls. | | matchPaths | string[] | [] | Route prefixes that auto-expand this section. | | children | ReactNode | — | Nested items, typically a SidebarMenuSub of SidebarMenuItem isNested. |

<SidebarCollapsed>

Lower-level collapsed-strip renderer. The <Sidebar> component composes this for you; reach for it when you need to render the collapsed strip outside the standard layout.

| Prop | Type | Default | Description | | ------------------ | ------------------- | ------- | -------------------------------------------------- | | items | SidebarItemType[] | — | Top icon items. | | toolItems | SidebarItemType[] | [] | Tool icons after a spacer. | | bottomItems | SidebarItemType[] | [] | Bottom icons. | | onToggleCollapse | () => void | — | Click handler for the toggle button in the footer. | | onChatClick | () => void | — | Click handler for the chat button. | | showChat | boolean | true | Render the chat button. | | chatBadge | boolean | false | Show the unread dot. |

<SidebarContent>

Scrollable body container rendered inside <Sidebar>. Adds the configured padding and a hover-visible scrollbar.

<SidebarFooter>

Bordered footer row for the bottom of the sidebar; typically wraps the chat button and trigger.

<SidebarTrigger>

Collapse/expand toggle button. Reads/writes state from the surrounding SidebarProvider.

<SidebarChatButton>

Branded chat button for the footer. Props: onClick: () => void, badge?: boolean.

<SidebarGroup>

Section container with an optional uppercase label. Props: label?: ReactNode, children: ReactNode.

<SidebarMenu>

Flex-column wrapper around SidebarMenuItem / SidebarMenuCollapsible children.

<SidebarMenuSub>

Fragment used as a structural marker inside collapsibles to group nested menu items.

Types

type SidebarItemType = {
  to?: string;
  label: ReactNode;
  icon?: ReactNode;
  dataId?: string;
  exact?: boolean;
  external?: boolean;
  hasExternalIcon?: boolean;
  target?: string | null;
  onClick?: SidebarLinkClickHandler;
  hasTooltip?: boolean;
  beta?: boolean;
  multiLine?: boolean;
  isPinned?: boolean;
  showBadge?: boolean;
  reverse?: boolean; // reserved for legacy reversed-layout items
  isActive?: SidebarLinkActiveCheck;
  children?: SidebarItemType[];
  privileges?: string[];
  visibility?: string;
  disallowedIntegrationTypes?: string[];
  extra?: ReactNode;
};

interface SidebarLinkProps {
  to?: string;
  exact?: boolean;
  external?: boolean;
  target?: string | null;
  className?: string;
  activeClassName?: string;
  isActive?: SidebarLinkActiveCheck;
  onClick?: SidebarLinkClickHandler;
  dataId?: string;
  "aria-label"?: string;
  children: ReactNode;
}

type SidebarLinkActiveCheck = (
  match: unknown,
  location: { pathname: string; search?: string; hash?: string }
) => boolean;

type SidebarLinkClickHandler = (event?: React.MouseEvent<HTMLElement>) => void;

Examples

Custom link component

Supply your router's link via the linkComponent prop on SidebarProvider. The component receives to, exact, external, target, className, activeClassName, isActive, onClick, dataId, and renders children.

import { NavLink } from "react-router-dom";
import type { SidebarLinkProps } from "@xsolla/xui-b2b-sidebar";

const RouterLink: React.FC<SidebarLinkProps> = ({
  to,
  exact,
  className,
  activeClassName,
  children,
  onClick,
  dataId,
}) => (
  <NavLink
    to={to ?? "#"}
    end={exact}
    className={({ isActive }) =>
      [className, isActive && activeClassName].filter(Boolean).join(" ")
    }
    data-id={dataId}
    onClick={onClick}
  >
    {children}
  </NavLink>
);

<SidebarProvider linkComponent={RouterLink} pathname={location.pathname}>
  {/* ... */}
</SidebarProvider>;

Auto-expanding sections

Pass matchPaths to SidebarMenuCollapsible. When the current pathname starts with any of the listed prefixes, the section auto-expands. Only one collapsible can be open at a time, so navigating between sections collapses the others.

import { Wallet } from "@xsolla/xui-icons-base";
import {
  SidebarMenuCollapsible,
  SidebarMenuSub,
  SidebarMenuItem,
} from "@xsolla/xui-b2b-sidebar";

<SidebarMenuCollapsible
  icon={<Wallet size={18} variant="line" aria-hidden />}
  label="Finance"
  matchPaths={["/finance", "/finance/payouts", "/finance/reports"]}
>
  <SidebarMenuSub>
    <SidebarMenuItem to="/finance/payouts" label="Payouts" isNested />
    <SidebarMenuItem to="/finance/reports" label="Reports" isNested />
  </SidebarMenuSub>
</SidebarMenuCollapsible>;

Item flags

import { Pin, Graph, Layer } from "@xsolla/xui-icons-base";
import { SidebarMenuItem } from "@xsolla/xui-b2b-sidebar";

<>
  <SidebarMenuItem
    to="/pinned"
    label="Pinned report"
    icon={<Pin size={18} aria-hidden />}
    isPinned
    showBadge
  />
  <SidebarMenuItem
    to="/labs"
    label="New analytics"
    icon={<Graph size={18} aria-hidden />}
    beta
  />
  <SidebarMenuItem
    to="https://docs.example.com"
    label="Documentation"
    icon={<Layer size={18} aria-hidden />}
    external
    hasExternalIcon
    target="_blank"
  />
</>;

Collapsed icon strip

Because the collapsed layout is fundamentally different (icons only, hover popovers), pass icon-strip items separately as collapsedItems, collapsedToolItems (rendered after a spacer in the main scroll area), and collapsedBottomItems (rendered above the chat/toggle footer). Items with children open a hover popover listing the children; items without children navigate directly on click (the icon is rendered as a link), and show a label tooltip on hover for discoverability.

import { Home, Wallet, Settings } from "@xsolla/xui-icons-base";
import { Sidebar, type SidebarItemType } from "@xsolla/xui-b2b-sidebar";

const mainNavItems: SidebarItemType[] = [
  {
    to: "/dashboard",
    label: "Dashboard",
    icon: <Home size={18} variant="line" aria-hidden />,
  },
  {
    label: "Finance",
    icon: <Wallet size={18} variant="line" aria-hidden />,
    children: [
      { to: "/finance/payouts", label: "Payouts" },
      { to: "/finance/reports", label: "Reports" },
    ],
  },
];

<Sidebar
  collapsedItems={mainNavItems}
  collapsedBottomItems={[
    {
      to: "/settings",
      label: "Settings",
      icon: <Settings size={18} aria-hidden />,
    },
  ]}
  onChatClick={() => openSupportChat()}
  chatBadge={hasUnreadMessages}
>
  {/* expanded tree */}
</Sidebar>;

Accessibility

  • The root sidebar is role="navigation" with aria-label="Sidebar navigation". The expanded and collapsed panes are toggled via aria-hidden so only one is announced at a time.
  • SidebarTrigger and the collapsed-mode toggle have aria-pressed and a context-aware aria-label ("Expand sidebar" / "Collapse sidebar").
  • Each SidebarMenuCollapsible header is a <button> with aria-expanded and aria-controls linking to its region.
  • Collapsed icon items with children expose aria-haspopup="menu"; their popovers are keyboard-reachable (focus opens, blur closes after 150ms, Escape closes immediately). Leaf items (no children) render as native links and navigate on click or Enter.
  • Truncated labels (hasTooltip) fall back to a Tooltip with the full text.

Behaviour

  • Expanded width is sourced from theme.sizing.sidebar().widthExpanded, collapsed from widthCollapsed; the wrapper animates width and cross-fades the two views via opacity.
  • Only one SidebarMenuCollapsible is open at a time; the section auto-expands when pathname matches one of matchPaths (a leading /<merchantId> numeric prefix is stripped before matching).
  • Hover popovers in collapsed mode are rendered into a portal on document.body and positioned with viewport-clamped position: fixed math.
  • Active route detection lives in the consumer's linkComponent — apply activeClassName when the link matches the current route.