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

@swisper.ai/b2b

v0.1.0

Published

Swisper B2B UI components

Readme

@swisper.ai/b2b

npm license

Swisper B2B UI components. They hand the deployment's sign-in mode and branding to React through an optional B2BProvider; outside it, every hook falls back to b2c with no branding, so the same app runs in both modes. The app resolves the values from its own configuration and passes them in: the package never reads env.

Boundary

Code belongs here if only the B2B products use it: a page layout or a screen part with B2B wording and behaviour fixed, such as the sign-in page layout. A primitive that any product could use belongs in @swisper.ai/ui, a card in @swisper.ai/cards-ui, and the chat surface in @swisper.ai/web-chat-ui.

Install

npm install @swisper.ai/b2b @swisper.ai/tailwind-config @swisper.ai/design-system
npm install -D tailwindcss @tailwindcss/vite

| Peer | Range | Why | | ----------- | ---------- | -------------------- | | react | >=18.0.0 | Components and hooks | | react-dom | >=18.0.0 | Rendering |

It depends on @swisper.ai/ui, whose peers (@mui/material, @mui/system, @mui/icons-material, @emotion/react, @emotion/styled) apply too. Add tailwindcss() from @tailwindcss/vite to the plugins of your Vite config.

Setup

  1. Set up the styles. Add the Echo DS theme, the B2B token layer and the package sources to the top of your global stylesheet (for example main.css):

    @import '@swisper.ai/tailwind-config/tailwind.css';
    @import '@swisper.ai/design-system/dist/b2b.css';
    /* Required for Tailwind to find class references in @swisper.ai components. */
    @source '../node_modules/@swisper.ai/b2b/src';
    @source '../node_modules/@swisper.ai/ui/src';
    
    /* The rest of your application CSS */

    tailwind.css brings in Tailwind and the base theme, and b2b.css overrides its component tokens with the B2B values, so it comes second. The @source paths are relative to this CSS file: point them at wherever node_modules sits.

  2. Import your stylesheet before rendering any components:

    // Must be imported first, so the theme and Tailwind layers are defined before any component styles
    import './main.css';
    
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    
    import { App } from './App';
    
    createRoot(document.getElementById('root')!).render(
      <StrictMode>
        <App />
      </StrictMode>,
    );
  3. Configure the provider (optional). B2BProvider gives every component below it the deployment's mode, loginImageUrl, logoUrl and providerName (the customer's name, the alt text of both images). Resolve and validate them from your app's configuration; the provider renders its children immediately. Without a provider, the hooks return 'b2c' and no branding.

    // Must be imported first, so the theme and Tailwind layers are defined before any component styles
    import './main.css';
    
    import { B2BProvider } from '@swisper.ai/b2b';
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    
    import { App } from './App';
    
    createRoot(document.getElementById('root')!).render(
      <StrictMode>
        <B2BProvider
          mode="b2b"
          loginImageUrl="/branding/login.jpg"
          logoUrl="/branding/logo.svg"
          providerName="Acme"
        >
          <App />
        </B2BProvider>
      </StrictMode>,
    );
  4. Read the mode and branding anywhere below it:

import { useB2BBranding, useIsB2BMode } from '@swisper.ai/b2b';

export function LogInHeader() {
  const isB2B = useIsB2BMode();
  const { loginImageUrl, logoUrl, providerName } = useB2BBranding();

  if (!isB2B) {
    return <h1>Welcome back</h1>;
  }

  return (
    <header>
      {logoUrl ? <img src={logoUrl} alt={providerName ?? ''} className="h-32" /> : null}
      {loginImageUrl ? <img src={loginImageUrl} alt="" /> : null}
    </header>
  );
}

Sign-in page

B2BLoginPageLayout is the page shell of an enterprise sign-in: a header band, the customer's logo over your card, and a footer band, over the login image. Combine the parts you need; the login image and the logo come from the nearest B2BProvider, never from props. Below sm, or without an image, the page is plain bg-muted. The card and its h1 are yours, and so is the favicon: set it in your HTML or head manager.

import {
  B2BLoginPageLayout,
  B2BLoginPageLayoutBrand,
  B2BLoginPageLayoutContent,
  B2BLoginPageLayoutFooter,
  B2BLoginPageLayoutHeader,
  B2BLoginPageLayoutHeaderDescription,
  B2BLoginPageLayoutHeaderTitle,
  B2BLoginPageLayoutPoweredBy,
} from '@swisper.ai/b2b';
import { Link } from '@swisper.ai/ui';

