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

@petrarca/sonnet-shell

v0.4.2

Published

Application shell, layout, navigation, auth flow, and imperative API for the Petrarca Sonnet component library

Downloads

632

Readme

@petrarca/sonnet-shell

Application shell, layout, and navigation for the Petrarca Sonnet component library.

What's included

RootLayout / AppShell — Full application shell: TopBar, navigation (icon-rail or sidebar), SidePane, content area, overlays, command menu.

Navigation layouts — Two built-in layouts driven by the same module metadata:

  • ShellRail — narrow icon strip + contextual sub-nav panel (default)
  • ShellSidebar — single-column sidebar with icons, labels, and collapsible sections

Navigation primitives — Compose custom layouts from IconRail, RailIcon, RailSeparator, Sidebar, SidebarGroup, SidebarItem, SubNavPanel.

Shell chromeTopBar, ShellFooter, ShellVersion for header/footer slots.

Imperative API — Shell capabilities as simple function calls from any module: notification, dialog, navigation, panel, sidePane, fullscreen, events.

Module system — Register app modules (ShellModule) with routes, navigation, Cmd+K commands, and side pane config. createModuleRegistry() aggregates them.

Auth components (@petrarca/sonnet-shell/auth) — Login, ProtectedRoute, TenantSelection. Prop-driven — no auth logic baked in.


Install

pnpm add @petrarca/sonnet-shell @petrarca/sonnet-ui @petrarca/sonnet-core

Peer dependencies: react >=19, react-dom >=19, react-router-dom, tailwindcss.


Setup

1. Define modules

Each feature area is a ShellModule:

// src/modules/home/index.ts
import { Home } from "lucide-react";
import type { ShellModule } from "@petrarca/sonnet-shell";
import { routes } from "./routes";

const homeModule: ShellModule = {
  id: "home",
  label: "Home",
  icon: Home,
  basePath: "/home",
  navigation: [
    {
      id: "main",
      links: [
        { id: "home.overview", label: "Overview", path: "/home" },
        { id: "home.settings", label: "Settings", path: "/home/settings" },
      ],
    },
  ],
  // Optional: fixed top-zone links in sidebar mode (no heading, separator below)
  topNav: [
    { id: "home.overview", label: "Overview", path: "/home" },
  ],
  routes,
};

export default homeModule;

2. Create the registry

// src/modules/registry.ts
import { createModuleRegistry } from "@petrarca/sonnet-shell";
import home from "./home";
import settings from "./settings";

const registry = createModuleRegistry([home, settings]);

export const { allRoutes } = registry;
export default registry;

3. Wire the shell

// src/routes/AppRouter.tsx
import { createBrowserRouter, RouterProvider, Navigate } from "react-router-dom";
import {
  RootLayout,
  SearchTrigger,
  UserMenu,
  ShellVersion,
  type ShellConfig,
} from "@petrarca/sonnet-shell";
import pkg from "../../package.json";
import registry from "@/modules/registry";

function TopBarContent() {
  return (
    <>
      <div className="flex items-center gap-3">
        <span className="text-sm font-semibold">MY APP</span>
      </div>
      <div className="flex items-center gap-2">
        <SearchTrigger />
        <UserMenu user={...} onSignOut={...} />
      </div>
    </>
  );
}

const shellConfig: ShellConfig = {
  topBar: <TopBarContent />,
  footer: <ShellVersion name="My App" version={pkg.version} />,
};

const router = createBrowserRouter([
  {
    element: <RootLayout config={shellConfig} registry={registry} />,
    children: [
      { index: true, element: <Navigate to="/home" replace /> },
      ...registry.allRoutes,
    ],
  },
]);

export function AppRouter() {
  return <RouterProvider router={router} />;
}

4. CSS and Tailwind

/* index.css */
@import "@petrarca/sonnet-ui/styles.css";

/* Shell layout — app-level, not provided by the library */
html, body, #root { height: 100%; }
#root { display: flex; flex-direction: column; overflow: hidden; }
body { min-height: 100vh; overflow: hidden; }
// tailwind.config.js
module.exports = {
  presets: [require("@petrarca/sonnet-ui/tailwind-preset")],
  content: [
    "./src/**/*.{ts,tsx}",
    "./node_modules/@petrarca/sonnet-*/dist/**/*.js",
  ],
  plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
};

Sidebar layout

Pass a sidebar prop to RootLayout to replace the default icon-rail with a single-column sidebar:

import { ShellSidebar } from "@petrarca/sonnet-shell";

// Auto-wired from registry metadata:
<RootLayout config={shellConfig} registry={registry} sidebar={<ShellSidebar />} />

// Or fully custom:
import { Sidebar, SidebarGroup, SidebarItem } from "@petrarca/sonnet-shell";

<RootLayout
  config={shellConfig}
  registry={registry}
  sidebar={
    <Sidebar>
      <SidebarGroup separator>
        <SidebarItem icon={Home} label="Overview" path="/home" />
      </SidebarGroup>
      <SidebarGroup heading="PROJECTS" collapsible>
        <SidebarItem icon={Folder} label="Alpha" path="/projects/alpha" />
      </SidebarGroup>
    </Sidebar>
  }
/>

topNav on a ShellModule populates the top separator zone automatically when using ShellSidebar.


Imperative API

Any module can call the shell API without prop drilling:

import { notification, dialog, navigation, panel, sidePane } from "@petrarca/sonnet-shell";

notification.success("Saved.");
notification.error("Failed to save.");

const confirmed = await dialog.confirm({
  title: "Delete item?",
  confirmLabel: "Delete",
  variant: "destructive",
});

navigation.goTo("/home");
navigation.goToFeature("home.settings");  // stable id, path-independent

panel.open({
  title: "Details",
  content: <MyPanel />,
  width: "default",
});

sidePane.toggle({ moduleId: "my-module", content: <MySidePane /> });

ShellModule reference

interface ShellModule {
  id: string;                    // unique, e.g. "terminology"
  label: string;                 // shown in rail tooltip, sub-nav header
  description?: string;          // shown in Cmd+K
  icon: LucideIcon;

  basePath: string;              // URL prefix, e.g. "/terminology"
  routes: RouteObject[];         // React Router routes

  topNav?: NavLink[];            // fixed top-zone links (sidebar only)
  navigation: NavGroup[];        // grouped nav links (rail + sidebar)

  pinBottom?: boolean;           // pin to bottom of rail / sidebar tools group
  hidden?: boolean;              // hide from navigation entirely

  contributions?: Contribution[]; // inject links into other modules' nav
  commands?: ModuleCommand[];     // Cmd+K actions

  sidePane?: { ... };            // inline resizable panel config
  layout?: "default" | "full";   // "full" removes scroll wrapper and padding
}

License

See LICENSE.md.