├── .gitignore
├── dist
├── index.d.ts
├── index.js.map
└── index.js
├── .eslintrc.js
├── tsconfig.json
├── LICENSE
├── package.json
├── README.md
└── src
└── index.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | /**
3 | * Returns a ref, and a stateful value bound to the ref
4 | */
5 | export declare function useSticky(): readonly [import("react").RefObject, boolean];
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | export default {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: [
7 | 'eslint:recommended',
8 | 'plugin:react/recommended',
9 | 'plugin:@typescript-eslint/recommended',
10 | 'plugin:react-hooks/recommended',
11 | ],
12 | parser: '@typescript-eslint/parser',
13 | parserOptions: {
14 | ecmaFeatures: {
15 | jsx: true,
16 | },
17 | ecmaVersion: 13,
18 | sourceType: 'module',
19 | },
20 | plugins: ['react', '@typescript-eslint'],
21 | rules: {},
22 | };
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "commonjs",
5 | "allowJs": true,
6 | "checkJs": false,
7 | "outDir": "dist",
8 | "rootDir": "src",
9 | "jsx": "react-jsx",
10 | "strict": true,
11 | "esModuleInterop": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "declaration": true,
14 | "strictNullChecks": true,
15 | "resolveJsonModule": true,
16 | "sourceMap": true,
17 | "baseUrl": ".",
18 | "paths": {
19 | "*": ["*", "src/*"]
20 | }
21 | },
22 | "exclude": ["node_modules", "dist"],
23 | "include": ["./src", "./test", "./*"]
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Tailwind Labs
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 |
--------------------------------------------------------------------------------
/dist/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iCAAoD;AAEpD;;GAEG;AACH,SAAgB,SAAS;IACvB,MAAM,SAAS,GAAG,IAAA,cAAM,EAAI,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,IAAA,gBAAQ,EAAC,KAAK,CAAC,CAAC;IAE5C,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,iDAAiD;QACjD,mIAAmI;QACnI,SAAS,OAAO;YACd,IAAI,CAAC,SAAS,CAAC,OAAO;gBAAE,OAAO;YAC/B,MAAM,aAAa,GAAG,SAAS,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,GAAG,CAAC;YACpE,MAAM,YAAY,GAAG,QAAQ,CAAC,gBAAgB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;YACvE,MAAM,YAAY,GAAG,aAAa,IAAI,YAAY,CAAC;YAEnD,IAAI,YAAY,IAAI,CAAC,MAAM;gBAAE,SAAS,CAAC,IAAI,CAAC,CAAC;iBACxC,IAAI,CAAC,YAAY,IAAI,MAAM;gBAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,EAAE,CAAC;QAEV,cAAc;QACd,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC7C,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC;QAEtD,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAChD,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC9C,MAAM,CAAC,mBAAmB,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC;QAC3D,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,OAAO,CAAC,SAAS,EAAE,MAAM,CAAU,CAAC;AACtC,CAAC;AA/BD,8BA+BC"}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-use-sticky",
3 | "version": "0.1.4",
4 | "description": "Observe when DOM element enters or leaves sticky state",
5 | "main": "dist/index.js",
6 | "peerDependencies": {
7 | "react": "^16.8.0"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "build": "tsc"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/robinJonsson/react-use-sticky.git"
16 | },
17 | "keywords": [
18 | "react",
19 | "hooks",
20 | "sticky",
21 | "css-position-sticky",
22 | "position"
23 | ],
24 | "author": "Robin Jonsson",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/robinJonsson/react-use-sticky/issues"
28 | },
29 | "homepage": "https://github.com/robinJonsson/react-use-sticky#readme",
30 | "devDependencies": {
31 | "@types/react": "^17.0.37",
32 | "@types/react-dom": "^17.0.11",
33 | "@typescript-eslint/eslint-plugin": "^5.4.0",
34 | "@typescript-eslint/parser": "^5.4.0",
35 | "eslint": "^8.3.0",
36 | "eslint-plugin-react": "^7.27.1",
37 | "eslint-plugin-react-hooks": "^4.3.0",
38 | "typescript": "^4.5.2"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Sticky Hook
2 |
3 | A react hook for observing/watching `position: sticky` state on refs
4 |
5 | ## Installation
6 |
7 | `npm i react-use-sticky`
8 |
9 | ## Usage
10 |
11 | `useSticky` returns a pair of values, the ref to observe/watch and the current sticky state of the ref.
12 |
13 | ```jsx
14 | import { useSticky } from 'react-use-sticky';
15 |
16 | function HeaderBar() {
17 | const [headerBarRef, sticky] = useSticky();
18 | const style = {
19 | position: 'sticky',
20 | top: 0,
21 | background: sticky ? 'green' : 'red',
22 | };
23 |
24 | return (
25 |
28 | );
29 | }
30 |
31 | export default HeaderBar;
32 | ```
33 |
34 | Typescript with generic
35 |
36 | ```tsx
37 | import { useSticky } from 'react-use-sticky';
38 |
39 | function HeaderBar() {
40 | const [headerBarRef, sticky] = useSticky();
41 | const style = {
42 | position: 'sticky',
43 | top: 0,
44 | background: sticky ? 'green' : 'red',
45 | } as const;
46 |
47 | return (
48 |
49 | HeaderBar
50 |
51 | );
52 | }
53 |
54 | export default HeaderBar;
55 | ```
56 |
57 | ## Build
58 |
59 | ```bash
60 | npm run build
61 | ```
62 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 |
3 | /**
4 | * Returns a ref, and a stateful value bound to the ref
5 | */
6 | export function useSticky() {
7 | const stickyRef = useRef(null);
8 | const [sticky, setSticky] = useState(false);
9 |
10 | useEffect(() => {
11 | // Observe when ref enters or leaves sticky state
12 | // rAF https://stackoverflow.com/questions/41740082/scroll-events-requestanimationframe-vs-requestidlecallback-vs-passive-event-lis
13 | function observe() {
14 | if (!stickyRef.current) return;
15 | const refPageOffset = stickyRef.current.getBoundingClientRect().top;
16 | const stickyOffset = parseInt(getComputedStyle(stickyRef.current).top);
17 | const stickyActive = refPageOffset <= stickyOffset;
18 |
19 | if (stickyActive && !sticky) setSticky(true);
20 | else if (!stickyActive && sticky) setSticky(false);
21 | }
22 | observe();
23 |
24 | // Bind events
25 | document.addEventListener('scroll', observe);
26 | window.addEventListener('resize', observe);
27 | window.addEventListener('orientationchange', observe);
28 |
29 | return () => {
30 | document.removeEventListener('scroll', observe);
31 | window.removeEventListener('resize', observe);
32 | window.removeEventListener('orientationchange', observe);
33 | };
34 | }, [sticky]);
35 |
36 | return [stickyRef, sticky] as const;
37 | }
38 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.useSticky = void 0;
4 | const react_1 = require("react");
5 | /**
6 | * Returns a ref, and a stateful value bound to the ref
7 | */
8 | function useSticky() {
9 | const stickyRef = (0, react_1.useRef)(null);
10 | const [sticky, setSticky] = (0, react_1.useState)(false);
11 | (0, react_1.useEffect)(() => {
12 | // Observe when ref enters or leaves sticky state
13 | // rAF https://stackoverflow.com/questions/41740082/scroll-events-requestanimationframe-vs-requestidlecallback-vs-passive-event-lis
14 | function observe() {
15 | if (!stickyRef.current)
16 | return;
17 | const refPageOffset = stickyRef.current.getBoundingClientRect().top;
18 | const stickyOffset = parseInt(getComputedStyle(stickyRef.current).top);
19 | const stickyActive = refPageOffset <= stickyOffset;
20 | if (stickyActive && !sticky)
21 | setSticky(true);
22 | else if (!stickyActive && sticky)
23 | setSticky(false);
24 | }
25 | observe();
26 | // Bind events
27 | document.addEventListener('scroll', observe);
28 | window.addEventListener('resize', observe);
29 | window.addEventListener('orientationchange', observe);
30 | return () => {
31 | document.removeEventListener('scroll', observe);
32 | window.removeEventListener('resize', observe);
33 | window.removeEventListener('orientationchange', observe);
34 | };
35 | }, [sticky]);
36 | return [stickyRef, sticky];
37 | }
38 | exports.useSticky = useSticky;
39 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------