Skip to content

Commit f78415c

Browse files
committed
add support for tabler icons
1 parent 1a55df4 commit f78415c

File tree

6,859 files changed

+100917
-31043
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

6,859 files changed

+100917
-31043
lines changed

icon-sprite/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
"@babel/preset-react": "^7.28.5",
5454
"@babel/preset-typescript": "^7.28.5",
5555
"@babel/traverse": "^7.28.5",
56+
"@tabler/icons": "^3.35.0",
57+
"@tabler/icons-react": "^3.35.0",
5658
"lucide-react": "^0.555.0",
5759
"lucide-static": "^0.555.0",
5860
"svgstore": "^3.0.1"
Lines changed: 185 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,123 @@
11
#!/usr/bin/env node
2+
/**
3+
* Build SVG sprite from used icons (Lucide + Tabler + custom)
4+
*/
25
import fs from "fs";
36
import path from "path";
47
import { createRequire } from "module";
58
import svgstore from "svgstore";
69
import { ICONS } from "./used-icons.js";
710
import { loadConfig } from "../dist/loadConfig.js";
8-
import { componentNameToStaticId } from "../dist/utils.js";
911

1012
const require = createRequire(import.meta.url);
1113

12-
// Load user config (merged with defaults)
14+
// Load user config
1315
const { SPRITE_PATH, CUSTOM_SVG_DIR, OUTPUT_DIR } = await loadConfig();
1416

15-
// 1️⃣ Resolve lucide-static icons
16-
const sample = require.resolve("../../../lucide-static/icons/mail.svg");
17-
const iconsDir = path.dirname(sample);
17+
// ============================================================================
18+
// Icon pack directories
19+
// ============================================================================
20+
21+
function resolveLucideIconsDir() {
22+
try {
23+
// First try to find lucide-static package.json
24+
const pkgPath = require.resolve("lucide-static/package.json");
25+
const pkgDir = path.dirname(pkgPath);
26+
const iconsDir = path.join(pkgDir, "icons");
27+
if (fs.existsSync(iconsDir)) {
28+
return iconsDir;
29+
}
30+
} catch {}
31+
32+
// Fallback: check in process.cwd()/node_modules
33+
const fallbackPath = path.join(process.cwd(), "node_modules", "lucide-static", "icons");
34+
if (fs.existsSync(fallbackPath)) {
35+
return fallbackPath;
36+
}
37+
38+
return null;
39+
}
40+
41+
function resolveTablerIconsDir() {
42+
try {
43+
// First try to find @tabler/icons package.json
44+
const pkgPath = require.resolve("@tabler/icons/package.json");
45+
const pkgDir = path.dirname(pkgPath);
46+
const outlineDir = path.join(pkgDir, "icons", "outline");
47+
if (fs.existsSync(outlineDir)) {
48+
return outlineDir;
49+
}
50+
} catch {}
51+
52+
// Fallback: check in process.cwd()/node_modules
53+
const fallbackPath = path.join(process.cwd(), "node_modules", "@tabler", "icons", "icons", "outline");
54+
if (fs.existsSync(fallbackPath)) {
55+
return fallbackPath;
56+
}
57+
58+
return null;
59+
}
60+
61+
const lucideDir = resolveLucideIconsDir();
62+
const tablerDir = resolveTablerIconsDir();
63+
64+
// ============================================================================
65+
// Load component → sprite mapping (generated by gen-wrappers.js)
66+
// ============================================================================
67+
68+
import { fileURLToPath } from "url";
69+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
70+
71+
function loadSpriteMapping() {
72+
const mappingFile = path.join(__dirname, "component-sprite-map.json");
73+
if (fs.existsSync(mappingFile)) {
74+
const content = fs.readFileSync(mappingFile, "utf8");
75+
return JSON.parse(content);
76+
}
77+
console.warn("⚠️ component-sprite-map.json not found. Run `npm run build` first.");
78+
return {};
79+
}
80+
81+
const spriteMapping = loadSpriteMapping();
82+
83+
function componentNameToSpriteId(name) {
84+
// Use the mapping from gen-wrappers.js (source of truth)
85+
if (spriteMapping[name]) {
86+
return spriteMapping[name];
87+
}
88+
89+
// Fallback for custom icons or unknown components
90+
console.warn(`⚠️ No mapping found for ${name}, using fallback conversion`);
91+
const kebab = name
92+
.replace(/([a-z])([A-Z])/g, "$1-$2")
93+
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
94+
.replace(/([a-zA-Z])(\d)/g, "$1-$2")
95+
.replace(/(\d)([a-zA-Z])/g, "$1-$2")
96+
.toLowerCase();
97+
return { pack: "custom", spriteId: kebab, svgFile: `${kebab}.svg` };
98+
}
99+
100+
// ============================================================================
101+
// SVG Processing: Replace hardcoded values with CSS variables
102+
// ============================================================================
103+
104+
function processSvgForCssVars(svgContent) {
105+
// Only handle stroke-width with CSS variable (most useful, predictable)
106+
// stroke="currentColor" already works via CSS color property
107+
// fill is too complex - leave it alone
108+
109+
let processed = svgContent.replace(
110+
/stroke-width="([^"]+)"/g,
111+
(match, value) => `stroke-width="var(--icon-stroke-width, ${value})"`
112+
);
113+
114+
return processed;
115+
}
116+
117+
// ============================================================================
118+
// Build sprite
119+
// ============================================================================
18120

