2.9 KB120 lines
Blame
1import iconMap from "@/lib/file-icon-map.json";
2import { CanopyLogo } from "@/app/components/canopy-logo";
3
4interface FileIconProps {
5 type: "tree" | "file";
6 name?: string;
7 className?: string;
8 size?: number;
9}
10
11function getIconName(type: "tree" | "file", name?: string): string {
12 if (type === "tree") return "folder";
13
14 if (!name) return "file";
15 const lower = name.toLowerCase();
16
17 // Check exact filename first
18 const byName = (iconMap as any).fileNames[lower];
19 if (byName) return byName;
20
21 // Check compound extensions (e.g. "test.ts", "config.js")
22 const parts = lower.split(".");
23 for (let i = 1; i < parts.length; i++) {
24 const compoundExt = parts.slice(i).join(".");
25 const byCompound = (iconMap as any).fileExtensions[compoundExt];
26 if (byCompound) return byCompound;
27 }
28
29 // Check single extension
30 const ext = parts.pop() ?? "";
31 const byExt = (iconMap as any).fileExtensions[ext];
32 if (byExt) return byExt;
33
34 return "file";
35}
36
37function FolderIcon({ size }: { size: number }) {
38 return (
39 <svg
40 width={size}
41 height={size}
42 viewBox="0 0 16 16"
43 fill="none"
44 style={{ flexShrink: 0 }}
45 >
46 <path
47 d="M6.922 3.768l-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232z"
48 stroke="var(--accent)"
49 strokeWidth="0.9"
50 fill="none"
51 />
52 </svg>
53 );
54}
55
56function CanopyFolderIcon({ size }: { size: number }) {
57 const canopySize = Math.max(8, Math.round(size * 0.58));
58 return (
59 <span
60 style={{
61 width: size,
62 height: size,
63 position: "relative",
64 display: "inline-flex",
65 alignItems: "center",
66 justifyContent: "center",
67 flexShrink: 0,
68 }}
69 >
70 <FolderIcon size={size} />
71 <span
72 style={{
73 position: "absolute",
74 right: 0,
75 bottom: 0,
76 lineHeight: 0,
77 }}
78 >
79 <CanopyLogo size={canopySize} />
80 </span>
81 </span>
82 );
83}
84
85export function FileIcon({ type, name, className = "", size = 16 }: FileIconProps) {
86 if (type === "tree") {
87 const isCanopyDir = (name ?? "").toLowerCase() === ".canopy";
88 return (
89 <span className={className}>
90 {isCanopyDir ? <CanopyFolderIcon size={size} /> : <FolderIcon size={size} />}
91 </span>
92 );
93 }
94
95 const iconName = getIconName(type, name);
96
97 return (
98 <span
99 className={className}
100 style={{
101 display: "inline-block",
102 width: size,
103 height: size,
104 flexShrink: 0,
105 backgroundColor: "var(--text-faint)",
106 maskImage: `url(/file-icons/${iconName}.svg)`,
107 maskSize: "contain",
108 maskRepeat: "no-repeat",
109 maskPosition: "center",
110 WebkitMaskImage: `url(/file-icons/${iconName}.svg)`,
111 WebkitMaskSize: "contain",
112 WebkitMaskRepeat: "no-repeat",
113 WebkitMaskPosition: "center",
114 }}
115 role="img"
116 aria-hidden="true"
117 />
118 );
119}
120