@mrzr/react-svg-icons 0.1.0 → 0.1.1

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/CHANGELOG.md CHANGED
@@ -1,10 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1
4
+
5
+ - **`Icon` keeps the stroke width of the SVG.** It no longer passes `strokeWidth`, so an icon loaded by name is drawn with the width it has when imported directly (`logout` at 1.5, not 2). `defaultStrokeWidth` now only applies to an SVG that has no `stroke-width`. If you relied on `Icon` forcing the width, pass `strokeWidth` to it.
6
+ - **The full package name** is in the output: the banner of the generated files and the start of every message say `@mrzr/react-svg-icons`, and the docs write `npx @mrzr/react-svg-icons`. A bare `npx react-svg-icons` would run a different package of that name from npm. Files with the old banner are still recognised as generated.
7
+ - The README and the wiki say what the tool does, and no longer mention Figma or other frameworks.
8
+ - A test no longer depends on whether `tsc` prints colours, which failed `npm publish` in a terminal that forces them.
9
+
3
10
  ## 0.1.0
4
11
 
5
12
  First release.
6
13
 
7
- - Turns a folder of SVGs (for example exported from Figma) into React components: SVGO-optimised, typed, with `size`, `color`, `stroke`, `fill`, `strokeWidth`, `opacity`, `title`, a forwarded `ref`, and `colors`, `opacities` and `strokeWidths` lists with one entry per colour of the drawing.
14
+ - Turns a folder of SVGs into React components: SVGO-optimised, typed, with `size`, `color`, `stroke`, `fill`, `strokeWidth`, `opacity`, `title`, a forwarded `ref`, and `colors`, `opacities` and `strokeWidths` lists with one entry per colour of the drawing.
8
15
  - Writes a typed registry (`variant.name`, one lazy import per icon) and a lazy `Icon` component. A nested folder is part of the name, and two icons with one name stop the run.
9
16
  - The config is data, never a script: YAML (`react-svg-icons.config.yaml`, `.yml`), JSON (`react-svg-icons.config.json`) or an rc file (`.react-svg-iconsrc` and its `.yaml`, `.yml` and `.json` variants). Options: `input`, `outDir`, `removeWords`, `defaultSize`, `defaultStrokeWidth`, `format`. An unknown option is an error.
10
17
  - Formats the output with the project's own prettier and config (`format: prettier`, or `false` for raw text).
