main
home page
This commit is contained in:
parent
d792208441
commit
f71ff66ace
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@ -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"]
|
||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@ -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
|
||||
399
package-lock.json
generated
399
package-lock.json
generated
@ -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"
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/adviser-card.png
Normal file
BIN
public/adviser-card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
public/compatibility-card.png
Normal file
BIN
public/compatibility-card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
public/meditation-card.png
Normal file
BIN
public/meditation-card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
BIN
public/palm-card.png
Normal file
BIN
public/palm-card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
4
src/app/(core)/layout.module.scss
Normal file
4
src/app/(core)/layout.module.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.main {
|
||||
padding: 16px;
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
15
src/app/(core)/layout.tsx
Normal file
15
src/app/(core)/layout.tsx
Normal file
@ -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 <>
|
||||
<NavigationBar />
|
||||
<main className={styles.main}>
|
||||
{children}
|
||||
</main>
|
||||
</>
|
||||
}
|
||||
5
src/app/(core)/page.module.scss
Normal file
5
src/app/(core)/page.module.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
16
src/app/(core)/page.tsx
Normal file
16
src/app/(core)/page.tsx
Normal file
@ -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 <section className={styles.page}>
|
||||
<Horoscope />
|
||||
<AdvisersSection />
|
||||
<CompatibilitySection />
|
||||
<MeditationSection />
|
||||
<PalmSection />
|
||||
</section>;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
} */
|
||||
4
src/app/layout.module.scss
Normal file
4
src/app/layout.module.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.body {
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@ -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 (
|
||||
<html lang="en">
|
||||
<body className={`${geistSans.variable} ${geistMono.variable}`}>
|
||||
{children}
|
||||
</body>
|
||||
<body className={clsx(inter.variable, styles.body)}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import styles from "./page.module.css";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<main className={styles.main}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol>
|
||||
<li>
|
||||
Get started by editing <code>src/app/page.tsx</code>.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className={styles.ctas}>
|
||||
<a
|
||||
className={styles.primary}
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.secondary}
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className={styles.footer}>
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
66
src/components/AdviserCard/AdviserCard.module.scss
Normal file
66
src/components/AdviserCard/AdviserCard.module.scss
Normal file
@ -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);
|
||||
}
|
||||
55
src/components/AdviserCard/AdviserCard.tsx
Normal file
55
src/components/AdviserCard/AdviserCard.tsx
Normal file
@ -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 (
|
||||
<Card className={styles.card} style={{ backgroundImage: `url(/adviser-card.png)` }}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.info}>
|
||||
<div className={styles.name}>
|
||||
<Typography color="white" weight="bold">
|
||||
Marta
|
||||
</Typography>
|
||||
<div className={styles.indicator} />
|
||||
</div>
|
||||
<Typography className={styles.description} color="white" weight="medium" size="xs">
|
||||
Astrologer - 7 years
|
||||
</Typography>
|
||||
<div className={styles.rating}>
|
||||
<Typography color="white" weight="medium" size="xs">
|
||||
4.8
|
||||
</Typography>
|
||||
<div className={styles.stars}>
|
||||
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.57382 0.781219C4.49102 0.609363 4.31603 0.5 4.12387 0.5C3.9317 0.5 3.75828 0.609363 3.67392 0.781219L2.66934 2.84817L0.425835 3.17939C0.238356 3.20751 0.082123 3.33874 0.0243168 3.51841C-0.0334894 3.69808 0.0133805 3.8965 0.147741 4.02929L1.77569 5.64005L1.39135 7.91636C1.36011 8.10384 1.43822 8.29444 1.5929 8.40537C1.74757 8.51629 1.95223 8.53035 2.12096 8.4413L4.12543 7.37111L6.1299 8.4413C6.29863 8.53035 6.5033 8.51785 6.65797 8.40537C6.81264 8.29288 6.89075 8.10384 6.85951 7.91636L6.47361 5.64005L8.10156 4.02929C8.23592 3.8965 8.28435 3.69808 8.22498 3.51841C8.16561 3.33874 8.01094 3.20751 7.82346 3.17939L5.5784 2.84817L4.57382 0.781219Z" fill="#FFD600" />
|
||||
</svg>
|
||||
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.57382 0.781219C4.49102 0.609363 4.31603 0.5 4.12387 0.5C3.9317 0.5 3.75828 0.609363 3.67392 0.781219L2.66934 2.84817L0.425835 3.17939C0.238356 3.20751 0.082123 3.33874 0.0243168 3.51841C-0.0334894 3.69808 0.0133805 3.8965 0.147741 4.02929L1.77569 5.64005L1.39135 7.91636C1.36011 8.10384 1.43822 8.29444 1.5929 8.40537C1.74757 8.51629 1.95223 8.53035 2.12096 8.4413L4.12543 7.37111L6.1299 8.4413C6.29863 8.53035 6.5033 8.51785 6.65797 8.40537C6.81264 8.29288 6.89075 8.10384 6.85951 7.91636L6.47361 5.64005L8.10156 4.02929C8.23592 3.8965 8.28435 3.69808 8.22498 3.51841C8.16561 3.33874 8.01094 3.20751 7.82346 3.17939L5.5784 2.84817L4.57382 0.781219Z" fill="#FFD600" />
|
||||
</svg>
|
||||
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.57382 0.781219C4.49102 0.609363 4.31603 0.5 4.12387 0.5C3.9317 0.5 3.75828 0.609363 3.67392 0.781219L2.66934 2.84817L0.425835 3.17939C0.238356 3.20751 0.082123 3.33874 0.0243168 3.51841C-0.0334894 3.69808 0.0133805 3.8965 0.147741 4.02929L1.77569 5.64005L1.39135 7.91636C1.36011 8.10384 1.43822 8.29444 1.5929 8.40537C1.74757 8.51629 1.95223 8.53035 2.12096 8.4413L4.12543 7.37111L6.1299 8.4413C6.29863 8.53035 6.5033 8.51785 6.65797 8.40537C6.81264 8.29288 6.89075 8.10384 6.85951 7.91636L6.47361 5.64005L8.10156 4.02929C8.23592 3.8965 8.28435 3.69808 8.22498 3.51841C8.16561 3.33874 8.01094 3.20751 7.82346 3.17939L5.5784 2.84817L4.57382 0.781219Z" fill="#FFD600" />
|
||||
</svg>
|
||||
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.57382 0.781219C4.49102 0.609363 4.31603 0.5 4.12387 0.5C3.9317 0.5 3.75828 0.609363 3.67392 0.781219L2.66934 2.84817L0.425835 3.17939C0.238356 3.20751 0.082123 3.33874 0.0243168 3.51841C-0.0334894 3.69808 0.0133805 3.8965 0.147741 4.02929L1.77569 5.64005L1.39135 7.91636C1.36011 8.10384 1.43822 8.29444 1.5929 8.40537C1.74757 8.51629 1.95223 8.53035 2.12096 8.4413L4.12543 7.37111L6.1299 8.4413C6.29863 8.53035 6.5033 8.51785 6.65797 8.40537C6.81264 8.29288 6.89075 8.10384 6.85951 7.91636L6.47361 5.64005L8.10156 4.02929C8.23592 3.8965 8.28435 3.69808 8.22498 3.51841C8.16561 3.33874 8.01094 3.20751 7.82346 3.17939L5.5784 2.84817L4.57382 0.781219Z" fill="#FFD600" />
|
||||
</svg>
|
||||
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.57382 0.781219C4.49102 0.609363 4.31603 0.5 4.12387 0.5C3.9317 0.5 3.75828 0.609363 3.67392 0.781219L2.66934 2.84817L0.425835 3.17939C0.238356 3.20751 0.082123 3.33874 0.0243168 3.51841C-0.0334894 3.69808 0.0133805 3.8965 0.147741 4.02929L1.77569 5.64005L1.39135 7.91636C1.36011 8.10384 1.43822 8.29444 1.5929 8.40537C1.74757 8.51629 1.95223 8.53035 2.12096 8.4413L4.12543 7.37111L6.1299 8.4413C6.29863 8.53035 6.5033 8.51785 6.65797 8.40537C6.81264 8.29288 6.89075 8.10384 6.85951 7.91636L6.47361 5.64005L8.10156 4.02929C8.23592 3.8965 8.28435 3.69808 8.22498 3.51841C8.16561 3.33874 8.01094 3.20751 7.82346 3.17939L5.5784 2.84817L4.57382 0.781219Z" fill="#FFD600" />
|
||||
</svg>
|
||||
</div>
|
||||
<Typography color="white" weight="medium" size="xs">
|
||||
(5762)
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Button size="sm">
|
||||
<Typography color="white" weight="bold" size="sm">
|
||||
CHAT | FREE
|
||||
</Typography>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.shadow} />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
.sectionContent {
|
||||
overflow-x: scroll;
|
||||
width: calc(100% + 16px);
|
||||
}
|
||||
29
src/components/AdvisersSection/AdvisersSection.tsx
Normal file
29
src/components/AdvisersSection/AdvisersSection.tsx
Normal file
@ -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 (
|
||||
<Section title="Advisers" contentClassName={styles.sectionContent}>
|
||||
<Grid columns={5}>
|
||||
{advisers.map((adviser, index) => (
|
||||
<AdviserCard key={index} {...adviser} />
|
||||
))}
|
||||
</Grid>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
34
src/components/CompatibilityCard/CompatibilityCard.tsx
Normal file
34
src/components/CompatibilityCard/CompatibilityCard.tsx
Normal file
@ -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 (
|
||||
<Card className={styles.card}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/compatibility-card.png"
|
||||
alt="Compatibility image"
|
||||
width={120}
|
||||
height={110}
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<Typography size="lg" weight="medium">
|
||||
Compatibility
|
||||
</Typography>
|
||||
<MetaLabel iconLabelProps={{
|
||||
iconProps: {
|
||||
name: IconName.Article,
|
||||
},
|
||||
children: <Typography color="secondary">Article</Typography>
|
||||
}}>
|
||||
5 min
|
||||
</MetaLabel>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
.sectionContent {
|
||||
overflow-x: scroll;
|
||||
width: calc(100% + 16px);
|
||||
}
|
||||
29
src/components/CompatibilitySection/CompatibilitySection.tsx
Normal file
29
src/components/CompatibilitySection/CompatibilitySection.tsx
Normal file
@ -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 (
|
||||
<Section title="Compatibility" contentClassName={styles.sectionContent}>
|
||||
<Grid columns={5}>
|
||||
{compatibilities.map((compatibility, index) => (
|
||||
<CompatibilityCard key={index} {...compatibility} />
|
||||
))}
|
||||
</Grid>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
31
src/components/Horoscope/Horoscope.module.scss
Normal file
31
src/components/Horoscope/Horoscope.module.scss
Normal file
@ -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;
|
||||
}
|
||||
44
src/components/Horoscope/Horoscope.tsx
Normal file
44
src/components/Horoscope/Horoscope.tsx
Normal file
@ -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<Period>[] = [
|
||||
{ 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<Period>("today");
|
||||
|
||||
return (
|
||||
<Card className={styles.card}>
|
||||
<TabBar<Period> tabs={TABS} active={active} onChange={setActive} />
|
||||
<div className={styles.content}>
|
||||
<Text as="h3" weight="regular" className={styles.title}>
|
||||
— Your Horoscope today —
|
||||
</Text>
|
||||
<Text as="p" weight="medium" className={styles.text}>
|
||||
{HOROSCOPE_TEXT[active]}
|
||||
</Text>
|
||||
<Text as="span" size="sm" weight="medium" color="secondary" className={styles.seeAll}>
|
||||
See All
|
||||
</Text>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
5
src/components/Logo/Logo.tsx
Normal file
5
src/components/Logo/Logo.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import Typography from "../ui/Typography/Typography";
|
||||
|
||||
export default function Logo() {
|
||||
return <Typography size="xl" weight="medium">AURA</Typography>
|
||||
}
|
||||
36
src/components/MeditationCard/MeditationCard.module.scss
Normal file
36
src/components/MeditationCard/MeditationCard.module.scss
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/components/MeditationCard/MeditationCard.tsx
Normal file
52
src/components/MeditationCard/MeditationCard.tsx
Normal file
@ -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 (
|
||||
<Card className={styles.card}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/meditation-card.png"
|
||||
alt="Meditation image"
|
||||
width={342}
|
||||
height={216}
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.info}>
|
||||
<Typography size="lg" weight="regular">
|
||||
Reset
|
||||
</Typography>
|
||||
<MetaLabel iconLabelProps={{
|
||||
iconProps: {
|
||||
name: IconName.Video,
|
||||
color: "#6B7280",
|
||||
size: {
|
||||
width: 24,
|
||||
height: 25
|
||||
}
|
||||
},
|
||||
children: <Typography color="secondary">Therapy</Typography>
|
||||
}}>
|
||||
15 min
|
||||
</MetaLabel>
|
||||
</div>
|
||||
<Button className={styles.button}>
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
name={IconName.Chevron}
|
||||
size={{
|
||||
width: 18,
|
||||
height: 18
|
||||
}}
|
||||
color="#A0A7B5"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
.sectionContent {
|
||||
overflow-x: scroll;
|
||||
width: calc(100% + 16px);
|
||||
}
|
||||
24
src/components/MeditationSection/MeditationSection.tsx
Normal file
24
src/components/MeditationSection/MeditationSection.tsx
Normal file
@ -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 (
|
||||
<Section title="Meditations" contentClassName={styles.sectionContent}>
|
||||
<Grid columns={5}>
|
||||
{meditations.map((meditation, index) => (
|
||||
<MeditationCard key={index} {...meditation} />
|
||||
))}
|
||||
</Grid>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
23
src/components/NavigationBar/NavigationBar.module.scss
Normal file
23
src/components/NavigationBar/NavigationBar.module.scss
Normal file
@ -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;
|
||||
}
|
||||
16
src/components/NavigationBar/NavigationBar.tsx
Normal file
16
src/components/NavigationBar/NavigationBar.tsx
Normal file
@ -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 (
|
||||
<header className={styles.header}>
|
||||
<Icon name={IconName.Menu} />
|
||||
<Logo />
|
||||
<div>
|
||||
<Icon name={IconName.Notification} />
|
||||
<Icon name={IconName.Search} />
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
28
src/components/PalmCard/PalmCard.module.scss
Normal file
28
src/components/PalmCard/PalmCard.module.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
43
src/components/PalmCard/PalmCard.tsx
Normal file
43
src/components/PalmCard/PalmCard.tsx
Normal file
@ -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 (
|
||||
<Card className={styles.card}>
|
||||
<div className={styles.image}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/palm-card.png"
|
||||
alt="Palm image"
|
||||
width={99}
|
||||
height={123}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.info}>
|
||||
<Typography size="lg" align="left">
|
||||
Код рождения в линиях
|
||||
</Typography>
|
||||
<MetaLabel iconLabelProps={{
|
||||
iconProps: {
|
||||
name: IconName.Video,
|
||||
color: "#6B7280",
|
||||
size: {
|
||||
width: 24,
|
||||
height: 25
|
||||
}
|
||||
},
|
||||
children: <Typography color="secondary">Article</Typography>
|
||||
}}>
|
||||
5 min
|
||||
</MetaLabel>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
4
src/components/PalmSection/PalmSection.module.scss
Normal file
4
src/components/PalmSection/PalmSection.module.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.sectionContent {
|
||||
overflow-x: scroll;
|
||||
width: calc(100% + 16px);
|
||||
}
|
||||
24
src/components/PalmSection/PalmSection.tsx
Normal file
24
src/components/PalmSection/PalmSection.tsx
Normal file
@ -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 (
|
||||
<Section title="Palm" contentClassName={styles.sectionContent}>
|
||||
<Grid columns={5}>
|
||||
{palms.map((palm, index) => (
|
||||
<PalmCard key={index} {...palm} />
|
||||
))}
|
||||
</Grid>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
64
src/components/ui/Button/Button.module.scss
Normal file
64
src/components/ui/Button/Button.module.scss
Normal file
@ -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;
|
||||
}
|
||||
35
src/components/ui/Button/Button.tsx
Normal file
35
src/components/ui/Button/Button.tsx
Normal file
@ -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<HTMLButtonElement> {
|
||||
children: ReactNode;
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Button({
|
||||
children,
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
className,
|
||||
...rest
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
styles.button,
|
||||
styles[variant],
|
||||
styles[size],
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
7
src/components/ui/Card/Card.module.scss
Normal file
7
src/components/ui/Card/Card.module.scss
Normal file
@ -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;
|
||||
}
|
||||
17
src/components/ui/Card/Card.tsx
Normal file
17
src/components/ui/Card/Card.tsx
Normal file
@ -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 (
|
||||
<div className={clsx(styles.card, className)} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
4
src/components/ui/Grid/Grid.module.scss
Normal file
4
src/components/ui/Grid/Grid.module.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.grid {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
}
|
||||
28
src/components/ui/Grid/Grid.tsx
Normal file
28
src/components/ui/Grid/Grid.tsx
Normal file
@ -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 (
|
||||
<div
|
||||
className={clsx(styles.grid, className)}
|
||||
style={{
|
||||
gridTemplateColumns,
|
||||
gap,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
src/components/ui/Icon/Icon.tsx
Normal file
65
src/components/ui/Icon/Icon.tsx
Normal file
@ -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, React.ComponentType<React.SVGProps<SVGSVGElement>>> = {
|
||||
[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 (
|
||||
<span style={{ position: "relative", display: "inline-block", cursor, width: size.width, height: size.height }} className={clsx(className)}>
|
||||
<Component
|
||||
width={size.width}
|
||||
height={size.height}
|
||||
color={color}
|
||||
aria-hidden="true"
|
||||
{...rest}
|
||||
/>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
8
src/components/ui/Icon/icons/Article.tsx
Normal file
8
src/components/ui/Icon/icons/Article.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function ArticleIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return <svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M7.66667 19.9166C7.36875 19.9166 7.125 19.6728 7.125 19.3749V6.37492C7.125 6.077 7.36875 5.83325 7.66667 5.83325H13.0833V8.54159C13.0833 9.1408 13.5674 9.62492 14.1667 9.62492H16.875V19.3749C16.875 19.6728 16.6313 19.9166 16.3333 19.9166H7.66667ZM7.66667 4.20825C6.47161 4.20825 5.5 5.17987 5.5 6.37492V19.3749C5.5 20.57 6.47161 21.5416 7.66667 21.5416H16.3333C17.5284 21.5416 18.5 20.57 18.5 19.3749V9.43872C18.5 8.8632 18.2732 8.31138 17.8669 7.90513L14.7997 4.84132C14.3935 4.43507 13.8451 4.20825 13.2695 4.20825H7.66667ZM9.5625 12.8749C9.11224 12.8749 8.75 13.2372 8.75 13.6874C8.75 14.1377 9.11224 14.4999 9.5625 14.4999H14.4375C14.8878 14.4999 15.25 14.1377 15.25 13.6874C15.25 13.2372 14.8878 12.8749 14.4375 12.8749H9.5625ZM9.5625 16.1249C9.11224 16.1249 8.75 16.4872 8.75 16.9374C8.75 17.3877 9.11224 17.7499 9.5625 17.7499H14.4375C14.8878 17.7499 15.25 17.3877 15.25 16.9374C15.25 16.4872 14.8878 16.1249 14.4375 16.1249H9.5625Z" fill="#6B7280" />
|
||||
</svg>
|
||||
|
||||
}
|
||||
9
src/components/ui/Icon/icons/Chevron.tsx
Normal file
9
src/components/ui/Icon/icons/Chevron.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function ChevronIcon(props: SVGProps<SVGSVGElement>) {
|
||||
const color = props?.color || "#333333"
|
||||
return <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M9.43074 12.001L17.7039 3.72301C18.0987 3.32834 18.0987 2.68992 17.7039 2.29525C17.309 1.90158 16.6699 1.90158 16.276 2.29525L7.28893 11.2866C6.90013 11.6743 6.90727 12.3318 7.28893 12.7134L16.276 21.7047C16.6709 22.0984 17.31 22.0984 17.7039 21.7047C18.0977 21.3111 18.0987 20.6717 17.7039 20.278L9.43074 12.001Z" fill={color} />
|
||||
</svg>
|
||||
|
||||
}
|
||||
8
src/components/ui/Icon/icons/Menu.tsx
Normal file
8
src/components/ui/Icon/icons/Menu.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function MenuIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M1.5 4.5C1.5 3.67031 2.17031 3 3 3H21C21.8297 3 22.5 3.67031 22.5 4.5C22.5 5.32969 21.8297 6 21 6H3C2.17031 6 1.5 5.32969 1.5 4.5ZM1.5 12C1.5 11.1703 2.17031 10.5 3 10.5H21C21.8297 10.5 22.5 11.1703 22.5 12C22.5 12.8297 21.8297 13.5 21 13.5H3C2.17031 13.5 1.5 12.8297 1.5 12ZM22.5 19.5C22.5 20.3297 21.8297 21 21 21H3C2.17031 21 1.5 20.3297 1.5 19.5C1.5 18.6703 2.17031 18 3 18H21C21.8297 18 22.5 18.6703 22.5 19.5Z" fill="#333333" />
|
||||
</svg>
|
||||
|
||||
}
|
||||
25
src/components/ui/Icon/icons/Notification.tsx
Normal file
25
src/components/ui/Icon/icons/Notification.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function NotificationIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<g clipPath="url(#clip0_20_1490)">
|
||||
<g clipPath="url(#clip1_20_1490)">
|
||||
<path d="M9.99998 4C9.30858 4 8.74998 4.55859 8.74998 5.25V6C5.89842 6.57812 3.74998 9.10156 3.74998 12.125V12.8594C3.74998 14.6953 3.0742 16.4688 1.85545 17.8438L1.56639 18.168C1.23827 18.5352 1.16014 19.0625 1.35936 19.5117C1.55858 19.9609 2.0078 20.25 2.49998 20.25H17.5C17.9922 20.25 18.4375 19.9609 18.6406 19.5117C18.8437 19.0625 18.7617 18.5352 18.4336 18.168L18.1445 17.8438C16.9258 16.4688 16.25 14.6992 16.25 12.8594V12.125C16.25 9.10156 14.1015 6.57812 11.25 6V5.25C11.25 4.55859 10.6914 4 9.99998 4ZM11.7695 23.2695C12.2383 22.8008 12.5 22.1641 12.5 21.5H9.99998H7.49998C7.49998 22.1641 7.7617 22.8008 8.23045 23.2695C8.6992 23.7383 9.33592 24 9.99998 24C10.664 24 11.3008 23.7383 11.7695 23.2695Z" fill="#333333" />
|
||||
</g>
|
||||
</g>
|
||||
<path d="M14.75 0C19.1683 0 22.75 3.58172 22.75 8C22.75 12.4183 19.1683 16 14.75 16C10.3317 16 6.75 12.4183 6.75 8C6.75 3.58172 10.3317 0 14.75 0Z" fill="#EF4444" />
|
||||
<path d="M14.75 0C19.1683 0 22.75 3.58172 22.75 8C22.75 12.4183 19.1683 16 14.75 16C10.3317 16 6.75 12.4183 6.75 8C6.75 3.58172 10.3317 0 14.75 0Z" stroke="#E5E7EB" />
|
||||
<path d="M14.7766 12.0994C14.3079 12.0994 13.89 12.0189 13.5231 11.858C13.1585 11.697 12.8685 11.4732 12.6531 11.1868C12.44 10.898 12.324 10.563 12.305 10.1818H13.1999C13.2189 10.4162 13.2994 10.6186 13.4414 10.7891C13.5835 10.9571 13.7693 11.0874 13.9989 11.1797C14.2286 11.272 14.4831 11.3182 14.7624 11.3182C15.0749 11.3182 15.3519 11.2637 15.5934 11.1548C15.8349 11.0459 16.0243 10.8944 16.1616 10.7003C16.2989 10.5062 16.3675 10.2812 16.3675 10.0256C16.3675 9.75805 16.3013 9.52249 16.1687 9.31889C16.0361 9.11293 15.842 8.95194 15.5863 8.83594C15.3306 8.71993 15.0181 8.66193 14.6488 8.66193H14.0664V7.88068H14.6488C14.9376 7.88068 15.1909 7.8286 15.4087 7.72443C15.6289 7.62026 15.8005 7.47348 15.9237 7.28409C16.0491 7.0947 16.1119 6.87216 16.1119 6.61648C16.1119 6.37026 16.0574 6.15601 15.9485 5.97372C15.8396 5.79143 15.6857 5.64938 15.4869 5.54759C15.2904 5.44579 15.0584 5.39489 14.7908 5.39489C14.5399 5.39489 14.3031 5.44105 14.0806 5.53338C13.8604 5.62334 13.6805 5.75473 13.5408 5.92756C13.4012 6.09801 13.3254 6.30398 13.3136 6.54545H12.4613C12.4755 6.1643 12.5903 5.83049 12.8058 5.54403C13.0212 5.25521 13.3029 5.0303 13.6509 4.86932C14.0013 4.70833 14.386 4.62784 14.805 4.62784C15.2549 4.62784 15.6407 4.71899 15.9627 4.90128C16.2847 5.0812 16.5321 5.31913 16.7049 5.61506C16.8777 5.91098 16.9641 6.23059 16.9641 6.57386C16.9641 6.98343 16.8564 7.33262 16.641 7.62145C16.4279 7.91027 16.1379 8.11032 15.771 8.22159V8.27841C16.2302 8.35417 16.5889 8.54948 16.8469 8.86435C17.105 9.17685 17.234 9.56392 17.234 10.0256C17.234 10.4209 17.1263 10.776 16.9109 11.0909C16.6978 11.4034 16.4066 11.6496 16.0373 11.8295C15.668 12.0095 15.2478 12.0994 14.7766 12.0994Z" fill="white" />
|
||||
<defs>
|
||||
<clipPath id="clip0_20_1490">
|
||||
<rect width="17.5" height="20" fill="white" transform="translate(1.25 4)" />
|
||||
</clipPath>
|
||||
<clipPath id="clip1_20_1490">
|
||||
<path d="M1.25 4H18.75V24H1.25V4Z" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
);
|
||||
}
|
||||
8
src/components/ui/Icon/icons/Search.tsx
Normal file
8
src/components/ui/Icon/icons/Search.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function SearchIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M18.25 10.125C18.25 11.918 17.668 13.5742 16.6875 14.918L21.6328 19.8672C22.1211 20.3555 22.1211 21.1484 21.6328 21.6367C21.1445 22.125 20.3516 22.125 19.8633 21.6367L14.918 16.6875C13.5742 17.6719 11.918 18.25 10.125 18.25C5.63672 18.25 2 14.6133 2 10.125C2 5.63672 5.63672 2 10.125 2C14.6133 2 18.25 5.63672 18.25 10.125ZM10.125 15.75C10.8637 15.75 11.5951 15.6045 12.2776 15.3218C12.9601 15.0391 13.5801 14.6248 14.1025 14.1025C14.6248 13.5801 15.0391 12.9601 15.3218 12.2776C15.6045 11.5951 15.75 10.8637 15.75 10.125C15.75 9.38631 15.6045 8.65486 15.3218 7.97241C15.0391 7.28995 14.6248 6.66985 14.1025 6.14752C13.5801 5.62519 12.9601 5.21086 12.2776 4.92818C11.5951 4.64549 10.8637 4.5 10.125 4.5C9.38631 4.5 8.65486 4.64549 7.97241 4.92818C7.28995 5.21086 6.66985 5.62519 6.14752 6.14752C5.62519 6.66985 5.21086 7.28995 4.92818 7.97241C4.64549 8.65486 4.5 9.38631 4.5 10.125C4.5 10.8637 4.64549 11.5951 4.92818 12.2776C5.21086 12.9601 5.62519 13.5801 6.14752 14.1025C6.66985 14.6248 7.28995 15.0391 7.97241 15.3218C8.65486 15.6045 9.38631 15.75 10.125 15.75Z" fill="#333333" />
|
||||
</svg>
|
||||
|
||||
}
|
||||
9
src/components/ui/Icon/icons/Video.tsx
Normal file
9
src/components/ui/Icon/icons/Video.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export default function VideoIcon(props: SVGProps<SVGSVGElement>) {
|
||||
const color = props?.color || "#A0A7B5";
|
||||
return <svg width="25" height="26" viewBox="0 0 25 26" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path d="M18.5 13C18.5 11.2761 17.8152 9.62279 16.5962 8.40381C15.3772 7.18482 13.7239 6.5 12 6.5C10.2761 6.5 8.62279 7.18482 7.40381 8.40381C6.18482 9.62279 5.5 11.2761 5.5 13C5.5 14.7239 6.18482 16.3772 7.40381 17.5962C8.62279 18.8152 10.2761 19.5 12 19.5C13.7239 19.5 15.3772 18.8152 16.5962 17.5962C17.8152 16.3772 18.5 14.7239 18.5 13ZM4 13C4 10.8783 4.84285 8.84344 6.34315 7.34315C7.84344 5.84285 9.87827 5 12 5C14.1217 5 16.1566 5.84285 17.6569 7.34315C19.1571 8.84344 20 10.8783 20 13C20 15.1217 19.1571 17.1566 17.6569 18.6569C16.1566 20.1571 14.1217 21 12 21C9.87827 21 7.84344 20.1571 6.34315 18.6569C4.84285 17.1566 4 15.1217 4 13ZM9.88438 9.59688C10.1219 9.46563 10.4094 9.46875 10.6438 9.6125L15.1438 12.3625C15.3656 12.5 15.5031 12.7406 15.5031 13.0031C15.5031 13.2656 15.3656 13.5063 15.1438 13.6438L10.6438 16.3938C10.4125 16.5344 10.1219 16.5406 9.88438 16.4094C9.64688 16.2781 9.5 16.0281 9.5 15.7563V10.25C9.5 9.97813 9.64688 9.72813 9.88438 9.59688Z" fill={color} />
|
||||
</svg>
|
||||
|
||||
}
|
||||
16
src/components/ui/IconLabel/IconLabel.module.scss
Normal file
16
src/components/ui/IconLabel/IconLabel.module.scss
Normal file
@ -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;
|
||||
}
|
||||
19
src/components/ui/IconLabel/IconLabel.tsx
Normal file
19
src/components/ui/IconLabel/IconLabel.tsx
Normal file
@ -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 (
|
||||
<span className={clsx(styles.iconLabel, className)}>
|
||||
<Icon {...iconProps} />
|
||||
<span className={styles.label}>{children}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
5
src/components/ui/MetaLabel/MetaLabel.module.scss
Normal file
5
src/components/ui/MetaLabel/MetaLabel.module.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.metaLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
23
src/components/ui/MetaLabel/MetaLabel.tsx
Normal file
23
src/components/ui/MetaLabel/MetaLabel.tsx
Normal file
@ -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 (
|
||||
<div className={styles.metaLabel}>
|
||||
<IconLabel {...iconLabelProps} />
|
||||
<Typography color="secondary">
|
||||
•
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{children}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
10
src/components/ui/Section/Section.module.scss
Normal file
10
src/components/ui/Section/Section.module.scss
Normal file
@ -0,0 +1,10 @@
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
25
src/components/ui/Section/Section.tsx
Normal file
25
src/components/ui/Section/Section.tsx
Normal file
@ -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 (
|
||||
<section className={clsx(styles.section, className)}>
|
||||
{title &&
|
||||
<Typography className={clsx(styles.title, titleClassName)} as="h2" weight="medium" size="2xl">
|
||||
{title}
|
||||
</Typography>
|
||||
}
|
||||
<div className={clsx(styles.content, contentClassName)}>{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
32
src/components/ui/TabBar/TabBar.module.scss
Normal file
32
src/components/ui/TabBar/TabBar.module.scss
Normal file
@ -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;
|
||||
}
|
||||
37
src/components/ui/TabBar/TabBar.tsx
Normal file
37
src/components/ui/TabBar/TabBar.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./TabBar.module.scss";
|
||||
import Typography from "../Typography/Typography";
|
||||
|
||||
export type Tab<T extends string> = {
|
||||
label: string;
|
||||
value: T;
|
||||
};
|
||||
|
||||
type TabBarProps<T extends string> = {
|
||||
tabs: Tab<T>[];
|
||||
active: T;
|
||||
onChange: (value: T) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function TabBar<T extends string>({ tabs, active, onChange, className }: TabBarProps<T>) {
|
||||
return (
|
||||
<nav className={clsx(styles.tabBar, className)}>
|
||||
{tabs.map((tab) => {
|
||||
const isActive = tab.value === active;
|
||||
return (
|
||||
<button
|
||||
key={tab.value}
|
||||
className={clsx(styles.tab, { [styles.active]: isActive })}
|
||||
onClick={() => onChange(tab.value)}
|
||||
type="button"
|
||||
>
|
||||
<Typography color={isActive ? "black" : "secondary"} size="md" weight="semiBold">
|
||||
{tab.label}
|
||||
</Typography>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav >
|
||||
);
|
||||
}
|
||||
72
src/components/ui/Typography/Typography.module.scss
Normal file
72
src/components/ui/Typography/Typography.module.scss
Normal file
@ -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;
|
||||
}
|
||||
66
src/components/ui/Typography/Typography.tsx
Normal file
66
src/components/ui/Typography/Typography.tsx
Normal file
@ -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 (
|
||||
<Component
|
||||
className={clsx(
|
||||
styles[sizeMap[size]],
|
||||
styles[weightMap[weight]],
|
||||
styles[colorMap[color]],
|
||||
styles.typography,
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
textAlign: align
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user