jengacore.shadcn registry · Tailwind v4 · Radix and Base UI
Components
Blocks · @jengacore/app-frame

App frame

Sidebar, top bar, ⌘K palette, theme and product signature wired together. Mount once in the root layout.

Install

terminal
npx shadcn@latest add @jengacore/app-frame

Without the namespace configured: npx shadcn@latest add https://ui.jengacore.com/r/<new-york-v4|base-nova>/app-frame.json

Source

components/blocks/app-frame.tsx
"use client"

import * as React from "react"
import { Bell, LayoutGrid, Settings } from "lucide-react"

import { Button } from "@/components/ui/button"
import { AppSidebar, useSidebarCollapsed, type NavGroup } from "@/components/jc/app-sidebar"
import { AppMain, AppShell, AppTopbar, type Crumb } from "@/components/jc/app-shell"
import { CommandPalette, useCommandShortcut, type PaletteAction } from "@/components/jc/command-palette"
import type { Environment } from "@/components/jc/env-chip"
import { useProduct, useTheme, type Product } from "@/components/jc/theme"

const DEFAULT_NAV: NavGroup[] = [
  {
    label: "Workspace",
    items: [
      { label: "Overview", icon: LayoutGrid, href: "#", current: true },
      { label: "Settings", icon: Settings, href: "#" },
    ],
  },
]

/**
 * The frame for a jengacore product, wired: sidebar (collapsible, remembered), top bar with breadcrumbs and
 * environment, ⌘K palette with Appearance, theme and product signature. Mount once in the root layout.
 */
export function AppFrame({
  product,
  env,
  nav = DEFAULT_NAV,
  crumbs = [{ label: "Overview" }],
  user = { name: "Aline Uwase", role: "Operations", initials: "AU" },
  pages = [],
  actions = [],
  linkAs,
  children,
}: {
  product: Product
  env?: Environment
  nav?: NavGroup[]
  crumbs?: Crumb[]
  user?: { name: string; role: string; initials: string }
  pages?: PaletteAction[]
  actions?: PaletteAction[]
  linkAs?: React.ComponentProps<typeof AppSidebar>["linkAs"]
  children: React.ReactNode
}) {
  const { setTheme } = useTheme()
  useProduct(product)
  const [collapsed, setCollapsed] = useSidebarCollapsed()
  const [open, setOpen] = React.useState(false)
  useCommandShortcut(setOpen)

  return (
    <AppShell
      sidebar={
        <AppSidebar
          product={product}
          groups={nav}
          user={user}
          collapsed={collapsed}
          onCollapsedChange={setCollapsed}
          onSearch={() => setOpen(true)}
          linkAs={linkAs}
        />
      }
    >
      <AppTopbar
        crumbs={crumbs}
        env={env}
        actions={
          <Button variant="ghost" size="icon" aria-label="Notifications">
            <Bell />
          </Button>
        }
      />
      <AppMain>{children}</AppMain>
      <CommandPalette open={open} onOpenChange={setOpen} product={product} pages={pages} actions={actions} onTheme={setTheme} />
    </AppShell>
  )
}