Repository: kubotama/linkpage
Pull request #54: MUIを導入するMUIを導入する
Author: login: kubotama
Created: 2025-03-06T14:25:00Z | Merged: 2025-03-07T04:22:52Z



Diff:
diff --git a/package-lock.json b/package-lock.json
index 1c2b2b76..726e6145 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,12 @@
       "name": "linkpage",
       "version": "0.1.0",
       "dependencies": {
+        "@emotion/cache": "^11.14.0",
+        "@emotion/react": "^11.14.0",
+        "@emotion/styled": "^11.14.0",
+        "@mui/material": "^6.4.6",
+        "@mui/material-nextjs": "^6.4.3",
+        "@testing-library/user-event": "^14.6.1",
         "@types/jsdom": "^21.1.7",
         "jsdom": "^26.0.0",
         "next": "15.1.7",
@@ -87,7 +93,6 @@
       "version": "7.26.2",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
       "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/helper-validator-identifier": "^7.25.9",
@@ -166,7 +171,6 @@
       "version": "7.26.5",
       "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
       "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/parser": "^7.26.5",
@@ -220,7 +224,6 @@
       "version": "7.25.9",
       "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
       "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/traverse": "^7.25.9",
@@ -262,7 +265,6 @@
       "version": "7.25.9",
       "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
       "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6.9.0"
@@ -272,7 +274,6 @@
       "version": "7.25.9",
       "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
       "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6.9.0"
@@ -306,7 +307,6 @@
       "version": "7.26.7",
       "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
       "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/types": "^7.26.7"
@@ -561,7 +561,6 @@
       "version": "7.26.7",
       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
       "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "regenerator-runtime": "^0.14.0"
@@ -574,7 +573,6 @@
       "version": "7.25.9",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
       "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/code-frame": "^7.25.9",
@@ -589,7 +587,6 @@
       "version": "7.26.7",
       "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
       "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/code-frame": "^7.26.2",
@@ -608,7 +605,6 @@
       "version": "11.12.0",
       "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
       "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=4"
@@ -618,7 +614,6 @@
       "version": "7.26.7",
       "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
       "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/helper-string-parser": "^7.25.9",
@@ -779,6 +774,167 @@
         "tslib": "^2.4.0"
       }
     },
+    "node_modules/@emotion/babel-plugin": {
+      "version": "11.13.5",
+      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
+      "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.16.7",
+        "@babel/runtime": "^7.18.3",
+        "@emotion/hash": "^0.9.2",
+        "@emotion/memoize": "^0.9.0",
+        "@emotion/serialize": "^1.3.3",
+        "babel-plugin-macros": "^3.1.0",
+        "convert-source-map": "^1.5.0",
+        "escape-string-regexp": "^4.0.0",
+        "find-root": "^1.1.0",
+        "source-map": "^0.5.7",
+        "stylis": "4.2.0"
+      }
+    },
+    "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+      "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+      "license": "MIT"
+    },
+    "node_modules/@emotion/babel-plugin/node_modules/source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/@emotion/cache": {
+      "version": "11.14.0",
+      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
+      "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+      "license": "MIT",
+      "dependencies": {
+        "@emotion/memoize": "^0.9.0",
+        "@emotion/sheet": "^1.4.0",
+        "@emotion/utils": "^1.4.2",
+        "@emotion/weak-memoize": "^0.4.0",
+        "stylis": "4.2.0"
+      }
+    },
+    "node_modules/@emotion/hash": {
+      "version": "0.9.2",
+      "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+      "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+      "license": "MIT"
+    },
+    "node_modules/@emotion/is-prop-valid": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
+      "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
+      "license": "MIT",
+      "dependencies": {
+        "@emotion/memoize": "^0.9.0"
+      }
+    },
+    "node_modules/@emotion/memoize": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+      "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
+      "license": "MIT"
+    },
+    "node_modules/@emotion/react": {
+      "version": "11.14.0",
+      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
+      "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.18.3",
+        "@emotion/babel-plugin": "^11.13.5",
+        "@emotion/cache": "^11.14.0",
+        "@emotion/serialize": "^1.3.3",
+        "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+        "@emotion/utils": "^1.4.2",
+        "@emotion/weak-memoize": "^0.4.0",
+        "hoist-non-react-statics": "^3.3.1"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@emotion/serialize": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
+      "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
+      "license": "MIT",
+      "dependencies": {
+        "@emotion/hash": "^0.9.2",
+        "@emotion/memoize": "^0.9.0",
+        "@emotion/unitless": "^0.10.0",
+        "@emotion/utils": "^1.4.2",
+        "csstype": "^3.0.2"
+      }
+    },
+    "node_modules/@emotion/sheet": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+      "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
+      "license": "MIT"
+    },
+    "node_modules/@emotion/styled": {
+      "version": "11.14.0",
+      "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
+      "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.18.3",
+        "@emotion/babel-plugin": "^11.13.5",
+        "@emotion/is-prop-valid": "^1.3.0",
+        "@emotion/serialize": "^1.3.3",
+        "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+        "@emotion/utils": "^1.4.2"
+      },
+      "peerDependencies": {
+        "@emotion/react": "^11.0.0-rc.0",
+        "react": ">=16.8.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@emotion/unitless": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+      "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
+      "license": "MIT"
+    },
+    "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
+      "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      }
+    },
+    "node_modules/@emotion/utils": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
+      "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
+      "license": "MIT"
+    },
+    "node_modules/@emotion/weak-memoize": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+      "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
+      "license": "MIT"
+    },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
@@ -1868,7 +2024,6 @@
       "version": "0.3.8",
       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
       "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@jridgewell/set-array": "^1.2.1",
@@ -1883,7 +2038,6 @@
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
       "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6.0.0"
@@ -1893,7 +2047,6 @@
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
       "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6.0.0"
