Skip to content
Merged
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
90 changes: 74 additions & 16 deletions html/svg-to-react.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
}

.container {
display: grid;
grid-template-columns: 1fr 1fr;
display: flex;
flex-direction: column;
gap: 20px;
margin-bottom: 20px;
}
Expand Down Expand Up @@ -118,11 +118,6 @@
text-decoration: underline;
}

@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
Expand Down Expand Up @@ -260,23 +255,86 @@ <h1>SVG to React</h1>
reactSvg = reactSvg.replace(/\sxmlns(:\w+)?="[^"]*"/g, '');
reactSvg = reactSvg.trim();

// Add {...props} and ref to the SVG element
// Props come before ref so hardcoded attributes can be overridden, but ref is always preserved
reactSvg = reactSvg.replace(/^<svg([^>]*?)>/, '<svg$1 {...props} ref={ref}>');
// Format SVG with proper indentation
reactSvg = formatSvg(reactSvg);

const component = `import { forwardRef } from 'react';
const component = `import type { Ref, SVGProps } from "react";
import { forwardRef } from "react";

export default forwardRef<SVGSVGElement, React.SVGProps<SVGSVGElement>>(
function Icon(props, ref) {
const Icon = forwardRef(
(props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => {
return (
${reactSvg}
${reactSvg}
);
}
);`;
},
);

export default Icon;`;

return component;
}

function formatSvg(svgString) {
const parser = new DOMParser();
const doc = parser.parseFromString(svgString, 'image/svg+xml');
const svg = doc.querySelector('svg');

function formatElement(element, indent) {
const tagName = element.tagName;
const attrs = Array.from(element.attributes);
const children = Array.from(element.childNodes).filter(
node => node.nodeType === 1 || (node.nodeType === 3 && node.textContent.trim())
);

let result = `${indent}<${tagName}`;

// For SVG element, add attributes on separate lines, then props and ref
if (tagName === 'svg') {
attrs.forEach(attr => {
result += `\n${indent} ${attr.name}="${attr.value}"`;
});
result += `\n${indent} {...props}`;
result += `\n${indent} ref={ref}`;
result += `\n${indent}>`;
} else {
// For other elements
if (attrs.length === 0) {
result += ' />';
return result;
} else if (attrs.length <= 2 && children.length === 0) {
// Put few attributes inline for simple elements
attrs.forEach(attr => {
result += ` ${attr.name}="${attr.value}"`;
});
result += ' />';
return result;
} else {
// Many attributes on separate lines
attrs.forEach(attr => {
result += `\n${indent} ${attr.name}="${attr.value}"`;
});
result += children.length > 0 ? `\n${indent}>` : `\n${indent}/>`;
if (children.length === 0) {
return result;
}
}
}

children.forEach(child => {
if (child.nodeType === 1) {
result += '\n' + formatElement(child, indent + ' ');
} else {
result += child.textContent.trim();
}
});

result += `\n${indent}</${tagName}>`;
return result;
}

return formatElement(svg, ' ');
}

function updateOutput() {
const input = inputEl.value.trim();

Expand Down