diff --git a/app/components/developmentToolsComponent/svgConverter.tsx b/app/components/developmentToolsComponent/svgConverter.tsx new file mode 100644 index 0000000..c9636d4 --- /dev/null +++ b/app/components/developmentToolsComponent/svgConverter.tsx @@ -0,0 +1,394 @@ +"use client"; +import React, { useMemo, useRef, useState } from "react"; +import DevelopmentToolsStyles from "../../developmentToolsStyles.module.scss"; + +const SvgConverter = () => { + const [svgInput, setSvgInput] = useState(""); + const [outputFormat, setOutputFormat] = useState<"react" | "css-data-uri" | "css-mask">("react"); + const [defaultWidth, setDefaultWidth] = useState("24"); + const [defaultHeight, setDefaultHeight] = useState("24"); + const [useCurrentColor, setUseCurrentColor] = useState(true); + const [error, setError] = useState(null); + const fileInputRef = useRef(null); + + // Clean SVG by removing metadata and unnecessary attributes + const cleanSvg = (svg: string): string => { + try { + let cleaned = svg.trim(); + // Remove XML declaration if present + cleaned = cleaned.replace(/<\?xml[^?]*\?>/g, ""); + // Remove comments + cleaned = cleaned.replace(//g, ""); + // Remove DOCTYPE + cleaned = cleaned.replace(/]*>/g, ""); + // Remove unnecessary whitespace between tags + cleaned = cleaned.replace(/>\s+<"); + // Remove trailing whitespace + cleaned = cleaned.trim(); + return cleaned; + } catch { + return svg; + } + }; + + // Normalize SVG attributes to JSX equivalents + const normalizeSvgAttributes = (svg: string): string => { + let normalized = svg; + // Convert SVG attribute names to JSX equivalents + normalized = normalized.replace(/fill-rule="/g, 'fillRule="'); + normalized = normalized.replace(/clip-rule="/g, 'clipRule="'); + normalized = normalized.replace(/stroke-linecap="/g, 'strokeLinecap="'); + normalized = normalized.replace(/stroke-linejoin="/g, 'strokeLinejoin="'); + normalized = normalized.replace(/stroke-miterlimit="/g, 'strokeMiterlimit="'); + normalized = normalized.replace(/stroke-width="/g, 'strokeWidth="'); + normalized = normalized.replace(/stroke-dasharray="/g, 'strokeDasharray="'); + normalized = normalized.replace(/stroke-dashoffset="/g, 'strokeDashoffset="'); + normalized = normalized.replace(/text-anchor="/g, 'textAnchor="'); + normalized = normalized.replace(/class="/g, 'className="'); + return normalized; + }; + + // Convert SVG for React component output + const generateReactComponent = (svg: string): string => { + try { + const cleaned = cleanSvg(svg); + + // Extract viewBox or use defaults + const viewBoxMatch = cleaned.match(/viewBox="([^"]*)"/); + const viewBox = viewBoxMatch ? viewBoxMatch[1] : "0 0 24 24"; + + // Replace default fill/stroke with currentColor if enabled + let reactSvg = cleaned; + if (useCurrentColor) { + // Replace fill colors (except fill="none") with currentColor + reactSvg = reactSvg.replace(/fill="(?!none)[^"]*"/g, 'fill="currentColor"'); + // Replace stroke colors with currentColor if they exist + reactSvg = reactSvg.replace(/stroke="[^"]*"/g, 'stroke="currentColor"'); + } + + // Remove width and height to make it responsive + reactSvg = reactSvg.replace(/\s*width="[^"]*"\s*/g, " "); + reactSvg = reactSvg.replace(/\s*height="[^"]*"\s*/g, " "); + + // Normalize SVG attributes to JSX equivalents + reactSvg = normalizeSvgAttributes(reactSvg); + + // Clean up multiple spaces + reactSvg = reactSvg.replace(/\s+/g, " "); + + const component = `import React from 'react'; + +interface SvgIconProps { + width?: number | string; + height?: number | string; + className?: string; +} + +export const SvgIcon: React.FC = ({ + width = ${defaultWidth}, + height = ${defaultHeight}, + className = '' +}) => ( + + ${reactSvg.replace(/]*>/g, "").replace(/<\/svg>/g, "").trim()} + +); + +export default SvgIcon;`; + + return component; + } catch (e) { + throw new Error("Failed to convert to React component"); + } + }; + + // Convert SVG to CSS Data URI + const generateCssDataUri = (svg: string): string => { + try { + const cleaned = cleanSvg(svg); + + // Encode SVG as data URI + // Escape special characters but keep it relatively readable + const encoded = cleaned + .replace(/"/g, "'") + .replace(/%/g, "%25") + .replace(/#/g, "%23") + .replace(/{/g, "%7B") + .replace(/}/g, "%7D") + .replace(//g, "%3E"); + + const dataUri = `url("data:image/svg+xml,${encoded}")`; + + const css = `.icon { + width: ${defaultWidth}px; + height: ${defaultHeight}px; + background-image: ${dataUri}; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +}`; + + return css; + } catch (e) { + throw new Error("Failed to convert to CSS Data URI"); + } + }; + + // Convert SVG to CSS Mask + const generateCssMask = (svg: string): string => { + try { + const cleaned = cleanSvg(svg); + + const encoded = cleaned + .replace(/"/g, "'") + .replace(/%/g, "%25") + .replace(/#/g, "%23") + .replace(/{/g, "%7B") + .replace(/}/g, "%7D") + .replace(//g, "%3E"); + + const dataUri = `url("data:image/svg+xml,${encoded}")`; + + const css = `.icon { + width: ${defaultWidth}px; + height: ${defaultHeight}px; + background-color: currentColor; + -webkit-mask-image: ${dataUri}; + mask-image: ${dataUri}; + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; +}`; + + return css; + } catch (e) { + throw new Error("Failed to convert to CSS Mask"); + } + }; + + const output = useMemo(() => { + try { + setError(null); + if (!svgInput.trim()) return ""; + + // Validate SVG + if (!svgInput.includes(" tag"); + return ""; + } + + switch (outputFormat) { + case "react": + return generateReactComponent(svgInput); + case "css-data-uri": + return generateCssDataUri(svgInput); + case "css-mask": + return generateCssMask(svgInput); + default: + return ""; + } + } catch (e: any) { + setError(e.message || "Failed to convert SVG"); + return ""; + } + }, [svgInput, outputFormat, defaultWidth, defaultHeight, useCurrentColor]); + + const handleCopy = async () => { + if (!output) return; + try { + await navigator.clipboard.writeText(output); + } catch (_) {} + }; + + const handleClear = () => { + setSvgInput(""); + setError(null); + }; + + const handlePickFile = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange: React.ChangeEventHandler = (e) => { + const file = e.target.files?.[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + const text = typeof reader.result === "string" ? reader.result : ""; + setSvgInput(text); + }; + reader.readAsText(file); + e.target.value = ""; + }; + + return ( +
+
+
+
+
+ {/* Options Section */} +
+

Conversion Options

+
+
+ + +
+ +
+ + setDefaultWidth(e.target.value)} + placeholder="24" + className="w-full bg-black border border-[#222222] rounded px-3 py-2 text-white focus:outline-none focus:border-blue-500" + /> +
+ +
+ + setDefaultHeight(e.target.value)} + placeholder="24" + className="w-full bg-black border border-[#222222] rounded px-3 py-2 text-white focus:outline-none focus:border-blue-500" + /> +
+ +
+ +
+
+
+ + {/* Input/Output Section */} +
+ {/* Input */} +
+