package/README.md CHANGED
@@ -1,12 +1,6 @@
1
1
  # @mrzr/react-svg-icons
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@mrzr/react-svg-icons)](https://www.npmjs.com/package/@mrzr/react-svg-icons)
4
- [![types](https://img.shields.io/npm/types/@mrzr/react-svg-icons)](https://www.npmjs.com/package/@mrzr/react-svg-icons)
5
- [![license](https://img.shields.io/npm/l/@mrzr/react-svg-icons)](LICENSE)
6
-
7
- Turn the SVG icons you export from Figma into typed React components. Each icon takes `size`, `color` and a prop per colour in the drawing, and a lazy `Icon` loads any of them by name.
8
-
9
- **[Wiki](https://github.com/mohammadreza-zr/react-svg-icons/wiki)** · [Quick start](https://github.com/mohammadreza-zr/react-svg-icons/wiki/Quick-Start) · [Components](https://github.com/mohammadreza-zr/react-svg-icons/wiki/Components) · [Configuration](https://github.com/mohammadreza-zr/react-svg-icons/wiki/Configuration) · [Changelog](https://github.com/mohammadreza-zr/react-svg-icons/blob/main/CHANGELOG.md)
3
+ Turn a folder of SVG files into typed React components (TSX). Every icon gets props for its size and colours, so you can restyle it without editing the SVG.
10
4
 
11
5
  ```bash
12
6
  npm install --save-dev @mrzr/react-svg-icons
@@ -15,30 +9,14 @@ npx @mrzr/react-svg-icons
15
9
 
16
10
  ---
17
11
 
18
- ## Is this for you?
19
-
20
- **Use it if**
21
-
22
- - Your icons are SVG files exported from Figma, grouped in folders (`bold/`, `linear/`, …)
23
- - You use React, and want an icon to be a component that takes `size` and `color`
24
- - You want two-tone icons whose colours you can set one by one
25
- - You want CI to fail when someone adds an SVG but forgets to regenerate
26
-
27
- **Use something else if not.** It writes React components only: no Vue, Svelte or sprite sheet. For a single icon set from npm, use that set's own package.
28
-
29
- ---
30
-
31
12
  ## Quick start
32
13
 
33
- Put the exported SVGs in a folder (subfolders are variants) and add `react-svg-icons.config.yaml` next to your `package.json`:
14
+ Put your SVG files in a folder (subfolders are variants) and add `react-svg-icons.config.yaml` next to your `package.json`:
34
15
 
35
16
  <!-- verbatim: examples/basic/react-svg-icons.config.yaml -->
36
17
  ```yaml
37
18
  input: icons
38
19
  outDir: generated
39
-
40
- # Words dropped from every icon name.
41
- removeWords: [vuesax, icon]
42
20
  ```
43
21
 
44
22
  ```bash
@@ -46,29 +24,27 @@ npx @mrzr/react-svg-icons # writes the components into generated/
46
24
  npx @mrzr/react-svg-icons --check # writes nothing; exit code 1 when they are out of date
47
25
  ```
48
26
 
27
+ Use an icon by name, loaded on demand, or import it directly:
28
+
49
29
  ```tsx
50
30
  import { Icon } from "./generated";
51
31
  import { Logout } from "./generated/bold";
52
32
 
53
- <Icon name="bold.logout" size={20} /> {/* loaded on demand, the name is typed */}
54
- <Logout size={20} color="crimson" /> {/* imported directly */}
33
+ <Icon name="bold.logout" size={20} />
34
+ <Logout size={20} color="crimson" />
55
35
  ```
56
36
 
57
- The config can also be `.yml`, `.json` or an rc file (`.react-svg-iconsrc`). It is always data, never a script.
37
+ The config can also be `.yml`, `.json` or an rc file (`.react-svg-iconsrc`).
58
38
 
59
39
  ---
60
40
 
61
- ## What it does
62
-
63
- - **One component per SVG**, optimised with SVGO, with `size`, `color`, `stroke`, `fill`, `strokeWidth`, `opacity`, a `title` for accessibility, and a forwarded `ref`
64
- - **A prop per colour**: `colors={["red", "blue"]}` sets the first and the second colour of the drawing, and `opacities` and `strokeWidths` do the same
65
- - **A lazy `Icon`** and a typed registry: `name="bold.logout"`, one chunk per icon
66
- - **Prettier-formatted output** with your project's prettier and its config, so `--check` passes after your own formatting
67
- - **`--check`** and cleanup: a generated file for an SVG you removed is deleted, and nothing else is ever deleted
68
-
69
- ## What it does not do
41
+ ## What you get
70
42
 
71
- No Vue, Svelte or sprite output. Gradients, masks and ids are kept as they are, so two icons that use the same id can clash on one page. The `style` attribute of an SVG is dropped, and `stroke-width` becomes one prop for the whole icon. The [Home](https://github.com/mohammadreza-zr/react-svg-icons/wiki) page lists every limit.
43
+ - **A typed component per SVG**, optimised with SVGO, with a forwarded `ref`
44
+ - **Props to customise it**: `size`, `color`, `stroke`, `fill`, `strokeWidth`, `opacity`, `title`, and `colors`, `opacities`, `strokeWidths` lists that set the colours of the drawing one by one
45
+ - **A typed `name`** for `Icon`: `name="bold.logut"` is a type error, and each icon loads as its own chunk
46
+ - **Output formatted by your own prettier**, so it matches what your formatting would leave
47
+ - **`--check`** for CI, and cleanup of generated files whose SVG you removed. A file the tool did not write is never touched
72
48
 
73
49
  ---
74
50
 
@@ -83,7 +59,7 @@ Everything is in the **[Wiki](https://github.com/mohammadreza-zr/react-svg-icons
83
59
 
84
60
  ## Requirements
85
61
 
86
- Node.js 20 or newer. The generated code needs React (tested with 19) and a bundler-style module resolution (`"moduleResolution": "bundler"` in TypeScript), because its imports have no file extension.
62
+ Node.js 20 or newer. The generated code needs React (tested with 19) and, in TypeScript, `"moduleResolution": "bundler"`, because its imports have no file extension.
87
63
 
88
64
  ## License
89
65
 
package/dist/build.js CHANGED
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { formatFiles } from "./format.js";
4
4
  import { generateComponentCode } from "./component.js";
5
5
  import { resolveConfig } from "./config.js";
6
- import { HEADER, MARKER } from "./header.js";
6
+ import { HEADER, MARKER_PATTERN } from "./header.js";
7
7
  import { cleanIconName, toCamelCase, toKebabCase } from "./names.js";
8
8
  import { iconComponentCode, registryCode } from "./registry.js";
9
9
  function buildTree(dir, basePath = []) {
@@ -34,7 +34,7 @@ function walkTree(node, isRoot, outDir, config, out) {
34
34
  const fileName = `${toKebabCase(componentName)}.tsx`;
35
35
  const stem = fileName.replace(".tsx", "");
36
36
  if (stem === "index" || (isRoot && (stem === "icon" || stem === "registry"))) {
37
- throw new Error(`react-svg-icons: ${file.fullPath} would be written to ${fileName}, a name the generated ${stem} file already uses; rename the SVG`);
37
+ throw new Error(`@mrzr/react-svg-icons: ${file.fullPath} would be written to ${fileName}, a name the generated ${stem} file already uses; rename the SVG`);
38
38
  }
39
39
  const code = generateComponentCode(readFileSync(file.fullPath, "utf8"), componentName, file.fullPath, config);
40
40
  out.push({ path: path.join(currentDir, fileName), content: code });
@@ -53,7 +53,7 @@ function walkTree(node, isRoot, outDir, config, out) {
53
53
  // variant would pull every icon of it into one chunk and defeat the lazy
54
54
  // imports of the registry.
55
55
  out.push({ path: path.join(currentDir, "registry.ts"), content: registryCode(allFiles) });
56
- out.push({ path: path.join(currentDir, "icon.tsx"), content: iconComponentCode(config.defaultSize, config.defaultStrokeWidth) });
56
+ out.push({ path: path.join(currentDir, "icon.tsx"), content: iconComponentCode(config.defaultSize) });
57
57
  index += `export * from "./registry";\nexport * from "./icon";\n`;
58
58
  }
59
59
  else {
@@ -72,16 +72,16 @@ export async function generate(input, cwd = process.cwd()) {
72
72
  const inputDir = path.resolve(cwd, config.input);
73
73
  const outDir = path.resolve(cwd, config.outDir);
74
74
  if (!existsSync(inputDir))
75
- throw new Error(`react-svg-icons: input folder not found: ${inputDir}`);
75
+ throw new Error(`@mrzr/react-svg-icons: input folder not found: ${inputDir}`);
76
76
  if (outDir === inputDir || inputDir.startsWith(outDir + path.sep)) {
77
- throw new Error(`react-svg-icons: outDir (${outDir}) must not hold the input folder (${inputDir})`);
77
+ throw new Error(`@mrzr/react-svg-icons: outDir (${outDir}) must not hold the input folder (${inputDir})`);
78
78
  }
79
79
  const tree = buildTree(inputDir);
80
80
  const files = [];
81
81
  walkTree(tree, true, outDir, config, files);
82
82
  const iconCount = files.filter((f) => f.path.endsWith(".tsx") && !f.path.endsWith(`${path.sep}icon.tsx`)).length;
83
83
  if (iconCount === 0)
84
- throw new Error(`react-svg-icons: no .svg files in ${inputDir}`);
84
+ throw new Error(`@mrzr/react-svg-icons: no .svg files in ${inputDir}`);
85
85
  return { outDir, files: await formatFiles(files, cwd, config.format) };
86
86
  }
87
87
  const lf = (text) => text.replace(/\r\n/g, "\n");
@@ -93,7 +93,7 @@ function existingGenerated(dir) {
93
93
  const full = path.join(dir, entry.name);
94
94
  if (entry.isDirectory())
95
95
  found.push(...existingGenerated(full));
96
- else if (/\.tsx?$/.test(entry.name) && readFileSync(full, "utf8").slice(0, 200).includes(MARKER))
96
+ else if (/\.tsx?$/.test(entry.name) && MARKER_PATTERN.test(readFileSync(full, "utf8").slice(0, 200)))
97
97
  found.push(full);
98
98
  }
99
99
  return found;
package/dist/cli.js CHANGED
@@ -3,10 +3,10 @@ import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { compare, generate, write } from "./build.js";
5
5
  import { findConfig, loadConfig } from "./config.js";
6
- const HELP = `react-svg-icons — SVG icons to typed React components
6
+ const HELP = `@mrzr/react-svg-icons — SVG icons to typed React components
7
7
 
8
8
  Usage:
9
- react-svg-icons [build] [--config <file>] [--check]
9
+ npx @mrzr/react-svg-icons [build] [--config <file>] [--check]
10
10
 
11
11
  Options:
12
12
  --config <file> Config file (default: react-svg-icons.config.yaml|yml|json or .react-svg-iconsrc in the cwd)
@@ -30,11 +30,11 @@ function parse(argv) {
30
30
  else if (arg === "--config") {
31
31
  const value = rest[++i];
32
32
  if (!value)
33
- throw new Error("react-svg-icons: --config needs a file");
33
+ throw new Error("@mrzr/react-svg-icons: --config needs a file");
34
34
  args.config = value;
35
35
  }
36
36
  else
37
- throw new Error(`react-svg-icons: unknown argument "${arg}" (see --help)`);
37
+ throw new Error(`@mrzr/react-svg-icons: unknown argument "${arg}" (see --help)`);
38
38
  }
39
39
  return args;
40
40
  }
package/dist/component.js CHANGED
@@ -41,11 +41,11 @@ export function generateComponentCode(rawSvg, componentName, file, options) {
41
41
  ast = parseSync(optimizeSvg(rawSvg));
42
42
  }
43
43
  catch (error) {
44
- throw new Error(`react-svg-icons: ${file} is not a valid SVG (${error.message})`);
44
+ throw new Error(`@mrzr/react-svg-icons: ${file} is not a valid SVG (${error.message})`);
45
45
  }
46
46
  const svgNode = (ast.name === "svg" ? ast : (ast.children || []).find((n) => n.name === "svg"));
47
47
  if (!svgNode)
48
- throw new Error(`react-svg-icons: no <svg> root in ${file}`);
48
+ throw new Error(`@mrzr/react-svg-icons: no <svg> root in ${file}`);
49
49
  svgNode.attributes = svgNode.attributes || {};
50
50
  const fallbackWidth = Number.parseFloat(svgNode.attributes.width || "24");
51
51
  const fallbackHeight = Number.parseFloat(svgNode.attributes.height || "24");
package/dist/config.d.ts CHANGED
@@ -3,7 +3,7 @@ export interface ReactSvgIconsConfig {
3
3
  input: string;
4
4
  /** Folder the components are written to. */
5
5
  outDir: string;
6
- /** Words dropped from every icon name, in any case: `[vuesax, icon]`. */
6
+ /** Words dropped from every icon name, in any case: `[ic, icon]`. */
7
7
  removeWords?: string[];
8
8
  /** The `size` an icon has when none is given. Default 24. */
9
9
  defaultSize?: number;
package/dist/config.js CHANGED
@@ -8,26 +8,26 @@ export function defineConfig(config) {
8
8
  export function resolveConfig(config) {
9
9
  for (const key of Object.keys(config)) {
10
10
  if (!OPTIONS.includes(key)) {
11
- throw new Error(`react-svg-icons: unknown option "${key}" (options: ${OPTIONS.join(", ")})`);
11
+ throw new Error(`@mrzr/react-svg-icons: unknown option "${key}" (options: ${OPTIONS.join(", ")})`);
12
12
  }
13
13
  }
14
14
  if (!config.input)
15
- throw new Error("react-svg-icons: `input` is required");
15
+ throw new Error("@mrzr/react-svg-icons: `input` is required");
16
16
  if (!config.outDir)
17
- throw new Error("react-svg-icons: `outDir` is required");
17
+ throw new Error("@mrzr/react-svg-icons: `outDir` is required");
18
18
  const number = (name, value, fallback) => {
19
19
  if (value === undefined)
20
20
  return fallback;
21
21
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
22
- throw new Error(`react-svg-icons: \`${name}\` must be a positive number`);
22
+ throw new Error(`@mrzr/react-svg-icons: \`${name}\` must be a positive number`);
23
23
  }
24
24
  return value;
25
25
  };
26
26
  if (config.removeWords !== undefined && !Array.isArray(config.removeWords)) {
27
- throw new Error("react-svg-icons: `removeWords` must be a list of words");
27
+ throw new Error("@mrzr/react-svg-icons: `removeWords` must be a list of words");
28
28
  }
29
29
  if (config.format !== undefined && config.format !== "prettier" && config.format !== false) {
30
- throw new Error('react-svg-icons: `format` must be "prettier" or false');
30
+ throw new Error('@mrzr/react-svg-icons: `format` must be "prettier" or false');
31
31
  }
32
32
  return {
33
33
  input: config.input,
@@ -52,7 +52,7 @@ export function findConfig(cwd, explicit) {
52
52
  if (explicit) {
53
53
  const file = path.resolve(cwd, explicit);
54
54
  if (!existsSync(file))
55
- throw new Error(`react-svg-icons: no config at ${file}`);
55
+ throw new Error(`@mrzr/react-svg-icons: no config at ${file}`);
56
56
  return file;
57
57
  }
58
58
  for (const name of CONFIG_NAMES) {
@@ -60,11 +60,11 @@ export function findConfig(cwd, explicit) {
60
60
  if (existsSync(file))
61
61
  return file;
62
62
  }
63
- throw new Error(`react-svg-icons: no config found; create ${CONFIG_NAMES[0]} (see the README)`);
63
+ throw new Error(`@mrzr/react-svg-icons: no config found; create ${CONFIG_NAMES[0]} (see the README)`);
64
64
  }
65
65
  export async function loadConfig(file) {
66
66
  if (SCRIPT.test(file)) {
67
- throw new Error(`react-svg-icons: ${file} is a script; the config must be YAML or JSON`);
67
+ throw new Error(`@mrzr/react-svg-icons: ${file} is a script; the config must be YAML or JSON`);
68
68
  }
69
69
  const text = readFileSync(file, "utf8").replace(/^/, "");
70
70
  const isJson = file.endsWith(".json");
@@ -73,10 +73,10 @@ export async function loadConfig(file) {
73
73
  parsed = isJson ? JSON.parse(text) : parseYaml(text);
74
74
  }
75
75
  catch (error) {
76
- throw new Error(`react-svg-icons: ${file} is not valid ${isJson ? "JSON" : "YAML"} (${error.message})`);
76
+ throw new Error(`@mrzr/react-svg-icons: ${file} is not valid ${isJson ? "JSON" : "YAML"} (${error.message})`);
77
77
  }
78
78
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
79
- throw new Error(`react-svg-icons: ${file} must hold a mapping of options`);
79
+ throw new Error(`@mrzr/react-svg-icons: ${file} must hold a mapping of options`);
80
80
  }
81
81
  return parsed;
82
82
  }
package/dist/format.js CHANGED
@@ -21,7 +21,7 @@ export async function formatFiles(files, cwd, format) {
21
21
  return files;
22
22
  const prettier = await loadPrettier(cwd);
23
23
  if (!prettier) {
24
- console.warn("react-svg-icons: prettier is not installed, so the output is not formatted. " +
24
+ console.warn("@mrzr/react-svg-icons: prettier is not installed, so the output is not formatted. " +
25
25
  "Install it in the project, or set `format: false` to silence this.");
26
26
  return files;
27
27
  }
@@ -35,7 +35,7 @@ export async function formatFiles(files, cwd, format) {
35
35
  });
36
36
  }
37
37
  catch (error) {
38
- throw new Error(`react-svg-icons: the generated ${file.path} does not parse (${error.message})`);
38
+ throw new Error(`@mrzr/react-svg-icons: the generated ${file.path} does not parse (${error.message})`);
39
39
  }
40
40
  }
41
41
  return out;
package/dist/header.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- export declare const MARKER = "auto-generated by react-svg-icons";
2
- export declare const HEADER = "/* eslint-disable */\n/* This file is auto-generated by react-svg-icons. Do not edit manually. */\n\n";
1
+ export declare const MARKER = "auto-generated by @mrzr/react-svg-icons";
2
+ /** Also the banner of 0.1.0, which did not carry the scope. */
3
+ export declare const MARKER_PATTERN: RegExp;
4
+ export declare const HEADER = "/* eslint-disable */\n/* This file is auto-generated by @mrzr/react-svg-icons. Do not edit manually. */\n\n";
package/dist/header.js CHANGED
@@ -1,2 +1,4 @@
1
- export const MARKER = "auto-generated by react-svg-icons";
1
+ export const MARKER = "auto-generated by @mrzr/react-svg-icons";
2
+ /** Also the banner of 0.1.0, which did not carry the scope. */
3
+ export const MARKER_PATTERN = /auto-generated by (@mrzr\/)?react-svg-icons/;
2
4
  export const HEADER = `/* eslint-disable */\n/* This file is ${MARKER}. Do not edit manually. */\n\n`;
@@ -7,4 +7,4 @@ export interface IconFile {
7
7
  /** `bold.logout`, `a.b.x` for a nested folder; an icon at the top takes the input folder's name. */
8
8
  export declare function registryKey(file: IconFile): string;
9
9
  export declare function registryCode(files: IconFile[]): string;
10
- export declare function iconComponentCode(defaultSize: number, defaultStrokeWidth: number): string;
10
+ export declare function iconComponentCode(defaultSize: number): string;
package/dist/registry.js CHANGED
@@ -11,7 +11,7 @@ export function registryCode(files) {
11
11
  const importPath = "./" + [...file.path, base].join("/");
12
12
  const key = registryKey(file);
13
13
  if (seen.has(key)) {
14
- throw new Error(`react-svg-icons: two icons have the name "${key}": ${seen.get(key)} and ${importPath}`);
14
+ throw new Error(`@mrzr/react-svg-icons: two icons have the name "${key}": ${seen.get(key)} and ${importPath}`);
15
15
  }
16
16
  seen.set(key, importPath);
17
17
  return ` "${key}": () => import("${importPath}").then((m) => m.${file.componentName}),`;
@@ -25,7 +25,7 @@ ${entries.join("\n")}
25
25
  export type IconName = keyof typeof iconRegistry;
26
26
  `;
27
27
  }
28
- export function iconComponentCode(defaultSize, defaultStrokeWidth) {
28
+ export function iconComponentCode(defaultSize) {
29
29
  return `${HEADER}
30
30
  "use client";
31
31
 
@@ -52,7 +52,6 @@ export type IconProps = Omit<
52
52
  const DEFAULT_ICON_PROPS = {
53
53
  size: ${defaultSize},
54
54
  color: "currentColor",
55
- strokeWidth: ${defaultStrokeWidth},
56
55
  };
57
56
 
58
57
  type RegistryLoader = (typeof iconRegistry)[IconName];
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@mrzr/react-svg-icons",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Turn a folder of SVG icons into typed React components, with a prop per colour and a lazy-loaded Icon.",
5
5
  "keywords": [
6
- "figma",
7
6
  "icons",
8
7
  "svg",
9
8
  "react",