diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9204d40 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM node:22.16.0-alpine + +WORKDIR /app + +# Установка зависимостей +COPY package*.json ./ +RUN npm install + +# Копирование исходного кода +COPY . . + +# Открытие порта +EXPOSE 3000 + +# Запуск приложения +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..984ab01 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + app: + build: . + ports: + - "3000:3000" + volumes: + - .:/app + - /app/node_modules + environment: + - NODE_ENV=development + command: npm run dev \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 978bb35..e218724 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "app-core", "version": "0.1.0", "dependencies": { + "clsx": "^2.1.1", "next": "15.3.3", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "sass": "^1.89.2" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -863,6 +865,315 @@ "node": ">=12.4.0" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1790,7 +2101,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -1907,12 +2218,36 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -2848,7 +3183,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3189,6 +3524,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", + "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3387,7 +3728,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3432,7 +3773,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -3471,7 +3812,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -3845,7 +4186,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -3980,6 +4321,13 @@ } } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4221,7 +4569,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -4349,6 +4697,19 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4524,6 +4885,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sass": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", + "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -5015,7 +5396,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" diff --git a/package.json b/package.json index c190ae0..c03f0c1 100644 --- a/package.json +++ b/package.json @@ -9,17 +9,19 @@ "lint": "next lint" }, "dependencies": { + "clsx": "^2.1.1", + "next": "15.3.3", "react": "^19.0.0", "react-dom": "^19.0.0", - "next": "15.3.3" + "sass": "^1.89.2" }, "devDependencies": { - "typescript": "^5", + "@eslint/eslintrc": "^3", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.3.3", - "@eslint/eslintrc": "^3" + "typescript": "^5" } } diff --git a/public/adviser-card.png b/public/adviser-card.png new file mode 100644 index 0000000..dc6a328 Binary files /dev/null and b/public/adviser-card.png differ diff --git a/public/compatibility-card.png b/public/compatibility-card.png new file mode 100644 index 0000000..2ad4243 Binary files /dev/null and b/public/compatibility-card.png differ diff --git a/public/meditation-card.png b/public/meditation-card.png new file mode 100644 index 0000000..b1d0f54 Binary files /dev/null and b/public/meditation-card.png differ diff --git a/public/palm-card.png b/public/palm-card.png new file mode 100644 index 0000000..ee3db3e Binary files /dev/null and b/public/palm-card.png differ diff --git a/src/app/(core)/layout.module.scss b/src/app/(core)/layout.module.scss new file mode 100644 index 0000000..8f7786a --- /dev/null +++ b/src/app/(core)/layout.module.scss @@ -0,0 +1,4 @@ +.main { + padding: 16px; + padding-bottom: 64px; +} \ No newline at end of file diff --git a/src/app/(core)/layout.tsx b/src/app/(core)/layout.tsx new file mode 100644 index 0000000..63f1329 --- /dev/null +++ b/src/app/(core)/layout.tsx @@ -0,0 +1,15 @@ +import NavigationBar from "@/components/NavigationBar/NavigationBar"; +import styles from "./layout.module.scss"; + +export default function CoreLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return <> + +
+ {children} +
+ +} \ No newline at end of file diff --git a/src/app/(core)/page.module.scss b/src/app/(core)/page.module.scss new file mode 100644 index 0000000..42f2106 --- /dev/null +++ b/src/app/(core)/page.module.scss @@ -0,0 +1,5 @@ +.page { + display: flex; + flex-direction: column; + gap: 24px; +} \ No newline at end of file diff --git a/src/app/(core)/page.tsx b/src/app/(core)/page.tsx new file mode 100644 index 0000000..ce53bf6 --- /dev/null +++ b/src/app/(core)/page.tsx @@ -0,0 +1,16 @@ +import Horoscope from "@/components/Horoscope/Horoscope"; +import styles from "./page.module.scss" +import AdvisersSection from "@/components/AdvisersSection/AdvisersSection"; +import CompatibilitySection from "@/components/CompatibilitySection/CompatibilitySection"; +import MeditationSection from "@/components/MeditationSection/MeditationSection"; +import PalmSection from "@/components/PalmSection/PalmSection"; + +export default function Home() { + return
+ + + + + +
; +} \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index e3734be..a6a757f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,25 +1,26 @@ :root { - --background: #ffffff; - --foreground: #171717; + --background: #F7F7F7; + --foreground: #333333; } -@media (prefers-color-scheme: dark) { +/* @media (prefers-color-scheme: dark) { :root { --background: #0a0a0a; --foreground: #ededed; } -} +} */ html, body { max-width: 100vw; overflow-x: hidden; + font-family: var(--font-inter), -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } body { color: var(--foreground); background: var(--background); - font-family: Arial, Helvetica, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @@ -35,8 +36,15 @@ a { text-decoration: none; } +button { + background: none; + border: none; + outline: none; +} + +/* @media (prefers-color-scheme: dark) { html { color-scheme: dark; } -} +} */ \ No newline at end of file diff --git a/src/app/layout.module.scss b/src/app/layout.module.scss new file mode 100644 index 0000000..3f1d55e --- /dev/null +++ b/src/app/layout.module.scss @@ -0,0 +1,4 @@ +.body { + max-width: 560px; + margin: 0 auto; +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 42fc323..cb10b5c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,15 +1,13 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Inter } from "next/font/google"; import "./globals.css"; +import clsx from "clsx"; +import styles from "./layout.module.scss" -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], +const inter = Inter({ + subsets: ["latin", "cyrillic"], + display: "swap", + variable: "--font-inter", }); export const metadata: Metadata = { @@ -24,9 +22,7 @@ export default function RootLayout({ }>) { return ( - - {children} - + {children} ); } diff --git a/src/app/page.module.css b/src/app/page.module.css deleted file mode 100644 index a11c8f3..0000000 --- a/src/app/page.module.css +++ /dev/null @@ -1,168 +0,0 @@ -.page { - --gray-rgb: 0, 0, 0; - --gray-alpha-200: rgba(var(--gray-rgb), 0.08); - --gray-alpha-100: rgba(var(--gray-rgb), 0.05); - - --button-primary-hover: #383838; - --button-secondary-hover: #f2f2f2; - - display: grid; - grid-template-rows: 20px 1fr 20px; - align-items: center; - justify-items: center; - min-height: 100svh; - padding: 80px; - gap: 64px; - font-family: var(--font-geist-sans); -} - -@media (prefers-color-scheme: dark) { - .page { - --gray-rgb: 255, 255, 255; - --gray-alpha-200: rgba(var(--gray-rgb), 0.145); - --gray-alpha-100: rgba(var(--gray-rgb), 0.06); - - --button-primary-hover: #ccc; - --button-secondary-hover: #1a1a1a; - } -} - -.main { - display: flex; - flex-direction: column; - gap: 32px; - grid-row-start: 2; -} - -.main ol { - font-family: var(--font-geist-mono); - padding-left: 0; - margin: 0; - font-size: 14px; - line-height: 24px; - letter-spacing: -0.01em; - list-style-position: inside; -} - -.main li:not(:last-of-type) { - margin-bottom: 8px; -} - -.main code { - font-family: inherit; - background: var(--gray-alpha-100); - padding: 2px 4px; - border-radius: 4px; - font-weight: 600; -} - -.ctas { - display: flex; - gap: 16px; -} - -.ctas a { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: none; - border: 1px solid transparent; - transition: - background 0.2s, - color 0.2s, - border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; -} - -a.primary { - background: var(--foreground); - color: var(--background); - gap: 8px; -} - -a.secondary { - border-color: var(--gray-alpha-200); - min-width: 158px; -} - -.footer { - grid-row-start: 3; - display: flex; - gap: 24px; -} - -.footer a { - display: flex; - align-items: center; - gap: 8px; -} - -.footer img { - flex-shrink: 0; -} - -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - a.primary:hover { - background: var(--button-primary-hover); - border-color: transparent; - } - - a.secondary:hover { - background: var(--button-secondary-hover); - border-color: transparent; - } - - .footer a:hover { - text-decoration: underline; - text-underline-offset: 4px; - } -} - -@media (max-width: 600px) { - .page { - padding: 32px; - padding-bottom: 80px; - } - - .main { - align-items: center; - } - - .main ol { - text-align: center; - } - - .ctas { - flex-direction: column; - } - - .ctas a { - font-size: 14px; - height: 40px; - padding: 0 16px; - } - - a.secondary { - min-width: auto; - } - - .footer { - flex-wrap: wrap; - align-items: center; - justify-content: center; - } -} - -@media (prefers-color-scheme: dark) { - .logo { - filter: invert(); - } -} diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index 84af2cb..0000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import Image from "next/image"; -import styles from "./page.module.css"; - -export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing src/app/page.tsx. -
  2. -
  3. Save and see your changes instantly.
  4. -
- -
- - Vercel logomark - Deploy now - - - Read our docs - -
-
- -
- ); -} diff --git a/src/components/AdviserCard/AdviserCard.module.scss b/src/components/AdviserCard/AdviserCard.module.scss new file mode 100644 index 0000000..6466970 --- /dev/null +++ b/src/components/AdviserCard/AdviserCard.module.scss @@ -0,0 +1,66 @@ +.card { + padding: 0; + min-width: 160px; + height: 235px; + display: flex; + flex-direction: column; + justify-content: flex-end; + position: relative; + overflow: hidden; +} + +.content { + width: 100%; + padding: 10px; + display: flex; + flex-direction: column; + gap: 10px; + + &>* { + z-index: 1; + } + + &>.info { + width: 100%; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 6px; + + &>.name { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 6px; + + &>.indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #34D399; + } + } + + &>.rating { + display: flex; + align-items: center; + gap: 4px; + + &>.stars { + display: flex; + align-items: center; + gap: 3px; + } + } + } +} + +.shadow { + height: 160px; + width: 100%; + position: absolute; + bottom: 0; + left: 0; + background: linear-gradient(0deg, #174280 0%, rgba(0, 0, 0, 0) 70.95%); + // border: 1px solid rgba(229, 231, 235, 1); +} \ No newline at end of file diff --git a/src/components/AdviserCard/AdviserCard.tsx b/src/components/AdviserCard/AdviserCard.tsx new file mode 100644 index 0000000..d8f3017 --- /dev/null +++ b/src/components/AdviserCard/AdviserCard.tsx @@ -0,0 +1,55 @@ +import Button from "../ui/Button/Button" +import Card from "../ui/Card/Card" +import Typography from "../ui/Typography/Typography" +import styles from "./AdviserCard.module.scss" + +export default function AdviserCard() { + return ( + +
+
+
+ + Marta + +
+
+ + Astrologer - 7 years + +
+ + 4.8 + +
+ + + + + + + + + + + + + + + +
+ + (5762) + +
+
+ +
+
+ + ) +} \ No newline at end of file diff --git a/src/components/AdvisersSection/AdvisersSection.module.scss b/src/components/AdvisersSection/AdvisersSection.module.scss new file mode 100644 index 0000000..940d36a --- /dev/null +++ b/src/components/AdvisersSection/AdvisersSection.module.scss @@ -0,0 +1,4 @@ +.sectionContent { + overflow-x: scroll; + width: calc(100% + 16px); +} \ No newline at end of file diff --git a/src/components/AdvisersSection/AdvisersSection.tsx b/src/components/AdvisersSection/AdvisersSection.tsx new file mode 100644 index 0000000..9659f94 --- /dev/null +++ b/src/components/AdvisersSection/AdvisersSection.tsx @@ -0,0 +1,29 @@ +import AdviserCard from "../AdviserCard/AdviserCard"; +import Grid from "../ui/Grid/Grid" +import Section from "../ui/Section/Section" +import styles from "./AdvisersSection.module.scss" + +const advisers = [ + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, + { name: "Marta", img: "/marta.jpg", rating: 4.8, years: 7 }, +]; + +export default function AdvisersSection() { + return ( +
+ + {advisers.map((adviser, index) => ( + + ))} + +
+ ) +} \ No newline at end of file diff --git a/src/components/CompatibilityCard/CompatibilityCard.module.scss b/src/components/CompatibilityCard/CompatibilityCard.module.scss new file mode 100644 index 0000000..5cf1a49 --- /dev/null +++ b/src/components/CompatibilityCard/CompatibilityCard.module.scss @@ -0,0 +1,16 @@ +.card { + padding: 0; + min-width: 320px; + height: 110px; + overflow: hidden; + box-shadow: none; + display: flex; + flex-direction: row; +} + +.content { + padding: 22px 16px 16px; + display: flex; + flex-direction: column; + justify-content: space-between; +} \ No newline at end of file diff --git a/src/components/CompatibilityCard/CompatibilityCard.tsx b/src/components/CompatibilityCard/CompatibilityCard.tsx new file mode 100644 index 0000000..2848fbf --- /dev/null +++ b/src/components/CompatibilityCard/CompatibilityCard.tsx @@ -0,0 +1,34 @@ +import Image from "next/image"; +import Card from "../ui/Card/Card" +import Typography from "../ui/Typography/Typography" +import styles from "./CompatibilityCard.module.scss" +import IconLabel from "../ui/IconLabel/IconLabel"; +import { IconName } from "../ui/Icon/Icon"; +import MetaLabel from "../ui/MetaLabel/MetaLabel"; + +export default function CompatibilityCard() { + return ( + + Compatibility image +
+ + Compatibility + + Article + }}> + 5 min + +
+
+ ) +} \ No newline at end of file diff --git a/src/components/CompatibilitySection/CompatibilitySection.module.scss b/src/components/CompatibilitySection/CompatibilitySection.module.scss new file mode 100644 index 0000000..940d36a --- /dev/null +++ b/src/components/CompatibilitySection/CompatibilitySection.module.scss @@ -0,0 +1,4 @@ +.sectionContent { + overflow-x: scroll; + width: calc(100% + 16px); +} \ No newline at end of file diff --git a/src/components/CompatibilitySection/CompatibilitySection.tsx b/src/components/CompatibilitySection/CompatibilitySection.tsx new file mode 100644 index 0000000..ff5cbb4 --- /dev/null +++ b/src/components/CompatibilitySection/CompatibilitySection.tsx @@ -0,0 +1,29 @@ +import CompatibilityCard from "../CompatibilityCard/CompatibilityCard"; +import Grid from "../ui/Grid/Grid" +import Section from "../ui/Section/Section" +import styles from "./CompatibilitySection.module.scss" + +const compatibilities = [ + { title: "Compatibility" }, + { title: "Compatibility" }, + { title: "Compatibility" }, + { title: "Compatibility" }, + { title: "Compatibility" }, + { title: "Compatibility" }, + { title: "Compatibility" }, + { title: "Compatibility" }, + { title: "Compatibility" }, + { title: "Compatibility" }, +]; + +export default function CompatibilitySection() { + return ( +
+ + {compatibilities.map((compatibility, index) => ( + + ))} + +
+ ) +} \ No newline at end of file diff --git a/src/components/Horoscope/Horoscope.module.scss b/src/components/Horoscope/Horoscope.module.scss new file mode 100644 index 0000000..59128c0 --- /dev/null +++ b/src/components/Horoscope/Horoscope.module.scss @@ -0,0 +1,31 @@ +.horoscope { + background: #fff; + border-radius: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + padding: 24px 16px 16px 16px; + max-width: 400px; + margin: 0 auto; +} + +.title, +.text { + color: #2A74DD; +} + +.card { + padding: 16px 0px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.content { + display: flex; + flex-direction: column; + padding-inline: 8px; + gap: 20px; +} + +.seeAll { + text-align: right; +} \ No newline at end of file diff --git a/src/components/Horoscope/Horoscope.tsx b/src/components/Horoscope/Horoscope.tsx new file mode 100644 index 0000000..b485f44 --- /dev/null +++ b/src/components/Horoscope/Horoscope.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { useState } from "react"; +import TabBar, { Tab } from "../ui/TabBar/TabBar"; +import Text from "../ui/Typography/Typography"; +import styles from "./Horoscope.module.scss"; +import Card from "../ui/Card/Card"; + +type Period = "today" | "week" | "month" | "year"; + +const TABS: Tab[] = [ + { label: "Today", value: "today" }, + { label: "Week", value: "week" }, + { label: "Month", value: "month" }, + { label: "Year", value: "year" }, +]; + +const HOROSCOPE_TEXT = { + today: "Today, Cancer men should trust their intuition — it will guide them through a tricky moment. A pleasant surprise from a loved one is likely. Spend the evening in comfort and calm.", + week: "Weekly horoscope text...", + month: "Monthly horoscope text...", + year: "Yearly horoscope text...", +}; + +export default function Horoscope() { + const [active, setActive] = useState("today"); + + return ( + + tabs={TABS} active={active} onChange={setActive} /> +
+ + — Your Horoscope today — + + + {HOROSCOPE_TEXT[active]} + + + See All + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/Logo/Logo.tsx b/src/components/Logo/Logo.tsx new file mode 100644 index 0000000..7b66906 --- /dev/null +++ b/src/components/Logo/Logo.tsx @@ -0,0 +1,5 @@ +import Typography from "../ui/Typography/Typography"; + +export default function Logo() { + return AURA +} \ No newline at end of file diff --git a/src/components/MeditationCard/MeditationCard.module.scss b/src/components/MeditationCard/MeditationCard.module.scss new file mode 100644 index 0000000..15d5d07 --- /dev/null +++ b/src/components/MeditationCard/MeditationCard.module.scss @@ -0,0 +1,36 @@ +.card { + padding: 0; + min-width: 342px; + height: 308px; + overflow: hidden; + box-shadow: none; + display: flex; + flex-direction: column; +} + +.content { + padding: 16px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + &>.info { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + } + + &>.button { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: #F5F5F7; + padding: 0; + + &>.icon { + transform: rotate(180deg); + } + } +} \ No newline at end of file diff --git a/src/components/MeditationCard/MeditationCard.tsx b/src/components/MeditationCard/MeditationCard.tsx new file mode 100644 index 0000000..6edbf2a --- /dev/null +++ b/src/components/MeditationCard/MeditationCard.tsx @@ -0,0 +1,52 @@ +import Image from "next/image"; +import Card from "../ui/Card/Card" +import Typography from "../ui/Typography/Typography" +import styles from "./MeditationCard.module.scss" +import Icon, { IconName } from "../ui/Icon/Icon"; +import MetaLabel from "../ui/MetaLabel/MetaLabel"; +import Button from "../ui/Button/Button"; + +export default function MeditationCard() { + return ( + + Meditation image +
+
+ + Reset + + Therapy + }}> + 15 min + +
+ +
+
+ ) +} \ No newline at end of file diff --git a/src/components/MeditationSection/MeditationSection.module.scss b/src/components/MeditationSection/MeditationSection.module.scss new file mode 100644 index 0000000..940d36a --- /dev/null +++ b/src/components/MeditationSection/MeditationSection.module.scss @@ -0,0 +1,4 @@ +.sectionContent { + overflow-x: scroll; + width: calc(100% + 16px); +} \ No newline at end of file diff --git a/src/components/MeditationSection/MeditationSection.tsx b/src/components/MeditationSection/MeditationSection.tsx new file mode 100644 index 0000000..c30fafc --- /dev/null +++ b/src/components/MeditationSection/MeditationSection.tsx @@ -0,0 +1,24 @@ +import MeditationCard from "../MeditationCard/MeditationCard"; +import Grid from "../ui/Grid/Grid" +import Section from "../ui/Section/Section" +import styles from "./MeditationSection.module.scss" + +const meditations = [ + { title: "Meditation" }, + { title: "Meditation" }, + { title: "Meditation" }, + { title: "Meditation" }, + { title: "Meditation" }, +]; + +export default function MeditationSection() { + return ( +
+ + {meditations.map((meditation, index) => ( + + ))} + +
+ ) +} \ No newline at end of file diff --git a/src/components/NavigationBar/NavigationBar.module.scss b/src/components/NavigationBar/NavigationBar.module.scss new file mode 100644 index 0000000..f66be8e --- /dev/null +++ b/src/components/NavigationBar/NavigationBar.module.scss @@ -0,0 +1,23 @@ +.header { + width: 100%; + min-height: 56px; + height: fit-content; + padding: 16px; + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; +} + +.header> :first-child { + justify-self: start; +} + +.header> :nth-child(2) { + justify-self: center; +} + +.header> :nth-child(n+3) { + justify-self: end; + display: inline-flex; + gap: 16px; +} \ No newline at end of file diff --git a/src/components/NavigationBar/NavigationBar.tsx b/src/components/NavigationBar/NavigationBar.tsx new file mode 100644 index 0000000..2dd3905 --- /dev/null +++ b/src/components/NavigationBar/NavigationBar.tsx @@ -0,0 +1,16 @@ +import Logo from "../Logo/Logo" +import Icon, { IconName } from "../ui/Icon/Icon" +import styles from "./NavigationBar.module.scss" + +export default function NavigationBar() { + return ( +
+ + +
+ + +
+
+ ) +} \ No newline at end of file diff --git a/src/components/PalmCard/PalmCard.module.scss b/src/components/PalmCard/PalmCard.module.scss new file mode 100644 index 0000000..dd94e00 --- /dev/null +++ b/src/components/PalmCard/PalmCard.module.scss @@ -0,0 +1,28 @@ +.card { + padding: 0; + min-width: 200px; + height: 227px; + overflow: hidden; + box-shadow: none; + display: flex; + flex-direction: column; +} + +.image { + width: 100%; + height: 123px; + background: linear-gradient(90deg, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%); + display: flex; + justify-content: center; +} + +.content { + padding: 14px 12px 12px; + + &>.info { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 15px; + } +} \ No newline at end of file diff --git a/src/components/PalmCard/PalmCard.tsx b/src/components/PalmCard/PalmCard.tsx new file mode 100644 index 0000000..ac8f9cb --- /dev/null +++ b/src/components/PalmCard/PalmCard.tsx @@ -0,0 +1,43 @@ +import Image from "next/image"; +import Card from "../ui/Card/Card" +import Typography from "../ui/Typography/Typography" +import styles from "./PalmCard.module.scss" +import Icon, { IconName } from "../ui/Icon/Icon"; +import MetaLabel from "../ui/MetaLabel/MetaLabel"; +import Button from "../ui/Button/Button"; + +export default function PalmCard() { + return ( + +
+ Palm image +
+
+
+ + Код рождения в линиях + + Article + }}> + 5 min + +
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/PalmSection/PalmSection.module.scss b/src/components/PalmSection/PalmSection.module.scss new file mode 100644 index 0000000..940d36a --- /dev/null +++ b/src/components/PalmSection/PalmSection.module.scss @@ -0,0 +1,4 @@ +.sectionContent { + overflow-x: scroll; + width: calc(100% + 16px); +} \ No newline at end of file diff --git a/src/components/PalmSection/PalmSection.tsx b/src/components/PalmSection/PalmSection.tsx new file mode 100644 index 0000000..ec5a6b1 --- /dev/null +++ b/src/components/PalmSection/PalmSection.tsx @@ -0,0 +1,24 @@ +import PalmCard from "../PalmCard/PalmCard"; +import Grid from "../ui/Grid/Grid" +import Section from "../ui/Section/Section" +import styles from "./PalmSection.module.scss" + +const palms = [ + { title: "Palm" }, + { title: "Palm" }, + { title: "Palm" }, + { title: "Palm" }, + { title: "Palm" }, +]; + +export default function PalmSection() { + return ( +
+ + {palms.map((palm, index) => ( + + ))} + +
+ ) +} \ No newline at end of file diff --git a/src/components/ui/Button/Button.module.scss b/src/components/ui/Button/Button.module.scss new file mode 100644 index 0000000..5b3bb09 --- /dev/null +++ b/src/components/ui/Button/Button.module.scss @@ -0,0 +1,64 @@ +.button { + display: inline-flex; + align-items: center; + justify-content: center; + border: none; + border-radius: 24px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s, color 0.2s, border 0.2s; + outline: none; + user-select: none; + width: 100%; +} + +.primary { + background: #1694F7; +} + +// .primary:hover:not(:disabled) { +// background: #2176bd; +// } + +.secondary { + background: #f3f4f6; +} + +// .secondary:hover:not(:disabled) { +// background: #e5e7eb; +// } + +.outline { + background: #fff; + border: 1.5px solid #3490ec; +} + +// .outline:hover:not(:disabled) { +// background: #f0f8ff; +// } + +.ghost { + background: transparent; +} + +// .ghost:hover:not(:disabled) { +// background: #f0f8ff; +// } + +.sm { + padding: 0px 16px; + min-height: 35px; +} + +.md { + padding: 10px 20px; +} + +.lg { + padding: 14px 28px; +} + +.button:disabled { + opacity: 0.6; + cursor: not-allowed; +} \ No newline at end of file diff --git a/src/components/ui/Button/Button.tsx b/src/components/ui/Button/Button.tsx new file mode 100644 index 0000000..4767662 --- /dev/null +++ b/src/components/ui/Button/Button.tsx @@ -0,0 +1,35 @@ +import { ButtonHTMLAttributes, ReactNode } from "react"; +import clsx from "clsx"; +import styles from "./Button.module.scss"; + +export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost"; +export type ButtonSize = "sm" | "md" | "lg"; + +export interface ButtonProps extends ButtonHTMLAttributes { + children: ReactNode; + variant?: ButtonVariant; + size?: ButtonSize; + className?: string; +} + +export default function Button({ + children, + variant = "primary", + size = "md", + className, + ...rest +}: ButtonProps) { + return ( + + ); +} \ No newline at end of file diff --git a/src/components/ui/Card/Card.module.scss b/src/components/ui/Card/Card.module.scss new file mode 100644 index 0000000..19dfa05 --- /dev/null +++ b/src/components/ui/Card/Card.module.scss @@ -0,0 +1,7 @@ +.card { + background: #fff; + border-radius: 24px; + border: 1px solid rgba(229, 231, 235, 1); + box-shadow: 0px 10px 15px 0px rgba(0, 0, 0, 0.1), 0px 4px 6px 0px rgba(0, 0, 0, 0.1); + padding: 16px; +} \ No newline at end of file diff --git a/src/components/ui/Card/Card.tsx b/src/components/ui/Card/Card.tsx new file mode 100644 index 0000000..4398f87 --- /dev/null +++ b/src/components/ui/Card/Card.tsx @@ -0,0 +1,17 @@ +import { ReactNode } from "react"; +import clsx from "clsx"; +import styles from "./Card.module.scss"; + +type CardProps = { + children: ReactNode; + className?: string; + style?: React.CSSProperties; +}; + +export default function Card({ children, className, style }: CardProps) { + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/src/components/ui/Grid/Grid.module.scss b/src/components/ui/Grid/Grid.module.scss new file mode 100644 index 0000000..744e517 --- /dev/null +++ b/src/components/ui/Grid/Grid.module.scss @@ -0,0 +1,4 @@ +.grid { + display: grid; + width: 100%; +} \ No newline at end of file diff --git a/src/components/ui/Grid/Grid.tsx b/src/components/ui/Grid/Grid.tsx new file mode 100644 index 0000000..c145a6f --- /dev/null +++ b/src/components/ui/Grid/Grid.tsx @@ -0,0 +1,28 @@ +import { ReactNode } from "react"; +import clsx from "clsx"; +import styles from "./Grid.module.scss"; + +type GridProps = { + children: ReactNode; + columns?: number; + gap?: number | string; + className?: string; + style?: React.CSSProperties; +}; + +export default function Grid({ children, columns = 3, gap = 16, className, style }: GridProps) { + let gridTemplateColumns = `repeat(${columns}, 1fr)` + + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/src/components/ui/Icon/Icon.tsx b/src/components/ui/Icon/Icon.tsx new file mode 100644 index 0000000..e297af9 --- /dev/null +++ b/src/components/ui/Icon/Icon.tsx @@ -0,0 +1,65 @@ +import { ReactNode } from "react"; +import NotificationIcon from "./icons/Notification"; +import clsx from "clsx"; +import SearchIcon from "./icons/Search"; +import MenuIcon from "./icons/Menu"; +import ArticleIcon from "./icons/Article"; +import VideoIcon from "./icons/Video"; +import ChevronIcon from "./icons/Chevron"; + +export enum IconName { + Notification, + Search, + Menu, + Article, + Video, + Chevron +}; + +const icons: Record>> = { + [IconName.Notification]: NotificationIcon, + [IconName.Search]: SearchIcon, + [IconName.Menu]: MenuIcon, + [IconName.Article]: ArticleIcon, + [IconName.Video]: VideoIcon, + [IconName.Chevron]: ChevronIcon, +}; + +export type IconProps = { + name: IconName; + size?: { + height: number | string; + width: number | string; + }; + color?: string; + className?: string; + children?: ReactNode; + cursor?: "pointer" | "auto" +}; + +export default function Icon({ + name, + size = { + height: 24, + width: 24 + }, + color = "currentColor", + className, + children, + cursor = "pointer", + ...rest +}: IconProps) { + const Component = icons[name]; + return ( + + + ); +} \ No newline at end of file diff --git a/src/components/ui/Icon/icons/Article.tsx b/src/components/ui/Icon/icons/Article.tsx new file mode 100644 index 0000000..3acef59 --- /dev/null +++ b/src/components/ui/Icon/icons/Article.tsx @@ -0,0 +1,8 @@ +import { SVGProps } from "react"; + +export default function ArticleIcon(props: SVGProps) { + return + + + +} \ No newline at end of file diff --git a/src/components/ui/Icon/icons/Chevron.tsx b/src/components/ui/Icon/icons/Chevron.tsx new file mode 100644 index 0000000..76c6e52 --- /dev/null +++ b/src/components/ui/Icon/icons/Chevron.tsx @@ -0,0 +1,9 @@ +import { SVGProps } from "react"; + +export default function ChevronIcon(props: SVGProps) { + const color = props?.color || "#333333" + return + + + +} \ No newline at end of file diff --git a/src/components/ui/Icon/icons/Menu.tsx b/src/components/ui/Icon/icons/Menu.tsx new file mode 100644 index 0000000..e7c626a --- /dev/null +++ b/src/components/ui/Icon/icons/Menu.tsx @@ -0,0 +1,8 @@ +import { SVGProps } from "react"; + +export default function MenuIcon(props: SVGProps) { + return + + + +} \ No newline at end of file diff --git a/src/components/ui/Icon/icons/Notification.tsx b/src/components/ui/Icon/icons/Notification.tsx new file mode 100644 index 0000000..7077bcf --- /dev/null +++ b/src/components/ui/Icon/icons/Notification.tsx @@ -0,0 +1,25 @@ +import { SVGProps } from "react"; + +export default function NotificationIcon(props: SVGProps) { + return ( + + + + + + + + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/components/ui/Icon/icons/Search.tsx b/src/components/ui/Icon/icons/Search.tsx new file mode 100644 index 0000000..e4d612a --- /dev/null +++ b/src/components/ui/Icon/icons/Search.tsx @@ -0,0 +1,8 @@ +import { SVGProps } from "react"; + +export default function SearchIcon(props: SVGProps) { + return + + + +} \ No newline at end of file diff --git a/src/components/ui/Icon/icons/Video.tsx b/src/components/ui/Icon/icons/Video.tsx new file mode 100644 index 0000000..c2d5bfc --- /dev/null +++ b/src/components/ui/Icon/icons/Video.tsx @@ -0,0 +1,9 @@ +import { SVGProps } from "react"; + +export default function VideoIcon(props: SVGProps) { + const color = props?.color || "#A0A7B5"; + return + + + +} \ No newline at end of file diff --git a/src/components/ui/IconLabel/IconLabel.module.scss b/src/components/ui/IconLabel/IconLabel.module.scss new file mode 100644 index 0000000..768241a --- /dev/null +++ b/src/components/ui/IconLabel/IconLabel.module.scss @@ -0,0 +1,16 @@ +.iconLabel { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.icon { + width: 24px; + height: 24px; + display: block; +} + +.label { + font-size: 16px; + color: #6b7280; +} \ No newline at end of file diff --git a/src/components/ui/IconLabel/IconLabel.tsx b/src/components/ui/IconLabel/IconLabel.tsx new file mode 100644 index 0000000..cbfc655 --- /dev/null +++ b/src/components/ui/IconLabel/IconLabel.tsx @@ -0,0 +1,19 @@ +import { ReactNode } from "react"; +import clsx from "clsx"; +import Icon, { IconName, IconProps } from "../Icon/Icon"; +import styles from "./IconLabel.module.scss"; + +export type IconLabelProps = { + iconProps: IconProps; + children: ReactNode; + className?: string; +}; + +export default function IconLabel({ iconProps, children, className }: IconLabelProps) { + return ( + + + {children} + + ); +} \ No newline at end of file diff --git a/src/components/ui/MetaLabel/MetaLabel.module.scss b/src/components/ui/MetaLabel/MetaLabel.module.scss new file mode 100644 index 0000000..d612aa3 --- /dev/null +++ b/src/components/ui/MetaLabel/MetaLabel.module.scss @@ -0,0 +1,5 @@ +.metaLabel { + display: flex; + align-items: center; + gap: 4px; +} \ No newline at end of file diff --git a/src/components/ui/MetaLabel/MetaLabel.tsx b/src/components/ui/MetaLabel/MetaLabel.tsx new file mode 100644 index 0000000..d73e60e --- /dev/null +++ b/src/components/ui/MetaLabel/MetaLabel.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from "react"; +import styles from "./MetaLabel.module.scss"; +import IconLabel, { IconLabelProps } from "../IconLabel/IconLabel"; +import Typography from "../Typography/Typography"; + +export type MetaLabelProps = { + iconLabelProps: IconLabelProps; + children: ReactNode; +}; + +export default function MetaLabel({ iconLabelProps, children }: MetaLabelProps) { + return ( +
+ + + • + + + {children} + +
+ ); +} \ No newline at end of file diff --git a/src/components/ui/Section/Section.module.scss b/src/components/ui/Section/Section.module.scss new file mode 100644 index 0000000..896d1c6 --- /dev/null +++ b/src/components/ui/Section/Section.module.scss @@ -0,0 +1,10 @@ +.section { + display: flex; + flex-direction: column; + align-items: start; + gap: 24px; +} + +.content { + width: 100%; +} \ No newline at end of file diff --git a/src/components/ui/Section/Section.tsx b/src/components/ui/Section/Section.tsx new file mode 100644 index 0000000..b44547b --- /dev/null +++ b/src/components/ui/Section/Section.tsx @@ -0,0 +1,25 @@ +import { ReactNode } from "react"; +import clsx from "clsx"; +import styles from "./Section.module.scss"; +import Typography from "../Typography/Typography"; + +interface SectionProps { + title?: string; + children: ReactNode; + className?: string; + titleClassName?: string; + contentClassName?: string; +} + +export default function Section({ title, children, className, titleClassName, contentClassName }: SectionProps) { + return ( +
+ {title && + + {title} + + } +
{children}
+
+ ); +} \ No newline at end of file diff --git a/src/components/ui/TabBar/TabBar.module.scss b/src/components/ui/TabBar/TabBar.module.scss new file mode 100644 index 0000000..3a905fe --- /dev/null +++ b/src/components/ui/TabBar/TabBar.module.scss @@ -0,0 +1,32 @@ +.tabBar { + width: 100%; + display: flex; + flex-direction: row; + justify-content: center; + gap: 16px; +} + +.tab { + position: relative; + padding: 14px 6px; + cursor: pointer; + transition: color 0.2s; +} + +.tab.active, +.tab:focus { + color: #111; + font-weight: 700; +} + +.tab.active::after { + content: ""; + display: block; + height: 2px; + width: 100%; + background: #000; + border-radius: 2px; + position: absolute; + left: 0; + bottom: -2px; +} \ No newline at end of file diff --git a/src/components/ui/TabBar/TabBar.tsx b/src/components/ui/TabBar/TabBar.tsx new file mode 100644 index 0000000..7e48dd2 --- /dev/null +++ b/src/components/ui/TabBar/TabBar.tsx @@ -0,0 +1,37 @@ +import clsx from "clsx"; +import styles from "./TabBar.module.scss"; +import Typography from "../Typography/Typography"; + +export type Tab = { + label: string; + value: T; +}; + +type TabBarProps = { + tabs: Tab[]; + active: T; + onChange: (value: T) => void; + className?: string; +}; + +export default function TabBar({ tabs, active, onChange, className }: TabBarProps) { + return ( + + ); +} \ No newline at end of file diff --git a/src/components/ui/Typography/Typography.module.scss b/src/components/ui/Typography/Typography.module.scss new file mode 100644 index 0000000..4f7e8ee --- /dev/null +++ b/src/components/ui/Typography/Typography.module.scss @@ -0,0 +1,72 @@ +.typography { + line-height: 1; + text-align: center; +} + +.typography-xs { + font-size: 12px; +} + +.typography-sm { + font-size: 14px; +} + +.typography-base { + font-size: 16px; +} + +.typography-lg { + font-size: 20px; +} + +.typography-xl { + font-size: 24px; +} + +.typography-2xl { + font-size: 32px; +} + +.font-normal { + font-weight: 400; +} + +.font-medium { + font-weight: 500; +} + +.font-semi-bold { + font-weight: 600; +} + +.font-bold { + font-weight: 700; +} + +.typography-default { + color: var(--foreground); +} + +.typography-black { + color: #000; +} + +.typography-white { + color: #fff; +} + +.typography-gray-600 { + color: #6B7280; +} + +.typography-red-600 { + color: #dc2626; +} + +.typography-green-600 { + color: #16a34a; +} + +.typography-gray-400 { + color: #9ca3af; +} \ No newline at end of file diff --git a/src/components/ui/Typography/Typography.tsx b/src/components/ui/Typography/Typography.tsx new file mode 100644 index 0000000..8dc40b8 --- /dev/null +++ b/src/components/ui/Typography/Typography.tsx @@ -0,0 +1,66 @@ +import { JSX, ReactNode } from 'react'; +import clsx from 'clsx'; +import styles from "./Typography.module.scss" + +export type TypographyProps = { + as?: keyof JSX.IntrinsicElements; + children: ReactNode; + className?: string; + weight?: 'regular' | 'medium' | 'semiBold' | 'bold'; + size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; + color?: 'default' | 'black' | 'white' | 'secondary' | 'danger' | 'success' | 'muted'; + align?: 'center' | 'left' | 'right'; +}; + +const sizeMap = { + xs: 'typography-xs', + sm: 'typography-sm', + md: 'typography-base', + lg: 'typography-lg', + xl: 'typography-xl', + '2xl': 'typography-2xl', +}; + +const weightMap = { + regular: 'font-normal', + medium: 'font-medium', + semiBold: 'font-semi-bold', + bold: 'font-bold', +}; + +const colorMap = { + default: 'typography-default', + black: 'typography-black', + white: 'typography-white', + secondary: 'typography-gray-600', + danger: 'typography-red-600', + success: 'typography-green-600', + muted: 'typography-gray-400', +}; + +export default function Typography({ + as: Component = 'span', + children, + className, + weight = 'regular', + size = 'md', + color = 'default', + align = 'center', +}: TypographyProps) { + return ( + + {children} + + ); +}