-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] svg 아이콘 컴포넌트 방식 변환 자동화 스크립트 작성 #31
Changes from all commits
84d7adc
6edcbd0
6ab2230
db49121
6f8a213
7687ddc
9cbab74
807ab4a
6c14c6c
2ca243f
15b5dd0
1662cd8
f173ef7
feb0396
3637b50
e9c6de7
5d29bbb
8aa7e42
476fa30
e835760
d518a60
988a436
87c8894
8ea29c1
1ffb655
fc4a5e3
8c1f90d
c1a56a8
0704e9b
7efae93
4f29092
8d39402
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ module.exports = { | |
globals: { | ||
React: true, | ||
JSX: true, | ||
console: true, | ||
}, | ||
env: { | ||
browser: true, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,13 +26,13 @@ | |
"@changesets/changelog-git": "^0.2.0", | ||
"@changesets/cli": "^2.27.1", | ||
"@pandacss/dev": "^0.39.0", | ||
"@rollup/plugin-alias": "^5.1.0", | ||
"@rollup/plugin-babel": "^6.0.4", | ||
"@rollup/plugin-commonjs": "^25.0.7", | ||
"@rollup/plugin-json": "^6.1.0", | ||
"@rollup/plugin-node-resolve": "^15.2.3", | ||
"@rollup/plugin-terser": "^0.4.4", | ||
"@rollup/plugin-url": "^8.0.2", | ||
"@svgr/rollup": "^8.1.0", | ||
"@testing-library/jest-dom": "^6.4.5", | ||
"@testing-library/react": "^15.0.7", | ||
"@testing-library/user-event": "^14.5.2", | ||
|
@@ -69,11 +69,13 @@ | |
"rollup-plugin-peer-deps-external": "^2.2.4", | ||
"rollup-plugin-preserve-directives": "^0.4.0", | ||
"shared-config": "workspace:^", | ||
"theme": "workspace:*", | ||
"theme": "workspace:^", | ||
"ts-jest": "^29.1.2", | ||
"ts-node": "^10.9.2", | ||
"tsx": "^4.11.0", | ||
"turbo": "latest", | ||
"typescript": "^5.3.3" | ||
"typescript": "^5.3.3", | ||
"wowds-tokens": "workspace:^" | ||
}, | ||
"packageManager": "[email protected]", | ||
"engines": { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { existsSync, promises as fs } from "fs"; | ||
import path from "path"; | ||
|
||
const SVG_DIR = "../wow-icons/src/svg"; | ||
const COMPONENT_DIR = "../wow-icons/src/react"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 넵 일단은 svg 폴더 내부에 필요한 파일들 다 넣어두고 스크립트 실행시키면 react 폴더에 변환된 아이콘 + export 파일 생성되게 하는 걸 의도했어요 |
||
|
||
type SvgComponentMap = { [key: string]: string }; | ||
|
||
const generateSvgComponentMap = async () => { | ||
const svgFiles = (await fs.readdir(SVG_DIR)).reduce<SvgComponentMap>( | ||
(map, svgFile) => { | ||
const componentName = path | ||
.basename(svgFile, ".svg") | ||
.replace(/(^\w|-\w)/g, (match) => match.replace("-", "").toUpperCase()); | ||
map[componentName] = svgFile; | ||
|
||
return map; | ||
}, | ||
{} | ||
); | ||
|
||
return svgFiles; | ||
}; | ||
|
||
const deleteUnusedComponentFiles = async (svgComponentMap: SvgComponentMap) => { | ||
if (!existsSync(COMPONENT_DIR)) { | ||
fs.mkdir(COMPONENT_DIR); | ||
return; | ||
} | ||
|
||
const componentFiles = await fs.readdir(COMPONENT_DIR); | ||
const componentFilesToDelete = componentFiles.filter((componentFile) => { | ||
const componentName = path.basename(componentFile, ".tsx"); | ||
return !(componentName in svgComponentMap); | ||
}); | ||
|
||
await Promise.all( | ||
componentFilesToDelete.map((file) => { | ||
const componentFilePath = path.resolve(COMPONENT_DIR, file); | ||
return fs.unlink(componentFilePath); | ||
}) | ||
); | ||
}; | ||
|
||
const createComponentContent = ( | ||
componentName: string, | ||
svgContent: string, | ||
svgFile: string | ||
): string => { | ||
const iconName = path.basename(svgFile, ".svg"); | ||
const hasStroke = svgContent.includes("stroke="); | ||
const fillAttributes = (svgContent.match(/fill="([^"]*)"/g) || []).filter( | ||
(attr) => attr !== 'fill="none"' | ||
); | ||
const hasFill = fillAttributes.length; | ||
const propsString = `{ className, width = 24, height = 24, viewBox = "0 0 24 24"${hasStroke || hasFill ? ` ${hasStroke ? ', stroke = "white"' : ""}${hasFill ? ', fill = "white"' : ""}` : ""}, ...rest }`; | ||
const modifiedSvgContent = svgContent | ||
.replace(/-(\w)/g, (_, letter) => letter.toUpperCase()) | ||
.replace(/width="(\d+)"/g, `width={width}`) | ||
.replace(/height="(\d+)"/g, `height={height}`) | ||
.replace(/viewBox="(.*?)"/g, `viewBox={viewBox}`) | ||
.replace(/<svg([^>]*)fill="[^"]*"([^>]*)>/, "<svg$1$2>") | ||
.replace(/fill="([^"]+)"/g, `fill={color[fill]}`) | ||
.replace(/stroke="([^"]+)"/g, `stroke={color[stroke]}`) | ||
.replace( | ||
/<svg([^>]*)>/, | ||
`<svg$1 aria-label="${iconName} icon" fill="none" ref={ref} className={className} {...rest}>` | ||
); | ||
|
||
return ` | ||
import { forwardRef } from 'react'; | ||
import { color } from "wowds-tokens"; | ||
|
||
import type { IconProps } from "../types/Icon.ts"; | ||
|
||
const ${componentName} = forwardRef<SVGSVGElement, IconProps>( | ||
(${propsString}, ref) => { | ||
return ( | ||
${modifiedSvgContent} | ||
); | ||
} | ||
); | ||
|
||
${componentName}.displayName = '${componentName}'; | ||
export default ${componentName}; | ||
`; | ||
}; | ||
|
||
const generateComponentFiles = async (svgComponentMap: SvgComponentMap) => { | ||
const components: string[] = []; | ||
|
||
for (const [componentName, svgFile] of Object.entries(svgComponentMap)) { | ||
const componentFilePath = path.resolve( | ||
COMPONENT_DIR, | ||
`${componentName}.tsx` | ||
); | ||
|
||
if (existsSync(componentFilePath)) { | ||
components.push(componentName); | ||
continue; | ||
} | ||
|
||
const svgFilePath = path.resolve(SVG_DIR, svgFile); | ||
const svgContent = (await fs.readFile(svgFilePath)).toString(); | ||
|
||
const componentContent = createComponentContent( | ||
componentName, | ||
svgContent, | ||
svgFile | ||
); | ||
|
||
await fs.writeFile(componentFilePath, componentContent); | ||
components.push(componentName); | ||
} | ||
|
||
return components; | ||
}; | ||
|
||
const generateExportFile = async (components: string[]) => { | ||
const EXPORT_FILE_PATH = "../wow-icons/src/react/index.ts"; | ||
const exportFileContent = components | ||
.map( | ||
(component) => | ||
`export { default as ${component} } from "./${component}.tsx";` | ||
) | ||
.join("\n"); | ||
|
||
await fs.writeFile(EXPORT_FILE_PATH, exportFileContent); | ||
}; | ||
|
||
(async () => { | ||
try { | ||
const svgComponentMap = await generateSvgComponentMap(); | ||
await deleteUnusedComponentFiles(svgComponentMap); | ||
const components = await generateComponentFiles(svgComponentMap); | ||
await generateExportFile(components); | ||
} catch (error) { | ||
console.log("Error generating components:", error); | ||
} | ||
})(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
export type ColorToken = | ||
| "red50" | ||
| "red100" | ||
| "red150" | ||
| "red200" | ||
| "red300" | ||
| "red400" | ||
| "red500" | ||
| "red600" | ||
| "red700" | ||
| "red800" | ||
| "red850" | ||
| "red900" | ||
| "red950" | ||
| "blue50" | ||
| "blue100" | ||
| "blue150" | ||
| "blue200" | ||
| "blue300" | ||
| "blue400" | ||
| "blue500" | ||
| "blue600" | ||
| "blue700" | ||
| "blue800" | ||
| "blue850" | ||
| "blue900" | ||
| "blue950" | ||
| "yellow50" | ||
| "yellow100" | ||
| "yellow150" | ||
| "yellow200" | ||
| "yellow300" | ||
| "yellow400" | ||
| "yellow500" | ||
| "yellow600" | ||
| "yellow700" | ||
| "yellow800" | ||
| "yellow850" | ||
| "yellow900" | ||
| "yellow950" | ||
| "green50" | ||
| "green100" | ||
| "green150" | ||
| "green200" | ||
| "green300" | ||
| "green400" | ||
| "green500" | ||
| "green600" | ||
| "green700" | ||
| "green800" | ||
| "green850" | ||
| "green900" | ||
| "green950" | ||
| "mono50" | ||
| "mono100" | ||
| "mono150" | ||
| "mono200" | ||
| "mono300" | ||
| "mono400" | ||
| "mono500" | ||
| "mono600" | ||
| "mono700" | ||
| "mono800" | ||
| "mono850" | ||
| "mono900" | ||
| "mono950" | ||
| "white" | ||
| "black" | ||
| "whiteOpacity20" | ||
| "whiteOpacity40" | ||
| "whiteOpacity60" | ||
| "whiteOpacity80" | ||
| "blackOpacity20" | ||
| "blackOpacity40" | ||
| "blackOpacity60" | ||
| "blackOpacity80" | ||
| "primary" | ||
| "success" | ||
| "error" | ||
| "backgroundNormal" | ||
| "backgroundAlternative" | ||
| "backgroundDimmer" | ||
| "sub" | ||
| "outline" | ||
| "textBlack" | ||
| "textWhite" | ||
| "darkDisabled" | ||
| "lightDisabled" | ||
| "blueHover" | ||
| "monoHover" | ||
| "elevatedHover" | ||
| "bluePressed" | ||
| "blueBackgroundPressed" | ||
| "monoBackgroundPressed" | ||
| "shadowSmall" | ||
| "shadowMedium" | ||
| "blueShadow" | ||
| "discord" | ||
| "github"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./Color.ts"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 자꾸 console이 정의되지 않은 거라고 되어 있어서 추가했습니다