SVG Input

+
+ + + {svgInput && ( + + )} + +
+ {error && ( +
{error}
+ )} +
+ + {/* Output */} +
+

Optimized Output

+
+ + {output && ( + + )} +
+
+
+
+
+
+
+
+ ); +}; + +export default SvgConverter; diff --git a/app/libs/constants.tsx b/app/libs/constants.tsx index 5974493..95fec36 100644 --- a/app/libs/constants.tsx +++ b/app/libs/constants.tsx @@ -153,6 +153,7 @@ import SqlFormatterAndBeautifier from '../components/developmentToolsComponent/s import SqlMinify from '../components/developmentToolsComponent/sqlMinify'; import SqlToCsvConverter from '../components/developmentToolsComponent/sqlToCsvConverter'; import SqlToJson from '../components/developmentToolsComponent/sqlToJson'; +import SvgConverter from '../components/developmentToolsComponent/svgConverter'; import StringDiffrenceChecker from '../components/developmentToolsComponent/stringDiffrenceChecker'; import StripHTML from '../components/developmentToolsComponent/stripHTML'; import TextCompare from '../components/developmentToolsComponent/textCompare'; @@ -1607,6 +1608,14 @@ export const developmentToolsCategoryContent: any = { description: 'Calculate subnet details like network address, broadcast address, and usable host range.', }, ], + Category177: [ + { + url: '/svg-converter', + title: 'SVG to React/CSS Utility', + description: + 'Convert SVG to optimized React components, CSS Data URIs, or CSS Masks.', + }, + ], }; export const PATHS = { @@ -1765,6 +1774,7 @@ export const PATHS = { HTML_UNESCAPE: '/html-unescape', JAVASCRIPT_REGEX_TESTER: '/javascript-regex-tester', STRIP_HTML: '/strip-html', + SVG_CONVERTER: '/svg-converter', WHAT_IS_MY_LOCAL_IP_ADDRESS: '/what-is-my-local-ip-address', JAVASCRIPT_TESTER: '/javascript-tester', WHAT_VERSION_OF_JAVA: '/what-version-of-java-do-i-have', @@ -2491,6 +2501,10 @@ export const developmentToolsRoutes = [ path: PATHS.SQL_TO_JSON, component: , }, + { + path: PATHS.SVG_CONVERTER, + component: , + }, { path: PATHS.HTML_TO_JADE, component: , diff --git a/app/libs/developmentToolsConstant.tsx b/app/libs/developmentToolsConstant.tsx index 0624c4a..fb303c8 100644 --- a/app/libs/developmentToolsConstant.tsx +++ b/app/libs/developmentToolsConstant.tsx @@ -15543,6 +15543,95 @@ family[1]: "Beth"`, og_image: '/images/og-images/Cover.png', }, }, + [`svg-converter`]: { + hero_section: { + title: 'SVG to React/CSS Utility', + description: + 'Convert raw SVG code to optimized React components, CSS Data URIs, or CSS Masks for different development needs.', + }, + development_tools_list: [ + { tool: 'Base64 Encoder', url: PATHS.BASE64_ENCODER }, + { tool: 'CSS Minify', url: PATHS.CSS_MINIFY }, + { tool: 'Color Inveror', url: PATHS.COLOR_INVERTOR }, + { tool: 'QR Code Generator', url: PATHS.QR_CODE_GENERATOR }, + ], + development_tools_about_details: { + about_title: 'What is the SVG Converter?', + about_description: [ + { + description: + 'The SVG Converter transforms raw SVG code into optimized variants for different use cases: clean React components, CSS Data URIs, or CSS Masks.', + }, + { + description: + 'Automates SVG cleanup (removing metadata), handles viewBox preservation, and supports dynamic sizing and color theming options.', + }, + ], + }, + development_tools_steps_guide: { + guide_title: 'How to Use', + guide_description: 'Follow these simple steps:', + steps: [ + { + step_key: 'Step 1:', + step_title: 'Paste SVG Code:', + step_description: 'Paste your SVG code or upload an SVG file.', + }, + { + step_key: 'Step 2:', + step_title: 'Choose Output Format:', + step_description: + 'Select React Component, CSS Data URI, or CSS Mask from the dropdown.', + }, + { + step_key: 'Step 3:', + step_title: 'Configure Options:', + step_description: + 'Set default width, height, and choose whether to use currentColor for fills.', + }, + { + step_key: 'Step 4:', + step_title: 'Copy Output:', + step_description: 'Copy the optimized code and use it in your project.', + }, + ], + }, + development_tools_how_use: { + how_use_title: 'Common Use Cases', + how_use_description: 'Popular reasons to use this tool:', + point: [ + { + title: 'React Component Generation', + description: + 'Automatically create reusable React icon components from SVG files with dynamic sizing and theming support.', + }, + { + title: 'CSS Background Images', + description: + 'Generate inline SVG Data URIs for use as CSS background images without external file requests.', + }, + { + title: 'Icon Masking', + description: + 'Convert SVGs to CSS mask properties for flexible icon styling and color customization.', + }, + { + title: 'Metadata Cleanup', + description: + 'Automatically remove unnecessary metadata, comments, and attributes from design tool exports.', + }, + ], + }, + meta_data: { + meta_title: 'SVG to React/CSS Converter – Free Online Tool', + meta_description: + 'Convert SVG to React components, CSS Data URIs, or CSS Masks. Remove metadata and optimize for web development.', + og_title: 'SVG Converter – Optimize SVGs for Development', + og_description: + 'Transform SVG files into React components, CSS URIs, or masks with one click. Supports custom dimensions and color theming.', + og_image: '/images/og-images/Cover.png', + }, + }, [`what-is-my-local-ip-address`]: { hero_section: { title: 'What Is My Local IP Address',