From f4af900e1c1cbd05146fe85a01bf966973d8b7d3 Mon Sep 17 00:00:00 2001 From: xobotyi Date: Mon, 9 Dec 2019 01:11:34 +0300 Subject: [PATCH 1/3] feat: useScrollbarWidth hook; --- README.md | 1 + docs/useScrollbarWidth.md | 25 ++++++++++++ package.json | 1 + src/__stories__/useScrollbarWidth.story.tsx | 18 +++++++++ src/index.ts | 1 + src/useScrollbarWidth.ts | 21 ++++++++++ tests/useScrollbarWidth.test.ts | 45 +++++++++++++++++++++ yarn.lock | 5 +++ 8 files changed, 117 insertions(+) create mode 100644 docs/useScrollbarWidth.md create mode 100644 src/__stories__/useScrollbarWidth.story.tsx create mode 100644 src/useScrollbarWidth.ts create mode 100644 tests/useScrollbarWidth.test.ts diff --git a/README.md b/README.md index 12be4371ba..6b65d8a35b 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ - [`useWindowSize`](./docs/useWindowSize.md) — tracks `Window` dimensions. [![][img-demo]](https://codesandbox.io/s/m7ln22668) - [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions using the Resize Observer API.[![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemeasure--demo) - [`createBreakpoint`](./docs/createBreakpoint.md) — tracks `innerWidth` + - [`useScrollbarWidth`](./docs/useScrollbarWidth.md) — detects browser's native scrollbars width. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usescrollbarwidth--demo)

- [**UI**](./docs/UI.md) diff --git a/docs/useScrollbarWidth.md b/docs/useScrollbarWidth.md new file mode 100644 index 0000000000..b3246dfa91 --- /dev/null +++ b/docs/useScrollbarWidth.md @@ -0,0 +1,25 @@ +# `useScrollbarWidth` + +Hook that will return current browser's scrollbar width. +In case hook been called before DOM ready, it will return `undefined` and will cause re-render on first available RAF. +> **_NOTE:_** it does not work (return 0) for mobile devices, because their scrollbar width can not be determined. + +## Usage + +```jsx +const Demo = () => { + const sbw = useScrollbarWidth(); + + return ( +
+ {sbw === undefined ? `DOM is not ready yet, SBW detection delayed` : `Browser's scrollbar width is ${sbw}px`} +
+ ); +}; +``` + +## Reference + +```typescript +const sbw: number | undefined = useScrollbarWidth(); +``` diff --git a/package.json b/package.json index 4805ab25df..057bfeda56 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@testing-library/react-hooks": "3.2.1", "@types/jest": "24.0.23", "@types/react": "16.9.11", + "@xobotyi/scrollbar-width": "1.4.1", "babel-core": "6.26.3", "babel-loader": "8.0.6", "babel-plugin-dynamic-import-node": "2.3.0", diff --git a/src/__stories__/useScrollbarWidth.story.tsx b/src/__stories__/useScrollbarWidth.story.tsx new file mode 100644 index 0000000000..2225f0f4fb --- /dev/null +++ b/src/__stories__/useScrollbarWidth.story.tsx @@ -0,0 +1,18 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import { useScrollbarWidth } from '..'; +import ShowDocs from './util/ShowDocs'; + +const Demo = () => { + const sbw = useScrollbarWidth(); + + return ( +
+ {sbw === undefined ? `DOM is not ready yet, SBW detection delayed` : `Browser's scrollbar width is ${sbw}px`} +
+ ); +}; + +storiesOf('Sensors/useScrollbarWidth', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/src/index.ts b/src/index.ts index 61ef03e3a3..9d78994a27 100644 --- a/src/index.ts +++ b/src/index.ts @@ -92,6 +92,7 @@ export { default as useUpsert } from './useUpsert'; export { default as useVibrate } from './useVibrate'; export { default as useVideo } from './useVideo'; export { default as useStateValidator } from './useStateValidator'; +export { useScrollbarWidth } from './useScrollbarWidth'; export { useMultiStateValidator } from './useMultiStateValidator'; export { default as useWindowScroll } from './useWindowScroll'; export { default as useWindowSize } from './useWindowSize'; diff --git a/src/useScrollbarWidth.ts b/src/useScrollbarWidth.ts new file mode 100644 index 0000000000..ff637ce4f9 --- /dev/null +++ b/src/useScrollbarWidth.ts @@ -0,0 +1,21 @@ +import { scrollbarWidth } from '@xobotyi/scrollbar-width'; +import { useEffect, useState } from 'react'; + +export function useScrollbarWidth(): number | undefined { + const [sbw, setSbw] = useState(scrollbarWidth()); + + // this needed to ensure the scrollbar width in case hook called before the DOM is ready + useEffect(() => { + if (typeof sbw !== 'undefined') { + return; + } + + const raf = requestAnimationFrame(() => { + setSbw(scrollbarWidth()); + }); + + return () => cancelAnimationFrame(raf); + }, []); + + return sbw; +} diff --git a/tests/useScrollbarWidth.test.ts b/tests/useScrollbarWidth.test.ts new file mode 100644 index 0000000000..26a60aa423 --- /dev/null +++ b/tests/useScrollbarWidth.test.ts @@ -0,0 +1,45 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import { scrollbarWidth } from '@xobotyi/scrollbar-width'; +import { useScrollbarWidth } from '../src'; +import { replaceRaf } from 'raf-stub'; + +declare var requestAnimationFrame: { + add: (cb: Function) => number; + remove: (id: number) => void; + flush: (duration?: number) => void; + reset: () => void; + step: (steps?: number, duration?: number) => void; +}; + +describe('useScrollbarWidth', () => { + beforeAll(() => { + replaceRaf(); + }); + + afterEach(() => { + requestAnimationFrame.reset(); + }); + + it('should be defined', () => { + expect(useScrollbarWidth).toBeDefined(); + }); + + it('should return value of scrollbarWidth result', () => { + scrollbarWidth.__cache = 21; + const { result } = renderHook(() => useScrollbarWidth()); + + expect(result.current).toBe(21); + }); + + it('should re-call scrollbar width in RAF in case `scrollbarWidth()` returned undefined', () => { + scrollbarWidth.__cache = undefined; + const { result } = renderHook(() => useScrollbarWidth()); + expect(result.current).toBe(undefined); + scrollbarWidth.__cache = 34; + act(() => { + requestAnimationFrame.step(); + }); + + expect(result.current).toBe(34); + }); +}); diff --git a/yarn.lock b/yarn.lock index 31ebec02d3..cff5f54175 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3094,6 +3094,11 @@ "@webassemblyjs/wast-parser" "1.8.5" "@xtuc/long" "4.2.2" +"@xobotyi/scrollbar-width@1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.4.1.tgz#806ca12611b16751d2ae3a4e1d7a6c1b131a4534" + integrity sha512-7OuEcZGd9tVeGUHf0QWJypdOVr6d2k7liyYIVGDc8r3D1/Fvi6yk48N1puuT8QdiAKFP5/vJB8+DnXpt4NBN9g== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" From 775a4c997a73eddf5520f5c6f77d550f1df47fd9 Mon Sep 17 00:00:00 2001 From: xobotyi Date: Mon, 9 Dec 2019 11:12:07 +0300 Subject: [PATCH 2/3] chore: bump @xobotyi/scrollbar-width to 1.5.0 and add it to dependencies. --- package.json | 3 ++- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 057bfeda56..72442165bb 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ }, "homepage": "https://github.com/streamich/react-use#readme", "dependencies": { + "@xobotyi/scrollbar-width": "1.5.0", "copy-to-clipboard": "^3.2.0", "nano-css": "^5.2.1", "react-fast-compare": "^2.0.4", @@ -78,7 +79,7 @@ "@testing-library/react-hooks": "3.2.1", "@types/jest": "24.0.23", "@types/react": "16.9.11", - "@xobotyi/scrollbar-width": "1.4.1", + "@xobotyi/scrollbar-width": "1.5.0", "babel-core": "6.26.3", "babel-loader": "8.0.6", "babel-plugin-dynamic-import-node": "2.3.0", diff --git a/yarn.lock b/yarn.lock index cff5f54175..770649f50a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3094,10 +3094,10 @@ "@webassemblyjs/wast-parser" "1.8.5" "@xtuc/long" "4.2.2" -"@xobotyi/scrollbar-width@1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.4.1.tgz#806ca12611b16751d2ae3a4e1d7a6c1b131a4534" - integrity sha512-7OuEcZGd9tVeGUHf0QWJypdOVr6d2k7liyYIVGDc8r3D1/Fvi6yk48N1puuT8QdiAKFP5/vJB8+DnXpt4NBN9g== +"@xobotyi/scrollbar-width@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.5.0.tgz#488210bff634548040dc22a72f62722a85b134e1" + integrity sha512-BK+HR1D00F2xh7n4+5en8/dMkG13uvIXLmEbsjtc1702b7+VwXkvlBDKoRPJMbkRN5hD7VqWa3nS9fNT8JG3CA== "@xtuc/ieee754@^1.2.0": version "1.2.0" From dcf5f25099e2ef18dfe454426b715dba3b900477 Mon Sep 17 00:00:00 2001 From: xobotyi Date: Mon, 9 Dec 2019 15:42:44 +0300 Subject: [PATCH 3/3] fix: remove @xobotyi/scrollbar-width from dev-deps. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 72442165bb..5d4901d582 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,6 @@ "@testing-library/react-hooks": "3.2.1", "@types/jest": "24.0.23", "@types/react": "16.9.11", - "@xobotyi/scrollbar-width": "1.5.0", "babel-core": "6.26.3", "babel-loader": "8.0.6", "babel-plugin-dynamic-import-node": "2.3.0",