diff --git a/package-lock.json b/package-lock.json index 0e62e29..eb3b590 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,20 +15,20 @@ "react-transition-state": "^2.2.0" }, "devDependencies": { - "@eslint/js": "^9.15.0", - "@types/node": "^22.10.0", + "@eslint/js": "^9.16.0", + "@types/node": "^22.10.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react-swc": "^3.7.2", - "eslint": "^9.15.0", + "eslint": "^9.16.0", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.14", - "globals": "^15.12.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.13.0", "lightningcss": "^1.28.2", - "typescript": "^5.6.3", - "typescript-eslint": "^8.15.0", - "vite": "^5.4.11", + "typescript": "^5.7.2", + "typescript-eslint": "^8.17.0", + "vite": "^6.0.2", "vite-plugin-svgr": "^4.3.0" }, "engines": { @@ -291,9 +291,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", "cpu": [ "ppc64" ], @@ -304,13 +304,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", "cpu": [ "arm" ], @@ -321,13 +321,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", "cpu": [ "arm64" ], @@ -338,13 +338,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", "cpu": [ "x64" ], @@ -355,13 +355,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", "cpu": [ "arm64" ], @@ -372,13 +372,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", "cpu": [ "x64" ], @@ -389,13 +389,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", "cpu": [ "arm64" ], @@ -406,13 +406,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", "cpu": [ "x64" ], @@ -423,13 +423,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", "cpu": [ "arm" ], @@ -440,13 +440,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", "cpu": [ "arm64" ], @@ -457,13 +457,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", "cpu": [ "ia32" ], @@ -474,13 +474,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", "cpu": [ "loong64" ], @@ -491,13 +491,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", "cpu": [ "mips64el" ], @@ -508,13 +508,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", "cpu": [ "ppc64" ], @@ -525,13 +525,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", "cpu": [ "riscv64" ], @@ -542,13 +542,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", "cpu": [ "s390x" ], @@ -559,13 +559,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", "cpu": [ "x64" ], @@ -576,13 +576,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", "cpu": [ "x64" ], @@ -593,13 +593,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", "cpu": [ "x64" ], @@ -610,13 +627,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", "cpu": [ "x64" ], @@ -627,13 +644,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", "cpu": [ "arm64" ], @@ -644,13 +661,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", "cpu": [ "ia32" ], @@ -661,13 +678,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", "cpu": [ "x64" ], @@ -678,7 +695,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -786,9 +803,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", + "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", "dev": true, "license": "MIT", "engines": { @@ -1012,9 +1029,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.3.tgz", - "integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.0.tgz", + "integrity": "sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==", "cpu": [ "arm" ], @@ -1026,9 +1043,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.3.tgz", - "integrity": "sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.0.tgz", + "integrity": "sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==", "cpu": [ "arm64" ], @@ -1040,9 +1057,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.3.tgz", - "integrity": "sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.0.tgz", + "integrity": "sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==", "cpu": [ "arm64" ], @@ -1054,9 +1071,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.3.tgz", - "integrity": "sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.0.tgz", + "integrity": "sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==", "cpu": [ "x64" ], @@ -1068,9 +1085,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.3.tgz", - "integrity": "sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.0.tgz", + "integrity": "sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==", "cpu": [ "arm64" ], @@ -1082,9 +1099,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.3.tgz", - "integrity": "sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.0.tgz", + "integrity": "sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==", "cpu": [ "x64" ], @@ -1096,9 +1113,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.3.tgz", - "integrity": "sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.0.tgz", + "integrity": "sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==", "cpu": [ "arm" ], @@ -1110,9 +1127,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.3.tgz", - "integrity": "sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.0.tgz", + "integrity": "sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==", "cpu": [ "arm" ], @@ -1124,9 +1141,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.3.tgz", - "integrity": "sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.0.tgz", + "integrity": "sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==", "cpu": [ "arm64" ], @@ -1138,9 +1155,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.3.tgz", - "integrity": "sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.0.tgz", + "integrity": "sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==", "cpu": [ "arm64" ], @@ -1152,9 +1169,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.3.tgz", - "integrity": "sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.0.tgz", + "integrity": "sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==", "cpu": [ "ppc64" ], @@ -1166,9 +1183,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.3.tgz", - "integrity": "sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.0.tgz", + "integrity": "sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==", "cpu": [ "riscv64" ], @@ -1180,9 +1197,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.3.tgz", - "integrity": "sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.0.tgz", + "integrity": "sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==", "cpu": [ "s390x" ], @@ -1194,9 +1211,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.3.tgz", - "integrity": "sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.0.tgz", + "integrity": "sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==", "cpu": [ "x64" ], @@ -1208,9 +1225,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.3.tgz", - "integrity": "sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.0.tgz", + "integrity": "sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==", "cpu": [ "x64" ], @@ -1222,9 +1239,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.3.tgz", - "integrity": "sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.0.tgz", + "integrity": "sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==", "cpu": [ "arm64" ], @@ -1236,9 +1253,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.3.tgz", - "integrity": "sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.0.tgz", + "integrity": "sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==", "cpu": [ "ia32" ], @@ -1250,9 +1267,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.3.tgz", - "integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.0.tgz", + "integrity": "sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==", "cpu": [ "x64" ], @@ -1729,9 +1746,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1767,17 +1784,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", - "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", + "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/type-utils": "8.15.0", - "@typescript-eslint/utils": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/type-utils": "8.17.0", + "@typescript-eslint/utils": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1801,16 +1818,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", - "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", + "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", "debug": "^4.3.4" }, "engines": { @@ -1830,14 +1847,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", - "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", + "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0" + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1848,14 +1865,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", - "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", + "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/typescript-estree": "8.17.0", + "@typescript-eslint/utils": "8.17.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1876,9 +1893,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", - "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", "dev": true, "license": "MIT", "engines": { @@ -1890,14 +1907,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", - "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", + "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1958,16 +1975,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", - "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", + "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0" + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1986,13 +2003,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", - "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/types": "8.17.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2350,9 +2367,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001683", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001683.tgz", - "integrity": "sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==", + "version": "1.0.30001686", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz", + "integrity": "sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==", "dev": true, "funding": [ { @@ -2623,9 +2640,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.64", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz", - "integrity": "sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==", + "version": "1.5.68", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.68.tgz", + "integrity": "sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ==", "dev": true, "license": "ISC" }, @@ -2802,15 +2819,15 @@ } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -2820,9 +2837,9 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2830,32 +2847,33 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" } }, "node_modules/escalade": { @@ -2882,9 +2900,9 @@ } }, "node_modules/eslint": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", - "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", + "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", "dev": true, "license": "MIT", "dependencies": { @@ -2893,7 +2911,7 @@ "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.15.0", + "@eslint/js": "9.16.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2988,13 +3006,13 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.14.tgz", - "integrity": "sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.16.tgz", + "integrity": "sha512-slterMlxAhov/DZO8NScf6mEeMBBXodFUolijDvrtTxyezyLoTQaa73FyYus/VbTdftd8wBgBxPMRk3poleXNQ==", "dev": true, "license": "MIT", "peerDependencies": { - "eslint": ">=7" + "eslint": ">=8.40" } }, "node_modules/eslint-scope": { @@ -3349,9 +3367,9 @@ } }, "node_modules/globals": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", - "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "version": "15.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz", + "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==", "dev": true, "license": "MIT", "engines": { @@ -3379,13 +3397,16 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz", + "integrity": "sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3" + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3432,11 +3453,14 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.1.0.tgz", + "integrity": "sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, "engines": { "node": ">= 0.4" }, @@ -3445,9 +3469,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -3579,27 +3603,30 @@ } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.0.tgz", + "integrity": "sha512-kR5g0+dXf/+kXnqI+lu0URKYPKgICtHGGNCDSB10AaUFj3o/HkB3u7WfpRBJGFopxxY0oH3ux7ZsDjLtK7xqvw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bind": "^1.0.7", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3680,13 +3707,16 @@ } }, "node_modules/is-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", + "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3758,13 +3788,14 @@ } }, "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.0.tgz", + "integrity": "sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bind": "^1.0.7", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3774,14 +3805,16 @@ } }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.0.tgz", + "integrity": "sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bind": "^1.0.7", + "gopd": "^1.1.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -3820,13 +3853,14 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.0.tgz", + "integrity": "sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bind": "^1.0.7", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3836,13 +3870,15 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.0.tgz", + "integrity": "sha512-qS8KkNNXUZ/I+nX6QT8ZS1/Yx0A444yhzdTKxCzKkNjQ9sHErBxJnJAgh+f5YhusYECEcjo4XcyH87hn6+ks0A==", "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "call-bind": "^1.0.7", + "has-symbols": "^1.0.3", + "safe-regex-test": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -4400,9 +4436,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -4821,19 +4857,19 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", - "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz", + "integrity": "sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.23.1", + "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" + "gopd": "^1.0.1", + "which-builtin-type": "^1.1.4" }, "engines": { "node": ">= 0.4" @@ -4901,9 +4937,9 @@ } }, "node_modules/rollup": { - "version": "4.27.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.3.tgz", - "integrity": "sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.0.tgz", + "integrity": "sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4917,24 +4953,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.27.3", - "@rollup/rollup-android-arm64": "4.27.3", - "@rollup/rollup-darwin-arm64": "4.27.3", - "@rollup/rollup-darwin-x64": "4.27.3", - "@rollup/rollup-freebsd-arm64": "4.27.3", - "@rollup/rollup-freebsd-x64": "4.27.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.27.3", - "@rollup/rollup-linux-arm-musleabihf": "4.27.3", - "@rollup/rollup-linux-arm64-gnu": "4.27.3", - "@rollup/rollup-linux-arm64-musl": "4.27.3", - "@rollup/rollup-linux-powerpc64le-gnu": "4.27.3", - "@rollup/rollup-linux-riscv64-gnu": "4.27.3", - "@rollup/rollup-linux-s390x-gnu": "4.27.3", - "@rollup/rollup-linux-x64-gnu": "4.27.3", - "@rollup/rollup-linux-x64-musl": "4.27.3", - "@rollup/rollup-win32-arm64-msvc": "4.27.3", - "@rollup/rollup-win32-ia32-msvc": "4.27.3", - "@rollup/rollup-win32-x64-msvc": "4.27.3", + "@rollup/rollup-android-arm-eabi": "4.28.0", + "@rollup/rollup-android-arm64": "4.28.0", + "@rollup/rollup-darwin-arm64": "4.28.0", + "@rollup/rollup-darwin-x64": "4.28.0", + "@rollup/rollup-freebsd-arm64": "4.28.0", + "@rollup/rollup-freebsd-x64": "4.28.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.0", + "@rollup/rollup-linux-arm-musleabihf": "4.28.0", + "@rollup/rollup-linux-arm64-gnu": "4.28.0", + "@rollup/rollup-linux-arm64-musl": "4.28.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.0", + "@rollup/rollup-linux-riscv64-gnu": "4.28.0", + "@rollup/rollup-linux-s390x-gnu": "4.28.0", + "@rollup/rollup-linux-x64-gnu": "4.28.0", + "@rollup/rollup-linux-x64-musl": "4.28.0", + "@rollup/rollup-win32-arm64-msvc": "4.28.0", + "@rollup/rollup-win32-ia32-msvc": "4.28.0", + "@rollup/rollup-win32-x64-msvc": "4.28.0", "fsevents": "~2.3.2" } }, @@ -5265,9 +5301,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", - "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dev": true, "license": "MIT", "engines": { @@ -5355,18 +5391,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -5390,15 +5426,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.15.0.tgz", - "integrity": "sha512-wY4FRGl0ZI+ZU4Jo/yjdBu0lVTSML58pu6PgGtJmCufvzfV565pUF6iACQt092uFOd49iLOTX/sEVmHtbSrS+w==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz", + "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.15.0", - "@typescript-eslint/parser": "8.15.0", - "@typescript-eslint/utils": "8.15.0" + "@typescript-eslint/eslint-plugin": "8.17.0", + "@typescript-eslint/parser": "8.17.0", + "@typescript-eslint/utils": "8.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5481,21 +5517,21 @@ } }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.2.tgz", + "integrity": "sha512-XdQ+VsY2tJpBsKGs0wf3U/+azx8BBpYRHFAyKm5VeEZNOJZRB63q7Sc8Iup3k0TrN3KO6QgyzFf+opSbfY1y0g==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.24.0", + "postcss": "^8.4.49", + "rollup": "^4.23.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5504,19 +5540,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -5537,6 +5579,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -5572,34 +5620,38 @@ } }, "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.0.tgz", + "integrity": "sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng==", "dev": true, "license": "MIT", "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.0", + "is-number-object": "^1.1.0", + "is-string": "^1.1.0", + "is-symbol": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-builtin-type": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", - "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.0.tgz", + "integrity": "sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==", "dev": true, "license": "MIT", "dependencies": { + "call-bind": "^1.0.7", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.0.2", + "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.1.4", "is-weakref": "^1.0.2", @@ -5635,9 +5687,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", + "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3d07eda..79e1077 100644 --- a/package.json +++ b/package.json @@ -45,20 +45,20 @@ "react-transition-state": "^2.2.0" }, "devDependencies": { - "@eslint/js": "^9.15.0", - "@types/node": "^22.10.0", + "@eslint/js": "^9.16.0", + "@types/node": "^22.10.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react-swc": "^3.7.2", - "eslint": "^9.15.0", + "eslint": "^9.16.0", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.14", - "globals": "^15.12.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.13.0", "lightningcss": "^1.28.2", - "typescript": "^5.6.3", - "typescript-eslint": "^8.15.0", - "vite": "^5.4.11", + "typescript": "^5.7.2", + "typescript-eslint": "^8.17.0", + "vite": "^6.0.2", "vite-plugin-svgr": "^4.3.0" } } diff --git a/packages/classes/TypedStorage.ts b/packages/classes/TypedStorage.ts new file mode 100644 index 0000000..ab99c59 --- /dev/null +++ b/packages/classes/TypedStorage.ts @@ -0,0 +1,62 @@ +export class TypedStorage> { + private registeredKeys = new Set(); + + setItem(key: K, value: T[K]) { + try { + const serializedValue = JSON.stringify(value); + localStorage.setItem(String(key), serializedValue); + this.registeredKeys.add(String(key)); + } catch (error) { + console.error(`Failed to set item with key "${String(key)}":`, error); + } + } + + getItem(key: K) { + try { + const item = localStorage.getItem(String(key)); + // It is possible we could consider some custom validator to be set + // when calling `setItem/getItem` that allows for stronger typing. + return item ? (JSON.parse(item) as T[K]) : null; + } catch (error) { + console.error(`Failed to get item with key "${String(key)}":`, error); + return null; + } + } + + removeItem(key: K) { + try { + localStorage.removeItem(String(key)); + this.registeredKeys.delete(String(key)); + } catch (error) { + console.error(`Failed to remove item with key "${String(key)}":`, error); + } + } + + clear() { + try { + this.registeredKeys.forEach((key) => { + localStorage.removeItem(key); + }); + this.registeredKeys.clear(); + } catch (error) { + console.error( + 'Failed to clear registered items from localStorage:', + error, + ); + } + } +} + +/* +// Usage example: + +interface MyLocalStorageEntries { + userSettings: { theme: string; language: string }; + sessionId: string; +} + +const myLocalStorage = new TypedStorage(); + +myLocalStorage.setItem('userSettings', { theme: 'dark', language: 'en' }); +myLocalStorage.setItem('sessionId', 'abc123'); +*/ diff --git a/packages/classes/index.ts b/packages/classes/index.ts new file mode 100644 index 0000000..f295348 --- /dev/null +++ b/packages/classes/index.ts @@ -0,0 +1 @@ +export {TypedStorage} from './TypedStorage.ts'; diff --git a/packages/hooks/index.ts b/packages/hooks/index.ts index a382174..015d52f 100644 --- a/packages/hooks/index.ts +++ b/packages/hooks/index.ts @@ -26,6 +26,8 @@ export { type KeyPressOptions, } from './useKeyPress.ts'; +export {useLocalStorage} from './useLocalStorage.ts'; + export {useMediaQuery, type MediaQueryOptions} from './useMediaQuery.ts'; export {useMounted} from './useMounted.ts'; diff --git a/packages/hooks/useLocalStorage.ts b/packages/hooks/useLocalStorage.ts new file mode 100644 index 0000000..bff51e9 --- /dev/null +++ b/packages/hooks/useLocalStorage.ts @@ -0,0 +1,99 @@ +import {useCallback, useEffect, useSyncExternalStore} from 'react'; +import type {AnyObj, Fn} from 'beeftools'; + +// Adapted from: +// https://usehooks.com/uselocalstorage + +type AcceptedTypes = string | number | boolean | AnyObj; +type AcceptedFn = (arg: AnyObj) => AcceptedTypes; + +type LocalStorageValue = AcceptedTypes | AcceptedTypes[] | AcceptedFn; +type ServerSnapshotFn = Parameters[2]; + +type LocalStorageReturn = [ + state: LocalStorageValue, + setter: (value: LocalStorageValue) => void, +]; + +function dispatchStorageEvent(key: string, newValue?: string | null) { + window.dispatchEvent(new StorageEvent('storage', {key, newValue})); +} + +function parseStore(store: unknown) { + return JSON.parse(store as string) as AnyObj; +} + +function setLocalStorageItem(key: string, value: LocalStorageValue) { + const stringifiedValue = JSON.stringify(value); + + window.localStorage.setItem(key, stringifiedValue); + dispatchStorageEvent(key, stringifiedValue); +} + +function removeLocalStorageItem(key: string) { + window.localStorage.removeItem(key); + dispatchStorageEvent(key, null); +} + +function getLocalStorageItem(key: string) { + return window.localStorage.getItem(key); +} + +const getLocalStorageServerSnapshot: ServerSnapshotFn = () => { + throw Error('useLocalStorage is a client-only hook'); +}; + +function useLocalStorageSubscribe(callback: Fn) { + window.addEventListener('storage', callback); + + return () => { + window.removeEventListener('storage', callback); + }; +} + +// TODO: Fix this to accept a `generic` / infer the correct type from `initialValue`. +// For now, I prefer using the `TypedStorage` class. +export function useLocalStorage(key: string, initialValue: LocalStorageValue) { + const getSnapshot = () => getLocalStorageItem(key); + + const store = useSyncExternalStore( + useLocalStorageSubscribe, + getSnapshot, + getLocalStorageServerSnapshot, + ); + + // Handles both `set` and `remove`. By passing `undefined` or `null`, + // `removeLocalStorageItem` is called on the provided `key`. + const setState = useCallback( + (value: LocalStorageValue) => { + try { + const isFn = typeof value === 'function'; + const parsed = isFn ? parseStore(store) : undefined; + const nextState = parsed ?? value; + + if (nextState === undefined || nextState === null) { + removeLocalStorageItem(key); + } else { + setLocalStorageItem(key, nextState); + } + } catch (error) { + console.warn(error); + } + }, + [key, store], + ); + + useEffect(() => { + if ( + getLocalStorageItem(key) === null && + typeof initialValue !== 'undefined' + ) { + setLocalStorageItem(key, initialValue); + } + }, [key, initialValue]); + + const finalValue = store ? parseStore(store) : initialValue; + const finalTuple: LocalStorageReturn = [finalValue, setState]; + + return finalTuple; +} diff --git a/packages/hooks/useMediaQuery.ts b/packages/hooks/useMediaQuery.ts index 4d4625d..5982c5c 100644 --- a/packages/hooks/useMediaQuery.ts +++ b/packages/hooks/useMediaQuery.ts @@ -20,13 +20,11 @@ export function useMediaQuery(query = '', options?: MediaQueryOptions) { const initializeWithValue = options?.initializeWithValue ?? true; function getMatches(query = '') { - if (IS_CLIENT) return window.matchMedia(query).matches; - return defaultValue; + return IS_CLIENT ? window.matchMedia(query).matches : defaultValue; } const [matches, setMatches] = useState(() => { - if (initializeWithValue) return getMatches(query); - return defaultValue; + return initializeWithValue ? getMatches(query) : defaultValue; }); // Handles the change event of the media query. diff --git a/src/App.module.css b/src/App.module.css index f174d99..3811530 100644 --- a/src/App.module.css +++ b/src/App.module.css @@ -3,3 +3,23 @@ grid-template-rows: auto 1fr auto; min-height: 100%; } + +.Layout { + display: grid; + gap: 20px; + margin: 0 auto; + max-width: 960px; +} + +.Card { + margin: 0 auto; + padding: 10px; + border-radius: var(--radius); + color: var(--color-text); + background-color: var(--shade-light-20); +} + +.invert { + color: var(--color-bg); + background-color: var(--color-text); +} diff --git a/src/App.tsx b/src/App.tsx index 5921048..1519a6d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,15 @@ +import {clx} from 'beeftools'; + +import {BreakpointProvider} from '@src/providers/BreakpointProvider.tsx'; import {ThemeProvider} from '@src/providers/ThemeProvider.tsx'; import {Footer} from '@src/components/sections/Footer/Footer.tsx'; import {Header} from '@src/components/sections/Header/Header.tsx'; import {Main} from '@src/components/sections/Main/Main.tsx'; +import {AccordionTest} from '@src/components/ui/Accordion/Accordion.test.tsx'; +import {ButtonTest} from '@src/components/ui/Button/Button.test.tsx'; + import styles from './App.module.css'; function AppContent() { @@ -11,7 +17,27 @@ function AppContent() { return (
-
+ +
+
+
+ +
+ +
+ +
+
+
+
); @@ -20,7 +46,9 @@ function AppContent() { export function App() { return ( - + + + ); } diff --git a/src/components/primitives/Overlay/Overlay.module.css b/src/components/primitives/Overlay/Overlay.module.css index ba62f56..313185a 100644 --- a/src/components/primitives/Overlay/Overlay.module.css +++ b/src/components/primitives/Overlay/Overlay.module.css @@ -6,7 +6,7 @@ display: grid; align-items: center; justify-items: center; - background-color: var(--shade-black-80); + background-color: var(--shade-dark-80); /* Relies on `react-transition-state` hook. */ opacity: 1; diff --git a/src/components/primitives/Spinner/Spinner.module.css b/src/components/primitives/Spinner/Spinner.module.css index 9c76446..b1f8838 100644 --- a/src/components/primitives/Spinner/Spinner.module.css +++ b/src/components/primitives/Spinner/Spinner.module.css @@ -5,8 +5,8 @@ .Icon { width: 1em; height: 1em; - border: 0.125em solid var(--shade-white-20); - border-top: 0.125em solid var(--shade-white-80); + border: 0.125em solid color-mix(in srgb, currentColor, transparent 60%); + border-top: 0.125em solid currentColor; border-radius: 50%; animation: var(--motion-spin) var(--speed-slow) var(--ease) infinite both; } diff --git a/src/components/primitives/YouTubeEmbed/YouTubeEmbed.module.css b/src/components/primitives/YouTubeEmbed/YouTubeEmbed.module.css index 2b8cc8b..7a8d567 100644 --- a/src/components/primitives/YouTubeEmbed/YouTubeEmbed.module.css +++ b/src/components/primitives/YouTubeEmbed/YouTubeEmbed.module.css @@ -13,6 +13,6 @@ /* `aspect-ratio` should be dynamic. */ aspect-ratio: 16 / 9; width: 100%; - background-color: var(--shade-black-80); + background-color: var(--shade-dark-80); } } diff --git a/src/components/sections/Footer/Footer.module.css b/src/components/sections/Footer/Footer.module.css index c72898e..7953e0c 100644 --- a/src/components/sections/Footer/Footer.module.css +++ b/src/components/sections/Footer/Footer.module.css @@ -1,9 +1,9 @@ @import '@src/styles/media-queries.css'; .Footer { - --footer-action-padding: 6px; - --footer-underline-width: 0%; - + display: grid; + align-content: start; + gap: 20px; padding: 20px; width: 100%; text-align: center; @@ -18,42 +18,14 @@ gap: 64px; margin: 0 auto; max-width: var(--breakpoint-x-desktop); + + @media (--min-tablet) { + grid-template-columns: repeat(3, 1fr); + align-items: center; + } } .LegalText { font-size: 13px; letter-spacing: 0.06em; } - -/* --- Social Links --- */ - -.SocialLinks { - display: grid; - grid-auto-flow: column; - justify-content: center; - justify-items: center; - gap: 16px; -} - -.SocialLinkItem { -} - -.SocialLinkAction { - display: block; - padding: var(--footer-action-padding); - border-radius: var(--radius); - background-color: transparent; - /* `focus-ring` + `transition` is inherited by `button-basic` */ - - &:hover { - color: var(--color-primary); - background-color: var(--shade-black-4); - } - - &:active { - opacity: 0.8; - } -} - -.SocialIconWrapper { -} diff --git a/src/components/sections/Footer/Footer.tsx b/src/components/sections/Footer/Footer.tsx index 9a2e18a..428682c 100644 --- a/src/components/sections/Footer/Footer.tsx +++ b/src/components/sections/Footer/Footer.tsx @@ -1,12 +1,9 @@ -import {clx} from 'beeftools'; - -import SvgSocialLinkedIn from '@src/assets/svg/social-linkedin.svg?react'; -import SvgSocialX from '@src/assets/svg/social-x.svg?react'; -import SvgSocialYouTube from '@src/assets/svg/social-youtube.svg?react'; - import {DisplayText} from '@src/components/ui/DisplayText/DisplayText.tsx'; import {TextLink} from '@src/components/ui/TextLink/TextLink.tsx'; +import {ActionsDemo} from './parts/ActionsDemo.tsx'; +import {SocialLinks} from './parts/SocialLinks.tsx'; + import styles from './Footer.module.css'; const CURRENT_YEAR = new Date().getFullYear(); @@ -29,49 +26,8 @@ export function Footer() { All rights reserved.

- + + ); diff --git a/src/components/sections/Footer/parts/ActionsDemo.module.css b/src/components/sections/Footer/parts/ActionsDemo.module.css new file mode 100644 index 0000000..7c2bb84 --- /dev/null +++ b/src/components/sections/Footer/parts/ActionsDemo.module.css @@ -0,0 +1,10 @@ +.ActionsDemo { +} + +.Actions { + display: grid; + grid-auto-flow: column; + justify-content: center; + justify-items: center; + gap: 10px; +} diff --git a/src/components/sections/Footer/parts/ActionsDemo.tsx b/src/components/sections/Footer/parts/ActionsDemo.tsx new file mode 100644 index 0000000..dc89448 --- /dev/null +++ b/src/components/sections/Footer/parts/ActionsDemo.tsx @@ -0,0 +1,51 @@ +import {useState, type ReactNode} from 'react'; + +import {Portal} from '@src/components/primitives/Portal.tsx'; +import {Overlay} from '@src/components/primitives/Overlay/Overlay.tsx'; +import {YouTubeEmbed} from '@src/components/primitives/YouTubeEmbed/YouTubeEmbed.tsx'; +import {useTheme} from '@src/providers/ThemeProvider.tsx'; + +import {Button} from '@src/components/ui/Button/Button.tsx'; + +import styles from './ActionsDemo.module.css'; + +export interface ActionsDemoProps { + children?: ReactNode; +} + +export function ActionsDemo({children}: ActionsDemoProps) { + const {toggleTheme} = useTheme(); + const [open, setOpen] = useState(false); + + return ( +
+
+
+ + + setOpen(false)}> + + + + + {children} +
+ ); +} diff --git a/src/components/sections/Footer/parts/SocialLinks.module.css b/src/components/sections/Footer/parts/SocialLinks.module.css new file mode 100644 index 0000000..3126aaf --- /dev/null +++ b/src/components/sections/Footer/parts/SocialLinks.module.css @@ -0,0 +1,38 @@ +@import '@src/styles/media-queries.css'; + +.SocialLinks { + --social-links-action-padding: 6px; + display: grid; + grid-auto-flow: column; + justify-content: center; + justify-items: center; + gap: 16px; + + @media (--min-tablet) { + justify-content: end; + justify-items: end; + } +} + +.SocialLinkItem { +} + +.SocialLinkAction { + display: block; + padding: var(--social-links-action-padding); + border-radius: var(--radius); + background-color: transparent; + /* `focus-ring` + `transition` is inherited by `button-basic` */ + + &:hover { + color: var(--color-primary); + background-color: var(--shade-dark-4); + } + + &:active { + opacity: 0.8; + } +} + +.SocialIconWrapper { +} diff --git a/src/components/sections/Footer/parts/SocialLinks.tsx b/src/components/sections/Footer/parts/SocialLinks.tsx new file mode 100644 index 0000000..e26a8b8 --- /dev/null +++ b/src/components/sections/Footer/parts/SocialLinks.tsx @@ -0,0 +1,55 @@ +import {clx} from 'beeftools'; + +import SvgSocialLinkedIn from '@src/assets/svg/social-linkedin.svg?react'; +import SvgSocialX from '@src/assets/svg/social-x.svg?react'; +import SvgSocialYouTube from '@src/assets/svg/social-youtube.svg?react'; + +import styles from './SocialLinks.module.css'; + +export function SocialLinks() { + return ( + + ); +} diff --git a/src/components/sections/Header/Header.module.css b/src/components/sections/Header/Header.module.css index f825c0a..c2e8a3c 100644 --- a/src/components/sections/Header/Header.module.css +++ b/src/components/sections/Header/Header.module.css @@ -1,19 +1,25 @@ @import '@src/styles/media-queries.css'; .Header { +} + +.Layout { + display: grid; + grid-auto-flow: column; + align-items: center; + justify-content: space-between; + gap: 20px; padding: 20px; width: 100%; - text-align: center; + + @media (--min-tablet) { + grid-auto-flow: unset; + align-content: start; + justify-content: center; + text-align: center; + } @media (--min-desktop) { padding: 40px; } } - -.Actions { - display: grid; - grid-auto-flow: column; - justify-content: center; - justify-items: center; - gap: 10px; -} diff --git a/src/components/sections/Header/Header.tsx b/src/components/sections/Header/Header.tsx index 1bdde34..7f0182a 100644 --- a/src/components/sections/Header/Header.tsx +++ b/src/components/sections/Header/Header.tsx @@ -1,13 +1,6 @@ -import {useState, type ReactNode} from 'react'; +import {type ReactNode} from 'react'; -import {Portal} from '@src/components/primitives/Portal.tsx'; -import {Overlay} from '@src/components/primitives/Overlay/Overlay.tsx'; -import {YouTubeEmbed} from '@src/components/primitives/YouTubeEmbed/YouTubeEmbed.tsx'; -import {useTheme} from '@src/providers/ThemeProvider.tsx'; - -import {Button} from '@src/components/ui/Button/Button.tsx'; import {DisplayText} from '@src/components/ui/DisplayText/DisplayText.tsx'; - import styles from './Header.module.css'; export interface HeaderProps { @@ -15,33 +8,12 @@ export interface HeaderProps { } export function Header({children}: HeaderProps) { - const {toggle} = useTheme(); - const [open, setOpen] = useState(false); - return ( ); diff --git a/src/components/sections/Main/Main.module.css b/src/components/sections/Main/Main.module.css index e457c2d..ba574a6 100644 --- a/src/components/sections/Main/Main.module.css +++ b/src/components/sections/Main/Main.module.css @@ -1,6 +1,9 @@ @import '@src/styles/media-queries.css'; .Main { + display: grid; + align-content: start; + gap: 20px; padding: 20px; width: 100%; text-align: center; @@ -9,12 +12,3 @@ padding: 40px; } } - -.Card { - margin: 0 auto; - padding: 10px; - max-width: 540px; - border-radius: var(--radius); - color: var(--color-dark); - background-color: var(--shade-white-80); -} diff --git a/src/components/sections/Main/Main.tsx b/src/components/sections/Main/Main.tsx index 1749146..13ce3be 100644 --- a/src/components/sections/Main/Main.tsx +++ b/src/components/sections/Main/Main.tsx @@ -1,10 +1,6 @@ -import type {ReactNode} from 'react'; +import {type ReactNode} from 'react'; -import {Accordion} from '@src/components/ui/Accordion/Accordion.tsx'; -import {AccordionList} from '@src/components/ui/Accordion/AccordionList.tsx'; import {DisplayText} from '@src/components/ui/DisplayText/DisplayText.tsx'; - -import {MOCK_ACCORDION} from '@src/data.ts'; import styles from './Main.module.css'; export interface MainProps { @@ -15,23 +11,6 @@ export function Main({children}: MainProps) { return (
Section: Main - -
- - {MOCK_ACCORDION.map(({title, text}, index) => ( - - - - {text.map((line) => ( - {line} - ))} - - - - ))} - -
- {children}
); diff --git a/src/components/ui/Accordion/Accordion.module.css b/src/components/ui/Accordion/Accordion.module.css index 629935f..c217474 100644 --- a/src/components/ui/Accordion/Accordion.module.css +++ b/src/components/ui/Accordion/Accordion.module.css @@ -54,7 +54,7 @@ } &:hover:not(:active) { - background-color: var(--shade-black-4); + background-color: var(--shade-dark-4); } } diff --git a/src/data.ts b/src/components/ui/Accordion/Accordion.test.data.ts similarity index 100% rename from src/data.ts rename to src/components/ui/Accordion/Accordion.test.data.ts diff --git a/src/components/ui/Accordion/Accordion.test.tsx b/src/components/ui/Accordion/Accordion.test.tsx new file mode 100644 index 0000000..dae4244 --- /dev/null +++ b/src/components/ui/Accordion/Accordion.test.tsx @@ -0,0 +1,21 @@ +import {Accordion} from './Accordion.tsx'; +import {AccordionList} from './AccordionList.tsx'; +import {MOCK_ACCORDION} from './Accordion.test.data.ts'; + +export function AccordionTest() { + return ( + + {MOCK_ACCORDION.map(({title, text}, index) => ( + + + + {text.map((line) => ( + {line} + ))} + + + + ))} + + ); +} diff --git a/src/components/ui/Accordion/AccordionList.module.css b/src/components/ui/Accordion/AccordionList.module.css index 468ab66..7a56300 100644 --- a/src/components/ui/Accordion/AccordionList.module.css +++ b/src/components/ui/Accordion/AccordionList.module.css @@ -7,9 +7,9 @@ padding-top: 4px; padding-bottom: 4px; - border-top: 1px solid var(--color-dark); + border-top: 1px solid var(--color-text); &:last-child { - border-bottom: 1px solid var(--color-dark); + border-bottom: 1px solid var(--color-text); } } diff --git a/src/components/ui/Button/Button.module.css b/src/components/ui/Button/Button.module.css index 4a4f7b2..646311f 100644 --- a/src/components/ui/Button/Button.module.css +++ b/src/components/ui/Button/Button.module.css @@ -1,5 +1,11 @@ .Button { + --button-padding-x: 12px; + --button-padding-y: 8px; + --button-font-size: 14px; + --button-color-text: var(--color-text); + --button-color-bg: var(--color-secondary); --button-label-opacity: 1; + --button-spinner-size: 24px; position: relative; display: grid; @@ -7,8 +13,9 @@ align-items: center; justify-content: center; text-align: center; - padding: 20px; - background-color: var(--color-primary); + padding: var(--button-padding-y) var(--button-padding-x); + color: var(--button-color-text); + background-color: var(--button-color-bg); border: 1px solid transparent; border-radius: var(--radius); overflow: hidden; @@ -18,7 +25,7 @@ } &[aria-pressed] { - /* TODO: Author styles */ + box-shadow: inset 0 0 0 2px var(--button-color-text); } &:focus-visible { @@ -26,7 +33,7 @@ } &:hover:not(:active) { - background-color: var(--color-primary-dark); + --button-color-bg: var(--color-secondary-dark); } } @@ -41,20 +48,91 @@ /* --- Variants --- */ .variantPrimary { + --button-color-text: var(--color-bg); + --button-color-bg: var(--color-primary); + + &:hover:not(:active) { + --button-color-bg: var(--color-primary-dark); + } } .variantSecondary { + /* Is the default */ +} + +html[data-theme='dark'] { + & .variantSecondary:not(.outline) { + --button-color-text: var(--color-bg); + } +} + +.variantTertiary { + --button-color-text: var(--color-bg); + --button-color-bg: var(--color-accent); + + &:hover:not(:active) { + --button-color-bg: var(--color-accent-dark); + } +} + +.variantForeground { + --button-color-bg: var(--color-bg); + + &:hover:not(:active) { + --button-color-bg: var(--color-bg-dark); + } +} + +.variantBackground { + --button-color-text: var(--color-bg); + --button-color-bg: var(--color-text); + + &:hover:not(:active) { + --button-color-bg: var(--color-text-dark); + } } .variantDanger { + --button-color-bg: var(--color-danger); + + &:hover:not(:active) { + --button-color-bg: var(--color-danger-dark); + } +} + +html[data-theme='light'] { + & .variantDanger:not(.outline) { + --button-color-text: var(--color-bg); + } } /* This selector needs to come AFTER the `.variant*` classes */ .outline { - border-color: var(--color-primary); + --button-color-text: var(--button-color-bg); + border-color: var(--button-color-text); background-color: transparent; } +/* --- Sizes --- */ + +.sizeSmall { + /* Is the default */ +} + +.sizeMedium { + --button-padding-x: 16px; + --button-padding-y: 12px; + --button-font-size: 18px; + --button-spinner-size: 32px; +} + +.sizeLarge { + --button-padding-x: 20px; + --button-padding-y: 16px; + --button-font-size: 24px; + --button-spinner-size: 38px; +} + /* --- Label --- */ .LabelWrapper { @@ -65,8 +143,8 @@ } .Label { - /* font-family: var(--font-body); */ - font-size: 15px; + /* Not using `text-box-trim` for this font. */ + font-size: var(--button-font-size); font-weight: 700; letter-spacing: 0.04em; line-height: 1.2; @@ -80,7 +158,8 @@ display: grid; place-items: center; place-content: center; - font-size: 32px; + font-size: var(--button-spinner-size); + color: var(--button-color-text); /* Relies on `react-transition-state` hook. */ opacity: 1; diff --git a/src/components/ui/Button/Button.test.tsx b/src/components/ui/Button/Button.test.tsx new file mode 100644 index 0000000..41c85a3 --- /dev/null +++ b/src/components/ui/Button/Button.test.tsx @@ -0,0 +1,180 @@ +import {Button, type ButtonProps} from './Button.tsx'; + +export function ButtonTest() { + const handleTest: ButtonProps['onClick'] = ({currentTarget}) => { + const isPressed = currentTarget.getAttribute('aria-pressed') === 'true'; + + if (isPressed) { + currentTarget.removeAttribute('aria-pressed'); + currentTarget.setAttribute('disabled', 'true'); + } else { + currentTarget.setAttribute('aria-pressed', 'true'); + } + }; + + return ( +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); +} diff --git a/src/components/ui/Button/Button.tsx b/src/components/ui/Button/Button.tsx index 385b6a1..5331b99 100644 --- a/src/components/ui/Button/Button.tsx +++ b/src/components/ui/Button/Button.tsx @@ -38,7 +38,14 @@ type CommonActionSubset = Omit; export interface ButtonProps extends CommonActionSubset { label: string | number; - variant?: 'primary' | 'secondary' | 'danger'; + variant?: + | 'primary' + | 'secondary' + | 'tertiary' + | 'foreground' + | 'background' + | 'danger'; + size?: 'small' | 'medium' | 'large'; loading?: boolean; outline?: boolean; } @@ -46,13 +53,14 @@ export interface ButtonProps extends CommonActionSubset { function ButtonComponent( { label, - variant = 'primary', + variant = 'secondary', + size = 'small', disabled = false, loading = false, outline = false, ...commonProps }: ButtonProps, - ref: ForwardedRef + ref: ForwardedRef, ) { const [{status, isMounted}, toggle] = useTransitionState({ // TODO: This needs to align with our `--speed` values. @@ -83,6 +91,7 @@ function ButtonComponent( ref={ref} className={clx('button-basic', styles.Button, { [vrx('variant', variant, styles)]: Boolean(variant), + [vrx('size', size, styles)]: Boolean(size), [styles.loading]: loading, [styles.outline]: outline, })} @@ -90,7 +99,7 @@ function ButtonComponent( {...commonProps} >
- {label} + {label}
{loadingMarkup} diff --git a/src/components/ui/DisplayText/DisplayText.tsx b/src/components/ui/DisplayText/DisplayText.tsx index 2d08512..607dc05 100644 --- a/src/components/ui/DisplayText/DisplayText.tsx +++ b/src/components/ui/DisplayText/DisplayText.tsx @@ -7,13 +7,23 @@ export interface DisplayTextProps { // Consider restricting this to `string | number`. children: ReactNode; size?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; + truncate?: boolean; } -export function DisplayText({children, size = 'h1'}: DisplayTextProps) { +export function DisplayText({ + children, + size = 'h1', + truncate = false, +}: DisplayTextProps) { const Tag = size; return ( - + {children} ); diff --git a/src/components/ui/TextLink/TextLink.module.css b/src/components/ui/TextLink/TextLink.module.css index 48d1199..6107346 100644 --- a/src/components/ui/TextLink/TextLink.module.css +++ b/src/components/ui/TextLink/TextLink.module.css @@ -35,7 +35,7 @@ button.TextLink { left: -0.25em; right: -0.25em; border-radius: var(--radius-tight); - background-color: var(--shade-black-4); + background-color: var(--shade-dark-4); opacity: var(--text-link-bg-opacity); transition: opacity var(--speed) var(--ease); } diff --git a/src/hooks/media-queries/constants.ts b/src/hooks/media-queries/breakpoints.ts similarity index 59% rename from src/hooks/media-queries/constants.ts rename to src/hooks/media-queries/breakpoints.ts index 7003375..c061e5c 100644 --- a/src/hooks/media-queries/constants.ts +++ b/src/hooks/media-queries/breakpoints.ts @@ -1,21 +1,22 @@ -type BreakpointKey = - | 'mobile' - | 'phablet' - | 'tablet' - | 'classic' - | 'hd' - | 'desktop' - | 'widescreen' - | 'ultrawide'; +export const BREAKPOINT_ORDER = [ + 'phablet', + 'tablet', + 'classic', + 'hd', + 'desktop', + 'widescreen', + 'ultrawide', +] as const; + +export type BreakpointKey = (typeof BREAKPOINT_ORDER)[number]; type BreakpointEntry = [BreakpointKey, number]; -type BreakpointValueRecord = Record; type BreakpointQueryRecord = Record; // TODO: These hooks need to be manually syncronized with the -// breakpoint values in `media-queries.css / global.css`. -const BREAKPOINT_X: BreakpointValueRecord = { - mobile: 320, +// breakpoint values in `design-system.css / media-queries.css`. +const BREAKPOINT_X: Record = { + // "mobile" is considered anything before `phablet`. phablet: 540, tablet: 768, classic: 960, diff --git a/src/hooks/media-queries/useBreakpoint.ts b/src/hooks/media-queries/useBreakpoint.ts deleted file mode 100644 index 35f2f04..0000000 --- a/src/hooks/media-queries/useBreakpoint.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {useMediaQuery} from '@pkg/hooks'; -import {BREAKPOINT_QUERY_X} from './constants.ts'; - -// TODO: Consider a hook for `useBetweenBreakpoints(min, max)`. - -// It is recommended to use the individual `useMin*` hooks -// instead of this one. This hook is useful if we actually have -// a single for the entire app. -export function useBreakpoint() { - // const mobile = useMediaQuery(BREAKPOINT_QUERY_X.mobile, {defaultValue: true}); - - const phablet = useMediaQuery(BREAKPOINT_QUERY_X.phablet); - const tablet = useMediaQuery(BREAKPOINT_QUERY_X.tablet); - const classic = useMediaQuery(BREAKPOINT_QUERY_X.classic); - const hd = useMediaQuery(BREAKPOINT_QUERY_X.hd); - const desktop = useMediaQuery(BREAKPOINT_QUERY_X.desktop); - const widescreen = useMediaQuery(BREAKPOINT_QUERY_X.widescreen); - const ultrawide = useMediaQuery(BREAKPOINT_QUERY_X.ultrawide); - - // TODO: Does this need to be memoized? - return { - phablet, - tablet, - classic, - hd, - desktop, - widescreen, - ultrawide, - }; -} diff --git a/src/hooks/media-queries/useMinMedia.ts b/src/hooks/media-queries/useMinMedia.ts index 28f9e02..86fbb62 100644 --- a/src/hooks/media-queries/useMinMedia.ts +++ b/src/hooks/media-queries/useMinMedia.ts @@ -1,8 +1,11 @@ import {useMediaQuery} from '@pkg/hooks'; -import {BREAKPOINT_QUERY_X} from './constants.ts'; +import {BREAKPOINT_QUERY_X} from './breakpoints.ts'; -// TODO: If we ever implement a ``, we can remove -// all of these in favour of `useMediaQuery()`. +// These hooks are useful when there is no global "breakpoint provider". +// While we do offer a , it is not always recommended. +// The current implementation of `useBreakpoint()` causes re-renders when +// any breakpoint changes, even if it is not used by the component. Something +// like a `zustand` global store would be preferrable. export function useMinPhablet() { return useMediaQuery(BREAKPOINT_QUERY_X.phablet); diff --git a/src/hooks/useInterval.ts b/src/hooks/useInterval.ts new file mode 100644 index 0000000..c9ea162 --- /dev/null +++ b/src/hooks/useInterval.ts @@ -0,0 +1,29 @@ +import {useEffect, useRef} from 'react'; + +// Future features: +// 1. Execute immediately +// 2. Pause/resume vs stop + +type IntervalId = ReturnType | number; + +interface IntervalOptions { + durationMs?: number; + playing?: boolean; +} + +export function useInterval( + callback: () => void, + {durationMs = 0, playing = false}: IntervalOptions, +) { + const intervalId = useRef(0); + + useEffect(() => { + if (playing) { + intervalId.current = setInterval(callback, durationMs); + } else { + clearInterval(intervalId.current); + } + + return () => clearInterval(intervalId.current); + }, [callback, durationMs, playing]); +} diff --git a/src/providers/BreakpointProvider.tsx b/src/providers/BreakpointProvider.tsx new file mode 100644 index 0000000..b80e440 --- /dev/null +++ b/src/providers/BreakpointProvider.tsx @@ -0,0 +1,130 @@ +import {createContext, useContext, useMemo, type ReactNode} from 'react'; + +import {useMediaQuery} from '@pkg/hooks'; +import { + BREAKPOINT_ORDER, + BREAKPOINT_QUERY_X, + type BreakpointKey, +} from '@src/hooks/media-queries/breakpoints.ts'; + +export interface BreakpointState { + onlyMobile: boolean; + minPhablet: boolean; + minTablet: boolean; + minClassic: boolean; + minHd: boolean; + minDesktop: boolean; + minWidescreen: boolean; + minUltrawide: boolean; + between: ( + min?: BreakpointKey | 'mobile', + max?: BreakpointKey | 'infinite', + ) => boolean; +} + +export interface BreakpointProviderProps { + children: ReactNode; +} + +// TODO: The design of this Provider means that we re-render components +// even when they do not import a particular breakpoint value. To avoid +// this, each breakpoint needs to be its own context provider. Additionally, +// we cannot have a `between()` helper, as it would require all breakpoints +// to be in the same context and therefor cause unnecessary re-renders. +const BreakpointContext = createContext(null); + +export function BreakpointProvider({children}: BreakpointProviderProps) { + // We could include `prefersDark`, and rename our provider to + // ... but checking for `color-scheme` + // is likely to only ever happen once at the "theme" level. + // const prefersDark = usePrefersDark(); + + const minPhablet = useMediaQuery(BREAKPOINT_QUERY_X.phablet); + const minTablet = useMediaQuery(BREAKPOINT_QUERY_X.tablet); + const minClassic = useMediaQuery(BREAKPOINT_QUERY_X.classic); + const minHd = useMediaQuery(BREAKPOINT_QUERY_X.hd); + const minDesktop = useMediaQuery(BREAKPOINT_QUERY_X.desktop); + const minWidescreen = useMediaQuery(BREAKPOINT_QUERY_X.widescreen); + const minUltrawide = useMediaQuery(BREAKPOINT_QUERY_X.ultrawide); + + const value = useMemo(() => { + // Must be kept in sync with the `useMediaQuery()` calls above. + const queries = [ + minPhablet, + minTablet, + minClassic, + minHd, + minDesktop, + minWidescreen, + minUltrawide, + ]; + + // Debatable if we even want this helper, as it is just as + // easy to do a condition like: `minPhablet && !minDesktop`. + const between: BreakpointState['between'] = ( + min = 'mobile', + max = 'infinite', + ) => { + const minMatch = + min === 'mobile' ? true : queries[BREAKPOINT_ORDER.indexOf(min)]; + + // The `max` value represents the breakpoint you want your + // range to end BEFORE. Therefore, we check against `false`. + const maxMatch = + max === 'infinite' + ? true + : queries[BREAKPOINT_ORDER.indexOf(max)] === false; + + return minMatch && maxMatch; + }; + + return { + onlyMobile: !minPhablet, + minPhablet, + minTablet, + minClassic, + minHd, + minDesktop, + minWidescreen, + minUltrawide, + between, + }; + }, [ + minPhablet, + minTablet, + minClassic, + minHd, + minDesktop, + minWidescreen, + minUltrawide, + ]); + + return ( + + {children} + + ); +} + +// USAGE: +// All breakpoints are a `min-width` query. This allows you to do things like: +// 1. Am I at least `tablet` width? +// - const {minTablet} = useBreakpoint(); +// 2. Am I before `desktop` width? +// - if (!minDesktop) +// 3. Am I at least `phablet` width, but before `desktop` width? +// - if (between('phablet', 'desktop')) +// - This is the equivalent of `minPhablet && !minDesktop`. + +// eslint-disable-next-line react-refresh/only-export-components +export function useBreakpoint() { + const context = useContext(BreakpointContext); + + if (!context) { + throw new Error( + 'useBreakpoint() must be used within a ', + ); + } + + return context; +} diff --git a/src/providers/ThemeProvider.tsx b/src/providers/ThemeProvider.tsx index c7fe6c7..0174a58 100644 --- a/src/providers/ThemeProvider.tsx +++ b/src/providers/ThemeProvider.tsx @@ -9,6 +9,8 @@ import { type ReactNode, type SetStateAction, } from 'react'; + +import {TypedStorage} from '@pkg/classes'; import {usePrefersDark} from '@src/hooks/media-queries/usePrefersDark.ts'; export type AppTheme = 'light' | 'dark'; @@ -16,26 +18,39 @@ export type AppTheme = 'light' | 'dark'; export interface AppThemeState { theme: AppTheme; setTheme: Dispatch>; + toggleTheme: () => void; } export interface ThemeProviderProps { children: ReactNode; } +const themeStorage = new TypedStorage<{theme: AppTheme}>(); const ThemeContext = createContext(null); export function ThemeProvider({children}: ThemeProviderProps) { const prefersDark = usePrefersDark(); - const [theme, setTheme] = useState(() => - prefersDark ? 'dark' : 'light' - ); + const [theme, setTheme] = useState(() => { + const fromStorage = themeStorage.getItem('theme'); + if (fromStorage) return fromStorage; + + return prefersDark ? 'dark' : 'light'; + }); + + const toggleTheme = useCallback(() => { + setTheme((current) => (current === 'light' ? 'dark' : 'light')); + }, []); // Does this really need to be memoized? - const value = useMemo(() => ({theme, setTheme}), [theme]); + const value = useMemo( + () => ({theme, setTheme, toggleTheme}), + [theme, toggleTheme], + ); useEffect(() => { document.documentElement.dataset.theme = theme; + themeStorage.setItem('theme', theme); }, [theme]); return ( @@ -51,13 +66,5 @@ export function useTheme() { throw new Error('useTheme() must be used within a '); } - const toggle = useCallback(() => { - context?.setTheme((current) => (current === 'light' ? 'dark' : 'light')); - }, [context]); - - return { - // If memoization is required, does this destroy that? - ...context, - toggle, - }; + return context; } diff --git a/src/styles/design-system.css b/src/styles/design-system.css index 4a6f1e5..ca168e3 100644 --- a/src/styles/design-system.css +++ b/src/styles/design-system.css @@ -1,45 +1,42 @@ /* --- Colours --- */ :root { - /* - Consider using `color-mix` for color shades: - color-mix(in srgb, var(--color-primary), black 10%) - */ - - --color-light: #eadeda; - --color-dark: #181818; - - /* Dark Purple */ - /* - --color-primary-light: #3f386b; - --color-primary: #2e294e; - --color-primary-dark: #201c35; - */ - - /* Blue */ - --color-primary-light: #293be0; - --color-primary: #1b2cc1; - --color-primary-dark: #1725a1; - - /* Yellow */ - --color-secondary-light: #ffdd33; - --color-secondary: #ffd400; - --color-secondary-dark: #e0bb00; - - /* Red */ - /* --color-danger: #ee2e31; */ + --color-text-light: #1c5f54; + --color-text: #0f332d; + --color-text-dark: #09201c; + + --color-bg-light: #f6f8f2; + --color-bg: #e1e8d4; + --color-bg-dark: #d1dbbd; + + --color-primary-light: #397f63; + --color-primary: #2c634d; + --color-primary-dark: #204637; + + --color-secondary-light: #aecea1; + --color-secondary: #8eba7d; + --color-secondary-dark: #80b26c; + + --color-accent-light: #56b37a; + --color-accent: #41905f; + --color-accent-dark: #397f54; + + /* Persistent Colors */ + --color-danger-light: #e34f6c; + --color-danger: #d62246; + --color-danger-dark: #c21e3f; /* Shades */ - --shade-white-4: rgb(255, 255, 255, 0.04); - --shade-white-20: rgb(255, 255, 255, 0.2); - --shade-white-80: rgb(255, 255, 255, 0.8); + --shade-light-4: rgb(255, 255, 255, 0.04); + --shade-light-20: rgb(255, 255, 255, 0.2); + --shade-light-80: rgb(255, 255, 255, 0.8); - --shade-black-4: rgb(0, 0, 0, 0.04); - --shade-black-20: rgb(0, 0, 0, 0.2); - --shade-black-80: rgb(0, 0, 0, 0.8); + --shade-dark-4: rgb(0, 0, 0, 0.04); + --shade-dark-20: rgb(0, 0, 0, 0.2); + --shade-dark-80: rgb(0, 0, 0, 0.8); /* UI */ - --selection-primary: var(--color-light); - --selection-secondary: var(--color-secondary); + --selection-primary: var(--color-bg); + --selection-secondary: var(--color-text); } /* --- Sizing --- */ @@ -126,29 +123,32 @@ /* --- Dark Mode --- */ html[data-theme='dark'] { - /* - "Dark mode" essentially flips all of the design-system variables. - */ - - --color-light: #181818; - --color-dark: #eadeda; - - /* Yellow */ - --color-primary-light: #ffdd33; - --color-primary: #ffd400; - --color-primary-dark: #e0bb00; - - /* Blue */ - --color-secondary-light: #293be0; - --color-secondary: #1b2cc1; - --color-secondary-dark: #1725a1; - - /* Shades */ - --shade-white-4: rgb(0, 0, 0, 0.04); - --shade-white-20: rgb(0, 0, 0, 0.2); - --shade-white-80: rgb(0, 0, 0, 0.8); - - --shade-black-4: rgb(255, 255, 255, 0.04); - --shade-black-20: rgb(255, 255, 255, 0.2); - --shade-black-80: rgb(255, 255, 255, 0.8); + --color-text-light: #fff; + --color-text: #e7f8e2; + --color-text-dark: #d6f3ce; + + --color-bg-light: #213131; + --color-bg: #101818; + --color-bg-dark: #080c0c; + + --color-primary-light: #8bf8a1; + --color-primary: #60f67e; + --color-primary-dark: #3ef463; + + --color-secondary-light: #c0edd2; + --color-secondary: #98e1b7; + --color-secondary-dark: #81daa6; + + --color-accent-light: #eef47c; + --color-accent: #e9f056; + --color-accent-dark: #e4ed31; + + /* Shades (inverted) */ + --shade-light-4: rgb(0, 0, 0, 0.04); + --shade-light-20: rgb(0, 0, 0, 0.2); + --shade-light-80: rgb(0, 0, 0, 0.8); + + --shade-dark-4: rgb(255, 255, 255, 0.04); + --shade-dark-20: rgb(255, 255, 255, 0.2); + --shade-dark-80: rgb(255, 255, 255, 0.8); } diff --git a/src/styles/global.css b/src/styles/global.css index a6ccef1..51668c9 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,7 +1,7 @@ html { display: grid; - color: var(--color-light); - background-color: var(--color-dark); + color: var(--color-text); + background-color: var(--color-bg); } body { @@ -13,13 +13,13 @@ body { min-height: 100%; } +/* --- Typography --- */ + ::selection { color: var(--selection-primary); background-color: var(--selection-secondary); } -/* --- Typography --- */ - .font-body { font-family: var(--font-body); font-optical-sizing: auto; diff --git a/src/utilities/json.ts b/src/utilities/json.ts new file mode 100644 index 0000000..d49c0b1 --- /dev/null +++ b/src/utilities/json.ts @@ -0,0 +1,38 @@ +// Taken from: +// https://medium.com/@wujido20/runtime-types-in-typescript-5f74fc9dc6c4 +export type TypeGuard = (value: unknown) => T; + +export const typedString: TypeGuard = (value: unknown) => { + if (typeof value !== 'string') throw new Error(); + return value; +}; + +export const typedNumber: TypeGuard = (value: unknown) => { + if (typeof value !== 'number') throw new Error(); + return value; +}; + +export const typedArray = + (inner: TypeGuard) => + (value: unknown): T[] => { + if (!Array.isArray(value)) throw new Error(); + return value.map(inner); + }; + +/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */ +export const typedObject = >>( + inner: T, +) => { + return (value: unknown): {[P in keyof T]: ReturnType} => { + if (value === null || typeof value !== 'object') throw new Error(); + + const out: {[P in keyof T]: ReturnType} = {} as any; + + for (const k in inner) { + out[k] = inner[k]((value as any)[k]); + } + + return out; + }; +}; +/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */