Merged in chore/icons-to-components (pull request #3420)

Convert all material symbols to react components

* convert all material symbols to react components

* remove svgr


Approved-by: Linus Flood
This commit is contained in:
Joakim Jäderberg
2026-01-12 14:55:08 +00:00
parent 6a008ba342
commit 8befbf405a
436 changed files with 5247 additions and 4826 deletions

View File

@@ -1,5 +1,12 @@
import { writeFile } from "node:fs/promises"
import { resolve } from "node:path"
import {
writeFile,
readFile,
mkdir,
rmdir,
readdir,
rm,
} from "node:fs/promises"
import { resolve, join } from "node:path"
import { existsSync } from "node:fs"
// Your full list of Material Symbol icon names
@@ -219,13 +226,18 @@ const ICONS = [
"yard",
].sort()
const STYLES = ["outlined", "rounded", "sharp"] as const
const STYLES = ["outlined"] as const
const OUT = resolve(
__dirname,
"../packages/design-system/lib/components/Icons/MaterialIcon/generated.tsx"
)
const REACT_OUT = resolve(
__dirname,
"../packages/design-system/lib/components/Icons/MaterialIcon/generated"
)
const PACKAGE_BASE = resolve(
__dirname,
"../node_modules/@material-symbols/svg-400"
@@ -235,7 +247,15 @@ function camelCase(str: string) {
return str.replace(/[-_](\w)/g, (_, c: string) => c.toUpperCase())
}
function pascalCase(str: string) {
const camel = camelCase(str)
return camel.charAt(0).toUpperCase() + camel.slice(1)
}
async function main() {
await rm(REACT_OUT, { recursive: true, force: true })
await mkdir(REACT_OUT, { recursive: true })
const imports: string[] = []
const registryEntries: string[] = []
@@ -247,28 +267,64 @@ async function main() {
for (const style of STYLES) {
const parts: string[] = []
const fill0Path = resolve(PACKAGE_BASE, style, `${icon}.svg`)
const fill1Path = resolve(PACKAGE_BASE, style, `${icon}-fill.svg`)
const variants = [
{
suffix: "",
varNameSuffix: "Outlined",
path: resolve(PACKAGE_BASE, style, `${icon}.svg`),
key: "outlined",
},
{
suffix: "-fill",
varNameSuffix: "Filled",
path: resolve(PACKAGE_BASE, style, `${icon}-fill.svg`),
key: "filled",
},
]
let outlinedVar: string | undefined
let filledVar: string | undefined
for (const variant of variants) {
if (existsSync(variant.path)) {
const content = await readFile(variant.path, "utf8")
const svgMatch = content.match(
/<svg[^>]*viewBox="([^"]*)"[^>]*>([\s\S]*?)<\/svg>/
)
if (existsSync(fill0Path)) {
outlinedVar = `${camelCase(icon)}${camelCase(style)}Outlined`
imports.push(
`import ${outlinedVar} from "@material-symbols/svg-400/${style}/${icon}.svg"`
)
parts.push(`outlined: ${outlinedVar}`)
} else {
missing.push(`${style}/${icon}.svg`)
}
if (svgMatch) {
const viewBox = svgMatch[1]
const inner = svgMatch[2]
if (existsSync(fill1Path)) {
filledVar = `${camelCase(icon)}${camelCase(style)}Filled`
imports.push(
`import ${filledVar} from "@material-symbols/svg-400/${style}/${icon}-fill.svg"`
)
parts.push(`filled: ${filledVar}`)
const componentName = `${pascalCase(icon)}${variant.varNameSuffix}`
const fileName = `${componentName}.tsx`
const filePath = join(REACT_OUT, fileName)
const componentContent =
`
/* AUTO-GENERATED — DO NOT EDIT */
import type { SVGProps } from "react"
const ${componentName} = (props: SVGProps<SVGSVGElement>) => (
<svg viewBox="${viewBox}" {...props}>
${inner.trim()}
</svg>
)
export default ${componentName}
`.trim() + "\n"
await writeFile(filePath, componentContent, "utf8")
imports.push(
`const ${componentName} = lazy(() => import("./generated/${componentName}"))`
)
parts.push(`${variant.key}: ${componentName}`)
} else {
console.warn(`Could not parse SVG for ${variant.path}`)
}
} else {
if (variant.key === "outlined") {
missing.push(`${style}/${icon}.svg`)
}
}
}
if (parts.length) {
@@ -289,6 +345,7 @@ async function main() {
/* AUTO-GENERATED — DO NOT EDIT */
import type { FunctionComponent, SVGProps } from "react"
import { lazy } from "react"
${imports.join("\n")}