{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "commit",
  "title": "Commit",
  "description": "An expandable changed-file summary with folder groups, line counts, and a diff action.",
  "dependencies": [
    "@solar-icons/react@^2.2.0",
    "cn@^0.3.0"
  ],
  "registryDependencies": [
    "button",
    "https://pastaui.com/r/pastaui-styles.json"
  ],
  "files": [
    {
      "path": "src/components/ai/commit.tsx",
      "content": "import { AltArrowDownIcon as ChevronDownIcon } from '@solar-icons/react/linear/alt-arrow-down'\nimport { FolderIcon } from '@solar-icons/react/linear/folder'\nimport { DocumentIcon as FileIcon } from '@solar-icons/react/linear/document'\nimport * as React from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { cn } from 'cn'\n\ninterface CommitFile {\n  path: string\n  additions: number\n  deletions: number\n}\n\ninterface CommitProps extends React.ComponentProps<'article'> {\n  files: CommitFile[]\n  onOpen?: () => void\n}\n\nfunction ChangeCounts({ additions, deletions }: { additions: number; deletions: number }) {\n  return (\n    <span className=\"ml-auto flex shrink-0 gap-4 font-mono text-xs tabular-nums\">\n      <span\n        className=\"text-emerald-600 dark:text-emerald-400\"\n        aria-label={`${additions} additions`}\n      >\n        +{additions}\n      </span>\n      <span className=\"text-red-600 dark:text-red-400\" aria-label={`${deletions} deletions`}>\n        −{deletions}\n      </span>\n    </span>\n  )\n}\n\nfunction CommitFileRow({ file, nested = false }: { file: CommitFile; nested?: boolean }) {\n  const name = file.path.split('/').pop() ?? file.path\n  const isReact = /\\.[jt]sx$/.test(name)\n  const isMarkdown = /\\.mdx?$/.test(name)\n  const Icon = isReact ? ReactIcon : isMarkdown ? MarkdownIcon : FileIcon\n\n  return (\n    <li className={cn('flex min-h-8 items-center gap-2.5 px-4', nested ? 'pl-12' : 'pl-9')}>\n      <Icon\n        aria-hidden=\"true\"\n        className={cn(\n          'size-4 shrink-0',\n          isReact && 'text-sky-500 dark:text-sky-400',\n          isMarkdown && 'text-green-600 dark:text-green-400',\n        )}\n      />\n      <span className=\"min-w-0 truncate font-mono text-sm text-foreground/85\" title={file.path}>\n        {name}\n      </span>\n      <ChangeCounts additions={file.additions} deletions={file.deletions} />\n    </li>\n  )\n}\n\nfunction Commit({ className, files, onOpen, ...props }: CommitProps) {\n  const [expanded, setExpanded] = React.useState(true)\n  const [collapsedFolders, setCollapsedFolders] = React.useState<Set<string>>(new Set())\n  const contentId = React.useId()\n  const groups = new Map<string, CommitFile[]>()\n  for (const file of files) {\n    const folder = file.path.includes('/') ? file.path.slice(0, file.path.lastIndexOf('/')) : ''\n    const group = groups.get(folder) ?? []\n    group.push(file)\n    groups.set(folder, group)\n  }\n  const additions = files.reduce((total, file) => total + file.additions, 0)\n  const deletions = files.reduce((total, file) => total + file.deletions, 0)\n\n  function toggleFolder(folder: string) {\n    setCollapsedFolders((current) => {\n      const next = new Set(current)\n      if (next.has(folder)) next.delete(folder)\n      else next.add(folder)\n      return next\n    })\n  }\n\n  return (\n    <article\n      data-slot=\"commit\"\n      className={cn(\n        'overflow-hidden rounded-[16px] bg-card font-sans text-sm font-[450] tracking-[-0.05px] text-muted-foreground shadow-card',\n        className,\n      )}\n      {...props}\n    >\n      <header className=\"flex flex-wrap items-center gap-x-3 gap-y-2 bg-muted/25 px-4 py-3\">\n        <h3 className=\"card-heading text-foreground\">\n          {files.length} changed {files.length === 1 ? 'file' : 'files'}\n        </h3>\n        <span className=\"flex gap-1.5 text-sm tabular-nums\">\n          <span className=\"text-emerald-600 dark:text-emerald-400\">+{additions}</span>\n          <span className=\"text-red-600 dark:text-red-400\">−{deletions}</span>\n        </span>\n        <div className=\"ml-auto flex items-center gap-2\">\n          <Button\n            aria-label={expanded ? 'Collapse changed files' : 'Expand changed files'}\n            aria-expanded={expanded}\n            aria-controls={contentId}\n            onClick={() => setExpanded(!expanded)}\n            size=\"icon-sm\"\n            variant=\"ghost\"\n          >\n            <ChevronDownIcon\n              className={cn(\n                'size-4 transition-[rotate] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none',\n                !expanded && '-rotate-90',\n              )}\n            />\n          </Button>\n          <Button\n            className=\"h-7 rounded-full px-2 text-sm font-[450] tracking-[-0.05px] text-muted-foreground hover:text-foreground\"\n            disabled={!onOpen}\n            onClick={onOpen}\n            size=\"sm\"\n            variant=\"ghost\"\n          >\n            <FileIcon data-icon=\"inline-start\" /> Open diff\n          </Button>\n        </div>\n      </header>\n      <div\n        id={contentId}\n        hidden={!expanded}\n        className=\"py-2 transition-opacity duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] starting:opacity-0\"\n      >\n        {files.length === 0 && <p className=\"px-4 py-2 text-xs leading-5\">No changed files.</p>}\n        {Array.from(groups, ([folder, groupFiles]) =>\n          folder ? (\n            <div key={folder}>\n              <button\n                type=\"button\"\n                aria-expanded={!collapsedFolders.has(folder)}\n                onClick={() => toggleFolder(folder)}\n                className=\"flex min-h-8 w-full items-center gap-2.5 px-4 text-left outline-none hover:bg-muted/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\"\n              >\n                <ChevronDownIcon\n                  aria-hidden=\"true\"\n                  className={cn(\n                    'size-3.5 shrink-0 transition-[rotate] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none',\n                    collapsedFolders.has(folder) && '-rotate-90',\n                  )}\n                />\n                <FolderIcon aria-hidden=\"true\" className=\"size-4 shrink-0\" />\n                <span className=\"min-w-0 truncate font-mono\" title={folder}>\n                  {folder}\n                </span>\n                <ChangeCounts\n                  additions={groupFiles.reduce((total, file) => total + file.additions, 0)}\n                  deletions={groupFiles.reduce((total, file) => total + file.deletions, 0)}\n                />\n              </button>\n              {!collapsedFolders.has(folder) && (\n                <ul className=\"transition-opacity duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] starting:opacity-0\">\n                  {groupFiles.map((file) => (\n                    <CommitFileRow key={file.path} file={file} nested />\n                  ))}\n                </ul>\n              )}\n            </div>\n          ) : (\n            <ul key=\"root\">\n              {groupFiles.map((file) => (\n                <CommitFileRow key={file.path} file={file} />\n              ))}\n            </ul>\n          ),\n        )}\n      </div>\n    </article>\n  )\n}\n\nexport { Commit }\nexport type { CommitFile, CommitProps }\n\nfunction ReactIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentColor\" height=\"1em\" viewBox=\"0 0 24 24\" width=\"1em\" {...props}>\n      <path d=\"M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236a2.236 2.236 0 0 1-2.236-2.236a2.236 2.236 0 0 1 2.235-2.236a2.236 2.236 0 0 1 2.236 2.236m2.648-10.69c-1.346 0-3.107.96-4.888 2.622c-1.78-1.653-3.542-2.602-4.887-2.602c-.41 0-.783.093-1.106.278c-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03c-.704 3.113-.39 5.588.988 6.38c.32.187.69.275 1.102.275c1.345 0 3.107-.96 4.888-2.624c1.78 1.654 3.542 2.603 4.887 2.603c.41 0 .783-.09 1.106-.275c1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032c.704-3.11.39-5.587-.988-6.38a2.17 2.17 0 0 0-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127c.666.382.955 1.835.73 3.704c-.054.46-.142.945-.25 1.44a23.5 23.5 0 0 0-3.107-.534A24 24 0 0 0 12.769 4.7c1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28c-.686.72-1.37 1.537-2.02 2.442a23 23 0 0 0-3.113.538a15 15 0 0 1-.254-1.42c-.23-1.868.054-3.32.714-3.707c.19-.09.4-.127.563-.132zm4.882 3.05q.684.704 1.36 1.564c-.44-.02-.89-.034-1.345-.034q-.691-.001-1.36.034c.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093q.61.874 1.183 1.86q.557.961 1.018 1.946c-.308.655-.646 1.31-1.013 1.95c-.38.66-.773 1.288-1.18 1.87a25.6 25.6 0 0 1-4.412.005a27 27 0 0 1-1.183-1.86q-.557-.961-1.018-1.946a25 25 0 0 1 1.013-1.954c.38-.66.773-1.286 1.18-1.868A25 25 0 0 1 12 8.098zm-3.635.254c-.24.377-.48.763-.704 1.16q-.336.585-.635 1.174c-.265-.656-.49-1.31-.676-1.947c.64-.15 1.315-.283 2.015-.386zm7.26 0q1.044.153 2.006.387c-.18.632-.405 1.282-.66 1.933a26 26 0 0 0-1.345-2.32zm3.063.675q.727.226 1.375.498c1.732.74 2.852 1.708 2.852 2.476c-.005.768-1.125 1.74-2.857 2.475c-.42.18-.88.342-1.355.493a24 24 0 0 0-1.1-2.98c.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98a23 23 0 0 0-1.086 2.964c-.484-.15-.944-.318-1.37-.5c-1.732-.737-2.852-1.706-2.852-2.474s1.12-1.742 2.852-2.476c.42-.18.88-.342 1.356-.494m11.678 4.28c.265.657.49 1.312.676 1.948c-.64.157-1.316.29-2.016.39a26 26 0 0 0 1.341-2.338zm-9.945.02c.2.392.41.783.64 1.175q.345.586.705 1.143a22 22 0 0 1-2.006-.386c.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423c.23 1.868-.054 3.32-.714 3.708c-.147.09-.338.128-.563.128c-1.012 0-2.514-.807-4.11-2.28c.686-.72 1.37-1.536 2.02-2.44c1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532c.66.905 1.345 1.727 2.035 2.446c-1.595 1.483-3.092 2.295-4.11 2.295a1.2 1.2 0 0 1-.553-.132c-.666-.38-.955-1.834-.73-3.703c.054-.46.142-.944.25-1.438zm4.56.64q.661.032 1.345.034q.691.001 1.36-.034c-.44.572-.895 1.095-1.345 1.565q-.684-.706-1.36-1.565\" />\n    </svg>\n  )\n}\n\nfunction MarkdownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentColor\" height=\"1em\" viewBox=\"0 0 24 24\" width=\"1em\" {...props}>\n      <path d=\"M22.27 19.385H1.73A1.73 1.73 0 0 1 0 17.655V6.345a1.73 1.73 0 0 1 1.73-1.73h20.54A1.73 1.73 0 0 1 24 6.345v11.308a1.73 1.73 0 0 1-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885l2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885l-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z\" />\n    </svg>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/ai/commit.tsx"
    }
  ],
  "type": "registry:component"
}