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

spry-apps-dropdown

v3.0.4

Published

React components for Spry apps dropdown and profile menu with dynamic API integration - Google-style UI

Readme

spry-apps-dropdown

A React component library for Spry apps dropdown and profile menu with multi-account switching. Features a Google-style UI with beautiful animations and automatic Keycloak authentication.

Features

  • 🎨 Beautiful Material-UI design with smooth animations (Google-inspired)
  • 👥 Multi-account switching - Users can log into multiple accounts and switch between them
  • 🔄 Automatic data fetching from API with caching
  • 🔐 Built-in Keycloak/OIDC authentication via react-oidc-context
  • ⚡ Loading and error states
  • 📱 Responsive layout
  • 🎯 TypeScript support
  • 🔌 Easy integration - Just 3 steps!

Installation

npm install spry-apps-dropdown react-oidc-context oidc-client-ts

Peer Dependencies:

  • react ^18.0.0 || ^19.0.0
  • react-dom ^18.0.0 || ^19.0.0
  • react-oidc-context ^3.3.0
  • oidc-client-ts ^1.0.0

Quick Start (3 Steps)

Step 1: Wrap Your App with SpryAuthProvider

// main.tsx or index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { SpryAuthProvider } from 'spry-apps-dropdown'
import App from './App'

const oidcConfig = {
  authority: 'https://auth.sprylogin.com/realms/sprylogin',
  client_id: 'your-client-id',
  redirect_uri: window.location.origin,
  post_logout_redirect_uri: window.location.origin,
  onSigninCallback: () => {
    window.history.replaceState({}, document.title, window.location.pathname)
  },
}

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <SpryAuthProvider config={oidcConfig}>
      <App />
    </SpryAuthProvider>
  </React.StrictMode>
)

Step 2: Use useSpryAccountManager Hook

// App.tsx
import { useSpryAccountManager } from 'spry-apps-dropdown'

function App() {
  const accountManager = useSpryAccountManager()

  return (
    <div>
      {/* Your app content */}
    </div>
  )
}

Step 3: Add the TopBar Component

// App.tsx
import { TopBar, useSpryAuth, useSpryAccountManager } from 'spry-apps-dropdown'

function App() {
  const auth = useSpryAuth()
  const accountManager = useSpryAccountManager()

  return (
    <div>
      <AppBar>
        <Toolbar>
          <Typography variant="h6" sx={{ flexGrow: 1 }}>
            My App
          </Typography>

          <TopBar
            apiUrl="https://sprylogin.com/apps-api"
            onSignOut={() => auth.signoutRedirect()}
            getAuthToken={() => auth.user?.access_token || null}
            accountManager={accountManager}
          />
        </Toolbar>
      </AppBar>

      {/* Your app content */}
    </div>
  )
}

That's it! Your app now has:

  • ✅ Multi-account switching
  • ✅ Apps dropdown
  • ✅ Profile menu
  • ✅ Automatic authentication

Using Auth Token in Your API Calls

Get the current user's token using useSpryAuth():

import { useSpryAuth } from 'spry-apps-dropdown'

function MyComponent() {
  const auth = useSpryAuth()

  useEffect(() => {
    const token = auth.user?.access_token

    fetch('/api/data', {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    })
  }, [auth.user])
}

For React Query:

import { useSpryAuth } from 'spry-apps-dropdown'
import { useQuery } from '@tanstack/react-query'

function MyComponent() {
  const auth = useSpryAuth()

  const { data } = useQuery({
    queryKey: ['data'],
    queryFn: async () => {
      const token = auth.user?.access_token
      const res = await fetch('/api/data', {
        headers: { 'Authorization': `Bearer ${token}` }
      })
      return res.json()
    },
    enabled: !!auth.user,
  })
}

Multi-Account Switching

The package automatically handles multi-account switching:

  1. User clicks "Add Another Account" in ProfileMenu
  2. Redirects to Keycloak for login (with prompt=login)
  3. New account is added to localStorage
  4. User can switch between accounts from ProfileMenu
  5. API calls automatically use the active account's token

No manual code needed - it just works!


API Reference

TopBar Component

Combined apps dropdown + profile menu (recommended).

Props

| Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | apiUrl | string | Yes | - | Base URL for apps API | | accountManager | UseAccountManagerReturn | Yes | - | Account manager from useSpryAccountManager() | | profileApiUrl | string | No | Same as apiUrl | Base URL for profile API | | onSignOut | () => void | No | - | Callback when user signs out | | getAuthToken | () => string \| null | No | - | Function to get auth token | | profileHeaders | Record<string, string> | No | - | Custom headers for profile API | | showAppsDropdown | boolean | No | true | Show apps dropdown | | showProfileMenu | boolean | No | true | Show profile menu | | appsRefetchInterval | number | No | 300000 (5 min) | Apps refetch interval (ms) | | appsCacheTime | number | No | 300000 (5 min) | Apps cache duration (ms) | | profileRefetchInterval | number | No | 300000 (5 min) | Profile refetch interval (ms) | | profileCacheTime | number | No | 300000 (5 min) | Profile cache duration (ms) |