@@ -1903,20 +2056,269 @@
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
       "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/@jridgewell/trace-mapping": {
       "version": "0.3.25",
       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
       "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@jridgewell/resolve-uri": "^3.1.0",
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@mui/core-downloads-tracker": {
+      "version": "6.4.6",
+      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.6.tgz",
+      "integrity": "sha512-rho5Q4IscbrVmK9rCrLTJmjLjfH6m/NcqKr/mchvck0EIXlyYUB9+Z0oVmkt/+Mben43LMRYBH8q/Uzxj/c4Vw==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      }
+    },
+    "node_modules/@mui/material": {
+      "version": "6.4.6",
+      "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.6.tgz",
+      "integrity": "sha512-6UyAju+DBOdMogfYmLiT3Nu7RgliorimNBny1pN/acOjc+THNFVE7hlxLyn3RDONoZJNDi/8vO4AQQr6dLAXqA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.26.0",
+        "@mui/core-downloads-tracker": "^6.4.6",
+        "@mui/system": "^6.4.6",
+        "@mui/types": "^7.2.21",
+        "@mui/utils": "^6.4.6",
+        "@popperjs/core": "^2.11.8",
+        "@types/react-transition-group": "^4.4.12",
+        "clsx": "^2.1.1",
+        "csstype": "^3.1.3",
+        "prop-types": "^15.8.1",
+        "react-is": "^19.0.0",
+        "react-transition-group": "^4.4.5"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      },
+      "peerDependencies": {
+        "@emotion/react": "^11.5.0",
+        "@emotion/styled": "^11.3.0",
+        "@mui/material-pigment-css": "^6.4.6",
+        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@emotion/react": {
+          "optional": true
+        },
+        "@emotion/styled": {
+          "optional": true
+        },
+        "@mui/material-pigment-css": {
+          "optional": true
+        },
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/material-nextjs": {
+      "version": "6.4.3",
+      "resolved": "https://registry.npmjs.org/@mui/material-nextjs/-/material-nextjs-6.4.3.tgz",
+      "integrity": "sha512-4ZRLrcD1HeWpvY8c7MrKYKuaUSobtvqcLYeEfGh/x5ezzPgKizhl7C0jpVVEgf6g+C9OgOGbhLTVfks7Y2IBAQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.26.0"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      },
+      "peerDependencies": {
+        "@emotion/cache": "^11.11.0",
+        "@emotion/react": "^11.11.4",
+        "@emotion/server": "^11.11.0",
+        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "next": "^13.0.0 || ^14.0.0 || ^15.0.0",
+        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@emotion/cache": {
+          "optional": true
+        },
+        "@emotion/server": {
+          "optional": true
+        },
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/material/node_modules/react-is": {
+      "version": "19.0.0",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz",
+      "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==",
+      "license": "MIT"
+    },
+    "node_modules/@mui/private-theming": {
+      "version": "6.4.6",
+      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.6.tgz",
+      "integrity": "sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.26.0",
+        "@mui/utils": "^6.4.6",
+        "prop-types": "^15.8.1"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      },
+      "peerDependencies": {
+        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/styled-engine": {
+      "version": "6.4.6",
+      "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.6.tgz",
+      "integrity": "sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.26.0",
+        "@emotion/cache": "^11.13.5",
+        "@emotion/serialize": "^1.3.3",
+        "@emotion/sheet": "^1.4.0",
+        "csstype": "^3.1.3",
+        "prop-types": "^15.8.1"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      },
+      "peerDependencies": {
+        "@emotion/react": "^11.4.1",
+        "@emotion/styled": "^11.3.0",
+        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@emotion/react": {
+          "optional": true
+        },
+        "@emotion/styled": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/system": {
+      "version": "6.4.6",
+      "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.6.tgz",
+      "integrity": "sha512-FQjWwPec7pMTtB/jw5f9eyLynKFZ6/Ej9vhm5kGdtmts1z5b7Vyn3Rz6kasfYm1j2TfrfGnSXRvvtwVWxjpz6g==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.26.0",
+        "@mui/private-theming": "^6.4.6",
+        "@mui/styled-engine": "^6.4.6",
+        "@mui/types": "^7.2.21",
+        "@mui/utils": "^6.4.6",
+        "clsx": "^2.1.1",
+        "csstype": "^3.1.3",
+        "prop-types": "^15.8.1"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      },
+      "peerDependencies": {
+        "@emotion/react": "^11.5.0",
+        "@emotion/styled": "^11.3.0",
+        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@emotion/react": {
+          "optional": true
+        },
+        "@emotion/styled": {
+          "optional": true
+        },
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/types": {
+      "version": "7.2.21",
+      "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz",
+      "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/utils": {
+      "version": "6.4.6",
+      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.6.tgz",
+      "integrity": "sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.26.0",
+        "@mui/types": "^7.2.21",
+        "@types/prop-types": "^15.7.14",
+        "clsx": "^2.1.1",
+        "prop-types": "^15.8.1",
+        "react-is": "^19.0.0"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      },
+      "peerDependencies": {
+        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/utils/node_modules/react-is": {
+      "version": "19.0.0",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz",
+      "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==",
+      "license": "MIT"
+    },
     "node_modules/@next/env": {
       "version": "15.1.7",
       "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.7.tgz",
@@ -2120,6 +2522,16 @@
         "node": ">=14"
       }
     },
+    "node_modules/@popperjs/core": {
+      "version": "2.11.8",
+      "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+      "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
     "node_modules/@rtsao/scc": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -2180,7 +2592,6 @@
       "version": "10.4.0",
       "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
       "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
-      "dev": true,
       "license": "MIT",
       "peer": true,
       "dependencies": {
@@ -2201,7 +2612,6 @@
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
       "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
-      "dev": true,
       "license": "Apache-2.0",
       "peer": true,
       "dependencies": {
@@ -2278,6 +2688,19 @@
         }
       }
     },
+    "node_modules/@testing-library/user-event": {
+      "version": "14.6.1",
+      "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
+      "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12",
+        "npm": ">=6"
+      },
+      "peerDependencies": {
+        "@testing-library/dom": ">=7.21.4"
+      }
+    },
     "node_modules/@tootallnate/once": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -2320,7 +2743,6 @@
       "version": "5.0.4",
       "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
       "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
-      "dev": true,
       "license": "MIT",
       "peer": true
     },
@@ -2493,11 +2915,22 @@
         "undici-types": "~6.20.0"
       }
     },
+    "node_modules/@types/parse-json": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+      "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+      "license": "MIT"
+    },
+    "node_modules/@types/prop-types": {
+      "version": "15.7.14",
+      "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
+      "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
+      "license": "MIT"
+    },
     "node_modules/@types/react": {
       "version": "19.0.10",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
       "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "csstype": "^3.0.2"
@@ -2513,6 +2946,15 @@
         "@types/react": "^19.0.0"
       }
     },
+    "node_modules/@types/react-transition-group": {
+      "version": "4.4.12",
+      "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+      "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*"
+      }
+    },
     "node_modules/@types/stack-utils": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
@@ -2893,7 +3335,6 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-convert": "^2.0.1"
@@ -3255,6 +3696,21 @@
         "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
+    "node_modules/babel-plugin-macros": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+      "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "cosmiconfig": "^7.0.0",
+        "resolve": "^1.19.0"
+      },
+      "engines": {
+        "node": ">=10",
+        "npm": ">=6"
+      }
+    },
     "node_modules/babel-preset-current-node-syntax": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
@@ -3471,7 +3927,6 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
       "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6"
@@ -3521,7 +3976,6 @@
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^4.1.0",
@@ -3689,6 +4143,15 @@
         "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
       }
     },
+    "node_modules/clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/co": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -3725,7 +4188,6 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "color-name": "~1.1.4"
@@ -3738,7 +4200,6 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "devOptional": true,
       "license": "MIT"
     },
     "node_modules/color-string": {
@@ -3788,6 +4249,31 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/cosmiconfig": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+      "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/parse-json": "^4.0.0",
+        "import-fresh": "^3.2.1",
+        "parse-json": "^5.0.0",
+        "path-type": "^4.0.0",
+        "yaml": "^1.10.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/cosmiconfig/node_modules/yaml": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/create-jest": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
@@ -3886,7 +4372,6 @@
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/damerau-levenshtein": {
@@ -4143,10 +4628,19 @@
       "version": "0.5.16",
       "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
       "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
-      "dev": true,
       "license": "MIT",
       "peer": true
     },
+    "node_modules/dom-helpers": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+      "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.8.7",
+        "csstype": "^3.0.2"
+      }
+    },
     "node_modules/domexception": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
@@ -4256,7 +4750,6 @@
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
       "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "is-arrayish": "^0.2.1"
@@ -4266,7 +4759,6 @@
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
       "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/es-abstract": {
@@ -4454,7 +4946,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
       "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=10"
@@ -5142,6 +5633,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/find-root": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+      "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+      "license": "MIT"
+    },
     "node_modules/find-up": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -5253,7 +5750,6 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
       "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "dev": true,
       "license": "MIT",
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -5537,7 +6033,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -5605,7 +6100,6 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
       "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "function-bind": "^1.1.2"
