diff --git a/src/app/preview/tech-stack/page.tsx b/src/app/preview/tech-stack/page.tsx new file mode 100644 index 0000000..5d468e3 --- /dev/null +++ b/src/app/preview/tech-stack/page.tsx @@ -0,0 +1,106 @@ +import { backend, design, frontend } from '@/components/ui/Icon/iconRegistry'; +import React from 'react'; + +export default function TechStackPage() { + return ( +
+ {/* Frontend Section */} +
+

Frontend Technologies

+ + {/* Core */} +
+

Core

+
+ + + + +
+
+ + + + +
+
+ + {/* Framework & Libraries */} +
+

Framework & Libraries

+
+ + +
+
+ + {/* Mobile & Others */} +
+

Mobile & Others

+
+ + + + + + +
+
+
+ + {/* Backend Section */} +
+

Backend Technologies

+ + {/* Language & Runtime */} +
+

Language & Runtime

+
+ + + + + +
+
+ + {/* Framework */} +
+

Framework

+
+ + + + +
+
+ + {/* Database & Infrastructure */} +
+

+ Database & Infrastructure +

+
+ + + + + + + +
+
+
+ + {/* Design Tools */} +
+

Design Tools

+
+ + + +
+
+
+ ); +} diff --git a/src/components/ui/Icon/BaseIcon.tsx b/src/components/ui/Icon/BaseIcon.tsx new file mode 100644 index 0000000..29b81e2 --- /dev/null +++ b/src/components/ui/Icon/BaseIcon.tsx @@ -0,0 +1,46 @@ +import { cn } from '@/util/cn'; + +export interface BaseIconProps { + color?: string; + size?: number; + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'; + className?: string; + path: string; + ariaLabel?: string; + title?: string; +} + +export function BaseIcon({ + color, + size = 24, + radius = 'none', + className, + path, + ariaLabel, + title, +}: BaseIconProps) { + const radiusMap = { + none: 'rounded-none', + sm: 'rounded-sm', + md: 'rounded', + lg: 'rounded-lg', + full: 'rounded-full', + }; + + return ( + + {title && {title}} + + + ); +} diff --git a/src/components/ui/Icon/IconData.ts b/src/components/ui/Icon/IconData.ts new file mode 100644 index 0000000..8c2d4c3 --- /dev/null +++ b/src/components/ui/Icon/IconData.ts @@ -0,0 +1,268 @@ +import getPath from './getPath'; + +export interface IconConfig { + name: string; + color: string; + path: string; + category: 'frontend' | 'backend' | 'design'; +} + +export const ICON_LIST: IconConfig[] = [ + { + name: 'Javascript', + color: '#F7DF1E', + path: getPath( + 'JavaScript', + ), + category: 'frontend', + }, + { + name: 'Typescript', + color: '#3178C6', + path: getPath( + 'TypeScript', + ), + category: 'frontend', + }, + { + name: 'React', + color: '#61DAFB', + path: getPath( + 'React', + ), + category: 'frontend', + }, + { + name: 'Vue', + color: '#4FC08D', + path: getPath( + 'Vue.js', + ), + category: 'frontend', + }, + { + name: 'Next', + color: '#000000', + path: getPath( + 'Next.js', + ), + category: 'frontend', + }, + { + name: 'Svelte', + color: '#FF3E00', + path: 'M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.186 1.092l.208.063-.02.208a1.253 1.253 0 0 0 .226.83 1.337 1.337 0 0 0 1.435.533 1.231 1.231 0 0 0 .343-.15l5.59-3.562a1.164 1.164 0 0 0 .524-.778 1.242 1.242 0 0 0-.211-.937 1.338 1.338 0 0 0-1.435-.533 1.23 1.23 0 0 0-.343.15l-2.133 1.36a4.078 4.078 0 0 1-1.135.499 4.44 4.44 0 0 1-4.765-1.766 4.108 4.108 0 0 1-.702-3.108 3.855 3.855 0 0 1 1.742-2.582l5.589-3.563a4.072 4.072 0 0 1 1.135-.499 4.44 4.44 0 0 1 4.765 1.767 4.109 4.109 0 0 1 .703 3.107 3.943 3.943 0 0 1-.134.522l-.105.321-.286-.21a7.204 7.204 0 0 0-2.187-1.093l-.208-.063.02-.207a1.255 1.255 0 0 0-.226-.831 1.337 1.337 0 0 0-1.435-.532 1.231 1.231 0 0 0-.343.15L8.62 9.368a1.162 1.162 0 0 0-.524.778 1.24 1.24 0 0 0 .211.937 1.338 1.338 0 0 0 1.435.533 1.235 1.235 0 0 0 .344-.151l2.132-1.36a4.067 4.067 0 0 1 1.135-.498 4.44 4.44 0 0 1 4.765 1.766 4.108 4.108 0 0 1 .702 3.108 3.857 3.857 0 0 1-1.742 2.583l-5.589 3.562a4.072 4.072 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.412 6.412 0 0 0-2.896 4.295 6.753 6.753 0 0 0 .666 4.336 6.43 6.43 0 0 0-.96 2.396 6.833 6.833 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.41 6.41 0 0 0 2.896-4.295 6.756 6.756 0 0 0-.665-4.336 6.429 6.429 0 0 0 .958-2.396 6.831 6.831 0 0 0-1.167-5.168Z', + category: 'frontend', + }, + + { + name: 'Flutter', + color: '#02569B', + path: getPath( + 'Flutter', + ), + category: 'frontend', + }, + { + name: 'Swift', + color: '#F05138', + path: getPath( + 'Swift', + ), + category: 'frontend', + }, + { + name: 'Kotlin', + color: '#7F52FF', + path: getPath( + 'Kotlin', + ), + category: 'frontend', + }, + { + name: 'ReactNative', + color: '#61DAFB', + path: 'M12 9.861A2.139 2.139 0 1 0 12 14.139 2.139 2.139 0 1 0 12 9.861zM6.008 16.255l-.472-.12C2.018 15.246 0 13.737 0 11.996s2.018-3.25 5.536-4.139l.472-.119.133.468a23.53 23.53 0 0 0 1.363 3.578l.101.213-.101.213a23.307 23.307 0 0 0-1.363 3.578l-.133.469zM5.317 8.95c-2.674.751-4.315 1.9-4.315 3.046 0 1.145 1.641 2.294 4.315 3.046a24.95 24.95 0 0 1 1.182-3.046A24.752 24.752 0 0 1 5.317 8.95zM17.992 16.255l-.133-.469a23.357 23.357 0 0 0-1.364-3.578l-.101-.213.101-.213a23.42 23.42 0 0 0 1.364-3.578l.133-.468.473.119c3.517.889 5.535 2.398 5.535 4.139s-2.018 3.25-5.535 4.139l-.473.12zm-.491-4.259c.48 1.039.877 2.06 1.182 3.046 2.675-.752 4.315-1.901 4.315-3.046 0-1.146-1.641-2.294-4.315-3.046a24.788 24.788 0 0 1-1.182 3.046zM5.31 8.945l-.133-.467C4.188 4.992 4.488 2.494 6 1.622c1.483-.856 3.864.155 6.359 2.716l.34.349-.34.349a23.552 23.552 0 0 0-2.422 2.967l-.135.193-.235.02a23.657 23.657 0 0 0-3.785.61l-.472.119zm1.896-6.63c-.268 0-.505.058-.705.173-.994.573-1.17 2.565-.485 5.253a25.122 25.122 0 0 1 3.233-.501 24.847 24.847 0 0 1 2.052-2.544c-1.56-1.519-3.037-2.381-4.095-2.381zM16.795 22.677c-.001 0-.001 0 0 0-1.425 0-3.255-1.073-5.154-3.023l-.34-.349.34-.349a23.53 23.53 0 0 0 2.421-2.968l.135-.193.234-.02a23.63 23.63 0 0 0 3.787-.609l.472-.119.134.468c.987 3.484.688 5.983-.824 6.854a2.38 2.38 0 0 1-1.205.308zm-4.096-3.381c1.56 1.519 3.037 2.381 4.095 2.381h.001c.267 0 .505-.058.704-.173.994-.573 1.171-2.566.485-5.254a25.02 25.02 0 0 1-3.234.501 24.674 24.674 0 0 1-2.051 2.545zM18.69 8.945l-.472-.119a23.479 23.479 0 0 0-3.787-.61l-.234-.02-.135-.193a23.414 23.414 0 0 0-2.421-2.967l-.34-.349.34-.349C14.135 1.778 16.515.767 18 1.622c1.512.872 1.812 3.37.824 6.855l-.134.468zM14.75 7.24c1.142.104 2.227.273 3.234.501.686-2.688.509-4.68-.485-5.253-.988-.571-2.845.304-4.8 2.208A24.849 24.849 0 0 1 14.75 7.24zM7.206 22.677A2.38 2.38 0 0 1 6 22.369c-1.512-.871-1.812-3.369-.823-6.854l.132-.468.472.119c1.155.291 2.429.496 3.785.609l.235.02.134.193a23.596 23.596 0 0 0 2.422 2.968l.34.349-.34.349c-1.898 1.95-3.728 3.023-5.151 3.023zm-1.19-6.427c-.686 2.688-.509 4.681.485 5.254.987.563 2.843-.305 4.8-2.208a24.998 24.998 0 0 1-2.052-2.545 24.976 24.976 0 0 1-3.233-.501zM12 16.878c-.823 0-1.669-.036-2.516-.106l-.235-.02-.135-.193a30.388 30.388 0 0 1-1.35-2.122 30.354 30.354 0 0 1-1.166-2.228l-.1-.213.1-.213a30.3 30.3 0 0 1 1.166-2.228c.414-.716.869-1.43 1.35-2.122l.135-.193.235-.02a29.785 29.785 0 0 1 5.033 0l.234.02.134.193a30.006 30.006 0 0 1 2.517 4.35l.101.213-.101.213a29.6 29.6 0 0 1-2.517 4.35l-.134.193-.234.02c-.847.07-1.694.106-2.517.106zm-2.197-1.084c1.48.111 2.914.111 4.395 0a29.006 29.006 0 0 0 2.196-3.798 28.585 28.585 0 0 0-2.197-3.798 29.031 29.031 0 0 0-4.394 0 28.477 28.477 0 0 0-2.197 3.798 29.114 29.114 0 0 0 2.197 3.798z', + category: 'frontend', + }, + { + name: 'Unity', + color: '#FFFFFF', + path: getPath( + 'Unity', + ), + category: 'frontend', + }, + { + name: 'Jest', + color: '#C21325', + path: getPath( + 'Jest', + ), + category: 'frontend', + }, + { + name: 'Spring', + color: '#6DB33F', + path: getPath( + 'Spring', + ), + category: 'backend', + }, + { + name: 'Node', + color: '#5FA04E', + path: getPath( + 'Node.js', + ), + category: 'backend', + }, + { + name: 'Nest', + color: '#E0234E', + path: getPath( + 'NestJS', + ), + category: 'backend', + }, + { + name: 'Express', + color: '#000000', + path: getPath( + 'Express', + ), + category: 'backend', + }, + { + name: 'Go', + color: '#00ADD8', + path: getPath( + 'Go', + ), + category: 'backend', + }, + { + name: 'C', + color: '#A8B9CC', + path: getPath( + 'C', + ), + category: 'backend', + }, + { + name: 'Python', + color: '#3776AB', + path: getPath( + 'Python', + ), + category: 'backend', + }, + { + name: 'Django', + color: '#092E20', + path: getPath( + 'Django', + ), + category: 'backend', + }, + { + name: 'MySQL', + color: '#4479A1', + path: getPath( + 'MySQL', + ), + category: 'backend', + }, + { + name: 'MongoDB', + color: '#47A248', + path: getPath( + 'MongoDB', + ), + category: 'backend', + }, + { + name: 'PHP', + color: '#777BB4', + path: getPath( + 'PHP', + ), + category: 'backend', + }, + { + name: 'GraphQL', + color: '#E10098', + path: getPath( + 'GraphQL', + ), + category: 'backend', + }, + { + name: 'Firebase', + color: '#DD2C00', + path: getPath( + 'Firebase', + ), + category: 'backend', + }, + { + name: 'AWS', + color: '#FF9900', + path: getPath( + 'AWS Lambda', + ), + category: 'backend', + }, + { + name: 'Kubernetes', + color: '#326CE5', + path: getPath( + 'Kubernetes', + ), + category: 'backend', + }, + { + name: 'Docker', + color: '#2496ED', + path: getPath( + 'Docker', + ), + category: 'backend', + }, + { + name: 'Git', + color: '#F05032', + path: getPath( + 'Git', + ), + category: 'backend', + }, + { + name: 'Java', + color: '#007396', + path: 'M8.851 18.56s-.917.534.653.714c1.902.218 2.874.187 4.969-.211 0 0 .552.346 1.321.646-4.699 2.013-10.633-.118-6.943-1.149M8.276 15.933s-1.028.761.542.924c2.032.209 3.636.227 6.413-.308 0 0 .384.389.987.602-5.679 1.661-12.007.13-7.942-1.218M13.116 11.475c1.158 1.333-.304 2.533-.304 2.533s2.939-1.518 1.589-3.418c-1.261-1.772-2.228-2.652 3.007-5.688 0-.001-8.216 2.051-4.292 6.573M19.33 20.504s.679.559-.747.991c-2.712.822-11.288 1.069-13.669.033-.856-.373.75-.89 1.254-.998.527-.114.828-.093.828-.093-.953-.671-6.156 1.317-2.643 1.887 9.58 1.553 17.462-.7 14.977-1.82M9.292 13.21s-4.362 1.036-1.544 1.412c1.189.159 3.561.123 5.77-.062 1.806-.152 3.618-.477 3.618-.477s-.637.272-1.098.587c-4.429 1.165-12.986.623-10.522-.568 2.082-1.006 3.776-.892 3.776-.892M17.116 17.584c4.503-2.34 2.421-4.589.968-4.285-.355.074-.515.138-.515.138s.132-.207.385-.297c2.875-1.011 5.086 2.981-.928 4.562 0-.001.07-.062.09-.118M14.401 0s2.494 2.494-2.365 6.33c-3.896 3.077-.888 4.832-.001 6.836-2.274-2.053-3.943-3.858-2.824-5.539 1.644-2.469 6.197-3.665 5.19-7.627M9.734 23.924c4.322.277 10.959-.153 11.116-2.198 0 0-.302.775-3.572 1.391-3.688.694-8.239.613-10.937.168 0 0 .553.457 3.393.639', + category: 'backend', + }, + { + name: 'Figma', + color: '#F24E1E', + path: getPath( + 'Figma', + ), + category: 'design', + }, + { + name: 'Zeplin', + color: '#FDBD39', + path: 'M12 0C5.352 0 0 5.352 0 12s5.352 12 12 12c6.648 0 12-5.352 12-12S18.648 0 12 0zm-.24 4.8h.48c4.08 0 7.44 3.36 7.44 7.44v.48c0 4.08-3.36 7.44-7.44 7.44h-.48c-4.08 0-7.44-3.36-7.44-7.44v-.48c0-4.08 3.36-7.44 7.44-7.44zM12 16.8c2.64 0 4.8-2.16 4.8-4.8 0-2.64-2.16-4.8-4.8-4.8-2.64 0-4.8 2.16-4.8 4.8 0 2.64 2.16 4.8 4.8 4.8z', + category: 'design', + }, + { + name: 'Sketch', + color: '#F7B500', + path: getPath( + 'Sketch', + ), + category: 'design', + }, +]; diff --git a/src/components/ui/Icon/getPath.ts b/src/components/ui/Icon/getPath.ts new file mode 100644 index 0000000..a549fe8 --- /dev/null +++ b/src/components/ui/Icon/getPath.ts @@ -0,0 +1,4 @@ +export default function getPath(svgString: string): string { + const pathMatch = svgString.match(/d="([^"]+)"/); + return pathMatch ? pathMatch[1] : ''; +} diff --git a/src/components/ui/Icon/iconRegistry.ts b/src/components/ui/Icon/iconRegistry.ts new file mode 100644 index 0000000..06d2660 --- /dev/null +++ b/src/components/ui/Icon/iconRegistry.ts @@ -0,0 +1,47 @@ +import { createElement } from 'react'; + +import { BaseIcon, BaseIconProps } from './BaseIcon'; +import { ICON_LIST, IconConfig } from './IconData'; + +type IconProps = Omit; + +function createIconComponent(config: IconConfig) { + return function Icon(props: IconProps) { + return createElement(BaseIcon, { + ...props, + color: props.color ?? config.color, + path: config.path, + title: `${config.name} Icon`, + ariaLabel: config.name, + }); + }; +} + +// 카테고리별로 아이콘 분류 및 컴포넌트 생성 +export const Icons = ICON_LIST.reduce< + Record JSX.Element>> +>((acc, config) => { + const category = config.category; + const componentName = `${config.name}Icon`; + + if (!acc[category]) { + acc[category] = {}; + } + + acc[category][componentName] = createIconComponent(config); + return acc; +}, {}); + +// 카테고리별 export +export const { frontend, backend, design } = Icons; + +// 모든 아이콘 한번에 export +export const AllIcons = Object.values(Icons).reduce< + Record JSX.Element> +>( + (acc, categoryIcons) => ({ + ...acc, + ...categoryIcons, + }), + {}, +);