Skip to content

Commit 4e41a07

Browse files
committed
[ADD] useResponsive hook
1 parent c8e3b68 commit 4e41a07

11 files changed

Lines changed: 177 additions & 7 deletions

File tree

apps/react-tools-demo/scripts/generateMarkdown.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ function buildHooksUtilsMarkdownObject(file) {
226226
return obj;
227227
}
228228
const lines = getJsDoc(fileSplitted);
229-
lines.forEach(line => {
229+
lines.forEach((line, index) => {
230230
const depuratedLine = (line.split(" * ")[1]);
231231
if(depuratedLine.startsWith("@param")) {
232232
const typeNameDescriptionParam = depuratedLine.split("@param ")[1];
@@ -250,6 +250,13 @@ function buildHooksUtilsMarkdownObject(file) {
250250
titleDescription.shift();
251251
obj.description = titleDescription.join(":").trim();
252252
obj.description = obj.description.charAt(0).toUpperCase() + obj.description.substring(1);
253+
let lastDescriptionIndex = index+1;
254+
while([" * @param", " * @returns", "//", " */", "*/"].filter(key => lines[lastDescriptionIndex].startsWith(key)).length === 0) {
255+
lastDescriptionIndex++;
256+
}
257+
if(lastDescriptionIndex !== (index+1)) {
258+
obj.description += lines.splice(index+1, lastDescriptionIndex-1).map(el => el.split(" * ")[1]).join("\n");
259+
}
253260
}
254261
});
255262
let codeLines = fileSplitted.slice(fileSplitted.findIndex(line => line === lines.at(-1))+2);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { useResponsive } from "../../../../../../packages/react-tools/src"
2+
3+
/**
4+
The component initialize _useResponsive_ hook without param and displays screen size value.
5+
6+
Try to change window size to see how value changes.
7+
*/
8+
export const UseResponsive = () => {
9+
const responsive = useResponsive();
10+
11+
return <p>{JSON.stringify(responsive, null, 2)}</p>
12+
}

apps/react-tools-demo/src/constants/components.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ export const COMPONENTS = [
4747
"useEventDispatcher",
4848
"usePerformAction",
4949
"useDocumentVisibility",
50-
"useHover"
50+
"useHover",
51+
"useResponsive"
5152
],
5253
//API DOM
5354
[
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# useResponsive
2+
Hook for getting responsive window size. It receives an optional param __config__ to manually setting breakpoint keys. __config__ can have a keys subset and value can be a number or an object with _value_ and _condition_ properties. If _value_ is a number, the condition will be ">". By default Breakpoints are:
3+
- xs: { value: 576, condition: "<" }
4+
- sm: { value: 576, condition: ">=" }
5+
- md: { value: 768, condition: ">=" }
6+
- lg: { value: 992, condition: ">=" }
7+
- xl: { value: 1200, condition: ">=" }
8+
9+
## Usage
10+
11+
```tsx
12+
export const UseResponsive = () => {
13+
const responsive = useResponsive();
14+
15+
return <p>{JSON.stringify(responsive, null, 2)}</p>
16+
}
17+
```
18+
19+
> The component initialize _useResponsive_ hook without param and displays screen size value.
20+
>
21+
> Try to change window size to see how value changes.
22+
23+
24+
## API
25+
26+
```tsx
27+
useResponsive<T extends useResponsiveKeys>(config?: useResponsiveBreakpoints<T>): { [s in (keyof typeof defaultConfig)]: boolean } | { [s in useResponsiveKeys<T>]: boolean }
28+
```
29+
30+
> ### Params
31+
>
32+
> - __config?__: _useResponsiveBreakpoints_
33+
custom breakpoint object.
34+
>
35+
36+
> ### Returns
37+
>
38+
> __breakpoint key__: returns the __size key__ of the __config__, parameter if passed otherwise __default config__, corresponding to the size of the window.
39+
> - _keyof useResponsiveBreakpoints_
40+
>

packages/react-tools/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
- [x] usePerformAction
4646
- [x] useDocumentVisibility
4747
- [x] useHover
48-
- [ ] useResponsive
48+
- [x] useResponsive
4949
- [ ] useClickOutside
5050
- [ ] useNetwork
5151
- [ ] useOnline

packages/react-tools/src/hooks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ export { useColorScheme } from './useColorScheme';
4343
export { useTitle } from './useTitle'
4444
export { useLazyRef } from './useLazyRef';
4545
export { useId } from './useId';
46-
export { useHover } from './useHover';
46+
export { useHover } from './useHover';
47+
export { useResponsive } from './useResponsive';
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { useCallback, useMemo, useRef } from "react";
2+
import { useSyncExternalStore } from ".";
3+
import { useResponsiveBreakpoints, useResponsiveKeys } from "../models/useResponsive.model";
4+
5+
const listeners = new Set<() => void>();
6+
7+
const handler = () => listeners.forEach(l => l());
8+
9+
const defaultConfig: useResponsiveBreakpoints<"xs" | "sm" | "md" | "lg" | "xl"> = {
10+
xs: {
11+
value: 576,
12+
condition: "<"
13+
},
14+
sm: {
15+
value: 576,
16+
condition: ">="
17+
},
18+
md: {
19+
value: 768,
20+
condition: ">="
21+
},
22+
lg: {
23+
value: 992,
24+
condition: ">="
25+
},
26+
xl: {
27+
value: 1200,
28+
condition: ">="
29+
}
30+
};
31+
32+
function calcResponsive<T extends useResponsiveKeys>(config?: useResponsiveBreakpoints<T>): { [s in keyof typeof defaultConfig]: boolean } | { [s in useResponsiveKeys<T>]: boolean } {
33+
const width = window.innerWidth;
34+
const conf = {};
35+
const target = config ?? defaultConfig;
36+
const keys = Object.keys(target) as ((keyof typeof conf)[]);
37+
for (const key of keys) {
38+
if (Reflect.get(target, key)) {
39+
const point = Reflect.get(target, key);
40+
const { value, condition } = typeof point === "number" ? { value: point, condition: ">" } : point;
41+
Reflect.set(conf, key, eval(`${width}${condition}${value}`) as boolean);
42+
}
43+
}
44+
return conf as typeof config extends undefined ? { [k in keyof typeof defaultConfig]: boolean } : { [k in useResponsiveKeys<T>]: boolean };
45+
}
46+
47+
/**
48+
* **`useResponsive`**: Hook for getting responsive window size. It receives an optional param __config__ to manually setting breakpoint keys. __config__ can have a keys subset and value can be a number or an object with _value_ and _condition_ properties. If _value_ is a number, the condition will be ">". By default Breakpoints are:
49+
*
50+
* - xs: { value: 576, condition: "<" }
51+
* - sm: { value: 576, condition: ">=" }
52+
* - md: { value: 768, condition: ">=" }
53+
* - lg: { value: 992, condition: ">=" }
54+
* - xl: { value: 1200, condition: ">=" }
55+
* @param {useResponsiveBreakpoints} [config] - custom breakpoint object.
56+
* @returns {keyof useResponsiveBreakpoints} breakpoint key - returns the __size key__ of the __config__, parameter if passed otherwise __default config__, corresponding to the size of the window.
57+
*/
58+
function useResponsive(config?: undefined): { [s in (keyof typeof defaultConfig)]: boolean };
59+
function useResponsive<T extends useResponsiveKeys>(config?: useResponsiveBreakpoints<T>): { [s in useResponsiveKeys<T>]: boolean };
60+
function useResponsive<T extends useResponsiveKeys>(config?: useResponsiveBreakpoints<T>): { [s in (keyof typeof defaultConfig)]: boolean } | { [s in useResponsiveKeys<T>]: boolean } {
61+
const configCache = useRef(() => {
62+
if (config === undefined) {
63+
return calcResponsive(defaultConfig);
64+
} else {
65+
return calcResponsive<T>(config);
66+
}
67+
});
68+
return useSyncExternalStore(
69+
useCallback(notif => {
70+
listeners.size === 0 && window.addEventListener('resize', handler);
71+
listeners.add(notif);
72+
return () => {
73+
listeners.delete(notif);
74+
listeners.size === 0 && window.removeEventListener("resize", handler);
75+
}
76+
}, []),
77+
useMemo(() => {
78+
let cache = configCache.current();
79+
return () => {
80+
const result = configCache.current();
81+
const keys = Object.keys(result);
82+
for (const key of keys) {
83+
if (Reflect.get(cache, key) !== Reflect.get(result, key)) {
84+
cache = result;
85+
break;
86+
}
87+
}
88+
return cache;
89+
}
90+
}, []),
91+
);
92+
}
93+
94+
export { useResponsive };

packages/react-tools/src/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ export type {
33
CompareFn,
44
UseScriptProps,
55
UseScriptStatus,
6-
UseScript
6+
UseScript,
7+
TextSelection,
8+
useResponsiveKeys,
9+
useResponsiveBreakpoints
710
} from './models'
811

912
export {
@@ -52,7 +55,8 @@ export {
5255
useTitle,
5356
useLazyRef,
5457
useId,
55-
useHover
58+
useHover,
59+
useResponsive
5660
} from './hooks'
5761

5862
export {

packages/react-tools/src/models/common.model.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ export type DependencyListTyped<T = unknown> = ReadonlyArray<T>;
77
export interface CompareFn<T = unknown> {
88
(oldDeps: DependencyListTyped<T>, newDeps: DependencyListTyped<T>): boolean
99
}
10+
11+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
12+
export type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>>
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export type { DependencyListTyped, CompareFn } from "./common.model";
22
export type { UseScriptProps, UseScript, UseScriptStatus } from './useScript.model';
3-
export type { TextSelection } from './useTextSelection.model';
3+
export type { TextSelection } from './useTextSelection.model';
4+
export type { useResponsiveKeys, useResponsiveBreakpoints } from './useResponsive.model';

0 commit comments

Comments
 (0)