/
useAppSizeMedia.ts
160 lines (141 loc) · 4.94 KB
/
useAppSizeMedia.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { useEffect, useState } from "react";
import {
DEFAULT_DESKTOP_LARGE_MIN_WIDTH,
DEFAULT_DESKTOP_MIN_WIDTH,
DEFAULT_PHONE_MAX_WIDTH,
DEFAULT_TABLET_MAX_WIDTH,
DEFAULT_TABLET_MIN_WIDTH,
QuerySize,
} from "./constants";
import { useOrientation } from "./useOrientation";
import { useWidthMediaQuery } from "./useWidthMediaQuery";
/**
* The current size for your application. This should work both server side and
* client side, but you will have much better results client side.
*/
export interface AppSize {
/**
* Boolean if currently matching a phone by comparing the max width of the
* device.
*/
isPhone: boolean;
/**
* Boolean if currently matching a tablet by comparing the max width of the
* device.
*/
isTablet: boolean;
/**
* Boolean if currently matching a desktop screen by comparing the max width
* of the device.
*/
isDesktop: boolean;
/**
* Boolean if currently matching a large desktop screen by comparing the max
* width of the device.
*/
isLargeDesktop: boolean;
/**
* Boolean if the app is considered to be in landscape mode. This will just
* verify that the window width is greater than the window height.
*
* NOTE: This might not be super accurate on Android devices since the soft
* keyboard will change the dimensions of the viewport when it appears. It is
* recommended to use the `useOrientation` hook as well if you'd like to get
* the current orientation type.
*/
isLandscape: boolean;
}
export const DEFAULT_APP_SIZE: AppSize = {
isPhone: false,
isTablet: false,
isDesktop: true,
isLargeDesktop: false,
isLandscape: true,
};
export interface AppSizeOptions {
/**
* The max width to use for phones. This one is a max width unline the others
* since everything from 0 to this value will be considered a phone.
*/
phoneMaxWidth?: QuerySize;
/**
* The min width for a tablet device.
*/
tabletMinWidth?: QuerySize;
/**
* The max width for a tablet device. This should normally be `1px` less than
* the `desktopMinWidth`, but it can be any value if needed. The tablet has a
* range of min to max so that you can have a bit more control.
*/
tabletMaxWidth?: QuerySize;
/**
* The min width for a desktop screen.
*/
desktopMinWidth?: QuerySize;
/**
* The min width for a large desktop screen.
*/
desktopLargeMinWidth?: QuerySize;
/**
* An optional default size to use for your app. This is really only helpful
* when trying to do server side rendering or initial page render since the
* default behavior is to check and update the size once mounted in the DOM.
*/
defaultSize?: AppSize;
}
/**
* This hook is used to determine the current application size based on the
* provided query sizes. When you want to render your app server side, you will
* need to provide a custom `defaultSize` that implements your logic to
* determine the type of device requesting a page. Once the app has been
* rendered in the DOM, this hook will attach event listeners to automatically
* update the app size when the page is resized.
*
* @internal
*/
export function useAppSizeMedia({
phoneMaxWidth = DEFAULT_PHONE_MAX_WIDTH,
tabletMinWidth = DEFAULT_TABLET_MIN_WIDTH,
tabletMaxWidth = DEFAULT_TABLET_MAX_WIDTH,
desktopMinWidth = DEFAULT_DESKTOP_MIN_WIDTH,
desktopLargeMinWidth = DEFAULT_DESKTOP_LARGE_MIN_WIDTH,
defaultSize = DEFAULT_APP_SIZE,
}: AppSizeOptions = {}): AppSize {
/* eslint-disable react-hooks/rules-of-hooks */
// disabled since this is conditionally applied for SSR
if (typeof window === "undefined") {
return defaultSize;
}
const matchesDesktop = useWidthMediaQuery({ min: desktopMinWidth });
const matchesLargeDesktop = useWidthMediaQuery({ min: desktopLargeMinWidth });
const matchesTablet = useWidthMediaQuery({
min: tabletMinWidth,
max: tabletMaxWidth,
});
const matchesPhone = useWidthMediaQuery({ max: phoneMaxWidth });
const isDesktop = matchesDesktop;
const isTablet = !matchesDesktop && matchesTablet;
const isPhone = !isTablet && !isDesktop && matchesPhone;
const isLandscape = useOrientation().includes("landscape");
const isLargeDesktop = matchesLargeDesktop;
const [appSize, setAppSize] = useState(defaultSize);
useEffect(() => {
if (
appSize.isPhone === isPhone &&
appSize.isTablet === isTablet &&
appSize.isDesktop === isDesktop &&
appSize.isLargeDesktop === isLargeDesktop &&
appSize.isLandscape === isLandscape
) {
return;
}
// for some reason, it's sometimes possible to fail every single matchMedia
// value when you are resizing the browser a lot. this is an "invalid" event
// so skip it. It normally happens between 760px-768px
if (!isPhone && !isTablet && !isDesktop && !isLargeDesktop) {
return;
}
setAppSize({ isPhone, isTablet, isDesktop, isLargeDesktop, isLandscape });
}, [isPhone, isTablet, isDesktop, isLargeDesktop, isLandscape, appSize]);
return appSize;
}