{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "file-icon",
  "title": "File Icon",
  "description": "File Icon from the Get IT Done design system.",
  "dependencies": [
    "lucide-react@^0.379.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/file-icon.tsx",
      "content": "'use client';\n\nimport { File, FileArchive, FileCode, FileSpreadsheet, FileText, FileVideo, Folder, Image as ImageIcon, Presentation, type LucideIcon } from 'lucide-react';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport { useLabels, usePreset } from '@/components/ui/labels-context';\n\nexport type FileKind = 'document' | 'spreadsheet' | 'presentation' | 'image' | 'video' | 'archive' | 'code' | 'folder' | 'other';\n\nconst EXTENSION_KIND: Record<string, FileKind> = {\n  pdf: 'document', doc: 'document', docx: 'document', txt: 'document', md: 'document', rtf: 'document', odt: 'document',\n  xls: 'spreadsheet', xlsx: 'spreadsheet', csv: 'spreadsheet', ods: 'spreadsheet',\n  ppt: 'presentation', pptx: 'presentation', key: 'presentation', odp: 'presentation',\n  png: 'image', jpg: 'image', jpeg: 'image', gif: 'image', webp: 'image', svg: 'image', heic: 'image',\n  mp4: 'video', mov: 'video', webm: 'video', avi: 'video', mkv: 'video',\n  zip: 'archive', rar: 'archive', '7z': 'archive', tar: 'archive', gz: 'archive',\n  js: 'code', ts: 'code', tsx: 'code', jsx: 'code', json: 'code', html: 'code', css: 'code', py: 'code', xml: 'code',\n};\n\n// Extension without dot, lower case. Empty when the name has none.\nexport const fileExtensionOf = (name: string) => {\n  const index = name.lastIndexOf('.');\n  return index > 0 && index < name.length - 1 ? name.slice(index + 1).toLowerCase() : '';\n};\n\nexport const fileKindOf = (extension: string): FileKind => EXTENSION_KIND[extension.toLowerCase().replace(/^\\./, '')] ?? 'other';\n\nconst kindStyle: Record<FileKind, { icon: LucideIcon; tile: string }> = {\n  document: { icon: FileText, tile: 'bg-data-file-doc-subtle text-data-file-doc-on' },\n  spreadsheet: { icon: FileSpreadsheet, tile: 'bg-data-file-sheet-subtle text-data-file-sheet-on' },\n  presentation: { icon: Presentation, tile: 'bg-data-file-slide-subtle text-data-file-slide-on' },\n  image: { icon: ImageIcon, tile: 'bg-data-file-image-subtle text-data-file-image-on' },\n  video: { icon: FileVideo, tile: 'bg-data-file-video-subtle text-data-file-video-on' },\n  archive: { icon: FileArchive, tile: 'bg-data-file-archive-subtle text-data-file-archive-on' },\n  code: { icon: FileCode, tile: 'bg-data-file-code-subtle text-data-file-code-on' },\n  // Folders have no generic-file extension; they reuse the neutral generic tokens with the folder glyph.\n  folder: { icon: Folder, tile: 'bg-data-file-generic-subtle text-data-file-generic-on' },\n  other: { icon: File, tile: 'bg-data-file-generic-subtle text-data-file-generic-on' },\n};\n// PDF has its own colour within the document kind.\nconst pdfTile = 'bg-data-file-pdf-subtle text-data-file-pdf-on';\n\n// Document variant: the extension tag is a solid fill (`colors.data.file.<type>.default`) with the matching `on-default` text, in light and dark (tokens 5.8).\nconst documentTag: Record<FileKind, string> = {\n  document: 'bg-data-file-doc-default text-data-file-doc-on-default',\n  spreadsheet: 'bg-data-file-sheet-default text-data-file-sheet-on-default',\n  presentation: 'bg-data-file-slide-default text-data-file-slide-on-default',\n  image: 'bg-data-file-image-default text-data-file-image-on-default',\n  video: 'bg-data-file-video-default text-data-file-video-on-default',\n  archive: 'bg-data-file-archive-default text-data-file-archive-on-default',\n  code: 'bg-data-file-code-default text-data-file-code-on-default',\n  folder: 'bg-data-file-generic-default text-data-file-generic-on-default',\n  other: 'bg-data-file-generic-default text-data-file-generic-on-default',\n};\n// Static strings so Tailwind finds the classes.\nconst pdfTag = 'bg-data-file-pdf-default text-data-file-pdf-on-default';\n\nconst sizeStyle = {\n  xs: { box: 'h-8 w-8 rounded-item', icon: 'h-icon-sm w-icon-sm', label: 'hidden' },\n  sm: { box: 'h-control-md w-control-md rounded-item', icon: 'h-icon-sm w-icon-sm', label: 'hidden' },\n  md: { box: 'h-control-xl w-control-xl rounded-item', icon: 'h-icon-sm w-icon-sm', label: 'typography-avatar-xs' },\n  lg: { box: 'h-14 w-14 rounded-surface', icon: 'h-icon-lg w-icon-lg', label: 'typography-micro font-semibold' },\n} as const;\n\nexport interface FileIconProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'> {\n  // Extension without dot. Shown as label and used to pick the type.\n  ext?: string;\n  // File name. The extension is taken from it when `ext` is missing.\n  name?: string;\n  // Overrides the type derived from the extension.\n  kind?: FileKind;\n  size?: keyof typeof sizeStyle;\n  // `tile` is the tinted square (`size` xs is the 32px tile, sm 40, md 48 in neutral). `document` is the page outline with a filled extension tag (28 by 32, `size` is ignored). `bare` is the type glyph in the kind colour without a box or label (`size` sets the glyph size). Defaults to `document` in the zero preset, otherwise `tile`.\n  variant?: 'tile' | 'document' | 'bare';\n  // Failed upload: destructive border and tint.\n  error?: boolean;\n  // Decorative by default because the file name carries the meaning. Set false to expose \"Document PDF\" as an image.\n  decorative?: boolean;\n}\n\n// File type tile built on the data-file tokens (AA in light and dark).\nconst FileIcon = React.forwardRef<HTMLSpanElement, FileIconProps>(\n  ({ className, ext, name, kind, size = 'md', variant: variantProp, error = false, decorative = true, ...props }, ref) => {\n    const l = useLabels();\n    const zero = usePreset() === 'zero';\n    const variant = variantProp ?? (zero ? 'document' : 'tile');\n    const extension = (ext ?? (name ? fileExtensionOf(name) : '')).toLowerCase().replace(/^\\./, '');\n    const resolved = kind ?? fileKindOf(extension);\n    const { icon: Glyph, tile: kindTile } = kindStyle[resolved];\n    const tile = extension === 'pdf' ? pdfTile : kindTile;\n    const s = sizeStyle[size];\n    const accessibleName = `${l.fileKind(resolved)}${extension ? ` ${extension.toUpperCase()}` : ''}${error ? `, ${l.uploadFailed}` : ''}`;\n    if (variant === 'bare') {\n      const textClass = tile.split(' ').find((c) => c.startsWith('text-'));\n      return (\n        <span\n          ref={ref}\n          data-kind={resolved}\n          data-variant=\"bare\"\n          data-error={error || undefined}\n          {...(decorative ? { 'aria-hidden': true } : { role: 'img', 'aria-label': accessibleName })}\n          className={cn('inline-flex shrink-0 items-center justify-center', error ? 'text-text-destructive' : textClass, className)}\n          {...props}\n        >\n          <Glyph aria-hidden=\"true\" className={s.icon} />\n        </span>\n      );\n    }\n    if (variant === 'document') {\n      return (\n        <span\n          ref={ref}\n          data-kind={resolved}\n          data-variant=\"document\"\n          data-error={error || undefined}\n          {...(decorative ? { 'aria-hidden': true } : { role: 'img', 'aria-label': accessibleName })}\n          className={cn(\n            'relative inline-flex h-8 w-7 shrink-0 items-start justify-center pt-1 rounded-tight rounded-tr-control border border-solid bg-background-default text-icon-subtle',\n            error ? 'border-border-destructive text-text-destructive' : 'border-input',\n            className,\n          )}\n          {...props}\n        >\n          <Glyph aria-hidden=\"true\" className=\"h-icon-2xs w-icon-2xs\" />\n          {extension ? (\n            <b\n              className={cn(\n                'typography-file-tag absolute -left-1 bottom-1 max-w-8 truncate rounded-hairline px-micro',\n                error ? 'bg-background-destructive-bold-default text-text-inverse' : extension === 'pdf' ? pdfTag : documentTag[resolved],\n              )}\n            >\n              {extension.slice(0, 4)}\n            </b>\n          ) : null}\n        </span>\n      );\n    }\n    return (\n      <span\n        ref={ref}\n        data-kind={resolved}\n        data-error={error || undefined}\n        {...(decorative ? { 'aria-hidden': true } : { role: 'img', 'aria-label': accessibleName })}\n        className={cn(\n          'inline-flex shrink-0 flex-col items-center justify-center border border-solid',\n          error ? 'border-border-destructive bg-background-destructive-subtle text-text-destructive-on-subtle' : cn('border-hairline', tile),\n          s.box,\n          className,\n        )}\n        {...props}\n      >\n        <Glyph aria-hidden=\"true\" className={s.icon} />\n        {extension ? <b className={cn('max-w-full truncate px-1 uppercase', s.label)}>{extension.slice(0, 4)}</b> : null}\n      </span>\n    );\n  },\n);\nFileIcon.displayName = 'FileIcon';\n\nexport { FileIcon };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}