{"$schema":"https://ui.shadcn.com/schema/registry.json","author":"Kaiyu Hsu <uicapsule@kyh.io>","categories":["navigation","minimal"],"dependencies":["lucide-react"],"description":"A shadcn-inspired application sidebar with a draggable resize handle.","devDependencies":[],"files":[{"content":"import { Fragment } from \"react\";\n\nexport const Dashboard = () => (\n  <div>\n    <div className=\"border-b border-[#EEEFF1]\">\n      <div className=\"flex items-center justify-between pt-3 pr-[19px] pb-[11px] pl-4\">\n        <div className=\"flex items-center gap-x-[7px]\">\n          <div className=\"h-5 w-20 rounded bg-gray-200\" />\n          <div className=\"size-4 rounded-full bg-gray-200\" />\n        </div>\n        <div className=\"flex items-center gap-x-[26px]\">\n          <div className=\"flex\">\n            <div className=\"w-4\">\n              <div className=\"size-6 rounded-full bg-gray-200\" />\n            </div>\n            <div className=\"w-4\">\n              <div className=\"size-6 rounded-full bg-gray-200\" />\n            </div>\n            <div className=\"w-4\">\n              <div className=\"size-6 rounded-full bg-gray-200\" />\n            </div>\n            <div className=\"w-4\">\n              <div className=\"size-6 rounded-full bg-gray-200\" />\n            </div>\n          </div>\n          <div className=\"size-4 rounded bg-gray-200\" />\n        </div>\n      </div>\n    </div>\n    <div className=\"border-b border-[#EEEFF1]\">\n      <div className=\"flex items-center justify-between px-3 pt-2.5 pb-[9px]\">\n        <div className=\"h-5 w-24 rounded bg-gray-200\" />\n        <div className=\"flex items-center gap-x-2\">\n          <div className=\"flex items-center gap-1.5 rounded-sm py-1 pr-2 pl-1.5 shadow-[0px_0px_1px_0px_#E0E0E0,0px_1px_2px_-1px_rgba(24,39,75,0.02),0px_2px_2px_-1px_rgba(24,39,75,0.06)]\">\n            <div className=\"h-5 w-16 rounded bg-gray-200\" />\n          </div>\n          <div className=\"flex items-center rounded-sm py-1 pr-1.5 pl-2 shadow-[0px_0px_1px_0px_#E0E0E0,0px_1px_2px_-1px_rgba(24,39,75,0.02),0px_2px_2px_-1px_rgba(24,39,75,0.06)]\">\n            <div className=\"h-5 w-20 rounded bg-gray-200\" />\n          </div>\n        </div>\n      </div>\n    </div>\n    <div className=\"h-full w-full overflow-hidden\">\n      <div className=\"flex h-full w-full flex-col\">\n        <div className=\"flex h-[49px] shrink-0 items-center gap-x-[7px] border-b border-[#EEEFF1] px-3\">\n          <div className=\"flex h-7 items-center gap-x-1.5 rounded-lg border border-[#E6E7EA] bg-[#FBFBFB] pr-[4.5px] pl-[2.5px]\">\n            <div className=\"h-5 w-32 rounded bg-gray-200\" />\n          </div>\n          <div className=\"h-[21px] w-px rounded-full bg-[#EEEFF1]\" />\n          <div className=\"flex h-7 items-center rounded-lg border border-[#E6E7EA] bg-[#FBFBFB] pr-[2.5px] pl-1.5\">\n            <div className=\"flex items-center gap-x-[7px]\">\n              <div className=\"h-5 w-24 rounded bg-gray-200\" />\n            </div>\n            <div className=\"mr-1 ml-1.5 h-full w-px bg-[#E6E7EA]\" />\n            <div className=\"size-4 rounded bg-gray-200\" />\n          </div>\n        </div>\n        <div className=\"relative h-full w-full flex-1\">\n          <div className=\"grid auto-rows-[40px] grid-cols-[204px_minmax(136px,1fr)_minmax(204px,1.5fr)_minmax(136px,1fr)_204px_204px]\">\n            <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n              <div className=\"flex h-full w-full items-center justify-between gap-x-1.5 pr-[4.5px] pl-2\">\n                <div className=\"flex items-center gap-x-2.5 overflow-hidden\">\n                  <div className=\"h-5 w-16 rounded bg-gray-200\" />\n                </div>\n                <div className=\"h-5 w-5 rounded bg-gray-200\" />\n              </div>\n            </div>\n            <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n              <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                <div className=\"h-5 w-16 rounded bg-gray-200\" />\n              </div>\n            </div>\n            <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n              <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                <div className=\"h-5 w-20 rounded bg-gray-200\" />\n              </div>\n            </div>\n            <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n              <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                <div className=\"h-5 w-18 rounded bg-gray-200\" />\n              </div>\n            </div>\n            <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n              <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                <div className=\"h-5 w-20 rounded bg-gray-200\" />\n              </div>\n            </div>\n            <div className=\"overflow-hidden border-b border-[#EEEFF1]\">\n              <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                <div className=\"h-5 w-16 rounded bg-gray-200\" />\n              </div>\n            </div>\n            {Array.from({ length: 20 }, (_, index) => (\n              <Fragment key={index}>\n                <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n                  <div className=\"flex h-full w-full items-center justify-between gap-x-1.5 pr-[4.5px] pl-2\">\n                    <div className=\"flex items-center gap-x-2.5 overflow-hidden\">\n                      <div className=\"size-4 rounded bg-gray-200\" />\n                      <div className=\"h-5 w-24 rounded bg-gray-200\" />\n                    </div>\n                    <div className=\"h-5 w-5 rounded bg-gray-200\" />\n                  </div>\n                </div>\n                <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n                  <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                    <div className=\"h-5 w-20 rounded bg-gray-200\" />\n                  </div>\n                </div>\n                <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n                  <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                    <div className=\"h-5 w-28 rounded bg-gray-200\" />\n                  </div>\n                </div>\n                <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n                  <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                    <div className=\"h-5 w-16 rounded bg-gray-200\" />\n                  </div>\n                </div>\n                <div className=\"overflow-hidden border-r border-b border-[#EEEFF1]\">\n                  <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                    <div className=\"h-5 w-20 rounded bg-gray-200\" />\n                  </div>\n                </div>\n                <div className=\"overflow-hidden border-b border-[#EEEFF1]\">\n                  <div className=\"flex h-full w-full items-center gap-x-1.5 pr-[3.5px] pl-1\">\n                    <div className=\"h-5 w-18 rounded bg-gray-200\" />\n                  </div>\n                </div>\n              </Fragment>\n            ))}\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n);\n","path":"/dashboard.tsx","target":"uicapsule/sidebar/dashboard.tsx","type":"registry:file"},{"content":"\"use client\";\n\nimport { Dashboard } from \"./dashboard\";\nimport { Sidebar } from \"./sidebar\";\n\nconst Preview = () => (\n  <div\n    className=\"relative grid h-full w-full overflow-hidden\"\n    style={{ gridTemplateColumns: \"var(--sidebar-width, 280px) 1fr\" }}\n  >\n    <Sidebar />\n    <Dashboard />\n  </div>\n);\n\nexport default Preview;\n","path":"/preview.tsx","target":"uicapsule/sidebar/preview.tsx","type":"registry:file"},{"content":"import type { ReactNode } from \"react\";\nimport {\n  BarChart3,\n  Bell,\n  Building,\n  Building2,\n  CheckSquare,\n  ChevronDown,\n  DollarSign,\n  FileText,\n  GitBranch,\n  Grid3X3,\n  Link,\n  Mail,\n  Settings,\n  Star,\n  Users,\n  Workflow,\n} from \"lucide-react\";\n\nimport { useResizableSidebar } from \"./use-resizable-sidebar\";\n\ninterface NavItem {\n  id: string;\n  label: string;\n  /** A lucide element, or a plain string to render an emoji glyph instead. */\n  icon: ReactNode;\n  isActive?: boolean;\n  subItems?: NavItem[];\n}\n\nconst navigationItems: NavItem[] = [\n  { icon: <Bell size={14} />, id: \"notifications\", label: \"Notifications\" },\n  { icon: <CheckSquare size={14} />, id: \"tasks\", label: \"Tasks\" },\n  { icon: <Mail size={14} />, id: \"emails\", label: \"Emails\" },\n  { icon: <BarChart3 size={14} />, id: \"reports\", label: \"Reports\" },\n  {\n    icon: <Settings size={14} />,\n    id: \"automations\",\n    label: \"Automations\",\n    subItems: [\n      { icon: <Workflow size={14} />, id: \"workflows\", label: \"Workflows\" },\n      { icon: <GitBranch size={14} />, id: \"sequences\", label: \"Sequences\" },\n    ],\n  },\n];\n\nconst favoritesItems: NavItem[] = [\n  {\n    icon: <Star size={14} />,\n    id: \"onboarding-pipeline\",\n    label: \"Onboarding pipeline\",\n  },\n  {\n    icon: <Building2 size={14} />,\n    id: \"top-of-funnel\",\n    label: \"Top of funnel\",\n  },\n  {\n    icon: <FileText size={14} />,\n    id: \"revops-workflows\",\n    label: \"RevOps workflows\",\n  },\n];\n\nconst recordsItems: NavItem[] = [\n  {\n    icon: <Building size={14} />,\n    id: \"companies\",\n    isActive: true,\n    label: \"Companies\",\n  },\n  { icon: <Users size={14} />, id: \"people\", label: \"People\" },\n  { icon: <DollarSign size={14} />, id: \"deals\", label: \"Deals\" },\n  { icon: <Grid3X3 size={14} />, id: \"workspaces\", label: \"Workspaces\" },\n  { icon: <Link size={14} />, id: \"partnerships\", label: \"Partnerships\" },\n];\n\n/** Emoji glyphs need the centring wrapper a component icon draws itself. */\nconst EmojiIcon = ({ children }: { children: string }) => (\n  <span className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-[16px] leading-4\">\n    {children}\n  </span>\n);\n\nconst listsItems: NavItem[] = [\n  {\n    icon: <EmojiIcon>🚀</EmojiIcon>,\n    id: \"strategic-accounts\",\n    label: \"Strategic accounts\",\n  },\n];\n\nconst NavigationItem = ({ item }: { item: NavItem }) => (\n  <div className=\"flex w-full flex-col\">\n    <div\n      className={`flex min-w-0 items-center gap-x-1.5 rounded-[9px] px-2 py-1 transition-colors duration-500 [transition-timing-function:cubic-bezier(0.65,0,0.35,1)] ${item.isActive ? \"bg-[#F4F5F6]\" : \"\"}`}\n    >\n      <div className=\"relative w-[14px]\">{item.icon}</div>\n      <span className=\"min-w-0 flex-1 truncate text-[14px] leading-5 font-medium tracking-[-0.28px]\">\n        {item.label}\n      </span>\n      {item.subItems && <ChevronDown size={14} />}\n    </div>\n    {item.subItems && (\n      <div className=\"flex flex-col gap-y-px py-px\">\n        {item.subItems.map((subItem) => (\n          <div key={subItem.id} className=\"relative flex pl-5\">\n            <div className=\"absolute left-3.5 h-[32px] w-px -translate-y-[2px] bg-[#D1D3D6] opacity-40\" />\n            <NavigationItem item={subItem} />\n          </div>\n        ))}\n      </div>\n    )}\n  </div>\n);\n\nconst NavigationSection = ({ title, items }: { title: string; items: NavItem[] }) => (\n  <div className=\"flex flex-col\">\n    <div className=\"flex min-w-0 items-center gap-x-1.5 px-2 py-1.5\">\n      <ChevronDown size={14} />\n      <span className=\"min-w-0 flex-1 truncate text-[12px] leading-4 font-medium tracking-normal text-[#75777C]\">\n        {title}\n      </span>\n    </div>\n    <ul className=\"flex flex-col gap-px\">\n      {items.map((item) => (\n        <NavigationItem key={item.id} item={item} />\n      ))}\n    </ul>\n  </div>\n);\n\nexport const Sidebar = () => {\n  const { handleMouseDown } = useResizableSidebar();\n\n  return (\n    <div className=\"relative border-r border-[#EEEFF1] bg-[#FBFBFB]\">\n      <div className=\"flex items-center justify-between gap-x-6 border-r border-b border-[#EEEFF1] bg-[#FBFBFB] pt-3 pr-[15px] pb-[11px] pl-3\">\n        <div className=\"flex min-w-0 flex-1 items-center\">\n          <img\n            alt=\"Dataembed\"\n            loading=\"lazy\"\n            width=\"96\"\n            height=\"96\"\n            decoding=\"async\"\n            className=\"size-6 shrink-0 rounded\"\n            src=\"https://www.dataembed.com/favicon/web-app-manifest-512x512.png\"\n          />\n          <div className=\"text-secondary-foreground ml-2 min-w-0 truncate text-[16px] leading-5 font-semibold tracking-[-0.32px]\">\n            Dataembed\n          </div>\n          <svg className=\"ml-[5px] shrink-0\" width=\"18\" height=\"18\" fill=\"none\">\n            <path\n              d=\"M5.25 7.125 9 10.875l3.75-3.75\"\n              stroke=\"#5C5E63\"\n              strokeWidth=\"1.2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        </div>\n        <svg width=\"18\" height=\"18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n          <g stroke=\"#75777C\" strokeWidth=\"1.2\">\n            <rect x=\"1.5\" y=\"2.5\" width=\"15\" height=\"13\" rx=\"3\" />\n            <path d=\"M7.8 2.725v12.5\" />\n            <path\n              d=\"M3.975 5.425h1.35M3.975 7.674h1.35\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </g>\n        </svg>\n      </div>\n      <div className=\"relative row-span-2 block border-r border-[#EEEFF1] bg-[#FBFBFB] pt-[10px] pr-[7px] pl-2\">\n        <div className=\"flex min-w-0 flex-1 items-center justify-between rounded-sm bg-white py-1 pr-1 pl-1.5 shadow-[0px_0px_2px_0px_#E0E0E0,0px_2px_4px_-2px_rgba(24,39,75,0.02),0px_4px_4px_-2px_rgba(24,39,75,0.06)]\">\n          <div className=\"flex min-w-0 flex-1 items-center gap-x-1\">\n            <svg width=\"14\" height=\"14\" fill=\"none\" className=\"shrink-0\">\n              <rect\n                x=\"2\"\n                y=\"1\"\n                width=\"10\"\n                height=\"12\"\n                rx=\"2.5\"\n                stroke=\"#232529\"\n                strokeWidth=\"1.1\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n              <rect\n                x=\"2\"\n                y=\"1\"\n                width=\"10\"\n                height=\"9\"\n                rx=\"2.5\"\n                stroke=\"#232529\"\n                strokeWidth=\"1.1\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n              <path\n                d=\"M5.333 3.166v4.667m0-1.667 1.053-1m0 0 2.105-2m-2.105 2 2.28 2.667\"\n                stroke=\"#232529\"\n                strokeWidth=\"1.1\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n            <span className=\"min-w-0 flex-1 truncate text-[14px] leading-5 font-medium tracking-[-0.28px]\">\n              Quick actions\n            </span>\n          </div>\n          <div className=\"flex min-w-[20px] shrink-0 justify-center rounded-md border border-[#E6E7EA] p-[3px]\">\n            <span className=\"text-center text-[11px] leading-3 font-normal tracking-[0.22px] text-[#75777C]\">\n              ⌘K\n            </span>\n          </div>\n        </div>\n        <div className=\"mt-2.5 flex flex-col gap-[9px]\">\n          <div className=\"flex flex-col\">\n            <ul className=\"flex flex-col gap-px\">\n              {navigationItems.map((item) => (\n                <NavigationItem key={item.id} item={item} />\n              ))}\n            </ul>\n          </div>\n\n          <NavigationSection title=\"Favorites\" items={favoritesItems} />\n          <NavigationSection title=\"Records\" items={recordsItems} />\n          <NavigationSection title=\"Lists\" items={listsItems} />\n        </div>\n      </div>\n\n      {/* Resize Handle */}\n      {/* oxlint-disable-next-line jsx-a11y/no-static-element-interactions -- mouse-only resize affordance; the sidebar is fully usable at its default width, so there is no keyboard equivalent to provide */}\n      <div\n        className=\"absolute top-0 -right-0.5 h-full w-1 cursor-col-resize bg-transparent transition-colors duration-200 hover:bg-[#266DF0] active:bg-[#266DF0]\"\n        onMouseDown={handleMouseDown}\n      />\n    </div>\n  );\n};\n","path":"/sidebar.tsx","target":"uicapsule/sidebar/sidebar.tsx","type":"registry:file"},{"content":"import { useEffect, useRef } from \"react\";\nimport type { MouseEvent as ReactMouseEvent } from \"react\";\n\ninterface UseResizableSidebarOptions {\n  defaultWidth?: number;\n  minWidth?: number;\n  maxWidth?: number;\n}\n\nexport const useResizableSidebar = ({\n  defaultWidth = 240,\n  minWidth = 100,\n  maxWidth = 300,\n}: UseResizableSidebarOptions = {}) => {\n  const startXRef = useRef<number>(0);\n  const startWidthRef = useRef<number>(0);\n  const isResizingRef = useRef<boolean>(false);\n\n  const handleMouseDown = (e: ReactMouseEvent) => {\n    e.preventDefault();\n    isResizingRef.current = true;\n    startXRef.current = e.clientX;\n    // oxlint-disable-next-line unicorn/prefer-number-coercion -- parseInt tolerates a trailing unit the replace() misses; Number() would NaN on it\n    startWidthRef.current = Number.parseInt(\n      getComputedStyle(document.documentElement)\n        .getPropertyValue(\"--sidebar-width\")\n        .replace(\"px\", \"\") || defaultWidth.toString(),\n      10,\n    );\n    document.body.style.cursor = \"col-resize\";\n    document.body.style.userSelect = \"none\";\n  };\n\n  useEffect(() => {\n    document.documentElement.style.setProperty(\"--sidebar-width\", `${defaultWidth}px`);\n\n    // The width lives in a CSS variable rather than state so dragging never re-renders.\n    const handleMouseMove = (e: MouseEvent) => {\n      if (!isResizingRef.current) {\n        return;\n      }\n\n      const newWidth = startWidthRef.current + (e.clientX - startXRef.current);\n      const clampedWidth = Math.min(Math.max(newWidth, minWidth), maxWidth);\n      document.documentElement.style.setProperty(\"--sidebar-width\", `${clampedWidth}px`);\n    };\n\n    const handleMouseUp = () => {\n      if (!isResizingRef.current) {\n        return;\n      }\n      isResizingRef.current = false;\n      document.body.style.cursor = \"\";\n      document.body.style.userSelect = \"\";\n    };\n\n    document.addEventListener(\"mousemove\", handleMouseMove);\n    document.addEventListener(\"mouseup\", handleMouseUp);\n\n    return () => {\n      document.removeEventListener(\"mousemove\", handleMouseMove);\n      document.removeEventListener(\"mouseup\", handleMouseUp);\n      document.body.style.cursor = \"\";\n      document.body.style.userSelect = \"\";\n    };\n  }, [defaultWidth, minWidth, maxWidth]);\n\n  return {\n    handleMouseDown,\n  };\n};\n","path":"/use-resizable-sidebar.ts","target":"uicapsule/sidebar/use-resizable-sidebar.ts","type":"registry:file"}],"homepage":"https://www.uicapsule.com/ui/sidebar","name":"sidebar","registryDependencies":[],"title":"Resizable Sidebar","type":"registry:block"}