@liner-fe/internal-icon 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.ultra.cache.json CHANGED
@@ -1 +1 @@
1
- {"files":{"CHANGELOG.md":"00d02f268a3ec6c19b3e400062121531f62ca624","README.md":"f15c5d39ebf4f3533c54068fcf4f0b754a305170","figma.json":"585af060194d03eaf24bdcd2bf42565b57379f08","package.json":"80bf112fc824e50cf4e554dff5eeffa3fda3126c","scripts/generate-svg-files.js":"401a1dc1ecae41f3ba2ceb64d4dce759cc694a67","scripts/generate-tsx-components.js":"e3450c12457850fd1cab4e6358e860cfd7bd8c5a"},"deps":{}}
1
+ {"files":{"lib":"1761841196580.4036","node_modules":"1761841182497.3242","CHANGELOG.md":"eff09a9a15a5ebbf2b08147a930929c4fe221417","README.md":"f15c5d39ebf4f3533c54068fcf4f0b754a305170","figma.json":"585af060194d03eaf24bdcd2bf42565b57379f08","package.json":"2cb7f6063621990e97c897c24ef7eba9fedee551","src/generate-svg-files.ts":"18687c8c5c7766b988257f5bef3d296b86fefaa8","src/generate-tsx-components.ts":"175a39c7f9b02a6f76422f06bf9b16ca25e9d028","src/index.ts":"9e03c9c45c92fc534262982bce233b91892c05f1","tsconfig.json":"298030567ca332304807fcdbb95d1218c9c0814f","tsup.config.ts":"4dc8bfd36076fff8fa1d84a48a072d40e6581810"},"deps":{}}
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @internal/icon
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - bd7290c: icon 관리 방식 전체 변경
8
+
3
9
  ## 1.0.0
4
10
 
5
11
  ### Major Changes
