diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0f5ba90b0..7df9981b1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,27 +35,27 @@ jobs: run: npm run lint && npm test # Собираем приложение - - name: Build Application - run: npm run build + # - name: Build Application + # run: npm run build # Публикуем приложение на Github Pages - - name: Deploy to Github Pages - uses: JamesIves/github-pages-deploy-action@4.2.1 - with: - branch: gh-pages - folder: dist + # - name: Deploy to Github Pages + # uses: JamesIves/github-pages-deploy-action@4.2.1 + # with: + # branch: gh-pages + # folder: dist # # Собираем Storybook - # - name: Build Storybook - # run: npm run build-storybook + - name: Build Storybook + run: npm run build-storybook # # # Публикуем Storybook на Github Pages - # - name: Deploy Storybook to Github Pages - # uses: JamesIves/github-pages-deploy-action@4.2.1 - # with: - # branch: gh-pages - # folder: storybook-static - # commit-message: "Automatically publish Storybook" + - name: Deploy Storybook to Github Pages + uses: JamesIves/github-pages-deploy-action@4.2.1 + with: + branch: gh-pages + folder: storybook-static + commit-message: "Automatically publish Storybook" # Останавливаем выполнение строго при неудачных тестах - name: Fail on failed tests diff --git a/package-lock.json b/package-lock.json index 5b1dfa392..03e1d998b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.8.0", "fork-ts-checker-webpack-plugin": "^8.0.0", + "gh-pages": "^6.3.0", "html-webpack-plugin": "^5.5.1", "husky": "^8.0.0", "jest": "^29.5.0", @@ -116,6 +117,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -5126,6 +5128,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -7135,6 +7138,7 @@ "integrity": "sha512-vWgY07R/eqj1/a0vsRKLI9o9klGZfpLNOVEnrv4nrccxBgYPjcf22IWwAoaBJ+wpA7Q4fVjCUM8lP0m01dpxcg==", "dev": true, "hasInstallScript": true, + "peer": true, "dependencies": { "@swc/counter": "^0.1.2", "@swc/types": "^0.1.5" @@ -7866,6 +7870,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", "dev": true, + "peer": true, "dependencies": { "@types/react": "*" } @@ -8052,6 +8057,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.9.tgz", "integrity": "sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.59.9", "@typescript-eslint/types": "5.59.9", @@ -8546,6 +8552,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8641,6 +8648,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8958,8 +8966,7 @@ "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true, - "peer": true + "dev": true }, "node_modules/async": { "version": "3.2.5", @@ -8996,7 +9003,6 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -9006,7 +9012,6 @@ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", "dev": true, - "peer": true, "dependencies": { "deep-equal": "^2.0.5" } @@ -9115,6 +9120,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -9516,6 +9522,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001541", "electron-to-chromium": "^1.4.535", @@ -10206,7 +10213,6 @@ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", "dev": true, - "peer": true, "dependencies": { "is-what": "^3.14.1" }, @@ -10288,6 +10294,7 @@ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", "dev": true, + "peer": true, "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.21", @@ -10416,8 +10423,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/data-urls": { "version": "3.0.2", @@ -11021,6 +11027,13 @@ "integrity": "sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==", "dev": true }, + "node_modules/email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true, + "license": "MIT" + }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -11120,7 +11133,6 @@ "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, "optional": true, - "peer": true, "dependencies": { "prr": "~1.0.1" }, @@ -11266,6 +11278,7 @@ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, "hasInstallScript": true, + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -11375,6 +11388,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz", "integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==", "dev": true, + "peer": true, "dependencies": { "@eslint/eslintrc": "^1.3.0", "@humanwhocodes/config-array": "^0.10.4", @@ -11529,6 +11543,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -11655,6 +11670,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flatmap": "^1.3.1", @@ -11684,6 +11700,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -12511,6 +12528,34 @@ "node": ">=10" } }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -13147,6 +13192,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gh-pages": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.3.0.tgz", + "integrity": "sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.4", + "commander": "^13.0.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^11.1.1", + "globby": "^11.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gh-pages/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/giget": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", @@ -13785,7 +13863,6 @@ "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", "dev": true, "optional": true, - "peer": true, "bin": { "image-size": "bin/image-size.js" }, @@ -14488,8 +14565,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/is-wsl": { "version": "2.2.0", @@ -14713,6 +14789,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.5.0", "@jest/types": "^29.5.0", @@ -16784,15 +16861,13 @@ "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/language-tags": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", "dev": true, - "peer": true, "dependencies": { "language-subtag-registry": "~0.3.2" } @@ -16826,7 +16901,6 @@ "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", "dev": true, - "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -16871,7 +16945,6 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "optional": true, - "peer": true, "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -16886,7 +16959,6 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "optional": true, - "peer": true, "bin": { "semver": "bin/semver" } @@ -16897,7 +16969,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -18200,6 +18271,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -18397,7 +18469,6 @@ "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", "dev": true, "optional": true, - "peer": true, "dependencies": { "debug": "^3.2.6", "iconv-lite": "^0.6.3", @@ -18416,7 +18487,6 @@ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "optional": true, - "peer": true, "dependencies": { "ms": "^2.1.1" } @@ -18427,7 +18497,6 @@ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "optional": true, - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -19148,7 +19217,6 @@ "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true, - "peer": true, "engines": { "node": ">= 0.10" } @@ -19392,6 +19460,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -19493,6 +19562,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -19638,8 +19708,7 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "node_modules/psl": { "version": "1.9.0", @@ -19855,6 +19924,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -19912,6 +19982,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -19952,6 +20023,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -20573,6 +20645,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -20590,6 +20663,7 @@ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.1.tgz", "integrity": "sha512-cBTxmgyVA1nXPvIK4brjJMXOMJ2v2YrQEuHqLw3LylGb3gsR6jAvdjHMcy/+JGTmmIF9SauTrLLR7bsWDMWqgg==", "dev": true, + "peer": true, "dependencies": { "klona": "^2.0.6", "neo-async": "^2.6.2" @@ -20628,8 +20702,7 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "node_modules/saxes": { "version": "6.0.0", @@ -21335,11 +21408,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", "dev": true, + "peer": true, "engines": { "node": ">= 12.13.0" }, @@ -21964,6 +22051,19 @@ "node": ">=12" } }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -22154,6 +22254,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, + "peer": true, "engines": { "node": ">=12.20" }, @@ -22199,6 +22300,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -22769,6 +22871,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.86.0.tgz", "integrity": "sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==", "dev": true, + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -22816,6 +22919,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -22914,6 +23018,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -22967,6 +23072,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.0.tgz", "integrity": "sha512-HmNB5QeSl1KpulTBQ8UT4FPrByYyaLxpJoQ0+s7EvUrMc16m0ZS1sgb1XGqzmgCPk0c9y+aaXxn11tbLzuM7NQ==", "dev": true, + "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -23026,6 +23132,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -23168,6 +23275,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -23594,6 +23702,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -26990,6 +27099,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -28503,6 +28613,7 @@ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.2.tgz", "integrity": "sha512-vWgY07R/eqj1/a0vsRKLI9o9klGZfpLNOVEnrv4nrccxBgYPjcf22IWwAoaBJ+wpA7Q4fVjCUM8lP0m01dpxcg==", "dev": true, + "peer": true, "requires": { "@swc/core-darwin-arm64": "1.4.2", "@swc/core-darwin-x64": "1.4.2", @@ -29091,6 +29202,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", "dev": true, + "peer": true, "requires": { "@types/react": "*" } @@ -29254,6 +29366,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.9.tgz", "integrity": "sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "5.59.9", "@typescript-eslint/types": "5.59.9", @@ -29626,7 +29739,8 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true + "dev": true, + "peer": true }, "acorn-globals": { "version": "7.0.1", @@ -29695,6 +29809,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -29940,8 +30055,7 @@ "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true, - "peer": true + "dev": true }, "async": { "version": "3.2.5", @@ -29971,15 +30085,13 @@ "version": "4.7.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", - "dev": true, - "peer": true + "dev": true }, "axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", "dev": true, - "peer": true, "requires": { "deep-equal": "^2.0.5" } @@ -30057,6 +30169,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -30366,6 +30479,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001541", "electron-to-chromium": "^1.4.535", @@ -30884,7 +30998,6 @@ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", "dev": true, - "peer": true, "requires": { "is-what": "^3.14.1" } @@ -30945,6 +31058,7 @@ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", "dev": true, + "peer": true, "requires": { "icss-utils": "^5.1.0", "postcss": "^8.4.21", @@ -31040,8 +31154,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "peer": true + "dev": true }, "data-urls": { "version": "3.0.2", @@ -31513,6 +31626,12 @@ "integrity": "sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==", "dev": true }, + "email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true + }, "emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -31585,7 +31704,6 @@ "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, "optional": true, - "peer": true, "requires": { "prr": "~1.0.1" } @@ -31709,6 +31827,7 @@ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, + "peer": true, "requires": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", @@ -31793,6 +31912,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz", "integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==", "dev": true, + "peer": true, "requires": { "@eslint/eslintrc": "^1.3.0", "@humanwhocodes/config-array": "^0.10.4", @@ -32008,6 +32128,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -32094,6 +32215,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.6", "array.prototype.flatmap": "^1.3.1", @@ -32139,6 +32261,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", "dev": true, + "peer": true, "requires": {} }, "eslint-plugin-storybook": { @@ -32645,6 +32768,23 @@ } } }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -33105,6 +33245,29 @@ "get-intrinsic": "^1.1.1" } }, + "gh-pages": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.3.0.tgz", + "integrity": "sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==", + "dev": true, + "requires": { + "async": "^3.2.4", + "commander": "^13.0.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^11.1.1", + "globby": "^11.1.0" + }, + "dependencies": { + "commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true + } + } + }, "giget": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", @@ -33569,8 +33732,7 @@ "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "immutable": { "version": "4.3.4", @@ -34041,8 +34203,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true, - "peer": true + "dev": true }, "is-wsl": { "version": "2.2.0", @@ -34210,6 +34371,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", "dev": true, + "peer": true, "requires": { "@jest/core": "^29.5.0", "@jest/types": "^29.5.0", @@ -35733,15 +35895,13 @@ "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true, - "peer": true + "dev": true }, "language-tags": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", "dev": true, - "peer": true, "requires": { "language-subtag-registry": "~0.3.2" } @@ -35772,7 +35932,6 @@ "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", "dev": true, - "peer": true, "requires": { "copy-anything": "^2.0.1", "errno": "^0.1.1", @@ -35792,7 +35951,6 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "optional": true, - "peer": true, "requires": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -35803,16 +35961,14 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "optional": true, - "peer": true + "optional": true } } }, @@ -36694,6 +36850,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -36845,7 +37002,6 @@ "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", "dev": true, "optional": true, - "peer": true, "requires": { "debug": "^3.2.6", "iconv-lite": "^0.6.3", @@ -36858,7 +37014,6 @@ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "optional": true, - "peer": true, "requires": { "ms": "^2.1.1" } @@ -36869,7 +37024,6 @@ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "optional": true, - "peer": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } @@ -37390,8 +37544,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "peer": true + "dev": true }, "parse5": { "version": "7.1.2", @@ -37569,6 +37722,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -37637,7 +37791,8 @@ "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true + "dev": true, + "peer": true }, "prettier-linter-helpers": { "version": "1.0.0", @@ -37751,8 +37906,7 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "psl": { "version": "1.9.0", @@ -37914,6 +38068,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -37962,6 +38117,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -37996,7 +38152,8 @@ "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", - "dev": true + "dev": true, + "peer": true }, "react-remove-scroll": { "version": "2.5.5", @@ -38431,6 +38588,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, + "peer": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -38442,6 +38600,7 @@ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.1.tgz", "integrity": "sha512-cBTxmgyVA1nXPvIK4brjJMXOMJ2v2YrQEuHqLw3LylGb3gsR6jAvdjHMcy/+JGTmmIF9SauTrLLR7bsWDMWqgg==", "dev": true, + "peer": true, "requires": { "klona": "^2.0.6", "neo-async": "^2.6.2" @@ -38452,8 +38611,7 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "saxes": { "version": "6.0.0", @@ -39034,11 +39192,21 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", "dev": true, + "peer": true, "requires": {} }, "supports-color": { @@ -39512,6 +39680,15 @@ "punycode": "^2.1.1" } }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -39643,7 +39820,8 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true + "dev": true, + "peer": true }, "type-is": { "version": "1.6.18", @@ -39676,7 +39854,8 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", - "dev": true + "dev": true, + "peer": true }, "ufo": { "version": "1.4.0", @@ -40093,6 +40272,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.86.0.tgz", "integrity": "sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==", "dev": true, + "peer": true, "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -40130,7 +40310,8 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true + "dev": true, + "peer": true }, "acorn-import-assertions": { "version": "1.9.0", @@ -40146,6 +40327,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "peer": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -40203,6 +40385,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -40244,6 +40427,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.0.tgz", "integrity": "sha512-HmNB5QeSl1KpulTBQ8UT4FPrByYyaLxpJoQ0+s7EvUrMc16m0ZS1sgb1XGqzmgCPk0c9y+aaXxn11tbLzuM7NQ==", "dev": true, + "peer": true, "requires": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -40282,6 +40466,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", diff --git a/package.json b/package.json index 492664d1f..4dc8d5516 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "test": "jest src", "lint": "eslint src --fix", "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" + "build-storybook": "storybook build", + "deploy": "npx gh-pages -d dist" }, "license": "MIT", "devDependencies": { @@ -46,6 +47,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.8.0", "fork-ts-checker-webpack-plugin": "^8.0.0", + "gh-pages": "^6.3.0", "html-webpack-plugin": "^5.5.1", "husky": "^8.0.0", "jest": "^29.5.0", diff --git a/src/app/App.tsx b/src/app/App.tsx index dcc0ff8ad..814f07164 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -8,7 +8,12 @@ function App() {
logo