19-
// 2️⃣ Gather your needed Lucide IDs
20-
const needed = new Set(ICONS.map(componentNameToStaticId));
21121
const store = svgstore({
22122
copyAttrs: ["viewBox", "fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin", "style", "size"],
23123
svgAttrs: {
@@ -26,38 +126,91 @@ const store = svgstore({
26126
focusable: "false",
27127
},
28128
});
129+
29130
const found = new Set();
131+
const missing = [];
132+
133+
// Map component names to sprite info
134+
const neededIcons = ICONS.map((name) => ({
135+
name,
136+
...componentNameToSpriteId(name),
137+
}));
30138

31-
// 3️⃣ Add only the Lucide icons you actually use
32-
for (const file of fs.readdirSync(iconsDir)) {
33-
if (!file.endsWith(".svg")) continue;
34-
const id = file.slice(0, -4);
35-
if (!needed.has(id)) continue;
36-
found.add(id);
37-
store.add(id, fs.readFileSync(path.join(iconsDir, file), "utf8"));
139+
// Add Lucide icons
140+
if (lucideDir) {
141+
const lucideFiles = new Set(fs.readdirSync(lucideDir).filter((f) => f.endsWith(".svg")));
142+
143+
for (const icon of neededIcons) {
144+
if (icon.pack !== "lucide") continue;
145+
146+
if (lucideFiles.has(icon.svgFile)) {
147+
const svg = fs.readFileSync(path.join(lucideDir, icon.svgFile), "utf8");
148+
store.add(icon.spriteId, processSvgForCssVars(svg));
149+
found.add(icon.name);
150+
} else {
151+
// Try case-insensitive match
152+
const match = [...lucideFiles].find((f) => f.toLowerCase() === icon.svgFile.toLowerCase());
153+
if (match) {
154+
const svg = fs.readFileSync(path.join(lucideDir, match), "utf8");
155+
store.add(icon.spriteId, processSvgForCssVars(svg));
156+
found.add(icon.name);
157+
}
158+
}
159+
}
38160
}
39161

40-
// 4️⃣ Optionally include *all* SVGs from your custom folder
162+
// Add Tabler icons
163+
if (tablerDir) {
164+
const tablerFiles = new Set(fs.readdirSync(tablerDir).filter((f) => f.endsWith(".svg")));
165+
166+
for (const icon of neededIcons) {
167+
if (icon.pack !== "tabler") continue;
168+
169+
if (tablerFiles.has(icon.svgFile)) {
170+
const svg = fs.readFileSync(path.join(tablerDir, icon.svgFile), "utf8");
171+
store.add(icon.spriteId, processSvgForCssVars(svg));
172+
found.add(icon.name);
173+
} else {
174+
// Try case-insensitive match
175+
const match = [...tablerFiles].find((f) => f.toLowerCase() === icon.svgFile.toLowerCase());
176+
if (match) {
177+
const svg = fs.readFileSync(path.join(tablerDir, match), "utf8");
178+
store.add(icon.spriteId, processSvgForCssVars(svg));
179+
found.add(icon.name);
180+
}
181+
}
182+
}
183+
}
184+
185+
// Add custom icons
41186
const customDir = path.resolve(process.cwd(), OUTPUT_DIR, CUSTOM_SVG_DIR);
42187
if (fs.existsSync(customDir)) {
43188
for (const file of fs.readdirSync(customDir)) {
44189
if (!file.endsWith(".svg")) continue;
45-
const id = file.slice(0, -4); // "my-icon"
190+
const id = file.slice(0, -4);
46191
const svg = fs.readFileSync(path.join(customDir, file), "utf8");
47-
store.add(id, svg); // <symbol id="my-icon">…
48-
// Mark custom icons as found so they don't appear as missing
192+
store.add(id, processSvgForCssVars(svg));
49193
found.add(id);
50194
console.log(`🔧 Added custom icon: ${id}`);
51195
}
52196
}
53197

54-
// 5️⃣ Error on any missing Lucide icon
55-
const missing = [...needed].filter((id) => !found.has(id));
56-
if (missing.length) {
57-
throw new Error(`❌ Missing icons: ${missing.join(", ")}`);
198+
// Check for missing icons
199+
for (const icon of neededIcons) {
200+
if (!found.has(icon.name)) {
201+
missing.push(`${icon.name} (${icon.pack}: ${icon.svgFile})`);
202+
}
58203
}
59204

60-
// 6️⃣ Write a fresh sprite (inline: true drops xml/doctype)
205+
if (missing.length > 0) {
206+
console.warn(`⚠️ Missing ${missing.length} icons:`);
207+
missing.slice(0, 10).forEach((m) => console.warn(` - ${m}`));
208+
if (missing.length > 10) {
209+
console.warn(` ... and ${missing.length - 10} more`);
210+
}
211+
}
212+
213+
// Write sprite
61214
const sprite = store.toString({ inline: true });
62215
const outDir = path.join(process.cwd(), OUTPUT_DIR);
63216
const outFile = path.join(outDir, SPRITE_PATH);
@@ -66,4 +219,12 @@ fs.mkdirSync(outDir, { recursive: true });
66219
if (fs.existsSync(outFile)) fs.unlinkSync(outFile);
67220
fs.writeFileSync(outFile, sprite, "utf8");
68221

69-
console.log(`✅ Built ${SPRITE_PATH} with ${found.size} Lucide + custom icons.`);
222+
// Summary
223+
const lucideCount = neededIcons.filter((i) => i.pack === "lucide" && found.has(i.name)).length;
224+
const tablerCount = neededIcons.filter((i) => i.pack === "tabler" && found.has(i.name)).length;
225+
console.log(`✅ Built ${SPRITE_PATH} with ${found.size} icons`);
226+
console.log(` 📦 Lucide: ${lucideCount}`);
227+
console.log(` 📦 Tabler: ${tablerCount}`);
228+
if (found.size - lucideCount - tablerCount > 0) {
229+
console.log(` 📦 Custom: ${found.size - lucideCount - tablerCount}`);
230+
}

0 commit comments

Comments
 (0)