package/lib/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ declare const generateSvgFiles: () => void;
2
+
3
+ declare const generateTsxComponents: () => void;
4
+
5
+ export { generateSvgFiles, generateTsxComponents };
package/lib/index.js ADDED
@@ -0,0 +1,182 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // ../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/esm_shims.js
5
+ import path from "path";
6
+ import { fileURLToPath } from "url";
7
+ var getFilename = /* @__PURE__ */ __name(() => fileURLToPath(import.meta.url), "getFilename");
8
+ var getDirname = /* @__PURE__ */ __name(() => path.dirname(getFilename()), "getDirname");
9
+ var __dirname = /* @__PURE__ */ getDirname();
10
+
11
+ // src/generate-svg-files.ts
12
+ import fs from "fs";
13
+ import path2 from "path";
14
+ var generateSvgFiles = /* @__PURE__ */ __name(() => {
15
+ const figmaData = JSON.parse(fs.readFileSync(path2.resolve(__dirname, "../figma.json"), "utf8"));
16
+ function convertToCurrentColor(svgString) {
17
+ return svgString.replace(/#1E1E1F/g, "currentColor");
18
+ }
19
+ __name(convertToCurrentColor, "convertToCurrentColor");
20
+ function optimizeSvg(svgString) {
21
+ return svgString.replace(/\s+/g, " ").replace(/>\s+</g, "><").trim();
22
+ }
23
+ __name(optimizeSvg, "optimizeSvg");
24
+ const svgDir = "./";
25
+ if (!fs.existsSync(svgDir)) {
26
+ fs.mkdirSync(svgDir, { recursive: true });
27
+ }
28
+ Object.entries(figmaData).forEach(([iconName, variants]) => {
29
+ const iconDir = path2.join(svgDir, iconName);
30
+ if (!fs.existsSync(iconDir)) {
31
+ fs.mkdirSync(iconDir, { recursive: true });
32
+ }
33
+ Object.entries(variants).forEach(([variant, svgString]) => {
34
+ if (svgString && svgString.trim() !== "") {
35
+ const convertedSvg = convertToCurrentColor(svgString);
36
+ const optimizedSvg = optimizeSvg(convertedSvg);
37
+ const fileName = `${variant}.svg`;
38
+ const filePath = path2.join(iconDir, fileName);
39
+ fs.writeFileSync(filePath, optimizedSvg);
40
+ }
41
+ });
42
+ });
43
+ }, "generateSvgFiles");
44
+
45
+ // src/generate-tsx-components.ts
46
+ import fs2 from "fs";
47
+ import path3 from "path";
48
+ var generateTsxComponents = /* @__PURE__ */ __name(() => {
49
+ const FIGMA_DATA_PATH = path3.resolve(__dirname, "../figma.json");
50
+ const OUTPUT_DIR = "./assets";
51
+ const INDEX_FILE_PATH = "./index.tsx";
52
+ const COLOR = "type";
53
+ const FILL_TYPE = "fillType";
54
+ function convertToCurrentColor(svgString) {
55
+ return svgString.replace(/fill="#1E1E1F"/g, `style={{ fill: \`var(--\${${COLOR}})\` }}`).replace(/fill="white"/g, `style={{ fill: \`var(--\${${FILL_TYPE}})\` }}`);
56
+ }
57
+ __name(convertToCurrentColor, "convertToCurrentColor");
58
+ function normalizeSvgAttributes(svgString) {
59
+ return svgString.replace(/fill-rule/g, "fillRule").replace(/clip-rule/g, "clipRule").replace(/width="\d+"/, "width={iconSizeMap[size]}").replace(/height="\d+"/, "height={iconSizeMap[size]}").replace(
60
+ /xmlns="http:\/\/www.w3.org\/2000\/svg"/,
61
+ "ref={ref} className={className} {...props}"
62
+ ).replace(/style="[^"]*"/g, "").replace(/xmlns="http:\/\/www.w3.org\/1999\/xhtml"/g, "");
63
+ }
64
+ __name(normalizeSvgAttributes, "normalizeSvgAttributes");
65
+ const KEBAB_REGEX = /\p{Lu}/gu;
66
+ const kebabCase = /* @__PURE__ */ __name((str, keepLeadingDash = true) => {
67
+ const result = str.replace(KEBAB_REGEX, (match) => `-${match.toLowerCase()}`);
68
+ if (keepLeadingDash) {
69
+ return result;
70
+ }
71
+ if (result.startsWith("-")) {
72
+ return result.slice(1);
73
+ }
74
+ return result;
75
+ }, "kebabCase");
76
+ const generateIconName = /* @__PURE__ */ __name((iconName) => {
77
+ return iconName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
78
+ }, "generateIconName");
79
+ const IconProps = /* @__PURE__ */ __name((additionalProps = "") => `{
80
+ fill?: boolean;
81
+ thick?: boolean;
82
+ className?: string;
83
+ ${additionalProps}
84
+ ${COLOR}?: BasicColorType;
85
+ ${FILL_TYPE}?: BasicColorType;
86
+ }`, "IconProps");
87
+ function ensureDirectoryExists(dirPath) {
88
+ if (!fs2.existsSync(dirPath)) {
89
+ fs2.mkdirSync(dirPath, { recursive: true });
90
+ }
91
+ }
92
+ __name(ensureDirectoryExists, "ensureDirectoryExists");
93
+ function processSvgVariants(variants) {
94
+ const variantSvgs = {};
95
+ Object.entries(variants).forEach(([variant, svgString]) => {
96
+ if (svgString && svgString.trim() !== "") {
97
+ variantSvgs[variant] = normalizeSvgAttributes(convertToCurrentColor(svgString));
98
+ }
99
+ });
100
+ return variantSvgs;
101
+ }
102
+ __name(processSvgVariants, "processSvgVariants");
103
+ function generateTsxComponent(componentName, variantSvgs, defaultSvg) {
104
+ return `import React, { forwardRef } from 'react';
105
+ import { iconSizeMap } from '@liner-fe/design-token-primitive';
106
+ import { IconProps } from '../../index';
107
+
108
+ export const Icon${componentName} = forwardRef<SVGSVGElement, IconProps>(
109
+ ({ fill = false, thick = false, size = 'm', type = 'neutral-label-primary', className, fillType = 'inverse-label-primary', ...props }, ref) => {
110
+ if (fill && thick) {
111
+ return ${variantSvgs["fill-thick"] || defaultSvg};
112
+ } else if (fill) {
113
+ return ${variantSvgs.fill || defaultSvg};
114
+ } else if (thick) {
115
+ return ${variantSvgs.thick || defaultSvg};
116
+ }
117
+
118
+ return ${variantSvgs.default || defaultSvg};
119
+ }
120
+ );
121
+ `;
122
+ }
123
+ __name(generateTsxComponent, "generateTsxComponent");
124
+ function generateIconComponents() {
125
+ const figmaData = JSON.parse(fs2.readFileSync(FIGMA_DATA_PATH, "utf8"));
126
+ ensureDirectoryExists(OUTPUT_DIR);
127
+ Object.entries(figmaData).forEach(([iconName, variants]) => {
128
+ const iconDir = path3.join(OUTPUT_DIR, iconName);
129
+ ensureDirectoryExists(iconDir);
130
+ const variantSvgs = processSvgVariants(variants);
131
+ if (Object.keys(variantSvgs).length > 0) {
132
+ const componentName = generateIconName(iconName);
133
+ const defaultSvg = variantSvgs.default || variantSvgs.thick || Object.values(variantSvgs)[0];
134
+ const tsxCode = generateTsxComponent(componentName, variantSvgs, defaultSvg);
135
+ const filePath = path3.join(iconDir, "index.tsx");
136
+ fs2.writeFileSync(filePath, tsxCode);
137
+ }
138
+ });
139
+ }
140
+ __name(generateIconComponents, "generateIconComponents");
141
+ function generateIndexFile() {
142
+ const figmaData = JSON.parse(fs2.readFileSync(FIGMA_DATA_PATH, "utf8"));
143
+ const imports = Object.keys(figmaData).map((iconName) => {
144
+ const componentName = generateIconName(iconName);
145
+ return `export { Icon${componentName} } from './assets/${iconName}';`;
146
+ }).join("\n");
147
+ const iconKeys = Object.keys(figmaData).map((iconName) => {
148
+ return `"${kebabCase(generateIconName(iconName), false)}"`;
149
+ });
150
+ const indexContent = `import React, { SVGProps } from 'react';
151
+ import { BasicColorType, iconSizeMap } from '@liner-fe/design-token-primitive';
152
+ ${imports}
153
+
154
+ export type IconName = ${iconKeys.join(" | ")};
155
+
156
+ export type IconSizeKey = keyof typeof iconSizeMap;
157
+
158
+ export interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'fill' | 'name'> ${IconProps("size?: keyof typeof iconSizeMap;")}
159
+
160
+ export type IconComponentType = React.ForwardRefExoticComponent<
161
+ Omit<IconProps, 'ref'> & React.RefAttributes<SVGSVGElement>
162
+ >;
163
+ `;
164
+ fs2.writeFileSync(INDEX_FILE_PATH, indexContent, (err) => {
165
+ if (err) {
166
+ console.error(err);
167
+ }
168
+ console.log("Index file generated successfully");
169
+ });
170
+ }
171
+ __name(generateIndexFile, "generateIndexFile");
172
+ function main() {
173
+ generateIconComponents();
174
+ generateIndexFile();
175
+ }
176
+ __name(main, "main");
177
+ main();
178
+ }, "generateTsxComponents");
179
+ export {
180
+ generateSvgFiles,
181
+ generateTsxComponents
182
+ };
package/package.json CHANGED
@@ -1,13 +1,19 @@
1
1
  {
2
2
  "name": "@liner-fe/internal-icon",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
+ "type": "module",
4
5
  "publishConfig": {
5
6
  "access": "public"
6
7
  },
8
+ "devDependencies": {
9
+ "@types/node": "^24.9.2",
10
+ "tsup": "^8.5.0",
11
+ "typescript": "^5.9.3"
12
+ },
13
+ "main": "./lib/index.js",
14
+ "types": "./lib/index.d.ts",
7
15
  "scripts": {
8
- "generate-svg": "node scripts/generate-svg-files.js",
9
- "generate-tsx": "node scripts/generate-tsx-components.js",
10
- "build": "pnpm generate-svg && pnpm generate-tsx",
16
+ "build": "pnpm tsup --config ./tsup.config.ts",
11
17
  "build:package": "pnpm build"
12
18
  }
13
19
  }
@@ -0,0 +1,41 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export const generateSvgFiles = () => {
5
+ const figmaData = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../figma.json'), 'utf8'));
6
+
7
+ function convertToCurrentColor(svgString: string): string {
8
+ return svgString.replace(/#1E1E1F/g, 'currentColor');
9
+ }
10
+
11
+ function optimizeSvg(svgString: string): string {
12
+ return svgString.replace(/\s+/g, ' ').replace(/>\s+</g, '><').trim();
13
+ }
14
+
15
+ const svgDir = './';
16
+ if (!fs.existsSync(svgDir)) {
17
+ fs.mkdirSync(svgDir, { recursive: true });
18
+ }
19
+
20
+ Object.entries(figmaData).forEach(([iconName, variants]) => {
21
+ const iconDir = path.join(svgDir, iconName);
22
+ if (!fs.existsSync(iconDir)) {
23
+ fs.mkdirSync(iconDir, { recursive: true });
24
+ }
25
+
26
+ // @ts-ignore
27
+ Object.entries(variants).forEach(([variant, svgString]) => {
28
+ // @ts-ignore
29
+ if (svgString && svgString.trim() !== '') {
30
+ // @ts-ignore
31
+ const convertedSvg = convertToCurrentColor(svgString);
32
+ const optimizedSvg = optimizeSvg(convertedSvg);
33
+
34
+ const fileName = `${variant}.svg`;
35
+ const filePath = path.join(iconDir, fileName);
36
+
37
+ fs.writeFileSync(filePath, optimizedSvg);
38
+ }
39
+ });
40
+ });
41
+ };
@@ -0,0 +1,190 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export const generateTsxComponents = () => {
5
+ // 상수 정의
6
+ const FIGMA_DATA_PATH = path.resolve(__dirname, '../figma.json');
7
+ const OUTPUT_DIR = './assets';
8
+ const INDEX_FILE_PATH = './index.tsx';
9
+
10
+ const COLOR = 'type';
11
+ const FILL_TYPE = 'fillType';
12
+
13
+ // SVG 색상 변환 함수
14
+ function convertToCurrentColor(svgString: string): string {
15
+ return svgString
16
+ .replace(/fill="#1E1E1F"/g, `style={{ fill: \`var(--$\{${COLOR}\})\` }}`)
17
+ .replace(/fill="white"/g, `style={{ fill: \`var(--$\{${FILL_TYPE}\})\` }}`);
18
+ }
19
+
20
+ // SVG 속성 정규화 함수
21
+ function normalizeSvgAttributes(svgString: string): string {
22
+ return svgString
23
+ .replace(/fill-rule/g, 'fillRule')
24
+ .replace(/clip-rule/g, 'clipRule')
25
+ .replace(/width="\d+"/, 'width={iconSizeMap[size]}')
26
+ .replace(/height="\d+"/, 'height={iconSizeMap[size]}')
27
+ .replace(
28
+ /xmlns="http:\/\/www.w3.org\/2000\/svg"/,
29
+ 'ref={ref} className={className} {...props}',
30
+ )
31
+ .replace(/style="[^"]*"/g, '')
32
+ .replace(/xmlns="http:\/\/www.w3.org\/1999\/xhtml"/g, '');
33
+ }
34
+
35
+ // 케밥 케이스 변환 함수
36
+ const KEBAB_REGEX = /\p{Lu}/gu;
37
+ const kebabCase = (str: string, keepLeadingDash = true): string => {
38
+ const result = str.replace(KEBAB_REGEX, (match) => `-${match.toLowerCase()}`);
39
+
40
+ if (keepLeadingDash) {
41
+ return result;
42
+ }
43
+
44
+ if (result.startsWith('-')) {
45
+ return result.slice(1);
46
+ }
47
+
48
+ return result;
49
+ };
50
+
51
+ // 파스칼 케이스 변환 함수
52
+ const generateIconName = (iconName: string): string => {
53
+ return iconName
54
+ .split('-')
55
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
56
+ .join('');
57
+ };
58
+
59
+ // 아이콘 Props 인터페이스 생성 함수
60
+ const IconProps = (additionalProps = '') => `{
61
+ fill?: boolean;
62
+ thick?: boolean;
63
+ className?: string;
64
+ ${additionalProps}
65
+ ${COLOR}?: BasicColorType;
66
+ ${FILL_TYPE}?: BasicColorType;
67
+ }`;
68
+
69
+ // 디렉토리 생성 함수
70
+ function ensureDirectoryExists(dirPath: string): void {
71
+ if (!fs.existsSync(dirPath)) {
72
+ fs.mkdirSync(dirPath, { recursive: true });
73
+ }
74
+ }
75
+
76
+ // SVG 변형 처리 함수
77
+ function processSvgVariants(variants: Record<string, string>): Record<string, string> {
78
+ const variantSvgs = {};
79
+
80
+ Object.entries(variants).forEach(([variant, svgString]) => {
81
+ if (svgString && svgString.trim() !== '') {
82
+ // @ts-ignore
83
+ variantSvgs[variant] = normalizeSvgAttributes(convertToCurrentColor(svgString));
84
+ }
85
+ });
86
+
87
+ return variantSvgs;
88
+ }
89
+
90
+ // TSX 컴포넌트 코드 생성 함수
91
+ function generateTsxComponent(
92
+ componentName: string,
93
+ variantSvgs: Record<string, string>,
94
+ defaultSvg: string,
95
+ ): string {
96
+ return `import React, { forwardRef } from 'react';
97
+ import { iconSizeMap } from '@liner-fe/design-token-primitive';
98
+ import { IconProps } from '../../index';
99
+
100
+ export const Icon${componentName} = forwardRef<SVGSVGElement, IconProps>(
101
+ ({ fill = false, thick = false, size = 'm', type = 'neutral-label-primary', className, fillType = 'inverse-label-primary', ...props }, ref) => {
102
+ if (fill && thick) {
103
+ return ${variantSvgs['fill-thick'] || defaultSvg};
104
+ } else if (fill) {
105
+ return ${variantSvgs.fill || defaultSvg};
106
+ } else if (thick) {
107
+ return ${variantSvgs.thick || defaultSvg};
108
+ }
109
+
110
+ return ${variantSvgs.default || defaultSvg};
111
+ }
112
+ );
113
+ `;
114
+ }
115
+
116
+ // 메인 실행 함수
117
+ function generateIconComponents(): void {
118
+ const figmaData = JSON.parse(fs.readFileSync(FIGMA_DATA_PATH, 'utf8'));
119
+
120
+ // 출력 디렉토리 생성
121
+ ensureDirectoryExists(OUTPUT_DIR);
122
+
123
+ Object.entries(figmaData).forEach(([iconName, variants]) => {
124
+ const iconDir = path.join(OUTPUT_DIR, iconName);
125
+ ensureDirectoryExists(iconDir);
126
+
127
+ // @ts-ignore
128
+ const variantSvgs = processSvgVariants(variants);
129
+
130
+ if (Object.keys(variantSvgs).length > 0) {
131
+ const componentName = generateIconName(iconName);
132
+ const defaultSvg =
133
+ variantSvgs.default || variantSvgs.thick || Object.values(variantSvgs)[0];
134
+
135
+ const tsxCode = generateTsxComponent(componentName, variantSvgs, defaultSvg);
136
+ const filePath = path.join(iconDir, 'index.tsx');
137
+
138
+ fs.writeFileSync(filePath, tsxCode);
139
+ }
140
+ });
141
+ }
142
+
143
+ // 인덱스 파일 생성 함수
144
+ function generateIndexFile(): void {
145
+ const figmaData = JSON.parse(fs.readFileSync(FIGMA_DATA_PATH, 'utf8'));
146
+
147
+ const imports = Object.keys(figmaData)
148
+ .map((iconName) => {
149
+ const componentName = generateIconName(iconName);
150
+ return `export { Icon${componentName} } from './assets/${iconName}';`;
151
+ })
152
+ .join('\n');
153
+
154
+ const iconKeys = Object.keys(figmaData).map((iconName) => {
155
+ return `"${kebabCase(generateIconName(iconName), false)}"`;
156
+ });
157
+
158
+ const indexContent = `import React, { SVGProps } from 'react';
159
+ import { BasicColorType, iconSizeMap } from '@liner-fe/design-token-primitive';
160
+ ${imports}
161
+
162
+ export type IconName = ${iconKeys.join(' | ')};
163
+
164
+ export type IconSizeKey = keyof typeof iconSizeMap;
165
+
166
+ export interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'fill' | 'name'> ${IconProps('size?: keyof typeof iconSizeMap;')}
167
+
168
+ export type IconComponentType = React.ForwardRefExoticComponent<
169
+ Omit<IconProps, 'ref'> & React.RefAttributes<SVGSVGElement>
170
+ >;
171
+ `;
172
+
173
+ // @ts-ignore
174
+ fs.writeFileSync(INDEX_FILE_PATH, indexContent, (err) => {
175
+ if (err) {
176
+ console.error(err);
177
+ }
178
+
179
+ console.log('Index file generated successfully');
180
+ });
181
+ }
182
+
183
+ // 메인 실행
184
+ function main(): void {
185
+ generateIconComponents();
186
+ generateIndexFile();
187
+ }
188
+
189
+ main();
190
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './generate-svg-files';
2
+ export * from './generate-tsx-components';
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "target": "ESNext",
5
+ "emitDeclarationOnly": true,
6
+ "skipLibCheck": true,
7
+ "allowJs": true,
8
+ "incremental": false,
9
+ "rootDir": "src",
10
+ "outDir": "lib",
11
+ "module": "ESNext",
12
+ "esModuleInterop": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "declaration": true,
15
+ "strict": true,
16
+ "noImplicitAny": true,
17
+ "moduleResolution": "Bundler",
18
+ "paths": {
19
+ "@/*": ["./src/*"]
20
+ }
21
+ },
22
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
23
+ "exclude": ["node_modules", "lib"]
24
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ outDir: 'lib',
6
+ format: 'esm',
7
+ bundle: true,
8
+ minify: false,
9
+ keepNames: true,
10
+ clean: true,
11
+ loader: {
12
+ '.ts': 'ts',
13
+ '.tsx': 'tsx',
14
+ '.css': 'css',
15
+ '.ttf': 'file',
16
+ '.woff2': 'file',
17
+ },
18
+ tsconfig: './tsconfig.json',
19
+ platform: 'node',
20
+ dts: true,
21
+ outExtension: (ctx) => ({
22
+ js: '.js',
23
+ dts: '.d.ts',
24
+ }),
25
+ shims: true,
26
+ });
@@ -1,36 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- const figmaData = JSON.parse(fs.readFileSync('./figma.json', 'utf8'));
5
-
6
- function convertToCurrentColor(svgString) {
7
- return svgString.replace(/#1E1E1F/g, 'currentColor');
8
- }
9
-
10
- function optimizeSvg(svgString) {
11
- return svgString.replace(/\s+/g, ' ').replace(/>\s+</g, '><').trim();
12
- }
13
-
14
- const svgDir = './';
15
- if (!fs.existsSync(svgDir)) {
16
- fs.mkdirSync(svgDir, { recursive: true });
17
- }
18
-
19
- Object.entries(figmaData).forEach(([iconName, variants]) => {
20
- const iconDir = path.join(svgDir, iconName);
21
- if (!fs.existsSync(iconDir)) {
22
- fs.mkdirSync(iconDir, { recursive: true });
23
- }
24
-
25
- Object.entries(variants).forEach(([variant, svgString]) => {
26
- if (svgString && svgString.trim() !== '') {
27
- const convertedSvg = convertToCurrentColor(svgString);
28
- const optimizedSvg = optimizeSvg(convertedSvg);
29
-
30
- const fileName = `${variant}.svg`;
31
- const filePath = path.join(iconDir, fileName);
32
-
33
- fs.writeFileSync(filePath, optimizedSvg);
34
- }
35
- });
36
- });
@@ -1,179 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- // 상수 정의
5
- const FIGMA_DATA_PATH = './figma.json';
6
- const OUTPUT_DIR = './assets';
7
- const INDEX_FILE_PATH = './index.tsx';
8
-
9
- const COLOR = 'type';
10
- const FILL_TYPE = 'fillType';
11
-
12
- // SVG 색상 변환 함수
13
- function convertToCurrentColor(svgString) {
14
- return svgString
15
- .replace(/fill="#1E1E1F"/g, `style={{ fill: \`var(--$\{${COLOR}\})\` }}`)
16
- .replace(/fill="white"/g, `style={{ fill: \`var(--$\{${FILL_TYPE}\})\` }}`);
17
- }
18
-
19
- // SVG 속성 정규화 함수
20
- function normalizeSvgAttributes(svgString) {
21
- return svgString
22
- .replace(/fill-rule/g, 'fillRule')
23
- .replace(/clip-rule/g, 'clipRule')
24
- .replace(/width="\d+"/, 'width={iconSizeMap[size]}')
25
- .replace(/height="\d+"/, 'height={iconSizeMap[size]}')
26
- .replace(/xmlns="http:\/\/www.w3.org\/2000\/svg"/, 'ref={ref} className={className} {...props}')
27
- .replace(/style="[^"]*"/g, '')
28
- .replace(/xmlns="http:\/\/www.w3.org\/1999\/xhtml"/g, '');
29
- }
30
-
31
- // 케밥 케이스 변환 함수
32
- const KEBAB_REGEX = /\p{Lu}/gu;
33
- const kebabCase = (str, keepLeadingDash = true) => {
34
- const result = str.replace(KEBAB_REGEX, (match) => `-${match.toLowerCase()}`);
35
-
36
- if (keepLeadingDash) {
37
- return result;
38
- }
39
-
40
- if (result.startsWith('-')) {
41
- return result.slice(1);
42
- }
43
-
44
- return result;
45
- };
46
-
47
- // 파스칼 케이스 변환 함수
48
- const generateIconName = (iconName) => {
49
- return iconName
50
- .split('-')
51
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
52
- .join('');
53
- };
54
-
55
- // 아이콘 Props 인터페이스 생성 함수
56
- const IconProps = (additionalProps = '') => `{
57
- fill?: boolean;
58
- thick?: boolean;
59
- className?: string;
60
- ${additionalProps}
61
- ${COLOR}?: BasicColorType;
62
- ${FILL_TYPE}?: BasicColorType;
63
- }`;
64
-
65
- // 디렉토리 생성 함수
66
- function ensureDirectoryExists(dirPath) {
67
- if (!fs.existsSync(dirPath)) {
68
- fs.mkdirSync(dirPath, { recursive: true });
69
- }
70
- }
71
-
72
- // SVG 변형 처리 함수
73
- function processSvgVariants(variants) {
74
- const variantSvgs = {};
75
-
76
- Object.entries(variants).forEach(([variant, svgString]) => {
77
- if (svgString && svgString.trim() !== '') {
78
- variantSvgs[variant] = normalizeSvgAttributes(convertToCurrentColor(svgString));
79
- }
80
- });
81
-
82
- return variantSvgs;
83
- }
84
-
85
- // TSX 컴포넌트 코드 생성 함수
86
- function generateTsxComponent(componentName, variantSvgs, defaultSvg) {
87
- return `import React, { forwardRef } from 'react';
88
- import { iconSizeMap } from '@liner-fe/design-token-primitive';
89
- import { IconProps } from '../../index';
90
-
91
- export const Icon${componentName} = forwardRef<SVGSVGElement, IconProps>(
92
- ({ fill = false, thick = false, size = 'm', type = 'neutral-label-primary', className, fillType = 'inverse-label-primary', ...props }, ref) => {
93
- if (fill && thick) {
94
- return ${variantSvgs['fill-thick'] || defaultSvg};
95
- } else if (fill) {
96
- return ${variantSvgs.fill || defaultSvg};
97
- } else if (thick) {
98
- return ${variantSvgs.thick || defaultSvg};
99
- }
100
-
101
- return ${variantSvgs.default || defaultSvg};
102
- }
103
- );
104
- `;
105
- }
106
-
107
- // 메인 실행 함수
108
- function generateIconComponents() {
109
- const figmaData = JSON.parse(fs.readFileSync(FIGMA_DATA_PATH, 'utf8'));
110
-
111
- // 출력 디렉토리 생성
112
- ensureDirectoryExists(OUTPUT_DIR);
113
-
114
- Object.entries(figmaData).forEach(([iconName, variants]) => {
115
- const iconDir = path.join(OUTPUT_DIR, iconName);
116
- ensureDirectoryExists(iconDir);
117
-
118
- const variantSvgs = processSvgVariants(variants);
119
-
120
- if (Object.keys(variantSvgs).length > 0) {
121
- const componentName = generateIconName(iconName);
122
- const defaultSvg = variantSvgs.default || variantSvgs.thick || Object.values(variantSvgs)[0];
123
-
124
- const tsxCode = generateTsxComponent(componentName, variantSvgs, defaultSvg);
125
- const filePath = path.join(iconDir, 'index.tsx');
126
-
127
- fs.writeFileSync(filePath, tsxCode);
128
- }
129
- });
130
- }
131
-
132
- // 인덱스 파일 생성 함수
133
- function generateIndexFile() {
134
- const figmaData = JSON.parse(fs.readFileSync(FIGMA_DATA_PATH, 'utf8'));
135
-
136
- const imports = Object.keys(figmaData)
137
- .map((iconName) => {
138
- const componentName = generateIconName(iconName);
139
- return `export { Icon${componentName} } from './assets/${iconName}';`;
140
- })
141
- .join('\n');
142
-
143
- const iconKeys = Object.keys(figmaData).map((iconName) => {
144
- return `"${kebabCase(generateIconName(iconName), false)}"`;
145
- });
146
-
147
- const indexContent = `import React, { SVGProps } from 'react';
148
- import { BasicColorType, iconSizeMap } from '@liner-fe/design-token-primitive';
149
- ${imports}
150
-
151
- export type IconName = ${iconKeys.join(' | ')};
152
-
153
- export type IconSizeKey = keyof typeof iconSizeMap;
154
-
155
- export interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'fill' | 'name'> ${IconProps('size?: keyof typeof iconSizeMap;')}
156
-
157
- export type IconComponentType = React.ForwardRefExoticComponent<
158
- Omit<IconProps, 'ref'> & React.RefAttributes<SVGSVGElement>
159
- >;
160
- `;
161
-
162
- fs.writeFileSync(INDEX_FILE_PATH, indexContent, (err) => {
163
- if (err) {
164
- console.error(err);
165
- }
166
-
167
- console.log('Index file generated successfully');
168
- });
169
- }
170
-
171
- // 메인 실행
172
- function main() {
173
- generateIconComponents();
174
- generateIndexFile();
175
- }
176
-
177
- console.log('실행');
178
-
179
- main();