- Текст писать тут + - Разобраться с React & TS +
+ - Овладеть технологиями: React, TS, Webpack, babel, jest и прочие модули +
+ - Владею: JS, jQ +
- Опыт: PHP, C#, Vue

diff --git a/src/homeworks/ts1/1_base.js b/src/homeworks/ts1/1_base.js deleted file mode 100644 index 611b3a92f..000000000 --- a/src/homeworks/ts1/1_base.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Нужно превратить файл в ts и указать типы аргументов и типы возвращаемого значения - * */ -export const removePlus = (string) => string.replace(/^\+/, ''); - -export const addPlus = (string) => `+${string}`; - -export const removeFirstZeros = (value) => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); - -export const getBeautifulNumber = (value, separator = ' ') => - value?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); - -export const round = (value, accuracy = 2) => { - const d = 10 ** accuracy; - return Math.round(value * d) / d; -}; - -const transformRegexp = - /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; - -export const getTransformFromCss = (transformCssString) => { - const data = transformCssString.match(transformRegexp); - if (!data) return { x: 0, y: 0 }; - return { - x: parseInt(data[6], 10), - y: parseInt(data[8], 10), - }; -}; - -export const getColorContrastValue = ([red, green, blue]) => - // http://www.w3.org/TR/AERT#color-contrast - Math.round((red * 299 + green * 587 + blue * 114) / 1000); - -export const getContrastType = (contrastValue) => (contrastValue > 125 ? 'black' : 'white'); - -export const shortColorRegExp = /^#[0-9a-f]{3}$/i; -export const longColorRegExp = /^#[0-9a-f]{6}$/i; - -export const checkColor = (color) => { - if (!longColorRegExp.test(color) && !shortColorRegExp.test(color)) throw new Error(`invalid hex color: ${color}`); -}; - -export const hex2rgb = (color) => { - checkColor(color); - if (shortColorRegExp.test(color)) { - const red = parseInt(color.substring(1, 2), 16); - const green = parseInt(color.substring(2, 3), 16); - const blue = parseInt(color.substring(3, 4), 16); - return [red, green, blue]; - } - const red = parseInt(color.substring(1, 3), 16); - const green = parseInt(color.substring(3, 5), 16); - const blue = parseInt(color.substring(5, 8), 16); - return [red, green, blue]; -}; - -export const getNumberedArray = (arr) => arr.map((value, number) => ({ value, number })); -export const toStringArray = (arr) => arr.map(({ value, number }) => `${value}_${number}`); - -export const transformCustomers = (customers) => { - return customers.reduce((acc, customer) => { - acc[customer.id] = { name: customer.name, age: customer.age, isSubscribed: customer.isSubscribed }; - return acc; - }, {}); -}; diff --git a/src/homeworks/ts1/1_base.test.js b/src/homeworks/ts1/1_base.test.js index ee68dc16f..dbd708234 100644 --- a/src/homeworks/ts1/1_base.test.js +++ b/src/homeworks/ts1/1_base.test.js @@ -1,26 +1,21 @@ -// Этот блок кода удалить и раскомментировать код ниже -it('remove it', () => { - expect(true).toBe(true); -}); +import { transformCustomers } from './1_base'; + +describe('all', () => { + it('transformCustomers', () => { + const customers = [ + { id: 1, name: 'John', age: 25, isSubscribed: true }, + { id: 2, name: 'Mary', age: 40, isSubscribed: false }, + { id: 3, name: 'Bob', age: 32, isSubscribed: true }, + { id: 4, name: 'Alice', age: 22, isSubscribed: true }, + { id: 5, name: 'David', age: 48, isSubscribed: false }, + ]; -// import { transformCustomers } from './1_base'; -// -// describe('all', () => { -// it('transformCustomers', () => { -// const customers = [ -// { id: 1, name: 'John', age: 25, isSubscribed: true }, -// { id: 2, name: 'Mary', age: 40, isSubscribed: false }, -// { id: 3, name: 'Bob', age: 32, isSubscribed: true }, -// { id: 4, name: 'Alice', age: 22, isSubscribed: true }, -// { id: 5, name: 'David', age: 48, isSubscribed: false }, -// ]; -// -// expect(transformCustomers(customers)).toEqual({ -// 1: { name: 'John', age: 25, isSubscribed: true }, -// 2: { name: 'Mary', age: 40, isSubscribed: false }, -// 3: { name: 'Bob', age: 32, isSubscribed: true }, -// 4: { name: 'Alice', age: 22, isSubscribed: true }, -// 5: { name: 'David', age: 48, isSubscribed: false }, -// }); -// }); -// }); + expect(transformCustomers(customers)).toEqual({ + 1: { name: 'John', age: 25, isSubscribed: true }, + 2: { name: 'Mary', age: 40, isSubscribed: false }, + 3: { name: 'Bob', age: 32, isSubscribed: true }, + 4: { name: 'Alice', age: 22, isSubscribed: true }, + 5: { name: 'David', age: 48, isSubscribed: false }, + }); + }); +}); diff --git a/src/homeworks/ts1/1_base.ts b/src/homeworks/ts1/1_base.ts new file mode 100644 index 000000000..b33ea7ca4 --- /dev/null +++ b/src/homeworks/ts1/1_base.ts @@ -0,0 +1,89 @@ +/** + * Нужно превратить файл в ts и указать типы аргументов и типы возвращаемого значения + * */ +export const removePlus = (string: string): string => string.replace(/^\+/, ''); +export const addPlus = (string: string | number): string => `+${string}`; +export const removeFirstZeros = (value: string): string => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); + +export const getBeautifulNumber = (value: string | null, separator = ' '): string | null => + value?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); + +export const round = (value: number, accuracy = 2): number => { + const d = 10 ** accuracy; + return Math.round(value * d) / d; +}; + +const transformRegexp = + /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; + +type xy = { + x: number; + y: number; +}; + +export const getTransformFromCss = (transformCssString: string): xy => { + const data = transformCssString.match(transformRegexp); + if (!data) return { x: 0, y: 0 }; + return { + x: parseInt(data[6], 10), + y: parseInt(data[8], 10), + }; +}; + +export const getColorContrastValue = ([red, green, blue]: [number, number, number]): number => + // http://www.w3.org/TR/AERT#color-contrast + Math.round((red * 299 + green * 587 + blue * 114) / 1000); + +export const getContrastType = (contrastValue: number): 'black' | 'white' => (contrastValue > 125 ? 'black' : 'white'); + +export const shortColorRegExp = /^#[0-9a-f]{3}$/i; +export const longColorRegExp = /^#[0-9a-f]{6}$/i; + +export const checkColor = (color: string): void | Error => { + if (!longColorRegExp.test(color) && !shortColorRegExp.test(color)) throw new Error(`invalid hex color: ${color}`); +}; + +export const hex2rgb = (color: string): [red: number, green: number, blue: number] | Error => { + checkColor(color); + if (shortColorRegExp.test(color)) { + const red = parseInt(color.substring(1, 2), 16); + const green = parseInt(color.substring(2, 3), 16); + const blue = parseInt(color.substring(3, 4), 16); + return [red, green, blue]; + } + const red = parseInt(color.substring(1, 3), 16); + const green = parseInt(color.substring(3, 5), 16); + const blue = parseInt(color.substring(5, 8), 16); + return [red, green, blue]; +}; + +type valueNumber = { + value: number; + number: number; +}; + +export const getNumberedArray = (arr: []): Array => + arr.map((value: number, number: number) => ({ value, number })); +export const toStringArray = (arr: []): Array => arr.map(({ value, number }) => `${value}_${number}`); + +type customerType = { + id: number; + name: string; + age: number; + isSubscribed: boolean; +}; + +type customerWithoutId = { + name: string; + age: number; + isSubscribed: boolean; +}; + +type customRec = Record; + +export const transformCustomers = (customers: Array): customRec => { + return customers.reduce((acc, customer) => { + acc[customer.id] = { name: customer.name, age: customer.age, isSubscribed: customer.isSubscribed }; + return acc; + }, {}); +}; diff --git a/src/homeworks/ts1/2_repair.ts b/src/homeworks/ts1/2_repair.ts index 19e98c068..7589de4d9 100644 --- a/src/homeworks/ts1/2_repair.ts +++ b/src/homeworks/ts1/2_repair.ts @@ -2,46 +2,49 @@ * Здесь код с ошибками типов. Нужно их устранить * */ -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export const getFakeApi = async (): void => { -// const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); -// console.log(result); -// }; -// -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export class SomeClass { -// constructor() { -// this.set = new Set([1]); -// this.channel = new BroadcastChannel('test-broadcast-channel'); -// } -// } -// -// export type Data = { -// type: 'Money' | 'Percent'; -// value: DataValue; -// }; -// -// export type DataValue = Money | Percent; -// -// export type Money = { -// currency: string; -// amount: number; -// }; -// -// export type Percent = { -// percent: number; -// }; -// -// // Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому -// const getDataAmount = (data: Data): number => { -// switch (data.type) { -// case 'Money': -// return data.value.amount; -// -// default: { -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const unhandled: never = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться -// throw new Error(`unknown type: ${data.type}`); -// } -// } -// }; +// Мы это не проходили, но по тексту ошибки можно понять, как это починить +export const getFakeApi = async (): Promise => { + const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); + console.log(result); +}; + +// Мы это не проходили, но по тексту ошибки можно понять, как это починить +export class SomeClass { + set: Set; + channel: BroadcastChannel; + + constructor() { + this.set = new Set([1]); + this.channel = new BroadcastChannel('test-broadcast-channel'); + } +} + +export type Data = { + type: 'Money' | 'Percent'; + value: DataValue; +}; + +export type DataValue = Money | Percent; + +export type Money = { + currency: string; + amount: number; +}; + +export type Percent = { + percent: number; +}; + +// Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому +export const getDataAmount = (data: Data): number => { + switch (data.type) { + case 'Money': + return (data.value as Money).amount; + + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const unhandled: never | Data = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться + throw new Error(`unknown type: ${data.type}`); + } + } +}; diff --git a/src/homeworks/ts1/3_write.test.js b/src/homeworks/ts1/3_write.test.js index 5b5173063..a11ac89c3 100644 --- a/src/homeworks/ts1/3_write.test.js +++ b/src/homeworks/ts1/3_write.test.js @@ -1,35 +1,30 @@ -// Этот блок кода удалить и раскомментировать код ниже -it('remove it', () => { - expect(true).toBe(true); -}); +import { createRandomOperation, createRandomProduct } from './3_write'; + +describe('all', () => { + it('operation', () => { + const createdAt = '2023-06-06T12:06:56.957Z'; + const operation = createRandomOperation(createdAt); + expect(operation).toHaveProperty('createdAt', createdAt); + expect(operation).toHaveProperty('id'); + expect(operation).toHaveProperty('name'); + expect(operation).toHaveProperty('desc'); + expect(operation).toHaveProperty('createdAt'); + expect(operation).toHaveProperty('amount'); + expect(operation).toHaveProperty('category'); + expect(operation).toHaveProperty('type'); + }); -// import { createRandomOperation, createRandomProduct } from './3_write'; -// -// describe('all', () => { -// it('operation', () => { -// const createdAt = '2023-06-06T12:06:56.957Z'; -// const operation = createRandomOperation(createdAt); -// expect(operation).toHaveProperty('createdAt', createdAt); -// expect(operation).toHaveProperty('id'); -// expect(operation).toHaveProperty('name'); -// expect(operation).toHaveProperty('desc'); -// expect(operation).toHaveProperty('createdAt'); -// expect(operation).toHaveProperty('amount'); -// expect(operation).toHaveProperty('category'); -// expect(operation).toHaveProperty('type'); -// }); -// -// it('product', () => { -// const createdAt = '2023-06-06T12:06:56.957Z'; -// const product = createRandomProduct(createdAt); -// expect(product).toHaveProperty('createdAt', createdAt); -// expect(product).toHaveProperty('id'); -// expect(product).toHaveProperty('name'); -// expect(product).toHaveProperty('photo'); -// expect(product).toHaveProperty('desc'); -// expect(product).toHaveProperty('createdAt'); -// expect(product).toHaveProperty('oldPrice'); -// expect(product).toHaveProperty('price'); -// expect(product).toHaveProperty('category'); -// }); -// }); + it('product', () => { + const createdAt = '2023-06-06T12:06:56.957Z'; + const product = createRandomProduct(createdAt); + expect(product).toHaveProperty('createdAt', createdAt); + expect(product).toHaveProperty('id'); + expect(product).toHaveProperty('name'); + expect(product).toHaveProperty('photo'); + expect(product).toHaveProperty('desc'); + expect(product).toHaveProperty('createdAt'); + expect(product).toHaveProperty('oldPrice'); + expect(product).toHaveProperty('price'); + expect(product).toHaveProperty('category'); + }); +}); diff --git a/src/homeworks/ts1/3_write.ts b/src/homeworks/ts1/3_write.ts index 15f9dcdf2..f5461ebf6 100644 --- a/src/homeworks/ts1/3_write.ts +++ b/src/homeworks/ts1/3_write.ts @@ -5,52 +5,103 @@ * В целом сделайте так, как вам будет удобно. * */ -/** - * Нужно создать тип Category, он будет использоваться ниже. - * Категория содержит - * - id (строка) - * - name (строка) - * - photo (строка, необязательно) - * - * Продукт (Product) содержит - * - id (строка) - * - name (строка) - * - photo (строка) - * - desc (строка, необязательно) - * - createdAt (строка) - * - oldPrice (число, необязательно) - * - price (число) - * - category (Категория) - * - * Операция (Operation) может быть либо тратой (Cost), либо доходом (Profit) - * - * Трата (Cost) содержит - * - id (строка) - * - name (строка) - * - desc (строка, необязательно) - * - createdAt (строка) - * - amount (число) - * - category (Категория) - * - type ('Cost') - * - * Доход (Profit) содержит - * - id (строка) - * - name (строка) - * - desc (строка, необязательно) - * - createdAt (строка) - * - amount (число) - * - category (Категория) - * - type ('Profit') - * */ +export type Category = { + id: string; + name: string; + photo?: string; +}; + +export type Product = { + id: string; + name: string; + photo: string; + desc?: string; + createdAt: string; + oldPrice?: number; + price: number; + category: Category; +}; + +// Траты +export type Cost = { + id: string; + name: string; + desc?: string; + createdAt: string; + amount: number; + category: Category; + type: 'Cost'; +}; + +// Доходы +export type Profit = { + id: string; + name: string; + desc?: string; + createdAt: string; + amount: number; + category: Category; + type: 'Profit'; +}; + +export type Operation = Cost | Profit; + +export function getRandomNumber(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +export function getRandomChars(pref = '', len = 16): string { + const buffer = window.crypto.getRandomValues(new Uint8Array(len)); + return ( + pref + + Array.from(buffer) + .map((b) => b.toString(16).padStart(2, '0')) + .join('') + ); +} + +export function getRandomPhotoUrl(): string { + const photos = [ + 'https://resources.cdn-kaspi.kz/img/m/p/p55/p22/57117341.jpg', + 'https://sonycenter.kz/image/cache/catalog/products/old/PS719999591-Sony-PlayStation-Classic/PS719999591-Sony-PlayStation-Classic-7694-600x600.png', + 'https://www.euronics.lv/UserFiles/Products/Images/368452-554932-medium.png', + ]; + return photos[getRandomNumber(0, photos.length - 1)]; +} /** * Создает случайный продукт (Product). * Принимает дату создания (строка) * */ -// export const createRandomProduct = (createdAt: string) => {}; +export const createRandomProduct = (createdAt: string): Product => { + const prod: Product = { + id: getRandomNumber(1, 100000).toString(), + name: getRandomChars('name-', 8), + photo: getRandomPhotoUrl(), + desc: getRandomChars('desc-', 8), + createdAt, + oldPrice: getRandomNumber(1, 100000), + price: getRandomNumber(1, 100000), + category: { id: getRandomNumber(1, 100000).toString(), name: getRandomChars('cat-name-', 4) }, + }; + console.log('rndProd', prod); + return prod; +}; /** * Создает случайную операцию (Operation). * Принимает дату создания (строка) * */ -// export const createRandomOperation = (createdAt: string) => {}; +export const createRandomOperation = (createdAt: string): Operation => { + const op: Operation = { + id: getRandomNumber(1, 100000).toString(), + name: getRandomChars('name-', 8), + desc: getRandomChars('desc-', 8), + createdAt, + amount: getRandomNumber(500, 3000), + category: { id: getRandomNumber(1, 100000).toString(), name: getRandomChars('cat-name-', 4) }, + type: getRandomNumber(0, 1) ? 'Cost' : 'Profit', + }; + console.log('rndOp', op); + return op; +}; diff --git a/src/shared/add-to-cart/add-to-cart.module.css b/src/shared/add-to-cart/add-to-cart.module.css new file mode 100644 index 000000000..a4fcfbe9c --- /dev/null +++ b/src/shared/add-to-cart/add-to-cart.module.css @@ -0,0 +1,41 @@ +.addButton { + background: #27ae60; + color: #fff; + border: none; + padding: 8px 16px; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + width: 100px; + text-align: center; +} + +.counter { + display: flex; + align-items: center; + border-radius: 6px; + overflow: hidden; + width: 100px; + background: #27ae60; +} + +.btn { + background: transparent; + color: #fff; + border: none; + padding: 8px 12px; + font-size: 14px; + font-weight: 500; + cursor: pointer; +} + +.count { + background: transparent; + text-align: center; + color: #fff; + flex: 1; + border: none; + width: 20px; + outline: none; +} \ No newline at end of file diff --git a/src/shared/add-to-cart/add-to-cart.stories.tsx b/src/shared/add-to-cart/add-to-cart.stories.tsx new file mode 100644 index 000000000..50b8a9219 --- /dev/null +++ b/src/shared/add-to-cart/add-to-cart.stories.tsx @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { AddToCart } from './add-to-cart'; + +const meta: Meta = { + title: 'Common/AddToCart', + component: AddToCart, + parameters: { + layout: 'centered', + }, + argTypes: { + count: { + control: 'number', + description: 'Количество товара в корзине', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + count: 0, + }, +}; + +export const InCart: Story = { + args: { + count: 1, + }, +}; diff --git a/src/shared/add-to-cart/add-to-cart.tsx b/src/shared/add-to-cart/add-to-cart.tsx new file mode 100644 index 000000000..6489095b1 --- /dev/null +++ b/src/shared/add-to-cart/add-to-cart.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import styles from './add-to-cart.module.css'; + +interface AddToCartProps { + count: number; +} + +export const AddToCart: React.FC = ({ count }) => { + if (count === 0) { + return ( + + ); + } + + return ( +
+ + + +
+ ); +}; diff --git a/src/shared/cart-item/cart-item.module.css b/src/shared/cart-item/cart-item.module.css new file mode 100644 index 000000000..78d529bf7 --- /dev/null +++ b/src/shared/cart-item/cart-item.module.css @@ -0,0 +1,54 @@ +.item { + background: white; + border-radius: 8px; + padding: 12px 16px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); + border: 1px solid #e0e0e0; + display: flex; + align-items: center; + gap: 12px; + min-height: 80px; + max-width: 800px; +} + +.imageContainer { + width: 60px; + height: 60px; + border-radius: 6px; + overflow: hidden; + background: #f5f5f5; + flex-shrink: 0; +} + +.image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.content { + flex: 1; + display: flex; + align-items: center; +} + +.name { + margin: 0; + font-size: 16px; + font-weight: 600; + color: #333; + line-height: 1.3; +} + +.removeButton { + background: #e74c3c; + color: white; + border: none; + padding: 8px 16px; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + flex-shrink: 0; + transition: background-color 0.2s ease; +} diff --git a/src/shared/cart-item/cart-item.stories.tsx b/src/shared/cart-item/cart-item.stories.tsx new file mode 100644 index 000000000..20ba6965f --- /dev/null +++ b/src/shared/cart-item/cart-item.stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { CartItem } from './cart-item'; +import { createRandomProduct } from '../../homeworks/ts1/3_write'; + +const meta: Meta = { + title: 'Common/CartItem', + component: CartItem, + parameters: { + layout: 'padded', + }, + argTypes: { + name: { + control: 'text', + }, + photo: { + control: 'text', + }, + onRemove: { + action: 'removed', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const PlayStationCart: Story = { + args: { + id: '1', + name: 'Sony Playstation 1', + photo: + 'https://sonycenter.kz/image/cache/catalog/products/old/PS719999591-Sony-PlayStation-Classic/PS719999591-Sony-PlayStation-Classic-7694-600x600.png', + createdAt: '2025-02-15', + price: 5000, + category: { + id: '1', + name: 'Tech', + }, + }, +}; + +export const RandomProduct: Story = { + args: createRandomProduct('2024-01-15'), +}; diff --git a/src/shared/cart-item/cart-item.tsx b/src/shared/cart-item/cart-item.tsx new file mode 100644 index 000000000..be320d947 --- /dev/null +++ b/src/shared/cart-item/cart-item.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Product } from '../../homeworks/ts1/3_write'; +import styles from './cart-item.module.css'; + +interface CartItemProps extends Product { + onRemove?: () => void; +} + +export const CartItem: React.FC = ({ name, photo, onRemove }) => { + return ( +
+
+ {name} +
+
+

{name}

+
+ +
+ ); +}; diff --git a/src/shared/header/header.module.css b/src/shared/header/header.module.css new file mode 100644 index 000000000..0c23c9edf --- /dev/null +++ b/src/shared/header/header.module.css @@ -0,0 +1,15 @@ +.header { + top: 0; + background-color: white; + border-bottom: 1px solid #e0e0e0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + z-index: 100; +} + +.container { + padding: 0 20px; + display: flex; + align-items: center; + justify-content: start; + height: 64px; +} diff --git a/src/shared/header/header.stories.tsx b/src/shared/header/header.stories.tsx new file mode 100644 index 000000000..24ae425d9 --- /dev/null +++ b/src/shared/header/header.stories.tsx @@ -0,0 +1,15 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Header } from './header'; + +const meta: Meta = { + title: 'Common/Header', + component: Header, + parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/shared/header/header.tsx b/src/shared/header/header.tsx new file mode 100644 index 000000000..fa8fdbea8 --- /dev/null +++ b/src/shared/header/header.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Logo } from '../logo/logo'; +import styles from './header.module.css'; + +export const Header: React.FC = () => { + return ( +
+
+ +
+
+ ); +}; diff --git a/src/shared/layout/layout.module.css b/src/shared/layout/layout.module.css new file mode 100644 index 000000000..f4f16a848 --- /dev/null +++ b/src/shared/layout/layout.module.css @@ -0,0 +1,11 @@ +.layout { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.main { + flex: 1; + padding: 0; + background-color: #fafafa; +} \ No newline at end of file diff --git a/src/shared/layout/layout.stories.tsx b/src/shared/layout/layout.stories.tsx new file mode 100644 index 000000000..8a6cbb7fc --- /dev/null +++ b/src/shared/layout/layout.stories.tsx @@ -0,0 +1,24 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Layout } from './layout'; + +const meta: Meta = { + title: 'Common/Layout', + component: Layout, + parameters: { + layout: 'fullscreen', + }, + argTypes: { + mainContent: { + control: 'text', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + mainContent: 'Page content', + }, +}; diff --git a/src/shared/layout/layout.tsx b/src/shared/layout/layout.tsx new file mode 100644 index 000000000..97f1e7afe --- /dev/null +++ b/src/shared/layout/layout.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Header } from '../header/header'; +import styles from './layout.module.css'; + +interface LayoutProps { + mainContent: React.ReactNode; +} + +export const Layout: React.FC = ({ mainContent }) => { + return ( +
+
+
{mainContent}
+
+ ); +}; diff --git a/src/shared/logo/logo.module.css b/src/shared/logo/logo.module.css new file mode 100644 index 000000000..5564b35b7 --- /dev/null +++ b/src/shared/logo/logo.module.css @@ -0,0 +1,25 @@ +.logo { + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: transform 0.2s ease; +} + +.circle { + background: linear-gradient(135deg, #2d2d2d 0%, #7cbcb6 100%); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; +} + +.text { + color: white; + font-weight: bold; + font-family: sans-serif; + user-select: none; + font-size: 24px; +} diff --git a/src/shared/logo/logo.stories.tsx b/src/shared/logo/logo.stories.tsx new file mode 100644 index 000000000..6b05e85e9 --- /dev/null +++ b/src/shared/logo/logo.stories.tsx @@ -0,0 +1,12 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Logo } from './logo'; + +const meta: Meta = { + title: 'Common/Logo', + component: Logo, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/shared/logo/logo.tsx b/src/shared/logo/logo.tsx new file mode 100644 index 000000000..7617d3bbe --- /dev/null +++ b/src/shared/logo/logo.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import styles from './logo.module.css'; + +export const Logo: React.FC = () => { + return ( +
+
+ X +
+
+ ); +}; diff --git a/src/shared/modal/modal.module.css b/src/shared/modal/modal.module.css new file mode 100644 index 000000000..986f7098b --- /dev/null +++ b/src/shared/modal/modal.module.css @@ -0,0 +1,56 @@ +.overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.modal { + background-color: white; + border-radius: 8px; + padding: 24px; + position: relative; + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +.closeButton { + position: absolute; + top: 12px; + right: 12px; + background: none; + border: none; + font-size: 24px; + font-weight: bold; + color: #666; + cursor: pointer; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background-color 0.2s ease; +} + +.closeButton:hover { + background-color: #f0f0f0; + color: #333; +} + +.closeButton:focus { + outline: none; +} + +.content { + padding-right: 20px; +} \ No newline at end of file diff --git a/src/shared/modal/modal.stories.tsx b/src/shared/modal/modal.stories.tsx new file mode 100644 index 000000000..004ab28ff --- /dev/null +++ b/src/shared/modal/modal.stories.tsx @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Modal } from './modal'; + +const meta: Meta = { + title: 'Common/Modal', + component: Modal, + parameters: { + layout: 'centered', + }, + argTypes: { + visible: { + control: 'boolean', + }, + children: { + control: 'text', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + visible: true, + children: 'Modal content', + }, +}; diff --git a/src/shared/modal/modal.tsx b/src/shared/modal/modal.tsx new file mode 100644 index 000000000..8fbb87e85 --- /dev/null +++ b/src/shared/modal/modal.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import styles from './modal.module.css'; + +interface ModalProps { + visible: boolean; + children: React.ReactNode; +} + +export const Modal: React.FC = ({ visible, children }) => { + if (!visible) { + return null; + } + + return ( +
+
+ +
{children}
+
+
+ ); +}; diff --git a/src/shared/operation-card/operation-card.module.css b/src/shared/operation-card/operation-card.module.css new file mode 100644 index 000000000..144d1c8d5 --- /dev/null +++ b/src/shared/operation-card/operation-card.module.css @@ -0,0 +1,66 @@ +.card { + background: white; + border-radius: 6px; + padding: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border: 1px solid #e0e0e0; + cursor: pointer; + height: 100px; + width: 300px; + display: flex; + flex-direction: column; + justify-content: space-evenly; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + padding-bottom: 8px; + border-bottom: 1px solid #d3d0d0; +} + +.category { + display: flex; + align-items: center; +} + +.categoryName { + border-bottom: 1px dashed black; +} + +.amount { + font-size: 18px; + font-weight: bold; + font-family: monospace; +} + +.cost { + color: #e74c3c; +} + +.profit { + color: #27ae60; +} + +.content { + display: flex; + flex-direction: column; + justify-content: start; +} + +.name { + margin: 0; + font-size: 16px; + font-weight: 600; + color: #333; + line-height: 1.3; +} + +.description { + margin: 0; + font-size: 14px; + color: #666; + line-height: 1.4; +} diff --git a/src/shared/operation-card/operation-card.stories.tsx b/src/shared/operation-card/operation-card.stories.tsx new file mode 100644 index 000000000..8e48b6d76 --- /dev/null +++ b/src/shared/operation-card/operation-card.stories.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { OperationCard } from './operation-card'; +import { createRandomOperation } from '../../homeworks/ts1/3_write'; + +const meta: Meta = { + title: 'Common/OperationCard', + component: OperationCard, + parameters: { + layout: 'centered', + }, + argTypes: { + id: { + control: 'text', + }, + name: { + control: 'text', + }, + desc: { + control: 'text', + }, + createdAt: { + control: 'text', + }, + amount: { + control: 'number', + }, + category: { + control: 'object', + }, + type: { + control: 'select', + options: ['Cost', 'Profit'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Cost: Story = { + args: { + id: '1', + name: 'Покупка плойки', + desc: 'Play hundreds of incredible PS5, PS4 and classic PlayStation games, and discover epic adventures, unique indies, family favorites, and everything in between.', + createdAt: '2025-02-15', + amount: 550, + category: { + id: '1', + name: 'Tech', + }, + type: 'Cost', + }, +}; + +export const Profit: Story = { + args: { + id: '2', + name: 'Продажа плойки', + desc: 'Play hundreds of incredible PS5, PS4 and classic PlayStation games, and discover epic adventures, unique indies, family favorites, and everything in between.', + createdAt: '2025-02-15', + amount: 300, + category: { + id: '2', + name: 'Tech', + }, + type: 'Profit', + }, +}; + +export const RandomOp: Story = { + render: () => { + const operation = createRandomOperation('2025-02-15'); + return ; + }, +}; diff --git a/src/shared/operation-card/operation-card.tsx b/src/shared/operation-card/operation-card.tsx new file mode 100644 index 000000000..2141ef89f --- /dev/null +++ b/src/shared/operation-card/operation-card.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Operation } from 'src/homeworks/ts1/3_write'; +import styles from './operation-card.module.css'; + +export const OperationCard: React.FC = ({ name, desc, amount, category, type }) => { + const shortDescr = (text: string, maxLength = 50) => { + return text.substring(0, maxLength) + (text.length > maxLength ? '...' : ''); + }; + + return ( +
+
+
+ {type === 'Cost' ? '-' : '+'} + {amount.toFixed()}$ +
+
+ {category.name} +
+
+
+

{name}

+ {desc &&

{shortDescr(desc)}

} +
+
+ ); +}; diff --git a/src/shared/operation-detail/operation-detail.module.css b/src/shared/operation-detail/operation-detail.module.css new file mode 100644 index 000000000..b70122be2 --- /dev/null +++ b/src/shared/operation-detail/operation-detail.module.css @@ -0,0 +1,79 @@ +.detail { + background: white; + border-radius: 6px; + padding: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border: 1px solid #e0e0e0; + width: 300px; + display: flex; + flex-direction: column; + justify-content: space-evenly; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid #d3d0d0; +} + +.category { + display: flex; + align-items: center; +} + +.categoryName { + border-bottom: 1px dashed black; +} + +.amount { + font-size: 18px; + font-weight: bold; + font-family: monospace; +} + +.cost { + color: #e74c3c; +} + +.profit { + color: #27ae60; +} + +.content { + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + justify-content: start; +} + +.name { + margin: 0; + font-size: 16px; + font-weight: 600; + color: #333; + line-height: 1.3; +} + +.description { + margin: 0; + font-size: 14px; + color: #666; + line-height: 1.4; +} + +.date { + display: flex; + flex-direction: row-reverse; + font-size: 12px; + color: #555; +} + +.actions { + display: flex; + justify-content: flex-end; + margin-top: 12px; +} diff --git a/src/shared/operation-detail/operation-detail.stories.tsx b/src/shared/operation-detail/operation-detail.stories.tsx new file mode 100644 index 000000000..c99c69400 --- /dev/null +++ b/src/shared/operation-detail/operation-detail.stories.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { OperationDetail } from './operation-detail'; +import { createRandomOperation } from '../../homeworks/ts1/3_write'; + +const meta: Meta = { + title: 'Common/OperationDetail', + component: OperationDetail, + parameters: { + layout: 'centered', + }, + argTypes: { + id: { + control: 'text', + description: 'Уникальный идентификатор операции', + }, + name: { + control: 'text', + description: 'Название операции', + }, + desc: { + control: 'text', + description: 'Описание операции', + }, + createdAt: { + control: 'text', + description: 'Дата создания', + }, + amount: { + control: 'number', + description: 'Сумма операции', + }, + category: { + control: 'object', + description: 'Категория операции', + }, + type: { + control: 'select', + options: ['Cost', 'Profit'], + description: 'Тип операции', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Cost: Story = { + args: { + id: '1', + name: 'Покупка плойки', + desc: 'Play hundreds of incredible PS5, PS4 and classic PlayStation games, and discover epic adventures, unique indies, family favorites, and everything in between.', + createdAt: '2025-02-15', + amount: 550, + category: { + id: '1', + name: 'Tech', + }, + type: 'Cost', + }, +}; +export const Profit: Story = { + args: { + id: '2', + name: 'Продажа плойки', + desc: 'Play hundreds of incredible PS5, PS4 and classic PlayStation games, and discover epic adventures, unique indies, family favorites, and everything in between.', + createdAt: '2025-02-15', + amount: 300, + category: { + id: '2', + name: 'Tech', + }, + type: 'Profit', + }, +}; + +export const RandomOp: Story = { + render: () => { + const operation = createRandomOperation('2025-02-15'); + return ; + }, +}; diff --git a/src/shared/operation-detail/operation-detail.tsx b/src/shared/operation-detail/operation-detail.tsx new file mode 100644 index 000000000..de1a5d309 --- /dev/null +++ b/src/shared/operation-detail/operation-detail.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Operation } from '../../homeworks/ts1/3_write'; +import styles from './operation-detail.module.css'; + +export const OperationDetail: React.FC = ({ name, desc, amount, category, type, createdAt }) => { + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString(); + }; + + return ( +
+
+
+ {type === 'Cost' ? '-' : '+'} + {amount.toFixed()}$ +
+
+ {category.name} +
+
+ +
+

{name}

+ {desc &&

{desc}

} +
+ {formatDate(createdAt)} +
+
+ +
+ +
+
+ ); +}; diff --git a/src/shared/product-card-detail/product-card-detail.module.css b/src/shared/product-card-detail/product-card-detail.module.css new file mode 100644 index 000000000..c6cbf3d9c --- /dev/null +++ b/src/shared/product-card-detail/product-card-detail.module.css @@ -0,0 +1,95 @@ +.card { + background: white; + border-radius: 12px; + padding: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border: 1px solid #e0e0e0; + transition: box-shadow 0.2s ease, transform 0.2s ease; + cursor: pointer; + height: 400px; + width: 300px; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.imageContainer { + width: 100%; + height: 180px; + border-radius: 8px; + overflow: hidden; + margin-bottom: 12px; + background: #f5f5f5; +} + +.image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.content { + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid #d3d0d0; +} + +.category { + display: flex; + align-items: center; +} + +.categoryName { + border-bottom: 1px dashed black; +} + +.priceContainer { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; +} + +.price { + font-size: 18px; + font-weight: bold; + font-family: 'Courier New', monospace; + color: #333; +} + +.oldPrice { + font-size: 14px; + font-weight: 500; + font-family: 'Courier New', monospace; + color: #999; + text-decoration: line-through; +} + +.name { + margin: 0 0 8px 0; + font-size: 16px; + font-weight: 600; + color: #333; + line-height: 1.3; + display: -webkit-box; + overflow: hidden; +} + +.description { + margin: 0 0 16px 0; + font-size: 14px; + color: #666; + line-height: 1.4; + flex: 1; +} + diff --git a/src/shared/product-card-detail/product-card-detail.stories.tsx b/src/shared/product-card-detail/product-card-detail.stories.tsx new file mode 100644 index 000000000..6918d6323 --- /dev/null +++ b/src/shared/product-card-detail/product-card-detail.stories.tsx @@ -0,0 +1,52 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ProductCardDetail } from './product-card-detail'; +import { createRandomProduct } from '../../homeworks/ts1/3_write'; + +const meta: Meta = { + title: 'Common/ProductCardDetail', + component: ProductCardDetail, + parameters: { + layout: 'centered', + }, + argTypes: { + name: { + control: 'text', + }, + desc: { + control: 'text', + }, + price: { + control: 'number', + }, + photo: { + control: 'text', + }, + category: { + control: 'object', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const PlayStationProduct: Story = { + args: { + id: '1', + name: 'Sony Playstation 1', + desc: 'Play hundreds of incredible PS5, PS4 and classic PlayStation games, and discover epic adventures, unique indies, family favorites, and everything in between.', + price: 5000, + photo: + 'https://sonycenter.kz/image/cache/catalog/products/old/PS719999591-Sony-PlayStation-Classic/PS719999591-Sony-PlayStation-Classic-7694-600x600.png', + createdAt: '2025-02-15', + category: { + id: '1', + name: 'Tech', + photo: '', + }, + }, +}; + +export const RandomProduct: Story = { + args: createRandomProduct('2024-01-15'), +}; diff --git a/src/shared/product-card-detail/product-card-detail.tsx b/src/shared/product-card-detail/product-card-detail.tsx new file mode 100644 index 000000000..3478e84f2 --- /dev/null +++ b/src/shared/product-card-detail/product-card-detail.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Product } from '../../homeworks/ts1/3_write'; +import { AddToCart } from '../add-to-cart/add-to-cart'; +import styles from './product-card-detail.module.css'; + +export const ProductCardDetail: React.FC = ({ name, desc, price, photo, category }) => { + return ( +
+
+ {name} +
+
+
+
+ {price.toFixed()}$ +
+
+ {category.name} +
+
+

{name}

+ {desc &&

{desc}

} + +
+
+ ); +}; diff --git a/src/shared/product-card/product-card.module.css b/src/shared/product-card/product-card.module.css new file mode 100644 index 000000000..f3fbb5e7f --- /dev/null +++ b/src/shared/product-card/product-card.module.css @@ -0,0 +1,77 @@ +.card { + background: white; + border-radius: 8px; + padding: 12px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); + border: 1px solid #e0e0e0; + transition: box-shadow 0.2s ease, transform 0.2s ease; + cursor: pointer; + height: 200px; + width: 250px; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.card:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + transform: translateY(-1px); +} + +.imageContainer { + width: 100%; + height: 80px; + border-radius: 6px; + overflow: hidden; + margin-bottom: 8px; + background: #f5f5f5; +} + +.image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.content { + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; +} + +.name { + margin: 0 0 4px 0; + font-size: 14px; + font-weight: 600; + color: #333; + line-height: 1.2; + overflow: hidden; +} + +.description { + margin: 0 0 8px 0; + font-size: 12px; + color: #666; + line-height: 1.3; + flex: 1; +} + +.footer { + display: flex; + justify-content: space-between; +} + +.priceContainer { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 2px; +} + +.price { + font-size: 16px; + font-weight: bold; + font-family: 'Courier New', monospace; + color: #333; +} diff --git a/src/shared/product-card/product-card.stories.tsx b/src/shared/product-card/product-card.stories.tsx new file mode 100644 index 000000000..97bccf133 --- /dev/null +++ b/src/shared/product-card/product-card.stories.tsx @@ -0,0 +1,51 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ProductCard } from './product-card'; +import { createRandomProduct } from '../../homeworks/ts1/3_write'; + +const meta: Meta = { + title: 'Common/ProductCard', + component: ProductCard, + parameters: { + layout: 'centered', + }, + argTypes: { + name: { + control: 'text', + }, + desc: { + control: 'text', + }, + price: { + control: 'number', + }, + oldPrice: { + control: 'number', + }, + photo: { + control: 'text', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const PlayStationProduct: Story = { + args: { + id: '1', + name: 'Sony Playstation 1', + desc: 'Play hundreds of incredible PS5, PS4 and classic PlayStation games, and discover epic adventures, unique indies, family favorites, and everything in between.', + price: 5000, + photo: + 'https://sonycenter.kz/image/cache/catalog/products/old/PS719999591-Sony-PlayStation-Classic/PS719999591-Sony-PlayStation-Classic-7694-600x600.png', + createdAt: '2025-02-15', + category: { + id: '1', + name: 'Tech', + }, + }, +}; + +export const RandomProduct: Story = { + args: createRandomProduct('2024-01-15'), +}; diff --git a/src/shared/product-card/product-card.tsx b/src/shared/product-card/product-card.tsx new file mode 100644 index 000000000..0e2b85c86 --- /dev/null +++ b/src/shared/product-card/product-card.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Product } from '../../homeworks/ts1/3_write'; +import { AddToCart } from '../add-to-cart/add-to-cart'; +import styles from './product-card.module.css'; + +export const ProductCard: React.FC = ({ name, desc, price, photo }) => { + const shortDescr = (text: string, maxLength = 40) => { + return text.substring(0, maxLength) + (text.length > maxLength ? '...' : ''); + }; + + return ( +
+
+ {name} +
+
+

{name}

+ {desc &&

{shortDescr(desc)}

} +
+
+ {price.toFixed()}$ +
+ +
+
+
+ ); +}; diff --git a/src/stories/Button.tsx b/src/stories/Button.tsx index 244ff033e..57c74e5b0 100644 --- a/src/stories/Button.tsx +++ b/src/stories/Button.tsx @@ -30,12 +30,7 @@ interface ButtonProps { export function Button({ primary = false, size = 'medium', backgroundColor, label, ...props }: ButtonProps) { const mode = primary ? s.primary : s.secondary; return ( - );