1 | import useMediaQuery from './useMediaQuery';
|
2 | import { useMemo } from 'react';
|
3 | /**
|
4 | * Create a responsive hook we a set of breakpoint names and widths.
|
5 | * You can use any valid css units as well as a numbers (for pixels).
|
6 | *
|
7 | * **NOTE:** The object key order is important! it's assumed to be in order from smallest to largest
|
8 | *
|
9 | * ```ts
|
10 | * const useBreakpoint = createBreakpointHook({
|
11 | * xs: 0,
|
12 | * sm: 576,
|
13 | * md: 768,
|
14 | * lg: 992,
|
15 | * xl: 1200,
|
16 | * })
|
17 | * ```
|
18 | *
|
19 | * **Watch out!** using string values will sometimes construct media queries using css `calc()` which
|
20 | * is NOT supported in media queries by all browsers at the moment. use numbers for
|
21 | * the widest range of browser support.
|
22 | *
|
23 | * @param breakpointValues A object hash of names to breakpoint dimensions
|
24 | */
|
25 | export function createBreakpointHook(breakpointValues) {
|
26 | const names = Object.keys(breakpointValues);
|
27 | function and(query, next) {
|
28 | if (query === next) {
|
29 | return next;
|
30 | }
|
31 | return query ? `${query} and ${next}` : next;
|
32 | }
|
33 | function getNext(breakpoint) {
|
34 | return names[Math.min(names.indexOf(breakpoint) + 1, names.length - 1)];
|
35 | }
|
36 | function getMaxQuery(breakpoint) {
|
37 | const next = getNext(breakpoint);
|
38 | let value = breakpointValues[next];
|
39 | if (typeof value === 'number') value = `${value - 0.2}px`;else value = `calc(${value} - 0.2px)`;
|
40 | return `(max-width: ${value})`;
|
41 | }
|
42 | function getMinQuery(breakpoint) {
|
43 | let value = breakpointValues[breakpoint];
|
44 | if (typeof value === 'number') {
|
45 | value = `${value}px`;
|
46 | }
|
47 | return `(min-width: ${value})`;
|
48 | }
|
49 |
|
50 | /**
|
51 | * Match a set of breakpoints
|
52 | *
|
53 | * ```tsx
|
54 | * const MidSizeOnly = () => {
|
55 | * const isMid = useBreakpoint({ lg: 'down', sm: 'up' });
|
56 | *
|
57 | * if (isMid) return <div>On a Reasonable sized Screen!</div>
|
58 | * return null;
|
59 | * }
|
60 | * ```
|
61 | * @param breakpointMap An object map of breakpoints and directions, queries are constructed using "and" to join
|
62 | * breakpoints together
|
63 | * @param window Optionally specify the target window to match against (useful when rendering into iframes)
|
64 | */
|
65 |
|
66 | /**
|
67 | * Match a single breakpoint exactly, up, or down.
|
68 | *
|
69 | * ```tsx
|
70 | * const PhoneOnly = () => {
|
71 | * const isSmall = useBreakpoint('sm', 'down');
|
72 | *
|
73 | * if (isSmall) return <div>On a Small Screen!</div>
|
74 | * return null;
|
75 | * }
|
76 | * ```
|
77 | *
|
78 | * @param breakpoint The breakpoint key
|
79 | * @param direction A direction 'up' for a max, 'down' for min, true to match only the breakpoint
|
80 | * @param window Optionally specify the target window to match against (useful when rendering into iframes)
|
81 | */
|
82 |
|
83 | function useBreakpoint(breakpointOrMap, direction, window) {
|
84 | let breakpointMap;
|
85 | if (typeof breakpointOrMap === 'object') {
|
86 | breakpointMap = breakpointOrMap;
|
87 | window = direction;
|
88 | direction = true;
|
89 | } else {
|
90 | direction = direction || true;
|
91 | breakpointMap = {
|
92 | [breakpointOrMap]: direction
|
93 | };
|
94 | }
|
95 | let query = useMemo(() => Object.entries(breakpointMap).reduce((query, [key, direction]) => {
|
96 | if (direction === 'up' || direction === true) {
|
97 | query = and(query, getMinQuery(key));
|
98 | }
|
99 | if (direction === 'down' || direction === true) {
|
100 | query = and(query, getMaxQuery(key));
|
101 | }
|
102 | return query;
|
103 | }, ''), [JSON.stringify(breakpointMap)]);
|
104 | return useMediaQuery(query, window);
|
105 | }
|
106 | return useBreakpoint;
|
107 | }
|
108 | const useBreakpoint = createBreakpointHook({
|
109 | xs: 0,
|
110 | sm: 576,
|
111 | md: 768,
|
112 | lg: 992,
|
113 | xl: 1200,
|
114 | xxl: 1400
|
115 | });
|
116 | export default useBreakpoint; |
\ | No newline at end of file |