{
  "name": "file-icon",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "file-icon.tsx",
      "content": "\"use client\";\r\n\r\nimport { css } from \"@yugnex/core\";\r\n\r\n/**\r\n * How a file changed in a generated changeset.\r\n *\r\n * Lives here rather than in file-tree because file-tree, file-tabs,\r\n * diff-view, and review-gate all speak this vocabulary, and file-icon is the\r\n * one module all of them already depend on — so sharing it costs no extra\r\n * copied file, and nothing has to redeclare it.\r\n */\r\nexport type FileStatus = \"new\" | \"modified\" | \"deleted\" | \"unchanged\";\r\n\r\n/* ------------------------------------------------------------------ *\r\n * Glyph set\r\n *\r\n * Every path below is drawn for this library on a 16x16 grid — geometric,\r\n * single-stroke, and readable at 14px. Deliberately not an icon font or an\r\n * imported set: the whole library ships zero runtime dependencies, and a\r\n * file explorer that needed an icon package would break that for the sake\r\n * of ~40 small shapes.\r\n * ------------------------------------------------------------------ */\r\n\r\ninterface Glyph {\r\n  /** Rendered inside a 16x16 viewBox, stroked with currentColor. */\r\n  path: string;\r\n  /** OKLCH-ish hue angle, 0-360. Lightness/chroma are set per theme. */\r\n  hue: number;\r\n}\r\n\r\nconst G = {\r\n  /* --- Structure & markup --- */\r\n  // Angle brackets — JS/TS source.\r\n  markup: { path: \"M6 4L2 8l4 4M10 4l4 4-4 4\", hue: 15 },\r\n  // Angle bracket + slash — HTML/XML.\r\n  tag: { path: \"M5.5 4.5L2 8l3.5 3.5M10.5 4.5L14 8l-3.5 3.5M9.5 3l-3 10\", hue: 25 },\r\n  // Braces — structured data.\r\n  braces: { path: \"M6 3H5a2 2 0 0 0-2 2v1a2 2 0 0 1-2 2 2 2 0 0 1 2 2v1a2 2 0 0 0 2 2h1M10 3h1a2 2 0 0 1 2 2v1a2 2 0 0 0 2 2 2 2 0 0 0-2 2v1a2 2 0 0 1-2 2h-1\", hue: 90 },\r\n  // Stacked layers — component frameworks.\r\n  layers: { path: \"M8 2l6 3-6 3-6-3 6-3zM2 11l6 3 6-3M2 8l6 3 6-3\", hue: 260 },\r\n  // Nested squares — bundlers/monorepo tooling.\r\n  nested: { path: \"M2.5 2.5h5v5h-5v-5zM8.5 8.5h5v5h-5v-5zM8.5 2.5h5v5h-5v-5z\", hue: 275 },\r\n\r\n  /* --- Styling & media --- */\r\n  // Droplet — stylesheets.\r\n  droplet: { path: \"M8 2.5s4 4.2 4 6.8a4 4 0 0 1-8 0c0-2.6 4-6.8 4-6.8z\", hue: 200 },\r\n  // Wind stroke — utility CSS.\r\n  wind: { path: \"M2 6h7.5a2 2 0 1 0-2-2M2 9.5h9a2 2 0 1 1-2 2M2 12.8h4.5\", hue: 190 },\r\n  // Image frame.\r\n  image: { path: \"M2.5 3.5h11v9h-11v-9zM2.5 10l3-3 3 3 2-2 3 3\", hue: 300 },\r\n  // Bezier node — vector art.\r\n  vector: { path: \"M3 12c0-4.5 3-7.5 7.5-7.5M2 11.5h2v2H2v-2zM11 3.5h2v2h-2v-2z\", hue: 315 },\r\n  // Type specimen — fonts.\r\n  glyphMark: { path: \"M3.5 4.5v-1h9v1M8 3.5v9M6 12.5h4\", hue: 330 },\r\n\r\n  /* --- Prose --- */\r\n  // Text lines — plain prose.\r\n  lines: { path: \"M3 4h10M3 8h10M3 12h6\", hue: 220 },\r\n  // Hash + lines — markdown.\r\n  markdown: { path: \"M2.5 5.5h11M2.5 8h11M2.5 10.5h6M11 3v10M7.5 3v3\", hue: 230 },\r\n  // Bound page — PDF/print.\r\n  bound: { path: \"M4 2.5h8v11H4v-11zM6 2.5v11M8 5.5h2.5M8 8h2.5\", hue: 355 },\r\n\r\n  /* --- Execution --- */\r\n  // Terminal chevron — shell.\r\n  terminal: { path: \"M3 5l3 3-3 3M8.5 11H13\", hue: 145 },\r\n  // Play triangle — tasks/scripts.\r\n  play: { path: \"M5 3.5l8 4.5-8 4.5v-9z\", hue: 165 },\r\n  // Container blocks — Docker.\r\n  container: { path: \"M2 9h12v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V9zM4 6.5h2.5V9H4zM7 6.5h2.5V9H7zM7 4h2.5v2.5H7z\", hue: 210 },\r\n  // Gear — tooling config.\r\n  gear: { path: \"M8 6a2 2 0 1 0 0 4 2 2 0 0 0 0-4zM8 2v1.6M8 12.4V14M14 8h-1.6M3.6 8H2M12.2 3.8l-1.1 1.1M4.9 11.1l-1.1 1.1M12.2 12.2l-1.1-1.1M4.9 4.9L3.8 3.8\", hue: 250 },\r\n  // Hexagon — runtime version pins.\r\n  hexagon: { path: \"M8 2l5 3v6l-5 3-5-3V5l5-3z\", hue: 135 },\r\n\r\n  /* --- Data --- */\r\n  // Cylinder — databases.\r\n  cylinder: { path: \"M8 2c3 0 5 .9 5 2s-2 2-5 2-5-.9-5-2 2-2 5-2zM3 4v8c0 1.1 2 2 5 2s5-.9 5-2V4\", hue: 30 },\r\n  // Grid — spreadsheets.\r\n  grid: { path: \"M2.5 3.5h11v9h-11v-9zM2.5 6.5h11M2.5 9.5h11M6 3.5v9M10 3.5v9\", hue: 110 },\r\n  // Linked records — schema/ORM.\r\n  schema: { path: \"M2.5 3h5v3h-5V3zM8.5 10h5v3h-5v-3zM5 6v2.5a1.5 1.5 0 0 0 1.5 1.5h2\", hue: 120 },\r\n\r\n  /* --- Security --- */\r\n  // Key — env and secrets.\r\n  key: { path: \"M10.5 3a2.5 2.5 0 1 1-2.2 3.7L3 12v1.5h2V12h1.5v-1.5H8l.3-.3A2.5 2.5 0 1 1 10.5 3z\", hue: 45 },\r\n  // Lock — lockfiles.\r\n  lock: { path: \"M4.5 7V5.5a3.5 3.5 0 0 1 7 0V7M3.5 7h9v6h-9V7z\", hue: 0 },\r\n  // Shield — certificates.\r\n  shield: { path: \"M8 2l5 2v4.5c0 2.6-2 4.4-5 5.5-3-1.1-5-2.9-5-5.5V4l5-2z\", hue: 10 },\r\n\r\n  /* --- Version control --- */\r\n  // Fork — git.\r\n  fork: { path: \"M4.5 3.5v3a2 2 0 0 0 2 2h3a2 2 0 0 1 2 2v1M4.5 3.5a1.2 1.2 0 1 0 0-.1zM11.5 12.5a1.2 1.2 0 1 0 0-.1zM4.5 12.5a1.2 1.2 0 1 0 0-.1zM4.5 8v3.3\", hue: 20 },\r\n  // Crossed-out eye — ignore files.\r\n  ignore: { path: \"M2 8s2.5-3.5 6-3.5S14 8 14 8s-2.5 3.5-6 3.5S2 8 2 8zM8 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM3 13L13 3\", hue: 35 },\r\n\r\n  /* --- Per-language marks --- */\r\n  // Coiled body — Python.\r\n  python: { path: \"M6 3.5h4a2 2 0 0 1 2 2V8H4v2.5a2 2 0 0 0 2 2h4M4 8V5.5a2 2 0 0 1 2-2M12 8v2.5a2 2 0 0 1-2 2M6.5 5.5h.01M9.5 10.5h.01\", hue: 65 },\r\n  // Faceted gem — Ruby.\r\n  ruby: { path: \"M4 2.5h8l2 3.5-6 7.5-6-7.5 2-3.5zM2 6h12M6 2.5L4.5 6 8 13.5 11.5 6 10 2.5\", hue: 5 },\r\n  // Speed lines + circle — Go.\r\n  go: { path: \"M2 6h3.5M1.5 8.5H5M8.5 4.5a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM11 8.5h2.5\", hue: 195 },\r\n  // Gear-cog ring — Rust.\r\n  rust: { path: \"M8 3.5a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9zM8 6.5h2a1.25 1.25 0 0 1 0 2.5H8v-2.5zM8 9v3M8 1.8v1.7M8 12.5v1.7\", hue: 40 },\r\n  // Steam cup — Java.\r\n  java: { path: \"M3.5 7h7v3.5a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2V7zM10.5 8h1a1.5 1.5 0 0 1 0 3h-1M6 4.5c0-1 1-1 1-2M8.5 4.5c0-1 1-1 1-2\", hue: 25 },\r\n  // Elephant curve — PHP.\r\n  php: { path: \"M2 8c0-2.2 2.7-4 6-4s6 1.8 6 4-2.7 4-6 4H5l-.8 2M5.5 7h1.2a1 1 0 0 1 0 2H5.5V7zM9.5 7h1.2a1 1 0 0 1 0 2H9.5V7z\", hue: 265 },\r\n  // Bracket pair — C-family.\r\n  cfamily: { path: \"M6 3.5H4.5a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1H6M10 3.5h1.5a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H10M8 6.5v3M6.5 8h3\", hue: 240 },\r\n  // Swift bird stroke.\r\n  swift: { path: \"M3 3.5c4 3 7 5.5 9 9.5C9 12 5.5 10 3 7.5M11 3.5c1.5 2.5 2 5.5 1 8\", hue: 30 },\r\n  // Dart arrow.\r\n  dart: { path: \"M13 3l-8 3.5L3 11l4.5-2L11 13l2-10zM5 6.5L7.5 9\", hue: 205 },\r\n\r\n  /* --- Directories --- */\r\n  // Closed folder.\r\n  folder: { path: \"M2 4.5a1 1 0 0 1 1-1h3l1.5 1.5H13a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-7.5z\", hue: 220 },\r\n  // Open folder — leaning front face.\r\n  folderOpen: { path: \"M2 4.5a1 1 0 0 1 1-1h3l1.5 1.5H13a1 1 0 0 1 1 1V8M2 4.5V12a1 1 0 0 0 1 1h10l2-6H4.2a1 1 0 0 0-.95.68L2 12\", hue: 215 },\r\n\r\n  /* --- Fallback --- */\r\n  // Plain page.\r\n  page: { path: \"M4 2.5h5l3 3v8H4v-11zM9 2.5V6h3\", hue: 245 },\r\n} satisfies Record<string, Glyph>;\r\n\r\n/** Exact filename matches take priority over extensions. */\r\nconst BY_FILENAME: Record<string, Glyph> = {\r\n  dockerfile: G.container,\r\n  \"docker-compose.yml\": G.container,\r\n  \"docker-compose.yaml\": G.container,\r\n  \".dockerignore\": G.ignore,\r\n  \".gitignore\": G.ignore,\r\n  \".eslintignore\": G.ignore,\r\n  \".prettierignore\": G.ignore,\r\n  \".gitattributes\": G.fork,\r\n  \".gitmodules\": G.fork,\r\n  \".env\": G.key,\r\n  \".npmrc\": G.gear,\r\n  \".nvmrc\": G.hexagon,\r\n  \".node-version\": G.hexagon,\r\n  \".editorconfig\": G.gear,\r\n  \".eslintrc.json\": G.gear,\r\n  \".prettierrc\": G.gear,\r\n  \"package.json\": G.braces,\r\n  \"package-lock.json\": G.lock,\r\n  \"pnpm-lock.yaml\": G.lock,\r\n  \"yarn.lock\": G.lock,\r\n  \"bun.lockb\": G.lock,\r\n  \"cargo.lock\": G.lock,\r\n  \"tsconfig.json\": G.gear,\r\n  \"jsconfig.json\": G.gear,\r\n  \"readme.md\": G.markdown,\r\n  \"changelog.md\": G.markdown,\r\n  \"license\": G.bound,\r\n  \"makefile\": G.play,\r\n  \"justfile\": G.play,\r\n  \"turbo.json\": G.nested,\r\n  \"pnpm-workspace.yaml\": G.nested,\r\n  \"vite.config.ts\": G.nested,\r\n  \"webpack.config.js\": G.nested,\r\n  \"rollup.config.js\": G.nested,\r\n  \"next.config.mjs\": G.layers,\r\n  \"next.config.js\": G.layers,\r\n  \"tailwind.config.ts\": G.wind,\r\n  \"tailwind.config.js\": G.wind,\r\n  \"schema.prisma\": G.schema,\r\n};\r\n\r\nconst BY_EXTENSION: Record<string, Glyph> = {\r\n  // JS/TS\r\n  ts: G.markup,\r\n  mts: G.markup,\r\n  cts: G.markup,\r\n  js: G.markup,\r\n  mjs: G.markup,\r\n  cjs: G.markup,\r\n  tsx: G.layers,\r\n  jsx: G.layers,\r\n  vue: G.layers,\r\n  svelte: G.layers,\r\n  astro: G.layers,\r\n\r\n  // Structured data & config\r\n  json: G.braces,\r\n  jsonc: G.braces,\r\n  json5: G.braces,\r\n  yaml: G.gear,\r\n  yml: G.gear,\r\n  toml: G.gear,\r\n  ini: G.gear,\r\n  conf: G.gear,\r\n\r\n  // Markup\r\n  html: G.tag,\r\n  htm: G.tag,\r\n  xml: G.tag,\r\n  ejs: G.tag,\r\n  hbs: G.tag,\r\n\r\n  // Styling\r\n  css: G.droplet,\r\n  scss: G.droplet,\r\n  sass: G.droplet,\r\n  less: G.droplet,\r\n  styl: G.droplet,\r\n\r\n  // Media\r\n  svg: G.vector,\r\n  ai: G.vector,\r\n  png: G.image,\r\n  jpg: G.image,\r\n  jpeg: G.image,\r\n  gif: G.image,\r\n  webp: G.image,\r\n  avif: G.image,\r\n  ico: G.image,\r\n  woff: G.glyphMark,\r\n  woff2: G.glyphMark,\r\n  ttf: G.glyphMark,\r\n  otf: G.glyphMark,\r\n\r\n  // Prose\r\n  md: G.markdown,\r\n  mdx: G.markdown,\r\n  markdown: G.markdown,\r\n  txt: G.lines,\r\n  rst: G.lines,\r\n  pdf: G.bound,\r\n\r\n  // Shell & tasks\r\n  sh: G.terminal,\r\n  bash: G.terminal,\r\n  zsh: G.terminal,\r\n  fish: G.terminal,\r\n  ps1: G.terminal,\r\n  bat: G.play,\r\n  cmd: G.play,\r\n\r\n  // Data\r\n  sql: G.cylinder,\r\n  db: G.cylinder,\r\n  sqlite: G.cylinder,\r\n  prisma: G.schema,\r\n  graphql: G.schema,\r\n  gql: G.schema,\r\n  csv: G.grid,\r\n  tsv: G.grid,\r\n  xlsx: G.grid,\r\n  xls: G.grid,\r\n\r\n  // Security\r\n  env: G.key,\r\n  pem: G.key,\r\n  key: G.key,\r\n  cert: G.shield,\r\n  crt: G.shield,\r\n  lock: G.lock,\r\n\r\n  // Languages\r\n  py: G.python,\r\n  pyi: G.python,\r\n  rb: G.ruby,\r\n  gemspec: G.ruby,\r\n  go: G.go,\r\n  rs: G.rust,\r\n  java: G.java,\r\n  kt: G.java,\r\n  kts: G.java,\r\n  php: G.php,\r\n  c: G.cfamily,\r\n  h: G.cfamily,\r\n  cpp: G.cfamily,\r\n  cc: G.cfamily,\r\n  hpp: G.cfamily,\r\n  cs: G.cfamily,\r\n  swift: G.swift,\r\n  dart: G.dart,\r\n};\r\n\r\n/** Resolves the glyph a path maps to. Exported so file-tree/file-tabs can pre-resolve. */\r\nexport function glyphForFilename(filename: string): Glyph {\r\n  const base = (filename.split(\"/\").pop() ?? filename).toLowerCase();\r\n  const exact = BY_FILENAME[base];\r\n  if (exact) return exact;\r\n\r\n  // `.env.local`, `.env.production` — the meaningful part is the prefix.\r\n  if (base.startsWith(\".env\")) return G.key;\r\n  if (base.startsWith(\"dockerfile\")) return G.container;\r\n\r\n  const ext = base.includes(\".\") ? (base.split(\".\").pop() as string) : \"\";\r\n  return BY_EXTENSION[ext] ?? G.page;\r\n}\r\n\r\n/**\r\n * Hue is derived from the glyph, so every `.ts` file is the same colour\r\n * everywhere in the UI without callers having to pass one.\r\n */\r\nexport function hueForFilename(filename: string): number {\r\n  return glyphForFilename(filename).hue;\r\n}\r\n\r\nconst iconClass = css({\r\n  display: \"inline-block\",\r\n  flexShrink: 0,\r\n  verticalAlign: \"-0.125em\",\r\n});\r\n\r\nexport interface FileIconProps {\r\n  filename: string;\r\n  /** Pixel size of the square icon. */\r\n  size?: number;\r\n  /** Render in the surrounding text colour instead of the per-type hue. */\r\n  muted?: boolean;\r\n  /** Render a folder glyph instead of resolving `filename` to a file type. */\r\n  variant?: \"file\" | \"folder\" | \"folder-open\";\r\n  className?: string;\r\n}\r\n\r\n/** A file-type glyph, coloured by type. Pairs with file-tree and file-tabs. */\r\nexport function FileIcon({ filename, size = 16, muted = false, variant = \"file\", className }: FileIconProps) {\r\n  const glyph =\r\n    variant === \"folder\" ? G.folder : variant === \"folder-open\" ? G.folderOpen : glyphForFilename(filename);\r\n  // Chroma/lightness chosen to stay legible on both the light and dark\r\n  // surfaces without needing a second palette: mid-lightness, modest chroma.\r\n  const color = muted ? \"currentColor\" : `oklch(0.62 0.15 ${glyph.hue})`;\r\n\r\n  return (\r\n    <svg\r\n      className={className ? `${iconClass} ${className}` : iconClass}\r\n      width={size}\r\n      height={size}\r\n      viewBox=\"0 0 16 16\"\r\n      fill=\"none\"\r\n      aria-hidden=\"true\"\r\n      focusable=\"false\"\r\n    >\r\n      <path\r\n        d={glyph.path}\r\n        stroke={color}\r\n        fill=\"none\"\r\n        strokeWidth=\"1.25\"\r\n        strokeLinecap=\"round\"\r\n        strokeLinejoin=\"round\"\r\n      />\r\n    </svg>\r\n  );\r\n}\r\n",
      "type": "registry:component"
    }
  ]
}