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

@iot-platform-saf/ui-shell

v1.1.2

Published

Shared chrome components, types, and nav config for IoT Unified Platform child apps

Readme

@iot-platform-saf/ui-shell

Shared chrome components for the Safaricom IoT Unified Platform. Provides AppNavbar, AppSidebar, AppDrawer, AppFooter, and CommandPalette — the full layout shell — along with platform-wide types and nav config.


Requirements

| Peer dependency | Version | |---|---| | next | >= 16 | | react | >= 19 | | radix-ui | >= 1.5 | | class-variance-authority | >= 0.7 | | cmdk | >= 1.0 | | lucide-react | >= 1.0 |

Install peer dependencies if not already present:

pnpm add next react react-dom radix-ui class-variance-authority cmdk lucide-react

Installation

pnpm add @iot-platform-saf/ui-shell

Step-by-Step Setup

1. Import design tokens

The shell components depend on CSS custom properties defined in @iot-platform-saf/design-tokens. Import that package's CSS file at the top of your app's global stylesheet before any Tailwind layers:

/* app/globals.css */
@import "@iot-platform-saf/design-tokens";

@import "tailwindcss";
/* rest of your global styles */

See the design-tokens README for full details.

2. Re-export layout components

Create thin wrappers in your app's components/layout/ folder. This decouples feature code from the package path and gives you a place to apply app-specific defaults.

// components/layout/app-footer.tsx
export { AppFooter } from "@iot-platform-saf/ui-shell";
// components/layout/app-drawer.tsx
export { AppDrawer } from "@iot-platform-saf/ui-shell";
// components/layout/command-palette.tsx
export { CommandPalette } from "@iot-platform-saf/ui-shell";

3. Create the AppNavbar wrapper

AppNavbar needs user, authLoading, and onLogout — wire those in from your auth client:

// components/layout/app-navbar.tsx
"use client";

import { AppNavbar as PkgAppNavbar } from "@iot-platform-saf/ui-shell";
import { useAuth, useAuthStore } from "@/lib/auth-client";

export function AppNavbar() {
  const { user, logout } = useAuth();
  const authLoading = useAuthStore((s) => s.authLoading);

  return (
    <PkgAppNavbar
      logoSrc="/your-app/images/saf-logo.png"
      user={user}
      authLoading={authLoading}
      onLogout={logout}
    />
  );
}

4. Create the AppSidebar wrapper

AppSidebar requires navSections, navBottom, serviceIcon, and serviceName. Define your app's nav structure once and pass it in:

// components/layout/app-sidebar.tsx
"use client";

import { AppSidebar as PkgAppSidebar } from "@iot-platform-saf/ui-shell";
import type { NavSection, NavItem } from "@iot-platform-saf/ui-shell";

const navSections: NavSection[] = [
  {
    heading: "Overview",
    items: [
      { label: "Dashboard", href: "/your-app/dashboard", icon: "LayoutDashboard" },
      { label: "Assets",    href: "/your-app/assets",    icon: "Package" },
    ],
  },
  {
    heading: "Management",
    items: [
      { label: "Settings", href: "/your-app/settings", icon: "Settings" },
    ],
  },
];

const navBottom: NavItem[] = [
  { label: "Help", href: "/your-app/help", icon: "HelpCircle" },
];

export function AppSidebar() {
  return (
    <PkgAppSidebar
      navSections={navSections}
      navBottom={navBottom}
      serviceIcon="Package"
      serviceName="AssetHub"
      serviceSubtitle="Asset Management"
    />
  );
}

serviceIcon accepts any Lucide icon name.

5. Wire everything into the app layout

// app/(app)/layout.tsx
import { AppNavbar }      from "@/components/layout/app-navbar";
import { AppSidebar }     from "@/components/layout/app-sidebar";
import { AppFooter }      from "@/components/layout/app-footer";

export default function AppLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <AppNavbar />
      <AppSidebar />
      <main className="pl-[var(--sidebar-width)] pt-[var(--navbar-height)] pb-[var(--footer-height)]">
        {children}
      </main>
      <AppFooter />
    </>
  );
}

The layout tokens (--navbar-height, --sidebar-width, --footer-height) come from @iot-platform-saf/design-tokens.


API Reference

Components

AppNavbar

Top navigation bar with logo, app drawer trigger, command palette trigger, notification badge, and user dropdown.

| Prop | Type | Default | Description | |---|---|---|---| | logoSrc | string | — | Absolute path to the logo image (e.g. "/assethub/images/saf-logo.png") | | logoWidth | number | 120 | Logo width in px | | logoHeight | number | 26 | Logo height in px | | logoAlt | string | "Safaricom Logo" | Logo alt text | | user | AppNavbarUser \| null | undefined | Current user — { name, email, account: { displayName } } | | authLoading | boolean | false | Shows a skeleton while the session is resolving | | onLogout | () => void | undefined | Called when the user clicks Sign Out | | notificationCount | number | 0 | Badge count on the notifications icon |

