{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "file-type",
  "title": "File Type",
  "author": "@fma-ds/ds",
  "description": "A file type icon: a page with a folded corner and the extension label, in the format's brand or family color (Word blue, Excel green, the GitHub language colors).\n\n- **`type`**: one of 171 extensions (documents, certificates, code, config, archives…), one of 13 families (`image`, `folder`… drawn as a glyph) or any other string (a neutral labelled page).\n- **`getFileType()`**: resolves a file name, path, URL or MIME type, including well-known names without an extension (Dockerfile, `.env.local`, `go.mod`, LICENSE) and aliases such as `.cer`. `getFileTypeCategory()` returns the family.\n- **`variant`**: `outline`, `solid`, `light` or `mono` (theme gray). A `text-*` class recolors it.\n- **`size`**: from `mini` (18px, for table rows) to `xl`; a Tailwind `size-*` class scales the label and stroke with the box (container units).\n- **Content**: `label` replaces the text, `glyph` shows the family glyph and `icon` draws a custom icon.\n\nRenders a `span` with `role=\"img\"` named after the type. It has no state, so it works in Server Components.",
  "dependencies": [
    "class-variance-authority@^0.7.1",
    "cn@^0.5.0"
  ],
  "files": [
    {
      "path": "src/core/file-type/file-type.tsx",
      "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"cn\";\nimport type { ComponentProps, CSSProperties, ReactNode } from \"react\";\n\n/* -------------------------------------------------------------------------- */\n/*                                    Types                                   */\n/* -------------------------------------------------------------------------- */\n\n/** The family a type belongs to. It picks the glyph and the default color. */\ntype FileTypeCategory =\n  | \"archive\"\n  | \"audio\"\n  | \"code\"\n  | \"design\"\n  | \"document\"\n  | \"empty\"\n  | \"executable\"\n  | \"file\"\n  | \"folder\"\n  | \"font\"\n  | \"image\"\n  | \"presentation\"\n  | \"spreadsheet\"\n  | \"video\";\n\ntype FileTypeExtension = {\n  category: FileTypeCategory;\n  /** The format's text color class. */\n  color: string;\n  /** A short label for a key too long for the band, as DOCK for Dockerfile. */\n  label?: string;\n};\n\n/* -------------------------------------------------------------------------- */\n/*                                 Type table                                 */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The color of a format with no brand or family color of its own. A solid page\n * in it read as a dark slab beside the colored ones, so it fills lighter, and\n * its glyph turns dark gray, where white would fade on the pale page.\n */\nconst NEUTRAL =\n  \"text-zinc-500 [--file-type-fill:color-mix(in_oklab,currentColor_45%,white)] [--file-type-glyph:color-mix(in_oklab,currentColor_80%,black)]\";\n/** Yellow takes dark text, as the JavaScript logo does: white on it stays\n * under 3:1 until the 700 step. */\nconst YELLOW = \"text-yellow-500 [--file-type-foreground:var(--color-yellow-950)]\";\n\n/** The family colors, which a format without a brand color of its own takes. */\nconst CATEGORY_COLORS: Record<FileTypeCategory, string> = {\n  archive: NEUTRAL,\n  audio: \"text-pink-600\",\n  code: \"text-blue-600\",\n  design: \"text-purple-600\",\n  document: \"text-blue-600\",\n  empty: NEUTRAL,\n  executable: NEUTRAL,\n  file: NEUTRAL,\n  folder: \"text-violet-600\",\n  font: NEUTRAL,\n  image: \"text-violet-600\",\n  presentation: \"text-red-600\",\n  spreadsheet: \"text-green-600\",\n  video: \"text-blue-600\",\n};\n\nconst CATEGORY_NAMES: Record<FileTypeCategory, string> = {\n  archive: \"Archive\",\n  audio: \"Audio file\",\n  code: \"Code file\",\n  design: \"Design file\",\n  document: \"Document\",\n  empty: \"File\",\n  executable: \"Application\",\n  file: \"File\",\n  folder: \"Folder\",\n  font: \"Font file\",\n  image: \"Image\",\n  presentation: \"Presentation\",\n  spreadsheet: \"Spreadsheet\",\n  video: \"Video\",\n};\n\n/**\n * Known extensions. A format takes its brand's color (Word blue, Excel green,\n * the GitHub language colors for code), else its family's, and a format with\n * neither (archives, plain text, data, config, fonts, installers) is neutral.\n * Every color is the 600 step, so the set reads as one palette with white\n * label text at 3:1 or better.\n */\nconst FILE_TYPE_EXTENSIONS = {\n  // Documents\n  pdf: { category: \"document\", color: \"text-orange-600\" },\n  doc: { category: \"document\", color: \"text-blue-600\" },\n  docx: { category: \"document\", color: \"text-blue-600\" },\n  odt: { category: \"document\", color: \"text-blue-600\" },\n  rtf: { category: \"document\", color: \"text-blue-600\" },\n  pages: { category: \"document\", color: \"text-amber-600\" },\n  txt: { category: \"document\", color: NEUTRAL },\n  md: { category: \"document\", color: NEUTRAL },\n  log: { category: \"document\", color: NEUTRAL },\n  mdx: { category: \"document\", color: YELLOW },\n  mdc: { category: \"document\", color: NEUTRAL },\n  eml: { category: \"document\", color: NEUTRAL },\n  srt: { category: \"document\", color: NEUTRAL },\n  vtt: { category: \"document\", color: NEUTRAL },\n  // Spreadsheets\n  xls: { category: \"spreadsheet\", color: \"text-green-600\" },\n  xlsx: { category: \"spreadsheet\", color: \"text-green-600\" },\n  xlsm: { category: \"spreadsheet\", color: \"text-green-600\" },\n  ods: { category: \"spreadsheet\", color: \"text-green-600\" },\n  csv: { category: \"spreadsheet\", color: \"text-green-600\" },\n  tsv: { category: \"spreadsheet\", color: \"text-green-600\" },\n  numbers: { category: \"spreadsheet\", color: \"text-green-600\", label: \"NUM\" },\n  parquet: { category: \"spreadsheet\", color: NEUTRAL, label: \"PARQ\" },\n  sqlite: { category: \"spreadsheet\", color: \"text-sky-600\" },\n  db: { category: \"spreadsheet\", color: NEUTRAL },\n  // Presentations\n  ppt: { category: \"presentation\", color: \"text-red-600\" },\n  pptx: { category: \"presentation\", color: \"text-red-600\" },\n  odp: { category: \"presentation\", color: \"text-red-600\" },\n  key: { category: \"presentation\", color: \"text-blue-600\" },\n  // Images\n  png: { category: \"image\", color: YELLOW },\n  jpg: { category: \"image\", color: \"text-violet-600\" },\n  jpeg: { category: \"image\", color: \"text-violet-600\" },\n  gif: { category: \"image\", color: \"text-violet-600\" },\n  webp: { category: \"image\", color: \"text-violet-600\" },\n  avif: { category: \"image\", color: \"text-violet-600\" },\n  heic: { category: \"image\", color: \"text-violet-600\" },\n  bmp: { category: \"image\", color: \"text-violet-600\" },\n  tif: { category: \"image\", color: \"text-violet-600\" },\n  tiff: { category: \"image\", color: \"text-violet-600\" },\n  ico: { category: \"image\", color: \"text-violet-600\" },\n  raw: { category: \"image\", color: NEUTRAL },\n  svg: { category: \"image\", color: YELLOW },\n  jxl: { category: \"image\", color: \"text-violet-600\" },\n  // Video\n  mp4: { category: \"video\", color: \"text-blue-600\" },\n  mov: { category: \"video\", color: \"text-blue-600\" },\n  avi: { category: \"video\", color: \"text-blue-600\" },\n  mkv: { category: \"video\", color: \"text-blue-600\" },\n  webm: { category: \"video\", color: \"text-blue-600\" },\n  mpeg: { category: \"video\", color: \"text-blue-600\" },\n  mpg: { category: \"video\", color: \"text-blue-600\" },\n  wmv: { category: \"video\", color: \"text-blue-600\" },\n  m3u8: { category: \"video\", color: \"text-blue-600\" },\n  // Audio\n  mp3: { category: \"audio\", color: \"text-pink-600\" },\n  wav: { category: \"audio\", color: \"text-pink-600\" },\n  aac: { category: \"audio\", color: \"text-pink-600\" },\n  flac: { category: \"audio\", color: \"text-pink-600\" },\n  ogg: { category: \"audio\", color: \"text-pink-600\" },\n  m4a: { category: \"audio\", color: \"text-pink-600\" },\n  aiff: { category: \"audio\", color: \"text-pink-600\" },\n  opus: { category: \"audio\", color: \"text-pink-600\" },\n  // Code and data\n  js: { category: \"code\", color: YELLOW },\n  jsx: { category: \"code\", color: YELLOW },\n  mjs: { category: \"code\", color: YELLOW },\n  ts: { category: \"code\", color: \"text-blue-600\" },\n  tsx: { category: \"code\", color: \"text-blue-600\" },\n  html: { category: \"code\", color: \"text-orange-600\" },\n  htm: { category: \"code\", color: \"text-orange-600\" },\n  css: { category: \"code\", color: \"text-purple-600\" },\n  scss: { category: \"code\", color: \"text-pink-600\" },\n  json: { category: \"code\", color: NEUTRAL },\n  xml: { category: \"code\", color: NEUTRAL },\n  yaml: { category: \"code\", color: NEUTRAL },\n  yml: { category: \"code\", color: NEUTRAL },\n  sql: { category: \"code\", color: NEUTRAL },\n  py: { category: \"code\", color: \"text-blue-600\" },\n  java: { category: \"code\", color: \"text-orange-600\" },\n  php: { category: \"code\", color: \"text-indigo-600\" },\n  rb: { category: \"code\", color: \"text-red-600\" },\n  go: { category: \"code\", color: \"text-cyan-600\" },\n  rs: { category: \"code\", color: \"text-orange-600\" },\n  c: { category: \"code\", color: NEUTRAL },\n  cpp: { category: \"code\", color: \"text-blue-600\" },\n  h: { category: \"code\", color: NEUTRAL },\n  cs: { category: \"code\", color: \"text-purple-600\" },\n  sh: { category: \"code\", color: \"text-green-600\" },\n  rss: { category: \"code\", color: \"text-orange-600\" },\n  cjs: { category: \"code\", color: YELLOW },\n  mts: { category: \"code\", color: \"text-blue-600\" },\n  vue: { category: \"code\", color: \"text-emerald-600\" },\n  svelte: { category: \"code\", color: \"text-orange-600\" },\n  astro: { category: \"code\", color: \"text-orange-600\" },\n  graphql: { category: \"code\", color: \"text-pink-600\", label: \"GQL\" },\n  gql: { category: \"code\", color: \"text-pink-600\" },\n  prisma: { category: \"code\", color: \"text-sky-600\" },\n  wasm: { category: \"code\", color: \"text-indigo-600\" },\n  map: { category: \"code\", color: NEUTRAL },\n  kt: { category: \"code\", color: \"text-violet-600\" },\n  kts: { category: \"code\", color: \"text-violet-600\" },\n  swift: { category: \"code\", color: \"text-red-600\" },\n  dart: { category: \"code\", color: \"text-teal-600\" },\n  lua: { category: \"code\", color: \"text-blue-600\" },\n  r: { category: \"code\", color: \"text-sky-600\" },\n  scala: { category: \"code\", color: \"text-rose-600\" },\n  ex: { category: \"code\", color: \"text-purple-600\" },\n  exs: { category: \"code\", color: \"text-purple-600\" },\n  hpp: { category: \"code\", color: \"text-blue-600\" },\n  cc: { category: \"code\", color: \"text-blue-600\" },\n  cu: { category: \"code\", color: \"text-green-600\" },\n  ps1: { category: \"code\", color: \"text-blue-600\" },\n  bat: { category: \"code\", color: \"text-green-600\" },\n  bash: { category: \"code\", color: \"text-green-600\" },\n  zsh: { category: \"code\", color: \"text-green-600\" },\n  gradle: { category: \"code\", color: \"text-cyan-600\" },\n  ipynb: { category: \"code\", color: \"text-orange-600\" },\n  // Config and infrastructure\n  env: { category: \"code\", color: NEUTRAL },\n  toml: { category: \"code\", color: NEUTRAL },\n  ini: { category: \"code\", color: NEUTRAL },\n  conf: { category: \"code\", color: NEUTRAL },\n  jsonc: { category: \"code\", color: NEUTRAL },\n  lock: { category: \"code\", color: NEUTRAL },\n  git: { category: \"code\", color: \"text-red-600\" },\n  ignore: { category: \"code\", color: NEUTRAL },\n  dockerfile: { category: \"code\", color: \"text-blue-600\", label: \"DOCK\" },\n  makefile: { category: \"code\", color: \"text-green-600\", label: \"MAKE\" },\n  tf: { category: \"code\", color: \"text-purple-600\" },\n  tfvars: { category: \"code\", color: \"text-purple-600\" },\n  hcl: { category: \"code\", color: \"text-purple-600\" },\n  proto: { category: \"code\", color: \"text-blue-600\" },\n  jsonl: { category: \"code\", color: NEUTRAL },\n  ndjson: { category: \"code\", color: NEUTRAL },\n  diff: { category: \"code\", color: NEUTRAL },\n  patch: { category: \"code\", color: NEUTRAL },\n  pem: { category: \"code\", color: NEUTRAL },\n  crt: { category: \"code\", color: NEUTRAL },\n  // Archives\n  zip: { category: \"archive\", color: NEUTRAL },\n  rar: { category: \"archive\", color: NEUTRAL },\n  \"7z\": { category: \"archive\", color: NEUTRAL },\n  tar: { category: \"archive\", color: NEUTRAL },\n  gz: { category: \"archive\", color: NEUTRAL },\n  tgz: { category: \"archive\", color: NEUTRAL },\n  bz2: { category: \"archive\", color: NEUTRAL },\n  xz: { category: \"archive\", color: NEUTRAL },\n  zst: { category: \"archive\", color: NEUTRAL },\n  jar: { category: \"archive\", color: \"text-orange-600\" },\n  whl: { category: \"archive\", color: \"text-blue-600\" },\n  // Model weights, packed like an archive\n  safetensors: { category: \"archive\", color: NEUTRAL, label: \"SAFE\" },\n  gguf: { category: \"archive\", color: NEUTRAL },\n  onnx: { category: \"archive\", color: NEUTRAL },\n  pt: { category: \"archive\", color: \"text-red-600\" },\n  // Design\n  fig: { category: \"design\", color: \"text-purple-600\" },\n  sketch: { category: \"design\", color: \"text-amber-600\" },\n  psd: { category: \"design\", color: \"text-sky-600\" },\n  ai: { category: \"design\", color: \"text-orange-600\" },\n  eps: { category: \"design\", color: \"text-purple-600\" },\n  indd: { category: \"design\", color: \"text-pink-600\" },\n  aep: { category: \"design\", color: \"text-violet-600\" },\n  xd: { category: \"design\", color: \"text-fuchsia-600\" },\n  // Applications\n  exe: { category: \"executable\", color: NEUTRAL },\n  msi: { category: \"executable\", color: NEUTRAL },\n  dmg: { category: \"executable\", color: NEUTRAL },\n  apk: { category: \"executable\", color: \"text-green-600\" },\n  deb: { category: \"executable\", color: \"text-rose-600\" },\n  iso: { category: \"executable\", color: NEUTRAL },\n  app: { category: \"executable\", color: NEUTRAL },\n  pkg: { category: \"executable\", color: NEUTRAL },\n  ipa: { category: \"executable\", color: NEUTRAL },\n  aab: { category: \"executable\", color: \"text-green-600\" },\n  rpm: { category: \"executable\", color: \"text-red-600\" },\n  // Fonts\n  ttf: { category: \"font\", color: NEUTRAL },\n  otf: { category: \"font\", color: NEUTRAL },\n  woff: { category: \"font\", color: NEUTRAL },\n  woff2: { category: \"font\", color: NEUTRAL },\n} satisfies Record<string, FileTypeExtension>;\n\n/** The families that can be passed as a `type` on their own, glyph only. */\nconst FILE_TYPE_CATEGORIES = [\n  \"archive\",\n  \"audio\",\n  \"code\",\n  \"design\",\n  \"document\",\n  \"empty\",\n  \"executable\",\n  \"folder\",\n  \"font\",\n  \"image\",\n  \"presentation\",\n  \"spreadsheet\",\n  \"video\",\n] as const satisfies readonly FileTypeCategory[];\n\ntype FileTypeExtensionName = keyof typeof FILE_TYPE_EXTENSIONS;\n\n/** Every type the table knows. Any other string renders as a neutral file. */\ntype FileTypeName = FileTypeExtensionName | (typeof FILE_TYPE_CATEGORIES)[number];\n\nconst FILE_TYPE_EXTENSION_NAMES = Object.keys(FILE_TYPE_EXTENSIONS) as FileTypeExtensionName[];\n\n/* -------------------------------------------------------------------------- */\n/*                                 Resolution                                 */\n/* -------------------------------------------------------------------------- */\n\ntype ResolvedFileType = {\n  type: string;\n  category: FileTypeCategory;\n  /** The extension label, or undefined for a family or an empty file. */\n  label?: string;\n  color: string;\n};\n\nconst extensions: Record<string, FileTypeExtension> = FILE_TYPE_EXTENSIONS;\n\n/** `Object.hasOwn` would fail to typecheck in an app below the ES2022 lib. */\nfunction has(record: object, key: string) {\n  return Object.hasOwn(record, key);\n}\n\nfunction isCategory(value: string): value is FileTypeCategory {\n  return (FILE_TYPE_CATEGORIES as readonly string[]).includes(value);\n}\n\nfunction resolveFileType(type?: string): ResolvedFileType {\n  const key = (type ?? \"\").trim().toLowerCase().replace(/^\\.+/, \"\");\n\n  if (!key || key === \"empty\") {\n    return { type: \"empty\", category: \"empty\", color: CATEGORY_COLORS.empty };\n  }\n\n  const extension = has(extensions, key) ? extensions[key] : null;\n  if (extension) {\n    return {\n      type: key,\n      category: extension.category,\n      label: extension.label ?? key.toUpperCase(),\n      color: extension.color,\n    };\n  }\n\n  if (isCategory(key)) {\n    return { type: key, category: key, color: CATEGORY_COLORS[key] };\n  }\n\n  return {\n    type: key,\n    category: \"file\",\n    label: key.toUpperCase(),\n    color: CATEGORY_COLORS.file,\n  };\n}\n\n/**\n * MIME types whose subtype does not name the extension. Anything else under\n * image/, video/, audio/ or font/ falls back to its family.\n */\nconst MIME_TYPES: Record<string, FileTypeName> = {\n  \"application/pdf\": \"pdf\",\n  \"application/msword\": \"doc\",\n  \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\": \"docx\",\n  \"application/vnd.oasis.opendocument.text\": \"odt\",\n  \"application/rtf\": \"rtf\",\n  \"application/vnd.ms-excel\": \"xls\",\n  \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": \"xlsx\",\n  \"application/vnd.oasis.opendocument.spreadsheet\": \"ods\",\n  \"application/vnd.ms-powerpoint\": \"ppt\",\n  \"application/vnd.openxmlformats-officedocument.presentationml.presentation\": \"pptx\",\n  \"application/vnd.oasis.opendocument.presentation\": \"odp\",\n  \"application/vnd.ms-excel.sheet.macroenabled.12\": \"xlsm\",\n  \"application/vnd.apple.keynote\": \"key\",\n  \"application/zip\": \"zip\",\n  \"application/x-zip-compressed\": \"zip\",\n  \"application/vnd.rar\": \"rar\",\n  \"application/x-rar-compressed\": \"rar\",\n  \"application/x-7z-compressed\": \"7z\",\n  \"application/x-tar\": \"tar\",\n  \"application/gzip\": \"gz\",\n  \"application/x-gzip\": \"gz\",\n  \"application/x-bzip2\": \"bz2\",\n  \"application/json\": \"json\",\n  \"application/xml\": \"xml\",\n  \"application/yaml\": \"yaml\",\n  \"application/x-yaml\": \"yaml\",\n  \"application/rss+xml\": \"rss\",\n  \"application/javascript\": \"js\",\n  \"application/x-httpd-php\": \"php\",\n  \"application/x-sh\": \"sh\",\n  \"application/x-msdownload\": \"exe\",\n  \"application/x-msi\": \"msi\",\n  \"application/vnd.android.package-archive\": \"apk\",\n  \"application/vnd.debian.binary-package\": \"deb\",\n  \"application/x-apple-diskimage\": \"dmg\",\n  \"application/x-iso9660-image\": \"iso\",\n  \"application/x-font-ttf\": \"ttf\",\n  \"application/font-woff\": \"woff\",\n  \"application/postscript\": \"eps\",\n  \"application/vnd.adobe.photoshop\": \"psd\",\n  \"image/vnd.adobe.photoshop\": \"psd\",\n  \"image/jpeg\": \"jpg\",\n  \"image/svg+xml\": \"svg\",\n  \"image/x-icon\": \"ico\",\n  \"image/vnd.microsoft.icon\": \"ico\",\n  \"video/quicktime\": \"mov\",\n  \"video/x-msvideo\": \"avi\",\n  \"video/x-matroska\": \"mkv\",\n  \"audio/mpeg\": \"mp3\",\n  \"audio/wav\": \"wav\",\n  \"audio/x-wav\": \"wav\",\n  \"audio/mp4\": \"m4a\",\n  \"audio/x-m4a\": \"m4a\",\n  \"audio/x-flac\": \"flac\",\n  \"audio/x-aiff\": \"aiff\",\n  \"video/x-ms-wmv\": \"wmv\",\n  \"text/plain\": \"txt\",\n  \"text/markdown\": \"md\",\n  \"text/x-markdown\": \"md\",\n  \"text/x-python\": \"py\",\n  \"text/x-c\": \"c\",\n  \"text/yaml\": \"yaml\",\n  \"text/csv\": \"csv\",\n  \"text/tab-separated-values\": \"tsv\",\n  \"text/html\": \"html\",\n  \"text/css\": \"css\",\n  \"text/javascript\": \"js\",\n  \"text/xml\": \"xml\",\n  \"application/node\": \"cjs\",\n  \"application/java-archive\": \"jar\",\n  \"application/x-ipynb+json\": \"ipynb\",\n  \"application/x-ndjson\": \"ndjson\",\n  \"application/vnd.sqlite3\": \"sqlite\",\n  \"application/x-sqlite3\": \"sqlite\",\n  \"application/vnd.apple.mpegurl\": \"m3u8\",\n  \"application/x-mpegurl\": \"m3u8\",\n  \"application/x-subrip\": \"srt\",\n  \"message/rfc822\": \"eml\",\n  \"application/x-rpm\": \"rpm\",\n  \"application/x-pem-file\": \"pem\",\n  \"application/x-x509-ca-cert\": \"crt\",\n  \"application/pkix-cert\": \"crt\",\n  \"application/zstd\": \"zst\",\n  \"application/x-xz\": \"xz\",\n  \"text/x-c++src\": \"cpp\",\n  \"text/x-c++hdr\": \"hpp\",\n  \"text/x-kotlin\": \"kt\",\n  \"text/x-swift\": \"swift\",\n  \"text/x-rsrc\": \"r\",\n  \"text/x-lua\": \"lua\",\n  \"text/x-scala\": \"scala\",\n  \"text/x-diff\": \"diff\",\n  \"text/x-patch\": \"patch\",\n  \"text/x-makefile\": \"makefile\",\n};\n\n/**\n * Names that carry their type with no extension of their own, or with one\n * that names something else (\"go.mod\" is not a tracker module).\n */\nconst FILE_NAMES: Record<string, FileTypeName> = {\n  containerfile: \"dockerfile\",\n  gnumakefile: \"makefile\",\n  \".dev.vars\": \"env\",\n  \".gitignore\": \"git\",\n  \".gitattributes\": \"git\",\n  \".gitmodules\": \"git\",\n  \".gitkeep\": \"git\",\n  \".gitconfig\": \"git\",\n  \".git-blame-ignore-revs\": \"git\",\n  \".mailmap\": \"git\",\n  \"bun.lockb\": \"lock\",\n  \"go.sum\": \"lock\",\n  \"go.mod\": \"go\",\n  \"go.work\": \"go\",\n  gemfile: \"rb\",\n  rakefile: \"rb\",\n  brewfile: \"rb\",\n  podfile: \"rb\",\n  vagrantfile: \"rb\",\n  fastfile: \"rb\",\n  \".bashrc\": \"bash\",\n  \".bash_profile\": \"bash\",\n  \".bash_aliases\": \"bash\",\n  \".bash_logout\": \"bash\",\n  \".zshrc\": \"zsh\",\n  \".zprofile\": \"zsh\",\n  \".zshenv\": \"zsh\",\n  \".zlogin\": \"zsh\",\n  \".envrc\": \"sh\",\n  \".profile\": \"sh\",\n  \".editorconfig\": \"ini\",\n  \".npmrc\": \"ini\",\n  \".pypirc\": \"ini\",\n  \".flake8\": \"ini\",\n  \".pylintrc\": \"ini\",\n  \".coveragerc\": \"ini\",\n  caddyfile: \"conf\",\n  \".htaccess\": \"conf\",\n  \".babelrc\": \"json\",\n  \".swcrc\": \"json\",\n  \".cursorrules\": \"md\",\n  readme: \"txt\",\n  license: \"txt\",\n  licence: \"txt\",\n  changelog: \"txt\",\n  codeowners: \"txt\",\n  \".nvmrc\": \"txt\",\n  \".node-version\": \"txt\",\n  \".python-version\": \"txt\",\n  \".ruby-version\": \"txt\",\n  \".tool-versions\": \"txt\",\n  \".terraform-version\": \"txt\",\n};\n\n/** Other extensions of a known type. */\nconst EXTENSION_ALIASES: Record<string, FileTypeName> = {\n  cer: \"crt\",\n  mak: \"makefile\",\n  mk: \"makefile\",\n  sqlite3: \"sqlite\",\n};\n\n/**\n * Name families, read only when the name does not end in a known extension:\n * \".env.local\" is an env file and \"Dockerfile.dev\" a Dockerfile, while\n * \"Dockerfile.md\" is Markdown.\n */\nconst NAME_PATTERNS: [RegExp, FileTypeName][] = [\n  [/^\\.env\\./, \"env\"],\n  [/^(docker|container)file\\./, \"dockerfile\"],\n  [/^\\.[\\w-]+ignore$/, \"ignore\"],\n];\n\nconst MIME_FAMILIES: Record<string, FileTypeName> = {\n  image: \"image\",\n  video: \"video\",\n  audio: \"audio\",\n  font: \"font\",\n  text: \"txt\",\n};\n\nconst MIME_PATTERN =\n  /^(application|audio|font|image|message|model|multipart|text|video)\\/[\\w.+-]+$/;\nconst URL_ORIGIN = /^[a-z][\\w+.-]*:\\/\\/[^/]*/;\n/**\n * A value ending in an extension or a slash carries no query or hash. An\n * extension has a letter, so \"?v=1.2\" is still a query.\n */\nconst NAME_END = /(\\.\\d*[a-z][a-z\\d]*|[\\\\/])$/;\n\n/**\n * Resolves a file name, path, URL, bare extension or MIME type to a `type`.\n * A path ending in a slash is a folder; a well-known name resolves to its\n * type (Dockerfile, .env.local, go.mod, LICENSE); any other name without an\n * extension is an empty file; an unknown extension is returned as is and\n * renders neutral.\n */\nfunction getFileType(value: string): FileTypeName | (string & {}) {\n  let input = value.trim().toLowerCase();\n\n  // A URL is read by its path, so a bare host is not a \".com\" file. A \"?\" or\n  // \"#\" can be part of a plain name (\"invoice #12.pdf\"), so outside a URL it\n  // starts a query only when the value does not already end like a name.\n  if (URL_ORIGIN.test(input)) {\n    input = input.split(/[?#]/)[0].replace(URL_ORIGIN, \"\");\n    if (input === \"/\") return \"empty\";\n  } else if (!NAME_END.test(input)) {\n    input = input.split(/[?#]/)[0];\n  }\n  if (!input) return \"empty\";\n  if (input.endsWith(\"/\") || input.endsWith(\"\\\\\")) return \"folder\";\n\n  const mime = input.split(\";\")[0].trim();\n  if (MIME_PATTERN.test(mime)) {\n    if (has(MIME_TYPES, mime)) return MIME_TYPES[mime];\n    const [family, subtype] = mime.split(\"/\");\n    // A known extension after a dot makes it a storage key (\"image/a.png\").\n    const tail = subtype.slice(subtype.lastIndexOf(\".\") + 1);\n    if (subtype.includes(\".\") && has(extensions, tail)) return tail;\n    // A media subtype counts only inside its own family: audio/webm is audio.\n    if (\n      has(extensions, subtype) &&\n      (family === \"application\" || family === \"text\" || extensions[subtype].category === family)\n    ) {\n      return subtype;\n    }\n    return has(MIME_FAMILIES, family) ? MIME_FAMILIES[family] : \"empty\";\n  }\n\n  const name = input.split(/[\\\\/]/).pop() ?? \"\";\n  if (has(FILE_NAMES, name)) return FILE_NAMES[name];\n  const dot = name.lastIndexOf(\".\");\n  const extension = name.slice(dot + 1);\n  if (has(EXTENSION_ALIASES, extension)) return EXTENSION_ALIASES[extension];\n  if (!has(extensions, extension)) {\n    const match = NAME_PATTERNS.find(([pattern]) => pattern.test(name));\n    if (match) return match[1];\n  }\n  // No dot, or only a leading one: a bare extension or family (\"pdf\",\n  // \".pdf\", \"image\") resolves, anything else (\"notes\", \".hidden\") has no type.\n  if (dot <= 0) {\n    return resolveFileType(extension).category === \"file\" ? \"empty\" : extension;\n  }\n\n  return extension || \"empty\";\n}\n\n/** The family of any `type`, for grouping or filtering a file list. */\nfunction getFileTypeCategory(type?: string): FileTypeCategory {\n  return resolveFileType(type).category;\n}\n\n/* -------------------------------------------------------------------------- */\n/*                                   Glyphs                                   */\n/* -------------------------------------------------------------------------- */\n\n/*\n * Every glyph is drawn in the 24-unit page grid inside one box, x 7 to 17 and\n * y 10 to 20, so it sits under the fold at the same optical weight, and is\n * shown at 75% of it (full size up to 20px, where every pixel counts).\n * Parallel strokes are at least 3 units apart: the stroke stays 1px at every\n * size, and closer lines merge at `mini` (18px, where a unit is 0.75px).\n */\nconst STROKE = { vectorEffect: \"non-scaling-stroke\" } as const;\n\nconst GLYPHS: Partial<Record<FileTypeCategory, ReactNode>> = {\n  document: <path {...STROKE} d=\"M8 12h8M8 15h5.5M8 18h7\" />,\n  spreadsheet: (\n    <>\n      <rect {...STROKE} x=\"7.5\" y=\"11\" width=\"9\" height=\"8\" rx=\"1\" />\n      <path {...STROKE} d=\"M7.5 14.5h9M11 11v8\" />\n    </>\n  ),\n  presentation: (\n    <>\n      <rect {...STROKE} x=\"7\" y=\"11\" width=\"10\" height=\"5.5\" rx=\"1\" />\n      <path {...STROKE} d=\"m9.5 19.5 2.5-3 2.5 3\" />\n    </>\n  ),\n  image: (\n    <>\n      <path {...STROKE} d=\"m7 19.5 3.5-5.5 2.75 3.75 1.25-1.75 2.5 3.5Z\" />\n      <circle {...STROKE} cx=\"14.25\" cy=\"12\" r=\"1.25\" />\n    </>\n  ),\n  video: <path {...STROKE} d=\"M9.5 11v8l7-4Z\" />,\n  audio: (\n    <>\n      <path {...STROKE} d=\"M12.5 18v-7l3 1.5\" />\n      <circle {...STROKE} cx=\"10.75\" cy=\"18\" r=\"1.75\" />\n    </>\n  ),\n  code: <path {...STROKE} d=\"m10 12-3 3 3 3m4-6 3 3-3 3\" />,\n  archive: (\n    <>\n      <rect {...STROKE} x=\"7\" y=\"11\" width=\"10\" height=\"3\" rx=\"0.75\" />\n      <path {...STROKE} d=\"M8 14v4.5a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V14M11 16.5h2\" />\n    </>\n  ),\n  design: (\n    <>\n      <circle {...STROKE} cx=\"10.5\" cy=\"13\" r=\"2.5\" />\n      <rect {...STROKE} x=\"12\" y=\"14.5\" width=\"5\" height=\"5\" rx=\"1\" />\n    </>\n  ),\n  executable: (\n    <>\n      <rect {...STROKE} x=\"7.5\" y=\"10.75\" width=\"3.5\" height=\"3.5\" rx=\"0.75\" />\n      <rect {...STROKE} x=\"13\" y=\"10.75\" width=\"3.5\" height=\"3.5\" rx=\"0.75\" />\n      <rect {...STROKE} x=\"7.5\" y=\"16.25\" width=\"3.5\" height=\"3.5\" rx=\"0.75\" />\n      <rect {...STROKE} x=\"13\" y=\"16.25\" width=\"3.5\" height=\"3.5\" rx=\"0.75\" />\n    </>\n  ),\n  font: <path {...STROKE} d=\"M8.5 12.5V11h7v1.5M12 11v8m-1.5 0h3\" />,\n  folder: (\n    <path\n      {...STROKE}\n      d=\"M7 12a1 1 0 0 1 1-1h2.25a1 1 0 0 1 .8.4l.9 1.1H16a1 1 0 0 1 1 1V18a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1Z\"\n    />\n  ),\n};\n\n/* -------------------------------------------------------------------------- */\n/*                                  Variants                                  */\n/* -------------------------------------------------------------------------- */\n\n/** The page: 16 by 20 units with 2-unit corners and a rounded 6-unit fold. */\nconst PAGE_PATH =\n  \"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h7.586a2 2 0 0 1 1.414.586l4.414 4.414A2 2 0 0 1 20 8.414V20a2 2 0 0 1-2 2Z\";\nconst FOLD_PATH = \"M14 2v5a1 1 0 0 0 1 1h5\";\n/** The folded corner, traced along the page edge so it never leaves the page. */\nconst FLAP_PATH =\n  \"M13.586 2a2 2 0 0 1 1.414.586l4.414 4.414A2 2 0 0 1 20 8.414V8h-5a1 1 0 0 1-1-1V2Z\";\n\n/**\n * Lyra and Sera square every radius, so the page, its fold and the flap square\n * too: both shapes render, and a per-style variable shows one. Outside a style\n * the var() fallback keeps the rounded page.\n */\nconst PAGE_PATH_SQUARE = \"M4 22V2h10l6 6v14Z\";\nconst FOLD_PATH_SQUARE = \"M14 2v6h6\";\nconst FLAP_PATH_SQUARE = \"M14 2l6 6h-6Z\";\nconst ROUND = \"[display:var(--file-type-round,inline)]\";\nconst SQUARE = \"[display:var(--file-type-square,none)]\";\n\n/**\n * CSS variable architecture, all read from the root:\n *\n *   --file-type-size        box width and height, set by `size`\n *   --file-type-stroke      line width; unset, 1px up to 40px, then proportional\n *   --file-type-border      page outline in outline, light and mono; unset, 40%\n *                           muted-foreground, 65% up to 20px\n *   --file-type-fill        solid page; unset, the type color\n *   --file-type-glyph       glyph, custom icon and label in solid; unset, the\n *                           foreground\n *   --file-type-foreground  label text, and the glyph in solid\n *   --file-type-radius      label corners; unset, the style's own (below)\n *\n * The root is an inline-size container and everything inside is measured in\n * `cqw` against it, so a plain Tailwind size class (`size-20`, `size-full`)\n * resizes the label and stroke with the box, not just the box. The label band\n * is the same shape on every type at every size (the Figma's 40px geometry in\n * percent); its text is 24% of the width (9.6px at 40px), so the capitals\n * fill half the band, and shrinks only when its characters would run past\n * the band. `--file-type-chars` carries the length for that fit; 0.72em is a\n * wide uppercase character, measured on WOFF2 and MPEG. Up to 20px the band\n * grows, the text turns semibold at a third of the box (5.8px at the 18px\n * mini), and the outline darkens,\n * or a row icon reads as a smudge.\n *\n * Browsers draw text on a whole-pixel baseline, which left capitals up to\n * half a pixel off center. The band's margin moves it onto that baseline\n * instead (`round()` against the label's own `1cap`), so a consumer's `top-*`\n * still places it; a browser without `round()` or `cap` keeps the plain\n * position.\n *\n * The type color is the root's text color, so one `text-*` class recolors a\n * type.\n */\nconst fileTypeVariants = cva(\n  [\n    \"@container relative inline-block shrink-0 align-middle leading-none select-none\",\n    \"size-(--file-type-size)\",\n    \"[--file-type-foreground:white]\",\n  ],\n  {\n    variants: {\n      size: {\n        mini: \"[--file-type-size:--spacing(4.5)]\",\n        xs: \"[--file-type-size:--spacing(6)]\",\n        sm: \"[--file-type-size:--spacing(8)]\",\n        default: \"[--file-type-size:--spacing(10)]\",\n        lg: \"[--file-type-size:--spacing(12)]\",\n        xl: \"[--file-type-size:--spacing(14)]\",\n      },\n    },\n    defaultVariants: {\n      size: \"default\",\n    },\n  },\n);\n\ntype FileTypeVariant = \"outline\" | \"solid\" | \"light\" | \"mono\";\n\n/**\n * Every type in `mono`: the theme's muted gray in place of its own color, with\n * the label text in the page color so it reads in both themes.\n */\nconst MONO = \"text-muted-foreground [--file-type-foreground:var(--background)]\";\n\nconst PAGE_CLASS: Record<FileTypeVariant, string> = {\n  outline: \"fill-transparent stroke-(color:--file-type-outline)\",\n  light: \"fill-muted stroke-(color:--file-type-outline)\",\n  solid: \"fill-[var(--file-type-fill,currentColor)] stroke-[var(--file-type-fill,currentColor)]\",\n  mono: \"fill-transparent stroke-(color:--file-type-outline)\",\n};\n\nconst FOLD_CLASS: Record<FileTypeVariant, string> = {\n  outline: \"stroke-(color:--file-type-outline)\",\n  light: \"stroke-(color:--file-type-outline)\",\n  solid: \"stroke-white/45\",\n  mono: \"stroke-(color:--file-type-outline)\",\n};\n\n/**\n * Solid paints the glyph in the foreground and mono in its gray, already light\n * on a dark page. The others use the type color, lifted toward white in dark\n * mode: a 1px line in a 600 shade is near 3:1 on a near-black page, while the\n * label keeps the shade its white text needs.\n */\nconst GLYPH_CLASS: Record<FileTypeVariant, string> = {\n  outline: \"dark:text-[color-mix(in_oklab,currentColor_70%,white)]\",\n  light: \"dark:text-[color-mix(in_oklab,currentColor_70%,white)]\",\n  solid: \"text-[color:var(--file-type-glyph,var(--file-type-foreground))]\",\n  mono: \"\",\n};\n\n/**\n * The label overhangs the page's left edge, so it is a band in front of the\n * sheet. Solid has no band: the text sits on the filled page in the glyph's\n * color, across the page's width (x 4 to 20). Every label up to 4 characters\n * takes one size (20.5cqw, 8.2px at 40px), so a row of mixed types reads\n * even; a longer one shrinks at 0.8em a character (0.9em in semibold up to\n * 20px, where each label fits on its own). Measured, WMV runs 0.875em and\n * WEBM 0.79em: at 20.5cqw WEBM spends most of the side padding, and 21cqw\n * would put it on the page edge, so this is the ceiling.\n * The band's radius is bespoke chrome in the ReUI Badge's proportions per\n * style: 10cqw (4px at 40px) where the badge is `rounded-sm` on 20px, a pill\n * in the round styles, square in lyra and sera. The pill pads 30% of its\n * height a side (20% elsewhere) so the round ends clear the text, and the text\n * fit reads the same padding. A label wider than the fit expects (WEBM: W and M\n * run past 0.72em) spends that extra padding, centered, before it truncates,\n * and never grows past the band's own 95% cap, so a smaller `px-*` from\n * `labelClassName` still ends in a whole ellipsis. The no-style fallbacks\n * live in the `var()`s, not in bare rungs: the registry flattens each ladder\n * to one bare rung per style, and a second one would win or lose on class\n * order. The capitals are trimmed to their own height (`text-box`), so they\n * center in the band whatever the font's ascent; the padding keeps a\n * descender from clipping.\n */\nconst LABEL_TEXT_CLASS: Record<FileTypeVariant, string> = {\n  outline: \"text-(color:--file-type-foreground)\",\n  light: \"text-(color:--file-type-foreground)\",\n  solid: \"max-w-[calc(200cqw/3)] text-[color:var(--file-type-glyph,var(--file-type-foreground))]\",\n  mono: \"text-(color:--file-type-foreground)\",\n};\n\nconst LABEL_CLASS: Record<FileTypeVariant, string> = {\n  outline: \"bg-current\",\n  light: \"bg-current\",\n  solid:\n    \"left-1/6 w-2/3 px-[3cqw] text-[length:min(20.5cqw,calc(65.6cqw/(var(--file-type-chars)*0.8)))] @max-[21px]:px-0 @max-[21px]:text-[length:min(32.5cqw,5.8px,calc(66cqw/(var(--file-type-chars)*0.9)))]\",\n  mono: \"bg-current\",\n};\n\n/* -------------------------------------------------------------------------- */\n/*                                  Component                                 */\n/* -------------------------------------------------------------------------- */\n\ntype FileTypeProps = Omit<ComponentProps<\"span\">, \"children\"> & {\n  /**\n   * An extension (`\"pdf\"`, `\".docx\"`), a family (`\"image\"`, `\"folder\"`) or\n   * `\"empty\"`. Unknown extensions render as a neutral file with their label.\n   * Use `getFileType` to resolve a file name or MIME type.\n   */\n  type?: FileTypeName | (string & {});\n  /** `mono` draws every type in the theme's gray instead of its own color. */\n  variant?: FileTypeVariant;\n  /** A preset box size. A Tailwind `size-*` class in `className` overrides it. */\n  size?: VariantProps<typeof fileTypeVariants>[\"size\"];\n  /** Replaces the extension label, for a version tag or a document code. */\n  label?: ReactNode;\n  /**\n   * Shows the family glyph instead of the label. A family `type` (`\"image\"`)\n   * or a type with no label shows its glyph without it.\n   */\n  glyph?: boolean;\n  /**\n   * A custom icon drawn on the page in place of the glyph and the label, in\n   * the glyph's color: a lock on a protected file, a link on a shortcut.\n   */\n  icon?: ReactNode;\n  /**\n   * Classes for the label badge, merged last, so its background, radius,\n   * padding, font and position can all be replaced. The text is a child of\n   * the badge: recolor it with `*:text-*`.\n   */\n  labelClassName?: string;\n};\n\nfunction FileType({\n  type,\n  variant = \"outline\",\n  size = \"default\",\n  label,\n  glyph,\n  icon,\n  labelClassName,\n  className,\n  ...props\n}: FileTypeProps) {\n  const resolved = resolveFileType(type);\n  // `null` hides the label like \"\", where `??` would fall back to it.\n  const text = label !== undefined ? label : resolved.label;\n  const hasText = text !== undefined && text !== null && text !== \"\";\n  // The name spells out a key its label shortens (DOCKERFILE, not DOCK).\n  const spelled = resolved.label ? resolved.type.toUpperCase() : undefined;\n  const typeName = spelled ? `${spelled} file` : CATEGORY_NAMES[resolved.category];\n  // A custom string label is part of what the icon says (\"V3, DOCX file\").\n  const custom = typeof label === \"string\" ? label.toUpperCase() : \"\";\n  const name =\n    custom && custom !== resolved.label && custom !== spelled ? `${label}, ${typeName}` : typeName;\n  const hasIcon = icon !== undefined && icon !== null && icon !== false;\n  const showLabel = hasText && !glyph && !hasIcon;\n  const glyphNode = showLabel || hasIcon ? null : GLYPHS[resolved.category];\n\n  return (\n    <span\n      role=\"img\"\n      aria-label={name}\n      data-slot=\"file-type\"\n      data-type={resolved.type}\n      data-category={resolved.category}\n      data-variant={variant}\n      data-size={size}\n      className={cn(\n        fileTypeVariants({ size }),\n        variant === \"mono\" ? MONO : resolved.color,\n        className,\n      )}\n      {...props}\n    >\n      <svg\n        data-slot=\"file-type-icon\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        className={cn(\n          \"absolute inset-0 size-full overflow-visible [stroke-width:var(--file-type-stroke,max(1px,2.5cqw))] [--file-type-outline:var(--file-type-border,color-mix(in_oklab,var(--muted-foreground)_40%,transparent))] @max-[21px]:[--file-type-outline:var(--file-type-border,color-mix(in_oklab,var(--muted-foreground)_65%,transparent))]\",\n          \"[--file-type-round:inline]\",\n          \"[--file-type-square:none]\",\n        )}\n      >\n        <path {...STROKE} d={PAGE_PATH} className={cn(PAGE_CLASS[variant], ROUND)} />\n        <path\n          {...STROKE}\n          d={PAGE_PATH_SQUARE}\n          strokeLinejoin=\"miter\"\n          className={cn(PAGE_CLASS[variant], SQUARE)}\n        />\n        {variant === \"solid\" ? (\n          <>\n            <path d={FLAP_PATH} className={cn(\"fill-white/30 stroke-none\", ROUND)} />\n            <path d={FLAP_PATH_SQUARE} className={cn(\"fill-white/30 stroke-none\", SQUARE)} />\n          </>\n        ) : null}\n        <path {...STROKE} d={FOLD_PATH} className={cn(FOLD_CLASS[variant], ROUND)} />\n        <path\n          {...STROKE}\n          d={FOLD_PATH_SQUARE}\n          strokeLinejoin=\"miter\"\n          className={cn(FOLD_CLASS[variant], SQUARE)}\n        />\n        {glyphNode ? (\n          <g\n            data-slot=\"file-type-glyph\"\n            stroke=\"currentColor\"\n            className={cn(\n              \"origin-[12px_15px] scale-75 @max-[21px]:scale-100\",\n              GLYPH_CLASS[variant],\n            )}\n          >\n            {glyphNode}\n          </g>\n        ) : null}\n      </svg>\n      {hasIcon ? (\n        <span\n          data-slot=\"file-type-custom-icon\"\n          aria-hidden=\"true\"\n          className={cn(\n            \"absolute top-[62.5%] left-1/2 flex size-[37.5cqw] -translate-1/2 items-center justify-center @max-[21px]:size-[50cqw] [&>svg]:size-full\",\n            GLYPH_CLASS[variant],\n          )}\n        >\n          {icon}\n        </span>\n      ) : null}\n      {showLabel ? (\n        <span\n          data-slot=\"file-type-label\"\n          style={\n            {\n              \"--file-type-chars\": typeof text === \"string\" ? text.length : 4,\n            } as CSSProperties\n          }\n          className={cn(\n            \"absolute top-(--file-type-label-y) left-[5%] flex h-(--file-type-label-h) max-w-[95%] items-center justify-center overflow-hidden rounded-[var(--file-type-radius,var(--file-type-label-radius,10cqw))] px-[var(--file-type-label-px,7cqw)] font-medium whitespace-nowrap\",\n            \"text-[length:min(24cqw,calc((95cqw-2*var(--file-type-label-px,7cqw))/(var(--file-type-chars)*0.72)))] [--file-type-label-h:35%] [--file-type-label-y:45%]\",\n            \"supports-[margin:round(1cap,1px)]:mt-[calc(round(nearest,calc(var(--file-type-label-y)+(var(--file-type-label-h)+1cap)/2),1px)-var(--file-type-label-y)-(var(--file-type-label-h)+1cap)/2)]\",\n            \"@max-[21px]:px-[4cqw] @max-[21px]:text-[length:min(32.5cqw,5.8px,calc(87cqw/(var(--file-type-chars)*0.88)))] @max-[21px]:font-semibold @max-[21px]:[--file-type-label-h:45%] @max-[21px]:[--file-type-label-y:40%]\",\n            \"[--file-type-label-radius:10cqw]\",\n            \"[--file-type-label-px:7cqw]\",\n            LABEL_CLASS[variant],\n            labelClassName,\n          )}\n        >\n          <span\n            className={cn(\n              \"max-w-[min(95cqw,calc(100%+2*max(0px,var(--file-type-label-px,7cqw)-7cqw)))] shrink-0 truncate py-[0.25em] [text-box:trim-both_cap_alphabetic] @max-[21px]:max-w-full\",\n              LABEL_TEXT_CLASS[variant],\n            )}\n          >\n            {text}\n          </span>\n        </span>\n      ) : null}\n    </span>\n  );\n}\n\nexport {\n  FILE_TYPE_CATEGORIES,\n  FILE_TYPE_EXTENSION_NAMES,\n  FileType,\n  type FileTypeCategory,\n  type FileTypeName,\n  type FileTypeProps,\n  type FileTypeVariant,\n  fileTypeVariants,\n  getFileType,\n  getFileTypeCategory,\n};\n",
      "type": "registry:component",
      "target": "components/fma-ds/file-type.tsx"
    }
  ],
  "meta": {
    "status": "stable",
    "source": "third-party",
    "license": "MIT",
    "origin": "reui.io (https://reui.io/docs/components/base/file-type, MIT)",
    "tags": [
      "data-display",
      "file",
      "icon",
      "upload"
    ],
    "contentHash": "5144cf26921c"
  },
  "categories": [
    "data-display"
  ],
  "type": "registry:ui"
}