mirror of https://github.com/buster-so/buster.git
start scroll to bottom
Update ChatContent.tsx auto scroll hook scroll in use auto scroll is working
This commit is contained in:
parent
a5d4ed77ca
commit
5b95394ece
|
@ -78,6 +78,7 @@
|
|||
"react-dom": "^18",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-scroll-to-bottom": "^4.2.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
|
@ -112,6 +113,7 @@
|
|||
"@types/pluralize": "^0.0.33",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-scroll-to-bottom": "^4.2.5",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.2.4",
|
||||
|
@ -2048,6 +2050,19 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime-corejs3": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.0.tgz",
|
||||
"integrity": "sha512-UWjX6t+v+0ckwZ50Y5ShZLnlk95pP5MyW/pon9tiYzl3+18pkTHTFNTKr7rQbfRXPkowt2QAn30o1b6oswszew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-js-pure": "^3.30.2",
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||
|
@ -2251,6 +2266,123 @@
|
|||
"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/css": {
|
||||
"version": "11.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.1.3.tgz",
|
||||
"integrity": "sha512-RSQP59qtCNTf5NWD6xM08xsQdCZmVYnX/panPYvB6LQAPKQB6GL49Njf0EMbS3CyDtrlWsBcmqBtysFvfWT3rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/babel-plugin": "^11.0.0",
|
||||
"@emotion/cache": "^11.1.3",
|
||||
"@emotion/serialize": "^1.0.0",
|
||||
"@emotion/sheet": "^1.0.0",
|
||||
"@emotion/utils": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"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/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/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/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/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/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
|
||||
|
@ -7472,7 +7604,6 @@
|
|||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/phoenix": {
|
||||
|
@ -7539,6 +7670,16 @@
|
|||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-scroll-to-bottom": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-scroll-to-bottom/-/react-scroll-to-bottom-4.2.5.tgz",
|
||||
"integrity": "sha512-gYMMxxhphzTNfKc4NIkEgu4XRiQjfj/6R7QK10Igz8jOUPNXBLSnK3RS7ofsNWnJDEgpLkNOwSLuASASiGsfHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-syntax-highlighter": {
|
||||
"version": "15.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
|
||||
|
@ -8970,6 +9111,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-plugin-polyfill-corejs2": {
|
||||
"version": "0.4.12",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz",
|
||||
|
@ -9819,6 +9975,12 @@
|
|||
"url": "https://polar.sh/cva"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/clean-css": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
|
||||
|
@ -10115,7 +10277,6 @@
|
|||
"version": "3.41.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.41.0.tgz",
|
||||
"integrity": "sha512-71Gzp96T9YPk63aUvE5Q5qP+DryB4ZloUZPSOebGM88VNw8VNfvdA7z6kGA8iGOTEzAomsRidp4jXSmUIJsL+Q==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -10134,7 +10295,6 @@
|
|||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/parse-json": "^4.0.0",
|
||||
|
@ -11291,7 +11451,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"
|
||||
|
@ -12167,6 +12326,12 @@
|
|||
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
@ -13310,7 +13475,6 @@
|
|||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parent-module": "^1.0.0",
|
||||
|
@ -15745,6 +15909,12 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/math-random": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/math-random/-/math-random-2.0.1.tgz",
|
||||
"integrity": "sha512-oIEbWiVDxDpl5tIF4S6zYS9JExhh3bun3uLb3YAinHPTlRtW4g1S66LtJrJ4Npq8dgIa8CLK5iPVah5n4n0s2w==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
@ -17557,7 +17727,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"
|
||||
|
@ -17701,7 +17870,6 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
|
@ -18706,6 +18874,53 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-scroll-to-bottom": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-scroll-to-bottom/-/react-scroll-to-bottom-4.2.0.tgz",
|
||||
"integrity": "sha512-1WweuumQc5JLzeAR81ykRdK/cEv9NlCPEm4vSwOGN1qS2qlpGVTyMgdI8Y7ZmaqRmzYBGV5/xPuJQtekYzQFGg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime-corejs3": "^7.15.4",
|
||||
"@emotion/css": "11.1.3",
|
||||
"classnames": "2.3.1",
|
||||
"core-js": "3.18.3",
|
||||
"math-random": "2.0.1",
|
||||
"prop-types": "15.7.2",
|
||||
"simple-update-in": "2.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/react-scroll-to-bottom/node_modules/core-js": {
|
||||
"version": "3.18.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.3.tgz",
|
||||
"integrity": "sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw==",
|
||||
"deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/react-scroll-to-bottom/node_modules/prop-types": {
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-scroll-to-bottom/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-style-singleton": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||
|
@ -19282,7 +19497,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"
|
||||
|
@ -19928,6 +20142,12 @@
|
|||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/simple-update-in": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-in/-/simple-update-in-2.2.0.tgz",
|
||||
"integrity": "sha512-FrW41lLiOs82jKxwq39UrE1HDAHOvirKWk4Nv8tqnFFFknVbTxcHZzDS4vt02qqdU/5+KNsQHWzhKHznDBmrww==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sirv": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
|
||||
|
@ -20515,6 +20735,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/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -22247,7 +22473,6 @@
|
|||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
"react-dom": "^18",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-scroll-to-bottom": "^4.2.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
|
@ -121,6 +122,7 @@
|
|||
"@types/pluralize": "^0.0.33",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-scroll-to-bottom": "^4.2.5",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.2.4",
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { useRef, useState, useCallback, useEffect } from 'react';
|
||||
import { useAutoScroll } from './useAutoScroll';
|
||||
|
||||
interface Message {
|
||||
id: number;
|
||||
text: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
const AutoScrollDemo = () => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [isAutoAddEnabled, setIsAutoAddEnabled] = useState(false);
|
||||
const intervalRef = useRef<NodeJS.Timeout>();
|
||||
const { isAutoScrollEnabled, scrollToBottom, scrollToTop, enableAutoScroll, disableAutoScroll } =
|
||||
useAutoScroll(containerRef);
|
||||
|
||||
const addMessage = () => {
|
||||
const newMessage: Message = {
|
||||
id: messages.length + 1,
|
||||
text: `Message ${messages.length + 1}: ${Lorem.generateSentences(1)}`,
|
||||
timestamp: new Date().toLocaleTimeString()
|
||||
};
|
||||
setMessages((prev) => [...prev, newMessage]);
|
||||
};
|
||||
|
||||
const addManyMessages = () => {
|
||||
const newMessages: Message[] = Array.from({ length: 10 }, (_, i) => ({
|
||||
id: messages.length + i + 1,
|
||||
text: `Message ${messages.length + i + 1}: ${Lorem.generateSentences(1)}`,
|
||||
timestamp: new Date().toLocaleTimeString()
|
||||
}));
|
||||
setMessages((prev) => [...prev, ...newMessages]);
|
||||
};
|
||||
|
||||
const toggleAutoAdd = useCallback(() => {
|
||||
if (isAutoAddEnabled) {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = undefined;
|
||||
}
|
||||
setIsAutoAddEnabled(false);
|
||||
} else {
|
||||
intervalRef.current = setInterval(addMessage, 1000);
|
||||
setIsAutoAddEnabled(true);
|
||||
}
|
||||
}, [isAutoAddEnabled]);
|
||||
|
||||
// Cleanup interval on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex w-[600px] flex-col gap-4">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={addMessage}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Add Message
|
||||
</button>
|
||||
<button
|
||||
onClick={addManyMessages}
|
||||
className="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600">
|
||||
Add 10 Messages
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleAutoAdd}
|
||||
className={`rounded px-4 py-2 text-white ${
|
||||
isAutoAddEnabled
|
||||
? 'bg-purple-500 hover:bg-purple-600'
|
||||
: 'bg-purple-400 hover:bg-purple-500'
|
||||
}`}>
|
||||
Auto Add {isAutoAddEnabled ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">Auto-scroll:</span>
|
||||
<button
|
||||
onClick={isAutoScrollEnabled ? disableAutoScroll : enableAutoScroll}
|
||||
className={`rounded px-3 py-1 text-white ${
|
||||
isAutoScrollEnabled ? 'bg-green-500' : 'bg-red-500'
|
||||
}`}>
|
||||
{isAutoScrollEnabled ? 'Enabled' : 'Disabled'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => scrollToTop('smooth')}
|
||||
className="rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600">
|
||||
Scroll to Top (smooth)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToTop('instant')}
|
||||
className="rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600">
|
||||
Scroll to Top (instant)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToBottom('smooth')}
|
||||
className="rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600">
|
||||
Scroll to Bottom (smooth)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToBottom('instant')}
|
||||
className="rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600">
|
||||
Scroll to Bottom (instant)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="h-[400px] space-y-2 overflow-y-auto rounded-lg border border-gray-200 p-4">
|
||||
{messages.map((message) => (
|
||||
<div key={message.id} className="rounded-lg bg-gray-100 p-3">
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<span className="text-sm font-semibold">Message #{message.id}</span>
|
||||
<span className="text-xs text-gray-500">{message.timestamp}</span>
|
||||
</div>
|
||||
<p className="text-sm">{message.text}</p>
|
||||
</div>
|
||||
))}
|
||||
{messages.length === 0 && (
|
||||
<div className="text-center text-gray-500">
|
||||
No messages. Click "Add Message" to start.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Lorem ipsum generator for demo purposes
|
||||
const Lorem = {
|
||||
words: [
|
||||
'lorem',
|
||||
'ipsum',
|
||||
'dolor',
|
||||
'sit',
|
||||
'amet',
|
||||
'consectetur',
|
||||
'adipiscing',
|
||||
'elit',
|
||||
'sed',
|
||||
'do',
|
||||
'eiusmod',
|
||||
'tempor',
|
||||
'incididunt',
|
||||
'ut',
|
||||
'labore',
|
||||
'et',
|
||||
'dolore',
|
||||
'magna',
|
||||
'aliqua'
|
||||
],
|
||||
generateSentences: (count: number) => {
|
||||
const sentences = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const wordCount = Math.floor(Math.random() * 10) + 5;
|
||||
const words = Array.from(
|
||||
{ length: wordCount },
|
||||
() => Lorem.words[Math.floor(Math.random() * Lorem.words.length)]
|
||||
);
|
||||
sentences.push(words.join(' ') + '.');
|
||||
}
|
||||
return sentences.join(' ');
|
||||
}
|
||||
};
|
||||
|
||||
const meta: Meta<typeof AutoScrollDemo> = {
|
||||
title: 'Hooks/useAutoScroll',
|
||||
component: AutoScrollDemo,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AutoScrollDemo>;
|
||||
|
||||
export const Default: Story = {};
|
|
@ -0,0 +1,232 @@
|
|||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
interface UseAutoScrollOptions {
|
||||
/** Debounce delay in milliseconds for scroll events */
|
||||
debounceDelay?: number;
|
||||
/** Smooth scroll behavior duration in milliseconds */
|
||||
scrollBehavior?: ScrollBehavior;
|
||||
}
|
||||
|
||||
interface UseAutoScrollReturn {
|
||||
/** Whether auto-scrolling is currently enabled */
|
||||
isAutoScrollEnabled: boolean;
|
||||
/** Manually scroll to the bottom of the container */
|
||||
scrollToBottom: (behavior?: ScrollBehavior) => void;
|
||||
/** Manually scroll to the top of the container */
|
||||
scrollToTop: (behavior?: ScrollBehavior) => void;
|
||||
/** Manually scroll to a specific node */
|
||||
scrollToNode: (node: HTMLElement, behavior?: ScrollBehavior) => void;
|
||||
|
||||
/** Enable auto-scrolling */
|
||||
enableAutoScroll: () => void;
|
||||
/** Disable auto-scrolling */
|
||||
disableAutoScroll: () => void;
|
||||
}
|
||||
|
||||
const isAtBottom = (element: HTMLElement, threshold = 30) => {
|
||||
const { scrollHeight, scrollTop, clientHeight } = element;
|
||||
return scrollHeight - (scrollTop + clientHeight) <= threshold;
|
||||
};
|
||||
|
||||
export const useAutoScroll = (
|
||||
containerRef: React.RefObject<HTMLElement>,
|
||||
options: UseAutoScrollOptions = {}
|
||||
): UseAutoScrollReturn => {
|
||||
const { debounceDelay = 150, scrollBehavior = 'smooth' } = options;
|
||||
|
||||
const [isAutoScrollEnabled, setIsAutoScrollEnabled] = useState(true);
|
||||
const wasAtBottom = useRef(true);
|
||||
const isScrollingRef = useRef(false);
|
||||
const mutationDebounceRef = useRef<number>();
|
||||
const forceScrollRef = useRef(false);
|
||||
|
||||
const scrollToBottom = useCallback(
|
||||
(behavior: ScrollBehavior = scrollBehavior) => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
// Set a flag to ignore scroll events while we're forcing a scroll
|
||||
forceScrollRef.current = true;
|
||||
isScrollingRef.current = false;
|
||||
|
||||
// Use RAF to ensure we scroll after any pending updates
|
||||
requestAnimationFrame(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
containerRef.current.scrollTo({
|
||||
top: containerRef.current.scrollHeight,
|
||||
behavior
|
||||
});
|
||||
|
||||
// Always enable auto-scroll when manually scrolling to bottom
|
||||
setIsAutoScrollEnabled(true);
|
||||
wasAtBottom.current = true;
|
||||
|
||||
// Reset the force scroll flag after the scroll completes
|
||||
if (behavior === 'instant') {
|
||||
forceScrollRef.current = false;
|
||||
} else {
|
||||
// For smooth scrolling, wait for the animation to complete
|
||||
setTimeout(() => {
|
||||
forceScrollRef.current = false;
|
||||
}, 300); // Typical smooth scroll duration
|
||||
}
|
||||
});
|
||||
},
|
||||
[containerRef, scrollBehavior]
|
||||
);
|
||||
|
||||
const scrollToTop = useCallback(
|
||||
(behavior: ScrollBehavior = scrollBehavior) => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
containerRef.current.scrollTo({
|
||||
top: 0,
|
||||
behavior
|
||||
});
|
||||
},
|
||||
[containerRef, scrollBehavior]
|
||||
);
|
||||
|
||||
const scrollToNode = useCallback(
|
||||
(node: HTMLElement, behavior: ScrollBehavior = scrollBehavior) => {
|
||||
if (!containerRef.current || !node) return;
|
||||
|
||||
// Set a flag to ignore scroll events while we're forcing a scroll
|
||||
forceScrollRef.current = true;
|
||||
isScrollingRef.current = false;
|
||||
|
||||
// Use RAF to ensure we scroll after any pending updates
|
||||
requestAnimationFrame(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
// Get the node's position relative to the container
|
||||
const containerRect = containerRef.current.getBoundingClientRect();
|
||||
const nodeRect = node.getBoundingClientRect();
|
||||
const scrollTop = nodeRect.top - containerRect.top + containerRef.current.scrollTop;
|
||||
|
||||
containerRef.current.scrollTo({
|
||||
top: scrollTop,
|
||||
behavior
|
||||
});
|
||||
|
||||
// Check if we're scrolling to the bottom
|
||||
const isBottom =
|
||||
Math.abs(
|
||||
containerRef.current.scrollHeight - (scrollTop + containerRef.current.clientHeight)
|
||||
) <= 30;
|
||||
|
||||
if (isBottom) {
|
||||
setIsAutoScrollEnabled(true);
|
||||
wasAtBottom.current = true;
|
||||
}
|
||||
|
||||
// Reset the force scroll flag after the scroll completes
|
||||
if (behavior === 'instant') {
|
||||
forceScrollRef.current = false;
|
||||
} else {
|
||||
// For smooth scrolling, wait for the animation to complete
|
||||
setTimeout(() => {
|
||||
forceScrollRef.current = false;
|
||||
}, 300); // Typical smooth scroll duration
|
||||
}
|
||||
});
|
||||
},
|
||||
[containerRef, scrollBehavior]
|
||||
);
|
||||
|
||||
// Debounced scroll handler
|
||||
const handleScrollThrottled = useCallback(
|
||||
debounce(() => {
|
||||
if (!containerRef.current || forceScrollRef.current) return;
|
||||
|
||||
const atBottom = isAtBottom(containerRef.current);
|
||||
|
||||
if (wasAtBottom.current && !atBottom) {
|
||||
// Only disable if we were at bottom and scrolled up
|
||||
setIsAutoScrollEnabled(false);
|
||||
} else if (atBottom) {
|
||||
// Enable when we reach bottom
|
||||
setIsAutoScrollEnabled(true);
|
||||
}
|
||||
|
||||
wasAtBottom.current = atBottom;
|
||||
isScrollingRef.current = false;
|
||||
}, debounceDelay),
|
||||
[containerRef]
|
||||
);
|
||||
|
||||
// Immediate scroll handler that calls the debounced version
|
||||
const handleScroll = useCallback(() => {
|
||||
if (forceScrollRef.current) return;
|
||||
|
||||
if (!isScrollingRef.current) {
|
||||
isScrollingRef.current = true;
|
||||
}
|
||||
handleScrollThrottled();
|
||||
}, [handleScrollThrottled]);
|
||||
|
||||
// Handle content changes
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
// Debounced mutation handler to prevent rapid scroll updates
|
||||
const handleMutation = () => {
|
||||
if (mutationDebounceRef.current) {
|
||||
window.cancelAnimationFrame(mutationDebounceRef.current);
|
||||
}
|
||||
|
||||
mutationDebounceRef.current = window.requestAnimationFrame(() => {
|
||||
if (isAutoScrollEnabled && !isScrollingRef.current && !forceScrollRef.current) {
|
||||
scrollToBottom();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(handleMutation);
|
||||
|
||||
observer.observe(container, {
|
||||
childList: true, // Only observe direct children changes
|
||||
subtree: false, // Don't observe deep changes
|
||||
characterData: false // Don't observe text changes
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (mutationDebounceRef.current) {
|
||||
window.cancelAnimationFrame(mutationDebounceRef.current);
|
||||
}
|
||||
};
|
||||
}, [containerRef, isAutoScrollEnabled, scrollToBottom]);
|
||||
|
||||
// Handle scroll events
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
container.addEventListener('scroll', handleScroll);
|
||||
return () => {
|
||||
container.removeEventListener('scroll', handleScroll);
|
||||
handleScrollThrottled.cancel();
|
||||
};
|
||||
}, [containerRef, handleScroll, handleScrollThrottled]);
|
||||
|
||||
const enableAutoScroll = useCallback(() => {
|
||||
setIsAutoScrollEnabled(true);
|
||||
scrollToBottom();
|
||||
}, [scrollToBottom]);
|
||||
|
||||
const disableAutoScroll = useCallback(() => {
|
||||
setIsAutoScrollEnabled(false);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isAutoScrollEnabled,
|
||||
scrollToBottom,
|
||||
scrollToTop,
|
||||
scrollToNode,
|
||||
enableAutoScroll,
|
||||
disableAutoScroll
|
||||
};
|
||||
};
|
|
@ -9,9 +9,7 @@ export const ChatContainer = React.memo(() => {
|
|||
header={<ChatHeader />}
|
||||
headerBorderVariant="ghost"
|
||||
scrollable
|
||||
className="flex h-full w-full min-w-[295px] flex-col"
|
||||
// mainClassName="max-w-[calc(100%_-_12px)]"
|
||||
>
|
||||
className="flex h-full w-full min-w-[295px] flex-col">
|
||||
<ChatContent />
|
||||
</AppPageLayout>
|
||||
);
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useChatIndividualContextSelector } from '../../ChatContext';
|
||||
import { ChatMessageBlock } from './ChatMessageBlock';
|
||||
import { ChatInput } from './ChatInput';
|
||||
import ScrollToBottom from 'react-scroll-to-bottom';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { cn } from '@/lib/classMerge';
|
||||
|
||||
const autoClass = 'mx-auto max-w-[600px] w-full';
|
||||
|
||||
|
@ -11,6 +14,14 @@ export const ChatContent: React.FC<{}> = React.memo(() => {
|
|||
const chatId = useChatIndividualContextSelector((state) => state.chatId);
|
||||
const chatMessageIds = useChatIndividualContextSelector((state) => state.chatMessageIds);
|
||||
|
||||
// const [autoMessages, setAutoMessages] = useState<string[]>([]);
|
||||
|
||||
// useEffect(() => {
|
||||
// setInterval(() => {
|
||||
// setAutoMessages((prev) => [...prev, faker.lorem.sentence()]);
|
||||
// }, 1500);
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-40 flex h-full w-full flex-col overflow-hidden">
|
||||
|
@ -19,6 +30,12 @@ export const ChatContent: React.FC<{}> = React.memo(() => {
|
|||
<ChatMessageBlock key={messageId} messageId={messageId} chatId={chatId || ''} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* {autoMessages.map((message, index) => (
|
||||
<div key={index} className={cn(autoClass, 'bg-red-300')}>
|
||||
<div className="text-red-700">{message}</div>
|
||||
</div>
|
||||
))} */}
|
||||
</div>
|
||||
|
||||
<ChatInputWrapper />
|
||||
|
|
Loading…
Reference in New Issue