SpryAuthProvider Component

Wraps your app with Keycloak/OIDC authentication.

Props

| Prop | Type | Required | Description | |------|------|----------|-------------| | config | UserManagerSettings | Yes | OIDC configuration object | | children | ReactNode | Yes | Your app components |

Example config:

const oidcConfig = {
  authority: 'https://auth.sprylogin.com/realms/sprylogin',
  client_id: 'my-app-client',
  redirect_uri: window.location.origin,
  post_logout_redirect_uri: window.location.origin,
  onSigninCallback: () => {
    window.history.replaceState({}, document.title, window.location.pathname)
  },
}

useSpryAccountManager() Hook

Returns account manager for multi-account switching.

Returns

{
  accounts: StoredAccount[]        // All logged-in accounts
  activeAccount: StoredAccount     // Currently active account
  switchAccount: (id: string) => Promise<void>
  addNewAccount: () => Promise<void>
  removeAccount: (id: string) => Promise<void>
  refreshAccountToken: (id: string) => Promise<void>
}

useSpryAuth() Hook

Returns the current authentication state (alias for useAuth() from react-oidc-context).

Returns

{
  user: User | null               // Current user object
  isAuthenticated: boolean        // Is user logged in
  isLoading: boolean             // Is auth loading
  signinRedirect: () => Promise<void>
  signoutRedirect: () => Promise<void>
  // ... other react-oidc-context methods
}

Advanced: Handling Account Switches

If you're using React Query and want to refetch data when accounts switch:

import { useSpryAccountManager } from 'spry-apps-dropdown'
import { useQueryClient } from '@tanstack/react-query'
import { useEffect } from 'react'

function useMultiAccountManager() {
  const queryClient = useQueryClient()
  const accountManager = useSpryAccountManager()

  // Listen for account switches and invalidate queries
  useEffect(() => {
    const handleAccountSwitch = () => {
      queryClient.invalidateQueries() // Refetch all data
    }

    window.addEventListener('auth:account-switched', handleAccountSwitch)
    return () => window.removeEventListener('auth:account-switched', handleAccountSwitch)
  }, [queryClient])

  return accountManager
}

Backend API Requirements

Your backend should expose these endpoints:

GET /api/apps

Returns list of apps for the dropdown.

{
  "apps": [
    {
      "id": "abc123",
      "name": "SpryBoard",
      "description": "Collaborative whiteboard",
      "url": "https://board.spry.com",
      "iconUrl": "https://example.com/icon.png",
      "color": "#4285f4",
      "order": 1,
      "createdAt": "2026-02-13T10:00:00.000Z",
      "updatedAt": "2026-02-13T10:00:00.000Z"
    }
  ],
  "lastUpdated": "2026-02-13T10:00:00.000Z"
}

GET /api/profile

Returns user profile information (requires Bearer token).

GET /api/profile
Authorization: Bearer <jwt-token>

Response:

{
  "email": "[email protected]",
  "firstName": "John",
  "lastName": "Doe",
  "avatar": "https://example.com/avatar.jpg",
  "manageAccountUrl": "https://account.spry.com"
}

Customization

Custom Theme

import { ThemeProvider, createTheme } from '@mui/material/styles'

const theme = createTheme({
  palette: {
    primary: {
      main: '#1a73e8',
    },
  },
})

<ThemeProvider theme={theme}>
  <SpryAuthProvider config={oidcConfig}>
    <App />
  </SpryAuthProvider>
</ThemeProvider>

Hide Apps Dropdown or Profile Menu

<TopBar
  apiUrl="https://sprylogin.com/apps-api"
  showAppsDropdown={false}  // Hide apps dropdown
  showProfileMenu={true}     // Show only profile menu
  accountManager={accountManager}
/>

Troubleshooting

"User not authenticated" error

Make sure you wrapped your app with SpryAuthProvider:

<SpryAuthProvider config={oidcConfig}>
  <App />
</SpryAuthProvider>

Account switching doesn't work

Pass the accountManager prop to TopBar:

const accountManager = useSpryAccountManager()

<TopBar accountManager={accountManager} {...otherProps} />

API calls use wrong token after switching

Use useSpryAuth() to get the current token:

const auth = useSpryAuth()
const token = auth.user?.access_token

TypeScript Support

Full TypeScript support included. Types are automatically available:

import type {
  UseAccountManagerReturn,
  StoredAccount,
  OIDCUser,
  TopBarProps
} from 'spry-apps-dropdown'

Browser Support

  • Modern browsers with ES2020 support
  • Requires localStorage API

License

MIT


Related Documentation


Support

For issues or questions:

  • Open an issue on GitHub
  • Check INTEGRATION.md for detailed guides