AppNavbar internally renders AppDrawer and CommandPalette — you do not need to mount those separately.


AppSidebar

Collapsible icon-rail sidebar. Persists collapsed state to localStorage.

| Prop | Type | Description | |---|---|---| | navSections | NavSection[] | Grouped nav items rendered in the main area | | navBottom | NavItem[] | Items pinned to the bottom of the rail | | serviceIcon | string | Lucide icon name shown as the service badge | | serviceName | string | Service display name (shown expanded) | | serviceSubtitle | string | Optional subtitle below the service name |


AppDrawer

Full-screen service navigation drawer. Renders drawerCategories and drawerPlatformNavItems from the built-in nav config. No props required.

import { AppDrawer } from "@iot-platform-saf/ui-shell";
// <AppDrawer /> — already mounted inside AppNavbar; only use standalone if building a custom navbar

AppFooter

Fixed bottom bar showing platform SLA, region, and policy links. No props.


CommandPalette

⌘K / Ctrl+K command palette. Searches all services from drawerCategories.

| Prop | Type | Description | |---|---|---| | open | boolean | Controlled open state | | onOpenChange | (open: boolean) => void | Open state setter |

Already mounted and wired inside AppNavbar. Only use standalone if building a custom navbar.


Hooks

useAppDrawer(onOpenChange)

Drives AppDrawer — filters categories and items by search query, handles flyout positioning. Used internally; exposed for custom drawer implementations.

useCommandPalette(onOpenChange)

Registers the ⌘K / Ctrl+K keyboard shortcut. Used internally by CommandPalette.


Nav config

The platform-wide drawer navigation is the source of truth for service links across all apps. Import these instead of duplicating the list in each app.

import {
  drawerCategories,        // DrawerCategory[] — Connectivity, Utilities, Infrastructure
  drawerPlatformNavItems,  // DrawerManagementItem[] — Dashboard, Service Catalog
} from "@iot-platform-saf/ui-shell";

Do not edit these values in your app. Update them in packages/ui-shell/src/config/nav.ts and publish a new version.


Platform types

All shared platform types are exported from this package. Use them in UI components, stores, and feature code.

import type {
  // User & auth
  User,
  UserRole,
  Account,
  // Platform
  AppId,
  ServiceStatus,
  // API
  ApiResponse,
  ApiError,
  // UI shell
  NavItem,
  NavSection,
  AppNavbarUser,
  // Nav config
  NavLinkType,
  DrawerServiceItem,
  DrawerCategory,
  DrawerManagementItem,
} from "@iot-platform-saf/ui-shell";

| Type | Description | |---|---| | User | Authenticated user — id, email, name, role, account | | UserRole | "admin" \| "editor" \| "viewer" | | Account | Organisation — id, name, displayName | | AppId | Union of all platform app slugs | | ServiceStatus | "active" \| "coming-soon" \| "beta" \| "maintenance" | | ApiResponse<T> | { data: T; success: boolean; message?: string } | | ApiError | { code: string; message: string; status: number } | | NavItem | Sidebar nav item — { label, href, icon } | | NavSection | Grouped sidebar items — { heading, items: NavItem[] } | | AppNavbarUser | Minimal user shape for AppNavbar | | NavLinkType | "in-app" \| "cross-zone" — drives <Link> vs <a> | | DrawerServiceItem | Service card in the drawer | | DrawerCategory | Group of DrawerServiceItems | | DrawerManagementItem | Management link in the drawer bottom bar |


Utilities

cn and DynamicIcon are exported so consuming apps don't need a separate install:

import { cn }                         from "@iot-platform-saf/ui-shell";
import { DynamicIcon, getIcon }       from "@iot-platform-saf/ui-shell";
import type { DynamicIconProps }      from "@iot-platform-saf/ui-shell";

cn(...inputs)

Merges Tailwind classes with clsx + tailwind-merge. Identical to the shadcn/ui cn helper.

DynamicIcon

Renders any Lucide icon by string name. Used internally by all shell components.

<DynamicIcon name="Package" className="h-4 w-4 text-primary" />

| Prop | Type | Default | Description | |---|---|---|---| | name | string | — | Lucide icon name (PascalCase) | | size | number | 16 | Icon size in px | | className | string | — | Additional classes |

getIcon(name)

Returns the Lucide icon component for a given name. Returns null if not found.

const Icon = getIcon("Package"); // LucideIcon | null