home 6 сар өмнө
commit
e1f63c1580

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 38 - 0
README.md

@@ -0,0 +1,38 @@
+### 프로젝트 초기 설정
+##### 1. 프로젝트 생성
+```
+npm create vite@latest ucell-clone -- --template react (TypeScript 사용시 --template react-ts)
+```
+
+##### 2. 라이브러리 설치
+- tailwindcss : 유틸리티 기반 CSS 프레임워크
+- postcss : Tailwind의 CSS 빌드 및 후처리 엔진
+- autoprefixer : 브라우저 호환 CSS 프리픽스 자동 추가
+```
+npm install tailwindcss @tailwindcss/vite
+```
+
+##### 3. 📁 기본 폴더 구조
+```
+ucell-clone/
+├── public/ # 정적 파일
+├── src/
+│ ├── assets/ # 이미지 및 기타 정적 리소스
+│ ├── components/ # 재사용 가능한 UI 컴포넌트
+│ ├── pages/ # 페이지 단위 컴포넌트
+│ ├── layouts/ # 공통 레이아웃 컴포넌트
+│ ├── App.jsx # 루트 컴포넌트
+│ ├── main.jsx # 진입점
+│ └── index.css # Tailwind 지시어 포함
+├── tailwind.config.js # Tailwind 설정 파일
+├── postcss.config.js # PostCSS 설정 파일
+├── vite.config.js # Vite 설정 파일
+├── package.json
+└── README.md
+```
+
+
+
+### 시작
+1. npm install
+2. npm run dev

+ 33 - 0
eslint.config.js

@@ -0,0 +1,33 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+
+export default [
+  { ignores: ['dist'] },
+  {
+    files: ['**/*.{js,jsx}'],
+    languageOptions: {
+      ecmaVersion: 2020,
+      globals: globals.browser,
+      parserOptions: {
+        ecmaVersion: 'latest',
+        ecmaFeatures: { jsx: true },
+        sourceType: 'module',
+      },
+    },
+    plugins: {
+      'react-hooks': reactHooks,
+      'react-refresh': reactRefresh,
+    },
+    rules: {
+      ...js.configs.recommended.rules,
+      ...reactHooks.configs.recommended.rules,
+      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
+      'react-refresh/only-export-components': [
+        'warn',
+        { allowConstantExport: true },
+      ],
+    },
+  },
+]

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + React</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.jsx"></script>
+  </body>
+</html>

+ 30 - 0
package.json

@@ -0,0 +1,30 @@
+{
+  "name": "ucell-clone",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "lint": "eslint .",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@tailwindcss/vite": "^4.1.7",
+    "react": "^19.1.0",
+    "react-dom": "^19.1.0",
+    "react-icons": "^5.5.0",
+    "tailwindcss": "^4.1.7"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.25.0",
+    "@types/react": "^19.1.2",
+    "@types/react-dom": "^19.1.2",
+    "@vitejs/plugin-react": "^4.4.1",
+    "eslint": "^9.25.0",
+    "eslint-plugin-react-hooks": "^5.2.0",
+    "eslint-plugin-react-refresh": "^0.4.19",
+    "globals": "^16.0.0",
+    "vite": "^6.3.5"
+  }
+}

+ 1 - 0
public/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 14 - 0
src/App.jsx

@@ -0,0 +1,14 @@
+import ResponsiveNavbar from "./components/ResponsiveNavbar.jsx";
+
+const App = () => {
+    return (
+        <>
+            <ResponsiveNavbar />
+            {/*<main className="pt-20">*/}
+            {/*    <h1>유셀네트웍스 페이지입니다</h1>*/}
+            {/*</main>*/}
+        </>
+    );
+};
+
+export default App;

BIN
src/assets/logo.png


+ 78 - 0
src/components/ResponsiveNavbar.jsx

@@ -0,0 +1,78 @@
+import { useState } from 'react';
+import { FaBars, FaTimes, FaChevronDown } from 'react-icons/fa';
+import logo from '../assets/logo.png';
+
+const menuItems = [
+    { label: '회사소개', children: [] },
+    { label: '사업소개', children: [] },
+    { label: '사업실적', children: [] },
+    { label: '고객지원', children: [] },
+];
+
+const ResponsiveNavbar = () => {
+    const [isOpen, setIsOpen] = useState(false);
+    const [expandedMenu, setExpandedMenu] = useState(null);
+
+    const toggleMobileMenu = () => {
+        setIsOpen(prev => !prev);
+    };
+
+    const toggleSubMenu = (index) => {
+        setExpandedMenu(prev => (prev === index ? null : index));
+    };
+
+    return (
+        <header className="fixed top-0 left-0 w-full z-50 bg-white shadow">
+            <div className="flex items-center justify-between px-4 md:px-10 py-4">
+                <img src={logo} alt="Logo" className="h-8" />
+                {/* Desktop Nav */}
+                <nav className="hidden md:flex space-x-10 font-semibold">
+                    {menuItems.map((item, idx) => (
+                        <a href="#" key={idx} className="hover:text-blue-600">
+                            {item.label}
+                        </a>
+                    ))}
+                </nav>
+                {/* Mobile Toggle */}
+                <button onClick={toggleMobileMenu} className="md:hidden text-2xl">
+                    {isOpen ? <FaTimes /> : <FaBars />}
+                </button>
+            </div>
+
+            {/* Mobile Menu */}
+            {isOpen && (
+                <div className="md:hidden bg-white w-full h-screen fixed top-0 right-0 p-4 pt-20 z-40 overflow-auto">
+                    <img src={logo} alt="Logo" className="h-8 mb-6" />
+                    <ul>
+                        {menuItems.map((item, idx) => (
+                            <li key={idx} className="mb-4">
+                                <button
+                                    onClick={() => toggleSubMenu(idx)}
+                                    className="flex justify-between items-center w-full font-semibold text-lg"
+                                >
+                                    {item.label}
+                                    <FaChevronDown
+                                        className={`ml-2 transform transition-transform ${
+                                            expandedMenu === idx ? 'rotate-180' : ''
+                                        }`}
+                                    />
+                                </button>
+                                {expandedMenu === idx && (
+                                    <ul className="ml-4 mt-2 space-y-2">
+                                        {(item.children.length > 0 ? item.children : ['하위 메뉴 준비중']).map((sub, i) => (
+                                            <li key={i} className="text-gray-600">
+                                                <a href="#">{sub}</a>
+                                            </li>
+                                        ))}
+                                    </ul>
+                                )}
+                            </li>
+                        ))}
+                    </ul>
+                </div>
+            )}
+        </header>
+    );
+};
+
+export default ResponsiveNavbar;

+ 31 - 0
src/index.css

@@ -0,0 +1,31 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* 전역 글꼴 및 렌더링 설정 */
+:root {
+  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+  margin: 0;
+  min-width: 320px;
+  min-height: 100vh;
+  background-color: #ffffff;
+  color: #213547;
+}
+
+/* 다크모드 대응을 위한 prefers-color-scheme */
+@media (prefers-color-scheme: dark) {
+  body {
+    background-color: #242424;
+    color: rgba(255, 255, 255, 0.87);
+  }
+}

+ 5 - 0
src/main.jsx

@@ -0,0 +1,5 @@
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.jsx'
+
+createRoot(document.getElementById('root')).render(<App />)

+ 11 - 0
vite.config.js

@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite'
+import tailwindcss from '@tailwindcss/vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [
+      react(),
+      tailwindcss()
+  ],
+})