Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 60 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
[![Zero Dependencies](https://img.shields.io/badge/Dependencies-Zero-green.svg)](https://www.npmjs.com/package/svger-cli)

> **The most advanced, zero-dependency SVG to component converter, now with first-class support for 8+ UI frameworks. Enjoy enterprise-grade performance, auto-generated exports, and a unified workflow for your entire design system.**
> **The most advanced, zero-dependency SVG to component converter, now with first-class support for 9+ UI frameworks. Enjoy enterprise-grade performance, auto-generated exports, and a unified workflow for your entire design system.**

## 🆕 **Latest Developer Experience Improvements**

Expand Down Expand Up @@ -55,7 +55,7 @@ svger-cli lock ./icons/critical-logo.svg # Protects during all operations
|-------------|--------------------|------------------|-------------------------|-------------------------|----------|
| **Dependencies** | ✅ **Zero** | ❌ 15+ deps | ❌ 9+ deps | ❌ 7+ deps | ❌ 8+ deps |
| **Auto-Generated Exports** | ✅ **Full Support** | ❌ Manual | ❌ Manual | ❌ Manual | ❌ N/A |
| **Framework Support** | ✅ **8+ Frameworks** | ❌ React only | ❌ Vue only | ❌ Svelte only | ❌ N/A |
| **Framework Support** | ✅ **9+ Frameworks** | ❌ React only | ❌ Vue only | ❌ Svelte only | ❌ N/A |
| **Advanced Props** | ✅ **Full Support** | ❌ Basic | ❌ Basic | ❌ Basic | ❌ N/A |
| **File Protection** | ✅ **Lock System** | ❌ None | ❌ None | ❌ None | ❌ None |
| **Performance** | ✅ **Up to 85% Faster** | Standard | Slow | Standard | Fast (Optimization) |
Expand Down Expand Up @@ -358,6 +358,62 @@ const MyIcon: FunctionalComponent<IconProps> = ({ size = 24, ...props }) => {
export default MyIcon;
```

### **React Native**

Generate React Native components using `react-native-svg` with proper TypeScript types.

```bash
svger-cli build ./my-svgs ./react-native-components --framework react-native
```

**Generated React Native Component (`.tsx`):**
```tsx
import React from "react";
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
import { StyleProp, ViewStyle } from "react-native";

export interface MyIconProps extends SvgProps {
width?: number | string;
height?: number | string;
fill?: string;
stroke?: string;
strokeWidth?: number | string;
style?: StyleProp<ViewStyle>;
}

const MyIcon = React.forwardRef<React.ComponentRef<typeof Svg>, MyIconProps>(
({ width = 24, height = 24, fill = "currentColor", stroke, strokeWidth, style, ...props }, ref) => {
return (
<Svg
ref={ref}
viewBox="0 0 24 24"
width={width}
height={height}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
style={style}
{...props}
>
<Path d="M12 2L2 7v10l10 5 10-5V7L12 2z"/>
</Svg>
);
}
);

MyIcon.displayName = "MyIcon";

export default MyIcon;
```

**Note:** Make sure you have `react-native-svg` installed in your React Native project:
```bash
npm install react-native-svg
# For iOS
cd ios && pod install
```

### **Vanilla JS/TS**

Generate framework-agnostic factory functions for use anywhere.
Expand Down Expand Up @@ -398,7 +454,7 @@ svger-cli init [options]
```

**Options:**
- `--framework <type>` - Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla)
- `--framework <type>` - Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla|react-native)
- `--typescript` - Enable TypeScript generation (default: true)
- `--src <path>` - Source directory for SVG files (default: ./src/assets/svg)
- `--out <path>` - Output directory for components (default: ./src/components/icons)
Expand Down Expand Up @@ -1926,4 +1982,4 @@ Their guidance and documentation on SVG integration methods in React, Vue, and o

---

**© 2025 SVGER-CLI Development Team. Built with ❤️ for the developer community.**
**© 2025 SVGER-CLI Development Team. Built with ❤️ for the developer community.**
2 changes: 1 addition & 1 deletion cli-framework.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { frameworkTemplateEngine } from './dist/index.js';
// Test file extension generation
console.log('\n📋 Testing File Extension Generation:\n');

const frameworks = ['react', 'vue', 'svelte', 'angular', 'solid', 'preact', 'lit', 'vanilla'];
const frameworks = ['react', 'vue', 'svelte', 'angular', 'solid', 'preact', 'lit', 'vanilla', 'react-native'];

frameworks.forEach(fw => {
const ext = frameworkTemplateEngine.getFileExtension(fw, true);
Expand Down
36 changes: 36 additions & 0 deletions cli-test-react-native/Arrowbenddownleft.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from "react";
import Svg, { G, Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
import { StyleProp, ViewStyle } from "react-native";
export interface ArrowbenddownleftProps extends SvgProps {
width?: number | string;
height?: number | string;
fill?: string;
stroke?: string;
strokeWidth?: number | string;
style?: StyleProp<ViewStyle>;
}

const Arrowbenddownleft = React.forwardRef<React.ComponentRef<typeof Svg>, ArrowbenddownleftProps>(
({ width = 24, height = 24, fill = "currentColor", stroke, strokeWidth, style, ...props }, ref) => {
return (
<Svg
ref={ref}
viewBox="0 0 24 24"
width={width}
height={height}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
style={style}
{...props}
>
<G id="surface1"><Path d="M 2.25 5.25 C 2.253906 7.835938 3.28125 10.3125 5.109375 12.140625 C 6.9375 13.96875 9.414062 14.996094 12 15 L 19.1875 15 L 15.96875 18.21875 C 15.828125 18.359375 15.75 18.550781 15.75 18.75 C 15.75 18.949219 15.828125 19.140625 15.96875 19.28125 C 16.109375 19.421875 16.300781 19.5 16.5 19.5 C 16.699219 19.5 16.890625 19.421875 17.03125 19.28125 L 21.53125 14.78125 C 21.601562 14.710938 21.65625 14.628906 21.691406 14.539062 C 21.730469 14.445312 21.75 14.347656 21.75 14.25 C 21.75 14.152344 21.730469 14.054688 21.691406 13.960938 C 21.65625 13.871094 21.601562 13.789062 21.53125 13.71875 L 17.03125 9.21875 C 16.890625 9.078125 16.699219 9 16.5 9 C 16.300781 9 16.109375 9.078125 15.96875 9.21875 C 15.828125 9.359375 15.75 9.550781 15.75 9.75 C 15.75 9.949219 15.828125 10.140625 15.96875 10.28125 L 19.1875 13.5 L 12 13.5 C 9.8125 13.496094 7.714844 12.628906 6.167969 11.082031 C 4.621094 9.535156 3.75 7.4375 3.75 5.25 C 3.75 5.050781 3.671875 4.859375 3.53125 4.71875 C 3.390625 4.578125 3.199219 4.5 3 4.5 C 2.800781 4.5 2.609375 4.578125 2.46875 4.71875 C 2.328125 4.859375 2.25 5.050781 2.25 5.25 Z M 2.25 5.25 "/></G>
</Svg>
);
}
);

Arrowbenddownleft.displayName = "Arrowbenddownleft";

export default Arrowbenddownleft;
36 changes: 36 additions & 0 deletions cli-test-react-native/Vite.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from "react";
import Svg, { Defs, Line, LinearGradient, Path, Stop } from "react-native-svg";
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import Line.

Suggested change
import Svg, { Defs, Line, LinearGradient, Path, Stop } from "react-native-svg";
import Svg from "react-native-svg";

Copilot uses AI. Check for mistakes.
import type { SvgProps } from "react-native-svg";
import { StyleProp, ViewStyle } from "react-native";
export interface ViteProps extends SvgProps {
width?: number | string;
height?: number | string;
fill?: string;
stroke?: string;
strokeWidth?: number | string;
style?: StyleProp<ViewStyle>;
}

const Vite = React.forwardRef<React.ComponentRef<typeof Svg>, ViteProps>(
({ width = 24, height = 24, fill = "currentColor", stroke, strokeWidth, style, ...props }, ref) => {
return (
<Svg
ref={ref}
viewBox="0 0 24 24"
width={width}
height={height}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
style={style}
{...props}
>
<Defs><LinearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><Stop offset="0%" stop-color="#41D1FF"></Stop><Stop offset="100%" stop-color="#BD34FE"></Stop></LinearGradient><LinearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><Stop offset="0%" stop-color="#FFEA83"></Stop><Stop offset="8.333%" stop-color="#FFDD35"></Stop><Stop offset="100%" stop-color="#FFA800"></Stop></LinearGradient></Defs><Path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></Path><Path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></Path>
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SVG attribute conversion is incomplete. The stop-color attribute has not been converted to stopColor in camelCase format. React Native requires all hyphenated SVG attributes to be in camelCase. The convertSVGToReactNative function should include a replacement for stop-color to stopColor.

Suggested change
<Defs><LinearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><Stop offset="0%" stop-color="#41D1FF"></Stop><Stop offset="100%" stop-color="#BD34FE"></Stop></LinearGradient><LinearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><Stop offset="0%" stop-color="#FFEA83"></Stop><Stop offset="8.333%" stop-color="#FFDD35"></Stop><Stop offset="100%" stop-color="#FFA800"></Stop></LinearGradient></Defs><Path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></Path><Path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></Path>
<Defs><LinearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><Stop offset="0%" stopColor="#41D1FF"></Stop><Stop offset="100%" stopColor="#BD34FE"></Stop></LinearGradient><LinearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><Stop offset="0%" stopColor="#FFEA83"></Stop><Stop offset="8.333%" stopColor="#FFDD35"></Stop><Stop offset="100%" stopColor="#FFA800"></Stop></LinearGradient></Defs><Path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></Path><Path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></Path>

Copilot uses AI. Check for mistakes.
</Svg>
);
}
);

Vite.displayName = "Vite";

export default Vite;
25 changes: 25 additions & 0 deletions cli-test-react-native/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* SVG Components Index
* Generated by svger-cli
*
* Import individual components:
* import { Arrowbenddownleft } from './components';
*
* Import all components:
* import * as Icons from './components';
* import Icons from './components'; // default export
*/

export { default as Arrowbenddownleft } from './Arrowbenddownleft';
export { default as Vite } from './Vite';
// Export all components
export {
Arrowbenddownleft,
Vite,
};

// Re-export for convenience
export default {
Arrowbenddownleft,
Vite,
};
75 changes: 36 additions & 39 deletions dist/cli.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
#!/usr/bin/env node
import { CLI } from "./utils/native.js";
import { svgService } from "./services/svg-service.js";
import { configService } from "./services/config.js";
import { logger } from "./core/logger.js";
import { CLI } from './utils/native.js';
import { svgService } from './services/svg-service.js';
import { configService } from './services/config.js';
import { logger } from './core/logger.js';
const program = new CLI();
/**
* svger-cli CLI
* Custom SVG to Angular, React, Vue, Svelte, Solid, and other component converter.
*/
program
.name("svger-cli")
.description("Custom SVG to Angular, React, Vue, Svelte, Solid, and other component converter")
.version("2.0.0");
program.name('svger-cli').description('Custom SVG to Angular, React, Vue, Svelte, Solid, React Native, and other component converter').version('2.0.0');
// -------- Build Command --------
/**
* Build all SVGs from a source folder to an output folder.
*/
program
.command("build <src> <out>")
.description("Build all SVGs from source to output")
.option("--framework <type>", "Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla)")
.option("--typescript", "Generate TypeScript components (default: true)")
.option("--no-typescript", "Generate JavaScript components")
.option("--composition", "Use Vue Composition API with <script setup>")
.option("--standalone", "Generate Angular standalone components")
.option("--signals", "Use Angular signals for reactive state")
.command('build <src> <out>')
.description('Build all SVGs from source to output')
.option('--framework <type>', 'Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla|react-native)')
.option('--typescript', 'Generate TypeScript components (default: true)')
.option('--no-typescript', 'Generate JavaScript components')
.option('--composition', 'Use Vue Composition API with <script setup>')
.option('--standalone', 'Generate Angular standalone components')
.option('--signals', 'Use Angular signals for reactive state')
.action(async (args, opts) => {
try {
const [src, out] = args;
Expand Down Expand Up @@ -62,8 +59,8 @@ program
* Watch a source folder and rebuild SVGs automatically on changes.
*/
program
.command("watch <src> <out>")
.description("Watch source folder and rebuild SVGs automatically")
.command('watch <src> <out>')
.description('Watch source folder and rebuild SVGs automatically')
.action(async (args) => {
try {
const [src, out] = args;
Expand All @@ -85,13 +82,13 @@ program
* Generate a component from a single SVG file.
*/
program
.command("generate <svgFile> <out>")
.description("Convert a single SVG file into a component")
.option("--framework <type>", "Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla)")
.option("--typescript", "Generate TypeScript component (default: true)")
.option("--no-typescript", "Generate JavaScript component")
.option("--composition", "Use Vue Composition API with <script setup>")
.option("--standalone", "Generate Angular standalone component")
.command('generate <svgFile> <out>')
.description('Convert a single SVG file into a component')
.option('--framework <type>', 'Target framework (react|vue|svelte|angular|solid|preact|lit|vanilla|react-native)')
.option('--typescript', 'Generate TypeScript component (default: true)')
.option('--no-typescript', 'Generate JavaScript component')
.option('--composition', 'Use Vue Composition API with <script setup>')
.option('--standalone', 'Generate Angular standalone component')
.action(async (args, opts) => {
try {
const [svgFile, out] = args;
Expand Down Expand Up @@ -124,8 +121,8 @@ program
* Lock one or more SVG files to prevent accidental overwrites.
*/
program
.command("lock <files...>")
.description("Lock one or more SVG files")
.command('lock <files...>')
.description('Lock one or more SVG files')
.action((args) => {
try {
svgService.lockService.lockFiles(args);
Expand All @@ -139,8 +136,8 @@ program
* Unlock one or more SVG files to allow modifications.
*/
program
.command("unlock <files...>")
.description("Unlock one or more SVG files")
.command('unlock <files...>')
.description('Unlock one or more SVG files')
.action((args) => {
try {
svgService.lockService.unlockFiles(args);
Expand All @@ -155,27 +152,27 @@ program
* Manage svger-cli configuration.
*/
program
.command("config")
.description("Manage svger-cli configuration")
.option("--init", "Create default .svgconfig.json")
.option("--set <keyValue>", "Set config key=value")
.option("--show", "Show current config")
.command('config')
.description('Manage svger-cli configuration')
.option('--init', 'Create default .svgconfig.json')
.option('--set <keyValue>', 'Set config key=value')
.option('--show', 'Show current config')
.action(async (args, opts) => {
try {
if (opts.init)
return await configService.initConfig();
if (opts.set) {
const [key, value] = opts.set.split("=");
const [key, value] = opts.set.split('=');
if (!key || value === undefined) {
logger.error("Invalid format. Use key=value");
logger.error('Invalid format. Use key=value');
process.exit(1);
}
const parsedValue = !isNaN(Number(value)) ? Number(value) : value;
return configService.setConfig(key, parsedValue);
}
if (opts.show)
return configService.showConfig();
logger.error("No option provided. Use --init, --set, or --show");
logger.error('No option provided. Use --init, --set, or --show');
}
catch (error) {
logger.error('Config operation failed:', error);
Expand All @@ -187,8 +184,8 @@ program
* Remove all generated SVG React components from an output folder.
*/
program
.command("clean <out>")
.description("Remove all generated SVG React components from output folder")
.command('clean <out>')
.description('Remove all generated SVG React components from output folder')
.action(async (args) => {
try {
const [out] = args;
Expand Down
Loading
Loading