diff --git a/html/svg-to-react.html b/html/svg-to-react.html index c0a093b..cd89303 100644 --- a/html/svg-to-react.html +++ b/html/svg-to-react.html @@ -32,8 +32,8 @@ } .container { - display: grid; - grid-template-columns: 1fr 1fr; + display: flex; + flex-direction: column; gap: 20px; margin-bottom: 20px; } @@ -118,11 +118,6 @@ text-decoration: underline; } - @media (max-width: 768px) { - .container { - grid-template-columns: 1fr; - } - } @@ -260,23 +255,86 @@

SVG to React

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(/^]*?)>/, ''); + // 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>( - function Icon(props, ref) { +const Icon = forwardRef( + (props: SVGProps, ref: Ref) => { 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}`; + return result; + } + + return formatElement(svg, ' '); + } + function updateOutput() { const input = inputEl.value.trim();