@@ -5614,6 +6108,15 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "react-is": "^16.7.0"
+      }
+    },
     "node_modules/html-encoding-sniffer": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
@@ -5695,7 +6198,6 @@
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
       "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "parent-module": "^1.0.0",
@@ -5900,7 +6402,6 @@
       "version": "2.16.1",
       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
       "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "hasown": "^2.0.2"
@@ -7619,7 +8120,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
       "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/js-yaml": {
@@ -7679,7 +8179,6 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
       "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
-      "dev": true,
       "license": "MIT",
       "bin": {
         "jsesc": "bin/jsesc"
@@ -7699,7 +8198,6 @@
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
       "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/json-schema-traverse": {
@@ -7826,7 +8324,6 @@
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
       "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/locate-path": {
@@ -7870,7 +8367,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
       "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "js-tokens": "^3.0.0 || ^4.0.0"
@@ -7889,7 +8385,6 @@
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
       "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
-      "dev": true,
       "license": "MIT",
       "peer": true,
       "bin": {
@@ -8262,7 +8757,6 @@
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
       "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -8505,7 +8999,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
       "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "callsites": "^3.0.0"
@@ -8518,7 +9011,6 @@
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
       "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/code-frame": "^7.0.0",
@@ -8579,7 +9071,6 @@
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/path-scurry": {
@@ -8599,6 +9090,15 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/path-type": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/picocolors": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -8881,7 +9381,6 @@
       "version": "27.5.1",
       "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
       "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
-      "dev": true,
       "license": "MIT",
       "peer": true,
       "dependencies": {
@@ -8897,7 +9396,6 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
       "license": "MIT",
       "peer": true,
       "engines": {
@@ -8908,7 +9406,6 @@
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
       "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
-      "dev": true,
       "license": "MIT",
       "peer": true,
       "engines": {
@@ -8922,7 +9419,6 @@
       "version": "17.0.2",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
-      "dev": true,
       "license": "MIT",
       "peer": true
     },
@@ -8951,7 +9447,6 @@
       "version": "15.8.1",
       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
       "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "loose-envify": "^1.4.0",
@@ -9051,9 +9546,24 @@
       "version": "16.13.1",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
-      "dev": true,
       "license": "MIT"
     },
+    "node_modules/react-transition-group": {
+      "version": "4.4.5",
+      "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+      "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5",
+        "dom-helpers": "^5.0.1",
+        "loose-envify": "^1.4.0",
+        "prop-types": "^15.6.2"
+      },
+      "peerDependencies": {
+        "react": ">=16.6.0",
+        "react-dom": ">=16.6.0"
+      }
+    },
     "node_modules/read-cache": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -9118,7 +9628,6 @@
       "version": "0.14.1",
       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
       "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/regexp.prototype.flags": {
@@ -9163,7 +9672,6 @@
       "version": "1.22.10",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
       "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "is-core-module": "^2.16.0",
@@ -9207,7 +9715,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
       "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=4"
@@ -9992,6 +10499,12 @@
         }
       }
     },
+    "node_modules/stylis": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+      "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+      "license": "MIT"
+    },
     "node_modules/sucrase": {
       "version": "3.35.0",
       "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
@@ -10019,7 +10532,6 @@
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "has-flag": "^4.0.0"
@@ -10032,7 +10544,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">= 0.4"
diff --git a/package.json b/package.json
index a2f33aa5..17c81650 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,12 @@
     "lint": "next lint"
   },
   "dependencies": {
+    "@emotion/cache": "^11.14.0",
+    "@emotion/react": "^11.14.0",
+    "@emotion/styled": "^11.14.0",
+    "@mui/material": "^6.4.6",
+    "@mui/material-nextjs": "^6.4.3",
+    "@testing-library/user-event": "^14.6.1",
     "@types/jsdom": "^21.1.7",
     "jsdom": "^26.0.0",
     "next": "15.1.7",
diff --git a/src/app/components/BookmarkManager.test.tsx b/src/app/components/BookmarkManager.test.tsx
index a788e73b..a5df82f6 100644
--- a/src/app/components/BookmarkManager.test.tsx
+++ b/src/app/components/BookmarkManager.test.tsx
@@ -3,7 +3,8 @@ import "@testing-library/jest-dom";
 import fetchMock from "jest-fetch-mock";
 import React from "react";
 
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
 
 import { MessageProvider } from "../contexts/MessageContext";
 import BmMessage from "./bmMessage";
@@ -106,17 +107,14 @@ describe("更新されたブックマークが、APIにPOSTで送られる。",
       expect(screen.queryByText("Example Site")).toBeNull();
     });
 
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
+    const updateButton = screen.getByRole("button", { name: "更新" });
 
-    fireEvent.change(urlInput, {
-      target: { value: "https://www.example.com" },
-    });
-    fireEvent.change(titleInput, {
-      target: { value: "Example Site" },
-    });
-    fireEvent.click(updateButton);
+    const user = userEvent.setup();
+    await user.type(urlInput, "https://www.example.com");
+    await user.type(titleInput, "Example Site");
+    await user.click(updateButton);
 
     // Trigger bookmark update
     await waitFor(() => {
@@ -142,9 +140,9 @@ describe("更新されたブックマークが、APIにPOSTで送られる。",
       expect(screen.queryByText("Example Site")).toBeNull();
     });
 
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
+    const updateButton = screen.getByRole("button", { name: "更新" });
 
     const updatedBookmark = {
       url: "https://www.example.com",
@@ -156,13 +154,11 @@ describe("更新されたブックマークが、APIにPOSTで送られる。",
       status: 200,
     });
 
-    fireEvent.change(urlInput, {
-      target: { value: "https://www.example.com" },
-    });
-    fireEvent.change(titleInput, {
-      target: { value: "Example Site" },
-    });
-    fireEvent.click(updateButton);
+    const user = userEvent.setup();
+
+    await user.type(urlInput, "https://www.example.com");
+    await user.type(titleInput, "Example Site");
+    await user.click(updateButton);
 
     // Trigger bookmark update
     await waitFor(() => {
@@ -193,22 +189,19 @@ describe("更新されたブックマークが、APIにPOSTで送られる。",
       expect(fetchMock.mock.calls.length).toEqual(1);
     });
 
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
+    const updateButton = screen.getByRole("button", { name: "更新" });
 
     fetchMock.mockResponseOnce("API Error", {
       status: 500,
       headers: { "Content-Type": "text/plain" },
     });
 
-    fireEvent.change(urlInput, {
-      target: { value: "https://www.example.com" },
-    });
-    fireEvent.change(titleInput, {
-      target: { value: "Example Site" },
-    });
-    fireEvent.click(updateButton);
+    const user = userEvent.setup();
+    await user.type(urlInput, "https://www.example.com");
+    await user.type(titleInput, "Example Site");
+    await user.click(updateButton);
 
     await waitFor(() => {
       expect(fetchMock.mock.calls.length).toEqual(2);
@@ -234,19 +227,16 @@ describe("更新されたブックマークが、APIにPOSTで送られる。",
       expect(fetchMock.mock.calls.length).toEqual(1);
     });
 
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
+    const updateButton = screen.getByRole("button", { name: "更新" });
 
     fetchMock.mockRejectOnce(new Error("API Error"));
 
-    fireEvent.change(urlInput, {
-      target: { value: "https://www.example.com" },
-    });
-    fireEvent.change(titleInput, {
-      target: { value: "Example Site" },
-    });
-    fireEvent.click(updateButton);
+    const user = userEvent.setup();
+    await user.type(urlInput, "https://www.example.com");
+    await user.type(titleInput, "Example Site");
+    await user.click(updateButton);
 
     await waitFor(() => {
       expect(fetchMock.mock.calls.length).toEqual(2);
diff --git a/src/app/components/bmDetail.test.tsx b/src/app/components/bmDetail.test.tsx
index 60bd367c..29fe3045 100644
--- a/src/app/components/bmDetail.test.tsx
+++ b/src/app/components/bmDetail.test.tsx
@@ -3,7 +3,8 @@ import "@testing-library/jest-dom";
 import fetchMock from "jest-fetch-mock";
 import React from "react";
 
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
 
 import { MessageProvider } from "../contexts/MessageContext";
 import { BmDetail } from "./bmDetail";
@@ -14,7 +15,8 @@ describe("BmDetail", () => {
     fetchMock.resetMocks();
   });
 
-  it("入力された文字列が親コンポーネントに渡されること", () => {
+  it("入力された文字列が親コンポーネントに渡されること", async () => {
+    const user = userEvent.setup();
     const url = "https://mail.google.com/mail/";
     const onAddBookmark = jest.fn(); // モック関数を作成
 
@@ -25,20 +27,20 @@ describe("BmDetail", () => {
       </MessageProvider>
     );
 
-    const urlInput = screen.getByLabelText("url");
-    const textInput = screen.getByLabelText("title") as HTMLInputElement;
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const textInput = screen.getByRole("textbox", { name: "title" });
 
-    fireEvent.change(urlInput, {
-      target: { value: url },
-    });
-    fireEvent.change(textInput, {
-      target: { value: "Gmail" },
-    });
-    fireEvent.click(updateButton);
+    const updateButton = screen.getByRole("button", { name: "更新" });
+
+    await user.type(urlInput, url);
+    await user.type(textInput, "Gmail");
 
-    expect(onAddBookmark).toHaveBeenCalledTimes(1);
-    expect(onAddBookmark).toHaveBeenCalledWith(url, "Gmail");
+    await user.click(updateButton);
+
+    await waitFor(() => {
+      expect(onAddBookmark).toHaveBeenCalledTimes(1);
+      expect(onAddBookmark).toHaveBeenCalledWith(url, "Gmail");
+    });
   });
 
   it("すべてのエレメントが表示される", () => {
@@ -51,16 +53,20 @@ describe("BmDetail", () => {
       </MessageProvider>
     );
 
-    expect(screen.getByLabelText("url")).toBeVisible();
-    expect(screen.getByLabelText("title")).toBeInTheDocument();
-    expect(screen.getByText("タイトル")).toBeInTheDocument();
-    expect(screen.getByText("更新")).toBeInTheDocument();
+    expect(screen.getByRole("textbox", { name: "url" })).toBeInTheDocument();
+    expect(screen.getByRole("textbox", { name: "title" })).toBeInTheDocument();
+    expect(
+      screen.getByRole("button", { name: "タイトル" })
+    ).toBeInTheDocument();
+    expect(screen.getByRole("button", { name: "更新" })).toBeInTheDocument();
   });
 
   it("タイトルを取得するボタンをクリック", async () => {
     // タイトルを取得するボタンをクリックすると、タイトルを取得するAPIを呼び出す。
     // パラメータとしてURLのテキストボックスに入力された文字列が渡される。
     // タイトルのテキストボックスに、APIから返されたタイトルが表示される。
+    const user = userEvent.setup();
+
     const url = "https://mail.google.com/mail/";
     const title = "Gmail";
     const onAddBookmark = jest.fn(); // モック関数を作成
@@ -72,21 +78,25 @@ describe("BmDetail", () => {
       </MessageProvider>
     );
 
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const titleButton = screen.getByText("タイトル");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
+
+    const titleButton = screen.getByRole("button", { name: "タイトル" });
+
+    // タイトルをAPIで取得する前は、クリアされていることを確認
+    await user.clear(titleInput);
+    expect(titleInput).toHaveValue("");
 
     fetchMock.mockResponseOnce(title);
 
-    fireEvent.change(urlInput, {
-      target: { value: url },
-    });
-    fireEvent.click(titleButton);
+    await user.type(urlInput, url);
+
+    await user.click(titleButton);
 
     await waitFor(() => {
-      expect(titleInput.value).toEqual(title);
       expect(fetchMock).toHaveBeenCalledTimes(1);
       expect(fetchMock.mock.calls[0][0]).toEqual("/api/title?url=" + url);
+      expect(titleInput).toHaveValue(title);
     });
   });
 
@@ -95,6 +105,8 @@ describe("BmDetail", () => {
     const url = "https://mail.google.com/mail/";
     const onAddBookmark = jest.fn(); // モック関数を作成
 
+    const user = userEvent.setup();
+
     fetchMock.mockResponseOnce("Can't find title", {
       status: 500,
       headers: { "Content-Type": "text/plain" },
@@ -107,29 +119,23 @@ describe("BmDetail", () => {
       </MessageProvider>
     );
 
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title");
-    const titleButton = screen.getByText("タイトル");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
 
-    fireEvent.change(urlInput, {
-      target: { value: url },
-    });
-    fireEvent.click(titleButton);
+    const titleButton = screen.getByRole("button", { name: "タイトル" });
+
+    await user.type(urlInput, url);
+    await user.click(titleButton);
 
     await waitFor(() => {
-      if (!(titleInput instanceof HTMLInputElement)) {
-        fail();
-      }
-      expect(titleInput.value).toEqual("");
       expect(fetchMock).toHaveBeenCalledTimes(1);
       expect(fetchMock.mock.calls[0][0]).toEqual("/api/title?url=" + url);
 
       expect(screen.getByTestId("bm-message")).toHaveTextContent(
-        /^Can't find title$/
+        "Can't find title: [500] " + url
       );
-      expect(
-        screen.queryByRole("button", { name: "確認" })
-      ).toBeInTheDocument();
+      expect(screen.getByRole("button", { name: "確認" })).toBeInTheDocument();
+      expect(titleInput).toHaveValue("");
     });
   });
 
@@ -139,6 +145,7 @@ describe("BmDetail", () => {
     const title = "Gmail";
     const title_edited = "GMAIL";
     const onAddBookmark = jest.fn(); // モック関数を作成
+    const user = userEvent.setup();
 
     render(
       <MessageProvider>
@@ -147,52 +154,35 @@ describe("BmDetail", () => {
       </MessageProvider>
     );
 
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const titleButton = screen.getByText("タイトル");
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
+
+    const titleButton = screen.getByRole("button", { name: "タイトル" });
+    const updateButton = screen.getByRole("button", { name: "更新" });
 
     fetchMock.mockResponseOnce(title);
 
-    fireEvent.change(urlInput, {
-      target: { value: url },
-    });
-    fireEvent.click(titleButton);
+    await user.type(urlInput, url);
+    await user.click(titleButton);
+
+    await user.clear(titleInput);
+    await user.type(titleInput, title_edited);
+    await user.click(updateButton);
 
     await waitFor(() => {
-      fireEvent.change(titleInput, {
-        target: { value: title_edited },
-      });
-      fireEvent.click(updateButton);
-      expect(titleInput.value).toEqual(title_edited);
+      expect(titleInput).toHaveValue(title_edited);
       expect(fetchMock).toHaveBeenCalledTimes(1);
       expect(fetchMock.mock.calls[0][0]).toEqual("/api/title?url=" + url);
     });
   });
 
-  it("URLが空白の場合、タイトルボタン、更新ボタンが無効になっている。", async () => {
-    const onAddBookmark = jest.fn();
-
-    render(
-      <MessageProvider>
-        <BmMessage />
-        <BmDetail onAddBookmark={onAddBookmark} />
-      </MessageProvider>
-    );
-
-    const urlInput = screen.getByLabelText("url");
-    const titleButton = screen.getByText("タイトル");
-    const updateButton = screen.getByText("更新");
-
-    fireEvent.change(urlInput, {
-      target: { value: "" },
-    });
-    expect(titleButton).toBeDisabled();
-    expect(updateButton).toBeDisabled();
-  });
+  it("APIからタイトルが返ってこない場合、メッセージ領域にエラーメッセージを表示する。", async () => {
+    // タイトルを取得するボタンをクリックして、エラーコード500の場合、タイトルのテキストボックスに、なにも表示しない。
+    const url = "https://mail.google.com/mail/";
+    const onAddBookmark = jest.fn(); // モック関数を作成
+    const user = userEvent.setup();
 
-  it("URLが入力されていて、タイトルが空白の場合、タイトルボタンが有効、更新ボタンが無効になっている。", async () => {
-    const onAddBookmark = jest.fn();
+    fetchMock.mockResponseOnce("");
 
     render(
       <MessageProvider>
@@ -201,39 +191,23 @@ describe("BmDetail", () => {
       </MessageProvider>
     );
 
-    const urlInput = screen.getByLabelText("url");
-    const titleButton = screen.getByText("タイトル");
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
 
-    fireEvent.change(urlInput, {
-      target: { value: "https://mail.google.com/mail/" },
-    });
-    expect(titleButton).toBeEnabled();
-    expect(updateButton).toBeDisabled();
-  });
+    const titleButton = screen.getByRole("button", { name: "タイトル" });
 
-  it("URLとタイトルが入力されている場合、タイトルボタン、更新ボタンが有効になっている。", async () => {
-    const onAddBookmark = jest.fn();
+    await user.type(urlInput, url);
+    await user.click(titleButton);
 
-    render(
-      <MessageProvider>
-        <BmMessage />
-        <BmDetail onAddBookmark={onAddBookmark} />
-      </MessageProvider>
-    );
-
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const titleButton = screen.getByText("タイトル");
-    const updateButton = screen.getByText("更新");
+    await waitFor(() => {
+      expect(fetchMock).toHaveBeenCalledTimes(1);
+      expect(fetchMock.mock.calls[0][0]).toEqual("/api/title?url=" + url);
 
-    fireEvent.change(urlInput, {
-      target: { value: "https://mail.google.com/mail/" },
-    });
-    fireEvent.change(titleInput, {
-      target: { value: "Gmail" },
+      expect(screen.getByTestId("bm-message")).toHaveTextContent(
+        "Can't find title: " + url
+      );
+      expect(screen.getByRole("button", { name: "確認" })).toBeInTheDocument();
+      expect(titleInput).toHaveValue("");
     });
-    expect(titleButton).toBeEnabled();
-    expect(updateButton).toBeEnabled();
   });
 });
diff --git a/src/app/components/bmDetail.tsx b/src/app/components/bmDetail.tsx
index 599e26dc..9468a95c 100644
--- a/src/app/components/bmDetail.tsx
+++ b/src/app/components/bmDetail.tsx
@@ -1,4 +1,7 @@
-import React, { useEffect, useRef, useState } from "react";
+import React, { useState } from "react";
+
+import Button from "@mui/material/Button";
+import TextField from "@mui/material/TextField";
 
 import { useMessage } from "../contexts/MessageContext";
 
@@ -11,68 +14,59 @@ export const BmDetail: React.FC<BmDetailProps> = ({
 }) => {
   const [textUrl, setTextUrl] = useState("");
   const [textTitle, setTextTitle] = useState("");
-  const [textUrlDisabled, setTextUrlDisabled] = useState(true);
-  const [textTitleDisabled, setTextTitleDisabled] = useState(true);
-  const textUrlRef1 = useRef<HTMLInputElement>(null);
-  const textTitleRef2 = useRef<HTMLInputElement>(null);
   const { setMessage } = useMessage();
 
   const updateClick = () => {
-    if (textUrlRef1.current && textTitleRef2.current) {
-      const inputTextUrl = textUrlRef1.current.value;
-      const inputTextTitle = textTitleRef2.current.value;
-      onAddBookmark(inputTextUrl, inputTextTitle);
-    }
+    onAddBookmark(textUrl, textTitle);
   };
 
   const titleClick = async () => {
-    const response = await fetch("/api/title?url=" + textUrl);
-    const text = await response.text();
-
-    if (!response.ok) {
-      setTextTitle("");
-      setMessage({ text: text });
-    } else {
-      setTextTitle(text);
-    }
+    fetch("/api/title?url=" + textUrl)
+      .then((response) => {
+        if (!response.ok) {
+          throw new Error(`Can't find title: [${response.status}] `);
+        } else {
+          return response.text();
+        }
+      })
+      .then((text) => {
+        if (!text) {
+          throw new Error(`Can't find title: `);
+        } else {
+          setTextTitle(text);
+          setMessage({ text: "" });
+        }
+      })
+      .catch((error) => {
+        setTextTitle("");
+        setMessage({ text: error.message + textUrl });
+      });
   };
 
-  useEffect(() => {
-    if (textUrl === "") {
-      setTextUrlDisabled(true);
-      setTextTitleDisabled(true);
-    } else {
-      setTextUrlDisabled(false);
-      if (textTitle === "") {
-        setTextTitleDisabled(true);
-      } else {
-        setTextTitleDisabled(false);
-      }
-    }
-  }, [textUrl, textTitle]);
-
   return (
     <div>
-      <input
+      <TextField
+        id="url"
         type="text"
+        label="url"
         aria-label="url"
         value={textUrl}
-        onChange={(e) => setTextUrl(e.target.value)}
-        ref={textUrlRef1}
+        onChange={(e) => {
+          setTextUrl(e.target.value);
+        }}
       />
-      <input
+      <TextField
+        id="title"
         type="text"
+        label="title"
         aria-label="title"
         value={textTitle}
-        onChange={(e) => setTextTitle(e.target.value)}
-        ref={textTitleRef2}
+        onChange={(e) => {
+          setTextTitle(e.target.value);
+        }}
       />
-      <button onClick={titleClick} disabled={textUrlDisabled}>
-        タイトル
-      </button>
-      <button onClick={updateClick} disabled={textTitleDisabled}>
-        更新
-      </button>
+      <Button onClick={titleClick}>タイトル</Button>
+      <Button onClick={updateClick}>更新</Button>
     </div>
   );
 };
diff --git a/src/app/components/bmMessage.tsx b/src/app/components/bmMessage.tsx
index dd74c3a6..960d53c8 100644
--- a/src/app/components/bmMessage.tsx
+++ b/src/app/components/bmMessage.tsx
@@ -1,3 +1,4 @@
+import Button from "@mui/material/Button";
 import { useEffect, useState } from "react";
 
 import { useMessage } from "../contexts/MessageContext";
@@ -16,13 +17,13 @@ const BmMessage = () => {
         ) : (
           <div data-testid="bm-message">linkpage</div>
         )}
-        <button
+        <Button
           onClick={() => {
             setMessage({ text: "" });
           }}
         >
           確認
-        </button>
+        </Button>
       </div>
     );
   }, [message, setMessage]);
diff --git a/src/app/globals.css b/src/app/globals.css
index 6b717ad3..8b137891 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,21 +1 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
 
-:root {
-  --background: #ffffff;
-  --foreground: #171717;
-}
-
-@media (prefers-color-scheme: dark) {
-  :root {
-    --background: #0a0a0a;
-    --foreground: #ededed;
-  }
-}
-
-body {
-  color: var(--foreground);
-  background: var(--background);
-  font-family: Arial, Helvetica, sans-serif;
-}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index cd99f2c4..4e3af935 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,7 @@
 import type { Metadata } from "next";
 import { Geist, Geist_Mono } from "next/font/google";
 import "./globals.css";
+import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
 
 const geistSans = Geist({
   variable: "--font-geist-sans",
@@ -27,7 +28,7 @@ export default function RootLayout({
       <body
         className={`${geistSans.variable} ${geistMono.variable} antialiased`}
       >
-        {children}
+        <AppRouterCacheProvider>{children}</AppRouterCacheProvider>
       </body>
     </html>
   );
diff --git a/src/app/page.test.tsx b/src/app/page.test.tsx
index 7ef779f9..87d76908 100644
--- a/src/app/page.test.tsx
+++ b/src/app/page.test.tsx
@@ -32,8 +32,13 @@ describe("テスト環境を動作確認するためのサンプルのテスト"
       expect(screen.getByText("kubotama/linkpage")).toBeInTheDocument();
       expect(screen.getByText("Google")).toBeInTheDocument();
 
-      expect(screen.getByLabelText("url")).toBeInTheDocument();
-      expect(screen.getByLabelText("title")).toBeInTheDocument();
+      // expect(screen.getByLabelText("url")).toBeInTheDocument();
+      // expect(screen.getByLabelText("title")).toBeInTheDocument();
+
+      expect(screen.getByRole("textbox", { name: "url" })).toBeInTheDocument();
+      expect(
+        screen.getByRole("textbox", { name: "title" })
+      ).toBeInTheDocument();
       expect(screen.getByText("タイトル")).toBeInTheDocument();
       expect(screen.getByText("更新")).toBeInTheDocument();
 


Reviews:
user: login: gemini-code-assist[bot]
body: Hello @kubotama, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

## Summary of Changes
This pull request introduces Material UI (MUI) to the project. The changes primarily involve adding MUI-related dependencies to `package-lock.json` and `package.json`, and updating the test files to use MUI components and `userEvent` for more accurate testing. The global CSS file was also removed, likely to be replaced by MUI's styling system. The layout file was updated to use `AppRouterCacheProvider` from `@mui/material-nextjs/v14-appRouter`.

### Highlights
* **MUI Installation**: MUI and its related dependencies (@emotion/cache, @emotion/react, @emotion/styled, @mui/material, @mui/material-nextjs) have been added to the project.
* **Testing Library Update**: The test files (`BookmarkManager.test.tsx` and `bmDetail.test.tsx`) have been updated to use `userEvent` instead of `fireEvent` for simulating user interactions, and the tests have been adjusted to work with MUI components.
* **Layout Update**: The `src/app/layout.tsx` file now includes `AppRouterCacheProvider` to properly integrate MUI with Next.js's app router.
* **CSS Reset**: The `src/app/globals.css` file has been removed, suggesting a shift towards MUI's styling solution.

### Changelog
<details>
<summary>Click here to see the changelog</summary>

* **package-lock.json**
  * Added MUI and Emotion dependencies.
  * Removed `dev` flag from several Babel dependencies.
  * Added `@popperjs/core` dependency.
  * Added `clsx` dependency.
  * Added `hoist-non-react-statics` dependency.
  * Added `@types/prop-types` dependency.
  * Added `@types/react-transition-group` dependency.
  * Added `dom-helpers` dependency.
  * Added `stylis` dependency.
  * Added `babel-plugin-macros` dependency.
* **package.json**
  * Added MUI and Emotion dependencies.
  * Added `@testing-library/user-event` dependency.
  * Removed `@types/react` as a dev dependency.
* **src/app/components/BookmarkManager.test.tsx**
  * Replaced `fireEvent` with `userEvent` for simulating user interactions.
  * Updated `getByLabelText` calls to `getByRole` calls to align with MUI components.
  * Added `userEvent.setup()` to set up user event handlers.
* **src/app/components/bmDetail.test.tsx**
  * Replaced `fireEvent` with `userEvent` for simulating user interactions.
  * Updated `getByLabelText` calls to `getByRole` calls to align with MUI components.
  * Added `userEvent.setup()` to set up user event handlers.
  * Updated assertions to check for correct values and error messages.
  * Added checks to ensure title input is cleared before fetching new titles.
* **src/app/components/bmDetail.tsx**
  * Imported MUI `Button` and `TextField` components.
  * Replaced HTML input elements with MUI `TextField` components.
  * Replaced HTML button elements with MUI `Button` components.
  * Removed `useRef` and `useEffect` hooks related to disabling the title and update buttons.
  * Updated `titleClick` function to handle API errors and set appropriate messages.
  * Removed `textUrlDisabled` and `textTitleDisabled` state variables.
* **src/app/components/bmMessage.tsx**
  * Imported MUI `Button` component.
  * Replaced HTML button element with MUI `Button` component.
* **src/app/globals.css**
  * Removed Tailwind CSS directives and custom CSS variables.
* **src/app/layout.tsx**
  * Added `AppRouterCacheProvider` from `@mui/material-nextjs/v14-appRouter` to wrap the children.
* **src/app/page.test.tsx**
  * Updated `getByLabelText` calls to `getByRole` calls to align with MUI components.

</details>


<details>

<summary><b>Using Gemini Code Assist</b></summary>
<br>

The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips.

<b>Invoking Gemini</b>

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands.

Feature | Command | Description
--- | --- | ---
Code Review | `/gemini review` | Performs a code review for the current pull request in its current state.
Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state.
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments.
Help | `/gemini help` | Displays a list of available commands.

<b>Customization</b>

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/review-github-code).

<b>Limitations & Feedback</b>

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments to provide feedback.

</details>

<b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b>


[^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/review-github-code). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).

---

**Trivia time!**

What company originally created and maintains Material UI (MUI)?

<details>
<summary><b>Click here for the answer</b></summary>
<br>
Google originally created and maintains Material UI (MUI).

</details>

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T14:25:17Z
user: login: gemini-code-assist[bot]
body: ## Code Review
The pull request introduces MUI (Material UI) components to the project, enhancing the UI with pre-built, customizable React components. The changes include adding MUI dependencies, updating component tests to use `userEvent` instead of `fireEvent`, and modifying the `bmDetail` component to use MUI `TextField` and `Button` components. The layout is also updated to use `AppRouterCacheProvider` from `@mui/material-nextjs`. Overall, the changes seem well-structured and improve the project's UI framework.

### Merge Readiness
The pull request appears to be well-structured and introduces MUI components effectively. There are no critical or high severity issues. I am unable to directly approve the pull request, and users should have others review and approve this code before merging. However, based on my review, the changes seem reasonable and improve the project's UI framework. It is recommended to address the medium severity issues before merging.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T14:32:08Z
user: login: gemini-code-assist[bot]
body: ## Code Review
The pull request introduces MUI (Material UI) to the project, enhancing the UI components and overall styling. The changes include updates to the test files to use `userEvent` and `getByRole` for improved accessibility and more accurate event handling. The `bmDetail` component is refactored to use MUI components and includes improved error handling. The `globals.css` file is updated to remove Tailwind CSS imports. Overall, the changes improve the project's UI, accessibility, and error handling.

### Summary of Findings
* **Accessibility improvements in tests**: The test files are updated to use `getByRole` instead of `getByLabelText` for better accessibility. This ensures that the tests are more robust and reflect how users with assistive technologies interact with the application.
* **Modernize event handling in tests**: The test files are updated to use `userEvent` instead of `fireEvent` for simulating user interactions. This provides a more accurate simulation of user behavior and improves the reliability of the tests.
* **Error handling in `bmDetail.tsx`**: The `bmDetail.tsx` component now includes more robust error handling when fetching the title from the API. It displays an error message to the user if the API call fails or if the title is not found.
* **Component styling**: The components are updated to use MUI components for styling, which provides a consistent look and feel across the application.
* **Unnecessary state variables and refs**: The `bmDetail.tsx` component removes unnecessary state variables and refs, simplifying the component's logic and improving its performance.
* **Tailwind CSS**: The `globals.css` file removes Tailwind CSS imports, as MUI is now used for styling.

### Merge Readiness
The pull request is in good shape and ready to be merged. The changes improve the project's UI, accessibility, and error handling. There are no critical or high severity issues that need to be addressed before merging. However, it's always a good idea to have another reviewer take a look before merging, as I am unable to directly approve the pull request. The author should ensure that all tests pass and that the changes do not introduce any regressions.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T14:33:14Z

Review comments:
diff_hunk: @@ -3,7 +3,8 @@ import "@testing-library/jest-dom";
 import fetchMock from "jest-fetch-mock";
 import React from "react";
 
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
path: src/app/components/BookmarkManager.test.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider replacing `fireEvent` with `userEvent` throughout the test suite for more realistic user interactions. `userEvent` simulates actual user behavior more closely, leading to more reliable tests.

```typescript
import userEvent from "@testing-library/user-event";
```
created_at: 2025-03-06T14:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,17 +107,14 @@ describe("更新されたブックマークが、APIにPOSTで送られる。",
       expect(screen.queryByText("Example Site")).toBeNull();
     });
 
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
+    const updateButton = screen.getByRole("button", { name: "更新" });
 
-    fireEvent.change(urlInput, {
-      target: { value: "https://www.example.com" },
-    });
-    fireEvent.change(titleInput, {
-      target: { value: "Example Site" },
-    });
-    fireEvent.click(updateButton);
+    const user = userEvent.setup();
+    await user.type(urlInput, "https://www.example.com");
+    await user.type(titleInput, "Example Site");
+    await user.click(updateButton);
 
     // Trigger bookmark update
path: src/app/components/BookmarkManager.test.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Since you're using `userEvent`, you can simplify the event triggering using `user.type` and `user.click`. This makes the test more readable and closer to actual user interactions.

```typescript
    const user = userEvent.setup();
    await user.type(urlInput, "https://www.example.com");
    await user.type(titleInput, "Example Site");
    await user.click(updateButton);
```
created_at: 2025-03-06T14:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +15,8 @@ describe("BmDetail", () => {
     fetchMock.resetMocks();
   });
 
-  it("入力された文字列が親コンポーネントに渡されること", () => {
+  it("入力された文字列が親コンポーネントに渡されること", async () => {
path: src/app/components/bmDetail.test.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider setting up `userEvent` at the beginning of the test to avoid repetition.

```typescript
  it("入力された文字列が親コンポーネントに渡されること", async () => {
    const user = userEvent.setup();
```
created_at: 2025-03-06T14:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,20 +27,20 @@ describe("BmDetail", () => {
       </MessageProvider>
     );
 
-    const urlInput = screen.getByLabelText("url");
-    const textInput = screen.getByLabelText("title") as HTMLInputElement;
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const textInput = screen.getByRole("textbox", { name: "title" });
 
-    fireEvent.change(urlInput, {
-      target: { value: url },
-    });
-    fireEvent.change(textInput, {
-      target: { value: "Gmail" },
-    });
-    fireEvent.click(updateButton);
+    const updateButton = screen.getByRole("button", { name: "更新" });
path: src/app/components/bmDetail.test.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Using `getByRole` with appropriate ARIA roles is generally preferred over `getByLabelText` for better accessibility and more robust tests. Consider updating these selectors.

```typescript
    const urlInput = screen.getByRole("textbox", { name: "url" });
    const textInput = screen.getByRole("textbox", { name: "title" });

    const updateButton = screen.getByRole("button", { name: "更新" });
```
created_at: 2025-03-06T14:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -95,6 +105,8 @@ describe("BmDetail", () => {
     const url = "https://mail.google.com/mail/";
     const onAddBookmark = jest.fn(); // モック関数を作成
 
+    const user = userEvent.setup();
+
     fetchMock.mockResponseOnce("Can't find title", {
       status: 500,
       headers: { "Content-Type": "text/plain" },
path: src/app/components/bmDetail.test.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Using `getByRole` with appropriate ARIA roles is generally preferred over `getByLabelText` for better accessibility and more robust tests. Consider updating these selectors.

```typescript
    const urlInput = screen.getByRole("textbox", { name: "url" });
    const titleInput = screen.getByRole("textbox", { name: "title" });
```
created_at: 2025-03-06T14:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -201,39 +191,23 @@ describe("BmDetail", () => {
       </MessageProvider>
     );
 
-    const urlInput = screen.getByLabelText("url");
-    const titleButton = screen.getByText("タイトル");
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
 
-    fireEvent.change(urlInput, {
-      target: { value: "https://mail.google.com/mail/" },
-    });
-    expect(titleButton).toBeEnabled();
-    expect(updateButton).toBeDisabled();
-  });
+    const titleButton = screen.getByRole("button", { name: "タイトル" });
 
-  it("URLとタイトルが入力されている場合、タイトルボタン、更新ボタンが有効になっている。", async () => {
-    const onAddBookmark = jest.fn();
+    await user.type(urlInput, url);
+    await user.click(titleButton);
 
-    render(
-      <MessageProvider>
-        <BmMessage />
-        <BmDetail onAddBookmark={onAddBookmark} />
-      </MessageProvider>
-    );
-
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const titleButton = screen.getByText("タイトル");
-    const updateButton = screen.getByText("更新");
+    await waitFor(() => {
+      expect(fetchMock).toHaveBeenCalledTimes(1);
+      expect(fetchMock.mock.calls[0][0]).toEqual("/api/title?url=" + url);
path: src/app/components/bmDetail.test.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider using `getByRole` instead of `getByText` for better accessibility and more robust tests.

```typescript
    const titleButton = screen.getByRole("button", { name: "タイトル" });
```
created_at: 2025-03-06T14:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -201,39 +191,23 @@ describe("BmDetail", () => {
       </MessageProvider>
     );
 
-    const urlInput = screen.getByLabelText("url");
-    const titleButton = screen.getByText("タイトル");
-    const updateButton = screen.getByText("更新");
+    const urlInput = screen.getByRole("textbox", { name: "url" });
+    const titleInput = screen.getByRole("textbox", { name: "title" });
 
-    fireEvent.change(urlInput, {
-      target: { value: "https://mail.google.com/mail/" },
-    });
-    expect(titleButton).toBeEnabled();
-    expect(updateButton).toBeDisabled();
-  });
+    const titleButton = screen.getByRole("button", { name: "タイトル" });
 
-  it("URLとタイトルが入力されている場合、タイトルボタン、更新ボタンが有効になっている。", async () => {
-    const onAddBookmark = jest.fn();
+    await user.type(urlInput, url);
+    await user.click(titleButton);
 
-    render(
-      <MessageProvider>
-        <BmMessage />
-        <BmDetail onAddBookmark={onAddBookmark} />
-      </MessageProvider>
-    );
-
-    const urlInput = screen.getByLabelText("url");
-    const titleInput = screen.getByLabelText("title") as HTMLInputElement;
-    const titleButton = screen.getByText("タイトル");
-    const updateButton = screen.getByText("更新");
+    await waitFor(() => {
+      expect(fetchMock).toHaveBeenCalledTimes(1);
+      expect(fetchMock.mock.calls[0][0]).toEqual("/api/title?url=" + url);
 
path: src/app/components/bmDetail.test.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider using `getByRole` instead of `getByText` for better accessibility and more robust tests.

```typescript
    const updateButton = screen.getByRole("button", { name: "更新" });
```
created_at: 2025-03-06T14:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,68 +14,59 @@ export const BmDetail: React.FC<BmDetailProps> = ({
 }) => {
   const [textUrl, setTextUrl] = useState("");
   const [textTitle, setTextTitle] = useState("");
-  const [textUrlDisabled, setTextUrlDisabled] = useState(true);
-  const [textTitleDisabled, setTextTitleDisabled] = useState(true);
-  const textUrlRef1 = useRef<HTMLInputElement>(null);
-  const textTitleRef2 = useRef<HTMLInputElement>(null);
   const { setMessage } = useMessage();
 
   const updateClick = () => {
-    if (textUrlRef1.current && textTitleRef2.current) {
-      const inputTextUrl = textUrlRef1.current.value;
-      const inputTextTitle = textTitleRef2.current.value;
-      onAddBookmark(inputTextUrl, inputTextTitle);
-    }
+    onAddBookmark(textUrl, textTitle);
   };
 
   const titleClick = async () => {
-    const response = await fetch("/api/title?url=" + textUrl);
-    const text = await response.text();
-
-    if (!response.ok) {
-      setTextTitle("");
-      setMessage({ text: text });
-    } else {
-      setTextTitle(text);
-    }
+    fetch("/api/title?url=" + textUrl)
+      .then((response) => {
+        if (!response.ok) {
+          throw new Error(`Can't find title: [${response.status}] `);
+        } else {
+          return response.text();
+        }
+      })
+      .then((text) => {
+        if (!text) {
+          throw new Error(`Can't find title: `);
+        } else {
+          setTextTitle(text);
+          setMessage({ text: "" });
+        }
+      })
+      .catch((error) => {
+        setTextTitle("");
+        setMessage({ text: error.message + textUrl });
+      });
path: src/app/components/bmDetail.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider refactoring the `titleClick` function to handle errors more gracefully. Instead of directly setting the `textTitle` to an empty string in the `catch` block, maintain the previous value or set a default value to avoid unexpected UI changes. Also, consider adding a more specific error message to the `setMessage` function to provide better feedback to the user.

```typescript
  const titleClick = async () => {
    try {
      const response = await fetch("/api/title?url=" + textUrl);
      if (!response.ok) {
        throw new Error(`Can't find title: [${response.status}] `);
      } else {
        const text = await response.text();
        if (!text) {
          throw new Error(`Can't find title: `);
        } else {
          setTextTitle(text);
          setMessage({ text: "" });
        }
      }
    } catch (error: any) {
      setTextTitle(""); // or a default title
      setMessage({ text: error.message + textUrl });
    }
  };
```
created_at: 2025-03-06T14:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,68 +14,59 @@ export const BmDetail: React.FC<BmDetailProps> = ({
 }) => {
   const [textUrl, setTextUrl] = useState("");
   const [textTitle, setTextTitle] = useState("");
-  const [textUrlDisabled, setTextUrlDisabled] = useState(true);
-  const [textTitleDisabled, setTextTitleDisabled] = useState(true);
-  const textUrlRef1 = useRef<HTMLInputElement>(null);
-  const textTitleRef2 = useRef<HTMLInputElement>(null);
   const { setMessage } = useMessage();
 
   const updateClick = () => {
-    if (textUrlRef1.current && textTitleRef2.current) {
-      const inputTextUrl = textUrlRef1.current.value;
-      const inputTextTitle = textTitleRef2.current.value;
-      onAddBookmark(inputTextUrl, inputTextTitle);
-    }
+    onAddBookmark(textUrl, textTitle);
   };
 
   const titleClick = async () => {
-    const response = await fetch("/api/title?url=" + textUrl);
-    const text = await response.text();
-
-    if (!response.ok) {
-      setTextTitle("");
-      setMessage({ text: text });
-    } else {
-      setTextTitle(text);
-    }
+    fetch("/api/title?url=" + textUrl)
+      .then((response) => {
+        if (!response.ok) {
+          throw new Error(`Can't find title: [${response.status}] `);
+        } else {
+          return response.text();
+        }
+      })
+      .then((text) => {
+        if (!text) {
+          throw new Error(`Can't find title: `);
+        } else {
+          setTextTitle(text);
+          setMessage({ text: "" });
+        }
+      })
+      .catch((error) => {
+        setTextTitle("");
+        setMessage({ text: error.message + textUrl });
+      });
path: src/app/components/bmDetail.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

This is a good approach to handling errors when fetching the title. Consider adding a more specific error message to the `catch` block to provide more context to the user.

```typescript
.catch((error) => {
        setTextTitle("");
        setMessage({ text: `Failed to fetch title: ${error.message} for URL: ${textUrl}` });
      });
```
created_at: 2025-03-06T14:33:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,68 +14,59 @@ export const BmDetail: React.FC<BmDetailProps> = ({
 }) => {
   const [textUrl, setTextUrl] = useState("");
   const [textTitle, setTextTitle] = useState("");
-  const [textUrlDisabled, setTextUrlDisabled] = useState(true);
-  const [textTitleDisabled, setTextTitleDisabled] = useState(true);
-  const textUrlRef1 = useRef<HTMLInputElement>(null);
path: src/app/components/bmDetail.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

The `textUrlDisabled` and `textTitleDisabled` state variables and the `textUrlRef1` and `textTitleRef2` refs are no longer needed. Consider removing them to simplify the component's logic and improve its performance.
created_at: 2025-03-06T14:33:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: gemini-code-assist[bot]
created_at: 2025-03-06T14:26:48Z
body: > [!WARNING]
> Gemini encountered an error creating the review. You can try again by commenting `/gemini review`.
author_association: CONTRIBUTOR
user: login: kubotama
created_at: 2025-03-06T14:31:58Z
body: /gemini review
author_association: OWNER