From 2e997b8a70d1552ef42500ce6e8735ddacee9559 Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Wed, 17 Jul 2024 17:47:37 +0700 Subject: [PATCH 01/12] migrate to vitest --- jest.config.js | 5 - package.json | 8 +- pnpm-lock.yaml | 713 +++++++++++++++++++++++++++++++++++++++++++--- src/setupTests.js | 17 +- vite.config.js | 5 + 5 files changed, 704 insertions(+), 44 deletions(-) delete mode 100644 jest.config.js diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index e659dd9..0000000 --- a/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - snapshotSerializers: [ - '@emotion/jest/serializer' - ] -} \ No newline at end of file diff --git a/package.json b/package.json index 2f0334a..99b661e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "scripts": { "start": "vite", "build": "vite build", - "serve": "vite preview" + "serve": "vite preview", + "test": "vitest" }, "eslintConfig": { "extends": [ @@ -42,6 +43,9 @@ "devDependencies": { "@emotion/jest": "^11.2.1", "@vitejs/plugin-react": "^4.3.1", - "vite": "^5.3.4" + "@vitest/coverage-v8": "^2.0.3", + "jsdom": "^24.1.0", + "vite": "^5.3.4", + "vitest": "^2.0.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfbeaec..7d9d731 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,9 +46,18 @@ devDependencies: '@vitejs/plugin-react': specifier: ^4.3.1 version: 4.3.1(vite@5.3.4) + '@vitest/coverage-v8': + specifier: ^2.0.3 + version: 2.0.3(vitest@2.0.3) + jsdom: + specifier: ^24.1.0 + version: 24.1.0 vite: specifier: ^5.3.4 version: 5.3.4 + vitest: + specifier: ^2.0.3 + version: 2.0.3(jsdom@24.1.0) packages: @@ -288,6 +297,10 @@ packages: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true + /@cush/relative@1.0.0: resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==} dev: false @@ -626,7 +639,11 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: false + + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true /@jest/expect-utils@29.7.0: resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} @@ -702,7 +719,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: false optional: true /@rollup/rollup-android-arm-eabi@4.18.1: @@ -1171,15 +1187,90 @@ packages: - supports-color dev: true + /@vitest/coverage-v8@2.0.3(vitest@2.0.3): + resolution: {integrity: sha512-53d+6jXFdYbasXBmsL6qaGIfcY5eBQq0sP57AjdasOcSiGNj4qxkkpDKIitUNfjxcfAfUfQ8BD0OR2fSey64+g==} + peerDependencies: + vitest: 2.0.3 + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.5 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.10 + magicast: 0.3.4 + std-env: 3.7.0 + strip-literal: 2.1.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.0.3(jsdom@24.1.0) + transitivePeerDependencies: + - supports-color + dev: true + + /@vitest/expect@2.0.3: + resolution: {integrity: sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg==} + dependencies: + '@vitest/spy': 2.0.3 + '@vitest/utils': 2.0.3 + chai: 5.1.1 + tinyrainbow: 1.2.0 + dev: true + + /@vitest/pretty-format@2.0.3: + resolution: {integrity: sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==} + dependencies: + tinyrainbow: 1.2.0 + dev: true + + /@vitest/runner@2.0.3: + resolution: {integrity: sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ==} + dependencies: + '@vitest/utils': 2.0.3 + pathe: 1.1.2 + dev: true + + /@vitest/snapshot@2.0.3: + resolution: {integrity: sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg==} + dependencies: + '@vitest/pretty-format': 2.0.3 + magic-string: 0.30.10 + pathe: 1.1.2 + dev: true + + /@vitest/spy@2.0.3: + resolution: {integrity: sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A==} + dependencies: + tinyspy: 3.0.0 + dev: true + + /@vitest/utils@2.0.3: + resolution: {integrity: sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==} + dependencies: + '@vitest/pretty-format': 2.0.3 + estree-walker: 3.0.3 + loupe: 3.1.1 + tinyrainbow: 1.2.0 + dev: true + + /agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + dependencies: + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + dev: true + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: false /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: false /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -1201,7 +1292,6 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: false /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1221,6 +1311,15 @@ packages: dequal: 2.0.3 dev: false + /assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + dev: true + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true + /babel-plugin-emotion@10.2.2: resolution: {integrity: sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==} dependencies: @@ -1252,13 +1351,11 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: false /brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: false /braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1278,6 +1375,11 @@ packages: update-browserslist-db: 1.1.0(browserslist@4.23.2) dev: true + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1287,6 +1389,17 @@ packages: resolution: {integrity: sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==} dev: true + /chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 + dev: true + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -1310,6 +1423,11 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 + /check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + dev: true + /ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -1332,6 +1450,13 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: true + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1368,16 +1493,30 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: false /css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} dev: false + /cssstyle@4.0.1: + resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + engines: {node: '>=18'} + dependencies: + rrweb-cssom: 0.6.0 + dev: true + /csstype@2.6.21: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} dev: false + /data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + dev: true + /debug@4.3.5: resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} engines: {node: '>=6.0'} @@ -1389,11 +1528,25 @@ packages: dependencies: ms: 2.1.2 + /decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dev: true + + /deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + dev: true + /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} dev: false + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: true + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1410,7 +1563,6 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: false /electron-to-chromium@1.4.829: resolution: {integrity: sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==} @@ -1418,11 +1570,14 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: false /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: false + + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1474,6 +1629,27 @@ packages: engines: {node: '>=8'} dev: false + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: true + + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + /expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1502,7 +1678,15 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: false + + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: true /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -1520,6 +1704,15 @@ packages: engines: {node: '>=6.9.0'} dev: true + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + /glob-regex@0.3.2: resolution: {integrity: sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw==} dev: false @@ -1534,7 +1727,6 @@ packages: minipass: 7.1.2 package-json-from-dist: 1.0.0 path-scurry: 1.11.1 - dev: false /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -1567,6 +1759,49 @@ packages: resolution: {integrity: sha512-YlhVQtyG9f1b7GhtzdhR0Pl+cImD1ZrKI6zYUa7QLd0zuThiL7RzZ+ANJyy7z+kmcCpNYBf5PjBa3CjiQ5PFpw==} dev: false + /html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + dependencies: + whatwg-encoding: 3.1.1 + dev: true + + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + + /http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.1 + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + dev: true + + /https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.1 + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + dev: true + + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: true + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1594,16 +1829,56 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: false /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} dev: false + /is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + dev: true + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: false + + /istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + dev: true + + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.5 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true /jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -1611,7 +1886,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: false /jest-diff@29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} @@ -1668,6 +1942,46 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + /js-tokens@9.0.0: + resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} + dev: true + + /jsdom@24.1.0: + resolution: {integrity: sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.12 + parse5: 7.1.2 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -1705,9 +2019,14 @@ packages: js-tokens: 4.0.0 dev: false + /loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + dependencies: + get-func-name: 2.0.2 + dev: true + /lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - dev: false /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1720,6 +2039,31 @@ packages: hasBin: true dev: false + /magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + dev: true + + /magicast@0.3.4: + resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==} + dependencies: + '@babel/parser': 7.24.8 + '@babel/types': 7.24.9 + source-map-js: 1.2.0 + dev: true + + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.6.3 + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + /micromatch@4.0.7: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} @@ -1728,6 +2072,23 @@ packages: picomatch: 2.3.1 dev: false + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: true + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1738,7 +2099,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: false /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1747,7 +2107,6 @@ packages: /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - dev: false /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -1769,14 +2128,31 @@ packages: resolution: {integrity: sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==} dev: true + /npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /nwsapi@2.2.12: + resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} + dev: true + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} dev: false + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + /package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} - dev: false /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -1795,10 +2171,20 @@ packages: lines-and-columns: 1.2.4 dev: false + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 + dev: true + /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: false + + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -1810,13 +2196,21 @@ packages: dependencies: lru-cache: 10.4.3 minipass: 7.1.2 - dev: false /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} dev: false + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dev: true + + /pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + dev: true + /picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -1866,6 +2260,19 @@ packages: react-is: 18.3.1 dev: false + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: true + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true + + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true + /react-dom@17.0.2(react@17.0.2): resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==} peerDependencies: @@ -1930,6 +2337,10 @@ packages: /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: true + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1969,6 +2380,25 @@ packages: '@rollup/rollup-win32-x64-msvc': 4.18.1 fsevents: 2.3.3 + /rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + dev: true + + /rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + dev: true + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: true + + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + dependencies: + xmlchars: 2.2.0 + dev: true + /scheduler@0.20.2: resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} dependencies: @@ -1981,22 +2411,29 @@ packages: hasBin: true dev: true + /semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + dev: true + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: false /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: false + + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: false /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} @@ -2024,6 +2461,14 @@ packages: escape-string-regexp: 2.0.0 dev: false + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + dev: true + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2031,7 +2476,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: false /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -2040,27 +2484,29 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: false /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: false /strip-ansi@7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: false /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} dev: false + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -2068,6 +2514,12 @@ packages: min-indent: 1.0.1 dev: false + /strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + dependencies: + js-tokens: 9.0.0 + dev: true + /styled-system@5.1.5: resolution: {integrity: sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==} dependencies: @@ -2121,6 +2573,19 @@ packages: engines: {node: '>= 0.4'} dev: false + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: true + + /test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + dev: true + /theme-ui@0.3.5(react@17.0.2): resolution: {integrity: sha512-yxooGhvkdjFDotDeIFehKo5k6NnLZ3gsLSe8EDe2aDcoWqg1mZjkjjr8EYtVCrK3mk/tYz97AT5BpEnUfamNCQ==} peerDependencies: @@ -2150,6 +2615,25 @@ packages: any-promise: 1.3.0 dev: false + /tinybench@2.8.0: + resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + dev: true + + /tinypool@1.0.0: + resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} + engines: {node: ^18.0.0 || >=20.0.0} + dev: true + + /tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@3.0.0: + resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} + engines: {node: '>=14.0.0'} + dev: true + /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -2161,6 +2645,23 @@ packages: is-number: 7.0.0 dev: false + /tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + dev: true + + /tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + dependencies: + punycode: 2.3.1 + dev: true + /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: false @@ -2182,6 +2683,11 @@ packages: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} dev: false + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + dev: true + /update-browserslist-db@1.1.0(browserslist@4.23.2): resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} hasBin: true @@ -2193,6 +2699,13 @@ packages: picocolors: 1.0.1 dev: true + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: true + /vite-jsconfig-paths@2.0.1(vite@5.3.4): resolution: {integrity: sha512-rabcTTfKs0MdAsQWcZjbIMo5fcp6jthZce7uFEPgVPgpSY+RNOwjzIJOPES6cB/GJZLSoLGfHM9kt5HNmJvp7A==} peerDependencies: @@ -2207,6 +2720,27 @@ packages: - supports-color dev: false + /vite-node@2.0.3: + resolution: {integrity: sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.5 + pathe: 1.1.2 + tinyrainbow: 1.2.0 + vite: 5.3.4 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite@5.3.4: resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2241,17 +2775,112 @@ packages: optionalDependencies: fsevents: 2.3.3 + /vitest@2.0.3(jsdom@24.1.0): + resolution: {integrity: sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.0.3 + '@vitest/ui': 2.0.3 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@ampproject/remapping': 2.3.0 + '@vitest/expect': 2.0.3 + '@vitest/pretty-format': 2.0.3 + '@vitest/runner': 2.0.3 + '@vitest/snapshot': 2.0.3 + '@vitest/spy': 2.0.3 + '@vitest/utils': 2.0.3 + chai: 5.1.1 + debug: 4.3.5 + execa: 8.0.1 + jsdom: 24.1.0 + magic-string: 0.30.10 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.8.0 + tinypool: 1.0.0 + tinyrainbow: 1.2.0 + vite: 5.3.4 + vite-node: 2.0.3 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + dependencies: + xml-name-validator: 5.0.0 + dev: true + /web-vitals@0.2.4: resolution: {integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==} dev: false + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: true + + /whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + dependencies: + iconv-lite: 0.6.3 + dev: true + + /whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + dev: true + + /whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + dev: true + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true dependencies: isexe: 2.0.0 - dev: false + + /why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} @@ -2260,7 +2889,6 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: false /wrap-ansi@8.1.0: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} @@ -2269,7 +2897,28 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: false + + /ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + dev: true + + /xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + dev: true /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} diff --git a/src/setupTests.js b/src/setupTests.js index 8f2609b..d588ca6 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -1,5 +1,12 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; +// add Vitest functions here globally +import { expect, afterEach } from 'vitest'; +import { cleanup } from '@testing-library/react'; +import matchers from '@testing-library/jest-dom/matchers'; + +// Extend Vitest's expect method with methods from react-testing-library +expect.extend(matchers); + +// Run cleanup after each test case (e.g., clearing jsdom) +afterEach(() => { + cleanup(); +}); diff --git a/vite.config.js b/vite.config.js index 46f7172..445456b 100644 --- a/vite.config.js +++ b/vite.config.js @@ -8,5 +8,10 @@ export default defineConfig(() => { outDir: 'build', }, plugins: [jsconfigPaths(), react()], + test: { + globals: true, + environment: 'jsdom', + setupFiles: './src/setupTests.js', + }, }; }); From aa8c2024ad4bf223fdf28a3ef52742553ec5e2b4 Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Wed, 17 Jul 2024 21:08:36 +0700 Subject: [PATCH 02/12] migrate to typescript --- index.html | 2 +- jsconfig.json | 6 - package.json | 9 +- pnpm-lock.yaml | 328 ++++++++---------- src/{App.jsx => App.tsx} | 0 src/components/{Hotkey.jsx => Hotkey.tsx} | 0 src/components/{Letter.jsx => Letter.tsx} | 7 +- .../{MockThemeUI.jsx => MockThemeUI.tsx} | 0 .../{Statistic.jsx => Statistic.tsx} | 0 src/components/{Timer.jsx => Timer.tsx} | 2 +- .../{TypingArea.jsx => TypingArea.tsx} | 13 +- src/components/{Word.jsx => Word.tsx} | 19 +- .../{Letter.test.jsx => Letter.test.tsx} | 10 +- .../{Word.test.jsx => Word.test.tsx} | 6 +- src/{index.jsx => index.tsx} | 0 ...{reportWebVitals.js => reportWebVitals.ts} | 0 src/{setupTests.js => setupTests.ts} | 0 src/store/config/{action.js => action.ts} | 4 +- src/store/config/{index.jsx => index.tsx} | 18 +- src/store/config/{reducer.js => reducer.ts} | 9 +- src/store/typing/{action.js => action.ts} | 4 +- src/store/typing/{index.jsx => index.tsx} | 13 +- src/store/typing/{reducer.js => reducer.ts} | 0 src/theme/{colors.js => colors.ts} | 0 src/theme/{index.js => index.ts} | 0 src/theme/{styles.js => styles.ts} | 0 src/utils/Letter.js | 14 - src/utils/Letter.ts | 20 ++ src/utils/Word.js | 26 -- src/utils/Word.ts | 35 ++ .../{Letter.test.js => Letter.test.ts} | 3 +- .../__test__/{Word.test.js => Word.test.ts} | 11 +- ...{statistics.test.js => statistics.test.ts} | 40 +-- src/utils/{constant.js => constant.ts} | 13 +- src/utils/{statistics.js => statistics.ts} | 12 +- src/utils/store.js | 33 -- src/utils/store.ts | 62 ++++ src/utils/{theme.js => theme.ts} | 2 +- tsconfig.json | 22 ++ tsconfig.node.json | 9 + vite-env.d.ts | 1 + vite.config.js => vite.config.ts | 6 +- 42 files changed, 402 insertions(+), 357 deletions(-) delete mode 100644 jsconfig.json rename src/{App.jsx => App.tsx} (100%) rename src/components/{Hotkey.jsx => Hotkey.tsx} (100%) rename src/components/{Letter.jsx => Letter.tsx} (79%) rename src/components/{MockThemeUI.jsx => MockThemeUI.tsx} (100%) rename src/components/{Statistic.jsx => Statistic.tsx} (100%) rename src/components/{Timer.jsx => Timer.tsx} (96%) rename src/components/{TypingArea.jsx => TypingArea.tsx} (95%) rename src/components/{Word.jsx => Word.tsx} (69%) rename src/components/__tests__/{Letter.test.jsx => Letter.test.tsx} (85%) rename src/components/__tests__/{Word.test.jsx => Word.test.tsx} (91%) rename src/{index.jsx => index.tsx} (100%) rename src/{reportWebVitals.js => reportWebVitals.ts} (100%) rename src/{setupTests.js => setupTests.ts} (100%) rename src/store/config/{action.js => action.ts} (82%) rename src/store/config/{index.jsx => index.tsx} (51%) rename src/store/config/{reducer.js => reducer.ts} (52%) rename src/store/typing/{action.js => action.ts} (88%) rename src/store/typing/{index.jsx => index.tsx} (67%) rename src/store/typing/{reducer.js => reducer.ts} (100%) rename src/theme/{colors.js => colors.ts} (100%) rename src/theme/{index.js => index.ts} (100%) rename src/theme/{styles.js => styles.ts} (100%) delete mode 100644 src/utils/Letter.js create mode 100644 src/utils/Letter.ts delete mode 100644 src/utils/Word.js create mode 100644 src/utils/Word.ts rename src/utils/__test__/{Letter.test.js => Letter.test.ts} (81%) rename src/utils/__test__/{Word.test.js => Word.test.ts} (83%) rename src/utils/__test__/{statistics.test.js => statistics.test.ts} (75%) rename src/utils/{constant.js => constant.ts} (63%) rename src/utils/{statistics.js => statistics.ts} (80%) delete mode 100644 src/utils/store.js create mode 100644 src/utils/store.ts rename src/utils/{theme.js => theme.ts} (96%) create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite-env.d.ts rename vite.config.js => vite.config.ts (65%) diff --git a/index.html b/index.html index 8f0dbe7..8bf1e1c 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,6 @@
- + diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index ec9aa3f..0000000 --- a/jsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": "src" - }, - "include": ["src"] -} \ No newline at end of file diff --git a/package.json b/package.json index 99b661e..a9dd9fd 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,12 @@ "react-dom": "^17.0.1", "react-hotkeys-hook": "^2.3.1", "theme-ui": "^0.3.1", - "vite-jsconfig-paths": "^2.0.1", + "vite-tsconfig-paths": "^4.3.2", "web-vitals": "^0.2.4" }, "scripts": { "start": "vite", - "build": "vite build", + "build": "tsc && vite build", "serve": "vite preview", "test": "vitest" }, @@ -42,9 +42,14 @@ }, "devDependencies": { "@emotion/jest": "^11.2.1", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/theme-ui": "^0.3.7", + "@types/theme-ui__components": "^0.3.0", "@vitejs/plugin-react": "^4.3.1", "@vitest/coverage-v8": "^2.0.3", "jsdom": "^24.1.0", + "typescript": "^5.5.3", "vite": "^5.3.4", "vitest": "^2.0.3" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d9d731..6b19d68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,9 +32,9 @@ dependencies: theme-ui: specifier: ^0.3.1 version: 0.3.5(react@17.0.2) - vite-jsconfig-paths: - specifier: ^2.0.1 - version: 2.0.1(vite@5.3.4) + vite-tsconfig-paths: + specifier: ^4.3.2 + version: 4.3.2(typescript@5.5.3)(vite@5.3.4) web-vitals: specifier: ^0.2.4 version: 0.2.4 @@ -43,6 +43,18 @@ devDependencies: '@emotion/jest': specifier: ^11.2.1 version: 11.11.0 + '@types/react': + specifier: ^18.3.3 + version: 18.3.3 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 + '@types/theme-ui': + specifier: ^0.3.7 + version: 0.3.7(react@17.0.2) + '@types/theme-ui__components': + specifier: ^0.3.0 + version: 0.3.0(react@17.0.2) '@vitejs/plugin-react': specifier: ^4.3.1 version: 4.3.1(vite@5.3.4) @@ -52,6 +64,9 @@ devDependencies: jsdom: specifier: ^24.1.0 version: 24.1.0 + typescript: + specifier: ^5.5.3 + version: 5.5.3 vite: specifier: ^5.3.4 version: 5.3.4 @@ -301,10 +316,6 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@cush/relative@1.0.0: - resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==} - dev: false - /@emotion/cache@10.0.29: resolution: {integrity: sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==} dependencies: @@ -312,7 +323,6 @@ packages: '@emotion/stylis': 0.8.5 '@emotion/utils': 0.11.3 '@emotion/weak-memoize': 0.2.5 - dev: false /@emotion/core@10.3.1(react@17.0.2): resolution: {integrity: sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==} @@ -328,7 +338,6 @@ packages: react: 17.0.2 transitivePeerDependencies: - supports-color - dev: false /@emotion/css-prettifier@1.1.3: resolution: {integrity: sha512-KNv23+VQ+pcw3ebd1vSEl11CQ6SKAG5EQkrinjVGsfw3ZTWe6/tpWQrsvFLqCtU2LRiLPi04KgFCE4A9+crfpQ==} @@ -345,17 +354,14 @@ packages: babel-plugin-emotion: 10.2.2 transitivePeerDependencies: - supports-color - dev: false /@emotion/hash@0.8.0: resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} - dev: false /@emotion/is-prop-valid@0.8.8: resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} dependencies: '@emotion/memoize': 0.7.4 - dev: false /@emotion/jest@11.11.0: resolution: {integrity: sha512-XZlnmdUZ32YjQnInsCFk/plKpkV/NXN1Ab4YoNvXN887MeR3Hr5ZsTyoblIW8AWwdfQiZHHphaPMb56lk6Ofdw==} @@ -377,11 +383,9 @@ packages: /@emotion/memoize@0.7.4: resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} - dev: false /@emotion/memoize@0.7.5: resolution: {integrity: sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==} - dev: false /@emotion/memoize@0.8.1: resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} @@ -395,11 +399,9 @@ packages: '@emotion/unitless': 0.7.5 '@emotion/utils': 0.11.3 csstype: 2.6.21 - dev: false /@emotion/sheet@0.9.4: resolution: {integrity: sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==} - dev: false /@emotion/styled-base@10.3.0(@emotion/core@10.3.1)(react@17.0.2): resolution: {integrity: sha512-PBRqsVKR7QRNkmfH78hTSSwHWcwDpecH9W6heujWAcyp2wdz/64PP73s7fWS1dIPm8/Exc8JAzYS8dEWXjv60w==} @@ -413,7 +415,6 @@ packages: '@emotion/serialize': 0.11.16 '@emotion/utils': 0.11.3 react: 17.0.2 - dev: false /@emotion/styled@10.3.0(@emotion/core@10.3.1)(react@17.0.2): resolution: {integrity: sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==} @@ -427,23 +428,18 @@ packages: react: 17.0.2 transitivePeerDependencies: - supports-color - dev: false /@emotion/stylis@0.8.5: resolution: {integrity: sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==} - dev: false /@emotion/unitless@0.7.5: resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} - dev: false /@emotion/utils@0.11.3: resolution: {integrity: sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==} - dev: false /@emotion/weak-memoize@0.2.5: resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==} - dev: false /@esbuild/aix-ppc64@0.21.5: resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} @@ -639,6 +635,7 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true /@istanbuljs/schema@0.1.3: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} @@ -719,6 +716,7 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true + dev: true optional: true /@rollup/rollup-android-arm-eabi@4.18.1: @@ -841,59 +839,49 @@ packages: resolution: {integrity: sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/border@5.1.5: resolution: {integrity: sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/color@5.1.2: resolution: {integrity: sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/core@5.1.2: resolution: {integrity: sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==} dependencies: object-assign: 4.1.1 - dev: false /@styled-system/css@5.1.5: resolution: {integrity: sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==} - dev: false /@styled-system/flexbox@5.1.2: resolution: {integrity: sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/grid@5.1.2: resolution: {integrity: sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/layout@5.1.2: resolution: {integrity: sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/position@5.1.2: resolution: {integrity: sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/shadow@5.1.2: resolution: {integrity: sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/should-forward-prop@5.1.5: resolution: {integrity: sha512-+rPRomgCGYnUIaFabDoOgpSDc4UUJ1KsmlnzcEp0tu5lFrBQKgZclSo18Z1URhaZm7a6agGtS5Xif7tuC2s52Q==} @@ -901,26 +889,22 @@ packages: '@emotion/is-prop-valid': 0.8.8 '@emotion/memoize': 0.7.5 styled-system: 5.1.5 - dev: false /@styled-system/space@5.1.2: resolution: {integrity: sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/typography@5.1.2: resolution: {integrity: sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==} dependencies: '@styled-system/core': 5.1.2 - dev: false /@styled-system/variant@5.1.5: resolution: {integrity: sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==} dependencies: '@styled-system/core': 5.1.2 '@styled-system/css': 5.1.5 - dev: false /@testing-library/dom@10.3.2: resolution: {integrity: sha512-0bxIdP9mmPiOJ6wHLj8bdJRq+51oddObeCGdEf6PNEhYd93ZYAN+lPRnEOVFtheVwDM7+p+tza3LAQgp0PTudg==} @@ -1016,7 +1000,6 @@ packages: react: 17.0.2 transitivePeerDependencies: - supports-color - dev: false /@theme-ui/core@0.3.5(react@17.0.2): resolution: {integrity: sha512-80gbG4BW0ZQgZ8TWSG7vY72uXDxmkI/GttjpJee7AJlWVrPh7RCD2E3cuFPjqXzt7o4BJ9lZSHmTXcLzixNtRw==} @@ -1033,7 +1016,6 @@ packages: /@theme-ui/css@0.3.5: resolution: {integrity: sha512-XqsyXmifbnHOui1flSq4V7Lb3U+06Dbn2Q/leyr/cRd6Xgc0naiztdmD0MbXNvxgU51a2Ur9hyP4PnO5wE0yRg==} - dev: false /@theme-ui/mdx@0.3.5(@theme-ui/core@0.3.5)(@theme-ui/css@0.3.5)(react@17.0.2): resolution: {integrity: sha512-KMf5kkEcItQ3qIj7dston/kBOZc82ST2R0pUcyk/u8ZclX4ingRtZkMxm2zpmxybzdSUY3DIKf2MTK9CxUSpOQ==} @@ -1131,10 +1113,6 @@ packages: pretty-format: 29.7.0 dev: false - /@types/json5@0.0.29: - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - dev: false - /@types/node@20.14.11: resolution: {integrity: sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==} dependencies: @@ -1143,18 +1121,83 @@ packages: /@types/parse-json@4.0.2: resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - dev: false + + /@types/prop-types@15.7.12: + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + dev: true + + /@types/react-dom@18.3.0: + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + dependencies: + '@types/react': 18.3.3 + dev: true + + /@types/react@18.3.3: + resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + dev: true /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} dev: false + /@types/styled-system@5.1.22: + resolution: {integrity: sha512-NbRp37zWcrf/+Qf2NumdyZfhSx1dzJ50zgfKvnezYJx1HTRUMVYY8jtWvK1eoIAa6F5sXwHLhE8oXNu15ThBAA==} + dependencies: + csstype: 3.1.3 + dev: true + + /@types/styled-system__css@5.0.21: + resolution: {integrity: sha512-8S1lPbUbrE8U/2btqjh9X6pK9//kQdbQDe9z3vQl4SWtxtqoAVnrFZE6Xs+IHM7NMZ1uC68XrF9nLdzRHm3VyA==} + dependencies: + csstype: 3.1.3 + dev: true + /@types/testing-library__jest-dom@5.14.9: resolution: {integrity: sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==} dependencies: '@types/jest': 29.5.12 dev: false + /@types/theme-ui@0.3.7(react@17.0.2): + resolution: {integrity: sha512-4hzDlDhlFYmOdXBLZTbO4N2hWfuGo1N77AcIMaSyDGEyFbdZSpelMLTkEtNzYT8yQWIl3x0WITiBzjqkfc6dUg==} + dependencies: + '@emotion/serialize': 0.11.16 + '@types/react': 18.3.3 + '@types/styled-system': 5.1.22 + '@types/styled-system__css': 5.0.21 + '@types/theme-ui__components': 0.6.0(react@17.0.2) + csstype: 3.1.3 + transitivePeerDependencies: + - react + - supports-color + dev: true + + /@types/theme-ui__components@0.3.0(react@17.0.2): + resolution: {integrity: sha512-nFQ3rNWgn1Pmhtv/z8s/uPI8f/bkOT0iS619PIdpsaQ+keloYUUbCKRogjPunnDcI/Xsr56wiC6bpANWJQIRAQ==} + dependencies: + '@emotion/core': 10.3.1(react@17.0.2) + '@emotion/styled': 10.3.0(@emotion/core@10.3.1)(react@17.0.2) + '@types/react': 18.3.3 + '@types/styled-system': 5.1.22 + '@types/theme-ui': 0.3.7(react@17.0.2) + transitivePeerDependencies: + - react + - supports-color + dev: true + + /@types/theme-ui__components@0.6.0(react@17.0.2): + resolution: {integrity: sha512-LYRJBgaHQfC7VPpj/gaEFk7gJB725RbP0UcI1ChrzoXlMCSGA3L+oAkLneamdm1tlK827wqu2aSHM9VDyqEXVQ==} + deprecated: This is a stub types definition. @theme-ui/components provides its own type definitions, so you do not need this installed. + dependencies: + '@theme-ui/components': 0.3.5(react@17.0.2) + transitivePeerDependencies: + - react + - supports-color + dev: true + /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} dev: false @@ -1271,6 +1314,7 @@ packages: /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} + dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -1292,10 +1336,7 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - - /any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: false + dev: true /aria-query@4.2.2: resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==} @@ -1335,7 +1376,6 @@ packages: source-map: 0.5.7 transitivePeerDependencies: - supports-color - dev: false /babel-plugin-macros@2.8.0: resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==} @@ -1343,19 +1383,19 @@ packages: '@babel/runtime': 7.24.8 cosmiconfig: 6.0.0 resolve: 1.22.8 - dev: false /babel-plugin-syntax-jsx@6.18.0: resolution: {integrity: sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==} - dev: false /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true /brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 + dev: true /braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1383,7 +1423,6 @@ packages: /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - dev: false /caniuse-lite@1.0.30001642: resolution: {integrity: sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==} @@ -1457,14 +1496,8 @@ packages: delayed-stream: 1.0.0 dev: true - /commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - dev: false - /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - dev: false /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1484,7 +1517,6 @@ packages: parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 - dev: false /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} @@ -1493,6 +1525,7 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + dev: true /css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -1507,7 +1540,10 @@ packages: /csstype@2.6.21: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} - dev: false + + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true /data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} @@ -1563,6 +1599,7 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true /electron-to-chromium@1.4.829: resolution: {integrity: sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==} @@ -1570,9 +1607,11 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} @@ -1583,7 +1622,6 @@ packages: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 - dev: false /esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} @@ -1670,7 +1708,6 @@ packages: /find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - dev: false /foreground-child@3.2.1: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} @@ -1678,6 +1715,7 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 + dev: true /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} @@ -1697,7 +1735,6 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: false /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -1713,10 +1750,6 @@ packages: engines: {node: '>=16'} dev: true - /glob-regex@0.3.2: - resolution: {integrity: sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw==} - dev: false - /glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -1727,6 +1760,7 @@ packages: minipass: 7.1.2 package-json-from-dist: 1.0.0 path-scurry: 1.11.1 + dev: true /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -1753,7 +1787,6 @@ packages: engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - dev: false /hotkeys-js@3.8.1: resolution: {integrity: sha512-YlhVQtyG9f1b7GhtzdhR0Pl+cImD1ZrKI6zYUa7QLd0zuThiL7RzZ+ANJyy7z+kmcCpNYBf5PjBa3CjiQ5PFpw==} @@ -1808,7 +1841,6 @@ packages: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: false /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} @@ -1817,18 +1849,17 @@ packages: /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: false /is-core-module@2.14.0: resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 - dev: false /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + dev: true /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} @@ -1846,6 +1877,7 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true /istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} @@ -1886,6 +1918,7 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: true /jest-diff@29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} @@ -1989,14 +2022,6 @@ packages: /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: false - - /json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - dependencies: - minimist: 1.2.8 - dev: false /json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} @@ -2006,7 +2031,6 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: false /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -2017,7 +2041,6 @@ packages: hasBin: true dependencies: js-tokens: 4.0.0 - dev: false /loupe@3.1.1: resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} @@ -2027,6 +2050,7 @@ packages: /lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + dev: true /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -2099,26 +2123,16 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: false + dev: true /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + dev: true /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - /mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - dev: false - /nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2142,7 +2156,6 @@ packages: /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: false /onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} @@ -2153,13 +2166,13 @@ packages: /package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + dev: true /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} dependencies: callsites: 3.1.0 - dev: false /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} @@ -2169,7 +2182,6 @@ packages: error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - dev: false /parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} @@ -2180,6 +2192,7 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + dev: true /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} @@ -2188,7 +2201,6 @@ packages: /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: false /path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} @@ -2196,11 +2208,11 @@ packages: dependencies: lru-cache: 10.4.3 minipass: 7.1.2 + dev: true /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - dev: false /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -2219,11 +2231,6 @@ packages: engines: {node: '>=8.6'} dev: false - /pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - dev: false - /postcss@8.4.39: resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} engines: {node: ^10 || ^12 || >=14} @@ -2314,17 +2321,6 @@ packages: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - dev: false - - /recrawl-sync@2.2.3: - resolution: {integrity: sha512-vSaTR9t+cpxlskkdUFrsEpnf67kSmPk66yAGT1fZPrDudxQjoMzPgQhSMImQ0pAw5k0NPirefQfhopSjhdUtpQ==} - dependencies: - '@cush/relative': 1.0.0 - glob-regex: 0.3.2 - slash: 3.0.0 - sucrase: 3.35.0 - tslib: 1.14.1 - dev: false /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} @@ -2344,7 +2340,6 @@ packages: /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - dev: false /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} @@ -2353,7 +2348,6 @@ packages: is-core-module: 2.14.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: false /rollup@4.18.1: resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==} @@ -2422,10 +2416,12 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 + dev: true /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + dev: true /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -2434,6 +2430,7 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + dev: true /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} @@ -2447,7 +2444,6 @@ packages: /source-map@0.5.7: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} - dev: false /specificity@0.4.1: resolution: {integrity: sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==} @@ -2476,6 +2472,7 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + dev: true /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -2484,23 +2481,21 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 + dev: true /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 + dev: true /strip-ansi@7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: false + dev: true /strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} @@ -2536,26 +2531,11 @@ packages: '@styled-system/typography': 5.1.2 '@styled-system/variant': 5.1.5 object-assign: 4.1.1 - dev: false /stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} dev: true - /sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - commander: 4.1.1 - glob: 10.4.5 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - dev: false - /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -2571,7 +2551,6 @@ packages: /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - dev: false /symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -2602,19 +2581,6 @@ packages: - supports-color dev: false - /thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - dependencies: - thenify: 3.3.1 - dev: false - - /thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - dependencies: - any-promise: 1.3.0 - dev: false - /tinybench@2.8.0: resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} dev: true @@ -2662,22 +2628,23 @@ packages: punycode: 2.3.1 dev: true - /ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: false - - /tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + /tsconfck@3.1.1(typescript@5.5.3): + resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 + typescript: 5.5.3 dev: false - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: false + /typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + engines: {node: '>=14.17'} + hasBin: true /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -2706,20 +2673,6 @@ packages: requires-port: 1.0.0 dev: true - /vite-jsconfig-paths@2.0.1(vite@5.3.4): - resolution: {integrity: sha512-rabcTTfKs0MdAsQWcZjbIMo5fcp6jthZce7uFEPgVPgpSY+RNOwjzIJOPES6cB/GJZLSoLGfHM9kt5HNmJvp7A==} - peerDependencies: - vite: '>2.0.0-0' - dependencies: - debug: 4.3.5 - globrex: 0.1.2 - recrawl-sync: 2.2.3 - tsconfig-paths: 3.15.0 - vite: 5.3.4 - transitivePeerDependencies: - - supports-color - dev: false - /vite-node@2.0.3: resolution: {integrity: sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2741,6 +2694,23 @@ packages: - terser dev: true + /vite-tsconfig-paths@4.3.2(typescript@5.5.3)(vite@5.3.4): + resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + dependencies: + debug: 4.3.5 + globrex: 0.1.2 + tsconfck: 3.1.1(typescript@5.5.3) + vite: 5.3.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: false + /vite@5.3.4: resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2872,6 +2842,7 @@ packages: hasBin: true dependencies: isexe: 2.0.0 + dev: true /why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} @@ -2889,6 +2860,7 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + dev: true /wrap-ansi@8.1.0: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} @@ -2897,6 +2869,7 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 + dev: true /ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} @@ -2927,4 +2900,3 @@ packages: /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - dev: false diff --git a/src/App.jsx b/src/App.tsx similarity index 100% rename from src/App.jsx rename to src/App.tsx diff --git a/src/components/Hotkey.jsx b/src/components/Hotkey.tsx similarity index 100% rename from src/components/Hotkey.jsx rename to src/components/Hotkey.tsx diff --git a/src/components/Letter.jsx b/src/components/Letter.tsx similarity index 79% rename from src/components/Letter.jsx rename to src/components/Letter.tsx index 0a8c468..d478041 100644 --- a/src/components/Letter.jsx +++ b/src/components/Letter.tsx @@ -1,8 +1,9 @@ import { memo } from 'react' import { Text } from 'theme-ui' +import Letter from 'utils/Letter' -const Letter = memo( - ({ letter, cursor }) => ( +const LetterComponent = memo( + ({ letter, cursor }: { letter: Letter, cursor?: boolean }) => ( { const { typing, dispatch } = useTypingStore() const [timerCount, setTimerCount] = useState(duration) - const timerRef = useRef() + const timerRef = useRef() useEffect(() => { if (typing.typingStatus === 'pending') { diff --git a/src/components/TypingArea.jsx b/src/components/TypingArea.tsx similarity index 95% rename from src/components/TypingArea.jsx rename to src/components/TypingArea.tsx index b5dd82c..a2aa5b0 100644 --- a/src/components/TypingArea.jsx +++ b/src/components/TypingArea.tsx @@ -1,6 +1,6 @@ import React, { memo, useCallback, useEffect, useRef, useState } from 'react' import { Box, Flex, Input } from 'theme-ui' -import Word from './Word' +import WordComponent from './Word' import useTypingStore from '../store/typing' import actionType from '../store/typing/action' import { useHotkeys } from 'react-hotkeys-hook' @@ -20,7 +20,7 @@ const DISABLED_CTRL = ['a', 'c', 'v'] const TypingArea = memo(() => { const { typing, dispatch } = useTypingStore() - const inputRef = useRef() + const inputRef = useRef() const [blur, setBlur] = useState(false) useHotkeys('shift+Enter', () => { @@ -32,7 +32,10 @@ const TypingArea = memo(() => { keyup: true, }) - const focusInput = useCallback(() => { + const focusInput = useCallback(() => { + if(!inputRef.current) + return + return inputRef.current.focus() }, []) @@ -145,8 +148,8 @@ const TypingArea = memo(() => { disabled={typing.typingStatus === 'done'} /> {typing.wordSequence.map((w, i) => ( - { +const WordComponent = memo( + ({ word, cursorIndex }: { word: Word, cursorIndex?: number }) => { return ( word.show && ( @@ -21,14 +22,12 @@ const Word = memo( })} > {word.letterSequence.map((l, i) => ( - + ))} - = word.originalWord.length} - letter={{ - original: ' ', - }} + letter={new Letter(' ')} /> ) @@ -40,4 +39,4 @@ const Word = memo( prevProps.word.isTyped === nextProps.word.isTyped ) -export default Word +export default WordComponent diff --git a/src/components/__tests__/Letter.test.jsx b/src/components/__tests__/Letter.test.tsx similarity index 85% rename from src/components/__tests__/Letter.test.jsx rename to src/components/__tests__/Letter.test.tsx index ee8dec7..1295ea5 100644 --- a/src/components/__tests__/Letter.test.jsx +++ b/src/components/__tests__/Letter.test.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import { expect, test } from 'vitest'; import { render, screen } from '@testing-library/react' import Letter from 'utils/Letter' import LetterComponent from 'components/Letter' @@ -7,18 +7,18 @@ import MockThemeUI from 'components/MockThemeUI' import { matchers } from '@emotion/jest' // Add the custom matchers provided by '@emotion/jest' -expect.extend(matchers) +expect.extend(matchers as any) test('Letter component render correct letter', () => { let letter = new Letter('a', 'b') render() - expect(screen.getByTestId('letter')).toHaveTextContent('b') + expect(screen.getByTestId('letter').textContent).toBe('b') }) test('Letter component render correct letter - 2', () => { const letter = new Letter('a') render() - expect(screen.getByTestId('letter')).toHaveTextContent('a') + expect(screen.getByTestId('letter').textContent).toBe('a') }) test('Letter component should render caret', () => { @@ -43,4 +43,4 @@ test('Letter component should not render caret', () => { expect(screen.getByTestId('letter')).not.toHaveStyleRule('content','"|"',{ target: ':before' }) -}) \ No newline at end of file +}) diff --git a/src/components/__tests__/Word.test.jsx b/src/components/__tests__/Word.test.tsx similarity index 91% rename from src/components/__tests__/Word.test.jsx rename to src/components/__tests__/Word.test.tsx index 94a4663..3412a1e 100644 --- a/src/components/__tests__/Word.test.jsx +++ b/src/components/__tests__/Word.test.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import { expect, test } from 'vitest'; import { render, screen } from '@testing-library/react' import Word from 'utils/Word' import WordComponent from 'components/Word' @@ -7,7 +7,7 @@ import MockThemeUI from 'components/MockThemeUI' import { matchers } from '@emotion/jest' // Add the custom matchers provided by '@emotion/jest' -expect.extend(matchers) +expect.extend(matchers as any) test('Word component null if word.show is false', () => { @@ -25,7 +25,7 @@ test('Word component render correct word', () => { ) - expect(screen.getByTestId('word')).toHaveTextContent('javascript') + expect(screen.getByTestId('word').textContent).toBe('javascript') }) test('Correctly typed word is not strike through', () => { diff --git a/src/index.jsx b/src/index.tsx similarity index 100% rename from src/index.jsx rename to src/index.tsx diff --git a/src/reportWebVitals.js b/src/reportWebVitals.ts similarity index 100% rename from src/reportWebVitals.js rename to src/reportWebVitals.ts diff --git a/src/setupTests.js b/src/setupTests.ts similarity index 100% rename from src/setupTests.js rename to src/setupTests.ts diff --git a/src/store/config/action.js b/src/store/config/action.ts similarity index 82% rename from src/store/config/action.js rename to src/store/config/action.ts index ae1736b..27a37a5 100644 --- a/src/store/config/action.js +++ b/src/store/config/action.ts @@ -8,6 +8,8 @@ const actionType = { CHANGE_MODE, CHANGE_DURATION, CHANGE_THEME, -} +} as const + +export type ActionType = keyof typeof actionType; export default actionType diff --git a/src/store/config/index.jsx b/src/store/config/index.tsx similarity index 51% rename from src/store/config/index.jsx rename to src/store/config/index.tsx index 454ee10..397d641 100644 --- a/src/store/config/index.jsx +++ b/src/store/config/index.tsx @@ -1,15 +1,22 @@ -import { createContext, useContext, useReducer } from 'react' -import configReducer from './reducer' -import { createConfigStore } from '../../utils/store' +import { createContext, Dispatch, SetStateAction, useContext, useReducer } from 'react' +import configReducer, { DispatchParam } from './reducer' +import { Config, createConfigStore } from '../../utils/store' import { useColorMode } from 'theme-ui' +import { Theme } from 'utils/constant' const initialStore = createConfigStore({}) -export const ConfigStoreContext = createContext() +interface ConfigContext { + config: Config; + dispatch: Dispatch; + setTheme: Dispatch>; +} + +export const ConfigStoreContext = createContext(null) export const ConfigStoreProvider = ({ children }) => { const [config, dispatch] = useReducer(configReducer, initialStore) - const [theme, setTheme] = useColorMode() + const [theme, setTheme] = useColorMode() return ( { } const useConfigStore = () => useContext(ConfigStoreContext) + export default useConfigStore diff --git a/src/store/config/reducer.js b/src/store/config/reducer.ts similarity index 52% rename from src/store/config/reducer.js rename to src/store/config/reducer.ts index 78801c6..5738298 100644 --- a/src/store/config/reducer.js +++ b/src/store/config/reducer.ts @@ -1,6 +1,9 @@ -import actionType from './action' +import { Config } from 'utils/store' +import actionType, { ActionType } from './action' -function configReducer(state, { type, payload }) { +export type DispatchParam = { type: ActionType, payload: Partial } + +function configReducer(state: Config, { type, payload }: DispatchParam): Config { switch (type) { case actionType.CHANGE_LANGUAGE: return { @@ -13,7 +16,7 @@ function configReducer(state, { type, payload }) { duration: payload.duration, } default: - break + return state } } diff --git a/src/store/typing/action.js b/src/store/typing/action.ts similarity index 88% rename from src/store/typing/action.js rename to src/store/typing/action.ts index ffd0f01..0b8cfa3 100644 --- a/src/store/typing/action.js +++ b/src/store/typing/action.ts @@ -12,6 +12,8 @@ const action = { REFRESH_TYPING_STORE, START_TYPING, DONE_TYPING, -} +} as const; + +export type ActionType = keyof typeof action; export default action diff --git a/src/store/typing/index.jsx b/src/store/typing/index.tsx similarity index 67% rename from src/store/typing/index.jsx rename to src/store/typing/index.tsx index d8654dc..cce96b8 100644 --- a/src/store/typing/index.jsx +++ b/src/store/typing/index.tsx @@ -1,11 +1,16 @@ -import { createContext, useContext, useEffect, useReducer } from 'react' +import { createContext, Dispatch, useContext, useEffect, useReducer } from 'react' import typingReducer from './reducer' -import { createTypingStore } from '../../utils/store' -import { INITIALIZE_TYPING_STORE } from './action' +import { createTypingStore, Typing } from '../../utils/store' +import { ActionType, INITIALIZE_TYPING_STORE } from './action' const initialStore = createTypingStore({}) -export const TypingStoreContext = createContext() +interface TypingContext { + typing: Typing; + dispatch: Dispatch<{ type: ActionType, payload?: Partial }>; +} + +export const TypingStoreContext = createContext(null) export const TypingStoreProvider = ({ children, lang }) => { const [typing, dispatch] = useReducer(typingReducer, initialStore) diff --git a/src/store/typing/reducer.js b/src/store/typing/reducer.ts similarity index 100% rename from src/store/typing/reducer.js rename to src/store/typing/reducer.ts diff --git a/src/theme/colors.js b/src/theme/colors.ts similarity index 100% rename from src/theme/colors.js rename to src/theme/colors.ts diff --git a/src/theme/index.js b/src/theme/index.ts similarity index 100% rename from src/theme/index.js rename to src/theme/index.ts diff --git a/src/theme/styles.js b/src/theme/styles.ts similarity index 100% rename from src/theme/styles.js rename to src/theme/styles.ts diff --git a/src/utils/Letter.js b/src/utils/Letter.js deleted file mode 100644 index 17e2fad..0000000 --- a/src/utils/Letter.js +++ /dev/null @@ -1,14 +0,0 @@ -export function getStatus({ original, typed } = {}) { - if (original && !typed) return 'untyped' - if (original && typed) return typed === original ? 'correct' : 'incorrect' - if (!original && typed) return 'extra' - return null -} - -function Letter(original, typed = null) { - this.original = original - this.typed = typed - this.status = getStatus({ original, typed }) -} - -export default Letter diff --git a/src/utils/Letter.ts b/src/utils/Letter.ts new file mode 100644 index 0000000..455d54d --- /dev/null +++ b/src/utils/Letter.ts @@ -0,0 +1,20 @@ +export function getStatus({ original, typed }: { original?: string; typed?: string | null }) { + if (original && !typed) return 'untyped' + if (original && typed) return typed === original ? 'correct' : 'incorrect' + if (!original && typed) return 'extra' + return null +} + +class Letter { + original: string; + typed: string | null; + status: string; + + constructor(original: string, typed: string | null = null) { + this.original = original; + this.typed = typed; + this.status = getStatus({ original, typed }); + } +} + +export default Letter diff --git a/src/utils/Word.js b/src/utils/Word.js deleted file mode 100644 index 824654c..0000000 --- a/src/utils/Word.js +++ /dev/null @@ -1,26 +0,0 @@ -import { createRef } from 'react' -import { sampleSize } from 'lodash' -import Letter from './Letter' - -function Word(originalWord) { - this.key = originalWord + Math.round(Math.random() * 999999) - this.originalWord = originalWord - this.elRef = createRef() - this.show = true - this.letterSequence = originalWord.split('').map((c) => new Letter(c)) - this.isTyped = false -} - -export function isCorrectlyTyped(word) { - return word.letterSequence - .map((letter) => letter.status) - .every((status) => status === 'correct') -} - -export function createWordSequence(arrOfString, n = undefined) { - if (n) arrOfString = sampleSize(arrOfString, n) - - return arrOfString.map((s, i) => new Word(s)) -} - -export default Word diff --git a/src/utils/Word.ts b/src/utils/Word.ts new file mode 100644 index 0000000..8a50d16 --- /dev/null +++ b/src/utils/Word.ts @@ -0,0 +1,35 @@ +import { createRef, RefObject } from 'react' +import { sampleSize } from 'lodash' +import Letter from './Letter' + +class Word { + key: string; + originalWord: string; + elRef: RefObject; + show: boolean; + letterSequence: Letter[]; + isTyped: boolean; + + constructor(originalWord: string) { + this.key = originalWord + Math.round(Math.random() * 999999).toString(); + this.originalWord = originalWord; + this.elRef = createRef(); + this.show = true; + this.letterSequence = originalWord.split('').map(c => new Letter(c)); + this.isTyped = false; + } +} + +export function isCorrectlyTyped(word: Word) { + return word.letterSequence + .map((letter) => letter.status) + .every((status) => status === 'correct') +} + +export function createWordSequence(arrOfString: string[], n = undefined) { + if (n) arrOfString = sampleSize(arrOfString, n) + + return arrOfString.map((s, i) => new Word(s)) +} + +export default Word diff --git a/src/utils/__test__/Letter.test.js b/src/utils/__test__/Letter.test.ts similarity index 81% rename from src/utils/__test__/Letter.test.js rename to src/utils/__test__/Letter.test.ts index 1a2ef07..d67d3f0 100644 --- a/src/utils/__test__/Letter.test.js +++ b/src/utils/__test__/Letter.test.ts @@ -1,3 +1,4 @@ +import { describe, expect } from 'vitest'; import { getStatus } from 'utils/Letter' describe('getStatus function', () => { @@ -6,7 +7,5 @@ describe('getStatus function', () => { expect(getStatus({ original: 'a', typed: 'a' })).toEqual('correct') expect(getStatus({ original: 'a' })).toEqual('untyped') expect(getStatus({ typed: 'b' })).toEqual('extra') - expect(getStatus()).toEqual(null) - expect(getStatus('not object')).toEqual(null) }) }) diff --git a/src/utils/__test__/Word.test.js b/src/utils/__test__/Word.test.ts similarity index 83% rename from src/utils/__test__/Word.test.js rename to src/utils/__test__/Word.test.ts index f1ae1ef..db2efb1 100644 --- a/src/utils/__test__/Word.test.js +++ b/src/utils/__test__/Word.test.ts @@ -1,3 +1,4 @@ +import { describe, expect } from 'vitest'; import Letter from 'utils/Letter' import Word, { isCorrectlyTyped, createWordSequence } from 'utils/Word' @@ -24,14 +25,14 @@ describe('isCorrectlyTyped function', () => { describe('createWordSequence function', () => { test('should return correct length', () => { const wordArr = ` - Lorem ipsum dolor sit, amet consectetur adipisicing elit. + Lorem ipsum dolor sit, amet consectetur adipisicing elit. Possimus magnam iste ratione ipsam laborum consequatur, doloribus - labore maiores aperiam impedit. - Blanditiis, minus tenetur repellat - accusantium provident incidunt + labore maiores aperiam impedit. + Blanditiis, minus tenetur repellat + accusantium provident incidunt reiciendis assumenda illo? `.split(/[^a-zA-Z]+/) - + expect(createWordSequence(wordArr,10).length).toEqual(10) expect(createWordSequence(wordArr).length).toEqual(wordArr.length) }) diff --git a/src/utils/__test__/statistics.test.js b/src/utils/__test__/statistics.test.ts similarity index 75% rename from src/utils/__test__/statistics.test.js rename to src/utils/__test__/statistics.test.ts index 0b8c0f1..f43253e 100644 --- a/src/utils/__test__/statistics.test.js +++ b/src/utils/__test__/statistics.test.ts @@ -1,3 +1,4 @@ +import { describe, expect } from 'vitest'; import Letter from 'utils/Letter' import { getCorrectWordSequence, @@ -19,21 +20,8 @@ describe('getCorrectWordSequence function', () => { ] expect(Array.isArray(getCorrectWordSequence(wordSequence))).toEqual(true) - expect(Array.isArray(getCorrectWordSequence())).toEqual(true) expect(Array.isArray(getCorrectWordSequence(null))).toEqual(true) - expect(Array.isArray(getCorrectWordSequence('string'))).toEqual(true) - expect(Array.isArray(getCorrectWordSequence(1033))).toEqual(true) - expect(Array.isArray(getCorrectWordSequence(NaN))).toEqual(true) expect(Array.isArray(getCorrectWordSequence([]))).toEqual(true) - expect(Array.isArray(getCorrectWordSequence({}))).toEqual(true) - expect( - Array.isArray( - getCorrectWordSequence({ - property: 'value' - }) - ) - ).toEqual(true) - expect(Array.isArray(getCorrectWordSequence([[[]]]))).toEqual(true) }) test('should return correct output and length', () => { @@ -78,21 +66,8 @@ describe('getIncorrectWordSequence function', () => { ] expect(Array.isArray(getIncorrectWordSequence(wordSequence))).toEqual(true) - expect(Array.isArray(getIncorrectWordSequence())).toEqual(true) expect(Array.isArray(getIncorrectWordSequence(null))).toEqual(true) - expect(Array.isArray(getIncorrectWordSequence('string'))).toEqual(true) - expect(Array.isArray(getIncorrectWordSequence(1033))).toEqual(true) - expect(Array.isArray(getIncorrectWordSequence(NaN))).toEqual(true) expect(Array.isArray(getIncorrectWordSequence([]))).toEqual(true) - expect(Array.isArray(getIncorrectWordSequence({}))).toEqual(true) - expect( - Array.isArray( - getIncorrectWordSequence({ - property: 'value' - }) - ) - ).toEqual(true) - expect(Array.isArray(getIncorrectWordSequence([[[]]]))).toEqual(true) }) test('getIncorrectWordSequence', () => { @@ -132,7 +107,6 @@ describe('getIncorrectWordSequence function', () => { describe('calculateWPM function', () => { test('should always return a number', () => { - expect(calculateWPM()).toBeNaN() expect(calculateWPM(undefined, 10)).toBeNaN() expect(calculateWPM(null, -1)).toBeNaN() expect(calculateWPM([], 10)).toEqual(0) @@ -151,7 +125,7 @@ describe('calculateWPM function', () => { expect(calculateWPM(wordSequence, 60)).toBeGreaterThan(0) - wordSequence.letterSequence = wordSequence[0].letterSequence.map( + wordSequence[0].letterSequence = wordSequence[0].letterSequence.map( letter => new Letter(letter.original, letter.original) ) @@ -163,10 +137,6 @@ describe('calculateWPM function', () => { describe('calculateAccuary function', () => { test('should always return a number', () => { - expect(calculateAccuracy()).toBeNaN() - expect(calculateAccuracy(undefined, 10)).toBeNaN() - expect(calculateAccuracy(null, -1)).toBeNaN() - const wordSequence = [ new Word('hello'), new Word('world'), @@ -181,7 +151,7 @@ describe('calculateAccuary function', () => { expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0) - wordSequence.letterSequence = wordSequence[0].letterSequence.map( + wordSequence[0].letterSequence = wordSequence[0].letterSequence.map( letter => new Letter(letter.original, letter.original) ) @@ -192,8 +162,8 @@ describe('calculateAccuary function', () => { }) describe('getStatistics function', () => { - test('should be numbers', () => { - const output = Object.values(getStatistics()) + test('should be numbers', () => { + const output = Object.values(getStatistics([], 0)) .map(val => typeof val) .every(type => type === 'number') diff --git a/src/utils/constant.js b/src/utils/constant.ts similarity index 63% rename from src/utils/constant.js rename to src/utils/constant.ts index a9b71a7..98e5f37 100644 --- a/src/utils/constant.js +++ b/src/utils/constant.ts @@ -1,17 +1,17 @@ export const language = { id: 'id', en: 'en', -} +} as const export const mode = { time: 'time', word: 'word', -} +} as const export const duration = { '30s': 30, '60s': 60, -} +} as const export const theme = { default: 'Dark', @@ -19,7 +19,7 @@ export const theme = { oneDarkPro: 'One Dark Pro', monokai: 'Monokai', pink: 'Pink', -} +} as const const constant = { language, @@ -28,4 +28,9 @@ const constant = { theme, } +export type Language = keyof typeof language; +export type Mode = keyof typeof mode; +export type Theme = keyof typeof theme; +export type Duration = keyof typeof duration; + export default constant diff --git a/src/utils/statistics.js b/src/utils/statistics.ts similarity index 80% rename from src/utils/statistics.js rename to src/utils/statistics.ts index caed5d9..40289d1 100644 --- a/src/utils/statistics.js +++ b/src/utils/statistics.ts @@ -1,6 +1,6 @@ -import { isCorrectlyTyped } from '../utils/Word' +import Word, { isCorrectlyTyped } from '../utils/Word' -export function getCorrectWordSequence (wordSequence) { +export function getCorrectWordSequence (wordSequence: Array) { if (!Array.isArray(wordSequence)) { return [] } @@ -9,7 +9,7 @@ export function getCorrectWordSequence (wordSequence) { return typedWordSeq.filter(word => isCorrectlyTyped(word)) } -export function getIncorrectWordSequence (wordSequence) { +export function getIncorrectWordSequence (wordSequence: Array) { if (!Array.isArray(wordSequence)) { return [] } @@ -18,7 +18,7 @@ export function getIncorrectWordSequence (wordSequence) { return typedWordSeq.filter(word => !isCorrectlyTyped(word)) } -export function calculateWPM (wordSequence, seconds) { +export function calculateWPM (wordSequence: Array, seconds: number) { if (!Array.isArray(wordSequence) || seconds < 0) { return NaN } @@ -34,7 +34,7 @@ export function calculateWPM (wordSequence, seconds) { ) } -export function calculateAccuracy (wordSequence) { +export function calculateAccuracy (wordSequence: Array): number { if (!Array.isArray(wordSequence)) { return NaN } @@ -60,7 +60,7 @@ export function calculateAccuracy (wordSequence) { return Math.round((correctKeys / totalKeys) * 100) / 100 } -export function getStatistics (wordSequence, seconds) { +export function getStatistics (wordSequence: Array, seconds: number) { return { wpm: calculateWPM(wordSequence, seconds), accuracy: calculateAccuracy(wordSequence), diff --git a/src/utils/store.js b/src/utils/store.js deleted file mode 100644 index d97832f..0000000 --- a/src/utils/store.js +++ /dev/null @@ -1,33 +0,0 @@ -export function createTypingStore({ - inputValue = '', - languageJSON = null, - wordSequence = [], - caretPosition = [0, 0], - statistics = {}, - typingStatus = 'pending', - startTime = null, - finishTime = null, -}) { - return { - inputValue, - languageJSON, - wordSequence, - caretPosition, - statistics, - typingStatus, - startTime, - finishTime, - } -} - -export function createConfigStore({ - lang = 'id', - mode = 'time', - duration = 60, -}) { - return { - lang, - mode, - duration, - } -} diff --git a/src/utils/store.ts b/src/utils/store.ts new file mode 100644 index 0000000..d00587c --- /dev/null +++ b/src/utils/store.ts @@ -0,0 +1,62 @@ +import { Language, Mode, Theme } from "./constant"; +import Word from "./Word"; + +export interface Config { + lang: Language; + duration: number; + mode: Mode; + theme: Theme; +} + +export interface Typing { + inputValue: string; + languageJSON: Record; + wordSequence: Array; + caretPosition: [number, number]; + statistics: { + accuracy: number; + wpm: number; + correctWords: number; + incorrectWords: number; + }; + typingStatus: 'pending' | 'done' | 'started'; + typingMinutes?: number; + startTime: number; + finishTime: number; +} + +export function createTypingStore({ + inputValue = '', + languageJSON = null, + wordSequence = [], + caretPosition = [0, 0], + statistics = { accuracy: 0, correctWords: 0, incorrectWords: 0, wpm: 0 }, + typingStatus = 'pending', + startTime = null, + finishTime = null, +}: Partial): Typing { + return { + inputValue, + languageJSON, + wordSequence, + caretPosition, + statistics, + typingStatus, + startTime, + finishTime, + } +} + +export function createConfigStore({ + lang = 'id', + mode = 'time', + duration = 60, + theme = 'default' +}: Partial): Config { + return { + lang, + mode, + duration, + theme + } +} diff --git a/src/utils/theme.js b/src/utils/theme.ts similarity index 96% rename from src/utils/theme.js rename to src/utils/theme.ts index 100f46e..5372c4c 100644 --- a/src/utils/theme.js +++ b/src/utils/theme.ts @@ -7,7 +7,7 @@ export function createColorTheme({ incorrectLetter, extraLetter, caret, -}) { +}: any) { return { background, text, diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1579807 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..9d31e2a --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite-env.d.ts b/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/vite.config.js b/vite.config.ts similarity index 65% rename from vite.config.js rename to vite.config.ts index 445456b..3db4740 100644 --- a/vite.config.js +++ b/vite.config.ts @@ -1,17 +1,17 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; -import jsconfigPaths from 'vite-jsconfig-paths' +import tsconfigPaths from 'vite-tsconfig-paths' export default defineConfig(() => { return { build: { outDir: 'build', }, - plugins: [jsconfigPaths(), react()], + plugins: [tsconfigPaths(), react()], test: { globals: true, environment: 'jsdom', - setupFiles: './src/setupTests.js', + setupFiles: './src/setupTests.ts', }, }; }); From 515c1833e0b13f85d7961ecabfa47328c0fe428c Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Wed, 17 Jul 2024 22:21:40 +0700 Subject: [PATCH 03/12] typescript strict true --- package.json | 2 ++ pnpm-lock.yaml | 51 +++++++-------------------- src/App.tsx | 12 +++---- src/components/Hotkey.tsx | 2 +- src/components/Letter.tsx | 3 ++ src/components/MockThemeUI.tsx | 6 ++-- src/components/Statistic.tsx | 2 +- src/components/Timer.tsx | 2 +- src/components/TypingArea.tsx | 12 +++---- src/components/Word.tsx | 4 +-- src/reportWebVitals.ts | 2 +- src/setupTests.ts | 1 + src/store/config/action.ts | 3 ++ src/store/config/index.tsx | 21 +++++++---- src/store/config/reducer.ts | 10 +++--- src/store/typing/action.ts | 3 ++ src/store/typing/index.tsx | 19 ++++++---- src/store/typing/reducer.ts | 29 +++++++-------- src/utils/Letter.ts | 8 ++--- src/utils/Word.ts | 2 +- src/utils/__test__/statistics.test.ts | 10 ++---- src/utils/store.ts | 12 +++++-- tsconfig.json | 2 +- 23 files changed, 110 insertions(+), 108 deletions(-) diff --git a/package.json b/package.json index a9dd9fd..8a94230 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,10 @@ }, "devDependencies": { "@emotion/jest": "^11.2.1", + "@types/lodash": "^4.17.7", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@types/testing-library__jest-dom": "5.13.0", "@types/theme-ui": "^0.3.7", "@types/theme-ui__components": "^0.3.0", "@vitejs/plugin-react": "^4.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b19d68..d76c4f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,12 +43,18 @@ devDependencies: '@emotion/jest': specifier: ^11.2.1 version: 11.11.0 + '@types/lodash': + specifier: ^4.17.7 + version: 4.17.7 '@types/react': specifier: ^18.3.3 version: 18.3.3 '@types/react-dom': specifier: ^18.3.0 version: 18.3.0 + '@types/testing-library__jest-dom': + specifier: 5.13.0 + version: 5.13.0 '@types/theme-ui': specifier: ^0.3.7 version: 0.3.7(react@17.0.2) @@ -647,14 +653,12 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-get-type: 29.6.3 - dev: false /@jest/schemas@29.6.3: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@sinclair/typebox': 0.27.8 - dev: false /@jest/types@26.6.2: resolution: {integrity: sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==} @@ -677,7 +681,6 @@ packages: '@types/node': 20.14.11 '@types/yargs': 17.0.32 chalk: 4.1.2 - dev: false /@jridgewell/gen-mapping@0.3.5: resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} @@ -833,7 +836,6 @@ packages: /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - dev: false /@styled-system/background@5.1.2: resolution: {integrity: sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==} @@ -940,7 +942,7 @@ packages: dependencies: '@adobe/css-tools': 4.4.0 '@babel/runtime': 7.24.8 - '@types/testing-library__jest-dom': 5.14.9 + '@types/testing-library__jest-dom': 5.13.0 aria-query: 5.3.0 chalk: 3.0.0 css.escape: 1.5.1 @@ -1092,32 +1094,31 @@ packages: /@types/istanbul-lib-coverage@2.0.6: resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - dev: false /@types/istanbul-lib-report@3.0.3: resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} dependencies: '@types/istanbul-lib-coverage': 2.0.6 - dev: false /@types/istanbul-reports@3.0.4: resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} dependencies: '@types/istanbul-lib-report': 3.0.3 - dev: false /@types/jest@29.5.12: resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} dependencies: expect: 29.7.0 pretty-format: 29.7.0 - dev: false + + /@types/lodash@4.17.7: + resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} + dev: true /@types/node@20.14.11: resolution: {integrity: sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==} dependencies: undici-types: 5.26.5 - dev: false /@types/parse-json@4.0.2: resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -1141,7 +1142,6 @@ packages: /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - dev: false /@types/styled-system@5.1.22: resolution: {integrity: sha512-NbRp37zWcrf/+Qf2NumdyZfhSx1dzJ50zgfKvnezYJx1HTRUMVYY8jtWvK1eoIAa6F5sXwHLhE8oXNu15ThBAA==} @@ -1155,11 +1155,10 @@ packages: csstype: 3.1.3 dev: true - /@types/testing-library__jest-dom@5.14.9: - resolution: {integrity: sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==} + /@types/testing-library__jest-dom@5.13.0: + resolution: {integrity: sha512-tfjY4Fzzwg1wSU31MWaIH8rzJ2WPtQtUNnZ0wcZwzzhWWRa63Jb1fB7tl79fGX7PUL/4ZHjKs+tcY5BZ8nfNyg==} dependencies: '@types/jest': 29.5.12 - dev: false /@types/theme-ui@0.3.7(react@17.0.2): resolution: {integrity: sha512-4hzDlDhlFYmOdXBLZTbO4N2hWfuGo1N77AcIMaSyDGEyFbdZSpelMLTkEtNzYT8yQWIl3x0WITiBzjqkfc6dUg==} @@ -1200,7 +1199,6 @@ packages: /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - dev: false /@types/yargs@15.0.19: resolution: {integrity: sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==} @@ -1212,7 +1210,6 @@ packages: resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} dependencies: '@types/yargs-parser': 21.0.3 - dev: false /@vitejs/plugin-react@4.3.1(vite@5.3.4): resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} @@ -1331,7 +1328,6 @@ packages: /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - dev: false /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} @@ -1402,7 +1398,6 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.1.1 - dev: false /browserslist@4.23.2: resolution: {integrity: sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==} @@ -1470,7 +1465,6 @@ packages: /ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - dev: false /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -1591,7 +1585,6 @@ packages: /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: false /dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -1665,7 +1658,6 @@ packages: /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} - dev: false /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1697,14 +1689,12 @@ packages: jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 jest-util: 29.7.0 - dev: false /fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - dev: false /find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} @@ -1772,7 +1762,6 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: false /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} @@ -1864,7 +1853,6 @@ packages: /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: false /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -1928,12 +1916,10 @@ packages: diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: false /jest-get-type@29.6.3: resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: false /jest-matcher-utils@29.7.0: resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} @@ -1943,7 +1929,6 @@ packages: jest-diff: 29.7.0 jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: false /jest-message-util@29.7.0: resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} @@ -1958,7 +1943,6 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 - dev: false /jest-util@29.7.0: resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} @@ -1970,7 +1954,6 @@ packages: ci-info: 3.9.0 graceful-fs: 4.2.11 picomatch: 2.3.1 - dev: false /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2094,7 +2077,6 @@ packages: dependencies: braces: 3.0.3 picomatch: 2.3.1 - dev: false /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} @@ -2229,7 +2211,6 @@ packages: /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: false /postcss@8.4.39: resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} @@ -2265,7 +2246,6 @@ packages: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.3.1 - dev: false /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -2308,7 +2288,6 @@ packages: /react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - dev: false /react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} @@ -2435,7 +2414,6 @@ packages: /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - dev: false /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} @@ -2455,7 +2433,6 @@ packages: engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 - dev: false /stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -2609,7 +2586,6 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - dev: false /tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} @@ -2648,7 +2624,6 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: false /universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} diff --git a/src/App.tsx b/src/App.tsx index 29ff36c..93ac9a9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,8 +3,8 @@ import TypingArea from 'components/TypingArea' import useConfigStore from './store/config' import { TypingStoreProvider } from './store/typing' import { CHANGE_LANGUAGE } from './store/config/action' -import { language, theme } from './utils/constant' -import { useCallback } from 'react' +import { Language, language, Theme, theme } from './utils/constant' +import React, { useCallback } from 'react' import Statistic from 'components/Statistic' import Timer from 'components/Timer' @@ -12,16 +12,16 @@ const App = () => { const { config, dispatch, setTheme } = useConfigStore() const handleSelectThemeChange = useCallback( - (e) => { - const selectedValue = e.target.options[e.target.selectedIndex].value + (e: React.ChangeEvent) => { + const selectedValue = e.target.options[e.target.selectedIndex].value as Theme setTheme(selectedValue) }, [setTheme] ) const handleSelectLanguageChange = useCallback( - (e) => { - const selectedValue = e.target.options[e.target.selectedIndex].value + (e: React.ChangeEvent) => { + const selectedValue = e.target.options[e.target.selectedIndex].value as Language dispatch({ type: CHANGE_LANGUAGE, payload: { lang: selectedValue }, diff --git a/src/components/Hotkey.tsx b/src/components/Hotkey.tsx index ef4ae34..be32f3f 100644 --- a/src/components/Hotkey.tsx +++ b/src/components/Hotkey.tsx @@ -1,6 +1,6 @@ import { Box } from 'theme-ui' -const Hotkey = ({ children }) => { +const Hotkey = ({ children }: React.PropsWithChildren) => { return ( diff --git a/src/components/MockThemeUI.tsx b/src/components/MockThemeUI.tsx index 05a16e6..b13eb00 100644 --- a/src/components/MockThemeUI.tsx +++ b/src/components/MockThemeUI.tsx @@ -2,12 +2,12 @@ import React from 'react'; import { ThemeProvider } from 'theme-ui' import theme from 'theme' -function MockThemeUI({ children }) { +function MockThemeUI({ children }: React.PropsWithChildren) { return ( {children} - + ) } -export default MockThemeUI; \ No newline at end of file +export default MockThemeUI; diff --git a/src/components/Statistic.tsx b/src/components/Statistic.tsx index 4c64e1d..64e3f80 100644 --- a/src/components/Statistic.tsx +++ b/src/components/Statistic.tsx @@ -4,7 +4,7 @@ import { jsx } from 'theme-ui' import { Flex } from 'theme-ui' import useTypingStore from '../store/typing' -const ListItem = (props) => ( +const ListItem = (props: React.PropsWithChildren) => (
) diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx index c03d32d..876f9dd 100644 --- a/src/components/Timer.tsx +++ b/src/components/Timer.tsx @@ -3,7 +3,7 @@ import { Flex } from 'theme-ui' import actionType from '../store/typing/action' import useTypingStore from '../store/typing' -const Timer = ({ duration }) => { +const Timer = ({ duration }: React.PropsWithChildren<{ duration: number}>) => { const { typing, dispatch } = useTypingStore() const [timerCount, setTimerCount] = useState(duration) const timerRef = useRef() diff --git a/src/components/TypingArea.tsx b/src/components/TypingArea.tsx index a2aa5b0..b73e847 100644 --- a/src/components/TypingArea.tsx +++ b/src/components/TypingArea.tsx @@ -20,7 +20,7 @@ const DISABLED_CTRL = ['a', 'c', 'v'] const TypingArea = memo(() => { const { typing, dispatch } = useTypingStore() - const inputRef = useRef() + const inputRef = useRef(null) const [blur, setBlur] = useState(false) useHotkeys('shift+Enter', () => { @@ -39,7 +39,7 @@ const TypingArea = memo(() => { return inputRef.current.focus() }, []) - const handleKeyDown = useCallback((e) => { + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { if (DISABLED_KEYS.includes(e.key.toLowerCase())) { e.preventDefault() return @@ -52,7 +52,7 @@ const TypingArea = memo(() => { }, []) const handleOverlayClick = useCallback( - (e) => { + (e: React.MouseEvent) => { if (typing.typingStatus === 'done') dispatch({ type: actionType.REFRESH_TYPING_STORE }) @@ -62,14 +62,14 @@ const TypingArea = memo(() => { ) const handleInputChange = useCallback( - (e) => { + (e: React.ChangeEvent) => { if (typing.typingStatus === 'pending') { dispatch({ type: actionType.START_TYPING }) } const value = e.target.value if (value.endsWith(' ')) { - const seconds = (Date.now() - typing.startTime) / 1000 + const seconds = (Date.now() - (typing.startTime ?? 0)) / 1000 dispatch({ type: actionType.GOTO_NEXT_WORD, payload: { typingMinutes: seconds }, @@ -114,7 +114,7 @@ const TypingArea = memo(() => { p: 3, filter: blur && 'blur(5px)', opacity: blur && 0.25, - }} + } as any} > { + ({ word, cursorIndex }: { word: Word, cursorIndex?: number | null }) => { return ( word.show && ( @@ -26,7 +26,7 @@ const WordComponent = memo( ))} = word.originalWord.length} + cursor={!!cursorIndex && (cursorIndex >= word.originalWord.length)} letter={new Letter(' ')} /> diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts index 5253d3a..f00fe6c 100644 --- a/src/reportWebVitals.ts +++ b/src/reportWebVitals.ts @@ -1,4 +1,4 @@ -const reportWebVitals = onPerfEntry => { +const reportWebVitals = (onPerfEntry: any) => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); diff --git a/src/setupTests.ts b/src/setupTests.ts index d588ca6..48ac224 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -1,6 +1,7 @@ // add Vitest functions here globally import { expect, afterEach } from 'vitest'; import { cleanup } from '@testing-library/react'; +// @ts-ignore import matchers from '@testing-library/jest-dom/matchers'; // Extend Vitest's expect method with methods from react-testing-library diff --git a/src/store/config/action.ts b/src/store/config/action.ts index 27a37a5..c710f1f 100644 --- a/src/store/config/action.ts +++ b/src/store/config/action.ts @@ -1,3 +1,5 @@ +import { Config } from "utils/store" + export const CHANGE_LANGUAGE = 'CHANGE_LANGUAGE' export const CHANGE_MODE = 'CHANGE_MODE' export const CHANGE_DURATION = 'CHANGE_DURATION' @@ -11,5 +13,6 @@ const actionType = { } as const export type ActionType = keyof typeof actionType; +export type Action = { type: ActionType, payload: Partial } export default actionType diff --git a/src/store/config/index.tsx b/src/store/config/index.tsx index 397d641..1ac4157 100644 --- a/src/store/config/index.tsx +++ b/src/store/config/index.tsx @@ -1,20 +1,21 @@ import { createContext, Dispatch, SetStateAction, useContext, useReducer } from 'react' -import configReducer, { DispatchParam } from './reducer' +import configReducer from './reducer' import { Config, createConfigStore } from '../../utils/store' import { useColorMode } from 'theme-ui' import { Theme } from 'utils/constant' +import { Action } from './action' const initialStore = createConfigStore({}) interface ConfigContext { config: Config; - dispatch: Dispatch; - setTheme: Dispatch>; + dispatch: Dispatch; + setTheme: Dispatch>; } -export const ConfigStoreContext = createContext(null) +export const ConfigStoreContext = createContext(null) -export const ConfigStoreProvider = ({ children }) => { +export const ConfigStoreProvider = ({ children }: React.PropsWithChildren) => { const [config, dispatch] = useReducer(configReducer, initialStore) const [theme, setTheme] = useColorMode() @@ -34,6 +35,14 @@ export const ConfigStoreProvider = ({ children }) => { ) } -const useConfigStore = () => useContext(ConfigStoreContext) +const useConfigStore = () => { + const ctx = useContext(ConfigStoreContext); + + if(ctx === null) { + throw new Error("`useConfigStore` should be used inside `ConfigStoreProvider`") + } + + return ctx +} export default useConfigStore diff --git a/src/store/config/reducer.ts b/src/store/config/reducer.ts index 5738298..6c8266c 100644 --- a/src/store/config/reducer.ts +++ b/src/store/config/reducer.ts @@ -1,19 +1,17 @@ import { Config } from 'utils/store' -import actionType, { ActionType } from './action' +import actionType, { Action } from './action' -export type DispatchParam = { type: ActionType, payload: Partial } - -function configReducer(state: Config, { type, payload }: DispatchParam): Config { +function configReducer(state: Config, { type, payload }: Action): Config { switch (type) { case actionType.CHANGE_LANGUAGE: return { ...state, - lang: payload.lang, + lang: payload.lang ?? state.lang, } case actionType.CHANGE_DURATION: return { ...state, - duration: payload.duration, + duration: payload.duration ?? state.duration, } default: return state diff --git a/src/store/typing/action.ts b/src/store/typing/action.ts index 0b8cfa3..c85afec 100644 --- a/src/store/typing/action.ts +++ b/src/store/typing/action.ts @@ -1,3 +1,5 @@ +import { Typing } from "utils/store" + export const INITIALIZE_TYPING_STORE = 'INITIALIZE_TYPING_STORE' export const UPDATE_WORD = 'UPDATE_WORD' export const GOTO_NEXT_WORD = 'GOTO_NEXT_WORD' @@ -15,5 +17,6 @@ const action = { } as const; export type ActionType = keyof typeof action; +export type Action = { type: ActionType, payload?: Partial } export default action diff --git a/src/store/typing/index.tsx b/src/store/typing/index.tsx index cce96b8..09443a5 100644 --- a/src/store/typing/index.tsx +++ b/src/store/typing/index.tsx @@ -1,18 +1,19 @@ import { createContext, Dispatch, useContext, useEffect, useReducer } from 'react' import typingReducer from './reducer' import { createTypingStore, Typing } from '../../utils/store' -import { ActionType, INITIALIZE_TYPING_STORE } from './action' +import { Action, ActionType, INITIALIZE_TYPING_STORE } from './action' +import { Language } from 'utils/constant' const initialStore = createTypingStore({}) interface TypingContext { typing: Typing; - dispatch: Dispatch<{ type: ActionType, payload?: Partial }>; + dispatch: Dispatch; } -export const TypingStoreContext = createContext(null) +export const TypingStoreContext = createContext(null) -export const TypingStoreProvider = ({ children, lang }) => { +export const TypingStoreProvider = ({ children, lang }: React.PropsWithChildren<{ lang: Language }>) => { const [typing, dispatch] = useReducer(typingReducer, initialStore) useEffect(() => { @@ -35,10 +36,16 @@ export const TypingStoreProvider = ({ children, lang }) => { ) } -const useTypingStore = () => useContext(TypingStoreContext) +const useTypingStore = () => { + const ctx = useContext(TypingStoreContext); + if (ctx === null) { + throw new Error("`useTypingStore` must be used inside `TypingStoreProvider`") + } + return ctx +} export default useTypingStore -async function fetchLanguageJSON(lang) { +async function fetchLanguageJSON(lang: Language) { const response = await fetch(`/words/lang/${lang}.json`) return await response.json() } diff --git a/src/store/typing/reducer.ts b/src/store/typing/reducer.ts index a7eb96f..38b25dd 100644 --- a/src/store/typing/reducer.ts +++ b/src/store/typing/reducer.ts @@ -1,28 +1,29 @@ -import actionType from './action' -import { createWordSequence } from '../../utils/Word' +import actionType, { Action } from './action' +import Word, { createWordSequence } from '../../utils/Word' import Letter from '../../utils/Letter' import { getStatistics } from '../../utils/statistics' -import { createTypingStore } from '../../utils/store' +import { createTypingStore, Typing } from '../../utils/store' +import { Reducer } from 'react' -function replaceItemInArray(originalArr, index, newItem) { +function replaceItemInArray(originalArr: Array, index: number, newItem: T) { return originalArr.map((item, i) => (i === index ? newItem : item)) } -function hideWord(word) { +function hideWord(word: Word) { return { ...word, show: false, } } -function markAsTyped(word) { +function markAsTyped(word: Word) { return { ...word, isTyped: true, } } -function typingReducer(state, { type, payload }) { +const typingReducer: Reducer = (state, { type, payload }) => { const [wordIndex, letterIndex] = state.caretPosition const activeWord = state.wordSequence[wordIndex] const N_WORD_TOBE_GENERATED = 20 //initial word count @@ -31,9 +32,9 @@ function typingReducer(state, { type, payload }) { switch (type) { case actionType.INITIALIZE_TYPING_STORE: { return createTypingStore({ - languageJSON: payload.languageJSON, + languageJSON: payload?.languageJSON, wordSequence: createWordSequence( - payload.languageJSON.words, + payload?.languageJSON?.words || [], N_WORD_TOBE_GENERATED ), }) @@ -57,7 +58,7 @@ function typingReducer(state, { type, payload }) { case actionType.UPDATE_WORD: { if (state.wordSequence.length <= 0) return state - const inputValueArr = payload.inputValue.split('') + const inputValueArr = payload?.inputValue?.split('') || [] const originalLetterSeq = activeWord.letterSequence.filter( (l) => l.original ) @@ -86,7 +87,7 @@ function typingReducer(state, { type, payload }) { updatedActiveWord ), caretPosition: [wordIndex, inputValueArr.length], - inputValue: payload.inputValue, + inputValue: payload?.inputValue || '', } } @@ -118,7 +119,7 @@ function typingReducer(state, { type, payload }) { const remaining_word_count = state.wordSequence.length - wordIndex + 1 if (remaining_word_count < APPEND_WORD_TRESHOLD) { const newlyGeneratedWordSeq = createWordSequence( - state.languageJSON.words, + state.languageJSON?.words || [], N_WORD_TOBE_GENERATED ) updatedWordSeq = [...updatedWordSeq, ...newlyGeneratedWordSeq] @@ -132,7 +133,7 @@ function typingReducer(state, { type, payload }) { wordSequence: updatedWordSeq, caretPosition: [wordIndex + 1, 0], inputValue: '', - statistics: getStatistics(updatedWordSeq, payload.typingMinutes), + statistics: getStatistics(updatedWordSeq, payload?.typingMinutes || -1), } } @@ -140,7 +141,7 @@ function typingReducer(state, { type, payload }) { return createTypingStore({ languageJSON: state.languageJSON, wordSequence: createWordSequence( - state.languageJSON.words, + state.languageJSON?.words || [], N_WORD_TOBE_GENERATED ), }) diff --git a/src/utils/Letter.ts b/src/utils/Letter.ts index 455d54d..9d47d30 100644 --- a/src/utils/Letter.ts +++ b/src/utils/Letter.ts @@ -1,4 +1,4 @@ -export function getStatus({ original, typed }: { original?: string; typed?: string | null }) { +export function getStatus({ original, typed }: { original?: string | null; typed?: string | null }) { if (original && !typed) return 'untyped' if (original && typed) return typed === original ? 'correct' : 'incorrect' if (!original && typed) return 'extra' @@ -6,11 +6,11 @@ export function getStatus({ original, typed }: { original?: string; typed?: stri } class Letter { - original: string; + original: string | null; typed: string | null; - status: string; + status: string | null; - constructor(original: string, typed: string | null = null) { + constructor(original: string | null, typed: string | null = null) { this.original = original; this.typed = typed; this.status = getStatus({ original, typed }); diff --git a/src/utils/Word.ts b/src/utils/Word.ts index 8a50d16..7f7e2b9 100644 --- a/src/utils/Word.ts +++ b/src/utils/Word.ts @@ -26,7 +26,7 @@ export function isCorrectlyTyped(word: Word) { .every((status) => status === 'correct') } -export function createWordSequence(arrOfString: string[], n = undefined) { +export function createWordSequence(arrOfString: string[], n?: number) { if (n) arrOfString = sampleSize(arrOfString, n) return arrOfString.map((s, i) => new Word(s)) diff --git a/src/utils/__test__/statistics.test.ts b/src/utils/__test__/statistics.test.ts index f43253e..c40be66 100644 --- a/src/utils/__test__/statistics.test.ts +++ b/src/utils/__test__/statistics.test.ts @@ -20,7 +20,6 @@ describe('getCorrectWordSequence function', () => { ] expect(Array.isArray(getCorrectWordSequence(wordSequence))).toEqual(true) - expect(Array.isArray(getCorrectWordSequence(null))).toEqual(true) expect(Array.isArray(getCorrectWordSequence([]))).toEqual(true) }) @@ -66,7 +65,6 @@ describe('getIncorrectWordSequence function', () => { ] expect(Array.isArray(getIncorrectWordSequence(wordSequence))).toEqual(true) - expect(Array.isArray(getIncorrectWordSequence(null))).toEqual(true) expect(Array.isArray(getIncorrectWordSequence([]))).toEqual(true) }) @@ -107,8 +105,8 @@ describe('getIncorrectWordSequence function', () => { describe('calculateWPM function', () => { test('should always return a number', () => { - expect(calculateWPM(undefined, 10)).toBeNaN() - expect(calculateWPM(null, -1)).toBeNaN() + expect(calculateWPM([], 0)).toBeNaN() + expect(calculateWPM([], -1)).toBeNaN() expect(calculateWPM([], 10)).toEqual(0) const wordSequence = [ @@ -129,8 +127,6 @@ describe('calculateWPM function', () => { letter => new Letter(letter.original, letter.original) ) - wordSequence[0] = null - expect(calculateWPM(wordSequence, 60)).toBeGreaterThan(0) }) }) @@ -155,8 +151,6 @@ describe('calculateAccuary function', () => { letter => new Letter(letter.original, letter.original) ) - wordSequence[0] = null - expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0) }) }) diff --git a/src/utils/store.ts b/src/utils/store.ts index d00587c..b406ca2 100644 --- a/src/utils/store.ts +++ b/src/utils/store.ts @@ -8,9 +8,15 @@ export interface Config { theme: Theme; } +interface LanguageJSON { + lang: string; + name: string; + words: string[] +} + export interface Typing { inputValue: string; - languageJSON: Record; + languageJSON: LanguageJSON | null; wordSequence: Array; caretPosition: [number, number]; statistics: { @@ -21,8 +27,8 @@ export interface Typing { }; typingStatus: 'pending' | 'done' | 'started'; typingMinutes?: number; - startTime: number; - finishTime: number; + startTime: number | null; + finishTime: number | null; } export function createTypingStore({ diff --git a/tsconfig.json b/tsconfig.json index 1579807..745b924 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "skipLibCheck": true, "esModuleInterop": false, "allowSyntheticDefaultImports": true, - "strict": false, + "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Node", From cda944d901135b81a5fa93a034c0e74cc6d1f7dd Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Fri, 19 Jul 2024 10:59:17 +0700 Subject: [PATCH 04/12] add biomejs & tailwind --- .vscode/settings.json | 17 + biome.json | 19 + package.json | 11 + pnpm-lock.yaml | 1181 +++++++++++++++++++++- postcss.config.js | 6 + src/App.tsx | 77 +- src/components/Hotkey.tsx | 16 +- src/components/Letter.tsx | 26 +- src/components/MockThemeUI.tsx | 12 +- src/components/Statistic.tsx | 36 +- src/components/Timer.tsx | 50 +- src/components/TypingArea.tsx | 183 ++-- src/components/Word.tsx | 37 +- src/components/__tests__/Letter.test.tsx | 72 +- src/components/__tests__/Word.test.tsx | 91 +- src/index.css | 69 ++ src/index.tsx | 20 +- src/lib/utils.ts | 6 + src/reportWebVitals.ts | 6 +- src/setupTests.ts | 8 +- src/store/config/action.ts | 16 +- src/store/config/index.tsx | 44 +- src/store/config/reducer.ts | 12 +- src/store/typing/action.ts | 18 +- src/store/typing/index.tsx | 55 +- src/store/typing/reducer.ts | 116 ++- src/theme/colors.ts | 102 +- src/theme/index.ts | 29 +- src/theme/styles.ts | 22 +- src/utils/Letter.ts | 15 +- src/utils/Word.ts | 20 +- src/utils/__test__/Letter.test.ts | 20 +- src/utils/__test__/Word.test.ts | 56 +- src/utils/__test__/statistics.test.ts | 256 ++--- src/utils/constant.ts | 34 +- src/utils/statistics.ts | 63 +- src/utils/store.ts | 24 +- src/utils/theme.ts | 15 +- tailwind.config.js | 77 ++ 39 files changed, 2152 insertions(+), 785 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 biome.json create mode 100644 postcss.config.js create mode 100644 src/index.css create mode 100644 src/lib/utils.ts create mode 100644 tailwind.config.js diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fc0a81c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "editor.defaultFormatter": "biomejs.biome", + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "quickfix.biome": "explicit", + "source.organizeImports.biome": "explicit" + }, +} \ No newline at end of file diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..3dac01c --- /dev/null +++ b/biome.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "lineWidth": 80, + "ignore": ["src/v0/**/*"] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + }, + "ignore": ["src/v0/**/*"] + } +} diff --git a/package.json b/package.json index 8a94230..bdc3cbb 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,20 @@ "type": "module", "dependencies": { "@emotion/core": "^10.0.0", + "@radix-ui/react-dropdown-menu": "^2.1.1", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-select": "^2.1.1", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", "lodash": "^4.17.21", "react": "^17.0.1", "react-dom": "^17.0.1", "react-hotkeys-hook": "^2.3.1", + "tailwind-merge": "^2.4.0", + "tailwindcss-animate": "^1.0.7", "theme-ui": "^0.3.1", "vite-tsconfig-paths": "^4.3.2", "web-vitals": "^0.2.4" @@ -41,6 +48,7 @@ ] }, "devDependencies": { + "@biomejs/biome": "1.8.3", "@emotion/jest": "^11.2.1", "@types/lodash": "^4.17.7", "@types/react": "^18.3.3", @@ -50,7 +58,10 @@ "@types/theme-ui__components": "^0.3.0", "@vitejs/plugin-react": "^4.3.1", "@vitest/coverage-v8": "^2.0.3", + "autoprefixer": "^10.4.19", "jsdom": "^24.1.0", + "postcss": "^8.4.39", + "tailwindcss": "^3.4.6", "typescript": "^5.5.3", "vite": "^5.3.4", "vitest": "^2.0.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d76c4f4..e4bc98d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ dependencies: '@emotion/core': specifier: ^10.0.0 version: 10.3.1(react@17.0.2) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.1 + version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-icons': + specifier: ^1.3.0 + version: 1.3.0(react@17.0.2) + '@radix-ui/react-select': + specifier: ^2.1.1 + version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) '@testing-library/jest-dom': specifier: ^5.11.4 version: 5.17.0 @@ -17,6 +26,12 @@ dependencies: '@testing-library/user-event': specifier: ^12.1.10 version: 12.8.3(@testing-library/dom@10.3.2) + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 + clsx: + specifier: ^2.1.1 + version: 2.1.1 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -29,6 +44,12 @@ dependencies: react-hotkeys-hook: specifier: ^2.3.1 version: 2.4.1(react-dom@17.0.2)(react@17.0.2) + tailwind-merge: + specifier: ^2.4.0 + version: 2.4.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.6) theme-ui: specifier: ^0.3.1 version: 0.3.5(react@17.0.2) @@ -40,6 +61,9 @@ dependencies: version: 0.2.4 devDependencies: + '@biomejs/biome': + specifier: 1.8.3 + version: 1.8.3 '@emotion/jest': specifier: ^11.2.1 version: 11.11.0 @@ -67,9 +91,18 @@ devDependencies: '@vitest/coverage-v8': specifier: ^2.0.3 version: 2.0.3(vitest@2.0.3) + autoprefixer: + specifier: ^10.4.19 + version: 10.4.19(postcss@8.4.39) jsdom: specifier: ^24.1.0 version: 24.1.0 + postcss: + specifier: ^8.4.39 + version: 8.4.39 + tailwindcss: + specifier: ^3.4.6 + version: 3.4.6 typescript: specifier: ^5.5.3 version: 5.5.3 @@ -86,6 +119,10 @@ packages: resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} dev: false + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + /@ampproject/remapping@2.3.0: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -322,6 +359,94 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@biomejs/biome@1.8.3: + resolution: {integrity: sha512-/uUV3MV+vyAczO+vKrPdOW0Iaet7UnJMU4bNMinggGJTAnBPjCoLEYcyYtYHNnUNYlv4xZMH6hVIQCAozq8d5w==} + engines: {node: '>=14.21.3'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.8.3 + '@biomejs/cli-darwin-x64': 1.8.3 + '@biomejs/cli-linux-arm64': 1.8.3 + '@biomejs/cli-linux-arm64-musl': 1.8.3 + '@biomejs/cli-linux-x64': 1.8.3 + '@biomejs/cli-linux-x64-musl': 1.8.3 + '@biomejs/cli-win32-arm64': 1.8.3 + '@biomejs/cli-win32-x64': 1.8.3 + dev: true + + /@biomejs/cli-darwin-arm64@1.8.3: + resolution: {integrity: sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-darwin-x64@1.8.3: + resolution: {integrity: sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64-musl@1.8.3: + resolution: {integrity: sha512-9yjUfOFN7wrYsXt/T/gEWfvVxKlnh3yBpnScw98IF+oOeCYb5/b/+K7YNqKROV2i1DlMjg9g/EcN9wvj+NkMuQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64@1.8.3: + resolution: {integrity: sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64-musl@1.8.3: + resolution: {integrity: sha512-UHrGJX7PrKMKzPGoEsooKC9jXJMa28TUSMjcIlbDnIO4EAavCoVmNQaIuUSH0Ls2mpGMwUIf+aZJv657zfWWjA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64@1.8.3: + resolution: {integrity: sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-arm64@1.8.3: + resolution: {integrity: sha512-J+Hu9WvrBevfy06eU1Na0lpc7uR9tibm9maHynLIoAjLZpQU3IW+OKHUtyL8p6/3pT2Ju5t5emReeIS2SAxhkQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-x64@1.8.3: + resolution: {integrity: sha512-/PJ59vA1pnQeKahemaQf4Nyj7IKUvGQSc3Ze1uIGi+Wvr1xF7rGobSrAAG01T/gUDG21vkDsZYM03NAmPiVkqg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@emotion/cache@10.0.29: resolution: {integrity: sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==} dependencies: @@ -631,6 +756,34 @@ packages: requiresBuild: true optional: true + /@floating-ui/core@1.6.4: + resolution: {integrity: sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==} + dependencies: + '@floating-ui/utils': 0.2.4 + dev: false + + /@floating-ui/dom@1.6.7: + resolution: {integrity: sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==} + dependencies: + '@floating-ui/core': 1.6.4 + '@floating-ui/utils': 0.2.4 + dev: false + + /@floating-ui/react-dom@2.1.1(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/dom': 1.6.7 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@floating-ui/utils@0.2.4: + resolution: {integrity: sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==} + dev: false + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -641,7 +794,6 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true /@istanbuljs/schema@0.1.3: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} @@ -710,17 +862,560 @@ packages: /@mdx-js/react@1.6.22(react@17.0.2): resolution: {integrity: sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==} peerDependencies: - react: ^16.13.1 || ^17.0.0 + react: ^16.13.1 || ^17.0.0 + dependencies: + react: 17.0.2 + dev: false + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + optional: true + + /@radix-ui/number@1.1.0: + resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} + dev: false + + /@radix-ui/primitive@1.1.0: + resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + dev: false + + /@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-context@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-direction@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-dropdown-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-menu': 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-icons@1.3.0(react@17.0.2): + resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x + dependencies: + react: 17.0.2 + dev: false + + /@radix-ui/react-id@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + aria-hidden: 1.2.4 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-remove-scroll: 2.5.7(@types/react@18.3.3)(react@17.0.2) + dev: false + + /@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@floating-ui/react-dom': 2.1.1(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/rect': 1.1.0 + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@radix-ui/react-select@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/number': 1.1.0 + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + aria-hidden: 1.2.4 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-remove-scroll: 2.5.7(@types/react@18.3.3)(react@17.0.2) + dev: false + + /@radix-ui/react-slot@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-use-previous@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-use-rect@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/rect': 1.1.0 + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-use-size@1.1.0(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@17.0.2) + '@types/react': 18.3.3 + react: 17.0.2 + dev: false + + /@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@17.0.2)(react@17.0.2) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) dev: false - /@pkgjs/parseargs@0.11.0: - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - requiresBuild: true - dev: true - optional: true + /@radix-ui/rect@1.1.0: + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + dev: false /@rollup/rollup-android-arm-eabi@4.18.1: resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==} @@ -1125,20 +1820,17 @@ packages: /@types/prop-types@15.7.12: resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - dev: true /@types/react-dom@18.3.0: resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} dependencies: '@types/react': 18.3.3 - dev: true /@types/react@18.3.3: resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 - dev: true /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -1311,7 +2003,6 @@ packages: /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -1332,7 +2023,26 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + /aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + dependencies: + tslib: 2.6.3 + dev: false /aria-query@4.2.2: resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==} @@ -1357,6 +2067,22 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: true + /autoprefixer@10.4.19(postcss@8.4.39): + resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.23.2 + caniuse-lite: 1.0.30001642 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.1 + postcss: 8.4.39 + postcss-value-parser: 4.2.0 + dev: true + /babel-plugin-emotion@10.2.2: resolution: {integrity: sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==} dependencies: @@ -1385,13 +2111,15 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true + + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} /brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: true /braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1419,6 +2147,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + /camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + /caniuse-lite@1.0.30001642: resolution: {integrity: sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==} dev: true @@ -1462,10 +2194,40 @@ packages: engines: {node: '>= 16'} dev: true + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + /ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + /class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + dependencies: + clsx: 2.0.0 + dev: false + + /clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + dev: false + + /clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -1490,6 +2252,10 @@ packages: delayed-stream: 1.0.0 dev: true + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -1519,12 +2285,16 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true /css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} dev: false + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + /cssstyle@4.0.1: resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} engines: {node: '>=18'} @@ -1537,7 +2307,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - dev: true /data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} @@ -1582,17 +2351,26 @@ packages: engines: {node: '>=6'} dev: false + /detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dev: false + + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + /dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dev: false /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true /electron-to-chromium@1.4.829: resolution: {integrity: sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==} @@ -1600,11 +2378,9 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} @@ -1690,6 +2466,21 @@ packages: jest-message-util: 29.7.0 jest-util: 29.7.0 + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.7 + + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + dependencies: + reusify: 1.0.4 + /fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1705,7 +2496,6 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} @@ -1716,6 +2506,10 @@ packages: mime-types: 2.1.35 dev: true + /fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + dev: true + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1735,11 +2529,28 @@ packages: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} dev: true + /get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + dev: false + /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} dev: true + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + /glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -1750,7 +2561,6 @@ packages: minipass: 7.1.2 package-json-from-dist: 1.0.0 path-scurry: 1.11.1 - dev: true /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -1836,19 +2646,40 @@ packages: engines: {node: '>=8'} dev: false + /invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + dependencies: + loose-envify: 1.4.0 + dev: false + /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.3.0 + /is-core-module@2.14.0: resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} @@ -1865,7 +2696,6 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true /istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} @@ -1906,7 +2736,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true /jest-diff@29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} @@ -1955,6 +2784,10 @@ packages: graceful-fs: 4.2.11 picomatch: 2.3.1 + /jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2012,6 +2845,14 @@ packages: hasBin: true dev: true + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + /lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2033,7 +2874,6 @@ packages: /lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - dev: true /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -2071,6 +2911,10 @@ packages: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + /micromatch@4.0.7: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} @@ -2105,16 +2949,21 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: true /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - dev: true /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + /nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2124,6 +2973,15 @@ packages: resolution: {integrity: sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==} dev: true + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + /normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + dev: true + /npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2139,6 +2997,10 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + /onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -2148,7 +3010,6 @@ packages: /package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} - dev: true /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -2174,7 +3035,6 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} @@ -2190,7 +3050,6 @@ packages: dependencies: lru-cache: 10.4.3 minipass: 7.1.2 - dev: true /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -2212,6 +3071,69 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + /postcss-import@15.1.0(postcss@8.4.39): + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.39 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + /postcss-js@4.0.1(postcss@8.4.39): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.39 + + /postcss-load-config@4.0.2(postcss@8.4.39): + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 3.1.2 + postcss: 8.4.39 + yaml: 2.4.5 + + /postcss-nested@6.0.1(postcss@8.4.39): + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.39 + postcss-selector-parser: 6.1.1 + + /postcss-selector-parser@6.1.1: + resolution: {integrity: sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + /postcss@8.4.39: resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} engines: {node: ^10 || ^12 || >=14} @@ -2260,6 +3182,9 @@ packages: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} dev: true + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + /react-dom@17.0.2(react@17.0.2): resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==} peerDependencies: @@ -2294,6 +3219,58 @@ packages: engines: {node: '>=0.10.0'} dev: true + /react-remove-scroll-bar@2.3.6(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + react-style-singleton: 2.2.1(@types/react@18.3.3)(react@17.0.2) + tslib: 2.6.3 + dev: false + + /react-remove-scroll@2.5.7(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@17.0.2) + react-style-singleton: 2.2.1(@types/react@18.3.3)(react@17.0.2) + tslib: 2.6.3 + use-callback-ref: 1.3.2(@types/react@18.3.3)(react@17.0.2) + use-sidecar: 1.1.2(@types/react@18.3.3)(react@17.0.2) + dev: false + + /react-style-singleton@2.2.1(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 17.0.2 + tslib: 2.6.3 + dev: false + /react@17.0.2: resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==} engines: {node: '>=0.10.0'} @@ -2301,6 +3278,17 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -2328,6 +3316,10 @@ packages: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + /rollup@4.18.1: resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2361,6 +3353,11 @@ packages: resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} dev: true + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true @@ -2395,12 +3392,10 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: true /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -2409,7 +3404,6 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} @@ -2449,7 +3443,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -2458,21 +3451,18 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi@7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} @@ -2513,6 +3503,19 @@ packages: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} dev: true + /sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -2533,6 +3536,48 @@ packages: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true + /tailwind-merge@2.4.0: + resolution: {integrity: sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==} + dev: false + + /tailwindcss-animate@1.0.7(tailwindcss@3.4.6): + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + dependencies: + tailwindcss: 3.4.6 + dev: false + + /tailwindcss@3.4.6: + resolution: {integrity: sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.7 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.1 + postcss: 8.4.39 + postcss-import: 15.1.0(postcss@8.4.39) + postcss-js: 4.0.1(postcss@8.4.39) + postcss-load-config: 4.0.2(postcss@8.4.39) + postcss-nested: 6.0.1(postcss@8.4.39) + postcss-selector-parser: 6.1.1 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + /test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} @@ -2558,6 +3603,17 @@ packages: - supports-color dev: false + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + /tinybench@2.8.0: resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} dev: true @@ -2604,6 +3660,9 @@ packages: punycode: 2.3.1 dev: true + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + /tsconfck@3.1.1(typescript@5.5.3): resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} engines: {node: ^18 || >=20} @@ -2617,6 +3676,10 @@ packages: typescript: 5.5.3 dev: false + /tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + dev: false + /typescript@5.5.3: resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} engines: {node: '>=14.17'} @@ -2648,6 +3711,40 @@ packages: requires-port: 1.0.0 dev: true + /use-callback-ref@1.3.2(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + react: 17.0.2 + tslib: 2.6.3 + dev: false + + /use-sidecar@1.1.2(@types/react@18.3.3)(react@17.0.2): + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.3 + detect-node-es: 1.1.0 + react: 17.0.2 + tslib: 2.6.3 + dev: false + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + /vite-node@2.0.3: resolution: {integrity: sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2817,7 +3914,6 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: true /why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} @@ -2835,7 +3931,6 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrap-ansi@8.1.0: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} @@ -2844,7 +3939,6 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true /ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} @@ -2875,3 +3969,8 @@ packages: /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + + /yaml@2.4.5: + resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} + engines: {node: '>= 14'} + hasBin: true diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/src/App.tsx b/src/App.tsx index 93ac9a9..0693e21 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,52 +1,55 @@ -import { Box, Flex, Heading, Link, Select } from 'theme-ui' -import TypingArea from 'components/TypingArea' -import useConfigStore from './store/config' -import { TypingStoreProvider } from './store/typing' -import { CHANGE_LANGUAGE } from './store/config/action' -import { Language, language, Theme, theme } from './utils/constant' -import React, { useCallback } from 'react' -import Statistic from 'components/Statistic' -import Timer from 'components/Timer' +import Statistic from "components/Statistic"; +import Timer from "components/Timer"; +import TypingArea from "components/TypingArea"; +import type React from "react"; +import { useCallback } from "react"; +import { Box, Flex, Heading, Link, Select } from "theme-ui"; +import useConfigStore from "./store/config"; +import { CHANGE_LANGUAGE } from "./store/config/action"; +import { TypingStoreProvider } from "./store/typing"; +import { type Language, type Theme, language, theme } from "./utils/constant"; const App = () => { - const { config, dispatch, setTheme } = useConfigStore() + const { config, dispatch, setTheme } = useConfigStore(); const handleSelectThemeChange = useCallback( (e: React.ChangeEvent) => { - const selectedValue = e.target.options[e.target.selectedIndex].value as Theme - setTheme(selectedValue) + const selectedValue = e.target.options[e.target.selectedIndex] + .value as Theme; + setTheme(selectedValue); }, - [setTheme] - ) + [setTheme], + ); const handleSelectLanguageChange = useCallback( (e: React.ChangeEvent) => { - const selectedValue = e.target.options[e.target.selectedIndex].value as Language + const selectedValue = e.target.options[e.target.selectedIndex] + .value as Language; dispatch({ type: CHANGE_LANGUAGE, payload: { lang: selectedValue }, - }) + }); }, - [dispatch] - ) + [dispatch], + ); return ( Typefaster @@ -55,8 +58,8 @@ const App = () => { sx={{ minHeight: 105, flexGrow: 1, - flexDirection: 'column', - justifyContent: 'center', + flexDirection: "column", + justifyContent: "center", }} > @@ -74,17 +77,17 @@ const App = () => { - © 2020 / Dandi Wiratsangka /{' '} + © 2020 / Dandi Wiratsangka /{" "} { {Object.entries(language).map(([key, value]) => ( - ) -} + ); +}; -export default App +export default App; diff --git a/src/components/Hotkey.tsx b/src/components/Hotkey.tsx index be32f3f..3764c66 100644 --- a/src/components/Hotkey.tsx +++ b/src/components/Hotkey.tsx @@ -1,4 +1,4 @@ -import { Box } from 'theme-ui' +import { Box } from "theme-ui"; const Hotkey = ({ children }: React.PropsWithChildren) => { return ( @@ -8,17 +8,17 @@ const Hotkey = ({ children }: React.PropsWithChildren) => { borderRadius: 5, px: 2, py: 1, - lineHeight: '15px', - bg: 'text', - color: 'background', - fontFamily: 'monospace', + lineHeight: "15px", + bg: "text", + color: "background", + fontFamily: "monospace", fontSize: 15, mx: 2, }} > {children} - ) -} + ); +}; -export default Hotkey +export default Hotkey; diff --git a/src/components/Letter.tsx b/src/components/Letter.tsx index 49012c5..513a435 100644 --- a/src/components/Letter.tsx +++ b/src/components/Letter.tsx @@ -1,22 +1,22 @@ -import { memo } from 'react' -import { Text } from 'theme-ui' -import Letter from 'utils/Letter' +import { memo } from "react"; +import { Text } from "theme-ui"; +import type Letter from "utils/Letter"; const LetterComponent = memo( - ({ letter, cursor }: { letter: Letter, cursor?: boolean }) => ( + ({ letter, cursor }: { letter: Letter; cursor?: boolean }) => ( prevProps.letter.typed === nextProps.letter.typed && - prevProps.cursor === nextProps.cursor -) + prevProps.cursor === nextProps.cursor, +); -export default LetterComponent +export default LetterComponent; diff --git a/src/components/MockThemeUI.tsx b/src/components/MockThemeUI.tsx index b13eb00..d415dc1 100644 --- a/src/components/MockThemeUI.tsx +++ b/src/components/MockThemeUI.tsx @@ -1,13 +1,9 @@ -import React from 'react'; -import { ThemeProvider } from 'theme-ui' -import theme from 'theme' +import type React from "react"; +import theme from "theme"; +import { ThemeProvider } from "theme-ui"; function MockThemeUI({ children }: React.PropsWithChildren) { - return ( - - {children} - - ) + return {children}; } export default MockThemeUI; diff --git a/src/components/Statistic.tsx b/src/components/Statistic.tsx index 64e3f80..8f716fd 100644 --- a/src/components/Statistic.tsx +++ b/src/components/Statistic.tsx @@ -1,37 +1,37 @@ /** @jsxRuntime classic */ /** @jsx jsx */ -import { jsx } from 'theme-ui' -import { Flex } from 'theme-ui' -import useTypingStore from '../store/typing' +import { jsx } from "theme-ui"; +import { Flex } from "theme-ui"; +import useTypingStore from "../store/typing"; const ListItem = (props: React.PropsWithChildren) => ( -
-) +
+); const Statistic = () => { - const { typing } = useTypingStore() - const { statistics } = typing - const { accuracy, wpm, correctWords, incorrectWords } = statistics + const { typing } = useTypingStore(); + const { statistics } = typing; + const { accuracy, wpm, correctWords, incorrectWords } = statistics; return ( - WPM : {wpm >= 0 ? wpm : '-'} - Acc : {accuracy >= 0 ? accuracy : '-'} + WPM : {wpm >= 0 ? wpm : "-"} + Acc : {accuracy >= 0 ? accuracy : "-"} - Correct words : {correctWords >= 0 ? correctWords : '-'} + Correct words : {correctWords >= 0 ? correctWords : "-"} - Incorrect words: {incorrectWords >= 0 ? incorrectWords : '-'} + Incorrect words: {incorrectWords >= 0 ? incorrectWords : "-"} {/* Time: {time >= 0 ? time : '-'} */} - ) -} + ); +}; -export default Statistic +export default Statistic; diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx index 876f9dd..5765374 100644 --- a/src/components/Timer.tsx +++ b/src/components/Timer.tsx @@ -1,44 +1,44 @@ -import { useEffect, useRef, useState } from 'react' -import { Flex } from 'theme-ui' -import actionType from '../store/typing/action' -import useTypingStore from '../store/typing' +import { useEffect, useRef, useState } from "react"; +import { Flex } from "theme-ui"; +import useTypingStore from "../store/typing"; +import actionType from "../store/typing/action"; -const Timer = ({ duration }: React.PropsWithChildren<{ duration: number}>) => { - const { typing, dispatch } = useTypingStore() - const [timerCount, setTimerCount] = useState(duration) - const timerRef = useRef() +const Timer = ({ duration }: React.PropsWithChildren<{ duration: number }>) => { + const { typing, dispatch } = useTypingStore(); + const [timerCount, setTimerCount] = useState(duration); + const timerRef = useRef(); useEffect(() => { - if (typing.typingStatus === 'pending') { - setTimerCount(duration) + if (typing.typingStatus === "pending") { + setTimerCount(duration); } - if (typing.typingStatus === 'started') { + if (typing.typingStatus === "started") { timerRef.current = setInterval( () => setTimerCount((val) => val - 1), - 1000 - ) + 1000, + ); setTimeout(() => { - dispatch({ type: actionType.DONE_TYPING }) - }, duration * 1000) - } else if (typing.typingStatus === 'done') { - clearInterval(timerRef.current) + dispatch({ type: actionType.DONE_TYPING }); + }, duration * 1000); + } else if (typing.typingStatus === "done") { + clearInterval(timerRef.current); } - return () => clearInterval(timerRef.current) - }, [typing.typingStatus, dispatch, duration]) + return () => clearInterval(timerRef.current); + }, [typing.typingStatus, dispatch, duration]); return ( - {timerCount === 0 ? 'Times up!' : timerCount} + {timerCount === 0 ? "Times up!" : timerCount} - ) -} + ); +}; -export default Timer +export default Timer; diff --git a/src/components/TypingArea.tsx b/src/components/TypingArea.tsx index b73e847..dbc2231 100644 --- a/src/components/TypingArea.tsx +++ b/src/components/TypingArea.tsx @@ -1,131 +1,136 @@ -import React, { memo, useCallback, useEffect, useRef, useState } from 'react' -import { Box, Flex, Input } from 'theme-ui' -import WordComponent from './Word' -import useTypingStore from '../store/typing' -import actionType from '../store/typing/action' -import { useHotkeys } from 'react-hotkeys-hook' -import Hotkey from './Hotkey' +import type React from "react"; +import { memo, useCallback, useEffect, useRef, useState } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; +import { Box, Flex, Input, type SxStyleProp } from "theme-ui"; +import useTypingStore from "../store/typing"; +import actionType from "../store/typing/action"; +import Hotkey from "./Hotkey"; +import WordComponent from "./Word"; const DISABLED_KEYS = [ - 'arrowup', - 'Arrowdown', - 'arrowleft', - 'arrowright', - 'home', - 'end', - 'tab', -] + "arrowup", + "Arrowdown", + "arrowleft", + "arrowright", + "home", + "end", + "tab", +]; -const DISABLED_CTRL = ['a', 'c', 'v'] +const DISABLED_CTRL = ["a", "c", "v"]; const TypingArea = memo(() => { - const { typing, dispatch } = useTypingStore() - const inputRef = useRef(null) - const [blur, setBlur] = useState(false) + const { typing, dispatch } = useTypingStore(); + const inputRef = useRef(null); + const [blur, setBlur] = useState(false); - useHotkeys('shift+Enter', () => { - dispatch({ type: actionType.REFRESH_TYPING_STORE }) - }) + useHotkeys("shift+Enter", () => { + dispatch({ type: actionType.REFRESH_TYPING_STORE }); + }); - useHotkeys('shift+f', () => focusInput(), { + useHotkeys("shift+f", () => focusInput(), { keydown: false, keyup: true, - }) + }); const focusInput = useCallback(() => { - if(!inputRef.current) - return + if (!inputRef.current) return; - return inputRef.current.focus() - }, []) + return inputRef.current.focus(); + }, []); - const handleKeyDown = useCallback((e: React.KeyboardEvent) => { - if (DISABLED_KEYS.includes(e.key.toLowerCase())) { - e.preventDefault() - return - } + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (DISABLED_KEYS.includes(e.key.toLowerCase())) { + e.preventDefault(); + return; + } - if (DISABLED_CTRL.includes(e.key.toLowerCase()) && e.ctrlKey) { - e.preventDefault() - return - } - }, []) + if (DISABLED_CTRL.includes(e.key.toLowerCase()) && e.ctrlKey) { + e.preventDefault(); + return; + } + }, + [], + ); const handleOverlayClick = useCallback( (e: React.MouseEvent) => { - if (typing.typingStatus === 'done') - dispatch({ type: actionType.REFRESH_TYPING_STORE }) + if (typing.typingStatus === "done") + dispatch({ type: actionType.REFRESH_TYPING_STORE }); - focusInput() + focusInput(); }, - [dispatch, typing.typingStatus, focusInput] - ) + [dispatch, typing.typingStatus, focusInput], + ); const handleInputChange = useCallback( (e: React.ChangeEvent) => { - if (typing.typingStatus === 'pending') { - dispatch({ type: actionType.START_TYPING }) + if (typing.typingStatus === "pending") { + dispatch({ type: actionType.START_TYPING }); } - const value = e.target.value - if (value.endsWith(' ')) { - const seconds = (Date.now() - (typing.startTime ?? 0)) / 1000 + const value = e.target.value; + if (value.endsWith(" ")) { + const seconds = (Date.now() - (typing.startTime ?? 0)) / 1000; dispatch({ type: actionType.GOTO_NEXT_WORD, payload: { typingMinutes: seconds }, - }) + }); } else { dispatch({ type: actionType.UPDATE_WORD, payload: { inputValue: value }, - }) + }); } }, - [dispatch, typing.typingStatus, typing.startTime] - ) + [dispatch, typing.typingStatus, typing.startTime], + ); useEffect(() => { const isInputFocused = - inputRef.current && inputRef.current === document.activeElement + inputRef.current && inputRef.current === document.activeElement; - if (isInputFocused) setBlur(false) - else setBlur(true) - }, [inputRef]) + if (isInputFocused) setBlur(false); + else setBlur(true); + }, []); useEffect(() => { - if (typing.typingStatus === 'done') { - setBlur(true) + if (typing.typingStatus === "done") { + setBlur(true); } - if (typing.typingStatus === 'pending') { - focusInput() + if (typing.typingStatus === "pending") { + focusInput(); } - }, [typing.typingStatus, focusInput]) + }, [typing.typingStatus, focusInput]); return ( { onChange={handleInputChange} onBlur={() => { // focusInput() - setBlur(true) + setBlur(true); }} onFocus={() => setBlur(false)} autoFocus sx={{ width: 10, - position: 'absolute', + position: "absolute", opacity: 0, }} onKeyDown={handleKeyDown} - disabled={typing.typingStatus === 'done'} + disabled={typing.typingStatus === "done"} /> {typing.wordSequence.map((w, i) => ( { - {typing.typingStatus === 'done' ? ( + {typing.typingStatus === "done" ? ( <>
Click or Shift+Enter to start new test @@ -191,7 +196,7 @@ const TypingArea = memo(() => { )} - ) -}) + ); +}); -export default TypingArea +export default TypingArea; diff --git a/src/components/Word.tsx b/src/components/Word.tsx index 4226985..bc1bf4c 100644 --- a/src/components/Word.tsx +++ b/src/components/Word.tsx @@ -1,11 +1,12 @@ -import { memo } from 'react' -import { Text } from 'theme-ui' -import Word, { isCorrectlyTyped } from '../utils/Word' -import LetterComponent from './Letter' -import Letter from 'utils/Letter' +import { memo } from "react"; +import { Text } from "theme-ui"; +import Letter from "utils/Letter"; +import type Word from "../utils/Word"; +import { isCorrectlyTyped } from "../utils/Word"; +import LetterComponent from "./Letter"; const WordComponent = memo( - ({ word, cursorIndex }: { word: Word, cursorIndex?: number | null }) => { + ({ word, cursorIndex }: { word: Word; cursorIndex?: number | null }) => { return ( word.show && ( @@ -13,30 +14,34 @@ const WordComponent = memo( data-testid="word" as="span" sx={(theme) => ({ - display: 'inline-block', + display: "inline-block", textDecorationColor: theme.colors.incorrectLetter, textDecoration: word.isTyped && !isCorrectlyTyped(word) - ? 'line-through' - : 'none', + ? "line-through" + : "none", })} > {word.letterSequence.map((l, i) => ( - + ))} = word.originalWord.length)} - letter={new Letter(' ')} + cursor={!!cursorIndex && cursorIndex >= word.originalWord.length} + letter={new Letter(" ")} /> ) - ) + ); }, (prevProps, nextProps) => prevProps.cursorIndex === nextProps.cursorIndex && prevProps.word.show === nextProps.word.show && - prevProps.word.isTyped === nextProps.word.isTyped -) + prevProps.word.isTyped === nextProps.word.isTyped, +); -export default WordComponent +export default WordComponent; diff --git a/src/components/__tests__/Letter.test.tsx b/src/components/__tests__/Letter.test.tsx index 1295ea5..a040d72 100644 --- a/src/components/__tests__/Letter.test.tsx +++ b/src/components/__tests__/Letter.test.tsx @@ -1,46 +1,48 @@ -import { expect, test } from 'vitest'; -import { render, screen } from '@testing-library/react' -import Letter from 'utils/Letter' -import LetterComponent from 'components/Letter' -import MockThemeUI from 'components/MockThemeUI' +import { render, screen } from "@testing-library/react"; +import LetterComponent from "components/Letter"; +import MockThemeUI from "components/MockThemeUI"; +import Letter from "utils/Letter"; +import { expect, test } from "vitest"; -import { matchers } from '@emotion/jest' +import { matchers } from "@emotion/jest"; // Add the custom matchers provided by '@emotion/jest' -expect.extend(matchers as any) -test('Letter component render correct letter', () => { - let letter = new Letter('a', 'b') - render() - expect(screen.getByTestId('letter').textContent).toBe('b') -}) +// biome-ignore lint/suspicious/noExplicitAny: +expect.extend(matchers as any); -test('Letter component render correct letter - 2', () => { - const letter = new Letter('a') - render() - expect(screen.getByTestId('letter').textContent).toBe('a') -}) +test("Letter component render correct letter", () => { + const letter = new Letter("a", "b"); + render(); + expect(screen.getByTestId("letter").textContent).toBe("b"); +}); -test('Letter component should render caret', () => { - const letter = new Letter('a') +test("Letter component render correct letter - 2", () => { + const letter = new Letter("a"); + render(); + expect(screen.getByTestId("letter").textContent).toBe("a"); +}); + +test("Letter component should render caret", () => { + const letter = new Letter("a"); render( - - - ) - expect(screen.getByTestId('letter')).toHaveStyleRule('content','"|"',{ - target: ':before' - }) -}) + + , + ); + expect(screen.getByTestId("letter")).toHaveStyleRule("content", '"|"', { + target: ":before", + }); +}); -test('Letter component should not render caret', () => { - const letter = new Letter('a') +test("Letter component should not render caret", () => { + const letter = new Letter("a"); render( - - - ) - expect(screen.getByTestId('letter')).not.toHaveStyleRule('content','"|"',{ - target: ':before' - }) -}) + + , + ); + expect(screen.getByTestId("letter")).not.toHaveStyleRule("content", '"|"', { + target: ":before", + }); +}); diff --git a/src/components/__tests__/Word.test.tsx b/src/components/__tests__/Word.test.tsx index 3412a1e..6523fea 100644 --- a/src/components/__tests__/Word.test.tsx +++ b/src/components/__tests__/Word.test.tsx @@ -1,63 +1,70 @@ -import { expect, test } from 'vitest'; -import { render, screen } from '@testing-library/react' -import Word from 'utils/Word' -import WordComponent from 'components/Word' -import MockThemeUI from 'components/MockThemeUI' +import { render, screen } from "@testing-library/react"; +import MockThemeUI from "components/MockThemeUI"; +import WordComponent from "components/Word"; +import Word from "utils/Word"; +import { expect, test } from "vitest"; -import { matchers } from '@emotion/jest' +import { matchers } from "@emotion/jest"; // Add the custom matchers provided by '@emotion/jest' -expect.extend(matchers as any) +// biome-ignore lint/suspicious/noExplicitAny: +expect.extend(matchers as any); -test('Word component null if word.show is false', () => { - let word = new Word('javascript') - word.show = false +test("Word component null if word.show is false", () => { + const word = new Word("javascript"); + word.show = false; - render() - expect(screen.queryByTestId('word')).toBe(null) -}) + render(); + expect(screen.queryByTestId("word")).toBe(null); +}); -test('Word component render correct word', () => { - let word = new Word('javascript') +test("Word component render correct word", () => { + const word = new Word("javascript"); render( - - ) - expect(screen.getByTestId('word').textContent).toBe('javascript') -}) - -test('Correctly typed word is not strike through', () => { - let word = new Word('javascript') - word.isTyped = true - word.letterSequence = word.letterSequence.map(letter=>({ + , + ); + expect(screen.getByTestId("word").textContent).toBe("javascript"); +}); + +test("Correctly typed word is not strike through", () => { + const word = new Word("javascript"); + word.isTyped = true; + word.letterSequence = word.letterSequence.map((letter) => ({ ...letter, - status: 'correct' - })) + status: "correct", + })); render( - - ) - expect(screen.getByTestId('word')).not.toHaveStyleRule('text-decoration',`line-through`) -}) - -test('Incorrectly typed word is strike through', () => { - let word = new Word('javascript') - word.isTyped = true - word.letterSequence = word.letterSequence.map(letter=>({ + , + ); + expect(screen.getByTestId("word")).not.toHaveStyleRule( + "text-decoration", + "line-through", + ); +}); + +test("Incorrectly typed word is strike through", () => { + const word = new Word("javascript"); + word.isTyped = true; + word.letterSequence = word.letterSequence.map((letter) => ({ ...letter, - status: 'correct' - })) + status: "correct", + })); - word.letterSequence[2].status = 'incorrect' + word.letterSequence[2].status = "incorrect"; render( - - ) - expect(screen.getByTestId('word')).toHaveStyleRule('text-decoration',`line-through`) -}) + , + ); + expect(screen.getByTestId("word")).toHaveStyleRule( + "text-decoration", + "line-through", + ); +}); diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..c84804d --- /dev/null +++ b/src/index.css @@ -0,0 +1,69 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 10% 3.9%; + --radius: 0.5rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + } + + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/src/index.tsx b/src/index.tsx index 9bd6b72..750f654 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,12 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import App from './App' -import { ThemeProvider } from 'theme-ui' -import theme from './theme' -import { Global, css } from '@emotion/core' -import { ConfigStoreProvider } from './store/config' +import { Global, css } from "@emotion/core"; +import React from "react"; +import ReactDOM from "react-dom"; +import { ThemeProvider } from "theme-ui"; +import App from "./App"; +import { ConfigStoreProvider } from "./store/config"; +import theme from "./theme"; + +import "./index.css"; // import reportWebVitals from './reportWebVitals' ReactDOM.render( @@ -30,8 +32,8 @@ ReactDOM.render( , - document.getElementById('root') -) + document.getElementById("root"), +); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..365058c --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts index f00fe6c..3e710fe 100644 --- a/src/reportWebVitals.ts +++ b/src/reportWebVitals.ts @@ -1,6 +1,8 @@ -const reportWebVitals = (onPerfEntry: any) => { +import type { ReportHandler } from "web-vitals"; + +const reportWebVitals = (onPerfEntry: ReportHandler) => { if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); diff --git a/src/setupTests.ts b/src/setupTests.ts index 48ac224..a6676b0 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -1,8 +1,8 @@ -// add Vitest functions here globally -import { expect, afterEach } from 'vitest'; -import { cleanup } from '@testing-library/react'; // @ts-ignore -import matchers from '@testing-library/jest-dom/matchers'; +import matchers from "@testing-library/jest-dom/matchers"; +// add Vitest functions here globally +import { cleanup } from "@testing-library/react"; +import { afterEach, expect } from "vitest"; // Extend Vitest's expect method with methods from react-testing-library expect.extend(matchers); diff --git a/src/store/config/action.ts b/src/store/config/action.ts index c710f1f..0ac2e85 100644 --- a/src/store/config/action.ts +++ b/src/store/config/action.ts @@ -1,18 +1,18 @@ -import { Config } from "utils/store" +import type { Config } from "utils/store"; -export const CHANGE_LANGUAGE = 'CHANGE_LANGUAGE' -export const CHANGE_MODE = 'CHANGE_MODE' -export const CHANGE_DURATION = 'CHANGE_DURATION' -export const CHANGE_THEME = 'CHANGE_THEME' +export const CHANGE_LANGUAGE = "CHANGE_LANGUAGE"; +export const CHANGE_MODE = "CHANGE_MODE"; +export const CHANGE_DURATION = "CHANGE_DURATION"; +export const CHANGE_THEME = "CHANGE_THEME"; const actionType = { CHANGE_LANGUAGE, CHANGE_MODE, CHANGE_DURATION, CHANGE_THEME, -} as const +} as const; export type ActionType = keyof typeof actionType; -export type Action = { type: ActionType, payload: Partial } +export type Action = { type: ActionType; payload: Partial }; -export default actionType +export default actionType; diff --git a/src/store/config/index.tsx b/src/store/config/index.tsx index 1ac4157..4fdc947 100644 --- a/src/store/config/index.tsx +++ b/src/store/config/index.tsx @@ -1,11 +1,17 @@ -import { createContext, Dispatch, SetStateAction, useContext, useReducer } from 'react' -import configReducer from './reducer' -import { Config, createConfigStore } from '../../utils/store' -import { useColorMode } from 'theme-ui' -import { Theme } from 'utils/constant' -import { Action } from './action' - -const initialStore = createConfigStore({}) +import { + type Dispatch, + type SetStateAction, + createContext, + useContext, + useReducer, +} from "react"; +import { useColorMode } from "theme-ui"; +import type { Theme } from "utils/constant"; +import { type Config, createConfigStore } from "../../utils/store"; +import type { Action } from "./action"; +import configReducer from "./reducer"; + +const initialStore = createConfigStore({}); interface ConfigContext { config: Config; @@ -13,11 +19,11 @@ interface ConfigContext { setTheme: Dispatch>; } -export const ConfigStoreContext = createContext(null) +export const ConfigStoreContext = createContext(null); export const ConfigStoreProvider = ({ children }: React.PropsWithChildren) => { - const [config, dispatch] = useReducer(configReducer, initialStore) - const [theme, setTheme] = useColorMode() + const [config, dispatch] = useReducer(configReducer, initialStore); + const [theme, setTheme] = useColorMode(); return ( { > {children} - ) -} + ); +}; const useConfigStore = () => { const ctx = useContext(ConfigStoreContext); - if(ctx === null) { - throw new Error("`useConfigStore` should be used inside `ConfigStoreProvider`") + if (ctx === null) { + throw new Error( + "`useConfigStore` should be used inside `ConfigStoreProvider`", + ); } - return ctx -} + return ctx; +}; -export default useConfigStore +export default useConfigStore; diff --git a/src/store/config/reducer.ts b/src/store/config/reducer.ts index 6c8266c..c2e9e5d 100644 --- a/src/store/config/reducer.ts +++ b/src/store/config/reducer.ts @@ -1,5 +1,5 @@ -import { Config } from 'utils/store' -import actionType, { Action } from './action' +import type { Config } from "utils/store"; +import actionType, { type Action } from "./action"; function configReducer(state: Config, { type, payload }: Action): Config { switch (type) { @@ -7,15 +7,15 @@ function configReducer(state: Config, { type, payload }: Action): Config { return { ...state, lang: payload.lang ?? state.lang, - } + }; case actionType.CHANGE_DURATION: return { ...state, duration: payload.duration ?? state.duration, - } + }; default: - return state + return state; } } -export default configReducer +export default configReducer; diff --git a/src/store/typing/action.ts b/src/store/typing/action.ts index c85afec..17ef9ce 100644 --- a/src/store/typing/action.ts +++ b/src/store/typing/action.ts @@ -1,11 +1,11 @@ -import { Typing } from "utils/store" +import type { Typing } from "utils/store"; -export const INITIALIZE_TYPING_STORE = 'INITIALIZE_TYPING_STORE' -export const UPDATE_WORD = 'UPDATE_WORD' -export const GOTO_NEXT_WORD = 'GOTO_NEXT_WORD' -export const REFRESH_TYPING_STORE = 'REFRESH_TYPING_STORE' -export const START_TYPING = 'START_TYPING' -export const DONE_TYPING = 'DONE_TYPING' +export const INITIALIZE_TYPING_STORE = "INITIALIZE_TYPING_STORE"; +export const UPDATE_WORD = "UPDATE_WORD"; +export const GOTO_NEXT_WORD = "GOTO_NEXT_WORD"; +export const REFRESH_TYPING_STORE = "REFRESH_TYPING_STORE"; +export const START_TYPING = "START_TYPING"; +export const DONE_TYPING = "DONE_TYPING"; const action = { INITIALIZE_TYPING_STORE, @@ -17,6 +17,6 @@ const action = { } as const; export type ActionType = keyof typeof action; -export type Action = { type: ActionType, payload?: Partial } +export type Action = { type: ActionType; payload?: Partial }; -export default action +export default action; diff --git a/src/store/typing/index.tsx b/src/store/typing/index.tsx index 09443a5..2a63b45 100644 --- a/src/store/typing/index.tsx +++ b/src/store/typing/index.tsx @@ -1,20 +1,29 @@ -import { createContext, Dispatch, useContext, useEffect, useReducer } from 'react' -import typingReducer from './reducer' -import { createTypingStore, Typing } from '../../utils/store' -import { Action, ActionType, INITIALIZE_TYPING_STORE } from './action' -import { Language } from 'utils/constant' - -const initialStore = createTypingStore({}) +import { + type Dispatch, + createContext, + useContext, + useEffect, + useReducer, +} from "react"; +import type { Language } from "utils/constant"; +import { type Typing, createTypingStore } from "utils/store"; +import { type Action, INITIALIZE_TYPING_STORE } from "./action"; +import typingReducer from "./reducer"; + +const initialStore = createTypingStore({}); interface TypingContext { typing: Typing; dispatch: Dispatch; } -export const TypingStoreContext = createContext(null) +export const TypingStoreContext = createContext(null); -export const TypingStoreProvider = ({ children, lang }: React.PropsWithChildren<{ lang: Language }>) => { - const [typing, dispatch] = useReducer(typingReducer, initialStore) +export const TypingStoreProvider = ({ + children, + lang, +}: React.PropsWithChildren<{ lang: Language }>) => { + const [typing, dispatch] = useReducer(typingReducer, initialStore); useEffect(() => { fetchLanguageJSON(lang) @@ -22,30 +31,32 @@ export const TypingStoreProvider = ({ children, lang }: React.PropsWithChildren< dispatch({ type: INITIALIZE_TYPING_STORE, payload: { languageJSON }, - }) + }); }) .catch((e) => { - console.log(e) - }) - }, [lang]) + console.log(e); + }); + }, [lang]); return ( {children} - ) -} + ); +}; const useTypingStore = () => { const ctx = useContext(TypingStoreContext); if (ctx === null) { - throw new Error("`useTypingStore` must be used inside `TypingStoreProvider`") + throw new Error( + "`useTypingStore` must be used inside `TypingStoreProvider`", + ); } - return ctx -} -export default useTypingStore + return ctx; +}; +export default useTypingStore; async function fetchLanguageJSON(lang: Language) { - const response = await fetch(`/words/lang/${lang}.json`) - return await response.json() + const response = await fetch(`/words/lang/${lang}.json`); + return await response.json(); } diff --git a/src/store/typing/reducer.ts b/src/store/typing/reducer.ts index 38b25dd..512e396 100644 --- a/src/store/typing/reducer.ts +++ b/src/store/typing/reducer.ts @@ -1,33 +1,38 @@ -import actionType, { Action } from './action' -import Word, { createWordSequence } from '../../utils/Word' -import Letter from '../../utils/Letter' -import { getStatistics } from '../../utils/statistics' -import { createTypingStore, Typing } from '../../utils/store' -import { Reducer } from 'react' - -function replaceItemInArray(originalArr: Array, index: number, newItem: T) { - return originalArr.map((item, i) => (i === index ? newItem : item)) +import type { Reducer } from "react"; +import Letter from "../../utils/Letter"; +import type Word from "../../utils/Word"; +import { createWordSequence } from "../../utils/Word"; +import { getStatistics } from "../../utils/statistics"; +import { type Typing, createTypingStore } from "../../utils/store"; +import actionType, { type Action } from "./action"; + +function replaceItemInArray( + originalArr: Array, + index: number, + newItem: T, +) { + return originalArr.map((item, i) => (i === index ? newItem : item)); } function hideWord(word: Word) { return { ...word, show: false, - } + }; } function markAsTyped(word: Word) { return { ...word, isTyped: true, - } + }; } const typingReducer: Reducer = (state, { type, payload }) => { - const [wordIndex, letterIndex] = state.caretPosition - const activeWord = state.wordSequence[wordIndex] - const N_WORD_TOBE_GENERATED = 20 //initial word count - const APPEND_WORD_TRESHOLD = 20 //minimal word has not been typed which new words will be generated (appended) + const [wordIndex, letterIndex] = state.caretPosition; + const activeWord = state.wordSequence[wordIndex]; + const N_WORD_TOBE_GENERATED = 20; //initial word count + const APPEND_WORD_TRESHOLD = 20; //minimal word has not been typed which new words will be generated (appended) switch (type) { case actionType.INITIALIZE_TYPING_STORE: { @@ -35,94 +40,95 @@ const typingReducer: Reducer = (state, { type, payload }) => { languageJSON: payload?.languageJSON, wordSequence: createWordSequence( payload?.languageJSON?.words || [], - N_WORD_TOBE_GENERATED + N_WORD_TOBE_GENERATED, ), - }) + }); } case actionType.START_TYPING: return { ...state, - typingStatus: 'started', + typingStatus: "started", startTime: Date.now(), - inputValue: '', - } + inputValue: "", + }; case actionType.DONE_TYPING: return { ...state, - typingStatus: 'done', + typingStatus: "done", finishTime: Date.now(), - } + }; case actionType.UPDATE_WORD: { - if (state.wordSequence.length <= 0) return state + if (state.wordSequence.length <= 0) return state; - const inputValueArr = payload?.inputValue?.split('') || [] + const inputValueArr = payload?.inputValue?.split("") || []; const originalLetterSeq = activeWord.letterSequence.filter( - (l) => l.original - ) + (l) => l.original, + ); let updatedLetterSeq = originalLetterSeq.map( - (l, i) => new Letter(l.original, inputValueArr[i]) - ) + (l, i) => new Letter(l.original, inputValueArr[i]), + ); - const extraLetters = inputValueArr.slice(activeWord.originalWord.length) + const extraLetters = inputValueArr.slice(activeWord.originalWord.length); updatedLetterSeq = [ ...updatedLetterSeq, ...extraLetters.map((l) => new Letter(null, l)), - ] + ]; const updatedActiveWord = { ...activeWord, letterSequence: updatedLetterSeq, - } + }; return { ...state, wordSequence: replaceItemInArray( state.wordSequence, wordIndex, - updatedActiveWord + updatedActiveWord, ), caretPosition: [wordIndex, inputValueArr.length], - inputValue: payload?.inputValue || '', - } + inputValue: payload?.inputValue || "", + }; } case actionType.GOTO_NEXT_WORD: { // if cursor at first letter of word, do nothing - if (letterIndex === 0) return state + if (letterIndex === 0) return state; - let updatedWord = markAsTyped(activeWord) + const updatedWord = markAsTyped(activeWord); let updatedWordSeq = replaceItemInArray( state.wordSequence, wordIndex, - updatedWord - ) + updatedWord, + ); // if new line detected, hide all word in previous line - const nextWord = state.wordSequence[wordIndex + 1] + const nextWord = state.wordSequence[wordIndex + 1]; if (activeWord.elRef.current && nextWord) { - const activeWordY = activeWord.elRef.current.getBoundingClientRect().y - const nextWordY = nextWord.elRef.current.getBoundingClientRect().y + const activeWordY = activeWord.elRef.current.getBoundingClientRect().y; + const nextWordY = nextWord.elRef.current.getBoundingClientRect().y; // if new line if (activeWordY !== nextWordY) { updatedWordSeq = state.wordSequence.map((w, i) => { - if (i <= wordIndex) return hideWord(w) - return w - }) + if (i <= wordIndex) return hideWord(w); + return w; + }); // if running out of word, generate and append new sequence of word - const remaining_word_count = state.wordSequence.length - wordIndex + 1 + const remaining_word_count = + state.wordSequence.length - wordIndex + 1; if (remaining_word_count < APPEND_WORD_TRESHOLD) { const newlyGeneratedWordSeq = createWordSequence( state.languageJSON?.words || [], - N_WORD_TOBE_GENERATED - ) - updatedWordSeq = [...updatedWordSeq, ...newlyGeneratedWordSeq] + N_WORD_TOBE_GENERATED, + ); + updatedWordSeq = [...updatedWordSeq, ...newlyGeneratedWordSeq]; } } } @@ -132,9 +138,9 @@ const typingReducer: Reducer = (state, { type, payload }) => { ...state, wordSequence: updatedWordSeq, caretPosition: [wordIndex + 1, 0], - inputValue: '', + inputValue: "", statistics: getStatistics(updatedWordSeq, payload?.typingMinutes || -1), - } + }; } case actionType.REFRESH_TYPING_STORE: { @@ -142,14 +148,14 @@ const typingReducer: Reducer = (state, { type, payload }) => { languageJSON: state.languageJSON, wordSequence: createWordSequence( state.languageJSON?.words || [], - N_WORD_TOBE_GENERATED + N_WORD_TOBE_GENERATED, ), - }) + }); } default: - return state + return state; } -} +}; -export default typingReducer +export default typingReducer; diff --git a/src/theme/colors.ts b/src/theme/colors.ts index 22493e7..e24238f 100644 --- a/src/theme/colors.ts +++ b/src/theme/colors.ts @@ -1,68 +1,68 @@ -import { createColorTheme } from '../utils/theme' +import { createColorTheme } from "../utils/theme"; const light = createColorTheme({ - background: '#fcfcfc', - text: '#060606', - untypedLetter: '#999999', - correctLetter: '#000', - incorrectLetter: '#ff4242', - extraLetter: '#999999', - typingBackground: '#f5f5f5', - caret: '#000000', - secondaryText: 'text', -}) + background: "#fcfcfc", + text: "#060606", + untypedLetter: "#999999", + correctLetter: "#000", + incorrectLetter: "#ff4242", + extraLetter: "#999999", + typingBackground: "#f5f5f5", + caret: "#000000", + secondaryText: "text", +}); const oneDarkPro = createColorTheme({ - background: '#21252B', - typingBackground: '#282C34', - text: '#999999', - untypedLetter: '#C678DD', - incorrectLetter: '#E5C07B', - correctLetter: '#69B679', - extraLetter: '#ABB0B2', - caret: '#528BFF', -}) + background: "#21252B", + typingBackground: "#282C34", + text: "#999999", + untypedLetter: "#C678DD", + incorrectLetter: "#E5C07B", + correctLetter: "#69B679", + extraLetter: "#ABB0B2", + caret: "#528BFF", +}); const monokai = createColorTheme({ - background: '#1E1F1C', - text: '#B67534', - typingBackground: '#272822', - correctLetter: '#fff', - incorrectLetter: '#D72F37', - caret: '#fff', -}) + background: "#1E1F1C", + text: "#B67534", + typingBackground: "#272822", + correctLetter: "#fff", + incorrectLetter: "#D72F37", + caret: "#fff", +}); const pink = createColorTheme({ - background: '#181818', - typingBackground: '#202020', - text: '#fd778d', - untypedLetter: '#ffd3da', - correctLetter: '#fd778d', - incorrectLetter: '#ffc710', - extraLetter: '#fdcddf', -}) + background: "#181818", + typingBackground: "#202020", + text: "#fd778d", + untypedLetter: "#ffd3da", + correctLetter: "#fd778d", + incorrectLetter: "#ffc710", + extraLetter: "#fdcddf", +}); const colors = { - text: '#fff', - secondaryText: 'grayText', - background: '#060606', - primary: '#3cf', - secondary: '#e0f', - muted: '#191919', - highlight: '#29112c', - gray: '#999', - purple: '#c0f', - correctLetter: '#55ec47', - incorrectLetter: '#F07178', - extraLetter: '#f0c471', - untypedLetter: 'grayText', - typingBackground: '#181717', + text: "#fff", + secondaryText: "grayText", + background: "#060606", + primary: "#3cf", + secondary: "#e0f", + muted: "#191919", + highlight: "#29112c", + gray: "#999", + purple: "#c0f", + correctLetter: "#55ec47", + incorrectLetter: "#F07178", + extraLetter: "#f0c471", + untypedLetter: "grayText", + typingBackground: "#181717", modes: { light, oneDarkPro, monokai, pink, }, -} +}; -export default colors +export default colors; diff --git a/src/theme/index.ts b/src/theme/index.ts index 221bde6..a132579 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -1,14 +1,13 @@ -import colors from './colors' -import styles from './styles' +import colors from "./colors"; +import styles from "./styles"; const theme = { colors, styles, fonts: { - body: - 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif', - heading: 'inherit', - monospace: 'Fira Code, Menlo, monospace', + body: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif', + heading: "inherit", + monospace: "Fira Code, Menlo, monospace", }, fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 72], fontWeights: { @@ -22,22 +21,22 @@ const theme = { }, textStyles: { heading: { - fontFamily: 'heading', - fontWeight: 'heading', - lineHeight: 'heading', + fontFamily: "heading", + fontWeight: "heading", + lineHeight: "heading", }, display: { - variant: 'textStyles.heading', + variant: "textStyles.heading", fontSize: [5, 6], - fontWeight: 'display', - letterSpacing: '-0.03em', + fontWeight: "display", + letterSpacing: "-0.03em", mt: 3, }, }, sizes: { container: [260, 640, 800], }, - breakpoints: ['576px', '768px', '992px', '1200px'], -} + breakpoints: ["576px", "768px", "992px", "1200px"], +}; -export default theme +export default theme; diff --git a/src/theme/styles.ts b/src/theme/styles.ts index 8a470fa..4cc6940 100644 --- a/src/theme/styles.ts +++ b/src/theme/styles.ts @@ -1,32 +1,32 @@ const styles = { root: { - fontFamily: 'body', - lineHeight: 'body', - fontWeight: 'body', + fontFamily: "body", + lineHeight: "body", + fontWeight: "body", }, h1: { - variant: 'textStyles.display', + variant: "textStyles.display", }, h2: { - variant: 'textStyles.heading', + variant: "textStyles.heading", fontSize: 5, }, h3: { - variant: 'textStyles.heading', + variant: "textStyles.heading", fontSize: 4, }, h4: { - variant: 'textStyles.heading', + variant: "textStyles.heading", fontSize: 3, }, h5: { - variant: 'textStyles.heading', + variant: "textStyles.heading", fontSize: 2, }, h6: { - variant: 'textStyles.heading', + variant: "textStyles.heading", fontSize: 1, }, -} +}; -export default styles +export default styles; diff --git a/src/utils/Letter.ts b/src/utils/Letter.ts index 9d47d30..a2e0420 100644 --- a/src/utils/Letter.ts +++ b/src/utils/Letter.ts @@ -1,8 +1,11 @@ -export function getStatus({ original, typed }: { original?: string | null; typed?: string | null }) { - if (original && !typed) return 'untyped' - if (original && typed) return typed === original ? 'correct' : 'incorrect' - if (!original && typed) return 'extra' - return null +export function getStatus({ + original, + typed, +}: { original?: string | null; typed?: string | null }) { + if (original && !typed) return "untyped"; + if (original && typed) return typed === original ? "correct" : "incorrect"; + if (!original && typed) return "extra"; + return null; } class Letter { @@ -17,4 +20,4 @@ class Letter { } } -export default Letter +export default Letter; diff --git a/src/utils/Word.ts b/src/utils/Word.ts index 7f7e2b9..5a7c50e 100644 --- a/src/utils/Word.ts +++ b/src/utils/Word.ts @@ -1,11 +1,11 @@ -import { createRef, RefObject } from 'react' -import { sampleSize } from 'lodash' -import Letter from './Letter' +import { sampleSize } from "lodash"; +import { type RefObject, createRef } from "react"; +import Letter from "./Letter"; class Word { key: string; originalWord: string; - elRef: RefObject; + elRef: RefObject; show: boolean; letterSequence: Letter[]; isTyped: boolean; @@ -15,7 +15,7 @@ class Word { this.originalWord = originalWord; this.elRef = createRef(); this.show = true; - this.letterSequence = originalWord.split('').map(c => new Letter(c)); + this.letterSequence = originalWord.split("").map((c) => new Letter(c)); this.isTyped = false; } } @@ -23,13 +23,15 @@ class Word { export function isCorrectlyTyped(word: Word) { return word.letterSequence .map((letter) => letter.status) - .every((status) => status === 'correct') + .every((status) => status === "correct"); } export function createWordSequence(arrOfString: string[], n?: number) { - if (n) arrOfString = sampleSize(arrOfString, n) + if (n) { + return sampleSize(arrOfString, n).map((s) => new Word(s)); + } - return arrOfString.map((s, i) => new Word(s)) + return arrOfString.map((s) => new Word(s)); } -export default Word +export default Word; diff --git a/src/utils/__test__/Letter.test.ts b/src/utils/__test__/Letter.test.ts index d67d3f0..d549d83 100644 --- a/src/utils/__test__/Letter.test.ts +++ b/src/utils/__test__/Letter.test.ts @@ -1,11 +1,11 @@ -import { describe, expect } from 'vitest'; -import { getStatus } from 'utils/Letter' +import { getStatus } from "utils/Letter"; +import { describe, expect } from "vitest"; -describe('getStatus function', () => { - test('getStatus', () => { - expect(getStatus({ original: 'a', typed: 'b' })).toEqual('incorrect') - expect(getStatus({ original: 'a', typed: 'a' })).toEqual('correct') - expect(getStatus({ original: 'a' })).toEqual('untyped') - expect(getStatus({ typed: 'b' })).toEqual('extra') - }) -}) +describe("getStatus function", () => { + test("getStatus", () => { + expect(getStatus({ original: "a", typed: "b" })).toEqual("incorrect"); + expect(getStatus({ original: "a", typed: "a" })).toEqual("correct"); + expect(getStatus({ original: "a" })).toEqual("untyped"); + expect(getStatus({ typed: "b" })).toEqual("extra"); + }); +}); diff --git a/src/utils/__test__/Word.test.ts b/src/utils/__test__/Word.test.ts index db2efb1..7ac2813 100644 --- a/src/utils/__test__/Word.test.ts +++ b/src/utils/__test__/Word.test.ts @@ -1,29 +1,29 @@ -import { describe, expect } from 'vitest'; -import Letter from 'utils/Letter' -import Word, { isCorrectlyTyped, createWordSequence } from 'utils/Word' +import Letter from "utils/Letter"; +import Word, { createWordSequence, isCorrectlyTyped } from "utils/Word"; +import { describe, expect } from "vitest"; -describe('isCorrectlyTyped function', () => { - test('isCorrectlyTyped is true', () => { - const word = new Word('javascript') - const letterSequence = 'javascript' - .split('') - .map(char => new Letter(char, char)) - word.letterSequence = letterSequence - expect(isCorrectlyTyped(word)).toEqual(true) - }) +describe("isCorrectlyTyped function", () => { + test("isCorrectlyTyped is true", () => { + const word = new Word("javascript"); + const letterSequence = "javascript" + .split("") + .map((char) => new Letter(char, char)); + word.letterSequence = letterSequence; + expect(isCorrectlyTyped(word)).toEqual(true); + }); - test('isCorrectlyTyped is false', () => { - const word = new Word('javascript') - const letterSequence = 'javascript' - .split('') - .map(char => new Letter(char, 'a')) - word.letterSequence = letterSequence - expect(isCorrectlyTyped(word)).toEqual(false) - }) -}) + test("isCorrectlyTyped is false", () => { + const word = new Word("javascript"); + const letterSequence = "javascript" + .split("") + .map((char) => new Letter(char, "a")); + word.letterSequence = letterSequence; + expect(isCorrectlyTyped(word)).toEqual(false); + }); +}); -describe('createWordSequence function', () => { - test('should return correct length', () => { +describe("createWordSequence function", () => { + test("should return correct length", () => { const wordArr = ` Lorem ipsum dolor sit, amet consectetur adipisicing elit. Possimus magnam iste ratione ipsam laborum consequatur, doloribus @@ -31,9 +31,9 @@ describe('createWordSequence function', () => { Blanditiis, minus tenetur repellat accusantium provident incidunt reiciendis assumenda illo? - `.split(/[^a-zA-Z]+/) + `.split(/[^a-zA-Z]+/); - expect(createWordSequence(wordArr,10).length).toEqual(10) - expect(createWordSequence(wordArr).length).toEqual(wordArr.length) - }) -}) + expect(createWordSequence(wordArr, 10).length).toEqual(10); + expect(createWordSequence(wordArr).length).toEqual(wordArr.length); + }); +}); diff --git a/src/utils/__test__/statistics.test.ts b/src/utils/__test__/statistics.test.ts index c40be66..e9db583 100644 --- a/src/utils/__test__/statistics.test.ts +++ b/src/utils/__test__/statistics.test.ts @@ -1,166 +1,166 @@ -import { describe, expect } from 'vitest'; -import Letter from 'utils/Letter' +import Letter from "utils/Letter"; +import Word from "utils/Word"; import { + calculateAccuracy, + calculateWPM, getCorrectWordSequence, getIncorrectWordSequence, - calculateWPM, - calculateAccuracy, - getStatistics -} from 'utils/statistics' -import Word from 'utils/Word' + getStatistics, +} from "utils/statistics"; +import { describe, expect } from "vitest"; -describe('getCorrectWordSequence function', () => { - test('should always return an array', () => { +describe("getCorrectWordSequence function", () => { + test("should always return an array", () => { const wordSequence = [ - new Word('hello'), - new Word('world'), - new Word('I'), - new Word('love'), - new Word('javascript') - ] - - expect(Array.isArray(getCorrectWordSequence(wordSequence))).toEqual(true) - expect(Array.isArray(getCorrectWordSequence([]))).toEqual(true) - }) - - test('should return correct output and length', () => { + new Word("hello"), + new Word("world"), + new Word("I"), + new Word("love"), + new Word("javascript"), + ]; + + expect(Array.isArray(getCorrectWordSequence(wordSequence))).toEqual(true); + expect(Array.isArray(getCorrectWordSequence([]))).toEqual(true); + }); + + test("should return correct output and length", () => { const wordSequence = [ - new Word('hello'), - new Word('world'), - new Word('I'), - new Word('love'), - new Word('javascript') - ] - - for (let word of wordSequence) { - word.isTyped = true + new Word("hello"), + new Word("world"), + new Word("I"), + new Word("love"), + new Word("javascript"), + ]; + + for (const word of wordSequence) { + word.isTyped = true; } wordSequence[1].letterSequence = wordSequence[0].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) + (letter) => new Letter(letter.original, letter.original), + ); wordSequence[2].letterSequence = wordSequence[1].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) - - let correctSequence = getCorrectWordSequence(wordSequence) - expect(correctSequence[0]).toEqual(wordSequence[1]) - expect(correctSequence[1]).toEqual(wordSequence[2]) - expect(correctSequence.length).toEqual(2) - - wordSequence[1].isTyped = false - expect(getCorrectWordSequence(wordSequence)[0]).toEqual(wordSequence[2]) - expect(getCorrectWordSequence(wordSequence).length).toEqual(1) - }) -}) - -describe('getIncorrectWordSequence function', () => { - test('should always return an array', () => { + (letter) => new Letter(letter.original, letter.original), + ); + + const correctSequence = getCorrectWordSequence(wordSequence); + expect(correctSequence[0]).toEqual(wordSequence[1]); + expect(correctSequence[1]).toEqual(wordSequence[2]); + expect(correctSequence.length).toEqual(2); + + wordSequence[1].isTyped = false; + expect(getCorrectWordSequence(wordSequence)[0]).toEqual(wordSequence[2]); + expect(getCorrectWordSequence(wordSequence).length).toEqual(1); + }); +}); + +describe("getIncorrectWordSequence function", () => { + test("should always return an array", () => { const wordSequence = [ - new Word('hello'), - new Word('world'), - new Word('I'), - new Word('love'), - new Word('javascript') - ] - - expect(Array.isArray(getIncorrectWordSequence(wordSequence))).toEqual(true) - expect(Array.isArray(getIncorrectWordSequence([]))).toEqual(true) - }) - - test('getIncorrectWordSequence', () => { + new Word("hello"), + new Word("world"), + new Word("I"), + new Word("love"), + new Word("javascript"), + ]; + + expect(Array.isArray(getIncorrectWordSequence(wordSequence))).toEqual(true); + expect(Array.isArray(getIncorrectWordSequence([]))).toEqual(true); + }); + + test("getIncorrectWordSequence", () => { const wordSequence = [ - new Word('hello'), - new Word('world'), - new Word('I'), - new Word('love'), - new Word('javascript') - ] - - for (let word of wordSequence) { - word.isTyped = true + new Word("hello"), + new Word("world"), + new Word("I"), + new Word("love"), + new Word("javascript"), + ]; + + for (const word of wordSequence) { + word.isTyped = true; } wordSequence[1].letterSequence = wordSequence[0].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) + (letter) => new Letter(letter.original, letter.original), + ); wordSequence[2].letterSequence = wordSequence[1].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) + (letter) => new Letter(letter.original, letter.original), + ); wordSequence[4].letterSequence = wordSequence[1].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) + (letter) => new Letter(letter.original, letter.original), + ); - let incorrectSequence = getIncorrectWordSequence(wordSequence) - expect(incorrectSequence[0]).toEqual(wordSequence[0]) + const incorrectSequence = getIncorrectWordSequence(wordSequence); + expect(incorrectSequence[0]).toEqual(wordSequence[0]); expect(incorrectSequence[incorrectSequence.length - 1]).toEqual( - wordSequence[3] - ) - expect(incorrectSequence.length).toEqual(2) + wordSequence[3], + ); + expect(incorrectSequence.length).toEqual(2); - wordSequence[0].isTyped = false - expect(getIncorrectWordSequence(wordSequence).length).toEqual(1) - }) -}) + wordSequence[0].isTyped = false; + expect(getIncorrectWordSequence(wordSequence).length).toEqual(1); + }); +}); -describe('calculateWPM function', () => { - test('should always return a number', () => { - expect(calculateWPM([], 0)).toBeNaN() - expect(calculateWPM([], -1)).toBeNaN() - expect(calculateWPM([], 10)).toEqual(0) +describe("calculateWPM function", () => { + test("should always return a number", () => { + expect(calculateWPM([], 0)).toBeNaN(); + expect(calculateWPM([], -1)).toBeNaN(); + expect(calculateWPM([], 10)).toEqual(0); const wordSequence = [ - new Word('hello'), - new Word('world'), - new Word('I'), - new Word('love'), - new Word('javascript') - ] - - for (let word of wordSequence) { - word.isTyped = true + new Word("hello"), + new Word("world"), + new Word("I"), + new Word("love"), + new Word("javascript"), + ]; + + for (const word of wordSequence) { + word.isTyped = true; } - expect(calculateWPM(wordSequence, 60)).toBeGreaterThan(0) + expect(calculateWPM(wordSequence, 60)).toBeGreaterThan(0); wordSequence[0].letterSequence = wordSequence[0].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) + (letter) => new Letter(letter.original, letter.original), + ); - expect(calculateWPM(wordSequence, 60)).toBeGreaterThan(0) - }) -}) + expect(calculateWPM(wordSequence, 60)).toBeGreaterThan(0); + }); +}); -describe('calculateAccuary function', () => { - test('should always return a number', () => { +describe("calculateAccuary function", () => { + test("should always return a number", () => { const wordSequence = [ - new Word('hello'), - new Word('world'), - new Word('I'), - new Word('love'), - new Word('javascript') - ] - - for (let word of wordSequence) { - word.isTyped = true + new Word("hello"), + new Word("world"), + new Word("I"), + new Word("love"), + new Word("javascript"), + ]; + + for (const word of wordSequence) { + word.isTyped = true; } - expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0) + expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0); wordSequence[0].letterSequence = wordSequence[0].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) + (letter) => new Letter(letter.original, letter.original), + ); - expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0) - }) -}) + expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0); + }); +}); -describe('getStatistics function', () => { - test('should be numbers', () => { +describe("getStatistics function", () => { + test("should be numbers", () => { const output = Object.values(getStatistics([], 0)) - .map(val => typeof val) - .every(type => type === 'number') + .map((val) => typeof val) + .every((type) => type === "number"); - expect(output).toEqual(true) - }) -}) + expect(output).toEqual(true); + }); +}); diff --git a/src/utils/constant.ts b/src/utils/constant.ts index 98e5f37..b98ee49 100644 --- a/src/utils/constant.ts +++ b/src/utils/constant.ts @@ -1,36 +1,36 @@ export const language = { - id: 'id', - en: 'en', -} as const + id: "id", + en: "en", +} as const; export const mode = { - time: 'time', - word: 'word', -} as const + time: "time", + word: "word", +} as const; export const duration = { - '30s': 30, - '60s': 60, -} as const + "30s": 30, + "60s": 60, +} as const; export const theme = { - default: 'Dark', - light: 'Light', - oneDarkPro: 'One Dark Pro', - monokai: 'Monokai', - pink: 'Pink', -} as const + default: "Dark", + light: "Light", + oneDarkPro: "One Dark Pro", + monokai: "Monokai", + pink: "Pink", +} as const; const constant = { language, mode, duration, theme, -} +}; export type Language = keyof typeof language; export type Mode = keyof typeof mode; export type Theme = keyof typeof theme; export type Duration = keyof typeof duration; -export default constant +export default constant; diff --git a/src/utils/statistics.ts b/src/utils/statistics.ts index 40289d1..22e1f11 100644 --- a/src/utils/statistics.ts +++ b/src/utils/statistics.ts @@ -1,71 +1,72 @@ -import Word, { isCorrectlyTyped } from '../utils/Word' +import type Word from "../utils/Word"; +import { isCorrectlyTyped } from "../utils/Word"; -export function getCorrectWordSequence (wordSequence: Array) { +export function getCorrectWordSequence(wordSequence: Array) { if (!Array.isArray(wordSequence)) { - return [] + return []; } - const typedWordSeq = wordSequence.filter(word => word?.isTyped) - return typedWordSeq.filter(word => isCorrectlyTyped(word)) + const typedWordSeq = wordSequence.filter((word) => word?.isTyped); + return typedWordSeq.filter((word) => isCorrectlyTyped(word)); } -export function getIncorrectWordSequence (wordSequence: Array) { +export function getIncorrectWordSequence(wordSequence: Array) { if (!Array.isArray(wordSequence)) { - return [] + return []; } - const typedWordSeq = wordSequence.filter(word => word?.isTyped) - return typedWordSeq.filter(word => !isCorrectlyTyped(word)) + const typedWordSeq = wordSequence.filter((word) => word?.isTyped); + return typedWordSeq.filter((word) => !isCorrectlyTyped(word)); } -export function calculateWPM (wordSequence: Array, seconds: number) { +export function calculateWPM(wordSequence: Array, seconds: number) { if (!Array.isArray(wordSequence) || seconds < 0) { - return NaN + return Number.NaN; } - const typedWordSeq = wordSequence.filter(word => word?.isTyped) - const spacesCount = typedWordSeq.length ? typedWordSeq.length - 1 : 0 + const typedWordSeq = wordSequence.filter((word) => word?.isTyped); + const spacesCount = typedWordSeq.length ? typedWordSeq.length - 1 : 0; const correctlyTypedLetters = getCorrectWordSequence(wordSequence) - .map(word => word?.originalWord || '') - .join('') + .map((word) => word?.originalWord || "") + .join(""); return Math.round( - (spacesCount + correctlyTypedLetters.length) / 5 / (seconds / 60) - ) + (spacesCount + correctlyTypedLetters.length) / 5 / (seconds / 60), + ); } -export function calculateAccuracy (wordSequence: Array): number { +export function calculateAccuracy(wordSequence: Array): number { if (!Array.isArray(wordSequence)) { - return NaN + return Number.NaN; } - const typedWordSeq = wordSequence.filter(word => word?.isTyped) - const spacesCount = typedWordSeq.length - 1 + const typedWordSeq = wordSequence.filter((word) => word?.isTyped); + const spacesCount = typedWordSeq.length - 1; const totalKeys = spacesCount + typedWordSeq - .map(word => word?.letterSequence?.length || 0) - .reduce((acc, curr) => acc + curr, 0) + .map((word) => word?.letterSequence?.length || 0) + .reduce((acc, curr) => acc + curr, 0); const correctKeys = spacesCount + typedWordSeq - .map(word => word.letterSequence) + .map((word) => word.letterSequence) .map( - letterSeq => - letterSeq.filter(letter => letter.status === 'correct').length + (letterSeq) => + letterSeq.filter((letter) => letter.status === "correct").length, ) - .reduce((acc, curr) => acc + curr, 0) + .reduce((acc, curr) => acc + curr, 0); - return Math.round((correctKeys / totalKeys) * 100) / 100 + return Math.round((correctKeys / totalKeys) * 100) / 100; } -export function getStatistics (wordSequence: Array, seconds: number) { +export function getStatistics(wordSequence: Array, seconds: number) { return { wpm: calculateWPM(wordSequence, seconds), accuracy: calculateAccuracy(wordSequence), correctWords: getCorrectWordSequence(wordSequence).length, incorrectWords: getIncorrectWordSequence(wordSequence).length, - time: seconds || NaN - } + time: seconds || Number.NaN, + }; } diff --git a/src/utils/store.ts b/src/utils/store.ts index b406ca2..f0ce31c 100644 --- a/src/utils/store.ts +++ b/src/utils/store.ts @@ -1,5 +1,5 @@ -import { Language, Mode, Theme } from "./constant"; -import Word from "./Word"; +import type Word from "./Word"; +import type { Language, Mode, Theme } from "./constant"; export interface Config { lang: Language; @@ -11,7 +11,7 @@ export interface Config { interface LanguageJSON { lang: string; name: string; - words: string[] + words: string[]; } export interface Typing { @@ -25,19 +25,19 @@ export interface Typing { correctWords: number; incorrectWords: number; }; - typingStatus: 'pending' | 'done' | 'started'; + typingStatus: "pending" | "done" | "started"; typingMinutes?: number; startTime: number | null; finishTime: number | null; } export function createTypingStore({ - inputValue = '', + inputValue = "", languageJSON = null, wordSequence = [], caretPosition = [0, 0], statistics = { accuracy: 0, correctWords: 0, incorrectWords: 0, wpm: 0 }, - typingStatus = 'pending', + typingStatus = "pending", startTime = null, finishTime = null, }: Partial): Typing { @@ -50,19 +50,19 @@ export function createTypingStore({ typingStatus, startTime, finishTime, - } + }; } export function createConfigStore({ - lang = 'id', - mode = 'time', + lang = "id", + mode = "time", duration = 60, - theme = 'default' + theme = "default", }: Partial): Config { return { lang, mode, duration, - theme - } + theme, + }; } diff --git a/src/utils/theme.ts b/src/utils/theme.ts index 5372c4c..0a41d92 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -1,3 +1,14 @@ +interface ColorTheme { + background: string; + text: string; + typingBackground: string; + untypedLetter: string; + correctLetter: string; + incorrectLetter: string; + extraLetter: string; + caret: string; +} + export function createColorTheme({ background, text, @@ -7,7 +18,7 @@ export function createColorTheme({ incorrectLetter, extraLetter, caret, -}: any) { +}: ColorTheme) { return { background, text, @@ -17,5 +28,5 @@ export function createColorTheme({ incorrectLetter, extraLetter, caret, - } + }; } diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..7cb7e37 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,77 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: [ + './pages/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './src/**/*.{ts,tsx}', + ], + prefix: "", + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} \ No newline at end of file From 82d700d07de51880987e66999233e99ba812d82d Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Fri, 19 Jul 2024 13:29:23 +0700 Subject: [PATCH 05/12] wip revamp: statistics & lang selector --- src/App.tsx | 142 +++++++++++++++++------------------ src/components/Statistic.tsx | 43 +++++------ src/utils/constant.ts | 5 ++ 3 files changed, 97 insertions(+), 93 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 0693e21..5d80a5c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,21 @@ +import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; +import { CheckIcon } from "@radix-ui/react-icons"; import Statistic from "components/Statistic"; import Timer from "components/Timer"; import TypingArea from "components/TypingArea"; import type React from "react"; import { useCallback } from "react"; -import { Box, Flex, Heading, Link, Select } from "theme-ui"; +import { Select } from "theme-ui"; import useConfigStore from "./store/config"; import { CHANGE_LANGUAGE } from "./store/config/action"; import { TypingStoreProvider } from "./store/typing"; -import { type Language, type Theme, language, theme } from "./utils/constant"; +import { + type Language, + type Theme, + language, + languageFlagMoji, + theme, +} from "./utils/constant"; const App = () => { const { config, dispatch, setTheme } = useConfigStore(); @@ -22,81 +30,82 @@ const App = () => { ); const handleSelectLanguageChange = useCallback( - (e: React.ChangeEvent) => { - const selectedValue = e.target.options[e.target.selectedIndex] - .value as Language; + (value: string) => { dispatch({ type: CHANGE_LANGUAGE, - payload: { lang: selectedValue }, + payload: { lang: value as Language }, }); }, [dispatch], ); return ( - - - Typefaster - - +
+
+

Typefaster

+ + + + + + + + 🇮🇩 Bahasa Indonesia + + + + + + 🇬🇧 English + + + + + + + +
+
- +
- +
- - - +
+
+
© 2020 / Dandi Wiratsangka /{" "} - Github - - - + +
+
- - - - +
+
+
); }; diff --git a/src/components/Statistic.tsx b/src/components/Statistic.tsx index 8f716fd..d366336 100644 --- a/src/components/Statistic.tsx +++ b/src/components/Statistic.tsx @@ -1,12 +1,17 @@ -/** @jsxRuntime classic */ -/** @jsx jsx */ -import { jsx } from "theme-ui"; -import { Flex } from "theme-ui"; import useTypingStore from "../store/typing"; -const ListItem = (props: React.PropsWithChildren) => ( -
-); +const ListItem = ({ + title, + children, + ...props +}: React.PropsWithChildren<{ title: string }>) => { + return ( +
+
{title}
+
{children}
+
+ ); +}; const Statistic = () => { const { typing } = useTypingStore(); @@ -14,23 +19,19 @@ const Statistic = () => { const { accuracy, wpm, correctWords, incorrectWords } = statistics; return ( - - WPM : {wpm >= 0 ? wpm : "-"} - Acc : {accuracy >= 0 ? accuracy : "-"} - - Correct words : {correctWords >= 0 ? correctWords : "-"} +
+ {wpm >= 0 ? wpm : "-"} + + {accuracy >= 0 ? `${accuracy * 100}%` : "-"} + + + {correctWords >= 0 ? correctWords : "-"} - - Incorrect words: {incorrectWords >= 0 ? incorrectWords : "-"} + + {incorrectWords >= 0 ? incorrectWords : "-"} {/* Time: {time >= 0 ? time : '-'} */} - +
); }; diff --git a/src/utils/constant.ts b/src/utils/constant.ts index b98ee49..61d5fdb 100644 --- a/src/utils/constant.ts +++ b/src/utils/constant.ts @@ -3,6 +3,11 @@ export const language = { en: "en", } as const; +export const languageFlagMoji = { + id: "🇮🇩", + en: "🇬🇧", +}; + export const mode = { time: "time", word: "word", From a88536ce270a191e55c7c625097434674596fe72 Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Fri, 19 Jul 2024 13:57:44 +0700 Subject: [PATCH 06/12] fix input on mobile --- src/App.tsx | 2 +- src/components/TypingArea.tsx | 14 ++++++-------- src/store/typing/reducer.ts | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 5d80a5c..4c7951d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,7 +40,7 @@ const App = () => { ); return ( -
+

Typefaster

diff --git a/src/components/TypingArea.tsx b/src/components/TypingArea.tsx index dbc2231..3de9f92 100644 --- a/src/components/TypingArea.tsx +++ b/src/components/TypingArea.tsx @@ -1,7 +1,7 @@ import type React from "react"; import { memo, useCallback, useEffect, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; -import { Box, Flex, Input, type SxStyleProp } from "theme-ui"; +import { Box, Flex, type SxStyleProp } from "theme-ui"; import useTypingStore from "../store/typing"; import actionType from "../store/typing/action"; import Hotkey from "./Hotkey"; @@ -133,7 +133,7 @@ const TypingArea = memo(() => { whiteSpace: "pre-wrap", }} > - { setBlur(true); }} onFocus={() => setBlur(false)} - autoFocus - sx={{ - width: 10, - position: "absolute", - opacity: 0, - }} + className="w-10 opacity-0 absolute" onKeyDown={handleKeyDown} disabled={typing.typingStatus === "done"} + autoCapitalize="off" + // biome-ignore lint/a11y/noAutofocus: + autoFocus /> {typing.wordSequence.map((w, i) => ( = (state, { type, payload }) => { const nextWord = state.wordSequence[wordIndex + 1]; if (activeWord.elRef.current && nextWord) { const activeWordY = activeWord.elRef.current.getBoundingClientRect().y; - const nextWordY = nextWord.elRef.current.getBoundingClientRect().y; + const nextWordY = nextWord.elRef.current?.getBoundingClientRect().y; // if new line if (activeWordY !== nextWordY) { From bc456284acd9b03a8ec0fb7f959cd891df9d5f66 Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Mon, 22 Jul 2024 16:18:19 +0700 Subject: [PATCH 07/12] migrate theme-ui to tailwindcss --- index.html | 2 +- src/App.tsx | 11 ++- src/components/Hotkey.tsx | 19 +--- src/components/Letter.tsx | 34 ++++--- src/components/MockThemeUI.tsx | 9 -- src/components/Timer.tsx | 12 +-- src/components/TypingArea.tsx | 62 ++++--------- src/components/Word.tsx | 23 ++--- src/components/__tests__/Letter.test.tsx | 21 +---- src/components/__tests__/Word.test.tsx | 29 ++---- src/index.css | 107 +++++++++++------------ src/index.tsx | 26 +----- src/store/config/index.tsx | 6 +- src/store/config/reducer.ts | 12 +++ src/theme/colors.ts | 68 -------------- src/theme/index.ts | 42 --------- src/theme/styles.ts | 32 ------- src/utils/constant.ts | 2 +- tailwind.config.js | 70 ++++----------- 19 files changed, 149 insertions(+), 438 deletions(-) delete mode 100644 src/components/MockThemeUI.tsx delete mode 100644 src/theme/colors.ts delete mode 100644 src/theme/index.ts delete mode 100644 src/theme/styles.ts diff --git a/index.html b/index.html index 8bf1e1c..e816bce 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + diff --git a/src/App.tsx b/src/App.tsx index 4c7951d..0189ecf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,15 +18,20 @@ import { } from "./utils/constant"; const App = () => { - const { config, dispatch, setTheme } = useConfigStore(); + const { config, dispatch } = useConfigStore(); const handleSelectThemeChange = useCallback( (e: React.ChangeEvent) => { const selectedValue = e.target.options[e.target.selectedIndex] .value as Theme; - setTheme(selectedValue); + dispatch({ + type: "CHANGE_THEME", + payload: { + theme: selectedValue, + }, + }); }, - [setTheme], + [dispatch], ); const handleSelectLanguageChange = useCallback( diff --git a/src/components/Hotkey.tsx b/src/components/Hotkey.tsx index 3764c66..c646f46 100644 --- a/src/components/Hotkey.tsx +++ b/src/components/Hotkey.tsx @@ -1,23 +1,8 @@ -import { Box } from "theme-ui"; - const Hotkey = ({ children }: React.PropsWithChildren) => { return ( - + {children} - + ); }; diff --git a/src/components/Letter.tsx b/src/components/Letter.tsx index 513a435..d7fddd6 100644 --- a/src/components/Letter.tsx +++ b/src/components/Letter.tsx @@ -1,29 +1,27 @@ +import { cn } from "lib/utils"; import { memo } from "react"; -import { Text } from "theme-ui"; import type Letter from "utils/Letter"; +const letterColor: { [key: string]: string } = { + untyped: "text-untypedLetter", + correct: "text-correctLetter", + incorrect: "text-incorrectLetter", + extra: "text-extraLetter", +}; + const LetterComponent = memo( ({ letter, cursor }: { letter: Letter; cursor?: boolean }) => ( - {letter.typed || letter.original} - + ), (prevProps, nextProps) => prevProps.letter.typed === nextProps.letter.typed && diff --git a/src/components/MockThemeUI.tsx b/src/components/MockThemeUI.tsx deleted file mode 100644 index d415dc1..0000000 --- a/src/components/MockThemeUI.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type React from "react"; -import theme from "theme"; -import { ThemeProvider } from "theme-ui"; - -function MockThemeUI({ children }: React.PropsWithChildren) { - return {children}; -} - -export default MockThemeUI; diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx index 5765374..43d35e2 100644 --- a/src/components/Timer.tsx +++ b/src/components/Timer.tsx @@ -1,5 +1,4 @@ import { useEffect, useRef, useState } from "react"; -import { Flex } from "theme-ui"; import useTypingStore from "../store/typing"; import actionType from "../store/typing/action"; @@ -28,16 +27,9 @@ const Timer = ({ duration }: React.PropsWithChildren<{ duration: number }>) => { }, [typing.typingStatus, dispatch, duration]); return ( - +
{timerCount === 0 ? "Times up!" : timerCount} - +
); }; diff --git a/src/components/TypingArea.tsx b/src/components/TypingArea.tsx index 3de9f92..89a961c 100644 --- a/src/components/TypingArea.tsx +++ b/src/components/TypingArea.tsx @@ -1,7 +1,7 @@ +import { cn } from "lib/utils"; import type React from "react"; import { memo, useCallback, useEffect, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; -import { Box, Flex, type SxStyleProp } from "theme-ui"; import useTypingStore from "../store/typing"; import actionType from "../store/typing/action"; import Hotkey from "./Hotkey"; @@ -105,34 +105,14 @@ const TypingArea = memo(() => { }, [typing.typingStatus, focusInput]); return ( - - +
- +
{ } /> ))} - - +
+
{blur && ( - +
{typing.typingStatus === "done" ? ( <> @@ -191,9 +159,9 @@ const TypingArea = memo(() => { Click or Shift+F to focus )} - +
)} -
+
); }); diff --git a/src/components/Word.tsx b/src/components/Word.tsx index bc1bf4c..316af2d 100644 --- a/src/components/Word.tsx +++ b/src/components/Word.tsx @@ -1,5 +1,5 @@ +import { cn } from "lib/utils"; import { memo } from "react"; -import { Text } from "theme-ui"; import Letter from "utils/Letter"; import type Word from "../utils/Word"; import { isCorrectlyTyped } from "../utils/Word"; @@ -9,18 +9,13 @@ const WordComponent = memo( ({ word, cursorIndex }: { word: Word; cursorIndex?: number | null }) => { return ( word.show && ( - - + ({ - display: "inline-block", - textDecorationColor: theme.colors.incorrectLetter, - textDecoration: - word.isTyped && !isCorrectlyTyped(word) - ? "line-through" - : "none", - })} + className={cn( + "inline-block decoration-incorrectLetter", + word.isTyped && !isCorrectlyTyped(word) && "line-through", + )} > {word.letterSequence.map((l, i) => ( ))} - + = word.originalWord.length} letter={new Letter(" ")} /> - + ) ); }, diff --git a/src/components/__tests__/Letter.test.tsx b/src/components/__tests__/Letter.test.tsx index a040d72..3d3d14b 100644 --- a/src/components/__tests__/Letter.test.tsx +++ b/src/components/__tests__/Letter.test.tsx @@ -1,6 +1,5 @@ import { render, screen } from "@testing-library/react"; import LetterComponent from "components/Letter"; -import MockThemeUI from "components/MockThemeUI"; import Letter from "utils/Letter"; import { expect, test } from "vitest"; @@ -25,24 +24,12 @@ test("Letter component render correct letter - 2", () => { test("Letter component should render caret", () => { const letter = new Letter("a"); - render( - - - , - ); - expect(screen.getByTestId("letter")).toHaveStyleRule("content", '"|"', { - target: ":before", - }); + render(); + expect(screen.getByTestId("letter")).toHaveClass("before:content-['|']"); }); test("Letter component should not render caret", () => { const letter = new Letter("a"); - render( - - - , - ); - expect(screen.getByTestId("letter")).not.toHaveStyleRule("content", '"|"', { - target: ":before", - }); + render(); + expect(screen.getByTestId("letter")).not.toHaveClass("before:content-['|']"); }); diff --git a/src/components/__tests__/Word.test.tsx b/src/components/__tests__/Word.test.tsx index 6523fea..63d3dc0 100644 --- a/src/components/__tests__/Word.test.tsx +++ b/src/components/__tests__/Word.test.tsx @@ -1,5 +1,4 @@ import { render, screen } from "@testing-library/react"; -import MockThemeUI from "components/MockThemeUI"; import WordComponent from "components/Word"; import Word from "utils/Word"; import { expect, test } from "vitest"; @@ -21,11 +20,7 @@ test("Word component null if word.show is false", () => { test("Word component render correct word", () => { const word = new Word("javascript"); - render( - - - , - ); + render(); expect(screen.getByTestId("word").textContent).toBe("javascript"); }); @@ -37,15 +32,8 @@ test("Correctly typed word is not strike through", () => { status: "correct", })); - render( - - - , - ); - expect(screen.getByTestId("word")).not.toHaveStyleRule( - "text-decoration", - "line-through", - ); + render(); + expect(screen.getByTestId("word")).not.toHaveClass("line-through"); }); test("Incorrectly typed word is strike through", () => { @@ -58,13 +46,6 @@ test("Incorrectly typed word is strike through", () => { word.letterSequence[2].status = "incorrect"; - render( - - - , - ); - expect(screen.getByTestId("word")).toHaveStyleRule( - "text-decoration", - "line-through", - ); + render(); + expect(screen.getByTestId("word")).toHaveClass("line-through"); }); diff --git a/src/index.css b/src/index.css index c84804d..0e9df91 100644 --- a/src/index.css +++ b/src/index.css @@ -3,67 +3,64 @@ @tailwind utilities; @layer base { - :root { - --background: 0 0% 100%; - --foreground: 240 10% 3.9%; - --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; - --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - --primary: 240 5.9% 10%; - --primary-foreground: 0 0% 98%; - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - --ring: 240 10% 3.9%; - --radius: 0.5rem; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; + [data-theme="oneDarkPro"] { + --color-background: #21252b; + --color-typingBackground: #282c34; + --color-typingText: #999999; + --color-untypedLetter: #c678dd; + --color-incorrectLetter: #e5c07b; + --color-correctLetter: #69b679; + --color-extraLetter: #abb0b2; + --color-caret: #528bff; } - .dark { - --background: 240 10% 3.9%; - --foreground: 0 0% 98%; - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 240 4.9% 83.9%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; + [data-theme="monokai"] { + --color-background: #1e1f1c; + --color-typingBackground: #272822; + --color-typingText: #b67534; + --color-untypedLetter: #ffd3da; + --color-incorrectLetter: #d72f37; + --color-correctLetter: #fff; + --color-caret: #fff; + } + + [data-theme="pink"] { + /* convert to hsl */ + --color-background: #181818; + --color-typingBackground: #202020; + --color-typingText: #fd778d; + --color-untypedLetter: #ffd3da; + --color-incorrectLetter: #ffc710; + --color-extraLetter: #fdcddf; + --color-caret: #fff; + } + + [data-theme="dark"] { + --color-background: #060606; + --color-typingBackground: #181818; + --color-typingText: #fff; + --color-untypedLetter: #999999; + --color-correctLetter: #55ec47; + --color-incorrectLetter: #f07178; + --color-extraLetter: #f0c471; + --color-caret: #fff; + } + + [data-theme="light"] { + --color-background: #fcfcfc; + --color-typingBackground: #f5f5f5; + --color-typingText: #060606; + --color-untypedLetter: #999999; + --color-correctLetter: #000; + --color-incorrectLetter: #ff4242; + --color-extraLetter: #999999; + --color-caret: #000000; } } @layer base { - * { - @apply border-border; - } body { - @apply bg-background text-foreground; + background-color: var(--color-background); + color: var(--color-typingText); } } diff --git a/src/index.tsx b/src/index.tsx index 750f654..5798c00 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,36 +1,16 @@ -import { Global, css } from "@emotion/core"; import React from "react"; import ReactDOM from "react-dom"; -import { ThemeProvider } from "theme-ui"; import App from "./App"; import { ConfigStoreProvider } from "./store/config"; -import theme from "./theme"; import "./index.css"; // import reportWebVitals from './reportWebVitals' ReactDOM.render( - - - - - - + + + , document.getElementById("root"), ); diff --git a/src/store/config/index.tsx b/src/store/config/index.tsx index 4fdc947..d90138a 100644 --- a/src/store/config/index.tsx +++ b/src/store/config/index.tsx @@ -2,10 +2,10 @@ import { type Dispatch, type SetStateAction, createContext, + useCallback, useContext, useReducer, } from "react"; -import { useColorMode } from "theme-ui"; import type { Theme } from "utils/constant"; import { type Config, createConfigStore } from "../../utils/store"; import type { Action } from "./action"; @@ -16,24 +16,20 @@ const initialStore = createConfigStore({}); interface ConfigContext { config: Config; dispatch: Dispatch; - setTheme: Dispatch>; } export const ConfigStoreContext = createContext(null); export const ConfigStoreProvider = ({ children }: React.PropsWithChildren) => { const [config, dispatch] = useReducer(configReducer, initialStore); - const [theme, setTheme] = useColorMode(); return ( {children} diff --git a/src/store/config/reducer.ts b/src/store/config/reducer.ts index c2e9e5d..964cc98 100644 --- a/src/store/config/reducer.ts +++ b/src/store/config/reducer.ts @@ -13,6 +13,18 @@ function configReducer(state: Config, { type, payload }: Action): Config { ...state, duration: payload.duration ?? state.duration, }; + case actionType.CHANGE_THEME: { + if (!payload.theme) return state; + + document.documentElement.setAttribute("data-theme", payload.theme); + localStorage.setItem("theme", payload.theme); + + return { + ...state, + theme: payload.theme ?? state.theme, + }; + } + default: return state; } diff --git a/src/theme/colors.ts b/src/theme/colors.ts deleted file mode 100644 index e24238f..0000000 --- a/src/theme/colors.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { createColorTheme } from "../utils/theme"; - -const light = createColorTheme({ - background: "#fcfcfc", - text: "#060606", - untypedLetter: "#999999", - correctLetter: "#000", - incorrectLetter: "#ff4242", - extraLetter: "#999999", - typingBackground: "#f5f5f5", - caret: "#000000", - secondaryText: "text", -}); - -const oneDarkPro = createColorTheme({ - background: "#21252B", - typingBackground: "#282C34", - text: "#999999", - untypedLetter: "#C678DD", - incorrectLetter: "#E5C07B", - correctLetter: "#69B679", - extraLetter: "#ABB0B2", - caret: "#528BFF", -}); - -const monokai = createColorTheme({ - background: "#1E1F1C", - text: "#B67534", - typingBackground: "#272822", - correctLetter: "#fff", - incorrectLetter: "#D72F37", - caret: "#fff", -}); - -const pink = createColorTheme({ - background: "#181818", - typingBackground: "#202020", - text: "#fd778d", - untypedLetter: "#ffd3da", - correctLetter: "#fd778d", - incorrectLetter: "#ffc710", - extraLetter: "#fdcddf", -}); - -const colors = { - text: "#fff", - secondaryText: "grayText", - background: "#060606", - primary: "#3cf", - secondary: "#e0f", - muted: "#191919", - highlight: "#29112c", - gray: "#999", - purple: "#c0f", - correctLetter: "#55ec47", - incorrectLetter: "#F07178", - extraLetter: "#f0c471", - untypedLetter: "grayText", - typingBackground: "#181717", - modes: { - light, - oneDarkPro, - monokai, - pink, - }, -}; - -export default colors; diff --git a/src/theme/index.ts b/src/theme/index.ts deleted file mode 100644 index a132579..0000000 --- a/src/theme/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import colors from "./colors"; -import styles from "./styles"; - -const theme = { - colors, - styles, - fonts: { - body: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif', - heading: "inherit", - monospace: "Fira Code, Menlo, monospace", - }, - fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 72], - fontWeights: { - body: 400, - heading: 700, - display: 900, - }, - lineHeights: { - body: 1.5, - heading: 1.25, - }, - textStyles: { - heading: { - fontFamily: "heading", - fontWeight: "heading", - lineHeight: "heading", - }, - display: { - variant: "textStyles.heading", - fontSize: [5, 6], - fontWeight: "display", - letterSpacing: "-0.03em", - mt: 3, - }, - }, - sizes: { - container: [260, 640, 800], - }, - breakpoints: ["576px", "768px", "992px", "1200px"], -}; - -export default theme; diff --git a/src/theme/styles.ts b/src/theme/styles.ts deleted file mode 100644 index 4cc6940..0000000 --- a/src/theme/styles.ts +++ /dev/null @@ -1,32 +0,0 @@ -const styles = { - root: { - fontFamily: "body", - lineHeight: "body", - fontWeight: "body", - }, - h1: { - variant: "textStyles.display", - }, - h2: { - variant: "textStyles.heading", - fontSize: 5, - }, - h3: { - variant: "textStyles.heading", - fontSize: 4, - }, - h4: { - variant: "textStyles.heading", - fontSize: 3, - }, - h5: { - variant: "textStyles.heading", - fontSize: 2, - }, - h6: { - variant: "textStyles.heading", - fontSize: 1, - }, -}; - -export default styles; diff --git a/src/utils/constant.ts b/src/utils/constant.ts index 61d5fdb..6598860 100644 --- a/src/utils/constant.ts +++ b/src/utils/constant.ts @@ -19,7 +19,7 @@ export const duration = { } as const; export const theme = { - default: "Dark", + dark: "Dark", light: "Light", oneDarkPro: "One Dark Pro", monokai: "Monokai", diff --git a/tailwind.config.js b/tailwind.config.js index 7cb7e37..41c6023 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,10 +2,10 @@ module.exports = { darkMode: ["class"], content: [ - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './src/**/*.{ts,tsx}', + "./pages/**/*.{ts,tsx}", + "./components/**/*.{ts,tsx}", + "./app/**/*.{ts,tsx}", + "./src/**/*.{ts,tsx}", ], prefix: "", theme: { @@ -18,60 +18,26 @@ module.exports = { }, extend: { colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", + background: "var(--color-background)", + typingBackground: "var(--color-typingBackground)", + typingText: "var(--color-typingText)", + untypedLetter: "var(--color-untypedLetter)", + correctLetter: "var(--color-correctLetter)", + incorrectLetter: "var(--color-incorrectLetter)", + extraLetter: "var(--color-extraLetter)", + caret: "var(--color-caret)", }, keyframes: { - "accordion-down": { - from: { height: "0" }, - to: { height: "var(--radix-accordion-content-height)" }, - }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: "0" }, + opacityBreath: { + "50%": { + opacity: 0, + }, }, }, animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", + caret: "opacityBreath 1s steps(1) infinite", }, }, }, plugins: [require("tailwindcss-animate")], -} \ No newline at end of file +}; From b136fb752b09e7007f0573f878882ee08fe256bd Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Tue, 23 Jul 2024 10:42:18 +0700 Subject: [PATCH 08/12] migrate theming to tailwindcss --- package.json | 3 - pnpm-lock.yaml | 319 +++------------------------- src/App.tsx | 104 +-------- src/components/Hotkey.tsx | 2 +- src/components/LanguageSwitcher.tsx | 62 ++++++ src/components/ThemeSwitcher.tsx | 65 ++++++ src/components/Timer.tsx | 29 ++- src/components/TypingArea.tsx | 2 +- src/components/Word.tsx | 6 +- src/index.css | 160 ++++++++++---- src/store/config/index.tsx | 9 +- src/utils/constant.ts | 5 + src/utils/store.ts | 4 +- tailwind.config.js | 8 +- 14 files changed, 340 insertions(+), 438 deletions(-) create mode 100644 src/components/LanguageSwitcher.tsx create mode 100644 src/components/ThemeSwitcher.tsx diff --git a/package.json b/package.json index bdc3cbb..fa9738d 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "react-hotkeys-hook": "^2.3.1", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", - "theme-ui": "^0.3.1", "vite-tsconfig-paths": "^4.3.2", "web-vitals": "^0.2.4" }, @@ -54,8 +53,6 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/testing-library__jest-dom": "5.13.0", - "@types/theme-ui": "^0.3.7", - "@types/theme-ui__components": "^0.3.0", "@vitejs/plugin-react": "^4.3.1", "@vitest/coverage-v8": "^2.0.3", "autoprefixer": "^10.4.19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4bc98d..1d0219e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,9 +50,6 @@ dependencies: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.6) - theme-ui: - specifier: ^0.3.1 - version: 0.3.5(react@17.0.2) vite-tsconfig-paths: specifier: ^4.3.2 version: 4.3.2(typescript@5.5.3)(vite@5.3.4) @@ -79,12 +76,6 @@ devDependencies: '@types/testing-library__jest-dom': specifier: 5.13.0 version: 5.13.0 - '@types/theme-ui': - specifier: ^0.3.7 - version: 0.3.7(react@17.0.2) - '@types/theme-ui__components': - specifier: ^0.3.0 - version: 0.3.0(react@17.0.2) '@vitejs/plugin-react': specifier: ^4.3.1 version: 4.3.1(vite@5.3.4) @@ -454,6 +445,7 @@ packages: '@emotion/stylis': 0.8.5 '@emotion/utils': 0.11.3 '@emotion/weak-memoize': 0.2.5 + dev: false /@emotion/core@10.3.1(react@17.0.2): resolution: {integrity: sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==} @@ -469,6 +461,7 @@ packages: react: 17.0.2 transitivePeerDependencies: - supports-color + dev: false /@emotion/css-prettifier@1.1.3: resolution: {integrity: sha512-KNv23+VQ+pcw3ebd1vSEl11CQ6SKAG5EQkrinjVGsfw3ZTWe6/tpWQrsvFLqCtU2LRiLPi04KgFCE4A9+crfpQ==} @@ -485,14 +478,11 @@ packages: babel-plugin-emotion: 10.2.2 transitivePeerDependencies: - supports-color + dev: false /@emotion/hash@0.8.0: resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} - - /@emotion/is-prop-valid@0.8.8: - resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} - dependencies: - '@emotion/memoize': 0.7.4 + dev: false /@emotion/jest@11.11.0: resolution: {integrity: sha512-XZlnmdUZ32YjQnInsCFk/plKpkV/NXN1Ab4YoNvXN887MeR3Hr5ZsTyoblIW8AWwdfQiZHHphaPMb56lk6Ofdw==} @@ -514,9 +504,7 @@ packages: /@emotion/memoize@0.7.4: resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} - - /@emotion/memoize@0.7.5: - resolution: {integrity: sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==} + dev: false /@emotion/memoize@0.8.1: resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} @@ -530,47 +518,27 @@ packages: '@emotion/unitless': 0.7.5 '@emotion/utils': 0.11.3 csstype: 2.6.21 + dev: false /@emotion/sheet@0.9.4: resolution: {integrity: sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==} - - /@emotion/styled-base@10.3.0(@emotion/core@10.3.1)(react@17.0.2): - resolution: {integrity: sha512-PBRqsVKR7QRNkmfH78hTSSwHWcwDpecH9W6heujWAcyp2wdz/64PP73s7fWS1dIPm8/Exc8JAzYS8dEWXjv60w==} - peerDependencies: - '@emotion/core': ^10.0.28 - react: '>=16.3.0' - dependencies: - '@babel/runtime': 7.24.8 - '@emotion/core': 10.3.1(react@17.0.2) - '@emotion/is-prop-valid': 0.8.8 - '@emotion/serialize': 0.11.16 - '@emotion/utils': 0.11.3 - react: 17.0.2 - - /@emotion/styled@10.3.0(@emotion/core@10.3.1)(react@17.0.2): - resolution: {integrity: sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==} - peerDependencies: - '@emotion/core': ^10.0.27 - react: '>=16.3.0' - dependencies: - '@emotion/core': 10.3.1(react@17.0.2) - '@emotion/styled-base': 10.3.0(@emotion/core@10.3.1)(react@17.0.2) - babel-plugin-emotion: 10.2.2 - react: 17.0.2 - transitivePeerDependencies: - - supports-color + dev: false /@emotion/stylis@0.8.5: resolution: {integrity: sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==} + dev: false /@emotion/unitless@0.7.5: resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + dev: false /@emotion/utils@0.11.3: resolution: {integrity: sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==} + dev: false /@emotion/weak-memoize@0.2.5: resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==} + dev: false /@esbuild/aix-ppc64@0.21.5: resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} @@ -859,14 +827,6 @@ packages: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - /@mdx-js/react@1.6.22(react@17.0.2): - resolution: {integrity: sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==} - peerDependencies: - react: ^16.13.1 || ^17.0.0 - dependencies: - react: 17.0.2 - dev: false - /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1532,77 +1492,6 @@ packages: /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - /@styled-system/background@5.1.2: - resolution: {integrity: sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/border@5.1.5: - resolution: {integrity: sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/color@5.1.2: - resolution: {integrity: sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/core@5.1.2: - resolution: {integrity: sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==} - dependencies: - object-assign: 4.1.1 - - /@styled-system/css@5.1.5: - resolution: {integrity: sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==} - - /@styled-system/flexbox@5.1.2: - resolution: {integrity: sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/grid@5.1.2: - resolution: {integrity: sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/layout@5.1.2: - resolution: {integrity: sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/position@5.1.2: - resolution: {integrity: sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/shadow@5.1.2: - resolution: {integrity: sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/should-forward-prop@5.1.5: - resolution: {integrity: sha512-+rPRomgCGYnUIaFabDoOgpSDc4UUJ1KsmlnzcEp0tu5lFrBQKgZclSo18Z1URhaZm7a6agGtS5Xif7tuC2s52Q==} - dependencies: - '@emotion/is-prop-valid': 0.8.8 - '@emotion/memoize': 0.7.5 - styled-system: 5.1.5 - - /@styled-system/space@5.1.2: - resolution: {integrity: sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/typography@5.1.2: - resolution: {integrity: sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==} - dependencies: - '@styled-system/core': 5.1.2 - - /@styled-system/variant@5.1.5: - resolution: {integrity: sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==} - dependencies: - '@styled-system/core': 5.1.2 - '@styled-system/css': 5.1.5 - /@testing-library/dom@10.3.2: resolution: {integrity: sha512-0bxIdP9mmPiOJ6wHLj8bdJRq+51oddObeCGdEf6PNEhYd93ZYAN+lPRnEOVFtheVwDM7+p+tza3LAQgp0PTudg==} engines: {node: '>=18'} @@ -1669,84 +1558,6 @@ packages: '@testing-library/dom': 10.3.2 dev: false - /@theme-ui/color-modes@0.3.5(react@17.0.2): - resolution: {integrity: sha512-3n5ExAnp1gAuVVFdGF2rRLyrVsa7qtmUXx+gj1wPJsADq23EE4ctkppC+aIfPFxT196WhR8fjErrVuO7Rh+wAg==} - peerDependencies: - react: ^16.11.0 - dependencies: - '@emotion/core': 10.3.1(react@17.0.2) - '@theme-ui/core': 0.3.5(react@17.0.2) - '@theme-ui/css': 0.3.5 - deepmerge: 4.3.1 - react: 17.0.2 - transitivePeerDependencies: - - supports-color - dev: false - - /@theme-ui/components@0.3.5(react@17.0.2): - resolution: {integrity: sha512-RdWwnN43H1Tq80lGCu6icNuYCWoHHNtwH+LJGaGfiPkv/uMXWrwzKPLMiAuYM5b3ofKtmdaAcxZLjqAld97jkw==} - peerDependencies: - react: ^16.8.0 - dependencies: - '@emotion/core': 10.3.1(react@17.0.2) - '@emotion/styled': 10.3.0(@emotion/core@10.3.1)(react@17.0.2) - '@styled-system/color': 5.1.2 - '@styled-system/should-forward-prop': 5.1.5 - '@styled-system/space': 5.1.2 - '@theme-ui/css': 0.3.5 - react: 17.0.2 - transitivePeerDependencies: - - supports-color - - /@theme-ui/core@0.3.5(react@17.0.2): - resolution: {integrity: sha512-80gbG4BW0ZQgZ8TWSG7vY72uXDxmkI/GttjpJee7AJlWVrPh7RCD2E3cuFPjqXzt7o4BJ9lZSHmTXcLzixNtRw==} - peerDependencies: - react: ^16.11.0 - dependencies: - '@emotion/core': 10.3.1(react@17.0.2) - '@theme-ui/css': 0.3.5 - deepmerge: 4.3.1 - react: 17.0.2 - transitivePeerDependencies: - - supports-color - dev: false - - /@theme-ui/css@0.3.5: - resolution: {integrity: sha512-XqsyXmifbnHOui1flSq4V7Lb3U+06Dbn2Q/leyr/cRd6Xgc0naiztdmD0MbXNvxgU51a2Ur9hyP4PnO5wE0yRg==} - - /@theme-ui/mdx@0.3.5(@theme-ui/core@0.3.5)(@theme-ui/css@0.3.5)(react@17.0.2): - resolution: {integrity: sha512-KMf5kkEcItQ3qIj7dston/kBOZc82ST2R0pUcyk/u8ZclX4ingRtZkMxm2zpmxybzdSUY3DIKf2MTK9CxUSpOQ==} - peerDependencies: - '@theme-ui/core': '*' - '@theme-ui/css': '*' - react: ^16.11.0 - dependencies: - '@emotion/core': 10.3.1(react@17.0.2) - '@emotion/styled': 10.3.0(@emotion/core@10.3.1)(react@17.0.2) - '@mdx-js/react': 1.6.22(react@17.0.2) - '@theme-ui/core': 0.3.5(react@17.0.2) - '@theme-ui/css': 0.3.5 - react: 17.0.2 - transitivePeerDependencies: - - supports-color - dev: false - - /@theme-ui/theme-provider@0.3.5(@theme-ui/css@0.3.5)(react@17.0.2): - resolution: {integrity: sha512-C1kVsGyrh/pqO/j4+KSF5IvVW1DOnZoQmpaJ9EjyU4bqY0PCTZfuNdNPfydKaDWiYxrKGXKBeX0xjvLLU6R0zQ==} - peerDependencies: - '@theme-ui/css': '*' - react: ^16.11.0 - dependencies: - '@emotion/core': 10.3.1(react@17.0.2) - '@theme-ui/color-modes': 0.3.5(react@17.0.2) - '@theme-ui/core': 0.3.5(react@17.0.2) - '@theme-ui/css': 0.3.5 - '@theme-ui/mdx': 0.3.5(@theme-ui/core@0.3.5)(@theme-ui/css@0.3.5)(react@17.0.2) - react: 17.0.2 - transitivePeerDependencies: - - supports-color - dev: false - /@types/aria-query@4.2.2: resolution: {integrity: sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==} dev: false @@ -1817,6 +1628,7 @@ packages: /@types/parse-json@4.0.2: resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + dev: false /@types/prop-types@15.7.12: resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -1835,60 +1647,11 @@ packages: /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - /@types/styled-system@5.1.22: - resolution: {integrity: sha512-NbRp37zWcrf/+Qf2NumdyZfhSx1dzJ50zgfKvnezYJx1HTRUMVYY8jtWvK1eoIAa6F5sXwHLhE8oXNu15ThBAA==} - dependencies: - csstype: 3.1.3 - dev: true - - /@types/styled-system__css@5.0.21: - resolution: {integrity: sha512-8S1lPbUbrE8U/2btqjh9X6pK9//kQdbQDe9z3vQl4SWtxtqoAVnrFZE6Xs+IHM7NMZ1uC68XrF9nLdzRHm3VyA==} - dependencies: - csstype: 3.1.3 - dev: true - /@types/testing-library__jest-dom@5.13.0: resolution: {integrity: sha512-tfjY4Fzzwg1wSU31MWaIH8rzJ2WPtQtUNnZ0wcZwzzhWWRa63Jb1fB7tl79fGX7PUL/4ZHjKs+tcY5BZ8nfNyg==} dependencies: '@types/jest': 29.5.12 - /@types/theme-ui@0.3.7(react@17.0.2): - resolution: {integrity: sha512-4hzDlDhlFYmOdXBLZTbO4N2hWfuGo1N77AcIMaSyDGEyFbdZSpelMLTkEtNzYT8yQWIl3x0WITiBzjqkfc6dUg==} - dependencies: - '@emotion/serialize': 0.11.16 - '@types/react': 18.3.3 - '@types/styled-system': 5.1.22 - '@types/styled-system__css': 5.0.21 - '@types/theme-ui__components': 0.6.0(react@17.0.2) - csstype: 3.1.3 - transitivePeerDependencies: - - react - - supports-color - dev: true - - /@types/theme-ui__components@0.3.0(react@17.0.2): - resolution: {integrity: sha512-nFQ3rNWgn1Pmhtv/z8s/uPI8f/bkOT0iS619PIdpsaQ+keloYUUbCKRogjPunnDcI/Xsr56wiC6bpANWJQIRAQ==} - dependencies: - '@emotion/core': 10.3.1(react@17.0.2) - '@emotion/styled': 10.3.0(@emotion/core@10.3.1)(react@17.0.2) - '@types/react': 18.3.3 - '@types/styled-system': 5.1.22 - '@types/theme-ui': 0.3.7(react@17.0.2) - transitivePeerDependencies: - - react - - supports-color - dev: true - - /@types/theme-ui__components@0.6.0(react@17.0.2): - resolution: {integrity: sha512-LYRJBgaHQfC7VPpj/gaEFk7gJB725RbP0UcI1ChrzoXlMCSGA3L+oAkLneamdm1tlK827wqu2aSHM9VDyqEXVQ==} - deprecated: This is a stub types definition. @theme-ui/components provides its own type definitions, so you do not need this installed. - dependencies: - '@theme-ui/components': 0.3.5(react@17.0.2) - transitivePeerDependencies: - - react - - supports-color - dev: true - /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2098,6 +1861,7 @@ packages: source-map: 0.5.7 transitivePeerDependencies: - supports-color + dev: false /babel-plugin-macros@2.8.0: resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==} @@ -2105,9 +1869,11 @@ packages: '@babel/runtime': 7.24.8 cosmiconfig: 6.0.0 resolve: 1.22.8 + dev: false /babel-plugin-syntax-jsx@6.18.0: resolution: {integrity: sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==} + dev: false /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2146,6 +1912,7 @@ packages: /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + dev: false /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} @@ -2258,6 +2025,7 @@ packages: /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: false /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2277,6 +2045,7 @@ packages: parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 + dev: false /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} @@ -2304,6 +2073,7 @@ packages: /csstype@2.6.21: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} + dev: false /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -2336,11 +2106,6 @@ packages: engines: {node: '>=6'} dev: true - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - dev: false - /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -2391,6 +2156,7 @@ packages: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 + dev: false /esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} @@ -2489,6 +2255,7 @@ packages: /find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + dev: false /foreground-child@3.2.1: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} @@ -2640,6 +2407,7 @@ packages: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 + dev: false /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} @@ -2654,6 +2422,7 @@ packages: /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: false /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} @@ -2838,6 +2607,7 @@ packages: /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: false /json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} @@ -2865,6 +2635,7 @@ packages: hasBin: true dependencies: js-tokens: 4.0.0 + dev: false /loupe@3.1.1: resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} @@ -3016,6 +2787,7 @@ packages: engines: {node: '>=6'} dependencies: callsites: 3.1.0 + dev: false /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} @@ -3025,6 +2797,7 @@ packages: error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + dev: false /parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} @@ -3054,6 +2827,7 @@ packages: /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + dev: false /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -3277,6 +3051,7 @@ packages: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 + dev: false /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} @@ -3307,6 +3082,7 @@ packages: /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + dev: false /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} @@ -3416,6 +3192,7 @@ packages: /source-map@0.5.7: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} + dev: false /specificity@0.4.1: resolution: {integrity: sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==} @@ -3482,23 +3259,6 @@ packages: js-tokens: 9.0.0 dev: true - /styled-system@5.1.5: - resolution: {integrity: sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==} - dependencies: - '@styled-system/background': 5.1.2 - '@styled-system/border': 5.1.5 - '@styled-system/color': 5.1.2 - '@styled-system/core': 5.1.2 - '@styled-system/flexbox': 5.1.2 - '@styled-system/grid': 5.1.2 - '@styled-system/layout': 5.1.2 - '@styled-system/position': 5.1.2 - '@styled-system/shadow': 5.1.2 - '@styled-system/space': 5.1.2 - '@styled-system/typography': 5.1.2 - '@styled-system/variant': 5.1.5 - object-assign: 4.1.1 - /stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} dev: true @@ -3587,22 +3347,6 @@ packages: minimatch: 9.0.5 dev: true - /theme-ui@0.3.5(react@17.0.2): - resolution: {integrity: sha512-yxooGhvkdjFDotDeIFehKo5k6NnLZ3gsLSe8EDe2aDcoWqg1mZjkjjr8EYtVCrK3mk/tYz97AT5BpEnUfamNCQ==} - peerDependencies: - react: ^16.11.0 - dependencies: - '@theme-ui/color-modes': 0.3.5(react@17.0.2) - '@theme-ui/components': 0.3.5(react@17.0.2) - '@theme-ui/core': 0.3.5(react@17.0.2) - '@theme-ui/css': 0.3.5 - '@theme-ui/mdx': 0.3.5(@theme-ui/core@0.3.5)(@theme-ui/css@0.3.5)(react@17.0.2) - '@theme-ui/theme-provider': 0.3.5(@theme-ui/css@0.3.5)(react@17.0.2) - react: 17.0.2 - transitivePeerDependencies: - - supports-color - dev: false - /thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -3969,6 +3713,7 @@ packages: /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + dev: false /yaml@2.4.5: resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} diff --git a/src/App.tsx b/src/App.tsx index 0189ecf..3c3e088 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,101 +1,28 @@ -import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; -import { CheckIcon } from "@radix-ui/react-icons"; +import LanguageSwitcher from "components/LanguageSwitcher"; import Statistic from "components/Statistic"; +import ThemeSwitcher from "components/ThemeSwitcher"; import Timer from "components/Timer"; import TypingArea from "components/TypingArea"; -import type React from "react"; -import { useCallback } from "react"; -import { Select } from "theme-ui"; import useConfigStore from "./store/config"; -import { CHANGE_LANGUAGE } from "./store/config/action"; import { TypingStoreProvider } from "./store/typing"; -import { - type Language, - type Theme, - language, - languageFlagMoji, - theme, -} from "./utils/constant"; const App = () => { - const { config, dispatch } = useConfigStore(); - - const handleSelectThemeChange = useCallback( - (e: React.ChangeEvent) => { - const selectedValue = e.target.options[e.target.selectedIndex] - .value as Theme; - dispatch({ - type: "CHANGE_THEME", - payload: { - theme: selectedValue, - }, - }); - }, - [dispatch], - ); - - const handleSelectLanguageChange = useCallback( - (value: string) => { - dispatch({ - type: CHANGE_LANGUAGE, - payload: { lang: value as Language }, - }); - }, - [dispatch], - ); + const { config } = useConfigStore(); return (

Typefaster

- - - - - - - - 🇮🇩 Bahasa Indonesia - - - - - - 🇬🇧 English - - - - - - - +
+ + +
-
+
-
- -
+
@@ -110,19 +37,6 @@ const App = () => { Github
-
- -
); diff --git a/src/components/Hotkey.tsx b/src/components/Hotkey.tsx index c646f46..0da3dc9 100644 --- a/src/components/Hotkey.tsx +++ b/src/components/Hotkey.tsx @@ -1,6 +1,6 @@ const Hotkey = ({ children }: React.PropsWithChildren) => { return ( - + {children} ); diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..2856303 --- /dev/null +++ b/src/components/LanguageSwitcher.tsx @@ -0,0 +1,62 @@ +import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; +import { CheckIcon } from "@radix-ui/react-icons"; +import { useCallback } from "react"; +import useConfigStore from "store/config"; +import { type Language, language, languageFlagMoji } from "utils/constant"; + +export default function LanguageSwitcher() { + const { config, dispatch } = useConfigStore(); + + const handleSelectLanguageChange = useCallback( + (value: string) => { + dispatch({ + type: "CHANGE_LANGUAGE", + payload: { lang: value as Language }, + }); + }, + [dispatch], + ); + + return ( + + + + + + + + 🇮🇩 Bahasa Indonesia + + + + + + 🇬🇧 English + + + + + + + + ); +} diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx new file mode 100644 index 0000000..133c038 --- /dev/null +++ b/src/components/ThemeSwitcher.tsx @@ -0,0 +1,65 @@ +import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; +import { CheckIcon } from "@radix-ui/react-icons"; +import { useCallback } from "react"; +import useConfigStore from "store/config"; +import { type Theme, theme } from "utils/constant"; + +export default function ThemeSwitcher() { + const { config, dispatch } = useConfigStore(); + + const handleSelectThemeChange = useCallback( + (theme: string) => { + dispatch({ + type: "CHANGE_THEME", + payload: { + theme: theme as Theme, + }, + }); + }, + [dispatch], + ); + + return ( + + + + + + + {Object.entries(theme).map(([key, value]) => ( + +
+ {value} + + + + + ))} + + + + ); +} diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx index 43d35e2..efb76a4 100644 --- a/src/components/Timer.tsx +++ b/src/components/Timer.tsx @@ -1,3 +1,4 @@ +import { cn } from "lib/utils"; import { useEffect, useRef, useState } from "react"; import useTypingStore from "../store/typing"; import actionType from "../store/typing/action"; @@ -27,8 +28,32 @@ const Timer = ({ duration }: React.PropsWithChildren<{ duration: number }>) => { }, [typing.typingStatus, dispatch, duration]); return ( -
- {timerCount === 0 ? "Times up!" : timerCount} +
+
+ {timerCount === 0 ? "Times up!" : timerCount} +
+ +
+ ); +}; + +const ProgressBar = ({ + duration, + status, +}: { duration: number; status: string }) => { + return ( +
+
); }; diff --git a/src/components/TypingArea.tsx b/src/components/TypingArea.tsx index 89a961c..797a97c 100644 --- a/src/components/TypingArea.tsx +++ b/src/components/TypingArea.tsx @@ -145,7 +145,7 @@ const TypingArea = memo(() => { {blur && ( // biome-ignore lint/a11y/useKeyWithClickEvents:
{typing.typingStatus === "done" ? ( diff --git a/src/components/Word.tsx b/src/components/Word.tsx index 316af2d..8626ecf 100644 --- a/src/components/Word.tsx +++ b/src/components/Word.tsx @@ -13,8 +13,10 @@ const WordComponent = memo( {word.letterSequence.map((l, i) => ( diff --git a/src/index.css b/src/index.css index 0e9df91..e33018f 100644 --- a/src/index.css +++ b/src/index.css @@ -4,63 +4,139 @@ @layer base { [data-theme="oneDarkPro"] { - --color-background: #21252b; - --color-typingBackground: #282c34; - --color-typingText: #999999; - --color-untypedLetter: #c678dd; - --color-incorrectLetter: #e5c07b; - --color-correctLetter: #69b679; - --color-extraLetter: #abb0b2; - --color-caret: #528bff; + --color-background: #282c34; + --color-typingBackground: #21252b; + --color-foreground: #abb2bf; + --color-untypedLetter: #5c6370; + --color-correctLetter: #56b6c2; + --color-incorrectLetter: #c678dd; + --color-extraLetter: #e5c07b; + --color-strikeColor: #4b5263; + --color-caret: #61afef; } [data-theme="monokai"] { - --color-background: #1e1f1c; - --color-typingBackground: #272822; - --color-typingText: #b67534; - --color-untypedLetter: #ffd3da; - --color-incorrectLetter: #d72f37; - --color-correctLetter: #fff; - --color-caret: #fff; + --color-background: #272822; + --color-typingBackground: #3e3d32; + --color-foreground: #f8f8f2; + --color-untypedLetter: #75715e; + --color-correctLetter: #66d9ef; + --color-incorrectLetter: #fd971f; + --color-extraLetter: #ae81ff; + --color-strikeColor: #75715e; + --color-caret: #a6e22e; + } + + [data-theme="dark"] { + --color-background: #1a1a1a; + --color-typingBackground: #2a2a2a; + --color-foreground: #e0e0e0; + --color-untypedLetter: #808080; + --color-correctLetter: #4caf50; + --color-incorrectLetter: #f44336; + --color-extraLetter: #ffa726; + --color-strikeColor: #616161; + --color-caret: #2196f3; + } + + [data-theme="light"] { + --color-background: #f5f5f5; + --color-typingBackground: #ffffff; + --color-foreground: #333333; + --color-untypedLetter: #9e9e9e; + --color-correctLetter: #4caf50; + --color-incorrectLetter: #f44336; + --color-extraLetter: #ff9800; + --color-strikeColor: #bdbdbd; + --color-caret: #2196f3; } [data-theme="pink"] { - /* convert to hsl */ - --color-background: #181818; - --color-typingBackground: #202020; - --color-typingText: #fd778d; - --color-untypedLetter: #ffd3da; - --color-incorrectLetter: #ffc710; - --color-extraLetter: #fdcddf; - --color-caret: #fff; + --color-background: #fce4ec; + --color-typingBackground: #fff0f5; + --color-foreground: #880e4f; + --color-untypedLetter: #ad1457; + --color-correctLetter: #00796b; + --color-incorrectLetter: #d81b60; + --color-extraLetter: #6a1b9a; + --color-strikeColor: #c2185b; + --color-caret: #ff6e40; } - [data-theme="dark"] { - --color-background: #060606; - --color-typingBackground: #181818; - --color-typingText: #fff; - --color-untypedLetter: #999999; - --color-correctLetter: #55ec47; - --color-incorrectLetter: #f07178; - --color-extraLetter: #f0c471; - --color-caret: #fff; + [data-theme="solarizedDark"] { + --color-background: #002b36; + --color-typingBackground: #073642; + --color-foreground: #839496; + --color-untypedLetter: #586e75; + --color-correctLetter: #859900; + --color-incorrectLetter: #dc322f; + --color-extraLetter: #b58900; + --color-strikeColor: #657b83; + --color-caret: #268bd2; } - [data-theme="light"] { - --color-background: #fcfcfc; - --color-typingBackground: #f5f5f5; - --color-typingText: #060606; - --color-untypedLetter: #999999; - --color-correctLetter: #000; - --color-incorrectLetter: #ff4242; - --color-extraLetter: #999999; - --color-caret: #000000; + [data-theme="nord"] { + --color-background: #2e3440; + --color-typingBackground: #3b4252; + --color-foreground: #d8dee9; + --color-untypedLetter: #99a6c0; + --color-correctLetter: #a3be8c; + --color-incorrectLetter: #bf616a; + --color-extraLetter: #ebcb8b; + --color-strikeColor: #5e81ac; + --color-caret: #88c0d0; + } + + [data-theme="dracula"] { + --color-background: #282a36; + --color-typingBackground: #44475a; + --color-foreground: #f8f8f2; + --color-untypedLetter: #6272a4; + --color-correctLetter: #50fa7b; + --color-incorrectLetter: #ff5555; + --color-extraLetter: #ffb86c; + --color-strikeColor: #bd93f9; + --color-caret: #8be9fd; + } + + [data-theme="paper"] { + --color-background: #f4f1e8; + --color-typingBackground: #f4f1e8; + --color-foreground: #2c2c2c; + --color-untypedLetter: #8c8787; + --color-correctLetter: #c29747; + --color-incorrectLetter: #bd7676; + --color-extraLetter: #7a7770; + --color-strikeColor: #757575; + --color-caret: #212121; + } + + [data-theme="neonNight"] { + --color-background: #0f0f1a; + --color-typingBackground: #1a1a2e; + --color-foreground: #00ffff; + --color-untypedLetter: #4a4a6a; + --color-correctLetter: #37ffa2; + --color-incorrectLetter: #ff00ff; + --color-extraLetter: #ff9100; + --color-strikeColor: #ff00aa; + --color-caret: #ffff00; } } @layer base { body { background-color: var(--color-background); - color: var(--color-typingText); + color: var(--color-foreground); + } +} + +@layer utilities { + .diagonal-split { + background-image: linear-gradient( + to bottom right, + var(--color-untypedLetter) 50%, + var(--color-background) 49.9% + ); } } diff --git a/src/store/config/index.tsx b/src/store/config/index.tsx index d90138a..e5c55fb 100644 --- a/src/store/config/index.tsx +++ b/src/store/config/index.tsx @@ -1,9 +1,8 @@ import { type Dispatch, - type SetStateAction, createContext, - useCallback, useContext, + useEffect, useReducer, } from "react"; import type { Theme } from "utils/constant"; @@ -23,6 +22,12 @@ export const ConfigStoreContext = createContext(null); export const ConfigStoreProvider = ({ children }: React.PropsWithChildren) => { const [config, dispatch] = useReducer(configReducer, initialStore); + useEffect(() => { + const theme = localStorage.getItem("theme") as Theme | null; + if (!theme) return; + dispatch({ type: "CHANGE_THEME", payload: { theme } }); + }, []); + return ( ): Config { return { lang, mode, duration, - theme, + theme: theme ?? "dark", }; } diff --git a/tailwind.config.js b/tailwind.config.js index 41c6023..e4a0014 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -20,12 +20,13 @@ module.exports = { colors: { background: "var(--color-background)", typingBackground: "var(--color-typingBackground)", - typingText: "var(--color-typingText)", + foreground: "var(--color-foreground)", untypedLetter: "var(--color-untypedLetter)", correctLetter: "var(--color-correctLetter)", incorrectLetter: "var(--color-incorrectLetter)", extraLetter: "var(--color-extraLetter)", caret: "var(--color-caret)", + incorrectDecor: "var(--color-strikeColor)", }, keyframes: { opacityBreath: { @@ -33,9 +34,14 @@ module.exports = { opacity: 0, }, }, + progress: { + "0%": { transform: "translateX(-100%)" }, + "100%": { transform: "translateX(0)" }, + }, }, animation: { caret: "opacityBreath 1s steps(1) infinite", + progress: "progress var(--duration) linear", }, }, }, From d966b484bd8e14736fff417f41d5c0fdd69ee622 Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Tue, 23 Jul 2024 10:44:53 +0700 Subject: [PATCH 09/12] update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9a0291a..60a8bed 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,6 @@ Typefaster is a clean and minimal typing test insipired by [Monkeytype](https:// # Built with -- React.js -- Theme-ui -- 💗 +- [React](https://reactjs.org) +- [Typescript](https://www.typescriptlang.org) +- [TailwindCSS](https://tailwindcss.com) From f1e6d331eb9d587637cfce5e1b2352d21c1378a9 Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Tue, 23 Jul 2024 17:04:12 +0700 Subject: [PATCH 10/12] switcher dropdown portal --- src/components/LanguageSwitcher.tsx | 56 +++++++++++++++-------------- src/components/ThemeSwitcher.tsx | 55 ++++++++++++++-------------- 2 files changed, 58 insertions(+), 53 deletions(-) diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx index 2856303..4bc9ac1 100644 --- a/src/components/LanguageSwitcher.tsx +++ b/src/components/LanguageSwitcher.tsx @@ -28,35 +28,37 @@ export default function LanguageSwitcher() { {config.lang.toUpperCase()} - - + - - 🇮🇩 Bahasa Indonesia - - - - - - 🇬🇧 English - - - - - - + + 🇮🇩 Bahasa Indonesia + + + + + + 🇬🇧 English + + + + + + + ); } diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx index 133c038..992cd17 100644 --- a/src/components/ThemeSwitcher.tsx +++ b/src/components/ThemeSwitcher.tsx @@ -33,33 +33,36 @@ export default function ThemeSwitcher() { {theme[config.theme] ?? "unknown"} - - + - {Object.entries(theme).map(([key, value]) => ( - -
- {value} - - - - - ))} - - + + {Object.entries(theme).map(([key, value]) => ( + +
+ {value} + + + + + ))} + + + ); } From ffd6eb4c2d9498b722a5104c7e9d2c76615a507a Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Tue, 23 Jul 2024 17:07:18 +0700 Subject: [PATCH 11/12] save lang to localStorage --- src/store/config/index.tsx | 6 +++++- src/store/config/reducer.ts | 13 ++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/store/config/index.tsx b/src/store/config/index.tsx index e5c55fb..02cc0a5 100644 --- a/src/store/config/index.tsx +++ b/src/store/config/index.tsx @@ -5,7 +5,7 @@ import { useEffect, useReducer, } from "react"; -import type { Theme } from "utils/constant"; +import type { Language, Theme } from "utils/constant"; import { type Config, createConfigStore } from "../../utils/store"; import type { Action } from "./action"; import configReducer from "./reducer"; @@ -26,6 +26,10 @@ export const ConfigStoreProvider = ({ children }: React.PropsWithChildren) => { const theme = localStorage.getItem("theme") as Theme | null; if (!theme) return; dispatch({ type: "CHANGE_THEME", payload: { theme } }); + + const lang = localStorage.getItem("lang") as Language | null; + if (!lang) return; + dispatch({ type: "CHANGE_LANGUAGE", payload: { lang } }); }, []); return ( diff --git a/src/store/config/reducer.ts b/src/store/config/reducer.ts index 964cc98..aaf136f 100644 --- a/src/store/config/reducer.ts +++ b/src/store/config/reducer.ts @@ -3,17 +3,21 @@ import actionType, { type Action } from "./action"; function configReducer(state: Config, { type, payload }: Action): Config { switch (type) { - case actionType.CHANGE_LANGUAGE: + case actionType.CHANGE_LANGUAGE: { + if (!payload.lang) return state; + + localStorage.setItem("lang", payload.lang); return { ...state, - lang: payload.lang ?? state.lang, + lang: payload.lang, }; + } case actionType.CHANGE_DURATION: return { ...state, duration: payload.duration ?? state.duration, }; - case actionType.CHANGE_THEME: { + case actionType.CHANGE_THEME: if (!payload.theme) return state; document.documentElement.setAttribute("data-theme", payload.theme); @@ -21,9 +25,8 @@ function configReducer(state: Config, { type, payload }: Action): Config { return { ...state, - theme: payload.theme ?? state.theme, + theme: payload.theme, }; - } default: return state; From 17a9bb368cbd38b8ad8365d132fd9c7da9bef849 Mon Sep 17 00:00:00 2001 From: Dandi Wiratsangka Date: Tue, 23 Jul 2024 17:53:51 +0700 Subject: [PATCH 12/12] use geist mono, adjust font size --- package.json | 1 + pnpm-lock.yaml | 42 +++++++++++++++++++++++++++++ src/assets/fonts/GeistMonoVF.woff | Bin 0 -> 67864 bytes src/assets/fonts/GeistMonoVF.woff2 | Bin 0 -> 58048 bytes src/components/TypingArea.tsx | 7 ++++- src/index.tsx | 1 + tailwind.config.js | 3 +++ vite.config.ts | 28 ++++++++++++++----- 8 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 src/assets/fonts/GeistMonoVF.woff create mode 100644 src/assets/fonts/GeistMonoVF.woff2 diff --git a/package.json b/package.json index fa9738d..49b4f19 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "postcss": "^8.4.39", "tailwindcss": "^3.4.6", "typescript": "^5.5.3", + "unplugin-fonts": "^1.1.1", "vite": "^5.3.4", "vitest": "^2.0.3" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d0219e..f3a94c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,6 +97,9 @@ devDependencies: typescript: specifier: ^5.5.3 version: 5.5.3 + unplugin-fonts: + specifier: ^1.1.1 + version: 1.1.1(vite@5.3.4) vite: specifier: ^5.3.4 version: 5.3.4 @@ -1750,6 +1753,12 @@ packages: tinyrainbow: 1.2.0 dev: true + /acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} @@ -3437,6 +3446,30 @@ packages: engines: {node: '>= 4.0.0'} dev: true + /unplugin-fonts@1.1.1(vite@5.3.4): + resolution: {integrity: sha512-/Aw/rL9D2aslGGM0vi+2R2aG508RSwawLnnBuo+JDSqYc4cHJO1R1phllhN6GysEhBp/6a4B6+vSFPVapWyAAw==} + peerDependencies: + '@nuxt/kit': ^3.0.0 + vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + dependencies: + fast-glob: 3.3.2 + unplugin: 1.11.0 + vite: 5.3.4 + dev: true + + /unplugin@1.11.0: + resolution: {integrity: sha512-3r7VWZ/webh0SGgJScpWl2/MRCZK5d3ZYFcNaeci/GQ7Teop7zf0Nl2pUuz7G21BwPd9pcUPOC5KmJ2L3WgC5g==} + engines: {node: '>=14.0.0'} + dependencies: + acorn: 8.12.1 + chokidar: 3.6.0 + webpack-sources: 3.2.3 + webpack-virtual-modules: 0.6.2 + dev: true + /update-browserslist-db@1.1.0(browserslist@4.23.2): resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} hasBin: true @@ -3632,6 +3665,15 @@ packages: engines: {node: '>=12'} dev: true + /webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + dev: true + + /webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + dev: true + /whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} diff --git a/src/assets/fonts/GeistMonoVF.woff b/src/assets/fonts/GeistMonoVF.woff new file mode 100644 index 0000000000000000000000000000000000000000..f2ae185cbfd16946a534d819e9eb03924abbcc49 GIT binary patch literal 67864 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ϣ%UY8`RWp~V-U_7 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 CLkb=M literal 0 HcmV?d00001 diff --git a/src/assets/fonts/GeistMonoVF.woff2 b/src/assets/fonts/GeistMonoVF.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..fb2f024aca0a7bfd14a88b7fa74129f107a7d7ee GIT binary patch literal 58048 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 Oe&? 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 { blur && "filter blur-[5px] opacity-25", )} > -
+
{ return { build: { - outDir: 'build', + outDir: "build", }, - plugins: [tsconfigPaths(), react()], + plugins: [ + tsconfigPaths(), + react(), + Unfonts({ + custom: { + families: [ + { + name: "Geist Mono", + src: "./src/assets/fonts/GeistMonoVF.woff2", + }, + ], + }, + }), + ], test: { globals: true, - environment: 'jsdom', - setupFiles: './src/setupTests.ts', + environment: "jsdom", + setupFiles: "./src/setupTests.ts", }, }; });