├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github
└── example.gif
├── .gitignore
├── .prettierignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── example
├── .npmignore
├── components
│ ├── Card.tsx
│ ├── HorizontalExample.tsx
│ ├── TwoDimensionalExample.tsx
│ ├── VerticalExample.tsx
│ └── WidthMultiRefComponent.tsx
├── index.html
├── index.tsx
├── package-lock.json
├── package.json
├── styles.css
├── tsconfig.json
└── yarn.lock
├── package.json
├── postcss.config.js
├── src
├── index.tsx
└── useIsomorphicLayoutEffect.ts
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /*.js
2 | node_modules
3 | dist
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "jest": true
6 | },
7 | "extends": [
8 | "plugin:react/recommended",
9 | "airbnb",
10 | "plugin:prettier/recommended",
11 | "plugin:@typescript-eslint/recommended"
12 | ],
13 | "parser": "@typescript-eslint/parser",
14 | "parserOptions": {
15 | "ecmaFeatures": {
16 | "jsx": true
17 | },
18 | "ecmaVersion": 12,
19 | "sourceType": "module"
20 | },
21 | "plugins": ["react", "@typescript-eslint", "prettier"],
22 | "rules": {
23 | "no-use-before-define": "off",
24 | "@typescript-eslint/no-use-before-define": ["error"],
25 | "react/jsx-props-no-spreading": ["off"],
26 | "react/jsx-filename-extension": "off",
27 | "react/react-in-jsx-scope": "off",
28 | "prettier/prettier": "error",
29 | "quotes": ["error", "double",
30 | {
31 | "avoidEscape": false,
32 | "allowTemplateLiterals": true
33 | }
34 | ],
35 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
36 | "import/prefer-default-export": "off",
37 | "import/extensions": [
38 | "error",
39 | "ignorePackages",
40 | {
41 | "ts": "never",
42 | "tsx": "never"
43 | }
44 | ]
45 | },
46 | "settings": {
47 | "import/extensions": [".js", ".jsx", ".ts", ".tsx"],
48 | "import/parsers": {
49 | "@typescript-eslint/parser": [".ts", ".tsx"]
50 | },
51 | "import/resolver": {
52 | "typescript": {
53 | "directory": "./tsconfig.json"
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/.github/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rfmiotto/react-use-draggable-scroll/f4bc9551d3fe2be02f53f9b8158b879b54a6fbf9/.github/example.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 | .parcel-cache
7 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /*.js
2 | node_modules
3 | dist
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ---
3 |
4 | # Running on development machine
5 |
6 | Install library dependencies:
7 |
8 | ```console
9 | yarn install
10 | ```
11 | ```console
12 | npm install
13 | ```
14 |
15 | Build library in watch mode:
16 |
17 | ```console
18 | yarn start
19 | ```
20 | ```console
21 | npm start
22 | ```
23 |
24 | In a separate console:
25 |
26 | Install example page dependencies:
27 |
28 | ```console
29 | cd example
30 | yarn install
31 | ```
32 | ```console
33 | cd example
34 | npm install
35 | ```
36 |
37 | Build and serve example page in watch mode:
38 |
39 | ```console
40 | yarn start
41 | ```
42 | ```console
43 | npm start
44 | ```
45 |
46 | Open a browser an point it to the URL where server is running:
47 | the example page should appear.
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Renato Fuzaro Miotto
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # useDraggable Hook
2 |
3 | [](https://www.npmjs.com/package/react-use-draggable-scroll)
4 |
5 | useDraggable is a React hook that allows a wrapping div to have a draggable scroll with an inertial effect.
6 | It is completely unstyled and just adds the functionality you are looking for so your application gives
7 | the best user experience possible. It works in both x- and y-coordinate directions.
8 |
9 | See [DEMO](https://stackblitz.com/edit/nextjs-tg52v4).
10 |
11 |
12 |
13 | ### Why useDraggable?
14 |
15 | Differently from other hooks designed for the same purpose, this hook does not rely on any state changes. The
16 | functionality is built entirely on event listeners. This means that the wrapping div and its children elements
17 | are not re-rendered, resulting in a better performance.
18 |
19 | ### Installation
20 |
21 | ```console
22 | yarn add react-use-draggable-scroll
23 | ```
24 |
25 | ```console
26 | npm install react-use-draggable-scroll
27 | ```
28 |
29 | ### How to use
30 |
31 | All you have to do is to create a reference to the wrapping div and pass it as parameter to to the useDraggable hook.
32 | The hook is totally unstyled. You can use any library of your choice to style the div and the child components as you would normally do.
33 | In the example below, we use TailwindCSS to illustrate.
34 |
35 | Just recapping some basics of CSS that you will probably use along with this hook: It is important to set `overflow-x: scroll;`
36 | property in the CSS of the wrapping div to create the scroll (same goes for y-direction, if that is your case). To prevent a
37 | flex item from growing or shrinking, use the CSS property `flex: none;`.
38 |
39 | **In Javascript:**
40 |
41 | ```javascript
42 | import { useRef } from "react";
43 | import { useDraggable } from "react-use-draggable-scroll";
44 |
45 | export default function MyComponent() {
46 | const ref = useRef(); // We will use React useRef hook to reference the wrapping div:
47 | const { events } = useDraggable(ref); // Now we pass the reference to the useDraggable hook:
48 |
49 | return (
50 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | );
66 | }
67 | ```
68 |
69 | **In Typescript:**
70 |
71 | ```typescript
72 | import { useRef } from "react";
73 | import { useDraggable } from "react-use-draggable-scroll";
74 |
75 | export default function MyComponent(): JSX.Element {
76 | // We will use React useRef hook to reference the wrapping div:
77 | const ref =
78 | useRef() as React.MutableRefObject;
79 | const { events } = useDraggable(ref); // Now we pass the reference to the useDraggable hook:
80 |
81 | return (
82 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | );
98 | }
99 | ```
100 |
101 | ### Additional settings:
102 |
103 | **`activeMouseButton`: Change which mouse button starts a scroll**
104 |
105 | By default, holding left mouse button will start a scroll. However, you can also use the middle or right
106 | mouse button to start a scroll.
107 |
108 | Accepts `"Left" | "Right" | "Middle`
109 |
110 | ```typescript
111 | const { events } = useDraggable(ref, {
112 | activeMouseButton?: "Middle"; // Sets which mouse button starts a scroll
113 | });
114 | ```
115 |
116 | **`applyRubberBandEffect`: Rubber Band Effect**
117 |
118 | It is possible to toggle a rubber band effect on and off for when the
119 | user scrolls past the end of the container. This effect is turned off by default to avoid conflicting CSS style rules in code that uses earlier versions of this hook.
120 |
121 | ```typescript
122 | const { events } = useDraggable(ref, {
123 | applyRubberBandEffect: true, // activate rubber band effect
124 | });
125 | ```
126 |
127 | > :warning: **If you are using rubber band effect**: This effect is applied
128 | > using the `transform` CSS property. User-defined styles can be overridden when `applyRubberBandEffect` is `true` (default value is `false`).
129 |
130 | **`decayRate`: Control the decay rate of the inertial effect**
131 |
132 | You can also control the decay rate of the inertial effect by using an optional
133 | parameter. The default value is 0.95, which means that at the speed will decay 5% of
134 | its current value at every 1/60 seconds.
135 |
136 | ```typescript
137 | const { events } = useDraggable(ref, {
138 | decayRate: 0.96, // specify the decay rate
139 | });
140 | ```
141 |
142 | **`safeDisplacement`: Control the drag sensitivity**
143 |
144 | Finally, you can control drag sensitivity by using an optional parameter that states
145 | the minimum distance in order to distinguish an intentional drag movement from
146 | an unwanted one, which should be instead considered as a click.
147 | The default value is 10, which means that when a drag movement travels for 10 pixels
148 | or less it is considered unintentional. In this scenario, the drag operation would
149 | still be performed, but the closing mouse-up event would still be propagated to the
150 | rest of the DOM.
151 |
152 | ```typescript
153 | const { events } = useDraggable(ref, {
154 | safeDisplacement: 11, // specify the drag sensitivity
155 | });
156 | ```
157 |
158 | **`isMounted`: Determine if ref is available or not, default `true`**
159 |
160 | In some use cases, such as you need to use `useImperativeHandle`,
161 | you will need to wait for the component to be rendered and the Ref to be accessible before
162 | using the `useDraggable` hook. In this case, use `isMounted` as a controllable switch.
163 |
164 | ```typescript
165 | const { events } = useDraggable(ref, {
166 | isMounted: true,
167 | });
168 | ```
169 |
170 | ### Contributing
171 |
172 | See [CONTRIBUTING.md](CONTRIBUTING.md).
173 |
--------------------------------------------------------------------------------
/example/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | dist
--------------------------------------------------------------------------------
/example/components/Card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | type CardPros = {
4 | onClick: () => void;
5 | } & React.ButtonHTMLAttributes;
6 |
7 | const Card = ({ onClick, ...rest }: CardPros): JSX.Element => {
8 | return (
9 |
16 | );
17 | };
18 | export default Card;
19 |
--------------------------------------------------------------------------------
/example/components/HorizontalExample.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useRef, useState } from "react";
3 | import { useDraggable } from "../../src";
4 |
5 | import Card from "./Card";
6 |
7 | function HorizontalExample(): JSX.Element {
8 | const [numberOfEventsFired, setNumberOfEventsFired] = useState(0);
9 |
10 | const handleClickEvent = () => {
11 | setNumberOfEventsFired((oldState) => oldState + 1);
12 | };
13 |
14 | const ref =
15 | useRef() as React.MutableRefObject;
16 | const { events } = useDraggable(ref, {
17 | decayRate: 0.96,
18 | safeDisplacement: 11,
19 | applyRubberBandEffect: true,
20 | });
21 |
22 | return (
23 |
18 |
19 | react-use-draggable-scroll
20 | {" "}
21 | does not interfere with other events passing through the same scroll
22 | container. In this example, clicking on one of the cards below causes a
23 | counter to increase its value.
24 |