export function SignInPage() {
  return (
    <B2BLoginPageLayout>
      <B2BLoginPageLayoutHeader>
        <B2BLoginPageLayoutHeaderTitle>Signing in to Swisper</B2BLoginPageLayoutHeaderTitle>
        <B2BLoginPageLayoutHeaderDescription>
          Use your company account to access Swisper.
        </B2BLoginPageLayoutHeaderDescription>
      </B2BLoginPageLayoutHeader>
      <B2BLoginPageLayoutContent>
        {/* The provider's logo; set href to make it a link. */}
        <B2BLoginPageLayoutBrand />
        <SignInCard />
      </B2BLoginPageLayoutContent>
      <B2BLoginPageLayoutFooter>
        <Link href="/privacy">Privacy</Link>
        <Link href="/terms">Terms</Link>
        <B2BLoginPageLayoutPoweredBy href="https://swisper.ai" />
      </B2BLoginPageLayoutFooter>
    </B2BLoginPageLayout>
  );
}

Styles

The setup above builds the package's classes into your app's own Tailwind CSS. @swisper.ai/tailwind-config/tailwind.css already scans the other @swisper.ai component packages when they are installed next to it; the @source lines cover layouts, such as pnpm's, where they are not.

Without a Tailwind build

@swisper.ai/b2b/index.css is a prebuilt stylesheet: Tailwind's preflight, the base design-system theme and every utility the @swisper.ai packages use. It does not include the B2B token layer, so load both once, at the app's entry, in this order:

import '@swisper.ai/b2b/index.css';
import '@swisper.ai/design-system/dist/b2b.css';

Use one setup, not both. If your app also loads the prebuilt index.css of another @swisper.ai package, or builds its own Tailwind CSS next to it, the same rules ship twice and whichever file loads last wins where they conflict.

API

Props and live examples are in the Echo DS Storybook, under B2B.

| Export | Why it exists | | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | B2BProvider, B2BProviderProps | Hands the deployment's sign-in mode, login image, logo and provider name to React once, so components read them instead of each app threading them through | | useB2BContext | The whole value from the nearest B2BProvider: b2c with no branding when there is none | | useAuthMode, useIsB2BMode | The sign-in mode, and true only inside a provider whose mode is 'b2b', so b2b-only UI has one check | | useB2BBranding | The loginImageUrl, logoUrl and providerName from the nearest provider, all null when there is none | | AuthMode, B2BContextValue | The 'b2c' \| 'b2b' mode and the provider's value | | B2BLoginPageLayout, B2BLoginPageLayoutContent | The sign-in page over the provider's login image, and its main column (up to max-w-sm) for the brand and your card | | B2BLoginPageLayoutHeader, B2BLoginPageLayoutHeaderTitle, B2BLoginPageLayoutHeaderLogo, B2BLoginPageLayoutHeaderDescription | The band that says what the user signs in to, with the product's logo inline | | B2BLoginPageLayoutBrand | The customer's logo above the card: its children, else the provider's logo; a link while href is set | | B2BLoginPageLayoutFooter | The band for brief links, such as privacy and terms | | B2BLoginPageLayoutPoweredBy | The "Powered by Swisper" pill, last in the footer: right of the links from sm up; a link while href is set | | B2BLoginPageLayoutProps, B2BLoginPageLayoutHeaderProps, … | The props of each part |

Troubleshooting

| Symptom | Cause | Fix | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | "Invalid hook call", or a context is missing | The app resolves two copies of React | Add resolve: { dedupe: ['react', 'react-dom'] } to the Vite config | | useIsB2BMode() is always false, or the logo never shows | No B2BProvider sits above the component, or its mode is not 'b2b' | Wrap the app in B2BProvider with mode="b2b" (Setup, step 3) | | Components render without layout or colors | Tailwind never scans the package, because it skips node_modules by default | Import @swisper.ai/tailwind-config/tailwind.css and add the @source lines from Setup, step 1, or load the prebuilt index.css instead | | Buttons and banners have the B2C colours and radii | The B2B token layer isn't loaded | Import @swisper.ai/design-system/dist/b2b.css after the theme or the package's CSS | | Can't resolve '@swisper.ai/design-system/b2b.css' | The layer is exported under dist/ | Import @swisper.ai/design-system/dist/b2b.css | | Your own base styles (fonts, headings, resets) get overridden | The prebuilt index.css is loaded next to your own Tailwind build, and its preflight wins | Use one of the two: drop index.css and keep the Tailwind setup | | Spacing looks wrong (huge gaps, tiny icons) | A number in a spacing class is pixels: gap-8 is 8px, not Tailwind's default 32px | Write the pixel value you want | | Styles are missing or doubled, or the MUI theme doesn't apply | The app resolves two copies of @emotion/react or @mui/material | Add them to resolve.dedupe in the Vite config, next to react | | ERR_UNSUPPORTED_DIR_IMPORT when a test imports the package | dist/ uses extensionless imports that only a bundler resolves, and Vitest externalizes it | Set server.deps.inline: [/@swisper\.ai\//] in the Vitest config |

License

Apache-2.0