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/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) 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/index.html b/index.html index 8f0dbe7..e816bce 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + @@ -16,6 +16,6 @@
- + 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/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 2f0334a..49b4f19 100644 --- a/package.json +++ b/package.json @@ -5,21 +5,28 @@ "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", - "theme-ui": "^0.3.1", - "vite-jsconfig-paths": "^2.0.1", + "tailwind-merge": "^2.4.0", + "tailwindcss-animate": "^1.0.7", + "vite-tsconfig-paths": "^4.3.2", "web-vitals": "^0.2.4" }, "scripts": { "start": "vite", - "build": "vite build", - "serve": "vite preview" + "build": "tsc && vite build", + "serve": "vite preview", + "test": "vitest" }, "eslintConfig": { "extends": [ @@ -40,8 +47,21 @@ ] }, "devDependencies": { + "@biomejs/biome": "1.8.3", "@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", "@vitejs/plugin-react": "^4.3.1", - "vite": "^5.3.4" + "@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", + "unplugin-fonts": "^1.1.1", + "vite": "^5.3.4", + "vitest": "^2.0.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfbeaec..f3a94c3 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,26 +44,68 @@ dependencies: react-hotkeys-hook: specifier: ^2.3.1 version: 2.4.1(react-dom@17.0.2)(react@17.0.2) - 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) + tailwind-merge: + specifier: ^2.4.0 + version: 2.4.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.6) + 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 devDependencies: + '@biomejs/biome': + specifier: 1.8.3 + version: 1.8.3 '@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 '@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) + 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 + unplugin-fonts: + specifier: ^1.1.1 + version: 1.1.1(vite@5.3.4) vite: specifier: ^5.3.4 version: 5.3.4 + vitest: + specifier: ^2.0.3 + version: 2.0.3(jsdom@24.1.0) packages: @@ -56,6 +113,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'} @@ -288,9 +349,97 @@ packages: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - /@cush/relative@1.0.0: - resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==} - dev: false + /@bcoe/v8-coverage@0.2.3: + 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==} @@ -338,12 +487,6 @@ packages: 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==} peerDependencies: @@ -366,10 +509,6 @@ packages: 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==} dev: true @@ -388,34 +527,6 @@ packages: 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==} - 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 - 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==} - 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 @@ -616,6 +727,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'} @@ -626,21 +765,23 @@ 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==} 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==} @@ -663,7 +804,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==} @@ -690,222 +830,671 @@ 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 + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} dependencies: - react: 17.0.2 - dev: false + '@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 - dev: false - optional: true - - /@rollup/rollup-android-arm-eabi@4.18.1: - resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==} - cpu: [arm] - os: [android] - requiresBuild: true - optional: true - - /@rollup/rollup-android-arm64@4.18.1: - resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==} - cpu: [arm64] - os: [android] - requiresBuild: true - optional: true - - /@rollup/rollup-darwin-arm64@4.18.1: - resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - optional: true - - /@rollup/rollup-darwin-x64@4.18.1: - resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==} - cpu: [x64] - os: [darwin] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-arm-gnueabihf@4.18.1: - resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==} - cpu: [arm] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-arm-musleabihf@4.18.1: - resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==} - cpu: [arm] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-arm64-gnu@4.18.1: - resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-arm64-musl@4.18.1: - resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-powerpc64le-gnu@4.18.1: - resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==} - cpu: [ppc64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-riscv64-gnu@4.18.1: - resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-s390x-gnu@4.18.1: - resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==} - cpu: [s390x] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-x64-gnu@4.18.1: - resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-x64-musl@4.18.1: - resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-win32-arm64-msvc@4.18.1: - resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==} - cpu: [arm64] - os: [win32] - requiresBuild: true - optional: true - - /@rollup/rollup-win32-ia32-msvc@4.18.1: - resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==} - cpu: [ia32] - os: [win32] - requiresBuild: true - optional: true - - /@rollup/rollup-win32-x64-msvc@4.18.1: - resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==} - cpu: [x64] - os: [win32] - requiresBuild: true optional: true - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + /@radix-ui/number@1.1.0: + resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} dev: false - /@styled-system/background@5.1.2: - resolution: {integrity: sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==} - dependencies: - '@styled-system/core': 5.1.2 + /@radix-ui/primitive@1.1.0: + resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} dev: false - /@styled-system/border@5.1.5: - resolution: {integrity: sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==} + /@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: - '@styled-system/core': 5.1.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) + '@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 - /@styled-system/color@5.1.2: - resolution: {integrity: sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==} + /@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: - '@styled-system/core': 5.1.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-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 - /@styled-system/core@5.1.2: - resolution: {integrity: sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==} + /@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: - object-assign: 4.1.1 - dev: false - - /@styled-system/css@5.1.5: - resolution: {integrity: sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==} + '@types/react': 18.3.3 + react: 17.0.2 dev: false - /@styled-system/flexbox@5.1.2: - resolution: {integrity: sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==} + /@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: - '@styled-system/core': 5.1.2 + '@types/react': 18.3.3 + react: 17.0.2 dev: false - /@styled-system/grid@5.1.2: - resolution: {integrity: sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==} + /@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: - '@styled-system/core': 5.1.2 + '@types/react': 18.3.3 + react: 17.0.2 dev: false - /@styled-system/layout@5.1.2: - resolution: {integrity: sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==} - dependencies: - '@styled-system/core': 5.1.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 - /@styled-system/position@5.1.2: - resolution: {integrity: sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==} + /@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: - '@styled-system/core': 5.1.2 + '@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 - /@styled-system/shadow@5.1.2: - resolution: {integrity: sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==} + /@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: - '@styled-system/core': 5.1.2 + '@types/react': 18.3.3 + react: 17.0.2 dev: false - /@styled-system/should-forward-prop@5.1.5: - resolution: {integrity: sha512-+rPRomgCGYnUIaFabDoOgpSDc4UUJ1KsmlnzcEp0tu5lFrBQKgZclSo18Z1URhaZm7a6agGtS5Xif7tuC2s52Q==} + /@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: - '@emotion/is-prop-valid': 0.8.8 - '@emotion/memoize': 0.7.5 - styled-system: 5.1.5 + '@types/react': 18.3.3 + react: 17.0.2 dev: false - /@styled-system/space@5.1.2: - resolution: {integrity: sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==} + /@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: - '@styled-system/core': 5.1.2 + '@radix-ui/rect': 1.1.0 + '@types/react': 18.3.3 + react: 17.0.2 dev: false - /@styled-system/typography@5.1.2: - resolution: {integrity: sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==} + /@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: - '@styled-system/core': 5.1.2 + '@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 - /@styled-system/variant@5.1.5: - resolution: {integrity: sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==} + /@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: - '@styled-system/core': 5.1.2 - '@styled-system/css': 5.1.5 + '@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/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==} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-android-arm64@4.18.1: + resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-arm64@4.18.1: + resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-x64@4.18.1: + resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.18.1: + resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.18.1: + resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.18.1: + resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.18.1: + resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.18.1: + resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.18.1: + resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.18.1: + resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.18.1: + resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.18.1: + resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.18.1: + resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.18.1: + resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.18.1: + resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + /@testing-library/dom@10.3.2: resolution: {integrity: sha512-0bxIdP9mmPiOJ6wHLj8bdJRq+51oddObeCGdEf6PNEhYd93ZYAN+lPRnEOVFtheVwDM7+p+tza3LAQgp0PTudg==} engines: {node: '>=18'} @@ -940,7 +1529,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 @@ -972,86 +1561,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 - dev: false - - /@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==} - 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==} - 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 @@ -1094,54 +1603,60 @@ 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/json5@0.0.29: - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - 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==} dev: false + /@types/prop-types@15.7.12: + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + + /@types/react-dom@18.3.0: + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + dependencies: + '@types/react': 18.3.3 + + /@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 + /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - dev: false - /@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/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==} @@ -1153,7 +1668,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==} @@ -1171,15 +1685,96 @@ 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 + + /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'} + 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==} @@ -1196,15 +1791,29 @@ 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==} engines: {node: '>=12'} - dev: false /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: @@ -1221,6 +1830,31 @@ 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 + + /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: @@ -1252,20 +1886,21 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: false + + /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: false /braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} dependencies: fill-range: 7.1.1 - dev: false /browserslist@4.23.2: resolution: {integrity: sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==} @@ -1278,15 +1913,35 @@ 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'} dev: false + /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 + /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,9 +1965,43 @@ 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 + + /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: @@ -1332,10 +2021,16 @@ 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'} - dev: false /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -1368,16 +2063,38 @@ 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 + /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'} + dependencies: + rrweb-cssom: 0.6.0 + dev: true + /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==} + + /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,20 +2106,38 @@ packages: dependencies: ms: 2.1.2 - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - dev: false + /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 + + /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'} 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} - dev: false + + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} /dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -1410,7 +2145,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 +2152,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==} @@ -1472,7 +2209,27 @@ 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==} + 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==} @@ -1483,14 +2240,27 @@ packages: jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 jest-util: 29.7.0 - dev: false + + /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'} dependencies: to-regex-range: 5.0.1 - dev: false /find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} @@ -1502,7 +2272,19 @@ 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 + + /fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + dev: true /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -1513,17 +2295,38 @@ 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==} engines: {node: '>=6.9.0'} dev: true - /glob-regex@0.3.2: - resolution: {integrity: sha512-m5blUd3/OqDTWwzBBtWBPrGlAzatRywHameHeekAZyZrskYouOGdNB8T/q6JucucvJXtOuyHIn0/Yia7iDasDw==} + /get-func-name@2.0.2: + 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 @@ -1534,7 +2337,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==} @@ -1546,7 +2348,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==} @@ -1561,12 +2362,54 @@ 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==} 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'} @@ -1580,30 +2423,90 @@ 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==} dev: false + /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 - dev: false + + /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: false + + /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==} 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 +2514,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==} @@ -1621,12 +2523,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==} @@ -1636,7 +2536,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==} @@ -1651,7 +2550,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==} @@ -1663,11 +2561,54 @@ packages: ci-info: 3.9.0 graceful-fs: 4.2.11 picomatch: 2.3.1 - dev: false + + /jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true /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'} @@ -1677,22 +2618,22 @@ packages: 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==} engines: {node: '>=6'} 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==} - dev: false /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -1705,9 +2646,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,13 +2666,58 @@ 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 + + /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'} dependencies: braces: 3.0.3 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==} @@ -1738,16 +2729,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: false - - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: false /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==} @@ -1758,7 +2743,6 @@ packages: 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==} @@ -1769,14 +2753,43 @@ 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} + 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 + + /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'} + 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,14 +2808,23 @@ 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==} - dev: false /path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} @@ -1810,25 +2832,90 @@ 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 - /picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + /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==} + + /picomatch@2.3.1: + 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 - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: false + /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 - /pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - dev: false + /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==} @@ -1864,7 +2951,22 @@ 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==} + 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 + + /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==} @@ -1894,13 +2996,64 @@ 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==} 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'} @@ -1909,15 +3062,16 @@ packages: object-assign: 4.1.1 dev: false - /recrawl-sync@2.2.3: - resolution: {integrity: sha512-vSaTR9t+cpxlskkdUFrsEpnf67kSmPk66yAGT1fZPrDudxQjoMzPgQhSMImQ0pAw5k0NPirefQfhopSjhdUtpQ==} + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} 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 + 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==} @@ -1930,6 +3084,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'} @@ -1942,7 +3100,10 @@ packages: is-core-module: 2.14.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: false + + /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==} @@ -1969,6 +3130,30 @@ 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 + + /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 + + /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,27 +3166,33 @@ 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==} engines: {node: '>=8'} - dev: false /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} @@ -2022,7 +3213,14 @@ 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==} + 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==} @@ -2031,7 +3229,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,26 +3237,23 @@ 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==} @@ -2068,23 +3262,11 @@ packages: min-indent: 1.0.1 dev: false - /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 - dev: false + /strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + dependencies: + js-tokens: 9.0.0 + dev: true /stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} @@ -2102,7 +3284,6 @@ packages: 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==} @@ -2119,36 +3300,91 @@ packages: /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: true + + /tailwind-merge@2.4.0: + resolution: {integrity: sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==} dev: false - /theme-ui@0.3.5(react@17.0.2): - resolution: {integrity: sha512-yxooGhvkdjFDotDeIFehKo5k6NnLZ3gsLSe8EDe2aDcoWqg1mZjkjjr8EYtVCrK3mk/tYz97AT5BpEnUfamNCQ==} + /tailwindcss-animate@1.0.7(tailwindcss@3.4.6): + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} 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 + 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'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + dev: true + /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 + + /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==} @@ -2159,28 +3395,80 @@ packages: engines: {node: '>=8.0'} dependencies: 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 - /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==} + /tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} 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==} - dev: false + + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + 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==} @@ -2193,18 +3481,83 @@ packages: picocolors: 1.0.1 dev: true - /vite-jsconfig-paths@2.0.1(vite@5.3.4): - resolution: {integrity: sha512-rabcTTfKs0MdAsQWcZjbIMo5fcp6jthZce7uFEPgVPgpSY+RNOwjzIJOPES6cB/GJZLSoLGfHM9kt5HNmJvp7A==} + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + 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} + 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-tsconfig-paths@4.3.2(typescript@5.5.3)(vite@5.3.4): + resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: - vite: '>2.0.0-0' + vite: '*' + peerDependenciesMeta: + vite: + optional: true dependencies: debug: 4.3.5 globrex: 0.1.2 - recrawl-sync: 2.2.3 - tsconfig-paths: 3.15.0 + tsconfck: 3.1.1(typescript@5.5.3) vite: 5.3.4 transitivePeerDependencies: - supports-color + - typescript dev: false /vite@5.3.4: @@ -2241,17 +3594,121 @@ 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 + + /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'} + 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 +3717,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 +3725,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==} @@ -2279,3 +3756,8 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} dev: false + + /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.jsx b/src/App.jsx deleted file mode 100644 index 29ff36c..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,125 +0,0 @@ -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, theme } from './utils/constant' -import { useCallback } from 'react' -import Statistic from 'components/Statistic' -import Timer from 'components/Timer' - -const App = () => { - const { config, dispatch, setTheme } = useConfigStore() - - const handleSelectThemeChange = useCallback( - (e) => { - const selectedValue = e.target.options[e.target.selectedIndex].value - setTheme(selectedValue) - }, - [setTheme] - ) - - const handleSelectLanguageChange = useCallback( - (e) => { - const selectedValue = e.target.options[e.target.selectedIndex].value - dispatch({ - type: CHANGE_LANGUAGE, - payload: { lang: selectedValue }, - }) - }, - [dispatch] - ) - - return ( - - - Typefaster - - - - - - - - - - - - - © 2020 / Dandi Wiratsangka /{' '} - - Github - - - - - - - - - ) -} - -export default App diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..3c3e088 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,45 @@ +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 useConfigStore from "./store/config"; +import { TypingStoreProvider } from "./store/typing"; + +const App = () => { + const { config } = useConfigStore(); + + return ( +
+
+

Typefaster

+
+ + +
+
+
+ + + + + +
+
+
+ © 2020 / Dandi Wiratsangka /{" "} + + Github + +
+
+
+ ); +}; + +export default App; diff --git a/src/assets/fonts/GeistMonoVF.woff b/src/assets/fonts/GeistMonoVF.woff new file mode 100644 index 0000000..f2ae185 Binary files /dev/null and b/src/assets/fonts/GeistMonoVF.woff differ diff --git a/src/assets/fonts/GeistMonoVF.woff2 b/src/assets/fonts/GeistMonoVF.woff2 new file mode 100644 index 0000000..fb2f024 Binary files /dev/null and b/src/assets/fonts/GeistMonoVF.woff2 differ diff --git a/src/components/Hotkey.jsx b/src/components/Hotkey.jsx deleted file mode 100644 index ef4ae34..0000000 --- a/src/components/Hotkey.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Box } from 'theme-ui' - -const Hotkey = ({ children }) => { - return ( - - {children} - - ) -} - -export default Hotkey diff --git a/src/components/Hotkey.tsx b/src/components/Hotkey.tsx new file mode 100644 index 0000000..0da3dc9 --- /dev/null +++ b/src/components/Hotkey.tsx @@ -0,0 +1,9 @@ +const Hotkey = ({ children }: React.PropsWithChildren) => { + return ( + + {children} + + ); +}; + +export default Hotkey; diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..4bc9ac1 --- /dev/null +++ b/src/components/LanguageSwitcher.tsx @@ -0,0 +1,64 @@ +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/Letter.jsx b/src/components/Letter.jsx deleted file mode 100644 index 0a8c468..0000000 --- a/src/components/Letter.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { memo } from 'react' -import { Text } from 'theme-ui' - -const Letter = memo( - ({ letter, cursor }) => ( - - {letter.typed || letter.original} - - ), - (prevProps, nextProps) => - prevProps.letter.typed === nextProps.letter.typed && - prevProps.cursor === nextProps.cursor -) - -export default Letter diff --git a/src/components/Letter.tsx b/src/components/Letter.tsx new file mode 100644 index 0000000..d7fddd6 --- /dev/null +++ b/src/components/Letter.tsx @@ -0,0 +1,31 @@ +import { cn } from "lib/utils"; +import { memo } from "react"; +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 && + prevProps.cursor === nextProps.cursor, +); + +export default LetterComponent; diff --git a/src/components/MockThemeUI.jsx b/src/components/MockThemeUI.jsx deleted file mode 100644 index 05a16e6..0000000 --- a/src/components/MockThemeUI.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { ThemeProvider } from 'theme-ui' -import theme from 'theme' - -function MockThemeUI({ children }) { - return ( - - {children} - - ) -} - -export default MockThemeUI; \ No newline at end of file diff --git a/src/components/Statistic.jsx b/src/components/Statistic.jsx deleted file mode 100644 index 4c64e1d..0000000 --- a/src/components/Statistic.jsx +++ /dev/null @@ -1,37 +0,0 @@ -/** @jsxRuntime classic */ -/** @jsx jsx */ -import { jsx } from 'theme-ui' -import { Flex } from 'theme-ui' -import useTypingStore from '../store/typing' - -const ListItem = (props) => ( -
-) - -const Statistic = () => { - const { typing } = useTypingStore() - const { statistics } = typing - const { accuracy, wpm, correctWords, incorrectWords } = statistics - - return ( - - WPM : {wpm >= 0 ? wpm : '-'} - Acc : {accuracy >= 0 ? accuracy : '-'} - - Correct words : {correctWords >= 0 ? correctWords : '-'} - - - Incorrect words: {incorrectWords >= 0 ? incorrectWords : '-'} - - {/* Time: {time >= 0 ? time : '-'} */} - - ) -} - -export default Statistic diff --git a/src/components/Statistic.tsx b/src/components/Statistic.tsx new file mode 100644 index 0000000..d366336 --- /dev/null +++ b/src/components/Statistic.tsx @@ -0,0 +1,38 @@ +import useTypingStore from "../store/typing"; + +const ListItem = ({ + title, + children, + ...props +}: React.PropsWithChildren<{ title: string }>) => { + return ( +
+
{title}
+
{children}
+
+ ); +}; + +const Statistic = () => { + const { typing } = useTypingStore(); + const { statistics } = typing; + const { accuracy, wpm, correctWords, incorrectWords } = statistics; + + return ( +
+ {wpm >= 0 ? wpm : "-"} + + {accuracy >= 0 ? `${accuracy * 100}%` : "-"} + + + {correctWords >= 0 ? correctWords : "-"} + + + {incorrectWords >= 0 ? incorrectWords : "-"} + + {/* Time: {time >= 0 ? time : '-'} */} +
+ ); +}; + +export default Statistic; diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx new file mode 100644 index 0000000..992cd17 --- /dev/null +++ b/src/components/ThemeSwitcher.tsx @@ -0,0 +1,68 @@ +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.jsx b/src/components/Timer.jsx deleted file mode 100644 index d527f0c..0000000 --- a/src/components/Timer.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useEffect, useRef, useState } from 'react' -import { Flex } from 'theme-ui' -import actionType from '../store/typing/action' -import useTypingStore from '../store/typing' - -const Timer = ({ duration }) => { - const { typing, dispatch } = useTypingStore() - const [timerCount, setTimerCount] = useState(duration) - const timerRef = useRef() - - useEffect(() => { - if (typing.typingStatus === 'pending') { - setTimerCount(duration) - } - - if (typing.typingStatus === 'started') { - timerRef.current = setInterval( - () => setTimerCount((val) => val - 1), - 1000 - ) - setTimeout(() => { - dispatch({ type: actionType.DONE_TYPING }) - }, duration * 1000) - } else if (typing.typingStatus === 'done') { - clearInterval(timerRef.current) - } - return () => clearInterval(timerRef.current) - }, [typing.typingStatus, dispatch, duration]) - - return ( - - {timerCount === 0 ? 'Times up!' : timerCount} - - ) -} - -export default Timer diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx new file mode 100644 index 0000000..efb76a4 --- /dev/null +++ b/src/components/Timer.tsx @@ -0,0 +1,61 @@ +import { cn } from "lib/utils"; +import { useEffect, useRef, useState } from "react"; +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(); + + useEffect(() => { + if (typing.typingStatus === "pending") { + setTimerCount(duration); + } + + if (typing.typingStatus === "started") { + timerRef.current = setInterval( + () => setTimerCount((val) => val - 1), + 1000, + ); + setTimeout(() => { + dispatch({ type: actionType.DONE_TYPING }); + }, duration * 1000); + } else if (typing.typingStatus === "done") { + clearInterval(timerRef.current); + } + return () => clearInterval(timerRef.current); + }, [typing.typingStatus, dispatch, duration]); + + return ( +
+
+ {timerCount === 0 ? "Times up!" : timerCount} +
+ +
+ ); +}; + +const ProgressBar = ({ + duration, + status, +}: { duration: number; status: string }) => { + return ( +
+
+
+ ); +}; + +export default Timer; diff --git a/src/components/TypingArea.jsx b/src/components/TypingArea.jsx deleted file mode 100644 index b5dd82c..0000000 --- a/src/components/TypingArea.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import React, { memo, useCallback, useEffect, useRef, useState } from 'react' -import { Box, Flex, Input } from 'theme-ui' -import Word from './Word' -import useTypingStore from '../store/typing' -import actionType from '../store/typing/action' -import { useHotkeys } from 'react-hotkeys-hook' -import Hotkey from './Hotkey' - -const DISABLED_KEYS = [ - 'arrowup', - 'Arrowdown', - 'arrowleft', - 'arrowright', - 'home', - 'end', - 'tab', -] - -const DISABLED_CTRL = ['a', 'c', 'v'] - -const TypingArea = memo(() => { - const { typing, dispatch } = useTypingStore() - const inputRef = useRef() - const [blur, setBlur] = useState(false) - - useHotkeys('shift+Enter', () => { - dispatch({ type: actionType.REFRESH_TYPING_STORE }) - }) - - useHotkeys('shift+f', () => focusInput(), { - keydown: false, - keyup: true, - }) - - const focusInput = useCallback(() => { - return inputRef.current.focus() - }, []) - - const handleKeyDown = useCallback((e) => { - if (DISABLED_KEYS.includes(e.key.toLowerCase())) { - e.preventDefault() - return - } - - if (DISABLED_CTRL.includes(e.key.toLowerCase()) && e.ctrlKey) { - e.preventDefault() - return - } - }, []) - - const handleOverlayClick = useCallback( - (e) => { - if (typing.typingStatus === 'done') - dispatch({ type: actionType.REFRESH_TYPING_STORE }) - - focusInput() - }, - [dispatch, typing.typingStatus, focusInput] - ) - - const handleInputChange = useCallback( - (e) => { - if (typing.typingStatus === 'pending') { - dispatch({ type: actionType.START_TYPING }) - } - - const value = e.target.value - if (value.endsWith(' ')) { - const seconds = (Date.now() - typing.startTime) / 1000 - dispatch({ - type: actionType.GOTO_NEXT_WORD, - payload: { typingMinutes: seconds }, - }) - } else { - dispatch({ - type: actionType.UPDATE_WORD, - payload: { inputValue: value }, - }) - } - }, - [dispatch, typing.typingStatus, typing.startTime] - ) - - useEffect(() => { - const isInputFocused = - inputRef.current && inputRef.current === document.activeElement - - if (isInputFocused) setBlur(false) - else setBlur(true) - }, [inputRef]) - - useEffect(() => { - if (typing.typingStatus === 'done') { - setBlur(true) - } - if (typing.typingStatus === 'pending') { - focusInput() - } - }, [typing.typingStatus, focusInput]) - - return ( - - - - { - // focusInput() - setBlur(true) - }} - onFocus={() => setBlur(false)} - autoFocus - sx={{ - width: 10, - position: 'absolute', - opacity: 0, - }} - onKeyDown={handleKeyDown} - disabled={typing.typingStatus === 'done'} - /> - {typing.wordSequence.map((w, i) => ( - - ))} - - - {blur && ( - - {typing.typingStatus === 'done' ? ( - <> -
- Click or Shift+Enter to start new test -
- - ) : ( - - Click or Shift+F to focus - - )} -
- )} -
- ) -}) - -export default TypingArea diff --git a/src/components/TypingArea.tsx b/src/components/TypingArea.tsx new file mode 100644 index 0000000..929fa27 --- /dev/null +++ b/src/components/TypingArea.tsx @@ -0,0 +1,173 @@ +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 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", +]; + +const DISABLED_CTRL = ["a", "c", "v"]; + +const TypingArea = memo(() => { + const { typing, dispatch } = useTypingStore(); + const inputRef = useRef(null); + const [blur, setBlur] = useState(false); + + useHotkeys("shift+Enter", () => { + dispatch({ type: actionType.REFRESH_TYPING_STORE }); + }); + + useHotkeys("shift+f", () => focusInput(), { + keydown: false, + keyup: true, + }); + + const focusInput = useCallback(() => { + if (!inputRef.current) return; + + return inputRef.current.focus(); + }, []); + + 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; + } + }, + [], + ); + + const handleOverlayClick = useCallback( + (e: React.MouseEvent) => { + if (typing.typingStatus === "done") + dispatch({ type: actionType.REFRESH_TYPING_STORE }); + + focusInput(); + }, + [dispatch, typing.typingStatus, focusInput], + ); + + const handleInputChange = useCallback( + (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 ?? 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], + ); + + useEffect(() => { + const isInputFocused = + inputRef.current && inputRef.current === document.activeElement; + + if (isInputFocused) setBlur(false); + else setBlur(true); + }, []); + + useEffect(() => { + if (typing.typingStatus === "done") { + setBlur(true); + } + if (typing.typingStatus === "pending") { + focusInput(); + } + }, [typing.typingStatus, focusInput]); + + return ( +
+
+
+ { + // focusInput() + setBlur(true); + }} + onFocus={() => setBlur(false)} + 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) => ( + + ))} +
+
+ {blur && ( + // biome-ignore lint/a11y/useKeyWithClickEvents: +
+ {typing.typingStatus === "done" ? ( + <> +
+ Click or Shift+Enter to start new test +
+ + ) : ( + + Click or Shift+F to focus + + )} +
+ )} +
+ ); +}); + +export default TypingArea; diff --git a/src/components/Word.jsx b/src/components/Word.jsx deleted file mode 100644 index a9e1eeb..0000000 --- a/src/components/Word.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import { memo } from 'react' -import { Text } from 'theme-ui' -import { isCorrectlyTyped } from '../utils/Word' -import Letter from './Letter' - -const Word = memo( - ({ word, cursorIndex }) => { - return ( - word.show && ( - - ({ - display: 'inline-block', - textDecorationColor: theme.colors.incorrectLetter, - textDecoration: - word.isTyped && !isCorrectlyTyped(word) - ? 'line-through' - : 'none', - })} - > - {word.letterSequence.map((l, i) => ( - - ))} - - = word.originalWord.length} - letter={{ - original: ' ', - }} - /> - - ) - ) - }, - (prevProps, nextProps) => - prevProps.cursorIndex === nextProps.cursorIndex && - prevProps.word.show === nextProps.word.show && - prevProps.word.isTyped === nextProps.word.isTyped -) - -export default Word diff --git a/src/components/Word.tsx b/src/components/Word.tsx new file mode 100644 index 0000000..8626ecf --- /dev/null +++ b/src/components/Word.tsx @@ -0,0 +1,44 @@ +import { cn } from "lib/utils"; +import { memo } from "react"; +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 }) => { + return ( + word.show && ( + + + {word.letterSequence.map((l, i) => ( + + ))} + + = word.originalWord.length} + letter={new Letter(" ")} + /> + + ) + ); + }, + (prevProps, nextProps) => + prevProps.cursorIndex === nextProps.cursorIndex && + prevProps.word.show === nextProps.word.show && + prevProps.word.isTyped === nextProps.word.isTyped, +); + +export default WordComponent; diff --git a/src/components/__tests__/Letter.test.jsx b/src/components/__tests__/Letter.test.jsx deleted file mode 100644 index ee8dec7..0000000 --- a/src/components/__tests__/Letter.test.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react' -import { render, screen } from '@testing-library/react' -import Letter from 'utils/Letter' -import LetterComponent from 'components/Letter' -import MockThemeUI from 'components/MockThemeUI' - -import { matchers } from '@emotion/jest' - -// Add the custom matchers provided by '@emotion/jest' -expect.extend(matchers) - -test('Letter component render correct letter', () => { - let letter = new Letter('a', 'b') - render() - expect(screen.getByTestId('letter')).toHaveTextContent('b') -}) - -test('Letter component render correct letter - 2', () => { - const letter = new Letter('a') - render() - expect(screen.getByTestId('letter')).toHaveTextContent('a') -}) - -test('Letter component should render caret', () => { - const letter = new Letter('a') - render( - - - - ) - expect(screen.getByTestId('letter')).toHaveStyleRule('content','"|"',{ - target: ':before' - }) -}) - -test('Letter component should not render caret', () => { - const letter = new Letter('a') - render( - - - - ) - expect(screen.getByTestId('letter')).not.toHaveStyleRule('content','"|"',{ - target: ':before' - }) -}) \ No newline at end of file diff --git a/src/components/__tests__/Letter.test.tsx b/src/components/__tests__/Letter.test.tsx new file mode 100644 index 0000000..3d3d14b --- /dev/null +++ b/src/components/__tests__/Letter.test.tsx @@ -0,0 +1,35 @@ +import { render, screen } from "@testing-library/react"; +import LetterComponent from "components/Letter"; +import Letter from "utils/Letter"; +import { expect, test } from "vitest"; + +import { matchers } from "@emotion/jest"; + +// Add the custom matchers provided by '@emotion/jest' + +// biome-ignore lint/suspicious/noExplicitAny: +expect.extend(matchers as any); + +test("Letter component render correct letter", () => { + const letter = new Letter("a", "b"); + render(); + expect(screen.getByTestId("letter").textContent).toBe("b"); +}); + +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")).toHaveClass("before:content-['|']"); +}); + +test("Letter component should not render caret", () => { + const letter = new Letter("a"); + render(); + expect(screen.getByTestId("letter")).not.toHaveClass("before:content-['|']"); +}); diff --git a/src/components/__tests__/Word.test.jsx b/src/components/__tests__/Word.test.jsx deleted file mode 100644 index 94a4663..0000000 --- a/src/components/__tests__/Word.test.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react' -import { render, screen } from '@testing-library/react' -import Word from 'utils/Word' -import WordComponent from 'components/Word' -import MockThemeUI from 'components/MockThemeUI' - -import { matchers } from '@emotion/jest' - -// Add the custom matchers provided by '@emotion/jest' -expect.extend(matchers) - - -test('Word component null if word.show is false', () => { - let word = new Word('javascript') - word.show = false - - render() - expect(screen.queryByTestId('word')).toBe(null) -}) - -test('Word component render correct word', () => { - let word = new Word('javascript') - render( - - - - ) - expect(screen.getByTestId('word')).toHaveTextContent('javascript') -}) - -test('Correctly typed word is not strike through', () => { - let word = new Word('javascript') - word.isTyped = true - word.letterSequence = word.letterSequence.map(letter=>({ - ...letter, - 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=>({ - ...letter, - status: 'correct' - })) - - word.letterSequence[2].status = 'incorrect' - - render( - - - - ) - expect(screen.getByTestId('word')).toHaveStyleRule('text-decoration',`line-through`) -}) diff --git a/src/components/__tests__/Word.test.tsx b/src/components/__tests__/Word.test.tsx new file mode 100644 index 0000000..63d3dc0 --- /dev/null +++ b/src/components/__tests__/Word.test.tsx @@ -0,0 +1,51 @@ +import { render, screen } from "@testing-library/react"; +import WordComponent from "components/Word"; +import Word from "utils/Word"; +import { expect, test } from "vitest"; + +import { matchers } from "@emotion/jest"; + +// Add the custom matchers provided by '@emotion/jest' + +// biome-ignore lint/suspicious/noExplicitAny: +expect.extend(matchers as any); + +test("Word component null if word.show is false", () => { + const word = new Word("javascript"); + word.show = false; + + render(); + expect(screen.queryByTestId("word")).toBe(null); +}); + +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", () => { + const word = new Word("javascript"); + word.isTyped = true; + word.letterSequence = word.letterSequence.map((letter) => ({ + ...letter, + status: "correct", + })); + + render(); + expect(screen.getByTestId("word")).not.toHaveClass("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", + })); + + word.letterSequence[2].status = "incorrect"; + + render(); + expect(screen.getByTestId("word")).toHaveClass("line-through"); +}); diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..e33018f --- /dev/null +++ b/src/index.css @@ -0,0 +1,142 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + [data-theme="oneDarkPro"] { + --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: #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"] { + --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="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="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-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/index.jsx b/src/index.jsx deleted file mode 100644 index 9bd6b72..0000000 --- a/src/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -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 reportWebVitals from './reportWebVitals' - -ReactDOM.render( - - - - - - - - , - document.getElementById('root') -) - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -// reportWebVitals() diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..9a0e67b --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./App"; +import { ConfigStoreProvider } from "./store/config"; + +import "unfonts.css"; +import "./index.css"; +// import reportWebVitals from './reportWebVitals' + +ReactDOM.render( + + + + + , + document.getElementById("root"), +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +// reportWebVitals() 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.js b/src/reportWebVitals.ts similarity index 59% rename from src/reportWebVitals.js rename to src/reportWebVitals.ts index 5253d3a..3e710fe 100644 --- a/src/reportWebVitals.js +++ b/src/reportWebVitals.ts @@ -1,6 +1,8 @@ -const reportWebVitals = onPerfEntry => { +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.js b/src/setupTests.js deleted file mode 100644 index 8f2609b..0000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// 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'; diff --git a/src/setupTests.ts b/src/setupTests.ts new file mode 100644 index 0000000..a6676b0 --- /dev/null +++ b/src/setupTests.ts @@ -0,0 +1,13 @@ +// @ts-ignore +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); + +// Run cleanup after each test case (e.g., clearing jsdom) +afterEach(() => { + cleanup(); +}); diff --git a/src/store/config/action.js b/src/store/config/action.js deleted file mode 100644 index ae1736b..0000000 --- a/src/store/config/action.js +++ /dev/null @@ -1,13 +0,0 @@ -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, -} - -export default actionType diff --git a/src/store/config/action.ts b/src/store/config/action.ts new file mode 100644 index 0000000..0ac2e85 --- /dev/null +++ b/src/store/config/action.ts @@ -0,0 +1,18 @@ +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"; + +const actionType = { + CHANGE_LANGUAGE, + CHANGE_MODE, + CHANGE_DURATION, + CHANGE_THEME, +} 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.jsx b/src/store/config/index.jsx deleted file mode 100644 index 454ee10..0000000 --- a/src/store/config/index.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import { createContext, useContext, useReducer } from 'react' -import configReducer from './reducer' -import { createConfigStore } from '../../utils/store' -import { useColorMode } from 'theme-ui' - -const initialStore = createConfigStore({}) - -export const ConfigStoreContext = createContext() - -export const ConfigStoreProvider = ({ children }) => { - const [config, dispatch] = useReducer(configReducer, initialStore) - const [theme, setTheme] = useColorMode() - - return ( - - {children} - - ) -} - -const useConfigStore = () => useContext(ConfigStoreContext) -export default useConfigStore diff --git a/src/store/config/index.tsx b/src/store/config/index.tsx new file mode 100644 index 0000000..02cc0a5 --- /dev/null +++ b/src/store/config/index.tsx @@ -0,0 +1,61 @@ +import { + type Dispatch, + createContext, + useContext, + useEffect, + useReducer, +} from "react"; +import type { Language, 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; + dispatch: Dispatch; +} + +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 } }); + + const lang = localStorage.getItem("lang") as Language | null; + if (!lang) return; + dispatch({ type: "CHANGE_LANGUAGE", payload: { lang } }); + }, []); + + return ( + + {children} + + ); +}; + +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.js b/src/store/config/reducer.js deleted file mode 100644 index 78801c6..0000000 --- a/src/store/config/reducer.js +++ /dev/null @@ -1,20 +0,0 @@ -import actionType from './action' - -function configReducer(state, { type, payload }) { - switch (type) { - case actionType.CHANGE_LANGUAGE: - return { - ...state, - lang: payload.lang, - } - case actionType.CHANGE_DURATION: - return { - ...state, - duration: payload.duration, - } - default: - break - } -} - -export default configReducer diff --git a/src/store/config/reducer.ts b/src/store/config/reducer.ts new file mode 100644 index 0000000..aaf136f --- /dev/null +++ b/src/store/config/reducer.ts @@ -0,0 +1,36 @@ +import type { Config } from "utils/store"; +import actionType, { type Action } from "./action"; + +function configReducer(state: Config, { type, payload }: Action): Config { + switch (type) { + case actionType.CHANGE_LANGUAGE: { + if (!payload.lang) return state; + + localStorage.setItem("lang", payload.lang); + return { + ...state, + lang: payload.lang, + }; + } + case actionType.CHANGE_DURATION: + return { + ...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, + }; + + default: + return state; + } +} + +export default configReducer; diff --git a/src/store/typing/action.js b/src/store/typing/action.js deleted file mode 100644 index ffd0f01..0000000 --- a/src/store/typing/action.js +++ /dev/null @@ -1,17 +0,0 @@ -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, - UPDATE_WORD, - GOTO_NEXT_WORD, - REFRESH_TYPING_STORE, - START_TYPING, - DONE_TYPING, -} - -export default action diff --git a/src/store/typing/action.ts b/src/store/typing/action.ts new file mode 100644 index 0000000..17ef9ce --- /dev/null +++ b/src/store/typing/action.ts @@ -0,0 +1,22 @@ +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"; + +const action = { + INITIALIZE_TYPING_STORE, + UPDATE_WORD, + GOTO_NEXT_WORD, + REFRESH_TYPING_STORE, + START_TYPING, + DONE_TYPING, +} 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.jsx b/src/store/typing/index.jsx deleted file mode 100644 index d8654dc..0000000 --- a/src/store/typing/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import { createContext, useContext, useEffect, useReducer } from 'react' -import typingReducer from './reducer' -import { createTypingStore } from '../../utils/store' -import { INITIALIZE_TYPING_STORE } from './action' - -const initialStore = createTypingStore({}) - -export const TypingStoreContext = createContext() - -export const TypingStoreProvider = ({ children, lang }) => { - const [typing, dispatch] = useReducer(typingReducer, initialStore) - - useEffect(() => { - fetchLanguageJSON(lang) - .then((languageJSON) => { - dispatch({ - type: INITIALIZE_TYPING_STORE, - payload: { languageJSON }, - }) - }) - .catch((e) => { - console.log(e) - }) - }, [lang]) - - return ( - - {children} - - ) -} - -const useTypingStore = () => useContext(TypingStoreContext) -export default useTypingStore - -async function fetchLanguageJSON(lang) { - const response = await fetch(`/words/lang/${lang}.json`) - return await response.json() -} diff --git a/src/store/typing/index.tsx b/src/store/typing/index.tsx new file mode 100644 index 0000000..2a63b45 --- /dev/null +++ b/src/store/typing/index.tsx @@ -0,0 +1,62 @@ +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 TypingStoreProvider = ({ + children, + lang, +}: React.PropsWithChildren<{ lang: Language }>) => { + const [typing, dispatch] = useReducer(typingReducer, initialStore); + + useEffect(() => { + fetchLanguageJSON(lang) + .then((languageJSON) => { + dispatch({ + type: INITIALIZE_TYPING_STORE, + payload: { languageJSON }, + }); + }) + .catch((e) => { + console.log(e); + }); + }, [lang]); + + return ( + + {children} + + ); +}; + +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: Language) { + const response = await fetch(`/words/lang/${lang}.json`); + return await response.json(); +} diff --git a/src/store/typing/reducer.js b/src/store/typing/reducer.js deleted file mode 100644 index a7eb96f..0000000 --- a/src/store/typing/reducer.js +++ /dev/null @@ -1,154 +0,0 @@ -import actionType from './action' -import { createWordSequence } from '../../utils/Word' -import Letter from '../../utils/Letter' -import { getStatistics } from '../../utils/statistics' -import { createTypingStore } from '../../utils/store' - -function replaceItemInArray(originalArr, index, newItem) { - return originalArr.map((item, i) => (i === index ? newItem : item)) -} - -function hideWord(word) { - return { - ...word, - show: false, - } -} - -function markAsTyped(word) { - return { - ...word, - isTyped: true, - } -} - -function typingReducer(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) - - switch (type) { - case actionType.INITIALIZE_TYPING_STORE: { - return createTypingStore({ - languageJSON: payload.languageJSON, - wordSequence: createWordSequence( - payload.languageJSON.words, - N_WORD_TOBE_GENERATED - ), - }) - } - - case actionType.START_TYPING: - return { - ...state, - typingStatus: 'started', - startTime: Date.now(), - inputValue: '', - } - - case actionType.DONE_TYPING: - return { - ...state, - typingStatus: 'done', - finishTime: Date.now(), - } - - case actionType.UPDATE_WORD: { - if (state.wordSequence.length <= 0) return state - - const inputValueArr = payload.inputValue.split('') - const originalLetterSeq = activeWord.letterSequence.filter( - (l) => l.original - ) - - let updatedLetterSeq = originalLetterSeq.map( - (l, i) => new Letter(l.original, inputValueArr[i]) - ) - - 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 - ), - caretPosition: [wordIndex, inputValueArr.length], - inputValue: payload.inputValue, - } - } - - case actionType.GOTO_NEXT_WORD: { - // if cursor at first letter of word, do nothing - if (letterIndex === 0) return state - - let updatedWord = markAsTyped(activeWord) - let updatedWordSeq = replaceItemInArray( - state.wordSequence, - wordIndex, - updatedWord - ) - - // if new line detected, hide all word in previous line - 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 - - // if new line - if (activeWordY !== nextWordY) { - updatedWordSeq = state.wordSequence.map((w, i) => { - 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 - if (remaining_word_count < APPEND_WORD_TRESHOLD) { - const newlyGeneratedWordSeq = createWordSequence( - state.languageJSON.words, - N_WORD_TOBE_GENERATED - ) - updatedWordSeq = [...updatedWordSeq, ...newlyGeneratedWordSeq] - } - } - } - - // go to next word - return { - ...state, - wordSequence: updatedWordSeq, - caretPosition: [wordIndex + 1, 0], - inputValue: '', - statistics: getStatistics(updatedWordSeq, payload.typingMinutes), - } - } - - case actionType.REFRESH_TYPING_STORE: { - return createTypingStore({ - languageJSON: state.languageJSON, - wordSequence: createWordSequence( - state.languageJSON.words, - N_WORD_TOBE_GENERATED - ), - }) - } - - default: - return state - } -} - -export default typingReducer diff --git a/src/store/typing/reducer.ts b/src/store/typing/reducer.ts new file mode 100644 index 0000000..1e4b122 --- /dev/null +++ b/src/store/typing/reducer.ts @@ -0,0 +1,161 @@ +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) + + switch (type) { + case actionType.INITIALIZE_TYPING_STORE: { + return createTypingStore({ + languageJSON: payload?.languageJSON, + wordSequence: createWordSequence( + payload?.languageJSON?.words || [], + N_WORD_TOBE_GENERATED, + ), + }); + } + + case actionType.START_TYPING: + return { + ...state, + typingStatus: "started", + startTime: Date.now(), + inputValue: "", + }; + + case actionType.DONE_TYPING: + return { + ...state, + typingStatus: "done", + finishTime: Date.now(), + }; + + case actionType.UPDATE_WORD: { + if (state.wordSequence.length <= 0) return state; + + const inputValueArr = payload?.inputValue?.split("") || []; + const originalLetterSeq = activeWord.letterSequence.filter( + (l) => l.original, + ); + + let updatedLetterSeq = originalLetterSeq.map( + (l, i) => new Letter(l.original, inputValueArr[i]), + ); + + 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, + ), + caretPosition: [wordIndex, inputValueArr.length], + inputValue: payload?.inputValue || "", + }; + } + + case actionType.GOTO_NEXT_WORD: { + // if cursor at first letter of word, do nothing + if (letterIndex === 0) return state; + + const updatedWord = markAsTyped(activeWord); + let updatedWordSeq = replaceItemInArray( + state.wordSequence, + wordIndex, + updatedWord, + ); + + // if new line detected, hide all word in previous line + 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; + + // if new line + if (activeWordY !== nextWordY) { + updatedWordSeq = state.wordSequence.map((w, i) => { + 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; + if (remaining_word_count < APPEND_WORD_TRESHOLD) { + const newlyGeneratedWordSeq = createWordSequence( + state.languageJSON?.words || [], + N_WORD_TOBE_GENERATED, + ); + updatedWordSeq = [...updatedWordSeq, ...newlyGeneratedWordSeq]; + } + } + } + + // go to next word + return { + ...state, + wordSequence: updatedWordSeq, + caretPosition: [wordIndex + 1, 0], + inputValue: "", + statistics: getStatistics(updatedWordSeq, payload?.typingMinutes || -1), + }; + } + + case actionType.REFRESH_TYPING_STORE: { + return createTypingStore({ + languageJSON: state.languageJSON, + wordSequence: createWordSequence( + state.languageJSON?.words || [], + N_WORD_TOBE_GENERATED, + ), + }); + } + + default: + return state; + } +}; + +export default typingReducer; diff --git a/src/theme/colors.js b/src/theme/colors.js deleted file mode 100644 index 22493e7..0000000 --- a/src/theme/colors.js +++ /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.js b/src/theme/index.js deleted file mode 100644 index 221bde6..0000000 --- a/src/theme/index.js +++ /dev/null @@ -1,43 +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.js b/src/theme/styles.js deleted file mode 100644 index 8a470fa..0000000 --- a/src/theme/styles.js +++ /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/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..a2e0420 --- /dev/null +++ b/src/utils/Letter.ts @@ -0,0 +1,23 @@ +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 { + original: string | null; + typed: string | null; + status: string | null; + + constructor(original: string | null, 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..5a7c50e --- /dev/null +++ b/src/utils/Word.ts @@ -0,0 +1,37 @@ +import { sampleSize } from "lodash"; +import { type RefObject, createRef } from "react"; +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?: number) { + if (n) { + return sampleSize(arrOfString, n).map((s) => new Word(s)); + } + + return arrOfString.map((s) => new Word(s)); +} + +export default Word; diff --git a/src/utils/__test__/Letter.test.js b/src/utils/__test__/Letter.test.js deleted file mode 100644 index 1a2ef07..0000000 --- a/src/utils/__test__/Letter.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { getStatus } from 'utils/Letter' - -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') - expect(getStatus()).toEqual(null) - expect(getStatus('not object')).toEqual(null) - }) -}) diff --git a/src/utils/__test__/Letter.test.ts b/src/utils/__test__/Letter.test.ts new file mode 100644 index 0000000..d549d83 --- /dev/null +++ b/src/utils/__test__/Letter.test.ts @@ -0,0 +1,11 @@ +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"); + }); +}); diff --git a/src/utils/__test__/Word.test.js b/src/utils/__test__/Word.test.js deleted file mode 100644 index f1ae1ef..0000000 --- a/src/utils/__test__/Word.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import Letter from 'utils/Letter' -import Word, { isCorrectlyTyped, createWordSequence } from 'utils/Word' - -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) - }) -}) - -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 - 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__/Word.test.ts b/src/utils/__test__/Word.test.ts new file mode 100644 index 0000000..7ac2813 --- /dev/null +++ b/src/utils/__test__/Word.test.ts @@ -0,0 +1,39 @@ +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); + }); + + 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", () => { + const wordArr = ` + 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 + 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.js deleted file mode 100644 index 0b8c0f1..0000000 --- a/src/utils/__test__/statistics.test.js +++ /dev/null @@ -1,202 +0,0 @@ -import Letter from 'utils/Letter' -import { - getCorrectWordSequence, - getIncorrectWordSequence, - calculateWPM, - calculateAccuracy, - getStatistics -} from 'utils/statistics' -import Word from 'utils/Word' - -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) - 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', () => { - 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 - } - - wordSequence[1].letterSequence = wordSequence[0].letterSequence.map( - 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', () => { - 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) - 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', () => { - 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 - } - - wordSequence[1].letterSequence = wordSequence[0].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) - wordSequence[2].letterSequence = wordSequence[1].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) - wordSequence[4].letterSequence = wordSequence[1].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) - - let incorrectSequence = getIncorrectWordSequence(wordSequence) - expect(incorrectSequence[0]).toEqual(wordSequence[0]) - expect(incorrectSequence[incorrectSequence.length - 1]).toEqual( - wordSequence[3] - ) - expect(incorrectSequence.length).toEqual(2) - - wordSequence[0].isTyped = false - expect(getIncorrectWordSequence(wordSequence).length).toEqual(1) - }) -}) - -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) - - 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 - } - - expect(calculateWPM(wordSequence, 60)).toBeGreaterThan(0) - - wordSequence.letterSequence = wordSequence[0].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) - - wordSequence[0] = null - - expect(calculateWPM(wordSequence, 60)).toBeGreaterThan(0) - }) -}) - -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'), - new Word('I'), - new Word('love'), - new Word('javascript') - ] - - for (let word of wordSequence) { - word.isTyped = true - } - - expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0) - - wordSequence.letterSequence = wordSequence[0].letterSequence.map( - letter => new Letter(letter.original, letter.original) - ) - - wordSequence[0] = null - - expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0) - }) -}) - -describe('getStatistics function', () => { - test('should be numbers', () => { - const output = Object.values(getStatistics()) - .map(val => typeof val) - .every(type => type === 'number') - - expect(output).toEqual(true) - }) -}) diff --git a/src/utils/__test__/statistics.test.ts b/src/utils/__test__/statistics.test.ts new file mode 100644 index 0000000..e9db583 --- /dev/null +++ b/src/utils/__test__/statistics.test.ts @@ -0,0 +1,166 @@ +import Letter from "utils/Letter"; +import Word from "utils/Word"; +import { + calculateAccuracy, + calculateWPM, + getCorrectWordSequence, + getIncorrectWordSequence, + getStatistics, +} from "utils/statistics"; +import { describe, expect } from "vitest"; + +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", () => { + const wordSequence = [ + 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), + ); + wordSequence[2].letterSequence = wordSequence[1].letterSequence.map( + (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", () => { + const wordSequence = [ + 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), + ); + wordSequence[2].letterSequence = wordSequence[1].letterSequence.map( + (letter) => new Letter(letter.original, letter.original), + ); + wordSequence[4].letterSequence = wordSequence[1].letterSequence.map( + (letter) => new Letter(letter.original, letter.original), + ); + + const incorrectSequence = getIncorrectWordSequence(wordSequence); + expect(incorrectSequence[0]).toEqual(wordSequence[0]); + expect(incorrectSequence[incorrectSequence.length - 1]).toEqual( + wordSequence[3], + ); + expect(incorrectSequence.length).toEqual(2); + + 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); + + const wordSequence = [ + 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); + + wordSequence[0].letterSequence = wordSequence[0].letterSequence.map( + (letter) => new Letter(letter.original, letter.original), + ); + + expect(calculateWPM(wordSequence, 60)).toBeGreaterThan(0); + }); +}); + +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 (const word of wordSequence) { + word.isTyped = true; + } + + expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0); + + wordSequence[0].letterSequence = wordSequence[0].letterSequence.map( + (letter) => new Letter(letter.original, letter.original), + ); + + expect(calculateAccuracy(wordSequence)).toBeGreaterThan(0); + }); +}); + +describe("getStatistics function", () => { + test("should be numbers", () => { + const output = Object.values(getStatistics([], 0)) + .map((val) => typeof val) + .every((type) => type === "number"); + + expect(output).toEqual(true); + }); +}); diff --git a/src/utils/constant.js b/src/utils/constant.js deleted file mode 100644 index a9b71a7..0000000 --- a/src/utils/constant.js +++ /dev/null @@ -1,31 +0,0 @@ -export const language = { - id: 'id', - en: 'en', -} - -export const mode = { - time: 'time', - word: 'word', -} - -export const duration = { - '30s': 30, - '60s': 60, -} - -export const theme = { - default: 'Dark', - light: 'Light', - oneDarkPro: 'One Dark Pro', - monokai: 'Monokai', - pink: 'Pink', -} - -const constant = { - language, - mode, - duration, - theme, -} - -export default constant diff --git a/src/utils/constant.ts b/src/utils/constant.ts new file mode 100644 index 0000000..bac133c --- /dev/null +++ b/src/utils/constant.ts @@ -0,0 +1,46 @@ +export const language = { + id: "id", + en: "en", +} as const; + +export const languageFlagMoji = { + id: "🇮🇩", + en: "🇬🇧", +}; + +export const mode = { + time: "time", + word: "word", +} as const; + +export const duration = { + "30s": 30, + "60s": 60, +} as const; + +export const theme = { + dark: "Dark", + light: "Light", + oneDarkPro: "One Dark Pro", + monokai: "Monokai", + pink: "Pink", + nord: "Nord", + dracula: "Dracula", + solarizedDark: "Solarized Dark", + paper: "Paper", + neonNight: "Neon Night", +} 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; diff --git a/src/utils/statistics.js b/src/utils/statistics.js deleted file mode 100644 index caed5d9..0000000 --- a/src/utils/statistics.js +++ /dev/null @@ -1,71 +0,0 @@ -import { isCorrectlyTyped } from '../utils/Word' - -export function getCorrectWordSequence (wordSequence) { - if (!Array.isArray(wordSequence)) { - return [] - } - - const typedWordSeq = wordSequence.filter(word => word?.isTyped) - return typedWordSeq.filter(word => isCorrectlyTyped(word)) -} - -export function getIncorrectWordSequence (wordSequence) { - if (!Array.isArray(wordSequence)) { - return [] - } - - const typedWordSeq = wordSequence.filter(word => word?.isTyped) - return typedWordSeq.filter(word => !isCorrectlyTyped(word)) -} - -export function calculateWPM (wordSequence, seconds) { - if (!Array.isArray(wordSequence) || seconds < 0) { - return NaN - } - - const typedWordSeq = wordSequence.filter(word => word?.isTyped) - const spacesCount = typedWordSeq.length ? typedWordSeq.length - 1 : 0 - const correctlyTypedLetters = getCorrectWordSequence(wordSequence) - .map(word => word?.originalWord || '') - .join('') - - return Math.round( - (spacesCount + correctlyTypedLetters.length) / 5 / (seconds / 60) - ) -} - -export function calculateAccuracy (wordSequence) { - if (!Array.isArray(wordSequence)) { - return NaN - } - - 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) - - const correctKeys = - spacesCount + - typedWordSeq - .map(word => word.letterSequence) - .map( - letterSeq => - letterSeq.filter(letter => letter.status === 'correct').length - ) - .reduce((acc, curr) => acc + curr, 0) - - return Math.round((correctKeys / totalKeys) * 100) / 100 -} - -export function getStatistics (wordSequence, seconds) { - return { - wpm: calculateWPM(wordSequence, seconds), - accuracy: calculateAccuracy(wordSequence), - correctWords: getCorrectWordSequence(wordSequence).length, - incorrectWords: getIncorrectWordSequence(wordSequence).length, - time: seconds || NaN - } -} diff --git a/src/utils/statistics.ts b/src/utils/statistics.ts new file mode 100644 index 0000000..22e1f11 --- /dev/null +++ b/src/utils/statistics.ts @@ -0,0 +1,72 @@ +import type Word from "../utils/Word"; +import { isCorrectlyTyped } from "../utils/Word"; + +export function getCorrectWordSequence(wordSequence: Array) { + if (!Array.isArray(wordSequence)) { + return []; + } + + const typedWordSeq = wordSequence.filter((word) => word?.isTyped); + return typedWordSeq.filter((word) => isCorrectlyTyped(word)); +} + +export function getIncorrectWordSequence(wordSequence: Array) { + if (!Array.isArray(wordSequence)) { + return []; + } + + const typedWordSeq = wordSequence.filter((word) => word?.isTyped); + return typedWordSeq.filter((word) => !isCorrectlyTyped(word)); +} + +export function calculateWPM(wordSequence: Array, seconds: number) { + if (!Array.isArray(wordSequence) || seconds < 0) { + return Number.NaN; + } + + const typedWordSeq = wordSequence.filter((word) => word?.isTyped); + const spacesCount = typedWordSeq.length ? typedWordSeq.length - 1 : 0; + const correctlyTypedLetters = getCorrectWordSequence(wordSequence) + .map((word) => word?.originalWord || "") + .join(""); + + return Math.round( + (spacesCount + correctlyTypedLetters.length) / 5 / (seconds / 60), + ); +} + +export function calculateAccuracy(wordSequence: Array): number { + if (!Array.isArray(wordSequence)) { + return Number.NaN; + } + + 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); + + const correctKeys = + spacesCount + + typedWordSeq + .map((word) => word.letterSequence) + .map( + (letterSeq) => + letterSeq.filter((letter) => letter.status === "correct").length, + ) + .reduce((acc, curr) => acc + curr, 0); + + return Math.round((correctKeys / totalKeys) * 100) / 100; +} + +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 || Number.NaN, + }; +} 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..890cdad --- /dev/null +++ b/src/utils/store.ts @@ -0,0 +1,68 @@ +import type Word from "./Word"; +import type { Language, Mode, Theme } from "./constant"; + +export interface Config { + lang: Language; + duration: number; + mode: Mode; + theme: Theme; +} + +interface LanguageJSON { + lang: string; + name: string; + words: string[]; +} + +export interface Typing { + inputValue: string; + languageJSON: LanguageJSON | null; + wordSequence: Array; + caretPosition: [number, number]; + statistics: { + accuracy: number; + wpm: number; + correctWords: number; + incorrectWords: number; + }; + typingStatus: "pending" | "done" | "started"; + typingMinutes?: number; + startTime: number | null; + finishTime: number | null; +} + +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, +}: Partial): Config { + return { + lang, + mode, + duration, + theme: theme ?? "dark", + }; +} diff --git a/src/utils/theme.js b/src/utils/theme.ts similarity index 56% rename from src/utils/theme.js rename to src/utils/theme.ts index 100f46e..0a41d92 100644 --- a/src/utils/theme.js +++ 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, -}) { +}: 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..4409e5d --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,52 @@ +/** @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: { + background: "var(--color-background)", + typingBackground: "var(--color-typingBackground)", + 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)", + }, + fontFamily: { + mono: ["Geist Mono", "monospace"], + }, + keyframes: { + opacityBreath: { + "50%": { + opacity: 0, + }, + }, + progress: { + "0%": { transform: "translateX(-100%)" }, + "100%": { transform: "translateX(0)" }, + }, + }, + animation: { + caret: "opacityBreath 1s steps(1) infinite", + progress: "progress var(--duration) linear", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..745b924 --- /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": true, + "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.js deleted file mode 100644 index 46f7172..0000000 --- a/vite.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; -import jsconfigPaths from 'vite-jsconfig-paths' - -export default defineConfig(() => { - return { - build: { - outDir: 'build', - }, - plugins: [jsconfigPaths(), react()], - }; -}); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..d19df39 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,31 @@ +import react from "@vitejs/plugin-react"; +import Unfonts from "unplugin-fonts/vite"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig(() => { + return { + build: { + outDir: "build", + }, + plugins: [ + tsconfigPaths(), + react(), + Unfonts({ + custom: { + families: [ + { + name: "Geist Mono", + src: "./src/assets/fonts/GeistMonoVF.woff2", + }, + ], + }, + }), + ], + test: { + globals: true, + environment: "jsdom", + setupFiles: "./src/setupTests.ts", + }, + }; +});