App Shell

v0.1.2

Collapsible desktop sidebar and sheet-based mobile drawer. Framework-portable: router links and the current path are injected via props.

View as Markdown

Install

npx shadcn@latest add @signalos/app-shell

Requires a configured @signalos registry and a valid SIGNALOS_REGISTRY_TOKEN - get access. Registry dependencies (@signalos/tokens, @signalos/utils, @signalos/sheet, @signalos/tooltip) are pulled automatically.

In a Next.js app, wire the shell to the router once: const pathname = usePathname() <AppShellSidebar activePath={pathname} LinkComponent={({ href, ...p }) => <Link href={href} {...p} />} ... />

Preview

Example & code

app-shell.example.tsx
// Example: wiring the shell in a plain React app (router-agnostic).
// In Next.js, pass `activePath={usePathname()}` and a Link adapter:
//   LinkComponent={({ href, ...p }) => <Link href={href} {...p} />}
import { useState } from "react"
import { Inbox, LayoutDashboard, Settings } from "lucide-react"

import { AppShellSidebar } from "@/components/widgets/app-shell/AppShell"
import type { NavigationItem } from "@/components/widgets/app-shell/AppShell.types"

const navigation: NavigationItem[] = [
  {
    label: "Dashboard",
    collapsedLabel: "Home",
    href: "#/dashboard",
    icon: LayoutDashboard,
  },
  { label: "Signal Feed", collapsedLabel: "Feed", href: "#/feed", icon: Inbox },
  { label: "Settings", href: "#/settings", icon: Settings, section: "bottom" },
]

export default function Example() {
  const [collapsed, setCollapsed] = useState(false)

  return (
    <div className="flex h-[480px] overflow-hidden rounded-lg border border-border [&_aside]:!flex [&_aside]:h-full">
      <AppShellSidebar
        navigationItems={navigation}
        activePath="#/feed"
        brand={
          <div className="flex items-center gap-2">
            <span className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-sm font-bold text-primary-foreground">
              A
            </span>
            <span className="text-lg font-semibold text-sidebar-foreground">
              Acme Ops
            </span>
          </div>
        }
        isCollapsed={collapsed}
        onCollapsedChange={setCollapsed}
      />
      <main className="flex-1 bg-background p-6 text-sm text-muted-foreground">
        Route content renders here.
      </main>
    </div>
  )
}

Props

AppShellLinkProps

PropTypeDefaultDescription
href*string-
onClick(() => void) | undefined-
classNamestring | undefined-
aria-current"page" | undefined-
children*ReactNode-

NavigationItem

PropTypeDefaultDescription
label*string-
collapsedLabelstring | undefined-Short label shown under the icon when the sidebar is collapsed. Falls back to `label` styled small when omitted.
href*string-
icon*ComponentType<{ className?: string | undefined; }>-
section"main" | "bottom" | undefined"main"

AppShellSidebarProps

PropTypeDefaultDescription
navigationItems*NavigationItem[]-
activePath*string-Current pathname used to highlight the active item (e.g. from usePathname()).
isActive((href: string, activePath: string) => boolean) | undefinedexact match or prefix match on `href + "/"`Active-state matcher.
LinkComponentComponentType<AppShellLinkProps> | undefined-Router link renderer; defaults to a plain anchor.
brandReactNode-Brand block at the top (logo + name); fully consumer-owned.
brandHrefstring | undefined"/"Where the brand block links to.
isCollapsedboolean | undefinedfalse
onCollapsedChange((collapsed: boolean) => void) | undefined-When provided, renders the collapse toggle.
onNavigate(() => void) | undefined-Called on any nav link click (close overlays, telemetry).
classNamestring | undefined-

AppShellMobileSidebarProps

PropTypeDefaultDescription
navigationItems*NavigationItem[]-
activePath*string-
isActive((href: string, activePath: string) => boolean) | undefined-
LinkComponentComponentType<AppShellLinkProps> | undefined-
brandReactNode-
brandHrefstring | undefined-
isOpen*boolean-
onClose*() => void-
classNamestring | undefined-

npm dependencies

lucide-react@^1.7.0

Changelog

app-shell

0.1.2

  • Content sync fix to bring source in line with published registry payload.

0.1.1

  • Standardize inline comment formatting (em dash → hyphen); no runtime or API change.

0.1.0

  • Initial release, lifted from signal-core-ui dashboard-sidebar.tsx + dashboard-mobile-sidebar.tsx.
  • Decoupled from Next.js: usePathname() replaced by an activePath prop, next/link by an injectable LinkComponent (plain <a> default), next/image brand block by a consumer-owned brand slot.
  • The hardcoded label-abbreviation switch replaced by NavigationItem.collapsedLabel.
  • Raw classes (bg-white/10, text-navy-300, hover:bg-brand-graphite) normalized to sidebar-* semantic tokens.
  • Mobile drawer rebuilt on the @signalos/sheet primitive (focus trap, scrim, esc-to-close).