@taterlaxmi/react-icon-library 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/cli.cjs +7 -2
- package/dist/cli.js +174 -4
- package/dist/index.cjs +28 -5
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +194 -5
- package/package.json +2 -2
- package/dist/chunk-7UCNUTGX.js +0 -178
- package/dist/chunk-7UCNUTGX.js.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -41,10 +41,16 @@ npm install @taterlaxmi/react-icon-library
|
|
|
41
41
|
Import icons directly from the package root. You do not need to copy an image or run the generator in the consuming app:
|
|
42
42
|
|
|
43
43
|
```tsx
|
|
44
|
-
import { Kiwi } from '@taterlaxmi/react-icon-library';
|
|
44
|
+
import { Drinkprime, Kiwi, Zepto } from '@taterlaxmi/react-icon-library';
|
|
45
45
|
|
|
46
46
|
export function Brand() {
|
|
47
|
-
|
|
47
|
+
return (
|
|
48
|
+
<div>
|
|
49
|
+
<Kiwi title="Kiwi" width={32} height={32} />
|
|
50
|
+
<Drinkprime title="Drinkprime" width={32} height={32} />
|
|
51
|
+
<Zepto title="Zepto" width={32} height={32} />
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
48
54
|
}
|
|
49
55
|
```
|
|
50
56
|
|
package/dist/cli.cjs
CHANGED
|
@@ -33,9 +33,15 @@ var import_node_crypto = require("crypto");
|
|
|
33
33
|
var import_image_size = require("image-size");
|
|
34
34
|
var import_core = require("@svgr/core");
|
|
35
35
|
|
|
36
|
-
// src/icons/
|
|
36
|
+
// src/icons/Drinkprime.tsx
|
|
37
37
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
38
38
|
|
|
39
|
+
// src/icons/Kiwi.tsx
|
|
40
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
41
|
+
|
|
42
|
+
// src/icons/Zepto.tsx
|
|
43
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
44
|
+
|
|
39
45
|
// src/index.ts
|
|
40
46
|
var RASTER_MIME_TYPES = {
|
|
41
47
|
".png": "image/png",
|
|
@@ -210,4 +216,3 @@ program.name("react-icon-library").description("Generate reusable React icon com
|
|
|
210
216
|
}
|
|
211
217
|
});
|
|
212
218
|
program.parseAsync();
|
|
213
|
-
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,181 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
generateIcons
|
|
4
|
-
} from "./chunk-7UCNUTGX.js";
|
|
5
2
|
|
|
6
3
|
// src/cli.ts
|
|
7
4
|
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
import { access, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "fs/promises";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
import { imageSize } from "image-size";
|
|
11
|
+
import { transform } from "@svgr/core";
|
|
12
|
+
|
|
13
|
+
// src/icons/Drinkprime.tsx
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
|
|
16
|
+
// src/icons/Kiwi.tsx
|
|
17
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
18
|
+
|
|
19
|
+
// src/icons/Zepto.tsx
|
|
20
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
21
|
+
|
|
22
|
+
// src/index.ts
|
|
23
|
+
var RASTER_MIME_TYPES = {
|
|
24
|
+
".png": "image/png",
|
|
25
|
+
".jpg": "image/jpeg",
|
|
26
|
+
".jpeg": "image/jpeg",
|
|
27
|
+
".gif": "image/gif",
|
|
28
|
+
".webp": "image/webp",
|
|
29
|
+
".avif": "image/avif"
|
|
30
|
+
};
|
|
31
|
+
var VECTOR_EXTENSIONS = /* @__PURE__ */ new Set([".svg"]);
|
|
32
|
+
function componentName(filePath) {
|
|
33
|
+
const baseName = path.basename(filePath, path.extname(filePath));
|
|
34
|
+
const words = baseName.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").match(/[A-Za-z0-9]+/g);
|
|
35
|
+
const name = (words ?? []).map((word) => word[0].toUpperCase() + word.slice(1)).join("");
|
|
36
|
+
const safeName = /^[A-Za-z_$]/.test(name) ? name : `Icon${name}`;
|
|
37
|
+
return safeName || "Icon";
|
|
38
|
+
}
|
|
39
|
+
function isWithin(parent, child) {
|
|
40
|
+
const relative = path.relative(parent, child);
|
|
41
|
+
return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
|
|
42
|
+
}
|
|
43
|
+
function errorMessage(error) {
|
|
44
|
+
return error instanceof Error ? error.message : String(error);
|
|
45
|
+
}
|
|
46
|
+
async function findAssets(inputDir, outputDir, recursive) {
|
|
47
|
+
const assets = [];
|
|
48
|
+
async function visit(directory) {
|
|
49
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
50
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const fullPath = path.join(directory, entry.name);
|
|
53
|
+
if (entry.isSymbolicLink()) continue;
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
if (recursive && !(directory === inputDir && isWithin(fullPath, outputDir))) await visit(fullPath);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!entry.isFile()) continue;
|
|
59
|
+
const extension = path.extname(entry.name).toLowerCase();
|
|
60
|
+
if (VECTOR_EXTENSIONS.has(extension) || extension in RASTER_MIME_TYPES) {
|
|
61
|
+
assets.push({ path: fullPath, name: componentName(fullPath), extension });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
await visit(inputDir);
|
|
66
|
+
return assets;
|
|
67
|
+
}
|
|
68
|
+
async function rasterComponent(asset, component) {
|
|
69
|
+
const bytes = await readFile(asset.path);
|
|
70
|
+
let dimensions;
|
|
71
|
+
try {
|
|
72
|
+
dimensions = imageSize(bytes);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new Error(`Cannot read image dimensions for "${asset.path}": ${errorMessage(error)}`);
|
|
75
|
+
}
|
|
76
|
+
if (!dimensions.width || !dimensions.height) throw new Error(`Image "${asset.path}" does not have valid dimensions.`);
|
|
77
|
+
const dataUri = `data:${RASTER_MIME_TYPES[asset.extension]};base64,${bytes.toString("base64")}`;
|
|
78
|
+
return `import type { SVGProps } from 'react';
|
|
79
|
+
|
|
80
|
+
export interface ${component}Props extends SVGProps<SVGSVGElement> {
|
|
81
|
+
title?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const imageSource = ${JSON.stringify(dataUri)};
|
|
85
|
+
|
|
86
|
+
export default function ${component}({ title, width, height, ...props }: ${component}Props) {
|
|
87
|
+
return (
|
|
88
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${dimensions.width} ${dimensions.height}" width={width ?? '1em'} height={height ?? '1em'} role={title ? 'img' : undefined} aria-hidden={title ? undefined : true} {...props}>
|
|
89
|
+
{title ? <title>{title}</title> : null}
|
|
90
|
+
<image href={imageSource} x="0" y="0" width="100%" height="100%" preserveAspectRatio="xMidYMid meet" />
|
|
91
|
+
</svg>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
async function svgComponent(asset, component) {
|
|
97
|
+
const source = await readFile(asset.path, "utf8");
|
|
98
|
+
try {
|
|
99
|
+
return await transform(source, {
|
|
100
|
+
plugins: ["@svgr/plugin-svgo", "@svgr/plugin-jsx"],
|
|
101
|
+
typescript: true,
|
|
102
|
+
jsxRuntime: "automatic",
|
|
103
|
+
icon: true,
|
|
104
|
+
titleProp: true,
|
|
105
|
+
exportType: "default",
|
|
106
|
+
prettier: false
|
|
107
|
+
}, { componentName: component });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throw new Error(`Cannot convert SVG "${asset.path}": ${errorMessage(error)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function generateIcons(options) {
|
|
113
|
+
const inputDir = path.resolve(options.inputDir);
|
|
114
|
+
const outputDir = path.resolve(options.outputDir);
|
|
115
|
+
const recursive = options.recursive ?? true;
|
|
116
|
+
const overwrite = options.overwrite ?? false;
|
|
117
|
+
let inputStat;
|
|
118
|
+
try {
|
|
119
|
+
inputStat = await stat(inputDir);
|
|
120
|
+
} catch {
|
|
121
|
+
throw new Error(`Input directory does not exist: "${inputDir}"`);
|
|
122
|
+
}
|
|
123
|
+
if (!inputStat.isDirectory()) throw new Error(`Input path is not a directory: "${inputDir}"`);
|
|
124
|
+
const assets = await findAssets(inputDir, outputDir, recursive);
|
|
125
|
+
if (assets.length === 0) throw new Error(`No supported image assets found in "${inputDir}".`);
|
|
126
|
+
const names = /* @__PURE__ */ new Map();
|
|
127
|
+
for (const asset of assets) {
|
|
128
|
+
const normalizedName = asset.name.toLocaleLowerCase("en-US");
|
|
129
|
+
const previous = names.get(normalizedName);
|
|
130
|
+
if (previous) {
|
|
131
|
+
throw new Error(`Name collision: "${previous}" and "${asset.path}" both generate the component "${asset.name}". Rename one of the source files.`);
|
|
132
|
+
}
|
|
133
|
+
names.set(normalizedName, asset.path);
|
|
134
|
+
}
|
|
135
|
+
const converted = await Promise.all(assets.map(async (asset) => ({
|
|
136
|
+
asset,
|
|
137
|
+
contents: VECTOR_EXTENSIONS.has(asset.extension) ? await svgComponent(asset, asset.name) : await rasterComponent(asset, asset.name)
|
|
138
|
+
})));
|
|
139
|
+
const indexContents = `${converted.map(({ asset }) => `export { default as ${asset.name} } from './${asset.name}.js';`).join("\n")}
|
|
140
|
+
`;
|
|
141
|
+
const outputs = [
|
|
142
|
+
...converted.map(({ asset, contents }) => ({ filePath: path.join(outputDir, `${asset.name}.tsx`), contents })),
|
|
143
|
+
{ filePath: path.join(outputDir, "index.ts"), contents: indexContents }
|
|
144
|
+
];
|
|
145
|
+
if (!overwrite) {
|
|
146
|
+
const conflicts = [];
|
|
147
|
+
for (const output of outputs) {
|
|
148
|
+
try {
|
|
149
|
+
await access(output.filePath);
|
|
150
|
+
conflicts.push(output.filePath);
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (conflicts.length) throw new Error(`Refusing to overwrite existing generated file(s): ${conflicts.join(", ")}. Use --overwrite to replace them.`);
|
|
155
|
+
}
|
|
156
|
+
await mkdir(outputDir, { recursive: true });
|
|
157
|
+
for (const output of outputs) {
|
|
158
|
+
const temporaryPath = `${output.filePath}.${randomUUID()}.tmp`;
|
|
159
|
+
try {
|
|
160
|
+
await writeFile(temporaryPath, output.contents, "utf8");
|
|
161
|
+
if (overwrite) await rm(output.filePath, { force: true });
|
|
162
|
+
await rename(temporaryPath, output.filePath);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
await rm(temporaryPath, { force: true });
|
|
165
|
+
throw new Error(`Failed to write "${output.filePath}": ${errorMessage(error)}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
icons: converted.map(({ asset }) => ({
|
|
170
|
+
name: asset.name,
|
|
171
|
+
source: path.relative(inputDir, asset.path),
|
|
172
|
+
componentFile: path.join(outputDir, `${asset.name}.tsx`)
|
|
173
|
+
})),
|
|
174
|
+
outputDir
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/cli.ts
|
|
8
179
|
var program = new Command();
|
|
9
180
|
program.name("react-icon-library").description("Generate reusable React icon components from SVG and raster image files.").argument("[input-dir]", "directory containing image assets", "icons").option("-o, --output <directory>", "output directory for generated components", "src/icons").option("--no-recursive", "only read images directly inside the input directory").option("--overwrite", "replace existing generated component files").action(async (inputDir, options) => {
|
|
10
181
|
try {
|
|
@@ -22,4 +193,3 @@ program.name("react-icon-library").description("Generate reusable React icon com
|
|
|
22
193
|
}
|
|
23
194
|
});
|
|
24
195
|
program.parseAsync();
|
|
25
|
-
//# sourceMappingURL=cli.js.map
|