├── .babelrc ├── .editorconfig ├── .eslintrc ├── .github └── workflows │ └── npmpublish.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierrc ├── LICENSE ├── README.md ├── close.svg ├── example ├── .gitignore ├── index.html ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public │ └── favicon.svg ├── src │ ├── App.css │ ├── App.tsx │ ├── Example.module.css │ ├── Example.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── src ├── .eslintrc ├── globals.d.ts ├── index.module.css ├── index.tsx └── useCustomScroller.ts ├── tsconfig.json └── tsup.config.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "@babel/react" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["prettier", "prettier/react"], 4 | "env": { 5 | "es6": true 6 | }, 7 | "plugins": ["prettier", "react", "react-hooks"], 8 | "parserOptions": { 9 | "sourceType": "module" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to npm 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | - run: yarn 16 | - run: yarn test 17 | 18 | publish-npm: 19 | needs: build 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v1 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: 12 26 | registry-url: https://registry.npmjs.org/ 27 | - run: yarn 28 | - run: npm publish 29 | env: 30 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | .rpt2_cache 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | yarn.lock 3 | rollup* 4 | .eslint* 5 | .babelrc 6 | .prettierrc 7 | .gitignore 8 | src 9 | example 10 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Elastic Inc. (Close.io) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-custom-scroller 2 | 3 | [![NPM](https://img.shields.io/npm/v/react-custom-scroller.svg)](https://www.npmjs.com/package/react-custom-scroller) [![JavaScript Style Guide](https://img.shields.io/badge/code%20style-prettier-success)](https://prettier.io) 4 | 5 | Super simple React component for creating a custom scrollbar cross-browser and cross-devices. 6 | 7 | [**Check the live DEMO**](https://closeio.github.io/react-custom-scroller/). 8 | 9 | ### 10 | 11 | Interested in working on projects like this? [Close](https://close.com) is looking for [great engineers](https://jobs.close.com) to join our team! 12 | 13 | ## Install 14 | 15 | ```bash 16 | yarn add react-custom-scroller 17 | ``` 18 | 19 | ## Benefits 20 | 21 | - Extremely lightweight (less than 2KB minzipped). 22 | - It uses the native scroll events, so all the events work and are smooth (mouse wheel, space, page down, page up, arrows etc). 23 | - No other 3rd-party dependencies. 24 | - The performance is excellent! 25 | 26 | ## Usage 27 | 28 | ```jsx 29 | import React from 'react'; 30 | import CustomScroller from 'react-custom-scroller'; 31 | import 'react-custom-scroller/dist/index.css'; 32 | 33 | const MyScrollableDiv = () => ( 34 | Content goes here. 35 | ); 36 | ``` 37 | 38 | ## License 39 | 40 | MIT © [Close](https://github.com/closeio) 41 | -------------------------------------------------------------------------------- /close.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 19 | 21 | 23 | 25 | 26 | 29 | 32 | 35 | 37 | 40 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Custom Scroller 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-custom-scroller-example", 3 | "homepage": "https://closeio.github.io/react-custom-scroller", 4 | "version": "0.0.0", 5 | "license": "MIT", 6 | "private": true, 7 | "type": "module", 8 | "scripts": { 9 | "dev": "vite", 10 | "build": "tsc -b && vite build", 11 | "lint": "eslint .", 12 | "preview": "vite preview", 13 | "prepare": "pnpm build" 14 | }, 15 | "dependencies": { 16 | "react": "^18.3.1", 17 | "react-dom": "^18.3.1", 18 | "react-custom-scroller": "2.0.0" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.3.12", 22 | "@types/react-dom": "^18.3.1", 23 | "@vitejs/plugin-react-swc": "^3.5.0", 24 | "globals": "^15.11.0", 25 | "typescript": "^5.7.2", 26 | "vite": "^5.4.10" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | react: 12 | specifier: ^18.3.1 13 | version: 18.3.1 14 | react-custom-scroller: 15 | specifier: 2.0.0 16 | version: 2.0.0(react-dom@18.3.1)(react@18.3.1) 17 | react-dom: 18 | specifier: ^18.3.1 19 | version: 18.3.1(react@18.3.1) 20 | devDependencies: 21 | '@types/react': 22 | specifier: ^18.3.12 23 | version: 18.3.12 24 | '@types/react-dom': 25 | specifier: ^18.3.1 26 | version: 18.3.1 27 | '@vitejs/plugin-react-swc': 28 | specifier: ^3.5.0 29 | version: 3.7.1(vite@5.4.11) 30 | globals: 31 | specifier: ^15.11.0 32 | version: 15.12.0 33 | typescript: 34 | specifier: ^5.7.2 35 | version: 5.7.2 36 | vite: 37 | specifier: ^5.4.10 38 | version: 5.4.11 39 | 40 | packages: 41 | 42 | '@esbuild/aix-ppc64@0.21.5': 43 | resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} 44 | engines: {node: '>=12'} 45 | cpu: [ppc64] 46 | os: [aix] 47 | 48 | '@esbuild/android-arm64@0.21.5': 49 | resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} 50 | engines: {node: '>=12'} 51 | cpu: [arm64] 52 | os: [android] 53 | 54 | '@esbuild/android-arm@0.21.5': 55 | resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} 56 | engines: {node: '>=12'} 57 | cpu: [arm] 58 | os: [android] 59 | 60 | '@esbuild/android-x64@0.21.5': 61 | resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} 62 | engines: {node: '>=12'} 63 | cpu: [x64] 64 | os: [android] 65 | 66 | '@esbuild/darwin-arm64@0.21.5': 67 | resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} 68 | engines: {node: '>=12'} 69 | cpu: [arm64] 70 | os: [darwin] 71 | 72 | '@esbuild/darwin-x64@0.21.5': 73 | resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} 74 | engines: {node: '>=12'} 75 | cpu: [x64] 76 | os: [darwin] 77 | 78 | '@esbuild/freebsd-arm64@0.21.5': 79 | resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} 80 | engines: {node: '>=12'} 81 | cpu: [arm64] 82 | os: [freebsd] 83 | 84 | '@esbuild/freebsd-x64@0.21.5': 85 | resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} 86 | engines: {node: '>=12'} 87 | cpu: [x64] 88 | os: [freebsd] 89 | 90 | '@esbuild/linux-arm64@0.21.5': 91 | resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} 92 | engines: {node: '>=12'} 93 | cpu: [arm64] 94 | os: [linux] 95 | 96 | '@esbuild/linux-arm@0.21.5': 97 | resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} 98 | engines: {node: '>=12'} 99 | cpu: [arm] 100 | os: [linux] 101 | 102 | '@esbuild/linux-ia32@0.21.5': 103 | resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} 104 | engines: {node: '>=12'} 105 | cpu: [ia32] 106 | os: [linux] 107 | 108 | '@esbuild/linux-loong64@0.21.5': 109 | resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} 110 | engines: {node: '>=12'} 111 | cpu: [loong64] 112 | os: [linux] 113 | 114 | '@esbuild/linux-mips64el@0.21.5': 115 | resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} 116 | engines: {node: '>=12'} 117 | cpu: [mips64el] 118 | os: [linux] 119 | 120 | '@esbuild/linux-ppc64@0.21.5': 121 | resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} 122 | engines: {node: '>=12'} 123 | cpu: [ppc64] 124 | os: [linux] 125 | 126 | '@esbuild/linux-riscv64@0.21.5': 127 | resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} 128 | engines: {node: '>=12'} 129 | cpu: [riscv64] 130 | os: [linux] 131 | 132 | '@esbuild/linux-s390x@0.21.5': 133 | resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} 134 | engines: {node: '>=12'} 135 | cpu: [s390x] 136 | os: [linux] 137 | 138 | '@esbuild/linux-x64@0.21.5': 139 | resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} 140 | engines: {node: '>=12'} 141 | cpu: [x64] 142 | os: [linux] 143 | 144 | '@esbuild/netbsd-x64@0.21.5': 145 | resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} 146 | engines: {node: '>=12'} 147 | cpu: [x64] 148 | os: [netbsd] 149 | 150 | '@esbuild/openbsd-x64@0.21.5': 151 | resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} 152 | engines: {node: '>=12'} 153 | cpu: [x64] 154 | os: [openbsd] 155 | 156 | '@esbuild/sunos-x64@0.21.5': 157 | resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} 158 | engines: {node: '>=12'} 159 | cpu: [x64] 160 | os: [sunos] 161 | 162 | '@esbuild/win32-arm64@0.21.5': 163 | resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} 164 | engines: {node: '>=12'} 165 | cpu: [arm64] 166 | os: [win32] 167 | 168 | '@esbuild/win32-ia32@0.21.5': 169 | resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} 170 | engines: {node: '>=12'} 171 | cpu: [ia32] 172 | os: [win32] 173 | 174 | '@esbuild/win32-x64@0.21.5': 175 | resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} 176 | engines: {node: '>=12'} 177 | cpu: [x64] 178 | os: [win32] 179 | 180 | '@rollup/rollup-android-arm-eabi@4.27.4': 181 | resolution: {integrity: sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==} 182 | cpu: [arm] 183 | os: [android] 184 | 185 | '@rollup/rollup-android-arm64@4.27.4': 186 | resolution: {integrity: sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==} 187 | cpu: [arm64] 188 | os: [android] 189 | 190 | '@rollup/rollup-darwin-arm64@4.27.4': 191 | resolution: {integrity: sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==} 192 | cpu: [arm64] 193 | os: [darwin] 194 | 195 | '@rollup/rollup-darwin-x64@4.27.4': 196 | resolution: {integrity: sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==} 197 | cpu: [x64] 198 | os: [darwin] 199 | 200 | '@rollup/rollup-freebsd-arm64@4.27.4': 201 | resolution: {integrity: sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==} 202 | cpu: [arm64] 203 | os: [freebsd] 204 | 205 | '@rollup/rollup-freebsd-x64@4.27.4': 206 | resolution: {integrity: sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==} 207 | cpu: [x64] 208 | os: [freebsd] 209 | 210 | '@rollup/rollup-linux-arm-gnueabihf@4.27.4': 211 | resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==} 212 | cpu: [arm] 213 | os: [linux] 214 | 215 | '@rollup/rollup-linux-arm-musleabihf@4.27.4': 216 | resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==} 217 | cpu: [arm] 218 | os: [linux] 219 | 220 | '@rollup/rollup-linux-arm64-gnu@4.27.4': 221 | resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==} 222 | cpu: [arm64] 223 | os: [linux] 224 | 225 | '@rollup/rollup-linux-arm64-musl@4.27.4': 226 | resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==} 227 | cpu: [arm64] 228 | os: [linux] 229 | 230 | '@rollup/rollup-linux-powerpc64le-gnu@4.27.4': 231 | resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==} 232 | cpu: [ppc64] 233 | os: [linux] 234 | 235 | '@rollup/rollup-linux-riscv64-gnu@4.27.4': 236 | resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==} 237 | cpu: [riscv64] 238 | os: [linux] 239 | 240 | '@rollup/rollup-linux-s390x-gnu@4.27.4': 241 | resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==} 242 | cpu: [s390x] 243 | os: [linux] 244 | 245 | '@rollup/rollup-linux-x64-gnu@4.27.4': 246 | resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==} 247 | cpu: [x64] 248 | os: [linux] 249 | 250 | '@rollup/rollup-linux-x64-musl@4.27.4': 251 | resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==} 252 | cpu: [x64] 253 | os: [linux] 254 | 255 | '@rollup/rollup-win32-arm64-msvc@4.27.4': 256 | resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==} 257 | cpu: [arm64] 258 | os: [win32] 259 | 260 | '@rollup/rollup-win32-ia32-msvc@4.27.4': 261 | resolution: {integrity: sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==} 262 | cpu: [ia32] 263 | os: [win32] 264 | 265 | '@rollup/rollup-win32-x64-msvc@4.27.4': 266 | resolution: {integrity: sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==} 267 | cpu: [x64] 268 | os: [win32] 269 | 270 | '@swc/core-darwin-arm64@1.9.3': 271 | resolution: {integrity: sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==} 272 | engines: {node: '>=10'} 273 | cpu: [arm64] 274 | os: [darwin] 275 | 276 | '@swc/core-darwin-x64@1.9.3': 277 | resolution: {integrity: sha512-IaRq05ZLdtgF5h9CzlcgaNHyg4VXuiStnOFpfNEMuI5fm5afP2S0FHq8WdakUz5WppsbddTdplL+vpeApt/WCQ==} 278 | engines: {node: '>=10'} 279 | cpu: [x64] 280 | os: [darwin] 281 | 282 | '@swc/core-linux-arm-gnueabihf@1.9.3': 283 | resolution: {integrity: sha512-Pbwe7xYprj/nEnZrNBvZfjnTxlBIcfApAGdz2EROhjpPj+FBqBa3wOogqbsuGGBdCphf8S+KPprL1z+oDWkmSQ==} 284 | engines: {node: '>=10'} 285 | cpu: [arm] 286 | os: [linux] 287 | 288 | '@swc/core-linux-arm64-gnu@1.9.3': 289 | resolution: {integrity: sha512-AQ5JZiwNGVV/2K2TVulg0mw/3LYfqpjZO6jDPtR2evNbk9Yt57YsVzS+3vHSlUBQDRV9/jqMuZYVU3P13xrk+g==} 290 | engines: {node: '>=10'} 291 | cpu: [arm64] 292 | os: [linux] 293 | 294 | '@swc/core-linux-arm64-musl@1.9.3': 295 | resolution: {integrity: sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==} 296 | engines: {node: '>=10'} 297 | cpu: [arm64] 298 | os: [linux] 299 | 300 | '@swc/core-linux-x64-gnu@1.9.3': 301 | resolution: {integrity: sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==} 302 | engines: {node: '>=10'} 303 | cpu: [x64] 304 | os: [linux] 305 | 306 | '@swc/core-linux-x64-musl@1.9.3': 307 | resolution: {integrity: sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==} 308 | engines: {node: '>=10'} 309 | cpu: [x64] 310 | os: [linux] 311 | 312 | '@swc/core-win32-arm64-msvc@1.9.3': 313 | resolution: {integrity: sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==} 314 | engines: {node: '>=10'} 315 | cpu: [arm64] 316 | os: [win32] 317 | 318 | '@swc/core-win32-ia32-msvc@1.9.3': 319 | resolution: {integrity: sha512-rqpzNfpAooSL4UfQnHhkW8aL+oyjqJniDP0qwZfGnjDoJSbtPysHg2LpcOBEdSnEH+uIZq6J96qf0ZFD8AGfXA==} 320 | engines: {node: '>=10'} 321 | cpu: [ia32] 322 | os: [win32] 323 | 324 | '@swc/core-win32-x64-msvc@1.9.3': 325 | resolution: {integrity: sha512-3YJJLQ5suIEHEKc1GHtqVq475guiyqisKSoUnoaRtxkDaW5g1yvPt9IoSLOe2mRs7+FFhGGU693RsBUSwOXSdQ==} 326 | engines: {node: '>=10'} 327 | cpu: [x64] 328 | os: [win32] 329 | 330 | '@swc/core@1.9.3': 331 | resolution: {integrity: sha512-oRj0AFePUhtatX+BscVhnzaAmWjpfAeySpM1TCbxA1rtBDeH/JDhi5yYzAKneDYtVtBvA7ApfeuzhMC9ye4xSg==} 332 | engines: {node: '>=10'} 333 | peerDependencies: 334 | '@swc/helpers': '*' 335 | peerDependenciesMeta: 336 | '@swc/helpers': 337 | optional: true 338 | 339 | '@swc/counter@0.1.3': 340 | resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} 341 | 342 | '@swc/types@0.1.17': 343 | resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} 344 | 345 | '@types/estree@1.0.6': 346 | resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 347 | 348 | '@types/prop-types@15.7.13': 349 | resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} 350 | 351 | '@types/react-dom@18.3.1': 352 | resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} 353 | 354 | '@types/react@18.3.12': 355 | resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} 356 | 357 | '@vitejs/plugin-react-swc@3.7.1': 358 | resolution: {integrity: sha512-vgWOY0i1EROUK0Ctg1hwhtC3SdcDjZcdit4Ups4aPkDcB1jYhmo+RMYWY87cmXMhvtD5uf8lV89j2w16vkdSVg==} 359 | peerDependencies: 360 | vite: ^4 || ^5 361 | 362 | csstype@3.1.3: 363 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 364 | 365 | esbuild@0.21.5: 366 | resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} 367 | engines: {node: '>=12'} 368 | hasBin: true 369 | 370 | fsevents@2.3.3: 371 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 372 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 373 | os: [darwin] 374 | 375 | globals@15.12.0: 376 | resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==} 377 | engines: {node: '>=18'} 378 | 379 | js-tokens@4.0.0: 380 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 381 | 382 | loose-envify@1.4.0: 383 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 384 | hasBin: true 385 | 386 | nanoid@3.3.7: 387 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 388 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 389 | hasBin: true 390 | 391 | picocolors@1.1.1: 392 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 393 | 394 | postcss@8.4.49: 395 | resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} 396 | engines: {node: ^10 || ^12 || >=14} 397 | 398 | react-custom-scroller@2.0.0: 399 | resolution: {integrity: sha512-uoqPdSout/9JUlYwE44mEm7pLPsEA8CfknuOCb/C3MHrVPIPBK+z66xIZ3KompCrjGOY16IPFLOkrwHqQiqqnQ==} 400 | engines: {node: '>=8', npm: '>=5'} 401 | peerDependencies: 402 | react: '>=16.8.0' 403 | react-dom: '>=16.8.0' 404 | 405 | react-dom@18.3.1: 406 | resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} 407 | peerDependencies: 408 | react: ^18.3.1 409 | 410 | react@18.3.1: 411 | resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} 412 | engines: {node: '>=0.10.0'} 413 | 414 | rollup@4.27.4: 415 | resolution: {integrity: sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==} 416 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 417 | hasBin: true 418 | 419 | scheduler@0.23.2: 420 | resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} 421 | 422 | source-map-js@1.2.1: 423 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 424 | engines: {node: '>=0.10.0'} 425 | 426 | typescript@5.7.2: 427 | resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} 428 | engines: {node: '>=14.17'} 429 | hasBin: true 430 | 431 | vite@5.4.11: 432 | resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} 433 | engines: {node: ^18.0.0 || >=20.0.0} 434 | hasBin: true 435 | peerDependencies: 436 | '@types/node': ^18.0.0 || >=20.0.0 437 | less: '*' 438 | lightningcss: ^1.21.0 439 | sass: '*' 440 | sass-embedded: '*' 441 | stylus: '*' 442 | sugarss: '*' 443 | terser: ^5.4.0 444 | peerDependenciesMeta: 445 | '@types/node': 446 | optional: true 447 | less: 448 | optional: true 449 | lightningcss: 450 | optional: true 451 | sass: 452 | optional: true 453 | sass-embedded: 454 | optional: true 455 | stylus: 456 | optional: true 457 | sugarss: 458 | optional: true 459 | terser: 460 | optional: true 461 | 462 | snapshots: 463 | 464 | '@esbuild/aix-ppc64@0.21.5': 465 | optional: true 466 | 467 | '@esbuild/android-arm64@0.21.5': 468 | optional: true 469 | 470 | '@esbuild/android-arm@0.21.5': 471 | optional: true 472 | 473 | '@esbuild/android-x64@0.21.5': 474 | optional: true 475 | 476 | '@esbuild/darwin-arm64@0.21.5': 477 | optional: true 478 | 479 | '@esbuild/darwin-x64@0.21.5': 480 | optional: true 481 | 482 | '@esbuild/freebsd-arm64@0.21.5': 483 | optional: true 484 | 485 | '@esbuild/freebsd-x64@0.21.5': 486 | optional: true 487 | 488 | '@esbuild/linux-arm64@0.21.5': 489 | optional: true 490 | 491 | '@esbuild/linux-arm@0.21.5': 492 | optional: true 493 | 494 | '@esbuild/linux-ia32@0.21.5': 495 | optional: true 496 | 497 | '@esbuild/linux-loong64@0.21.5': 498 | optional: true 499 | 500 | '@esbuild/linux-mips64el@0.21.5': 501 | optional: true 502 | 503 | '@esbuild/linux-ppc64@0.21.5': 504 | optional: true 505 | 506 | '@esbuild/linux-riscv64@0.21.5': 507 | optional: true 508 | 509 | '@esbuild/linux-s390x@0.21.5': 510 | optional: true 511 | 512 | '@esbuild/linux-x64@0.21.5': 513 | optional: true 514 | 515 | '@esbuild/netbsd-x64@0.21.5': 516 | optional: true 517 | 518 | '@esbuild/openbsd-x64@0.21.5': 519 | optional: true 520 | 521 | '@esbuild/sunos-x64@0.21.5': 522 | optional: true 523 | 524 | '@esbuild/win32-arm64@0.21.5': 525 | optional: true 526 | 527 | '@esbuild/win32-ia32@0.21.5': 528 | optional: true 529 | 530 | '@esbuild/win32-x64@0.21.5': 531 | optional: true 532 | 533 | '@rollup/rollup-android-arm-eabi@4.27.4': 534 | optional: true 535 | 536 | '@rollup/rollup-android-arm64@4.27.4': 537 | optional: true 538 | 539 | '@rollup/rollup-darwin-arm64@4.27.4': 540 | optional: true 541 | 542 | '@rollup/rollup-darwin-x64@4.27.4': 543 | optional: true 544 | 545 | '@rollup/rollup-freebsd-arm64@4.27.4': 546 | optional: true 547 | 548 | '@rollup/rollup-freebsd-x64@4.27.4': 549 | optional: true 550 | 551 | '@rollup/rollup-linux-arm-gnueabihf@4.27.4': 552 | optional: true 553 | 554 | '@rollup/rollup-linux-arm-musleabihf@4.27.4': 555 | optional: true 556 | 557 | '@rollup/rollup-linux-arm64-gnu@4.27.4': 558 | optional: true 559 | 560 | '@rollup/rollup-linux-arm64-musl@4.27.4': 561 | optional: true 562 | 563 | '@rollup/rollup-linux-powerpc64le-gnu@4.27.4': 564 | optional: true 565 | 566 | '@rollup/rollup-linux-riscv64-gnu@4.27.4': 567 | optional: true 568 | 569 | '@rollup/rollup-linux-s390x-gnu@4.27.4': 570 | optional: true 571 | 572 | '@rollup/rollup-linux-x64-gnu@4.27.4': 573 | optional: true 574 | 575 | '@rollup/rollup-linux-x64-musl@4.27.4': 576 | optional: true 577 | 578 | '@rollup/rollup-win32-arm64-msvc@4.27.4': 579 | optional: true 580 | 581 | '@rollup/rollup-win32-ia32-msvc@4.27.4': 582 | optional: true 583 | 584 | '@rollup/rollup-win32-x64-msvc@4.27.4': 585 | optional: true 586 | 587 | '@swc/core-darwin-arm64@1.9.3': 588 | optional: true 589 | 590 | '@swc/core-darwin-x64@1.9.3': 591 | optional: true 592 | 593 | '@swc/core-linux-arm-gnueabihf@1.9.3': 594 | optional: true 595 | 596 | '@swc/core-linux-arm64-gnu@1.9.3': 597 | optional: true 598 | 599 | '@swc/core-linux-arm64-musl@1.9.3': 600 | optional: true 601 | 602 | '@swc/core-linux-x64-gnu@1.9.3': 603 | optional: true 604 | 605 | '@swc/core-linux-x64-musl@1.9.3': 606 | optional: true 607 | 608 | '@swc/core-win32-arm64-msvc@1.9.3': 609 | optional: true 610 | 611 | '@swc/core-win32-ia32-msvc@1.9.3': 612 | optional: true 613 | 614 | '@swc/core-win32-x64-msvc@1.9.3': 615 | optional: true 616 | 617 | '@swc/core@1.9.3': 618 | dependencies: 619 | '@swc/counter': 0.1.3 620 | '@swc/types': 0.1.17 621 | optionalDependencies: 622 | '@swc/core-darwin-arm64': 1.9.3 623 | '@swc/core-darwin-x64': 1.9.3 624 | '@swc/core-linux-arm-gnueabihf': 1.9.3 625 | '@swc/core-linux-arm64-gnu': 1.9.3 626 | '@swc/core-linux-arm64-musl': 1.9.3 627 | '@swc/core-linux-x64-gnu': 1.9.3 628 | '@swc/core-linux-x64-musl': 1.9.3 629 | '@swc/core-win32-arm64-msvc': 1.9.3 630 | '@swc/core-win32-ia32-msvc': 1.9.3 631 | '@swc/core-win32-x64-msvc': 1.9.3 632 | 633 | '@swc/counter@0.1.3': {} 634 | 635 | '@swc/types@0.1.17': 636 | dependencies: 637 | '@swc/counter': 0.1.3 638 | 639 | '@types/estree@1.0.6': {} 640 | 641 | '@types/prop-types@15.7.13': {} 642 | 643 | '@types/react-dom@18.3.1': 644 | dependencies: 645 | '@types/react': 18.3.12 646 | 647 | '@types/react@18.3.12': 648 | dependencies: 649 | '@types/prop-types': 15.7.13 650 | csstype: 3.1.3 651 | 652 | '@vitejs/plugin-react-swc@3.7.1(vite@5.4.11)': 653 | dependencies: 654 | '@swc/core': 1.9.3 655 | vite: 5.4.11 656 | transitivePeerDependencies: 657 | - '@swc/helpers' 658 | 659 | csstype@3.1.3: {} 660 | 661 | esbuild@0.21.5: 662 | optionalDependencies: 663 | '@esbuild/aix-ppc64': 0.21.5 664 | '@esbuild/android-arm': 0.21.5 665 | '@esbuild/android-arm64': 0.21.5 666 | '@esbuild/android-x64': 0.21.5 667 | '@esbuild/darwin-arm64': 0.21.5 668 | '@esbuild/darwin-x64': 0.21.5 669 | '@esbuild/freebsd-arm64': 0.21.5 670 | '@esbuild/freebsd-x64': 0.21.5 671 | '@esbuild/linux-arm': 0.21.5 672 | '@esbuild/linux-arm64': 0.21.5 673 | '@esbuild/linux-ia32': 0.21.5 674 | '@esbuild/linux-loong64': 0.21.5 675 | '@esbuild/linux-mips64el': 0.21.5 676 | '@esbuild/linux-ppc64': 0.21.5 677 | '@esbuild/linux-riscv64': 0.21.5 678 | '@esbuild/linux-s390x': 0.21.5 679 | '@esbuild/linux-x64': 0.21.5 680 | '@esbuild/netbsd-x64': 0.21.5 681 | '@esbuild/openbsd-x64': 0.21.5 682 | '@esbuild/sunos-x64': 0.21.5 683 | '@esbuild/win32-arm64': 0.21.5 684 | '@esbuild/win32-ia32': 0.21.5 685 | '@esbuild/win32-x64': 0.21.5 686 | 687 | fsevents@2.3.3: 688 | optional: true 689 | 690 | globals@15.12.0: {} 691 | 692 | js-tokens@4.0.0: {} 693 | 694 | loose-envify@1.4.0: 695 | dependencies: 696 | js-tokens: 4.0.0 697 | 698 | nanoid@3.3.7: {} 699 | 700 | picocolors@1.1.1: {} 701 | 702 | postcss@8.4.49: 703 | dependencies: 704 | nanoid: 3.3.7 705 | picocolors: 1.1.1 706 | source-map-js: 1.2.1 707 | 708 | react-custom-scroller@2.0.0(react-dom@18.3.1)(react@18.3.1): 709 | dependencies: 710 | react: 18.3.1 711 | react-dom: 18.3.1(react@18.3.1) 712 | 713 | react-dom@18.3.1(react@18.3.1): 714 | dependencies: 715 | loose-envify: 1.4.0 716 | react: 18.3.1 717 | scheduler: 0.23.2 718 | 719 | react@18.3.1: 720 | dependencies: 721 | loose-envify: 1.4.0 722 | 723 | rollup@4.27.4: 724 | dependencies: 725 | '@types/estree': 1.0.6 726 | optionalDependencies: 727 | '@rollup/rollup-android-arm-eabi': 4.27.4 728 | '@rollup/rollup-android-arm64': 4.27.4 729 | '@rollup/rollup-darwin-arm64': 4.27.4 730 | '@rollup/rollup-darwin-x64': 4.27.4 731 | '@rollup/rollup-freebsd-arm64': 4.27.4 732 | '@rollup/rollup-freebsd-x64': 4.27.4 733 | '@rollup/rollup-linux-arm-gnueabihf': 4.27.4 734 | '@rollup/rollup-linux-arm-musleabihf': 4.27.4 735 | '@rollup/rollup-linux-arm64-gnu': 4.27.4 736 | '@rollup/rollup-linux-arm64-musl': 4.27.4 737 | '@rollup/rollup-linux-powerpc64le-gnu': 4.27.4 738 | '@rollup/rollup-linux-riscv64-gnu': 4.27.4 739 | '@rollup/rollup-linux-s390x-gnu': 4.27.4 740 | '@rollup/rollup-linux-x64-gnu': 4.27.4 741 | '@rollup/rollup-linux-x64-musl': 4.27.4 742 | '@rollup/rollup-win32-arm64-msvc': 4.27.4 743 | '@rollup/rollup-win32-ia32-msvc': 4.27.4 744 | '@rollup/rollup-win32-x64-msvc': 4.27.4 745 | fsevents: 2.3.3 746 | 747 | scheduler@0.23.2: 748 | dependencies: 749 | loose-envify: 1.4.0 750 | 751 | source-map-js@1.2.1: {} 752 | 753 | typescript@5.7.2: {} 754 | 755 | vite@5.4.11: 756 | dependencies: 757 | esbuild: 0.21.5 758 | postcss: 8.4.49 759 | rollup: 4.27.4 760 | optionalDependencies: 761 | fsevents: 2.3.3 762 | -------------------------------------------------------------------------------- /example/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /example/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 13 | 15 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import Example from './Example'; 2 | 3 | import logo from './logo.svg'; 4 | 5 | export default function App() { 6 | return ( 7 |
8 |
9 |

10 | Close logo 11 |

12 |
13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /example/src/Example.module.css: -------------------------------------------------------------------------------- 1 | .scroller { 2 | max-width: 450px; 3 | height: 300px; 4 | text-align: justify; 5 | padding: 0 16px 0 0; 6 | } 7 | 8 | .content { 9 | display: grid; 10 | row-gap: 12px; 11 | } 12 | 13 | .content > p { 14 | margin: 0; 15 | } 16 | -------------------------------------------------------------------------------- /example/src/Example.tsx: -------------------------------------------------------------------------------- 1 | import CustomScroller from 'react-custom-scroller'; 2 | import 'react-custom-scroller/index.css'; 3 | 4 | import styles from './Example.module.css'; 5 | 6 | export default function Example() { 7 | return ( 8 | 9 |

10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 11 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 12 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 13 | commodo consequat. 14 |

15 |

16 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum 17 | dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 18 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 19 | Sed ut perspiciatis unde omnis iste natus error sit voluptatem 20 | accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab 21 | illo inventore veritatis et quasi architecto beatae vitae dicta sunt 22 | explicabo. 23 |

24 |

25 | Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut 26 | fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem 27 | sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor 28 | sit amet, consectetur, adipisci velit, sed quia non numquam eius modi 29 | tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. 30 | Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis 31 | suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis 32 | autem vel eum iure reprehenderit qui in ea voluptate velit esse quam 33 | nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo 34 | voluptas nulla pariatur? 35 |

36 |

37 | But I must explain to you how all this mistaken idea of denouncing 38 | pleasure and praising pain was born and I will give you a complete 39 | account of the system, and expound the actual teachings of the great 40 | explorer of the truth, the master-builder of human happiness. No one 41 | rejects, dislikes, or avoids pleasure itself, because it is pleasure, 42 | but because those who do not know how to pursue pleasure rationally 43 | encounter consequences that are extremely painful. Nor again is there 44 | anyone who loves or pursues or desires to obtain pain of itself, because 45 | it is pain, but because occasionally circumstances occur in which toil 46 | and pain can procure him some great pleasure. To take a trivial example, 47 | which of us ever undertakes laborious physical exercise, except to 48 | obtain some advantage from it? But who has any right to find fault with 49 | a man who chooses to enjoy a pleasure that has no annoying consequences, 50 | or one who avoids a pain that produces no resultant pleasure? 51 |

52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0 20px; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | const root = createRoot(document.getElementById('root')!); 7 | root.render(); 8 | -------------------------------------------------------------------------------- /example/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 33 | 36 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "jsx": "preserve", 7 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 8 | "sourceMap": true, 9 | "module": "esnext", 10 | "target": "ES2018", 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "outDir": "./dist", 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "strictNullChecks": true, 17 | "noEmit": true, 18 | "strict": true, 19 | "noImplicitAny": true, 20 | "resolveJsonModule": true 21 | }, 22 | "include": ["**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules", "**/node_modules/*", "dist", "**/dist/*"] 24 | } 25 | -------------------------------------------------------------------------------- /example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react-swc'; 3 | 4 | export default defineConfig({ 5 | base: '/react-custom-scroller/', 6 | plugins: [react()], 7 | }); 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-custom-scroller", 3 | "version": "2.2.0", 4 | "description": "Super simple React component for creating a custom scrollbar cross-browser and cross-devices", 5 | "author": "Vitor Buzinaro ", 6 | "license": "MIT", 7 | "repository": "closeio/react-custom-scroller", 8 | "main": "dist/index.js", 9 | "module": "dist/index.mjs", 10 | "types": "dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "import": { 14 | "types": "./dist/index.d.mts", 15 | "default": "./dist/index.mjs" 16 | }, 17 | "require": { 18 | "types": "./dist/index.d.ts", 19 | "default": "./dist/index.js" 20 | } 21 | }, 22 | "./dist/index.css": { 23 | "default": "./dist/index.css" 24 | } 25 | }, 26 | "engines": { 27 | "node": ">=8", 28 | "npm": ">=5" 29 | }, 30 | "scripts": { 31 | "build": "tsup src/index.tsx", 32 | "start": "tsup src/index.tsx --watch", 33 | "prepare": "pnpm build", 34 | "deploy": "cd example && pnpm i && gh-pages -d dist -u \"github-actions-bot \"" 35 | }, 36 | "peerDependencies": { 37 | "react": ">=16.8.0", 38 | "react-dom": ">=16.8.0" 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "^7.6.4", 42 | "@svgr/rollup": "^4.3.3", 43 | "@types/react": "^18.3.12", 44 | "@types/react-dom": "^18.3.1", 45 | "autoprefixer": "^10.4.20", 46 | "cross-env": "^6.0.3", 47 | "eslint": "^6.5.1", 48 | "eslint-config-prettier": "^6.4.0", 49 | "eslint-plugin-import": "^2.13.0", 50 | "eslint-plugin-react": "^7.16.0", 51 | "eslint-plugin-react-hooks": "^2.1.2", 52 | "gh-pages": "^2.1.1", 53 | "postcss": "^8.4.49", 54 | "tsup": "^8.3.5", 55 | "typescript": "^5.7.2" 56 | }, 57 | "files": [ 58 | "dist" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.module.css' { 2 | const styles: Record; 3 | export default styles; 4 | } 5 | -------------------------------------------------------------------------------- /src/index.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | position: relative; 3 | } 4 | 5 | .main:hover .track { 6 | opacity: 0.75; 7 | } 8 | 9 | .wrapper { 10 | position: relative; 11 | height: 100%; 12 | overflow: hidden; 13 | } 14 | 15 | .inner { 16 | position: relative; 17 | height: 100%; 18 | box-sizing: border-box; 19 | overflow-y: scroll; 20 | } 21 | 22 | .track { 23 | position: absolute; 24 | top: 0; 25 | right: 3px; 26 | cursor: default; 27 | user-select: none; 28 | width: 6px; 29 | min-height: 30px; 30 | max-height: 100%; 31 | background: rgba(0, 0, 0, 0.4); 32 | border-radius: 4px; 33 | opacity: 0; 34 | transition: opacity 0.25s ease-in; 35 | z-index: 1; 36 | 37 | &:hover { 38 | width: 10px; 39 | right: 1px; 40 | } 41 | } 42 | 43 | [data-color-mode='dark'] .track { 44 | background: rgba(255, 255, 255, 0.3); 45 | 46 | &:hover { 47 | background: rgba(255, 255, 255, 0.5); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import type { HTMLAttributes, ReactNode } from 'react'; 2 | import { forwardRef } from 'react'; 3 | 4 | import useCustomScroller from './useCustomScroller'; 5 | 6 | import styles from './index.module.css'; 7 | 8 | const cx = (...args: (string | undefined | boolean)[]) => 9 | args.filter(Boolean).join(' '); 10 | 11 | type CustomScrollerProps = { 12 | scrollDisabled?: boolean; 13 | className?: string; 14 | innerClassName?: string; 15 | children: ReactNode; 16 | } & HTMLAttributes; 17 | 18 | const CustomScroller = forwardRef( 19 | ( 20 | { scrollDisabled = false, className, innerClassName, children, ...props }, 21 | ref, 22 | ) => { 23 | const [wrapperProps, scrollerProps, trackProps] = useCustomScroller( 24 | ref !== null && 'current' in ref ? ref : undefined, 25 | { disabled: scrollDisabled }, 26 | ); 27 | 28 | return ( 29 |
30 |
31 |
32 | {children} 33 |
34 |
35 |
36 |
37 | ); 38 | }, 39 | ); 40 | 41 | CustomScroller.displayName = 'CustomScroller'; 42 | 43 | export default CustomScroller; 44 | -------------------------------------------------------------------------------- /src/useCustomScroller.ts: -------------------------------------------------------------------------------- 1 | import type { MouseEvent as ReactMouseEvent, MutableRefObject } from 'react'; 2 | import { useLayoutEffect, useRef, useState, useCallback } from 'react'; 3 | 4 | /** 5 | * We use a negative right on the content to hide original OS scrollbars 6 | */ 7 | const OS_SCROLLBAR_WIDTH = (() => { 8 | const outer = document.createElement('div'); 9 | const inner = document.createElement('div'); 10 | outer.style.overflow = 'scroll'; 11 | outer.style.width = '100%'; 12 | inner.style.width = '100%'; 13 | 14 | document.body.appendChild(outer); 15 | outer.appendChild(inner); 16 | const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; 17 | outer.removeChild(inner); 18 | document.body.removeChild(outer); 19 | 20 | return scrollbarWidth; 21 | })(); 22 | 23 | /** 24 | * We need this for OSs that automatically hide the scrollbar (so the offset 25 | * doesn't change in such case). Eg: macOS with "Automatically based on mouse". 26 | */ 27 | const SCROLLBAR_WIDTH = OS_SCROLLBAR_WIDTH || 20; 28 | 29 | export default function useCustomScroller( 30 | customRef: MutableRefObject | undefined, 31 | { disabled }: { disabled: boolean } = { disabled: false }, 32 | ) { 33 | const [scrollRatio, setScrollRatio] = useState(1); 34 | const [isDraggingTrack, setIsDraggingTrack] = useState(false); 35 | 36 | const ref = useRef(null); 37 | const scrollerRef = customRef || ref; 38 | const trackRef = useRef(null); 39 | const trackAnimationRef = useRef(0); 40 | const memoizedProps = useRef<{ 41 | clientHeight: number; 42 | scrollHeight: number; 43 | trackHeight: number; 44 | }>(); 45 | 46 | useLayoutEffect(() => { 47 | const el = scrollerRef.current; 48 | if (!el) return; 49 | 50 | let scrollbarAnimation: number; 51 | 52 | const updateScrollbar = () => { 53 | cancelAnimationFrame(scrollbarAnimation); 54 | scrollbarAnimation = requestAnimationFrame(() => { 55 | const { clientHeight, scrollHeight } = el; 56 | setScrollRatio(clientHeight / scrollHeight); 57 | memoizedProps.current = { 58 | clientHeight, 59 | scrollHeight, 60 | trackHeight: trackRef.current?.clientHeight ?? 0, 61 | }; 62 | }); 63 | }; 64 | 65 | // Whenever scroller and its children resizes, update scrollbar 66 | const resizeObserver = new ResizeObserver(updateScrollbar); 67 | const observeScroller = () => { 68 | resizeObserver.observe(el); 69 | for (const node of el.children) { 70 | resizeObserver.observe(node); 71 | } 72 | }; 73 | observeScroller(); 74 | 75 | // Whenever children is added/removed, re-observe resizes, update scrollbar 76 | const mutationObserver = new MutationObserver(() => { 77 | resizeObserver.disconnect(); 78 | observeScroller(); 79 | updateScrollbar(); 80 | }); 81 | mutationObserver.observe(el, { childList: true, subtree: true }); 82 | 83 | window.addEventListener('resize', updateScrollbar); 84 | updateScrollbar(); 85 | 86 | return () => { 87 | cancelAnimationFrame(scrollbarAnimation); 88 | window.removeEventListener('resize', updateScrollbar); 89 | resizeObserver.disconnect(); 90 | mutationObserver.disconnect(); 91 | }; 92 | }, [scrollerRef]); 93 | 94 | useLayoutEffect(() => { 95 | if (!disabled) return; 96 | const el = scrollerRef.current; 97 | if (!el) return; 98 | 99 | const onWheel = (e: WheelEvent) => e.preventDefault(); 100 | el.addEventListener('wheel', onWheel, { passive: false }); 101 | 102 | return () => { 103 | el.removeEventListener('wheel', onWheel); 104 | }; 105 | }, [scrollerRef, disabled]); 106 | 107 | const onScroll = useCallback(() => { 108 | if (scrollRatio === 1) return; 109 | const el = scrollerRef.current; 110 | const track = trackRef.current; 111 | 112 | if (!el || !track) return; 113 | 114 | cancelAnimationFrame(trackAnimationRef.current); 115 | 116 | trackAnimationRef.current = requestAnimationFrame(() => { 117 | if (!memoizedProps.current) return; 118 | const { clientHeight, scrollHeight, trackHeight } = memoizedProps.current; 119 | const ratio = el.scrollTop / (scrollHeight - clientHeight); 120 | const y = ratio * (clientHeight - trackHeight); 121 | track.style.transform = `translateY(${y}px)`; 122 | }); 123 | }, [scrollerRef, scrollRatio]); 124 | 125 | const moveTrack = useCallback( 126 | (e: ReactMouseEvent) => { 127 | const el = scrollerRef.current; 128 | if (!el) return; 129 | 130 | let moveAnimation: number; 131 | let lastPageY = e.pageY; 132 | let lastScrollTop = el.scrollTop; 133 | 134 | setIsDraggingTrack(true); 135 | 136 | const drag = ({ pageY }: MouseEvent) => { 137 | cancelAnimationFrame(moveAnimation); 138 | moveAnimation = requestAnimationFrame(() => { 139 | const delta = pageY - lastPageY; 140 | lastScrollTop += delta / scrollRatio; 141 | lastPageY = pageY; 142 | el.scrollTop = lastScrollTop; 143 | }); 144 | }; 145 | 146 | const stop = () => { 147 | setIsDraggingTrack(false); 148 | window.removeEventListener('mousemove', drag); 149 | }; 150 | 151 | window.addEventListener('mousemove', drag); 152 | window.addEventListener('mouseup', stop, { once: true }); 153 | }, 154 | [scrollerRef, scrollRatio], 155 | ); 156 | 157 | const wrapperProps = { 158 | style: { 159 | marginLeft: `-${SCROLLBAR_WIDTH}px`, 160 | }, 161 | }; 162 | 163 | const scrollerProps = { 164 | ref: scrollerRef, 165 | onScroll: disabled ? undefined : onScroll, 166 | style: { 167 | right: `-${SCROLLBAR_WIDTH}px`, 168 | padding: `0 ${SCROLLBAR_WIDTH}px 0 0`, 169 | width: `calc(100% + ${OS_SCROLLBAR_WIDTH}px)`, 170 | }, 171 | }; 172 | 173 | const trackProps = { 174 | ref: trackRef, 175 | onMouseDown: disabled ? undefined : moveTrack, 176 | style: { 177 | right: isDraggingTrack ? 1 : undefined, 178 | width: isDraggingTrack ? 10 : undefined, 179 | height: `${scrollRatio * 100}%`, 180 | opacity: isDraggingTrack ? 1 : undefined, 181 | display: disabled || scrollRatio === 1 ? 'none' : undefined, 182 | }, 183 | }; 184 | 185 | return [wrapperProps, scrollerProps, trackProps] as const; 186 | } 187 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "jsx": "react-jsx", 7 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 8 | "sourceMap": true, 9 | "module": "esnext", 10 | "target": "ES2018", 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "outDir": "./dist", 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "strictNullChecks": true, 17 | "noEmit": true, 18 | "strict": true, 19 | "noImplicitAny": true, 20 | "resolveJsonModule": true 21 | }, 22 | "include": ["**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules", "**/node_modules/*", "dist", "**/dist/*"] 24 | } 25 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig(({ watch }) => ({ 4 | entry: ['src/index.ts'], 5 | format: watch ? ['iife'] : ['cjs', 'esm'], 6 | cjsInterop: true, 7 | sourcemap: true, 8 | clean: true, 9 | dts: !watch, 10 | loader: { 11 | '.css': 'local-css', 12 | }, 13 | })); 14 | --------------------------------------------------------------------------------