Skip to content

Commit 534d74b

Browse files
committed
refactor(suite): update layout size handling
1 parent ef6cd83 commit 534d74b

File tree

22 files changed

+190
-166
lines changed

22 files changed

+190
-166
lines changed

packages/components/src/config/variables.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { icons } from '@suite-common/icons/src/icons';
22

3+
/**
4+
* @deprecated This key is deprecated. Please use `useLayoutSize` hook.
5+
*/
36
export const SCREEN_SIZE = {
47
UNAVAILABLE: '260px',
58
SM: '576px', // phones
@@ -16,6 +19,9 @@ const HELPER_SCREEN_SIZE = {
1619
XL: '1199px', // extra Large laptops/desktops
1720
};
1821

22+
/**
23+
* @deprecated This key is deprecated. Please use `useLayoutSize` hook.
24+
*/
1925
export const SCREEN_QUERY = {
2026
MOBILE: `@media (max-width: ${HELPER_SCREEN_SIZE.SM})`,
2127
ABOVE_MOBILE: `@media (min-width: ${SCREEN_SIZE.SM})`,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export const UPDATE_WINDOW_SIZE = '@window/update-window-size';
2-
31
export const UPDATE_WINDOW_VISIBILITY = '@window/update-window-visibility';
2+
3+
export const UPDATE_BREAKPOINTS = '@window/update-breakpoints';
Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
import { createAction } from '@reduxjs/toolkit';
22

3-
import { WINDOW } from './constants';
3+
import { BreakpointFlags } from '@trezor/theme';
44

5-
export const updateWindowSize = createAction(
6-
WINDOW.UPDATE_WINDOW_SIZE,
7-
(screenWidth: number, screenHeight: number) => ({
8-
payload: {
9-
screenWidth,
10-
screenHeight,
11-
},
12-
}),
13-
);
5+
import { WINDOW } from './constants';
146

157
export const updateWindowVisibility = createAction(
168
WINDOW.UPDATE_WINDOW_VISIBILITY,
@@ -19,6 +11,13 @@ export const updateWindowVisibility = createAction(
1911
}),
2012
);
2113

14+
export const updateBreakpoints = createAction(
15+
WINDOW.UPDATE_BREAKPOINTS,
16+
(breakpointFlags: Partial<BreakpointFlags>) => ({
17+
payload: breakpointFlags,
18+
}),
19+
);
20+
2221
export type WindowAction =
23-
| ReturnType<typeof updateWindowSize>
24-
| ReturnType<typeof updateWindowVisibility>;
22+
| ReturnType<typeof updateWindowVisibility>
23+
| ReturnType<typeof updateBreakpoints>;

packages/suite/src/components/suite/layouts/LoggedOutLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const LoggedOutLayout = ({ children }: LoggedOutLayout) => {
3030

3131
const theme = useSelector(state => state.suite.settings.theme);
3232
const { scrollRef } = useResetScrollOnUrl();
33-
const { isMobileLayout } = useLayoutSize();
33+
const { isBelowTablet } = useLayoutSize();
3434
const wrapperRef = useRef<HTMLDivElement>(null);
3535

3636
useClearAnchorHighlightOnClick(wrapperRef);
@@ -63,7 +63,7 @@ export const LoggedOutLayout = ({ children }: LoggedOutLayout) => {
6363
</Body>
6464
</LayoutContext.Provider>
6565

66-
{!isMobileLayout && <GuideButton />}
66+
{!isBelowTablet && <GuideButton />}
6767
</Modal.Provider>
6868
</PageWrapper>
6969
<GuideRouter />

packages/suite/src/components/suite/layouts/SuiteLayout/SuiteLayout.tsx

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
1+
import { ReactNode, useEffect, useRef, useState } from 'react';
22

33
import styled from 'styled-components';
44

55
import { ElevationContext, ElevationDown, ElevationUp, Modal, variables } from '@trezor/components';
6+
import { useDebounce } from '@trezor/react-utils';
67
import { spacingsPx } from '@trezor/theme';
78

89
import { GuideButton, GuideRouter } from 'src/components/guide';
@@ -101,29 +102,31 @@ type MainContentProps = {
101102

102103
export const MainContent = ({ children }: MainContentProps) => {
103104
const ref = useRef<HTMLDivElement>(null);
104-
const { setContentWidth, sidebarWidth } = useResponsiveContext();
105+
const { setContentWidth } = useResponsiveContext();
106+
const debounce = useDebounce();
105107

106-
const updateContainerWidth = useCallback(() => {
107-
if (ref.current) {
108-
const { current } = ref;
109-
const boundingRect = current?.getBoundingClientRect();
110-
const { width } = boundingRect;
111-
setContentWidth(width);
112-
}
113-
}, [setContentWidth]);
114108
useEffect(() => {
115-
updateContainerWidth();
109+
const resizeObserver = new ResizeObserver(entries => {
110+
if (entries[0]) {
111+
const newWidth = entries[0].contentRect.width;
112+
113+
debounce(() => {
114+
setContentWidth(newWidth);
115+
});
116+
}
117+
});
116118

117-
window.addEventListener('resize', updateContainerWidth);
118-
window.addEventListener('orientationchange', updateContainerWidth);
119-
window.addEventListener('load', updateContainerWidth);
119+
if (ref.current) {
120+
const boundingRect = ref.current.getBoundingClientRect();
121+
122+
setContentWidth(boundingRect.width);
123+
resizeObserver.observe(ref.current);
124+
}
120125

121126
return () => {
122-
window.removeEventListener('resize', updateContainerWidth);
123-
window.removeEventListener('orientationchange', updateContainerWidth);
124-
window.removeEventListener('load', updateContainerWidth);
127+
resizeObserver.disconnect();
125128
};
126-
}, [ref, setContentWidth, sidebarWidth, updateContainerWidth]);
129+
}, [ref, setContentWidth, debounce]);
127130

128131
return <MainContentContainer ref={ref}>{children}</MainContentContainer>;
129132
};
@@ -137,7 +140,7 @@ export const SuiteLayout = ({ children }: SuiteLayoutProps) => {
137140
const theme = useSelector(state => state.suite.settings.theme);
138141
const [{ title, layoutHeader }, setLayoutPayload] = useState<LayoutContextPayload>({});
139142

140-
const { isMobileLayout } = useLayoutSize();
143+
const { isBelowTablet } = useLayoutSize();
141144
const wrapperRef = useRef<HTMLDivElement>(null);
142145
const { scrollRef } = useResetScrollOnUrl();
143146
useClearAnchorHighlightOnClick(wrapperRef);
@@ -155,30 +158,30 @@ export const SuiteLayout = ({ children }: SuiteLayoutProps) => {
155158

156159
<ModalSwitcher />
157160

158-
{isMobileLayout && <CoinjoinBars />}
161+
{isBelowTablet && <CoinjoinBars />}
159162

160-
{isMobileLayout && <MobileMenu />}
163+
{isBelowTablet && <MobileMenu />}
161164

162165
<DiscoveryProgress />
163166

164167
<LayoutContext.Provider value={setLayoutPayload}>
165168
<Body data-testid="@suite-layout/body">
166169
<Columns>
167-
{!isMobileLayout && (
170+
{!isBelowTablet && (
168171
<ElevationDown>
169172
<Sidebar />
170173
</ElevationDown>
171174
)}
172175
<MainContent>
173-
{!isMobileLayout && <CoinjoinBars />}
176+
{!isBelowTablet && <CoinjoinBars />}
174177
<SuiteBanners />
175178
<AppWrapper
176179
data-testid="@app"
177180
ref={scrollRef}
178181
id={SCROLL_WRAPPER_ID}
179182
>
180183
<ElevationUp>
181-
{isMobileLayout && isAccountPage && (
184+
{isBelowTablet && isAccountPage && (
182185
<MobileAccountsMenu />
183186
)}
184187
{layoutHeader}
@@ -191,7 +194,7 @@ export const SuiteLayout = ({ children }: SuiteLayoutProps) => {
191194
</Body>
192195
</LayoutContext.Provider>
193196

194-
{!isMobileLayout && <GuideButton />}
197+
{!isBelowTablet && <GuideButton />}
195198
</Modal.Provider>
196199
</PageWrapper>
197200

packages/suite/src/components/suite/notifications/Notifications/NotificationGroup/NotificationList/NotificationView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const NotificationView = ({
2929
variant,
3030
notification: { seen, id },
3131
}: NotificationViewProps) => {
32-
const { isMobileLayout } = useLayoutSize();
32+
const { isBelowTablet } = useLayoutSize();
3333
const defaultIcon = icon ?? getNotificationIcon(variant);
3434
const colorVariant = seen ? 'tertiary' : 'default';
3535

@@ -50,7 +50,7 @@ export const NotificationView = ({
5050
</Paragraph>
5151
</Column>
5252
{action?.onClick &&
53-
(isMobileLayout ? (
53+
(isBelowTablet ? (
5454
<Icon name="caretRight" onClick={action.onClick} size={18} />
5555
) : (
5656
<Button variant="tertiary" size="tiny" onClick={action.onClick} minWidth={80}>

packages/suite/src/hooks/guide/useGuide.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { useMemo } from 'react';
2-
31
import { close, open } from 'src/actions/suite/guideActions';
42
import { useDispatch, useLayoutSize, useSelector } from 'src/hooks/suite';
53

@@ -11,12 +9,10 @@ export const useGuide = () => {
119
const isGuideOpen = useSelector(state => state.guide.open);
1210
const dispatch = useDispatch();
1311

14-
const { layoutSize } = useLayoutSize();
12+
const { isBelowLaptop } = useLayoutSize();
1513

16-
const isGuideOnTop = useMemo(
17-
() => ['NORMAL', 'SMALL', 'TINY'].includes(layoutSize),
18-
[layoutSize],
19-
);
14+
// The guide should be on top for smaller screens (below laptop size)
15+
const isGuideOnTop = isBelowLaptop;
2016

2117
const isModalOpen = usePreferredModal().type !== 'none';
2218

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { useSelector } from 'src/hooks/suite';
2-
import { selectWindowSize } from 'src/reducers/suite/windowReducer';
2+
import { selectBreakpointFlags } from 'src/reducers/suite/windowReducer';
33

4-
export const useLayoutSize = () => {
5-
const layoutSize = useSelector(selectWindowSize);
6-
const isMobileLayout = !['XLARGE', 'LARGE', 'NORMAL'].includes(layoutSize);
7-
8-
return { isMobileLayout, layoutSize };
9-
};
4+
// This hook provides information about breakpoints using media queries
5+
export const useLayoutSize = () => useSelector(selectBreakpointFlags);
Lines changed: 18 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,11 @@
11
import { produce } from 'immer';
22

3-
import * as variables from '@trezor/components/src/config/variables'; // can't import from index cause it would import all UI components
4-
import { getNumberFromPixelString } from '@trezor/utils';
3+
import { BreakpointFlags, initialBreakpointFlags } from '@trezor/theme';
54

65
import { WINDOW } from 'src/actions/suite/constants';
76
import { Action } from 'src/types/suite';
87

9-
const sizes = {
10-
UNAVAILABLE: getNumberFromPixelString(variables.SCREEN_SIZE.UNAVAILABLE),
11-
SMALL: getNumberFromPixelString(variables.SCREEN_SIZE.SM),
12-
MEDIUM: getNumberFromPixelString(variables.SCREEN_SIZE.MD),
13-
LARGE: getNumberFromPixelString(variables.SCREEN_SIZE.LG),
14-
XLARGE: getNumberFromPixelString(variables.SCREEN_SIZE.XL),
15-
};
16-
17-
const getSize = (screenWidth: number | null): State['size'] => {
18-
if (!screenWidth) {
19-
return 'NORMAL';
20-
}
21-
22-
if (screenWidth < sizes.UNAVAILABLE) {
23-
return 'UNAVAILABLE';
24-
}
25-
26-
if (screenWidth <= sizes.SMALL) {
27-
return 'TINY';
28-
}
29-
30-
if (screenWidth <= sizes.MEDIUM) {
31-
return 'SMALL';
32-
}
33-
34-
if (screenWidth <= sizes.LARGE) {
35-
return 'NORMAL';
36-
}
37-
38-
if (screenWidth <= sizes.XLARGE) {
39-
return 'LARGE';
40-
}
41-
42-
if (screenWidth > sizes.XLARGE) {
43-
return 'XLARGE';
44-
}
45-
46-
return 'NORMAL';
47-
};
48-
49-
export interface State {
50-
size: 'UNAVAILABLE' | 'TINY' | 'SMALL' | 'NORMAL' | 'LARGE' | 'XLARGE';
51-
screenWidth: number | null;
52-
screenHeight: number | null;
8+
export interface State extends BreakpointFlags {
539
isVisible: boolean;
5410
}
5511

@@ -58,19 +14,16 @@ interface WindowRootState {
5814
}
5915

6016
export const initialState: State = {
61-
size: 'NORMAL',
62-
screenWidth: null,
63-
screenHeight: null,
17+
...initialBreakpointFlags,
6418
isVisible: true,
6519
};
6620

6721
const windowReducer = (state: State = initialState, action: Action): State =>
6822
produce(state, draft => {
6923
switch (action.type) {
70-
case WINDOW.UPDATE_WINDOW_SIZE:
71-
draft.size = getSize(action.payload.screenWidth);
72-
draft.screenWidth = action.payload.screenWidth;
73-
draft.screenHeight = action.payload.screenHeight;
24+
case WINDOW.UPDATE_BREAKPOINTS:
25+
// Simply copy all properties from the payload
26+
Object.assign(draft, action.payload);
7427
break;
7528
case WINDOW.UPDATE_WINDOW_VISIBILITY:
7629
draft.isVisible = action.payload.isVisible;
@@ -81,6 +34,16 @@ const windowReducer = (state: State = initialState, action: Action): State =>
8134

8235
export default windowReducer;
8336

84-
export const selectWindowSize = (state: WindowRootState) => state.window.size;
85-
8637
export const selectIsWindowVisible = (state: WindowRootState) => state.window.isVisible;
38+
39+
// Selector for the breakpoint flags
40+
export const selectBreakpointFlags = (state: WindowRootState): BreakpointFlags => ({
41+
isBelowMobile: state.window.isBelowMobile,
42+
isBelowTablet: state.window.isBelowTablet,
43+
isBelowLaptop: state.window.isBelowLaptop,
44+
isBelowDesktop: state.window.isBelowDesktop,
45+
isAboveMobile: state.window.isAboveMobile,
46+
isAboveTablet: state.window.isAboveTablet,
47+
isAboveLaptop: state.window.isAboveLaptop,
48+
isAboveDesktop: state.window.isAboveDesktop,
49+
});

0 commit comments

Comments
 (0)