@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 chrome — TopBar, 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-corePeer 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.
