{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dashboard-shell",
  "type": "registry:block",
  "title": "Dashboard shell",
  "description": "The frame an admin app hangs off: a sidebar, a header with search, a theme toggle and an account menu, and a row of stat tiles drawn with the chart primitives.",
  "author": "Box Kite <https://www.box-kite.dev>",
  "categories": [
    "dashboard",
    "layout"
  ],
  "dependencies": [
    "@box-kite/react",
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/blocks/dashboard-shell/dashboard-shell.tsx",
      "content": "'use client';\nimport Box from '@box-kite/react';\nimport Button from '@box-kite/react/components/button';\nimport Flex from '@box-kite/react/components/flex';\nimport Icon from '@box-kite/react/components/icon';\nimport Menu from '@box-kite/react/components/menu';\nimport { H1, Header, Li, Link, Main, Nav, Span, Ul } from '@box-kite/react/components/semantics';\nimport Textbox from '@box-kite/react/components/textbox';\nimport { LayoutDashboard, Moon, Receipt, Search, Settings, Sun, Users } from 'lucide-react';\nimport { type ReactElement, type ReactNode } from 'react';\n\n/**\n * The frame an admin app hangs off: a sidebar, a header with search, a theme toggle and an account\n * menu, and the page itself. The sidebar sticks to the top of its column and folds away below the `md`\n * breakpoint; nothing here is measured or positioned by script.\n *\n * `Box.Theme` is `use=\"local\"` here, so the theme lands on this element and the block can sit inside a\n * page that has its own. Move it to your root layout as `use=\"global\"` for an app-wide switch.\n */\nexport interface NavItem {\n  id: string;\n  label: string;\n  href: string;\n  icon: ReactElement;\n}\n\nconst dashboardNav: NavItem[] = [\n  { id: 'overview', label: 'Overview', href: '#overview', icon: <LayoutDashboard /> },\n  { id: 'invoices', label: 'Invoices', href: '#invoices', icon: <Receipt /> },\n  { id: 'customers', label: 'Customers', href: '#customers', icon: <Users /> },\n  { id: 'settings', label: 'Settings', href: '#settings', icon: <Settings /> },\n];\n\nfunction ThemeToggle() {\n  const [theme, setTheme] = Box.useTheme();\n  const next = theme === 'dark' ? 'light' : 'dark';\n\n  return (\n    <Button variant=\"ghost\" p={2} borderRadius={2} onClick={() => setTheme(next)} props={{ 'aria-label': `Switch to the ${next} theme` }}>\n      <Icon size={4}>{theme === 'dark' ? <Sun /> : <Moon />}</Icon>\n    </Button>\n  );\n}\n\ninterface SidebarProps {\n  current: string;\n}\n\nfunction Sidebar({ current }: SidebarProps) {\n  return (\n    <Nav\n      display=\"none\"\n      md={{ display: 'block' }}\n      width={64}\n      flexShrink={0}\n      be={1}\n      borderColor=\"slate-200\"\n      theme={{ dark: { borderColor: 'slate-800' } }}\n      props={{ 'aria-label': 'Sections' }}\n    >\n      <Box position=\"sticky\" top={0} p={4}>\n        <Flex ai=\"center\" gap={2} px={2} py={3} mb={2}>\n          <Box width={6} height={6} borderRadius={2} bgGradient={{ linear: 'br', colors: ['violet-500', 'sky-400'] }} />\n          <Span fontWeight={600}>Acme Ops</Span>\n        </Flex>\n        <Ul listStyle=\"none\" m={0} p={0}>\n          {dashboardNav.map((item) => (\n            <Li key={item.id}>\n              <Link\n                props={{ href: item.href, 'aria-current': item.id === current ? 'page' : undefined }}\n                display=\"flex\"\n                ai=\"center\"\n                gap={3}\n                px={3}\n                py={2}\n                my={0.5}\n                borderRadius={2}\n                fontSize={14}\n                textDecoration=\"none\"\n                color={item.id === current ? 'violet-700' : 'slate-600'}\n                bgColor={item.id === current ? 'violet-50' : 'transparent'}\n                hover={{ bgColor: item.id === current ? 'violet-50' : 'slate-100' }}\n                theme={{\n                  dark: {\n                    color: item.id === current ? 'violet-300' : 'slate-400',\n                    bgColor: item.id === current ? 'violet-950' : 'transparent',\n                    hover: { bgColor: item.id === current ? 'violet-950' : 'slate-900' },\n                  },\n                }}\n              >\n                <Icon size={4}>{item.icon}</Icon>\n                {item.label}\n              </Link>\n            </Li>\n          ))}\n        </Ul>\n      </Box>\n    </Nav>\n  );\n}\n\nexport interface DashboardShellProps {\n  /** The heading above the page, and the `id` of the nav item that is marked as current. */\n  title: string;\n  current?: string;\n  children: ReactNode;\n}\n\nexport default function DashboardShell({ title, current = 'overview', children }: DashboardShellProps) {\n  return (\n    <Box.Theme storageKey=\"dashboard-theme\">\n      <Flex\n        minHeight=\"fit\"\n        bgColor=\"slate-50\"\n        color=\"slate-900\"\n        theme={{ dark: { bgColor: 'slate-950', color: 'slate-100' } }}\n        overflow=\"hidden\"\n        borderRadius={3}\n      >\n        <Sidebar current={current} />\n        <Flex d=\"column\" flexGrow={1} minWidth={0}>\n          <Header\n            display=\"flex\"\n            ai=\"center\"\n            gap={3}\n            px={5}\n            py={3}\n            bb={1}\n            borderColor=\"slate-200\"\n            theme={{ dark: { borderColor: 'slate-800' } }}\n          >\n            <H1 fontSize={16} fontWeight={600} flexGrow={1}>\n              {title}\n            </H1>\n            {/* Away below `md`, with the sidebar: a fixed-width field is what pushes the header past a\n                phone's width, and search deserves a screen of its own there rather than a sliver. */}\n            <Flex ai=\"center\" gap={2} position=\"relative\" display=\"none\" md={{ display: 'flex' }}>\n              <Icon size={4} position=\"absolute\" insetStart={3} color=\"slate-400\" pointerEvents=\"none\">\n                <Search />\n              </Icon>\n              <Textbox type=\"search\" ps={9} py={2} width={56} placeholder=\"Search…\" props={{ 'aria-label': 'Search' }} />\n            </Flex>\n            <ThemeToggle />\n            <Menu\n              trigger={(trigger) => (\n                <Button {...trigger} variant=\"ghost\" p={2} borderRadius={2}>\n                  AL\n                </Button>\n              )}\n            >\n              <Menu.Item>Profile</Menu.Item>\n              <Menu.Item>Billing</Menu.Item>\n              <Menu.Separator />\n              <Menu.Item>Sign out</Menu.Item>\n            </Menu>\n          </Header>\n          <Main p={5} flexGrow={1}>\n            {children}\n          </Main>\n        </Flex>\n      </Flex>\n    </Box.Theme>\n  );\n}\n",
      "type": "registry:block",
      "target": "@components/box-kite/dashboard-shell.tsx"
    },
    {
      "path": "registry/blocks/dashboard-shell/dashboard-stats.tsx",
      "content": "import Box from '@box-kite/react';\nimport { ProgressRing, Sparkline } from '@box-kite/react/components/chart';\nimport Flex from '@box-kite/react/components/flex';\nimport Grid from '@box-kite/react/components/grid';\nimport { Span } from '@box-kite/react/components/semantics';\nimport { type ReactNode } from 'react';\n\n/**\n * The row of tiles above a dashboard. The shapes are `components/chart` primitives — an SVG each, no\n * chart library — and there is no `'use client'` here on purpose: nothing in this file holds state, so\n * it renders in a Server Component, styles and all.\n */\nexport interface Stat {\n  label: string;\n  value: string;\n  delta: string;\n  up: boolean;\n  trend: number[];\n}\n\nconst dashboardStats: Stat[] = [\n  { label: 'Revenue', value: '$48,210', delta: '+12.4%', up: true, trend: [12, 18, 14, 22, 25, 21, 28, 31, 29, 36, 34, 42] },\n  { label: 'Invoices paid', value: '182', delta: '+4.1%', up: true, trend: [30, 28, 33, 31, 36, 34, 38, 37, 41, 40, 44, 46] },\n  { label: 'Overdue', value: '$6,940', delta: '-8.2%', up: false, trend: [22, 24, 21, 19, 20, 17, 16, 18, 15, 13, 12, 10] },\n];\n\nfunction Card({ children }: { children: ReactNode }) {\n  return (\n    <Flex\n      d=\"column\"\n      gap={3}\n      p={4}\n      b={1}\n      borderRadius={3}\n      borderColor=\"slate-200\"\n      bgColor=\"white\"\n      theme={{ dark: { borderColor: 'slate-800', bgColor: 'slate-900' } }}\n    >\n      {children}\n    </Flex>\n  );\n}\n\nexport default function DashboardStats() {\n  return (\n    <Grid gridTemplateColumns={1} gap={4} md={{ gridTemplateColumns: 4 }}>\n      {dashboardStats.map((stat) => (\n        <Card key={stat.label}>\n          <Span fontSize={13} color=\"slate-500\" theme={{ dark: { color: 'slate-400' } }}>\n            {stat.label}\n          </Span>\n          {/* Stacked rather than side by side: a tile is a quarter of the row, and a long figure next\n              to its delta is the first thing to overflow — on one tile, which leaves the row ragged. */}\n          <Flex d=\"column\" gap={1}>\n            <Span fontSize={24} fontWeight={600}>\n              {stat.value}\n            </Span>\n            <Span\n              fontSize={13}\n              color={stat.up ? 'emerald-600' : 'rose-600'}\n              theme={{ dark: { color: stat.up ? 'emerald-400' : 'rose-400' } }}\n            >\n              {stat.delta}\n            </Span>\n          </Flex>\n          {/* `width`/`height` on an SVG are the attributes, not the ÷4 layout props — a CSS length, or\n              a bare number meaning user units. `color` paints it: the stroke is `currentColor`. */}\n          <Sparkline data={stat.trend} height=\"2rem\" color={stat.up ? 'emerald-500' : 'rose-500'} strokeWidth={2} />\n        </Card>\n      ))}\n      <Card>\n        <Span fontSize={13} color=\"slate-500\" theme={{ dark: { color: 'slate-400' } }}>\n          Collection rate\n        </Span>\n        <Flex ai=\"center\" gap={3} flexWrap=\"wrap\">\n          <ProgressRing value={0.86} width=\"3rem\" height=\"3rem\" color=\"violet-500\" />\n          <Box>\n            <Span fontSize={24} fontWeight={600}>\n              86%\n            </Span>\n            <Box fontSize={13} color=\"slate-500\" theme={{ dark: { color: 'slate-400' } }}>\n              of this quarter\n            </Box>\n          </Box>\n        </Flex>\n      </Card>\n    </Grid>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/box-kite/dashboard-stats.tsx"
    }
  ],
  "docs": "Wrap a page in <DashboardShell title=\"Overview\">. Box.Theme inside it is use=\"local\", so the theme lands on the shell — move it to your root layout as use=\"global\" for an app-wide switch."
}
