From 746f86fa5a7187acb6f161266bfe09612917f0c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=A1lvaro=20s?= <50526113+alvvaro@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:21:34 +0200 Subject: [PATCH 1/2] feat: useLanguage sensor --- README.md | 1 + docs/useLanguage.md | 26 ++++++++++++++++++++++++++ src/index.ts | 1 + src/misc/util.ts | 2 ++ src/useLanguage.ts | 34 ++++++++++++++++++++++++++++++++++ stories/useLanguage.story.tsx | 26 ++++++++++++++++++++++++++ tests/useLanguage.test.ts | 25 +++++++++++++++++++++++++ 7 files changed, 115 insertions(+) create mode 100644 docs/useLanguage.md create mode 100644 src/useLanguage.ts create mode 100644 stories/useLanguage.story.tsx create mode 100644 tests/useLanguage.test.ts diff --git a/README.md b/README.md index cc1728fafd..640a006084 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ - [`useIdle`](./docs/useIdle.md) — tracks whether user is being inactive. - [`useIntersection`](./docs/useIntersection.md) — tracks an HTML element's intersection. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-useintersection--demo) - [`useKey`](./docs/useKey.md), [`useKeyPress`](./docs/useKeyPress.md), [`useKeyboardJs`](./docs/useKeyboardJs.md), and [`useKeyPressEvent`](./docs/useKeyPressEvent.md) — track keys. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usekeypressevent--demo) + - [`useLanguage`](./docs/useLanguage.md) — tracks the language of the document. - [`useLocation`](./docs/useLocation.md) and [`useSearchParam`](./docs/useSearchParam.md) — tracks page navigation bar location state. - [`useLongPress`](./docs/useLongPress.md) — tracks long press gesture of some element. - [`useMedia`](./docs/useMedia.md) — tracks state of a CSS media query. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemedia--demo) diff --git a/docs/useLanguage.md b/docs/useLanguage.md new file mode 100644 index 0000000000..c9acc90cf8 --- /dev/null +++ b/docs/useLanguage.md @@ -0,0 +1,26 @@ +# `useLanguage` + +React state hook that tracks the document's `lang` attribute. + +## Usage + +```jsx +import { useLanguage } from 'react-use'; + +const Demo = () => { + const [lang, setLang] = useLanguage(); + + return ( +
+
+ The document's current language is: {lang} +
+ +
+ + + +
+ ); +}; +``` diff --git a/src/index.ts b/src/index.ts index 62b69356b7..42c7554e9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,6 +42,7 @@ export { default as createBreakpoint } from './factory/createBreakpoint'; // export { default as useKeyboardJs } from './useKeyboardJs'; export { default as useKeyPress } from './useKeyPress'; export { default as useKeyPressEvent } from './useKeyPressEvent'; +export { default as useLanguage } from './useLanguage'; export { default as useLatest } from './useLatest'; export { default as useLifecycles } from './useLifecycles'; export { default as useList } from './useList'; diff --git a/src/misc/util.ts b/src/misc/util.ts index 34fa42c206..34150b5503 100644 --- a/src/misc/util.ts +++ b/src/misc/util.ts @@ -21,3 +21,5 @@ export function off( export const isBrowser = typeof window !== 'undefined'; export const isNavigator = typeof navigator !== 'undefined'; + +export const isDocument = typeof document !== 'undefined'; diff --git a/src/useLanguage.ts b/src/useLanguage.ts new file mode 100644 index 0000000000..c6ef77ff81 --- /dev/null +++ b/src/useLanguage.ts @@ -0,0 +1,34 @@ +import { useEffect, useState, Dispatch, SetStateAction } from 'react'; +import { isDocument } from './misc/util'; + +function useLanguage() { + const [lang, setLangState] = useState(document.documentElement.lang); + + const setLang: Dispatch> = (action) => { + if (typeof action === 'function') { + document.documentElement.lang = action(lang); + } else { + document.documentElement.lang = action; + } + }; + + useEffect(() => { + const handler = () => { + setLangState(document.documentElement.lang); + }; + + const observer = new MutationObserver(handler); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['lang'], + }); + + return () => observer.disconnect(); + }, []); + + return [lang, setLang] as const; +} + +export default typeof isDocument + ? useLanguage + : () => ['', (_action: SetStateAction) => {}] as const; diff --git a/stories/useLanguage.story.tsx b/stories/useLanguage.story.tsx new file mode 100644 index 0000000000..ded4097cfe --- /dev/null +++ b/stories/useLanguage.story.tsx @@ -0,0 +1,26 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import { useLanguage } from '../src'; +import NewTabStory from './util/NewTabStory'; +import ShowDocs from './util/ShowDocs'; + +const Demo = () => { + const [lang, setLang] = useLanguage(); + + return ( + +
+
The document's current language is: {lang}
+ +
+ + + +
+
+ ); +}; + +storiesOf('Sensors/useLanguage', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/tests/useLanguage.test.ts b/tests/useLanguage.test.ts new file mode 100644 index 0000000000..dade9ca187 --- /dev/null +++ b/tests/useLanguage.test.ts @@ -0,0 +1,25 @@ +import { renderHook } from '@testing-library/react-hooks'; +import useLanguage from '../src/useLanguage'; + +describe('useLanguage', () => { + it('should be defined', () => { + expect(useLanguage).toBeDefined(); + }); + + it('should retrieve the document language', () => { + document.documentElement.lang = 'ar-SA'; + const hook = renderHook(() => useLanguage()); + + expect(document.documentElement.lang).toBe(hook.result.current[0]); + }); + + it('should update document language', () => { + const hook = renderHook(() => useLanguage()); + + hook.result.current[1]('bn-BD'); + expect(document.documentElement.lang).toBe('bn-BD'); + + hook.result.current[1]('bn-IN'); + expect(document.documentElement.lang).toBe('bn-IN'); + }); +}); From 043fa5d332e70e93da0b643ec8c829c6eb0b3e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=A1lvaro=20s?= <50526113+alvvaro@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:26:39 +0200 Subject: [PATCH 2/2] feat: useLanguage sensor --- src/useLanguage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useLanguage.ts b/src/useLanguage.ts index c6ef77ff81..4b793e0e39 100644 --- a/src/useLanguage.ts +++ b/src/useLanguage.ts @@ -29,6 +29,6 @@ function useLanguage() { return [lang, setLang] as const; } -export default typeof isDocument +export default isDocument ? useLanguage : () => ['', (_action: SetStateAction) => {}] as const;